CPU とメモリの仮想化の目的#
メモリの仮想化により、アプリケーションプログラミングが簡単になります。オペレーティングシステムはプログラムに大きな独立したアドレス空間を持っているかのように見せかけ、実際の小さく混雑した物理メモリの割り当て方法を考慮する必要はありません。また、他のプログラムのメモリを悪意のあるプログラムから保護することもできます。
CPU の仮想化により、オペレーティングシステムはスケジューリングアルゴリズムを使用して、複数のプログラムが実行されている場合でも CPU が単独で使用されないように保証できます。
仮想メモリの 3 つの目標:透明性、効率性、保護性。
ゾンビ状態#
プロセスは終了したがまだクリーンアップされていない最終状態になることがあります(UNIX ベースのシステムでは、これをゾンビ状態と呼びます)。この最終状態は非常に便利であり、他のプロセス(通常は親プロセス)がプロセスのリターンコードをチェックし、直前に完了したプロセスが正常に実行されたかどうかを確認できます(通常、UNIX ベースのシステムでは、タスクが成功した場合にはゼロを返し、それ以外の場合にはゼロ以外を返します)。
シェルの呼び出しプロセス、fork、exec#
シェルもユーザープログラムであり、まずプロンプトを表示し、ユーザーの入力を待ちます。それに対して、コマンド(実行可能プログラムの名前と必要なパラメータ)を入力できます。ほとんどの場合、シェルはファイルシステムでその実行可能プログラムを見つけ、fork () を呼び出して新しいプロセスを作成し、exec () のバリエーションのいずれかを呼び出してその実行可能プログラムを実行し、wait () を呼び出してコマンドの完了を待ちます。子プロセスの実行が終了すると、シェルは wait () から戻り、再びプロンプトを出力し、次のコマンドの入力を待ちます。
fork と exec の分離により、シェルは便利な機能を簡単に実装できます。例えば:
wc p3.c > newfile.txt
上記の例では、wc の出力結果がファイル newfile.txt にリダイレクトされます(リダイレクトを示す大なり記号の前に newfile.txt があります)。シェルは結果のリダイレクトを実装する方法も非常にシンプルです。子プロセスの作成が完了した後、シェルは exec () を呼び出す前に標準出力を閉じ、newfile.txt を開きます。これにより、実行されるプログラム wc の出力結果がファイルに送信され、画面に表示されなくなります。
CPU 仮想化の重要な基本メカニズム、制約付き直接実行#
システムコールのプロセス、トラップ命令、トラップテーブル#
システムコールを実行するには、プログラムは特別なトラップ(trap)命令を実行する必要があります。この命令は同時にカーネルにジャンプし、特権レベルをカーネルモードに引き上げます。一度カーネルに入ると、システムは必要な特権操作(許可されている場合)を実行して、呼び出しプロセスのために必要な作業を行うことができます。完了後、オペレーティングシステムは特別なトラップからの復帰(return-from-trap)命令を呼び出し、通常どおり呼び出し元のユーザープログラムに戻り、特権レベルを下げてユーザーモードに戻ります。
まだ議論していない重要な詳細があります:トラップが OS 内で実行されるコードをどのように知るのか?
明らかに、呼び出しプロセスはジャンプ先のアドレスを指定することはできません(プロシージャ呼び出しを行う場合と同様に)。これを行うと、プログラムが任意の場所にジャンプできるようになり、これは明らかに悪いアイデアです。実際、この機能を持つと、賢いプログラマーはカーネルで任意のコードシーケンスを実行できるかもしれません。したがって、カーネルはトラップが実行されるコードを慎重に制御する必要があります。
カーネルはトラップテーブル(trap table)を設定することでこれを実現します。マシンが起動すると、特権(カーネル)モードで実行されるため、必要に応じてマシンハードウェアを自由に設定できます。オペレーティングシステムが最初に行うことは、ハードウェアにこれらのトラップハンドラの場所を通知する特別な命令を使用することです。
たとえば、ハードディスクの割り込み、キーボードの割り込み、またはプログラムのシステムコールが発生した場合、どのコードを実行する必要がありますか?オペレーティングシステムは通常、特殊な命令を使用してハードウェアにこれらのトラップハンドラの場所を通知します。ハードウェアが通知されると、これらのハンドラの場所を覚えており、次回のマシンの再起動までその位置を覚えており、ハードウェアはシステムコールやその他の例外イベントが発生した場合に何をすべきか(つまり、どのコードにジャンプするか)を知っています。
最後に、もう一つ挿入します:ハードウェアにトラップテーブルの場所を伝える命令を実行できる能力は非常に強力な機能です。したがって、おそらくすでに推測しているかもしれませんが、これも特権(特権)操作です。ユーザーモードでこの命令を実行しようとすると、ハードウェアは許可しません。
協調スケジューリングシステム#
協調スケジューリングシステムでは、OS はシステムコールや特定の不正な操作が発生するのを待って CPU の制御を取り戻します。
非協調スケジューリングシステム#
非協調スケジューリングシステムでは、OS はクロック割り込み、システムコール、または特定の不正な操作が発生するのを待って CPU の制御を取り戻し、次に実行するプログラムを決定するために OS のスケジューリングアルゴリズムが使用されます。
仮想メモリ#
ハードウェアベースの動的リロケーション#
各 CPU には、ベースレジスタとリミットレジスタの 2 つのレジスタが必要です。プログラムの作成とコンパイル時に、アドレス空間は 0 から始まると仮定されますが、プログラムが実際に実行されると、オペレーティングシステムはその物理メモリ内での実際のロードアドレスを決定し、その開始アドレスをベースレジスタに記録します。プロセスの実行中に生成されるすべてのメモリ参照は、ハードウェアによって物理アドレス = 仮想アドレス + ベースの計算で処理されます。この方法にはメモリの断片化が存在します(割り当てられたスペースがプログラム内で使用されていない)。
セグメンテーション、汎用ベース / リミット#
典型的なアドレス空間には、コード、スタック、ヒープの 3 つの異なる論理セグメントがあります。これらの 3 つの部分を物理メモリに配置するために、それぞれのセグメントには一組のベース / リミットレジスタがあり、ハードウェアは仮想アドレスを変換する際にセグメントレジスタを使用します。
たとえば、セグメントレジスタの最初の 2 ビットは仮想アドレスが属するセグメントを示し、残りの 12 ビットはセグメント内のオフセットを示します。この場合、物理アドレス = オフセット + 対応するセグメントのベースです。リミットレジスタの値はセグメントオーバーフローが発生しないかどうかをチェックするために使用されます。各セグメントにはベース / リミットレジスタ以外にも、ハードウェアはセグメントの成長方向(スタックは逆方向に成長する可能性があります)を知る必要があります。
セグメンテーションによって引き起こされる問題は、物理メモリがすぐに多くの空きスペースの小さな穴で埋まることです。これにより、新しいセグメントに割り当てるか、既存のセグメントを拡張することが非常に困難になります。この問題は外部断片化と呼ばれます。
ページング#
ページングは、プロセスのアドレス空間を固定サイズの単位で分割し、各単位をページと呼ばれるものにします。対応するように、物理メモリもページフレームに分割され、各ページフレームには仮想メモリページが含まれます。
アドレス空間の各仮想ページが物理メモリ内のどこにあるかを記録するために、オペレーティングシステムは通常、ページテーブルと呼ばれるデータ構造を各プロセスごとに保存します。
ページテーブルの主な目的は、アドレス空間の各仮想ページに対するアドレス変換を保存することで、各ページが物理メモリ内のどこにあるかを知ることができます。ページテーブルは各プロセスごとに 1 つの(プロセスごとの)データ構造です。
このプロセスで生成された仮想アドレスを変換するためには、まずそれを 2 つのコンポーネントに分割する必要があります:仮想ページ番号(VPN)とオフセット。仮想アドレス空間が 64 バイトであると仮定すると、仮想アドレス全体には 6 ビット($2^6=64$)が必要です。ページサイズが 16 バイトの場合、オフセットには 4 ビット($2^4=16$)が必要で、残りの 2 ビットは VPN に割り当てられます($2^2$ は 4 ページを表すことができ、$4*16=64$)。
アドレス変換を行う際、オペレーティングシステムはページテーブルをクエリし、VPN を PFN(物理ページ番号)に変換します。オフセットは変わらずに保たれます。
ページテーブルは仮想アドレスを物理アドレスにマッピングするためのデータ構造であるため、どのようなデータ構造でも使用できます。最も単純な形式は、配列としての線形ページテーブル(linear page table)です。オペレーティングシステムは仮想ページ番号(VPN)をこの配列で検索し、対応するページテーブルエントリ(PTE)をそのインデックスで見つけて、必要な物理フレーム番号(PFN)を取得します。
ページテーブルエントリ(PTE)には有効ビット、保護ビット、存在ビットなどがありますが、最も重要なのは物理フレーム番号(PFN)です。
ページングの問題#
線形ページテーブルは大量のメモリを使用します。仮想ページ VPN が物理アドレスにマップされているかどうかに関係なく、すべてのページテーブルエントリがメモリに保存されます。
さらに、各メモリ参照(命令のフェッチや明示的なロードまたはストアなど)に対して、ページングは追加のメモリ参照を実行する必要があります。これにより、プロセスの速度が 2 倍以上遅くなる可能性があります。
したがって、解決する必要がある 2 つの実際の問題があります。ハードウェアとソフトウェアを注意深く設計しないと、ページテーブルはシステムの速度を遅くし、多くのメモリを使用します。
高速アドレス変換 - TLB#
TLB(Translation Look-aside Buffer)はハードウェアベースのキャッシュであり、アドレス変換の遅さの問題を解決することができます。ハードウェアは仮想アドレスを変換する際、まず TLB がヒットしているかどうかを確認し、ヒットしない場合はメモリ内のページテーブルにアクセスします。
TLB に含まれる仮想から物理へのアドレスマッピングは、現在のプロセスにのみ有効であり、他のプロセスには意味がありません。したがって、プロセスコンテキストの切り替えが発生する場合、ハードウェアまたはオペレーティングシステム(またはその両方)は、次に実行されるプロセスが以前のプロセスのアドレスマッピングを誤って読み取らないように注意する必要があります。
- 単純に TLB をクリアする。
- TLB にアドレス空間識別子(どのプロセスに属するかを示す識別子)を追加する。
マルチレベルページング#
ページテーブルが使用するメモリ量を減らすための簡単な方法は、ページサイズを大きくすることです(VPN の数が減り、オフセットが増えます)。ただし、大きなページはメモリフラグメンテーションが増えるというデメリットがあります。もう 1 つの方法は、セグメンテーションとページングを組み合わせる方法です。
ページテーブルをハッシュテーブルやツリーのようなデータ構造で保存することもできますが、二次ページングと呼ばれる方法を使用することが一般的です。
ページテーブルを仮想アドレス空間の固定サイズの単位で分割し、それぞれの単位をページと呼ばれるものにします。対応するように、物理メモリもページフレームに分割され、各ページフレームには仮想メモリページが含まれます。
アドレス空間の各仮想ページが物理メモリ内のどこにあるかを記録するために、オペレーティングシステムは通常、ページテーブルと呼ばれるデータ構造を各プロセスごとに保存します。
ページテーブルの主な目的は、アドレス空間の各仮想ページに対するアドレス変換を保存することで、各ページが物理メモリ内のどこにあるかを知ることができます。ページテーブルは各プロセスごとに 1 つの(プロセスごとの)データ構造です。
このプロセスで生成された仮想アドレスを変換するためには、まずそれを 2 つのコンポーネントに分割する必要があります:仮想ページ番号(VPN)とオフセット。仮想アドレス空間が 64 バイトであると仮定すると、仮想アドレス全体には 6 ビット($2^6=64$)が必要です。ページサイズが 16 バイトの場合、オフセットには 4 ビット($2^4=16$)が必要で、残りの 2 ビットは VPN に割り当てられます($2^2$ は 4 ページを表すことができ、$4*16=64$)。
アドレス変換を行う際、オペレーティングシステムはページテーブルをクエリし、VPN を PFN(物理ページ番号)に変換します。オフセットは変わらずに保たれます。
ページテーブルは仮想アドレスを物理アドレスにマッピングするためのデータ構造であるため、どのようなデータ構造でも使用できます。最も単純な形式は、配列としての線形ページテーブル(linear page table)です。オペレーティングシステムは仮想ページ番号(VPN)をこの配列で検索し、対応するページテーブルエントリ(PTE)をそのインデックスで見つけて、必要な物理フレーム番号(PFN)を取得します。
ページテーブルエントリ(PTE)には有効ビット、保護ビット、存在ビットなどがありますが、最も重要なのは物理フレーム番号(PFN)です。
マルチレベルページングの問題#
線形ページテーブルは大量のメモリを使用します。仮想ページ VPN が物理アドレスにマップされているかどうかに関係なく、すべてのページテーブルエントリがメモリに保存されます。
さらに、各メモリ参照(命令のフェッチや明示的なロードまたはストアなど)に対して、ページングは追加のメモリ参照を実行する必要があります。これにより、プロセスの速度が 2 倍以上遅くなる可能性があります。
したがって、解決する必要がある 2 つの実際の問題があります。ハードウェアとソフトウェアを注意深く設計しないと、ページテーブルはシステムの速度を遅くし、多くのメモリを使用します。