学校では教えてくれないこと

フラグメンテーション

2011.9

やぁみんな、ビリーだよ。

前回の講義で、「メモリのフラグメンテーションを回避するために、OSや標準ライブラリのメモリ管理機能を使用せず、自前でメモリ管理を行う場合がある」という話をした。

フラグメンテーションを回避することは、組み込み機器、特に長期間にわたって連続で動かす機器にとっては、注意しておかなければならないことだ。

まず、「フラグメンテーションとは何か」について説明しよう。

メモリのフラグメンテーションとは

「フラグメンテーション」には、断片化、破片といった意味がある。

プログラムの世界で言う「メモリのフラグメンテーション」とは、メモリの空き領域はあるのだが、小さな領域が飛び飛びに存在する、虫食いのような状態になってしまい、実際には使用できない状態になってしまったことを指す。malloc/free といったメモリの割り当て、解放を繰り返していると発生しやすい。具体的に見てみよう。

以下のように、malloc/free を行う複数のプログラムが並行して動作する場合を考える。

プログラムの動作の説明図1

プログラムの動作の説明図2

このように、malloc/free を行う処理が複数あり、それぞれ使用するサイズが異なる場合、そのサイズやタイミングによってメモリが虫食い状態になってしまう。

mallocで割り当てるメモリは連続した領域である必要があるため、最後の状態では空きエリアの合計は600バイトあるわけだが、実際には400バイトまでしかmallocできないということになる。

つまり、ヒープ領域の空き容量の合計は十分あるのに、連続した空き領域がないためmallocできなくなる、という状態になってしまう。

メモリのフラグメンテーションを引き起こす代表的な理由としては、malloc/freeでメモリの取得・開放を行うことだが、これ以外にもOSの資源(セマフォやメッセージキュー等)を生成、削除する場合も、内部的にmalloc/freeを行っている場合があるので、注意が必要だ。

では、フラグメンテーション化を防ぐには、どのようにすればよいだろう?

フラグメンテーションを防ぐには

フラグメンテーションを防ぐための最も効果的な手段は、malloc/freeによるメモリの割り当て・開放やOS資源の生成・開放といった処理を動的に行わないことだ。

ただしこれだと、前回の講義で説明した、malloc/freeを利用する「必要な時だけメモリを使用し、限られたメモリを効率的に使用する」利点が生かせないことになる。

この矛盾を解決するいい方法は無いだろうか?

デバイスドライバなどでは、ハードウェアの仕様上データのサイズが決まっていることが多い。例えば USB のデータ転送では、コントローラの仕様でデータ転送のサイズが64バイトや512バイトなどに決まっていることが多い。このような場合にはメモリ管理を自前で行ってしまうことである程度問題を解決できる。

またデバイスドライバでは転送開始時にメモリを確保し、転送が終わればメモリは不要になるので、その都度malloc/freeを行わないようにすればメモリのフラグメンテーションを防ぐのにかなり効果的だ。

例えば、あるUSBコントローラではデータを512バイトの整数倍の単位で扱うとしよう。USBには複数の機器が接続できるので、全部でどれだけのメモリが必要になるかをあらかじめ知ることはできない(できたとしても、想定しうる最大値でメモリを確保しておくのは非効率であることは、前回の講義で述べたとおり)。

そこで、初期状態として 512バイト×10ブロック分、5120バイトのメモリ領域を確保しておくことにしよう(この5120バイトはヒープ領域からmallocで確保することにする)。

メモリ領域

USBドライバは、必要なデータ転送にはすべてこのメモリをデータバッファとして使用するようにしておく。

たとえば一度のデータ転送に2048バイト(4ブロック)使うデバイスが3つと、1024バイト(2ブロック)使うデバイスが2つ接続されていたとすれば、この10個のブロックから連続する2または4ブロックを転送時に割り当て、終了時に開放するようなメモリ管理を行えば、OSのヒープ領域には全く影響を与えずに、ドライバ内でメモリ管理が完結することになる。

もっともこの想定の場合、すべてのデバイスが同時に転送をおこなおうとすると、空きブロックが足りないことになってしまう。その場合は、あるデバイスが転送を完了するまで待ってメモリが開くのを待ち、次の転送を始める、という手もあるが、追加で OSヒープ領域からメモリを確保してしまうこともできる。

この場合も、足りなくなった分を1ブロックずつ追加で確保するよりも、まとまった単位で確保してしまうほうがフラグメンテーションが発生しにくい。1ブロックを10回に分けて追加でmallocするよりは、10ブロック分をまとめて1回でmallocしてしまうというわけだ。

メモリ領域

一度に追加するメモリサイズが大きすぎると、無駄に使われないメモリができてしまう。一度に追加するメモリサイズが小さすぎると、すぐにメモリが足りなくなり何度もmallocで追加確保しなければならなくなるかもしれない(フラグメント化が進む)。

このあたりのバランスのとり方も、プログラマーの腕の見せ所だ。

ページのトップへ