初期Unixのメモリー管理

あるプログラマーが復元されたUnixのソースコードを調べたことで、このOSの最初期のCプログラムが、現代の開発者になじみのあるmallocやfreeとは大きく異なるインターフェースでメモリーを管理していた様子が浮かび上がった。分析は、1973年に公開されたUnix第4版から、その後の版に至るアロケーターのコードをたどり、言語とシステムの発展に伴う変化を追っている。

第4版のコンパイラーのソースで、筆者は単純な線形アロケーターを見つけた。これは要求ごとにアライメントを調整し、残りの領域を確認して、必要に応じてsbrkでプロセスのヒープを拡張するものだった。プログラムは個々の割り当てを解放しなかった。メモリーはプログラムの存続中ずっと利用可能であり、短命なツールが終了時にすべてを破棄できる場合には、無駄の少ない設計だった。割り当てに失敗するとプロセスが終了するため、呼び出し側はnullが返されたかどうかを繰り返し確認する必要がなかった。

さらに目を引くのは、デニス・リッチー氏のソースディレクトリー内のファイルに、mallocとmfreeという名前でありながら、現代の同名・後継関数とはシグネチャーの異なる関数が含まれていたことだ。どちらも明示的なmap引数を受け取り、mfreeには解放する領域のサイズも渡されていた。カーネルのコードは、主記憶とスワップ領域という別々のプールを表すcoremapとswapmapという構造体を渡していた。

この方式では、各プールが何を表すかという知識を処理に埋め込まずに、一つの割り当てルーチンを異なる資源に適用できた。マップは空きブロックをアドレス順のリストで管理し、最初に適合する領域を使うファーストフィット方式を採用するとともに、隣接する空き領域を結合していた。用途別にメモリーを分割できる点で、このインターフェースはアリーナ方式の割り当てに似ている。ただし分析は、設計者が今日のソフトウェア分離の目標を追求していたと決めつけないよう注意を促す。ハードウェアとOSの資源制約が、この設計を採る直接的な理由になっていたからだ。

UnixとCの成熟とともに変わった考え方

ソースをたどると、第6版のユーティリティーにも関連する線形割り当ての手法が見つかった。初期のdiffは、割り当ての際に共有領域のポインターを進め、後から元に戻すことができた。一時的なオブジェクトの集まりが同じ存続期間を持つ場合には、効率的な方式である。第7版になると、個々のプログラムに、割り当てと型のサイズ計算を組み合わせるマクロが導入されており、後のCやC++のコードで使われる便利な仕組みを先取りしていた。

これらの例から、初期Unixのルーチンから標準化されたCライブラリーまで、一本の直線的な系譜が確立されるわけではない。カーネルの資源アロケーターとユーザー空間のヒープアロケーターは異なる問題を解決しており、名称やインターフェースも版によって変化した。ただし、明示的な領域、存続期間をまとめて管理する仕組み、サイズを考慮した解放は、最近の発明ではないことを示している。開発者は今も、アリーナ、プール、アプリケーション専用のアロケーターで、これらの考え方の変形を利用している。

今回の再検証を可能にしたのは、システムの公開から数十年後に見つかったUnix第4版のテープなど、歴史的なソース資料の保存だった。コードを読むことは、懐古以上の意味を持つ。厳しいハードウェア制約の下で行われた設計上の選択を明らかにし、単純で目的に特化した割り当てが、汎用的なヒープインターフェースの代わりになり得たことを示すからだ。

現代のプログラマーにとっての教訓は、初期の一つの設計で標準のmallocを置き換えるべきだということではない。割り当てインターフェースには、所有権、存続期間、失敗についての前提が織り込まれているという点にある。初期Unixはそれらの前提を明示しており、その考え方は、予測可能な資源上限や関連するオブジェクトの迅速な一括破棄が必要なソフトウェアに、今も通じるものだ。