RTOSのメモリプール設計
RTOS(Real-Time Operating System:リアルタイムOS)ではリアルタイム性と安定性が求められ、メモリ管理の設計がシステム全体の品質を左右します。特にmalloc関数などの動的メモリ確保は便利ですが、メモリフラグメンテーションや実行時間のばらつき、解放忘れによるメモリリークに注意が必要です。この課題を解決する手段の1つであるメモリプールについて、基本的な仕組みや設計時のポイントを解説します。
メモリプールとは
メモリプールとは、あらかじめ一定量のメモリ領域を確保しておき、そこからブロック単位で割り当て・解放することでメモリを管理する方法です。頻繁にメモリ確保が必要なプログラムの場合、処理のたびにOSに割り当てを依頼していると時間がかかります。あらかじめ確保したメモリプールからブロック単位で確保・解放することで、高速な処理が期待できます。
メモリプールは、ヒープ領域から必要なメモリを処理ごとに割り当てる一般的な「動的メモリ確保(malloc関数など)」と比較されます。動的メモリ確保では空き領域を検索する時間が予測できず、メモリ確保に要する時間が許容範囲を超えるおそれがあるためです。多くのRTOSでは、メモリブロックを確保できない場合の待ち時間の上限を指定できるため、安定的・効率的なメモリ割り当てを実現しやすくなります(タイムアウト付き取得の可否はRTOSのAPIに依存します)。
固定長メモリプール
固定長メモリプールは、メモリサイズが固定されたブロックを管理する機能です。メモリブロックのサイズを管理する必要がなく、可変長メモリプールに比べてわずかに処理が速くなるため、データ長があらかじめ決まっているプログラムによく用いられます。ネットワークの受信パケットや、センサなどの入出力が固定されたデバイスのデータがその一例です。
サイズ違いのメモリブロックが必要な場合は、サイズごとに複数のメモリプールを用意して対応できます。用途に応じた固定長メモリプールをあらかじめ準備しておくことで、安定した処理につながります。
可変長メモリプール
RTOSによっては可変長メモリプール(または可変長相当の動的な割り当て)を提供します。可変長メモリプールは、サイズが任意のメモリブロックを管理する機能です。メモリプールの空き容量が足りなくなると、待ち状態になるのが一般的です。RTOSによっては複数のメモリプールを用意し、空き容量が足りないときに即時にエラーを返すか、待ち状態にするかを選択できるものもあります。
待ち状態になった処理は、メモリブロックの待ち行列に並びます。待ち行列の先頭が大きな領域を要求していると、後続が進みにくくなる場合があります(待ち行列の規則はRTOSに依存します)。
メモリプールの設計
限られたメモリを可能な限り速く確保・解放するために、メモリプールの設計は重要です。扱うメモリサイズが固定できる場合は、適切な長さの固定長メモリプールを用意する方法が適しています。可変長メモリプールを使用するときは、メモリを確保できない場合の処理を定めておくことで、メモリ確保の待ち行列に長時間並ぶ事態を防げます。
利用するシーンや必要な処理に応じて固定長と可変長のメモリプールを組み合わせることで、メモリ確保と解放に必要な時間を短縮できます。
メモリフラグメンテーション(断片化)の防止
メモリプールを用いるとメモリ管理が単純化され、メモリフラグメンテーションによるメモリ枯渇のリスクを回避できます。また、確保・解放にかかる時間が一定となり、リアルタイム性を保証しやすくなります。ただし、メモリプールでも解放(返却)忘れによるメモリリークが発生するおそれがあるため、所有権の設計・監視といった対策は別途必要です。
メモリプールも、確保した領域からブロックを割り当て、使用後に返却するという点ではmalloc関数と同様です。ただし、malloc関数とfree関数を繰り返す方式と比べて、メモリリークのリスクは低減できます。RTOSによってはカーネルが確保したメモリを解放する仕組みを持っていますが、アプリケーションが確保したメモリまでは自動解放されないことに注意が必要です。
RTOSでの実装
RTOSに実装する際に注意したい点を列挙します。
メモリプールの仕組み(RTOS)
RTOSでのメモリプールの役割
RTOSの実装でメモリプールはタスク間通信やバッファ管理に利用されます。たとえば送信するデータに相当する量のメモリをメモリプールから取得し、処理後に返却するようなケースです。メモリの動的確保を使わず、メモリブロック単位で確保できるため、メモリの効率的な管理と迅速で安定的な処理につなげられます。
malloc関数との併用
malloc関数はリスクがあるため注意して使用すべきですが、通信スタックなどデータサイズが変動する処理では避けられない場面もあります。malloc関数を利用する場面を限定したうえで、原則としてメモリプールを使用する実装のほうがリスク回避に有効です。
さらにフェールセーフやフェールソフトの考え方を取り入れることが重要です。たとえばメモリプールの空きブロックが枯渇した場合に、安全な状態へ移行する方法を決めておくことは、フェールセーフの考え方に基づきます。また、必要なメモリブロックが取得できないときに重要度の高い機能へ重点的にメモリを割り当て、縮退運転で機能を提供することは、フェールソフトの一例です。


