目次 このページのソースコードを表示
公開日:
更新日:

OSはタスクごとにメモリを動的に割り当てる必要があります(タスクが保有するメモリに関することはのちに説明します). というのも, これらのタスクはアプリケーション実行中に生成, 削除される可能性があるからです.

今回では, このメモリ管理をOSが行うことにします. OSがメモリ管理を行うことで, OS動作の理解がしやすくなるからです.

このページでは, OSによるメモリ管理をどのように実装するのか説明します.

メモリデータ構造

ヒープ領域

メモリ管理においてまず管理下にあるメモリ領域(ヒープ領域)を確保します. ヒープ領域の確保は, 通常の配列と同じように確保します.

            // Allocate the memory for the heap.
            static unsigned char heap[CONFIG_TOTAL_HEAP_SIZE];

マクロCONFIG_TOTAL_HEAP_SIZEはOSの設定ファイルで定義します.

確保したヒープ領域内でメモリの確保, 解放などといった管理が行われます.

ブロック

ブロックとは, ヒープ領域上にあるメモリの塊です. メモリの確保, 解放が行われていない初めの段階では, ヒープ領域すべてを一つのブロックが覆いますが, 確保, 解放を繰り返すごとにこのブロックが分裂, 融合を繰り返します.

ヒープ領域とブロックの関係は以下のとおりです.

ヒープ領域とブロックの関係図
ヒープ領域とブロックの関係図

上の図にあるアライメントについてですが, アライメントとはメモリの区切りにデータをそろえることを言います. メモリにはデータを入れる箱のようなものがあるのですが, この箱の大きさはメモリの種類によって様々です. 32bit メモリの場合は, 箱の大きさは 4byte, 8bit メモリの場合は, 箱の大きさは 1byteとなります. これら箱の境界を区切りと呼びます. もし, 区切りにデータをそろえない場合, そろえてる場合と比べてデータの読み書きに時間がかかります. というのも, 複数の箱にまたがったデータを取り出すために, 複数のメモリの読み出し, シフト演算, 論理和という処理を行う必要があるからです. データをそろえていると, メモリの読み出しのみで済みます.

アライメントの方法として例を挙げます. 今, データの始まりが15番地, メモリの箱の大きさが4とします. この時, アライメントされたデータの始まりは16番地となります. データの始まりが12番地の場合は, アライメントされても変わらず12番地となります. メモリの箱の大きさで割り切れるからです.

ヒープ領域をアライメントするプログラムは以下のとおりです.

            uint8_t *alignedHeap;
            size_t address;
            size_t totalHeapSize = CONFIG_TOTAL_HEAP_SIZE;
            
            // Ensure the heap starts on a correctly aligned boundry.
            address = (size_t)heap;
            
            // ヒープ領域のアドレスがアライメントされていないとき
            // PORT_BYTE_ALIGNMENT_MASK = PORT_BYTE_ALIGNMENT - 1
            if ((address & PORT_BYTE_ALIGNMENT_MASK) != 0)
            {
                // アライメントを行う
                address += (PORT_BYTE_ALIGNMENT - 1);
                address &= ~((size_t)PORT_BYTE_ALIGNMENT_MASK);
                totalHeapSize -= address - (size_t)heap;
            }
            alignedHeap = (uint8_t *)address;
ビット演算によるあまり計算

ビット演算であまりの計算を行う際, 次のようにします. ただし, 割る数は2の乗数でなければなりません.

                // 割る数をBとする. 割られる数をAとする.
                あまり = A & (B - 1)

補足ですが, 割られる数から余りを引いた数Cの計算は(B - 1)をビット反転しANDをとることで求めることができます.

                // 割る数をBとする. 割られる数をAとする.
                C = A & ~(B - 1)

ブロックの内部構造は次のようになっています.

ブロックの内部構造
ブロックの内部構造

ブロックリンクについては以下で詳しく述べます. フリースペースとは, アプリケーションが自由に使えるメモリ領域です. フリースペースの始まりをアライメントするようにします.

メモリの確保が行われた際, 確保された領域と確保されなかった領域という二つのブロックが生成されます. つまり, 一つの未使用ブロックが使用ブロックと未使用ブロックに分裂します.

確保時のブロック分裂例
確保時のブロック分裂例

メモリの開放が行われた際, 両端に未使用のブロックがあるときは一つのブロックへ融合しますが, 使用ブロックの場合は融合しません.

解放時のブロック融合可能例
解放時のブロック融合可能例
解放時のブロック融合不可能例
解放時のブロック融合不可能例

ブロックリンク

各ブロックの先頭にブロックリンクというヘッダ情報があります. 情報には, 次の空きブロックへの参照と空き領域が含まれます.

ブロックリンクは, 構造体で実現します. ブロックリンクのコードは以下のとおりです.

            //
            // 説明:
            //  空きブロックリストの要素
            //  空いている次のメモリのアドレスを持つ. また, そのサイズも保存する.
            //  メモリの構造は次のようになっている
            //  
            //      start - freeBlock0 - freeBlock1 - end
            //        |
            //      struct(BlockLink) - AlignmentMemory - FreeSpace
            //
            //      AlignmentMemory:
            //          BlockLinkとfreeSpaceの間にある
            //          ; メモリをアライメントするために必要
            // 
            //      FreeSpace:
            //          アプリケーションが自由に使えるメモリ空間
            //    
            //
            //  Define the linked list structure.
            //  This is used to link free blocks in order of their size.
            struct BlockLink
            {
                //次の空きブロックのポインタ
                //The next free block in the list
                struct BlockLink *nextFreeBlock;
            
                // These size of the free block.
                size_t blockSize;
            };

ブロックリンク内にある次の空きブロックへの参照によって, 現在使用できるメモリ領域を把握できるようにします. 空きブロックの参照概念図は以下のとおりです.

空きブロック参照概念図
空きブロック参照概念図

上図のように, startからそれぞれの空きブロックにリンクを張っていきます. リンクの最後にendを置くことで, リンクの最後であることを検知できるようにします. これは, 連結リスト構造の一種です.

ブロックリンクには空き容量の情報も含まれます. この情報によってブロックが保有するフリースペースの量がわかります.

「https://contentsviewer.work/Master/Arduino/ArduinOS/MemoryManagement」から取得