プログラム:ファイルに保存されたコードの一部。
コンパイラーは、プログラムをコンパイルして実行可能ファイルを生成するときに、各命令とデータのアドレスをソートします。
プログラムの実行中、命令とデータは指定されたメモリに配置されます。プログラムは実行中にのみメモリを占有するため、プログラムアドレス空間はプロセスアドレス空間とも呼ばれます。
メモリ空間はこんな感じ。
実行中のプログラムが物理アドレスに直接アクセスするとどうなりますか?
- プログラムの実行に失敗する可能性があります。プログラムがコンパイルされると、変数データはアドレスでソートされますが、アドレスが占有されている場合、プログラムは実行できません。(コンパイラーは、使用されているメモリーの一部を動的に取得できません)
- ワイルドポインターの問題。プロセスが物理アドレスに直接アクセスする場合、ワイルドポインタが他のプロセスのデータを変更する可能性があります。
- メモリ使用量が少ない。プログラム操作には、連続したアドレス空間が必要です。これにより、ある程度空間が無駄になります。
そのため、仮想メモリはOSで設定され、仮想アドレス空間を介して物理メモリにマッピングされます。C言語/ C ++を使用する場合、変数または関数のアドレスはすべて仮想空間内のアドレスであり、物理メモリアドレスはユーザーには見えず、OSによって管理されます。OSは、対応する仮想アドレスを物理アドレスにマッピングするための責任があります。
プログラムが実行されるたびに、連続したアドレス空間が開かれます各プログラムがより大きな空間を占有し、多くのプログラムが一緒に実行される場合、一部のプログラムはメモリで実行されません。継続的に開かれているメモリアドレススペースのスペース使用率は非常に低いです。
プロセスが仮想メモリを使用した後、各プロセスには独自の仮想アドレス空間があり、使用する連続した空間があります。
このコードを見てください:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int global_val = 200;
int main()
{
pid_t pid = fork();//创建子进程
if(pid < 0)
{
printf("fork error\n");
return 0;
}
else if(pid == 0)
{
printf("child:%d %p\n",global_val,&global_val);
}
else
{
printf("parent:%d %p\n",global_val,&global_val);
}
return 0;
}
出力は次のとおり
です。子プロセスと親プロセスで同じ変数とアドレスが使用されています。
コードに小さな変更を加えます。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int global_val = 200;
int main()
{
pid_t pid = fork();//创建子进程
if(pid < 0)
{
printf("fork error\n");
return 0;
}
else if(pid == 0)
{
global_val = 100;
printf("child:%d %p\n",global_val,&global_val);
}
else
{
sleep(3);
printf("parent:%d %p\n",global_val,&global_val);
}
return 0;
}
出力は次のとおり
です。親プロセスの変数は変更されていないが、子プロセスの変数が変更されていることがわかります。
子プロセス変数は変更されましたが、親プロセス変数は変更されなかったのはなぜですか?
子プロセスは親プロセスのコピーであり、子プロセスは親プロセスのすべての情報をコピーします。子プロセスのデータが変更されていない場合、子プロセスは親プロセスのすべての情報を使用します。
最初のコードでは、子プロセスの変数は変更されておらず、親プロセスの変数も変更されていません。したがって、最初のコードでは、アドレスは等しく、変数も等しくなっています。
2番目のコードでは、子プロセスの変数は変更されていますが、親プロセスの変数は変更されていません。同じ仮想アドレスが異なる物理アドレスにマッピングされています。したがって、2番目のコードのアドレスは同じですが、変数が異なります。
ここで同じことは、子プロセスが親プロセス、プロセスアドレススペース、PCB ...のすべての情報をコピーすることを意味します。
子プロセスのデータが変更され、コピーされます。
2番目のコードでは、ここにコピーオンライト技術が含まれています。Linuxのfork()は、コピーオンライトを使用して実装されています。コピーオンライトは、コピーを遅延または排除する手法です。OSはプロセスアドレス空間全体をコピーするのではなく、子プロセスの親プロセスがアドレスを共有します。データが書き込まれて変更されると、データがコピーされ、各プロセスが独自のコピーを持ちます。リソースのコピーは、書き込み時にのみ実行されます。 これまでは、子プロセスは読み取りと共有のみでした。これにより、親プロセスと子プロセスのコード共有とデータの独立性が保証されました。
コピーオンライト技術の利点:
- 子プロセス作成の効率を向上させます。
- リソースを節約します。
では、なぜOSは仮想アドレス空間を使用するのでしょうか。または、仮想アドレス空間の利点は何ですか?
- 物理メモリの使用を改善します。
- プロセス間の独立性を確保
仮想アドレスはどのように物理アドレスにマッピングされますか?
オペレーティングシステムのメモリ管理モード:
- セグメント化されたタイプ:セグメント番号+セグメント内のオフセット
セグメントテーブル:オペレーティングシステムは、メモリが分割されるブロックの数を記録します。
セグメント番号から対応する物理メモリの開始アドレスを見つけ、セグメント内のオフセットを追加して物理アドレスを見つけます。
- ページネーション:ページ番号+ページオフセット、ここでの描画は比較的単純です。
ここに違いがある場合、ページのサイズを知る必要があります。通常、ページのサイズは4Kです。
32ビットOSでは、メモリが4Gの場合、4* 1024* 1024* 1024/4* 1024
ページ番号、つまりページテーブルエントリを占有します。合計でページテーブルエントリ/ページ番号
があり2^20个
ます。メモリを多くの小さなブロックに分割します。
対応するページ番号、その物理アドレス、およびページ内のオフセットを見つけることにより、変数の物理アドレスを見つけることができます。
- セグメントページング:メモリはセグメントで管理され、ページングは各セグメントで使用されます。
まず、セグメント番号を取得してセグメントテーブルを検索します。セグメントテーブルには、セグメント番号に対応するページテーブルの開始アドレスが格納され、次にセグメント内のページテーブルの開始アドレスからページテーブルを検索します。
現在のコンピュータで使用されているセグメントページ管理。
仮想ページは物理メモリにキャッシュされます。図に示すように、
仮想メモリはページテーブルにキャッシュできます。ページがヒットすると、VP2がメモリにキャッシュされます。
キャッシュミス:ページフォールト、VP3はヒットせず、ページフォールト割り込みが発生します。次に、OSはVP3をディスクからメモリ内のPP3にコピーし、PTE3を更新してから戻ります。
VP3:仮想メモリ3。PP3
:物理メモリ3
PTE3:ページテーブルエントリ3. 0:割り込みが発生した、1:キャッシュ可能。
ページフォールトの中断後:ページフォールトハンドラーは、犠牲ページとして1つを選択し、ディスク上のVP3のコピーで置き換えます。
MMUはページテーブルを使用して、仮想アドレス空間から物理アドレスの空の空間へのマッピングを実装します。
では、ページを犠牲にすることはどうでしょうか?
使用するメモリの交換アルゴリズムを:
- OPT:最良の置換アルゴリズム。置換されたページは、今後二度と使用されないか、最も長期間使用されません。このアルゴリズムは理論上のアルゴリズムにすぎません。
- FIFO:先入れ先出しアルゴリズム。ページ不在率が増加します。
- LRU:長期間使用されていないアルゴリズム。最も長期間使用されていないページを置き換えます。(通常、このアルゴリズムを使用します)
- LFU:最も一般的に使用されていないアルゴリズム、一定期間で最も使用されていないアルゴリズム、および将来的に使用される可能性も非常に低いです。