目次
序文
この章では主にプロセス アドレス空間に関連する概念を紹介します。実験からプロセス アドレス空間を紹介し、次にプロセス アドレス空間を段階的に深く理解し、周囲の概念を洗練します。
1. 初期プロセスのアドレス空間
1. 実験的導入
コードの実行現象を観察するには、次のコードがあります。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int g_val = 10; // 已初始化全局变量
int main()
{
pid_t id = fork();
if(id < 0)
{
// fork执行失败
perror("fork");
exit(-1);
}
else if(id == 0)
{
// 子进程
int cnt = 0;
while(1)
{
if(cnt == 5)
{
g_val = 20;
}
printf("我是子进程,g_val:%d, &g_val:%p\n", g_val, &g_val);
cnt++;
sleep(1);
}
}
else
{
// 父进程
while(1)
{
printf("我是父进程,g_val:%d, &g_val:%p\n", g_val, &g_val);
sleep(1);
}
}
return 0;
}
上記のコードをコンパイルして実行すると、結果は次のようになります。
これは、以前の fork 関数の戻り値が残した問題とまったく同じですが、なぜこのような不思議な現象が起こるのでしょうか。この記事では主にこれについて説明します。
2. 仮想アドレス空間
これまでの研究で下の図を見たことがあるかどうかはわかりません (32 ビット マシン下)。
これはコンピュータを学ぶ学生なら誰でも見たことのある絵だと思います。これが今日の中心となる仮想アドレス空間です。上のように空間を分割し、アドレスを割り当てます。上の図に基づいて、対応するコードを書くことができます。図は?示されています。
#include <stdio.h> #include <stdlib.h>
// 未初始化全局变量
int g_unval;
// 已初始化全局变量
int g_val = 10;
int main(int argc, char* args[], char* env[])
{
// 代码段
printf("code addr: %p\n", main);
// 常量
const char* str = "hello world";
printf("constant quantity: %p\n", str);
// 已初始化全局变量
printf("init global var: %p\n", &g_val);
// 未初始化全局变量
printf("uninit global var: %p\n", &g_unval);
// 堆
char* p1 = (char*)malloc(10);
char* p2 = (char*)malloc(10);
char* p3 = (char*)malloc(10);
printf("heap: %p\n", p1);
printf("heap: %p\n", p2);
printf("heap: %p\n", p3);
// 栈
printf("stack: %p\n", &p1);
printf("stack: %p\n", &p2);
printf("stack: %p\n", &p3);
// 命令行参数与环境变量
printf("args[0]: %p\n", args[0]);
printf("args[1]: %p\n", args[1]);
printf("args[2]: %p\n", args[2]);
printf("env[0]: %p\n", env[0]);
printf("env[1]: %p\n", env[1]);
printf("env[2]: %p\n", env[2]);
return 0;
}
テスト結果を下の図に示します。
結果は予想と同じです。全体のアドレスは増加しています。上記のプロセス アドレス空間分布図と同じです。これは Linux でのテスト結果です。ウィンドウでのテスト結果は異なる場合があります。これは、次の原因が考えられます。コンパイラ 最適化の結果。
2. プロセスアドレス空間とは何ですか?
1. 基本的な考え方
プロセスのアドレス空間は、プロセスの観点から見たメモリ空間であり、実際には、仮想アドレスから物理アドレスへのマッピングをデータ構造を通じて記録します。
2. プロセスのアドレス空間についての深い理解
これを理解するために、まずタイムラインを過去に戻します。コンピュータが最初に起動したとき、プロセス アドレス空間という概念はありませんでした。私たちが作成したプログラムは、メモリ上のデータにアクセスするためにメモリの物理アドレスを直接使用していました。図に示すように、下の図では。
このとき、プログラムを実行したいのですが、まず実行可能プログラムをメモリにロードし、対応するPCB制御ブロックを生成し、CPU配下のレディキューに入れてスケジューリングを待ちます。この時点で A を呼び出した場合 この時点で、プログラム A が範囲外にアクセスし、プロセス B のコードを直接変更したため、プロセス B が直接クラッシュしました。このとき、プロセスの独立性はどこから来るのでしょうか?プロセスの独立性はプログラマのコードの正確さに完全に依存するため、この種のプロセスに実際の物理アドレスに直接アクセスさせることは信頼できません。
現在のコンピューターでは上記の戦略は採用されず、次の図に示すように、プログラムが実際の物理メモリに直接アクセスできないようにするために、仮想アドレスが導入されています。
プログラムがメモリにロードされると、まずオペレーティング システムは、プロセスに対応する PCB 制御ブロック (task_struct)、プロセス アドレス空間 (mm_struct)、およびユーザー レベルのページ テーブルを生成します。 、プロセス = カーネル データ 構造 + コードとデータ。各プロセスでは、アドレスが 0x0000 0000 から 0x FFFF FFFF であると考えられます。これらのアドレスは仮想アドレスです。CPU は、ページ テーブルを通じてこれらの仮想アドレスを実際の物理アドレスにマップします。メモリデータを操作します。
問題は、余分な仮想アドレスが作成されることですが、結局、仮想アドレスを物理アドレスにマッピングしてメモリ上のデータにアクセスするわけではないので、仮想アドレスも不正なアドレスなのでしょうか?それも国境を越えた訪問ではないでしょうか?
実際、仮想アドレスも不正なアドレスである場合、その仮想アドレスはページ テーブル内で検出され、不正な物理アドレスにはまったくマッピングされず、他のプロセスに影響を与えることはありません。
3. プロセスのアドレス空間の性質
よく考えてください。各プロセスにはプロセス アドレス空間を割り当てる必要があるため、メモリ内にプロセスが 1 つだけ存在することはできず、プロセス アドレス空間も 1 つだけということはあり得ません。多数あるため、オペレーティング システムはこれらのプロセスを分離する必要がありますか? ? アドレス空間は管理されていますが、これらのプロセスのアドレス空間はどのように管理すればよいでしょうか?同様に、「最初に記述し、次に整理する」では、最初に構造体を使用してプロセスのアドレス空間を記述し、次にデータ構造体を使用してこれらの構造体を整理し、これらの構造体を追加、削除、確認、変更できるようにします。オペレーティング システムによる PCB 制御ブロックの管理と同じですか? Linux では、このデータ構造は mm_struct と呼ばれます。
それをどう説明すればいいでしょうか?考えてみますが、プロセスのアドレス空間はただの領域が次々と続いているだけではないでしょうか?そうすると、確実に言えるのは、パーティショニングは確実に行われるということですが、ではどのようにパーティショニングを行うのかということです。レコードの実際の位置がレコードの終了位置であるだけではありませんか?次のように;
strcut mm_strcut
{
// 代码段
int code_start, code_end;
// 栈区
int stack_start, stack_end;
// 堆区
int heap_start, heap_end;
// 等等... 其他属性
};
これはプロセスのアドレス空間を表しているのではないでしょうか?スタック領域やヒープ領域は増加しますか?メンテナンス方法は?開始値または終了値を直接変更することはできないのでしょうか?プロセスのアドレス空間は実際には構造体であり、前に学習した PCB 制御ブロック task_struct も mm_struct のポインターを保存します。
4. 残りの問題を解決する
実験を開始したときは、同じ変数と同じアドレスがあったのに、なぜ異なる値になったのでしょうか? fork関数の戻り値もありますが、なぜ1つの変数に2つの値があるのでしょうか?以下に示すように、これらの質問には簡単に答えることができます。
子プロセスが作成されるとき、どのプロセスも g_val を変更しない場合、つまりプログラムの最初の 5 秒間は、親プロセスと子プロセスの両方がページ テーブルを通じて同じ物理アドレス空間にマップされます。親プロセスから継承しているため、ページテーブル、PCB、プロセスのアドレス空間など多くの情報も親プロセスからコピーされ、g_valの仮想アドレスも同じになります。このとき子プロセスがg_valを変更すると、以下に示すように;
このとき、子プロセスはデータを変更する必要があるため、OS はスペースを再度開き、ページ テーブルのマッピング関係を変更します。このとき、子プロセスは新しく開かれたスペースにデータを書き込み、ページのみを作成します。物理アドレスにマッピングされたデータはテーブルエントリ内で変更されるため、仮想アドレスは変更されません。したがって、アドレスは同じですが、そこに格納されている値が異なるという現象が見られます。 ; fork の戻り値が true の場合、コピーオンライト現象が発生します。
3. プロセス アドレス空間があるのはなぜですか?
1. 知識の拡大
この質問に答える前に、まず知識を追加しておきますが、プログラムがコンパイルされて実行可能プログラムが生成された後、この実行可能プログラムにはアドレスがあるでしょうか?もしそうなら、住所は何ですか?
実際、仮想アドレスの概念はオペレーティング システム内に存在するだけでなく、コンパイラもこれに準拠する必要があるため、コンパイル後、仮想アドレスはプログラムの内部で使用され、さまざまなセグメントも存在します。データセグメント、コードセグメントなど; プログラムがメモリにロードされるまで、物理スペースがプログラムに割り当てられ、ページテーブルのマッピングが埋められます。
2. プロセスアドレス空間の重要性
まず、プロセス アドレス空間を取得すると、CPU が認識するすべてのアドレスは仮想アドレスとなり、すべてのアドレスはページ テーブル マッピングを通じて見つける必要があります。実際の物理アドレスです。メモリを効果的に保護し、データの範囲外の変更が他のプロセスに影響を与えるのを防ぐことができます。ユーザーがデータを範囲外に変更すると、ページ テーブルでこの動作を検出し、操作の実行を拒否し、直接強制終了できます。現在のプロセス。これにより、メモリのセキュリティが確保されます。
2 番目に、アドレス空間を取得した後、ページ テーブル マッピングのため、プログラムが実メモリ内で連続しているとは限りません。プログラムにメモリを割り当てる方法を決定できます。私たちはプロセス アドレス空間を持っており、仮想アドレスを持っており、実際の物理アドレスがどこにあるかは気にせず、メモリ管理モジュールがメモリの割り当て方法を決定します。プロセス管理モジュールとは何の関係もありません。割り当て方法は気にしません。必要なのは、ページ テーブルと物理メモリ空間の間のマッピングを確立することだけです。これでプロセス管理モジュールが完了します。および管理モジュールのメモリ分離;
補充:
C 言語のmalloc 関数 と C++ の新しいスペースアドレスは申請されていますか?以上のことから、仮想アドレスを申請していると判断するのは難しくないので、申請後すぐにこの空間を使用しなかった場合、OS システムは物理メモリ空間を申請するのかという疑問がまた出てきます。私たちが申請した仮想空間は?答えは「いいえ、もちろん違います。私たちが malloc 仮想空間を申請して使用しなかった場合、OS が物理空間も申請した場合、この物理空間は使用されないため、リソースが無駄になるのではありませんか?」無駄に?
オペレーティング システムが行うことは、malloc のような関数を呼び出すと、オペレーティング システムは最初に仮想メモリ領域を適用してページ テーブルを埋めますが、実際の物理領域には適用しません。これを使用すると、オペレーティング システムが実際の物理スペースを適用し、ページ テーブルのマッピング関係を完了します。
この遅延割り当て戦略により、メモリ使用率が大幅に向上し、プロセスがゼロ認識されます。これは、プロセスがメモリ管理を考慮せず、仮想メモリ空間のみを考慮し、ページ テーブル内の対応するマッピングを見つけるだけであるためです。
3 番目に、ページ テーブル + プロセス アドレス空間により、コードとデータを物理メモリのどこにでも分散させることができますが、プロセスの観点からは連続しているように見えます。たとえば、変数 a を作成し、次に変数 A を作成します。 b、2 つの変数は仮想アドレスでは連続していると見なすことができますが、物理メモリは必ずしも連続しているわけではないため、物理アドレスはページ テーブルを通じて任意の場所にマッピングされ、順序付けされていない場合があります。コンピュータの場合、その中の複数のプロセスの場合、ページ テーブル マッピングの正確性が保証される限り、プロセス間の独立性も保証されます。
3. サスペンションを再理解する
上記の理論によってハング現象を再度理解することができます.いわゆるハングとは、特定の状況により、メモリ内の特定のプロセスのコードとデータをディスクのスワップ領域に一時的に保存する必要があることを意味します。もう一度考えてみましょう。プロセスをロードするときにコードデータをメモリに置かないことは可能でしょうか?
それは実際に可能です。メモリ リソースが非常に不足している場合、実行可能プログラムを実行します。つまり、このプログラムをメモリにロードします。しかし、メモリはすでに非常に不足しています。まず、この実行可能プログラムを OS 上に作成できます。対応する PCB 制御ブロック、プロセス アドレス空間、ページ テーブルなどのコードとデータがメモリに一時的に保存される、これもサスペンションの本質ではないでしょうか。
上記の知識を念頭に置いて、私たちが通常コンピュータでプレイする GTA5 などの 100 GB を超える大規模なゲームについて考えてみましょう。メモリは 8G または 16G しかありません。では、このような大規模な実行可能プログラムをどのように変換すればよいでしょうか。 ? メモリにロードされた場合はどうなりますか?まず、完全にメモリにロードするのは絶対に非現実的です サスペンドの話では、コードやデータをメモリにロードせずにカーネルデータだけを作成しましたが、その後は確実に部分的にロードできます コードとデータ使用する必要がありますか? メモリにロードするだけです。長期間使用しない場合は、メモリを交換します。これにより、プログラム全体がメモリにロードされたかのような錯覚を引き起こす可能性があります。