2. プロセスと仮想メモリ

2.1 プロセスとスレッドを分ける

プロセスは、実行中のプログラムに、仮想アドレス空間、ファイル記述子、権限などを結び付けたOS上の実行単位である。スレッドはその中の実行の流れで、各スレッドは個別のレジスタ状態とスタックを持つ。

この資料は単一スレッドだけを扱う。「プロセスのRIP」と簡略に言う場合も、正確には「実行中のスレッドのRIP」を指す。

2.2 メモリは番号付きのバイト列

メモリの各バイトには番号がある。その番号がアドレスである。ポインタはアドレスを値として保持する変数である。

char name[16];        // 16バイトの配列
char *p = name;       // pはname[0]のアドレスを持つ

16バイト配列、先頭アドレス、書き込み長の関係

図: 先頭アドレスと長さは別の情報である。ポインタ単体には配列の大きさが入っていない。

read(STDIN_FILENO, name, 128)の第2引数は書き込み先の先頭アドレス、第3引数は最大書き込み数である。readに渡される情報に「nameは16バイト」は含まれない。

2.3 仮想アドレス空間の全体

プロセスの仮想メモリ全体とread_name実行中のスタック拡大図

図: 図のスタックは上が高アドレス、下が低アドレス。x86-64では通常、使用領域が低アドレス側へ広がる。

領域 今回の例 主な用途
命令領域 win, greet, read_name, main CPUが命令として取得する
読み取り専用データ "YOU WIN", "Goodbye" 定数文字列
ヒープ 本例では未使用 動的に確保するオブジェクト
共有ライブラリ puts, printf, read, exit 外部関数の実装
スタック name[16], 保存されたフレーム情報、戻り先 関数呼び出しに伴う一時状態

C言語の仕様が「すべての局所変数をスタックに置く」と定めているわけではない。コンパイラは最適化によってレジスタだけに置いたり、変数自体を消したりできる。このビルド条件では、観察しやすくするためnameがスタック上に置かれる。

2.4 ページと権限

仮想メモリはページ単位で管理され、主に読み取り、書き込み、実行の権限を持つ。

命令領域       読取=可  書込=不可  実行=可
読取専用データ 読取=可  書込=不可  実行=不可
ヒープ/スタック  読取=可  書込=可    実行=通常不可

同じバイト列でも、CPUがデータとして読むか、命令位置から命令として取得するかで意味が変わる。ただし実行権限のないページからの命令取得はOSが停止させる。