仮想記憶(Virtual Memory)
仮想記憶とは、
[コンピュータ]]のメモリ管理における仮想化技術の一つです。
オペレーティングシステム]が、物理的なメモリをアプリケーションに対して、連続した専用の[[主[[記憶装置]]のように見せることで実現します。この技術により、物理的な
主[[記憶装置]](RAM)に加え、ハードディスクなどの
補助[[記憶装置]]を併用することで、物理メモリよりも大きな仮想メモリを扱うことができます。
仮想記憶の概要
仮想記憶は、アプリケーションが物理的なメモリのアドレスを意識せずにプログラムを作成できるようにします。これにより、複数のプログラムを同時に実行する
マルチタスクが容易になります。現代の
オペレーティングシステムの多くは、この仮想記憶をサポートしています。
仮想的に与えられたアドレスは
仮想アドレス(virtual address)または
論理アドレス(logical address)と呼ばれます。一方、実際の物理メモリ上で有効なアドレスは
物理アドレス(physical address)または
実アドレス(real address)と呼ばれます。仮想アドレスの範囲を
仮想アドレス空間、物理アドレスの範囲を
物理アドレス空間といいます。
実装方式
仮想記憶の実装方式には、主に
セグメント方式と
ページング方式の2種類があります。
セグメント方式: メモリを可変長のセグメントに分割して管理します。
ページング方式: メモリを固定長のページに分割して管理します。一般的にページング方式の方が広く利用されています。
ページング方式では、
メモリスワッピング(または
匿名メモリページング)という技術と組み合わせて使用されることが一般的です。メモリスワッピングは、物理メモリ上のページを、ハードディスクなどの二次
記憶装置(スワップファイルやスワップパーティション)に一時的に書き出すことで、物理メモリを他の
プロセスに解放する技術です。
Windows系OSのNT系では、ページング方式の仮想記憶を採用しており、設定項目として「仮想メモリ」という名称で設定できます。しかし、この設定は実際にはページングファイル(ストレージへのメモリスワップ)に関するものです。アプリケーションや多くのシステム
プロセスは、常に仮想アドレスを使用してメモリを参照しています。OS
カーネルのコア部分のみが、アドレスの
仮想化をバイパスし、物理アドレスを直接使用できます。
仮想記憶は、システムに搭載されているRAMの容量に関わらず常に使用されます。物理メモリ以上の記憶領域を仮想的に利用できるようにすることが主な目的ではありますが、本質は、不連続な物理メモリ領域を連続した仮想メモリ領域にマッピングすることです。また、複数の
プロセスがそれぞれ独立した仮想メモリ領域を持つことで、他の
プロセスの影響を受けずに動作できる環境が提供されます。仮想記憶が提供する環境は、
仮想機械がゲストOSに対して提供する環境と等価であるといえます。
仮想記憶は物理メモリの
フラグメンテーションを隠蔽し、アプリケーションのプログラミングを容易にする役割も担います。
カーネルにメモリ階層の管理を委任することで、
プログラマが明示的なオーバーレイ制御を行う必要性をなくしています。
技術的には、仮想記憶を利用することで、
ソフトウェアが動作する
メモリアドレス空間のサイズは、必ずしも物理メモリのサイズに制限されなくなります。ただし、仮想記憶を正しく実装するためには、
CPUがOSに対して仮想メモリを物理メモリにマッピングする手段と、物理メモリに対応しない仮想アドレスへのアクセスを検出する手段を提供する必要があります。
CPUの支援なしに仮想記憶を提供することも可能ですが、それは
CPUの機能をエミュレートすることと同じです。
背景
コンピュータの
記憶装置は、一般的に以下のような階層構造を持っています。
1.
CPU内のレジスタ: 最も高速なメモリですが、容量はごくわずかです。
2.
キャッシュメモリ:
CPUに近い高速なメモリですが、容量は限られています。
3.
主[[記憶装置]](RAM): プログラムが直接アクセスできるメモリで、
キャッシュメモリよりも容量が大きいですが、速度は遅くなります。
4.
補助[[記憶装置]](ハードディスクなど): 速度は遅いですが、非常に大きな容量を持っています。
多くのアプリケーションは、
実行ファイルやデータが物理メモリ上に格納された状態でアクセスできることを望んでいます。これは、複数の
プロセスを並行して実行するOSでは特に重要です。しかし、実行中のプログラムが要求する物理メモリの合計が、実際に搭載されている物理メモリの容量を超える場合、情報の一部をディスクに退避させ、必要に応じて物理メモリに戻す必要があります。
従来のメモリ管理の問題点
従来の方法では、アプリケーション自身が物理メモリに置くべき情報の範囲を決定し、
補助[[記憶装置]]との情報のやりとりを制御する必要がありました。これは
プログラマにとって大きな負担となり、プログラミング効率を低下させます。また、他のプログラムとの競合が発生する可能性もあります。
別の方法として、データの参照をポインタではなくハンドルで行う方式も考えられます。OSがハンドルと対応するデータを物理メモリにロードまたは
補助[[記憶装置]]に移動させますが、この方式もアプリケーションのコードが複雑になり、標準ライブラリの機能が利用できなくなるなどの問題点がありました。
仮想記憶の導入
仮想記憶は、特別な
ハードウェアとOSの組み合わせにより、主記憶容量が大きくなったように見せ、各プログラムが自由に空間を広げて使用できるようにします。仮想記憶機構の内部の動きは、他の
ソフトウェアからは見えません。仮想的な主記憶容量は、アドレスのサイズによって制限されますが、事実上ほぼどんな大きさにもできます。例えば、
32ビットシステムでは、仮想アドレス空間のサイズは4
ギガバイトになります。
仮想記憶によって、アプリケーション
プログラマは、必要なメモリ容量を気にせずに、仮想アドレス空間の好きな場所にデータを配置できます。
プログラマは、主記憶と補助記憶の間でデータをやりとりすることを意識する必要がありません。ただし、性能を考慮する場合は、アクセスするデータを近いアドレスに配置するように注意し、不要なスワッピングを避ける必要があります。
仮想記憶は
コンピュータアーキテクチャの重要な部分であり、その実装には
ハードウェアのサポートが不可欠です。メモリ管理ユニット (MMU) と呼ばれる
ハードウェアがこの役割を担います。1960年代までの
メインフレームの多くは仮想記憶をサポートしていませんでした。
歴史
仮想記憶技術が開発される以前、
プログラマは2レベルの
記憶装置(主記憶と磁気ディスク)を直接管理する必要があり、大規模プログラムではオーバーレイなどの技法が使われていました。仮想記憶は、主記憶の拡張だけでなく、
プログラマが拡張をより簡単に扱えるように導入されました。
ページング方式は、
マンチェスター大学のAtlas上で開発されました。最初のAtlasは1962年に稼働開始しましたが、ページングのプロトタイプは1959年に開発されています。
バロースは
1961年に、セグメント方式で仮想記憶をサポートした世界初の商用
コンピュータB5000をリリースしました。
1965年にMITが開発したMultics以降、仮想記憶は本格的に採用され始めました。
1969年、
IBMの研究チームが仮想記憶システムが手動制御システムよりも優位にあることを示し、仮想記憶に関する論争は終結しました。
1970年、
IBMはSystem/370シリーズのOSで仮想記憶をサポートしました。
ミニ
コンピュータでは、ノルウェーのNORD-1が初めて仮想記憶を導入し、
1976年にはDECの
VAXシリーズのVMSが仮想記憶をサポートしました。しかし、
1980年代初期のパーソナル
コンピュータでは仮想記憶は採用されていませんでした。x86アーキテクチャで仮想記憶が導入されたのは、
Intel 80286による
プロテクトモードが最初ですが、セグメント単位のスワッピングは性能が悪くなるという問題がありました。Intel 80386ではページング方式が導入され、ページフォールトによるページングが可能となりました。仮想記憶が導入されたのは、OS/2、Windows 3.0、MacintoshのSystem 7、
Linux[[カーネル]]などが最初です。
ページング方式の詳細
ページング方式では、仮想アドレスの下位ビット列部分はそのまま物理アドレスの下位ビット列部分として使用されます。上位ビット列部分は、アドレス変換テーブルのキーとして使用され、物理アドレスの上位ビット部分を取得します。仮想アドレス空間の連続したアドレス範囲は、物理アドレス空間の連続したアドレス範囲に変換されます。このような範囲で参照されるメモリをページと呼びます。
ページサイズは、512バイトから8192バイトが一般的で、4096バイトが最もよく使われています。
オペレーティングシステムは、ページテーブルと呼ばれるデータ構造に仮想ページ番号と物理ページ番号のマッピング情報を格納します。あるページが使用不可の場合(物理メモリに対応しておらず、スワップ領域に内容がある場合など)、
CPUがそのページ内のメモリ位置を参照しようとすると、
ハードウェア機構がページフォールトという例外を通知します。この例外処理ルーチンでは、スワップ領域から必要なページの内容を物理メモリに読み込むページスワップ処理が実行されます。
ページスワップ操作では、まずメモリ上のページを選択し、必要に応じてスワップ領域に書き出します。次に、必要なページ情報を読み込み、仮想アドレスから物理アドレスへの変換テーブルを更新します。ページスワップが完了すると、元のプログラムの実行が再開されます。また、仮想ページに何も割り当てられていない場合は、未使用の物理ページを割り当て、場合によってはゼロクリアします。
セグメント方式の詳細
セグメント方式では、仮想アドレス空間を可変長のセグメントに分割します。仮想アドレスは、セグメント番号とセグメント内オフセットから構成されます。セグメントとページは同時に使用できます。その場合、基本はページングで、セグメントはメモリ保護に使用されます。Intel 80386以降のプロセッサでは、セグメントをページ化された
32ビットのリニアなアドレス空間に配置しますが、実際にはページングのみを使用することが多くなっています。
単一レベル記憶
単一レベル記憶は、
ファイルシステム上のファイルがセグメント機構を通してアドレス空間にマッピングされるシステムです。ファイルのリンク部分にはポインタがあり、そのポインタにアクセスするとトラップが発生します。トラップハンドラは対応するセグメントをアドレス空間にマッピングし、ポインタを書き換えます。この方式ではリンケージエディタが不要であり、同じファイルを複数の
プロセスが異なる場所にマッピングしても問題ありません。
詳細な実装
仮想アドレスから物理アドレスへの変換は、メモリ管理ユニット (MMU) によって行われます。MMUは、
CPUに内蔵されていることもあれば、外付けのチップであることもあります。OSは、仮想アドレス空間のどの部分を物理メモリに保持するかを決定し、MMUが使用するアドレス変換テーブルを管理します。仮想メモリ例外が発生した場合は、物理メモリ領域を確保し、必要な情報をディスクから持ってきて、変換テーブルを更新し、例外が発生した
ソフトウェアの実行を再開します。
アドレス変換テーブルは、通常物理メモリに格納されています。このため、仮想メモリを参照すると、変換テーブルの参照が発生し、性能が低下する可能性があります。この性能低下を軽減するため、MMUは最近使われた仮想アドレスとそれに対応する物理アドレスを保持するトランスレーション・ルックアサイド・バッファ(TLB)を使用します。参照アドレスがTLBに格納されていれば、高速に変換できます。しかし、TLBは高価なため、テーブルの大きさが限られており、見つからない場合は物理メモリ上の変換テーブルを参照します。
MMUは、メモリ保護の機能も持っており、メモリ参照の種類や
CPUモードによってアクセス制御を行うことができます。これにより、OSは自身のコードやデータを不正なメモリアクセスから保護したり、アプリケーションを相互に保護したり、アプリケーション自身の不正動作から保護したりすることができます。
仮想アドレス空間の管理
各
プロセスの仮想アドレス空間には、その
プロセスが使用するコードやデータが配置されます。仮想アドレス空間内で使用している範囲の管理は、仮想記憶機構として必須です。例えば、
実行ファイルの内容を配置する領域や、
スタックを配置する領域などがあります。これらの領域をセグメントと呼びます。
実行ファイルを配置する領域は、必ずしも連続である必要はありません。プログラムのコード部分とデータ部分を分離して配置するのが一般的で、前者をテキストセグメント、後者をデータセグメントと呼びます。データセグメントには、BSSセクションとヒープ領域が含まれます。
テキストセグメントは、
ファイルシステム上の
実行ファイルに対応しており、変更されることはありません。データセグメントや
スタックは一時的な存在で、匿名ファイルをマッピングしているものとして管理されます。exec() システムコールなどで新たに
プロセスの仮想アドレス空間を設定した場合、実際の
実行ファイルの内容はロードされず、ページフォールトが発生した際に初めてページ単位にロードされます。
各
プロセスの仮想アドレス空間は、同じアドレス範囲を持ち、オーバーラップしているのが一般的です。これを多重仮想記憶と呼びます。MMUは、現在実行中の
プロセスの仮想空間のみを認識します。
プロセスを切り替える際には、MMUに対して仮想アドレス空間の切り替えも指示する必要があります。同じプログラムを実行する
プロセスが複数存在する場合、それぞれが同じ仮想アドレスに
実行ファイルをマッピングし、それぞれ独立した仮想空間を使用します。このため、
実行ファイル自体に配置すべきアドレスを格納しておくようになっています。
実装例
Ferranti Atlas
1962年に世界で初めてページングをサポートした
コンピュータです。このマシンは、プログラムに単一レベル記憶を提供していました。
Windows 3.xとWindows 9x
Windowsでは、1990年のWindows 3.0から仮想記憶をサポートしています。Windows 3.xには、スワップファイルとして使用される隠しファイルがありました。
NT系のWindowsでは、pagefile.sysというファイルを使用します。このファイルは、ページングファイルとして使用されます。ページングファイルは、初期サイズと最大サイズがあり、必要に応じて拡張されます。Windows XP以降では、ページファイルを使用しないオプションも選択できますが、推奨されません。
ページングファイルが徐々に拡張された場合、
フラグメンテーションが発生し、性能に悪影響を与えることがあります。これを防ぐには、ページファイルのサイズを固定する方法があります。
Mac OS
Mac OSはSystem 7から仮想メモリを実装しました。仮想メモリは、起動ディスクに作成されるスワップファイルを使用していました。
UNIXおよび
Unix系OSでは、ハードディスクのパーティションをスワップパーティションとして使用することが多く、ファイルへのスワッピングもサポートされています。
Linux
Linuxでは、スワップファイルはスワップパーティションと同程度の性能があります。Linuxは、複数のスワップデバイスをサポートし、それぞれに優先度を設定できます。
macOS
macOSでは、複数のスワップファイルを使用できます。デフォルトではルートパーティションに配置され、不足すると自動的に追加されます。
まとめ
仮想記憶は、現代の
コンピュータシステムにおいて不可欠な技術です。物理メモリを抽象化し、より大きな仮想メモリ空間をアプリケーションに提供することで、
マルチタスクの実現や、物理メモリ以上のメモリを扱うことを可能にしています。仮想記憶の実装には、ページング方式とセグメント方式の2種類があり、MMUという
ハードウェアがアドレス変換などの役割を担っています。仮想記憶の発展は、
コンピュータシステムの進化に大きく貢献してきました。