Mímisbrunnr知恵の泉

← コンピュータ基礎 一覧

🎓 レベル:基礎 | 重要度:A(必須)

📎 前提:OSの役割とカーネル | 関連:CPUスケジューリング競合状態とクリティカルセクション

要点(BLUF)

概念 ── プログラムとプロセスは違う

ディスク上の実行ファイルは「レシピ(静的)」、それを読み込んでCPUが実行している状態が「料理中(動的)」=プロセスです。同じプログラムから複数のプロセスを起動でき(ブラウザのタブごとのプロセスなど)、それぞれが独立した状態を持ちます。

1つのプロセスは、メモリ上に次の領域を持ちます。

flowchart TB
    subgraph PS["プロセスのアドレス空間(上が高位アドレス)"]
      stack["スタック(関数呼び出し・局所変数 / 下に伸びる)"]
      gap["… 空き …"]
      heap["ヒープ(malloc等の動的確保 / 上に伸びる)"]
      bss["BSS・データ(大域変数・静的変数)"]
      text["テキスト(機械語命令・読み取り専用)"]
    end

このレイアウトがどう作られるかは リンクとロード(実行ファイルがプロセスになるまで) で詳しく扱います。

仕組み① ── PCBと文脈切り替え

OSは各プロセスの情報を PCB(Process Control Block) という構造体で管理します。中身は、プロセスID、状態、PC・レジスタの退避値、メモリ管理情報(ページテーブルの位置:仮想記憶とページング)、開いているファイル一覧など。

CPUは1つ(1コア)しかないので、複数プロセスを高速に切り替えて並行に見せます。切り替えの瞬間にOIが行うのが文脈切り替え

sequenceDiagram
    participant A as プロセスA
    participant K as カーネル
    participant B as プロセスB
    A->>K: タイムスライス終了/割り込み
    Note over K: Aのレジスタ・PCをA-PCBへ退避
    Note over K: B-PCBからBのレジスタ・PCを復元
    K->>B: Bの続きから実行再開

ポイントは、文脈切り替えは何の仕事も進めない純粋なオーバーヘッドだということ。レジスタ退避・復元、キャッシュやTLB(仮想記憶とページング)の中身が新プロセス向けに入れ替わる(汚染される)コストもかかります。だから切り替えは「必要なときだけ」が原則で、頻度はスケジューラ(CPUスケジューリング)が握ります。

仕組み② ── プロセスの状態遷移

プロセスは一生のうちにいくつかの状態を行き来します。

stateDiagram-v2
    [*] --> 生成: 作成(fork)
    生成 --> 実行可能: 準備完了
    実行可能 --> 実行中: ディスパッチ(CPU割り当て)
    実行中 --> 実行可能: タイムスライス終了(プリエンプト)
    実行中 --> 待機: I/O要求などでブロック
    待機 --> 実行可能: I/O完了(割り込み)
    実行中 --> 終了: exit
    終了 --> [*]

肝は 実行中 → 待機。プロセスがI/Oを頼んで結果を待つ間(入出力とバス・割り込み)、CPUを手放して別の実行可能プロセスに譲ります。これでCPUが遊ばない。I/O完了の割り込みが来たら、待機していたプロセスが再び実行可能に戻ります。

仕組み③ ── プロセス生成(fork/exec)とスレッド

UNIX系では、プロセスは fork で親のコピーとして生まれ、exec で中身を別プログラムに入れ替えます。

pid_t pid = fork();        // 親のほぼ完全な複製を作る(戻り値で親子を判別)
if (pid == 0) {            // 子プロセス側
    execvp("ls", args);    // 自分自身を ls に置き換えて実行
} else {                   // 親プロセス側
    wait(NULL);            // 子の終了を待つ
}

シェルがコマンドを起動する仕組みがこれです。fork で自分を複製し、子側で exec して目的のコマンドに化け、親は wait で待つ。

スレッドは、この重いプロセスの中に複数の「実行の流れ」を持たせる軽量版。同じプロセスのスレッドはコード・ヒープ・大域変数・開いたファイルを共有し、各自が専用に持つのはスタックとレジスタ(=PC)だけです。

プロセススレッド(同一プロセス内)
アドレス空間独立(保護される)共有
生成・切替コスト重い軽い
通信IPCが必要共有メモリで直接
一方のクラッシュ他に波及しにくいプロセス全体が落ちうる
危険干渉しにくいデータ競合が起きやすい

仕組みの直観 ── なぜこの設計か

⚠️ よくある誤解・落とし穴

対応ラボ

cs-foundations-study/labs/02-02_fork.c(Linux: fork/exec/wait の最小C。親子の出力順とPIDを観察する参照コード)。

関連

第2章 オペレーティングシステム 目次