Linuxプロセスの状態と優先度のしくみ

プロセスの状態:カーネルが管理する実行フェーズ

Linuxにおけるプロセスは、単に「動いている」わけではなく、カーネルによって厳密に定義された複数の状態(state)を持っています。これらの状態は、task_struct構造体を通じて管理され、タスクスケジューラが次にどのプロセスを実行すべきかを判断する根拠となります。

static const char * const task_states[] = {
    "R (running)",      // 実行可能または実行中
    "S (sleeping)",     // 割り込み可能な待機
    "D (disk sleep)",   // 割り込み不可のIO待機
    "T (stopped)",      // シグナルによる停止
    "t (tracing)",      // デバッガによる一時停止
    "X (dead)",         // 終了(表示されない)
    "Z (zombie)"        // 終了待ちのゾンビ状態
};

1. 実行可能状態 R (Running)

R状態は「CPU上で動作中」または「実行キューに登録されて実行待ち」という意味です。たとえば以下のような無限出力プログラム:

#include <iostream>
#include <unistd.h>

int main() {
    while (true) {
        std::cout << "Executing...\n";
        usleep(1000);  // 1msの短い待機
    }
}

このプロセスはpsコマンドで確認するとR+と表示されます。+はフォアグラウンドプロセスであることを示し、Ctrl+Cで終了できる状態です。

2. 待機状態 S (Sleeping)

S状態は「割り込み可能な睡眠(interruptible sleep)」とも呼ばれ、I/O操作の完了を待っている状態です。例えば画面出力を行う際、プロセスはディスプレイドライバの準備ができるまで一時的にブロックされます。

while (true) {
    counter++;
    std::cout << "Tick: " << counter << "\n";
}

このコードは見た目には連続実行のように見えますが、実際には出力ごとにS状態に遷移しています。CPUサイクルの大部分は待機時間であり、R状態はごく短時間です。そのため、プロセス監視ツールではS状態として観測されることが多いです。

3. 不可殺状態 D (Uninterruptible Sleep)

D状態は、主にディスクI/Oなどのハードウェア操作中に発生します。この状態のプロセスはシグナルでも終了できません。これはデータ破損を防ぐための重要な仕組みです。

たとえば、重要なデータをストレージに書き込んでいる最中にOOM Killerによって強制終了された場合、データ整合性が失われる可能性があります。これを防ぐために、カーネルはI/O処理中のプロセスをD状態にし、外部からの介入を拒否します。

4. 停止状態 T および t

T状態はSIGSTOPシグナルによりプロセスが一時停止した状態です。SIGCONTを送信することで再開できます。

// 別の端末から
kill -19 <PID>  # 停止
kill -18 <PID>  # 再開

t状態は特別な停止形態で、gdbなどのデバッガがプロセスをトレースしているときに現れます。ブレークポイントで停止したプロセスはt状態になります。

5. ゾンビ状態 Z (Zombie)

子プロセスが終了しても、親プロセスがその終了ステータスを読み取るまで、プロセス識別情報(PCB)は解放されず、Z状態として残ります。

#include <iostream>
#include <sys/wait.h>
#include <unistd.h>

int main() {
    pid_t child = fork();
    if (child == 0) {
        std::cout << "Child exits\n";
        _exit(0);
    } else {
        std::cout << "Parent sleeping, child becomes Z\n";
        sleep(20);  // 親が長時間スリープ
    }
    return 0;
}

この間、プロセスのPIDや終了コードはカーネルが保持しており、メモリリークの原因になります。親プロセスはwait()またはwaitpid()を呼び出して子の終了を確認すべきです。

6. 孤児プロセスとinitの役割

親プロセスが先に終了した場合、子プロセスは孤児(orphan)となり、システムの初期プロセス(PID 1、通常はsystemd)が自動的に養子縁組を行います。

if (fork() == 0) {
    // 子プロセス
    for (;;) {
        std::cout << "Orphaned PID: " << getpid()
                  << ", Parent PID: " << getppid() << "\n";
        sleep(2);
    }
} else {
    // 親プロセスは即終了
    _exit(0);
}

このとき、子プロセスのPPIDは1に変化し、systemdが終了通知を受け取り、リソースを適切に回収します。

プロセスの優先度制御

Linuxでは、複数のプロセスが効率的にCPUリソースを共有するために、優先度(priority)とニース値(nice value)という仕組みがあります。

PRI と NI の関係

  • PRI:カーネル内部での実行優先順位。値が小さいほど優先度が高い(例:PRI=10 のプロセスは PRI=15 よりも優先して実行される)
  • NI (nice):ユーザーが調整可能な補正値。範囲は -20 ~ +19

実効PRIは以下のように計算されます:

PRInew = PRIdefault + NI

つまり、NIを負の値に設定することで実質的な優先度を上げることができますが、これは特権ユーザ(root)のみが許可されています。

優先度の変更方法

topコマンドを使用してリアルタイムに変更可能です:

  1. topを起動
  2. rキーを押下
  3. 対象プロセスのPIDを入力
  4. 新しいnice値を指定(例:-5で優先度アップ)

また、コマンドラインからreniceコマンドを使うこともできます:

renice -5 1234  # PID 1234のプロセスを高優先度に

高負荷環境では、特定のプロセスを特定のCPUコアにバインド(tasksetコマンド)することで、キャッシュ効率やコンテキストスイッチの削減が可能になり、システム全体のパフォーマンス向上につながります。

タグ: linux プロセス管理 プロセス状態 優先度 nice値

9月29日 08:58 投稿