P/ECEでのheap管理は、主に heapman.c で行われています。 ここでは、heapman.cのv1.03(2001.11.20)版に基づいて説明します。
P/ECEのheapは1方向のリンクリストになっています。各リンクの ノードはHEAPMEM構造体になっていて、ノードとノードの隙間が 記憶領域として使われます。
heaptop
|
| chain chain
| +----------------+ +---------------+
| | | | |
V | V | V
+-------+--------------+-------+-------------+-------+----
|HEAPMEM| |HEAPMEM| |HEAPMEM| ...
+-------+--------------+-------+-------------+-------+----
↑記憶領域その1 ↑記憶領域その2
HEAPMEM構造体の定義はpiece.hにあります。
HEAPMEM +-----+ |mark |unsigned short +-----+ |owner|unsigned short +-----+ |chain|(HEAPMEM *) | | | | +-----+
HEAPMEM構造体は3つのメンバーからなります。
メモリがheapとして使われていることを識別するためのマークです。 heapとして使われる場合、HEAPMEMMK(0xa431)が設定されます。
ノードの所有者を表します。 使用されている場合、通常 HMO_DEFAULT_USER(0x1)、 システムが利用している場合はHMO_SYSTEM(0xffff)、 使用されていない場合は0になります。
次のHEAPMEMへのリンク(ポインタ)です。
これらがchainのポインタによって数珠つなぎにリンクされています。
個々のHEAPMEMノードが確保するメモリの大きさは、mark->chain - mark になります。 そのうち sizeof(HEAPMEM)はheap管理用に使用されるので、実際に 記憶領域として使われるのは「mark->chain - mark - sizeof(HEAPMEM)」 になります。
エラー(APIErr)を吐き、heapが壊れている旨を通知します。 heapが壊れていた場合に呼び出して使います。
HEAPMEMのsetterです。phmが指すheapに対し、 markにはHEAPMEMKを、そしてownerとchainには、 それぞれ与えられた引数を代入します。
連結するheap領域をつなぎ合わせて一つにします。
heap領域の合わせ方ですが、まず、与えられたHEAPMEMへのポインタphmをphm0 として覚えておきます。 これ以降、
という役割分担になります。
phmを動かしながら順にHEAPMEMのノードをなぞっていきます。 現在注目しているheapが未使用であれば(phm->ownerが0なら)、 phm->chainをその後に続くHEAPMEMへのポインタにつけ変える、という 処理を繰返します。
phm0
|
|
V
+-------+- -+-------+- -+-------+- -+-------+-
|HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ...
+-------+- -+-------+- -+-------+- -+-------+-
A
|
phm
phm0 phm0->chain
| +------+
| | |
V | V
+-------+- -+-------+- -+-------+- -+-------+-
|HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ...
+-------+- -+-------+- -+-------+- -+-------+-
A
|
phm
phm0 phm0->chain
| +--------------------+
| | |
V | V
+-------+- -+-------+- -+-------+- -+-------+-
|HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ...
+-------+- -+-------+- -+-------+- -+-------+-
A
|
phm
phm0 phm0->chain
| +----------------------------------+
| | |
V | V
+-------+- -+-------+- -+-------+- -+-------+-
|HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ...
+-------+- -+-------+- -+-------+- -+-------+-
A
|
phm
最終的に、使用中のheap領域(phm->owner が0でないheap領域)に 辿りつくまで、phm0->chainの指すアドレスは更新し続けます。
さて、こうして連結できるheap領域が求まりました。とはいえ、 phm0からphmまでの連結されるheap領域について、 その内容を0x0で初期化する、といったようなことは特に行いません。 単にphm->chainポインタの内容が書き換わるだけです。 ですが、phmの指すアドレスからphm->chainの指すアドレスまでが 一つのheap領域として扱われるため、 phm0からphmまでが一つの大きなheap領域として確保できたことになる わけです。
phm0 phm0->chain
| +----------------------------------+
| | |
V | V
+-------+- -+-------+- -+-------+- -+-------+-
|HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ...
+-------+- -+-------+- -+-------+- -+-------+-
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
chainをつけ替えると、複数のheap領域(とその隙間)が、
↓
phm0 phm0->chain
| +----------------------------------+
| | |
V | V
+-------+--------------- ---------------+-------+-
|HEAPMEM| ... |HEAPMEM| ...
+-------+--------------- ---------------+-------+-
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
一つの大きなheap領域を表していることになる。
新しくheap領域を割り当てたり(pceHeapAlloc)、 heap領域の大きさを変更する(pceHeapRealloc)時に 呼び出される内部関数です。
sizeは割り当てたい大きさ、size1は割り当てるのに使おうとしている heap領域の大きさを表します。
phm1
|
| |<--size1--------------->|
| | |
| |<--size -->| |
| | | |
V | | |
+-------+------------------------+-------+--
|HEAPMEM| |HEAPMEM| ...
+-------+------------------------+-------+--
| A
| |
+--------------------------+
phm1->chain
↓
phm1
|
| |<--size1--------------->|
| | |
| |<--size -->| |
| | | |
V | | |
+-------+-----------+-------+----+-------+--
|HEAPMEM| |HEAPMEM| |HEAPMEM| ...
+-------+-----------+-------+----+-------+--
| A | A
| | | |
+-------------+ +-----+
phm1->chain phm1->chain->chain (== phm2)
対象となるheap領域のうち、sizeの大きさの分が新しいheap領域として、 また残りの size1-size の大きさの分が未使用のheap領域として、 SetHEAPMEM関数によって割り当てられます。前者のownerはHMO_DEFAULT_USERに、 後者のownerは0になります。そして、未使用領域はCombinedFreeHEAPMEM 関数によって、後続の未使用領域との連結が行われます。
なお、sizeが変わらない場合には、単にownerが替わるだけです。
大きさsize0のヒープ領域を割り当てます。成功すればそのヒープ領域への ポインタが、失敗すればNULLが返されます。
割りあての戦略は、「best-fit方式」とよばれるものです。これは、 heapのリンクリストを最初から最後までなぞっていき、そのなかで、 割り当てたいサイズよりは大きいもののうち、一番小さいものを 使います。
割り当てる場所が決まれば、そこから必要な分だけ heap領域を切り出します。 空いている領域が、使いたい記憶領域の大きさよりも大きければ、 その領域のうち必要な分だけをheapとして切り出します。
+-------+--------------+-------+--
|HEAPMEM| |HEAPMEM| ...
+-------+--------------+-------+--
↑空き記憶領域
phmは現在注目しているheap領域のアドレスを指すポインタとして、 phm1は割り当て候補のheap領域のアドレスを指すポインタとして 使われています。このうちphm1は、割り当て候補が見つかった場合、 最後にHEAPMEMの大きさの分だけずらされ、 またHEAPMEM構造体を指すポインタとしてキャストされて、返り値となります。
phm1
|
V
+-------+--------------+-------+--
|HEAPMEM| |HEAPMEM| ...
+-------+--------------+-------+--
↓phm1がsizeof(HEAPMEM)分ずらされる
phm1
|
V
+-------+--------------+-------+--
|HEAPMEM| |HEAPMEM| ...
+-------+--------------+-------+--
なお、best-fit方式は、理論的にはメモリの断片化が起きやすい、という 意見もあります(クヌース『The Art of Programming 基本算法/情報構造』228ページ)が、 実測ではそれほどでもないそうです。 (参考: <URL:http://www.memorymanagement.org/glossary/b.html#best.fit> )
割り当てられているheap領域を解放します。
単純に未使用領域とするだけなら、phm->ownerに0を代入すればOKです。 が、それでは間違ったポインタが渡された場合にデータを壊して しまいますし、解放された領域も分割されたままです。
そこで、heaptopから順にheapを辿っていき、そのような領域が 見つからない場合はエラーを返すようにしています。また、 見つかった場合には、ownerを0にしてから、CombineFreeHEAPMEMを 呼んで、連結を試みます。
なお、ここでの連結は、解放された領域の一つ前の領域から行います。 これは、ちょうど解放する際、その一つ前の領域へのポインタを 保存しているためです。下の図で言うと、mempが与えられ、 mempに対応するHEAPMEM構造体を指すポインタphmのheap領域を 解放する場合、phmの直前が未使用であれば、 phmbが指す領域から合わせて解放されるのです。
XXX phmb phm memp
| | | |
V V V V
+-------+- -+-------+- -+-------+- -+-------+-
|HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ... |HEAPMEM| ...
+-------+- -+-------+- -+-------+- -+-------+-
↑ ↑ ↑
未使用 未使用 未使用
割り当てられたheap領域の大きさを変更します。 この変更は、再配置は行いません。同じ場所で拡大・縮小を 試み、拡大できない場合にはエラーを返します。
ここでも、与えられたmempが実際にheap領域を差しているかどうか、 一度heap領域のリンクリストを辿って確認します。
また、sizeが4の倍数になるように調整を行います。 sizeを+3してから切り捨てを行うので、 実際には余りは切り上げられています。
これらが済むと、いよいよ領域の変更です。 縮小する場合は、単にAllocHEAPMEMを呼ぶだけです。 一方、拡大する場合は、領域の後ろに空きがあるか、 つまり直後のheapが未使用で、かつ大きさが十分にあるか どうかを調べます。 この2つの条件が満たされない場合にはエラー番号を返します。 エラー番号は、
となっています。
十分な空きがあった場合、heapを拡大します。 AllocHEAPMEMで領域を用意し、SetHEAPMEMで初期化します。
なお、後ろの空きが複数つながってる場合でも、直後の一つしか 見ません。ここはCombinedFreeHEAPMEMを呼んで連結しても よかったのではないかと思います。
未使用のheap領域のうち、最大のものの大きさを返します。
この関数は単純です。 リンクリストをなぞっていき、大きさを調べていきます。 そして、大きいものの値を覚えておき、終端にたどりついた時に その時点で最大のものを返す、という流れです。
mempをheapの先頭にして、heapを初期化します。 mempが0でなかった場合には、そこからsramの最後まで全体を heapとして扱うようにしています。
heaptop sysytem_info.sram_end | | | | V V +-------+---------------------------------------+-------+ |HEAPMEM| |HEAPMEM| +-------+---------------------------------------+-------+ ↑ ↑ ownerなし(未使用)で初期化 ownerはHMO_SYSTEMに設定
__PCEKN__ がセットされている場合、各公開APIをPIECEカーネル サービステーブルに設定します。
公開
APIのインターフェースを一部訂正。すみません。 さらに、スタイルシートを変更。
Copyright 2002 TAKAHASHI 'Maki' Masayoshi All rights reserved.
この文書の内容に関する一切の保証はありません。この文書の使用により発生した いかなる損害についても、筆者は一切責任を負いません。あしからずご了承ください。
この文書の転載は、この文書の筆者と、この文書が無保証であることが明らかで ある限りにおいて、変更の有無に関わらず、どのような形で再配布されても 構いません。