第11回 メモリ管理
オペレーティングシステムとメモリ管理
オペレーティングシステム(OS)とは,コンピュータ上の資源管理を行う基本ソフトウェアのことである.ここでいう資源(resource)とは,コンピュータのハードウェア全般であり,CPU,メモリ,I/O(入出力装置全般,キーボード・ディスプレイ・ハードディスクなど)のことをいう.
ここでは,特にメモリ管理について触れる.メモリの容量は有限であり,複数のプログラムを同時に実行する時に適切に管理する必要がある.PC向けなどのオペレーティングシステムでは,仮想記憶(Virtual Memory)が実装されている.これは,メモリ(主記憶と呼ぶ)が足りなくなったときなど,ハードディスクなどの外部記憶装置(二次記憶装置と呼ぶ)に一旦主記憶の内容を退避させ,主記憶を空にしてプログラムを続行し,必要に応じて二次記憶に対比しておいた内容を再度主記憶に読み込むことを行う.主記憶の退避のために二次記憶装置に確保された領域をスワップ領域と呼ぶ.通常スワップ領域は主記憶の数倍の容量を割り当てておく.このメモリ管理機構により,主記憶の容量以上の仮想メモリ空間を利用できるようになる.
オペレーティングシステムは,プログラムを実行する際に論理アドレスを使用させて実行する.論理アドレスは仮想のメモリ空間上でのアドレスであり,すべてのプログラムが独自のアドレス空間を割り当てられる.これにより,同時に実行されているプログラムAとプログラムBであっても,同じくメモリアドレス0番地から使用することができ,重複するメモリアドレスをそれぞれ利用できるようになっている.オペレーティングシステムは,CPUに搭載されているMMU(メモリ管理装置)により論理アドレスを実際の実メモリ上の物理アドレスに相互変換を行い,論理アドレスの中で今現在実行に必要なアドレス範囲をページ(通常4Kbyte程度のメモリ範囲)単位でスワップ領域から主記憶に読み込み実行を行う.主記憶に読み込まれるページとページは,論理アドレス上では連続したページであっても,連続した物理アドレスに順序どおり格納される必要はない.メモリが細切れになっていても問題はないようになっている.
MMUの中身を簡単に紹介すると,MMUにはページテーブルと呼ぶ論理アドレスと物理アドレスの変換表(対応表)を持つ.この表には,基本的に論理アドレスを分割した仮想ページごとに,対応する物理アドレスが記載される.実メモリに割り当てられていないページの場合,対応する実メモリの欄は空となる.実行中のプログラムが,まだ実メモリが割り当てられていないアドレスにアクセスしようとすると,「ページフォルト」割り込みが発生する.ページフォルトが起こると,オペレーティングシステムは実メモリ上で空いてある実ページを探して割り当て,スワップ領域から必要な論理ページを実ページにコピーし,MMUのページテーブルにその実ページアドレスを書き込む.ページフォルトの処理が終了したところで,元のプログラムの機械語の命令を再度実行し直す.これにより,プログラム側は,実メモリのことは何も意識せずに,ただ論理アドレスだけを使用して実行することができる.
こうしたCPUレベルでのメモリ管理機構ではなく,オペレーティングシステムのシステムソフトウェア的に提供されている仕組みとして,動的ライブラリ,DLL(Dynamic Link Libraty)がある.一般のアプリケーションプログラムを作成する場合は,ディスプレイに文字を表示したり,ファイルにデータを書き込む機能など,OSが提供する機能をライブラリとして使用することになる.例えばC言語で書かれたアプリケーションプログラムでは,printfやfwriteなど標準ライブラリ関数が定義されており,それを利用してこうした機能をプログラムで実現している.printf等のライブラリ関数は,あらかじめ機械語のライブラリとして用意されており,アプリケーションプログラムをコンパイルするときにこのライブラリとリンクして,全体を機械語の実行ファイルに変換する.DLLは,コンパイル時にはリンクされずに,実際に後でプログラムを実行した時に,printfを実行しようとした瞬間にメモリにロードされて実行される仕組みである.これにより,実行ファイルを小さくしておくことができ,また,必要になるまではメモリ容量を抑えておくことができる.
アプリケーションプログラムでのメモリ管理手法
ここまでは,オペレーティングシステム側のメモリ管理手法であり,一般にアプリケーションプログラムを作成する際には意識せずに済む部分である.このため,プログラム作成時には最大仮想記憶容量いっぱいまで自由に利用することができる.しかし,あまり広大なメモリ空間を利用するプログラムに対し,主記憶が小さい場合にはスワップ領域からのスワップが頻発し,動作が遅くなる.一般に外部記憶装置は主記憶に比較して圧倒的に遅いためである.
そこで,アプリケーション側でも,使用するメモリ容量を抑えるために工夫し,必要に応じてアプリケーションレベルでのメモリ管理が必要となる.
実行ファイルのメモリモデル
C言語でアプリケーションプログラムを書く場合,変数宣言,配列宣言などによって必要なメモリをあらかじめ確保を行う.それに加えて必要に応じて追加でメモリ容量をmalloc関数やcallooc関数で確保する.これらmalloc関数は,ライブラリとしてあらかじめOS側で用意されており,プログラムごとに自由に使えるメモリエリアから必要に応じてメモリを確保するような動作を行う.
プログラムが実行時に使用するメモリは,プログラム領域,静的メモリ領域,ヒープ領域,スタック領域という三つの領域で構成されていることが多い.プログラム領域はその名のとおり機械語のプログラムコードを格納する領域であり,概念的には論理アドレスの先頭付近に配置されることが多い.静的メモリ領域は,プログラムで使用する静的変数や大域変数などをあらかじめ割り当てておく領域である.スタック領域は論理アドレスの末尾から先頭に向かって伸びる関数呼び出しに対応するスタックフレームを構築するための領域である.C言語などのプログラムで関数呼び出しは,スタックの動作そのものであることを前に説明したが,関数内の自動変数(局所変数)はこのスタック領域にスタックとしてpushされる.関数が終了して呼び出し元に復帰するときにpopされ,消滅する.ヒープ領域は,プログラム領域に続き,スタック領域との間に存在する.ここがmallocなどを使用しての動的に確保されるメモリ領域となる.ヒープ領域からは,malloc関数などが使用されると,ヒープ領域から未使用の連続したメモリを切り出して使用する.
ヒープ領域の管理
ヒープ領域から必要サイズのメモリを切り出せるようにするために用いられる管理手法として,代表的なものがフリーリスト管理である.C言語のメモリ管理でもよく用いられる.
ガベージコクレション
Java言語では,C言語とは異なり,動的メモリ機能が言語仕様に含まれている.C言語ではmallocとfreeさえOS側で提供されていれば,その後のメモリ管理自体はプログラマが責任を持ってプログラムを組む.しかし,Java言語では動的メモリ管理機能がガベージコレクション機構が要求されている.
ガベージコレクションとは,プログラム実行中に使用済みとなり不必要となったメモリ領域を,その後明示的にfreeしなくとも,システム側で自動的に開放する機能のことである.ガベージ(ゴミ)を自動で集めてくれる(コレクション)ことを意味する.
ガベージコレクションを実現する方法は,大きく二つ存在する.参照カウント方式と,トレース方式である.
参照カウント方式は,Javaの場合メモリを確保するのは新しいオブジェクトを生成したときである.このとき新オブジェクトは何かの変数などに代入されて使用される.この代入などのタイミングでオブジェクトを参照する変数の数をオブジェクト内に格納しておく.参照される数が増えればカウントアップ,そのオブジェクトを参照していた変数に別の値が代入されるなど,そのオブジェクトの参照が減ればカウントダウンを行い,参照数が0となった時点でそのオブジェクトをゴミとして回収する.
トレース方式は,実行時のある時点でプログラムを一旦中断し,その時の全変数,全オブジェクトを調査し,ポインタの連結関係をトレースする.ある変数から参照されているオブジェクトには連結されていたことを示すマークをつけ,そのオブジェクト内からさらに参照されているオブジェクトを辿り,またマークをつけていく.すべてマークをつけたとき,マークされていないオブジェクトがあれば,それはもはや不必要なゴミとみなして回収される.