第10章 プロセス優先度とジョブ制御の応用

LPIC-1 103.5 / 103.6 相当

初級の第7章ではプロセスの基本操作を扱いました。ここでは、複数のプロセスが同時に動くときの「優先度」の考え方を学びます。

優先度(nice値)とは

Linuxのプロセスには nice値 という、CPUをどれだけ譲るかを示す数値があります。範囲は -20(最優先)〜19(最も譲る)で、既定値は0です。名前のとおり、値が大きいほど他のプロセスに「優しく(nice)」なります。

なぜこの仕組みが必要かというと、CPUは同時に1つのプロセスしか処理できない(マルチコアでもコア数分しか同時に処理できない)ため、カーネルのスケジューラは「どのプロセスに次のCPU時間を割り当てるか」を常に判断しています。nice値はこの判断材料の一つで、値が低い(マイナス側)プロセスほど優先的にCPU時間を割り当ててもらいやすくなります。バッチ処理やバックアップのような、多少遅くても構わないが他の対話的な処理の邪魔をしたくない作業には、nice値を上げて優先度を下げておくのが定石です。

nice — 優先度を指定して起動する

$ nice -n 10 ./heavy-task.sh    # nice値10で起動(優先度を下げる。プロセス自体の出力以外は表示なし)
$ sudo nice -n -5 ./important.sh # 優先度を上げるにはroot権限が必要(同様に無出力)
$ nice                          # 引数なしで実行すると、現在のシェルのnice値(既定0)を表示
0

一般ユーザーは自分のプロセスのnice値を「上げる」(優先度を下げる)ことはできますが、一度上げた値を元に戻したり、マイナス値にして優先度を上げたりするにはroot権限が必要です。これは、一般ユーザーが自分のプロセスの優先度を勝手に最優先にしてシステム全体を占有してしまうのを防ぐための制限です。

renice — 実行中のプロセスの優先度を変更する

$ renice 5 -p 1523      # PID 1523のnice値を5に変更
1523 (process ID) old priority 0, new priority 5
$ renice -n 5 -p 1523   # -n を付ける書式もある(意味は同じ)
1523 (process ID) old priority 5, new priority 5
$ renice 10 -u alice    # -u でユーザー単位(aliceの全プロセス)に変更することも可能
1000 (user ID) old priority 0, new priority 10

この行のこの値に注目: reniceは変更対象ごとにold priority ... new priority ...という形式で変更前後のnice値を報告します。-u aliceの場合は個々のPIDではなくユーザーID単位でまとめて報告される点が、PID指定の場合との違いです。

よくある間違い: nice は「これから起動するプロセス」に対して使い、renice は「すでに動いているプロセス」に対して使います。逆に覚えていると試験でもひっかかりやすいポイントです。すでに起動してしまったプロセスの優先度を下げたい場合、nice ではなく renice を使う必要があります。

uptime とロードアベレージ

uptime はシステムの稼働時間に加えて、ロードアベレージ(load average)という、CPUの実行待ちが発生しているプロセス数の平均を表示します。

$ uptime
 10:32:01 up 5 days,  3:11,  2 users,  load average: 0.15, 0.22, 0.30

3つの数値は、それぞれ直近1分・5分・15分の平均です。この値がCPUのコア数を大きく上回っている状態が続く場合、システムに負荷がかかりすぎている可能性があります。例えば4コアのサーバーでロードアベレージが8.0で高止まりしているなら、常にCPUの空きを待っているプロセスが存在することを意味し、処理の遅延や応答性の低下につながります。逆に1分値だけが一時的に跳ね上がっていて5分・15分値が低い場合は、瞬間的な負荷の可能性が高く、傾向を掴むには3つの数値の推移を見比べることが大切です。

ps のオプションの違い

コマンド特徴
ps auxBSD形式。全ユーザーの全プロセスをCPU/メモリ使用率つきで表示
ps -efUNIX形式。親プロセスID(PPID)が確認しやすい
pstreeプロセスの親子関係をツリー形式で表示
ps -eo pid,ni,cmd-o で表示項目を自由に指定(この例ではPID・nice値・コマンド名だけを表示)

ps aux の出力にはいくつも列がありますが、代表的なものだけでも意味を押さえておくと障害調査の際に役立ちます。

$ ps aux | head -3
USER   PID  %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root     1   0.0  0.1 168000 11000 ?        Ss   Aug10   0:03 /sbin/init
alice  4821   0.2  0.3  22000  8000 pts/0    S    10:30   0:00 sleep 300
列意味
%CPU / %MEMそのプロセスが使用しているCPU時間・メモリの割合
VSZ / RSS仮想メモリサイズ / 実際に確保している物理メモリ量(KB)
STATプロセスの状態(例: R=実行可能, S=割り込み可能な待機, D=割り込み不可能な待機, Z=ゾンビ, T=停止中)
TTY紐づいている端末。?はデーモンなど端末を持たないプロセス

STATがZ(ゾンビ)のプロセスは、処理自体はすでに終了しているものの、親プロセスが終了ステータスを回収(wait)していないために一覧に残り続けているものです。ゾンビプロセス自体はリソースをほとんど消費しませんが、大量に発生している場合は親プロセスの実装に問題がある可能性があります。

生成 fork() 実行可能 R 選択 実行権放棄 実行中 CPUを使用中 I/O待ちなど I/O完了 待機 S(割込可)/ D(不可) SIGSTOP SIGCONT 停止 T exit() ゾンビ Z 親がwait() で回収 消滅
プロセスの状態遷移。生成されたプロセスは実行可能状態(R)を経て、スケジューラに選ばれると実行中になります。I/O待ちで待機状態(S/D)に、SIGSTOPで停止状態(T)に移ることがあり、いずれも条件が整えば実行可能状態に戻ります。実行中のプロセスがexitするとゾンビ状態(Z)になり、親プロセスがwait()で終了ステータスを回収すると一覧から消滅します。

topコマンドによるリアルタイム監視

psがその瞬間のスナップショットを表示するのに対し、topは一定間隔で情報を更新し続け、CPUやメモリを多く使っているプロセスを一目で把握できます。

$ top
top - 10:32:01 up 5 days,  3:11,  2 users,  load average: 0.15, 0.22, 0.30
Tasks: 120 total,   1 running, 119 sleeping
%Cpu(s):  3.2 us,  1.1 sy,  0.0 ni, 95.5 id
PID   USER  PR  NI  %CPU  %MEM  COMMAND
4821  alice  20   0   0.2   0.3  sleep

top実行中に押せるキーにもよく使うものがあります。kでPIDを指定してシグナルを送信、rでreniceと同様に優先度を変更、qで終了です。より高機能な派生版としてhtopもよく使われます。

豆知識: top出力のNI列がnice値、PR列はカーネルが実際に使用する優先度(priority)で、nice値をもとに算出されます。ps aux の場合はps -elのように-lを付けるとNI列やPRI列を確認できます。

ジョブ制御 — フォアグラウンドとバックグラウンド

時間のかかる処理は、バックグラウンドで実行しつつ他の作業を続けることができます。

操作意味
コマンド &コマンドをバックグラウンドで実行しつつ、すぐにプロンプトへ戻る
Ctrl+Z実行中のフォアグラウンドジョブを一時停止する
jobs現在のシェルが管理しているジョブの一覧を表示
fg %1ジョブ番号1をフォアグラウンドに戻す
bg %1停止中のジョブ番号1をバックグラウンドで再開
$ sleep 300 &
[1] 4821
$ jobs
[1]+  Running    sleep 300 &
$ fg %1     # フォアグラウンドに戻す(Ctrl+Cで中断可能になる)
sleep 300

この行のこの値に注目: fgを実行すると、その時点でフォアグラウンドに戻すコマンドライン自体(sleep 300)がまずエコー表示され、その後は処理が終わるかCtrl+C/Ctrl+Zを押すまでプロンプトに戻りません。

Ctrl+Zで止めた直後のジョブは「停止中(Stopped)」の状態で、CPUを消費していません。bg %1でバックグラウンドに回すと、止まっていた処理が再開してバックグラウンドで動き続けます。jobsの出力にある+は「直前に操作したジョブ(カレントジョブ)」、-は「その一つ前のジョブ」を表す記号で、%+や%-でもそれぞれを指定できます。

nohup と disown — ログアウト後もジョブを続ける

通常、端末を閉じたりSSH接続が切れたりすると、そのシェルが管理していたジョブにはSIGHUPシグナルが送られ、処理も一緒に終了してしまいます。長時間かかる処理をログアウト後も続けたい場合には、次のような方法があります。

方法特徴
nohup コマンド &SIGHUPを無視するようにしてバックグラウンド起動。標準出力は既定でnohup.outに保存される
disown %1すでに起動済みのジョブをシェルの管理対象から外し、ログアウト時にSIGHUPが送られないようにする
screen / tmux端末セッションそのものを維持する仕組み。切断後も再接続して続きを確認できる
$ nohup ./long-task.sh &
[1] 5012
$ disown %1     # 既に起動済みのジョブ番号1をシェルの管理対象から外す(正常時は無出力)

シグナルの送信

プロセスへ終了などの要求を伝える手段が「シグナル」です。killコマンドは名前に反して、実際には指定したシグナルをプロセスに送るコマンドです。

シグナル番号意味
SIGHUP1端末切断。デーモンでは「設定ファイルの再読み込み」の意味で使われることが多い
SIGINT2割り込み(Ctrl+Cを押したときに送られるもの)
SIGKILL9強制終了。プロセス側で無視・後処理ができない
SIGTERM15正常終了の要求(killの既定値)。プロセスは後片付けをしてから終了できる
SIGSTOP19プロセスを一時停止させる(Ctrl+Zと同種の効果)。プロセス側で無視できない
SIGCONT18停止中のプロセスを再開させる
$ kill 4821            # SIGTERM(既定)を送信し、正常終了を要求(正常時は無出力)
$ kill -9 4821         # SIGKILLで強制終了(正常時は無出力)
$ kill -l               # 利用可能なシグナルの一覧と番号を表示
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
...(この後もシグナル一覧が続く)
15) SIGTERM
$ killall nginx        # プロセス名を指定して一括送信(正常時は無出力)
$ pkill -u alice        # 特定ユーザーが所有する全プロセスへ送信(正常時は無出力)

この行のこの値に注目: kill -lの一覧番号は、表内で紹介したSIGKILL(9)やSIGTERM(15)と一致しており、kill -9のように番号でシグナルを指定する際にこの一覧で正確な番号を確認できます。kill・killall・pkillはいずれも、対象が見つかって正常にシグナルを送れた場合は何も表示しません。

試験対策: kill -9(SIGKILL)はプロセスに後片付けの猶予を与えないため、まずはkill(SIGTERM)で正常終了を試み、応答がない場合の最終手段として使うのが基本です。SIGKILLとSIGSTOPはプロセス側でハンドラを登録して無視することができない特別なシグナルである点も、あわせて覚えておくとよいでしょう。
よくある間違い: kill 1234のようにPIDを直接指定するのが基本ですが、シェルのジョブ番号を使いたい場合はkill %1のように%を付ける必要があります。kill 1のようにジョブ番号のつもりでPID扱いの番号を渡すと、まったく別のプロセス(最悪の場合init/systemdなど)にシグナルが送られてしまうため注意してください。

I/Oスケジューリングクラス(ionice)

nice/reniceがCPU時間の優先度を扱うのに対し、ディスクの読み書き(I/O)の優先度を調整したい場合はioniceコマンドを使います。バックアップ処理のように大量のディスクI/Oを発生させる作業を、他のサービスの応答性を落とさずにバックグラウンドで実行したい場合に有効です。

$ ionice -c 3 -p 4821       # PID 4821のI/O優先度をIdle(他に処理がないときだけ実行)に設定
$ ionice -c 2 -n 7 rsync -a /data /backup   # Best Effortクラスの最低優先度でrsyncを起動

CPU負荷は低いのにサーバー全体が重い、という場合はCPUではなくディスクI/Oがボトルネックになっている可能性があり、ioniceや後述のiostatのようなI/O関連のツールを確認する視点も持っておくと、原因の切り分けの幅が広がります。

プロセスの優先度とコンテナ環境

近年ではDockerなどのコンテナ上でアプリケーションを動かすことも一般的になっていますが、本章で扱ったnice/ioniceの考え方は、コンテナ環境でも形を変えて生きています。Dockerの--cpu-sharesや--memoryのようなオプションは、複数のコンテナが同じホストのリソースを取り合う際の割り当て比率を指定するもので、根本的な発想はLinuxのプロセス優先度制御(cgroupsという仕組みを土台にしています)と共通しています。ホストOS上での優先度制御の考え方を理解しておくと、コンテナ技術を学ぶ際にも応用が利きます。

確認クイズ

Q1. nice値について正しい説明はどれですか?

解説: nice値は-20〜19の範囲で、値が大きいほど優先度が下がり、他のプロセスにCPUを譲るようになります。

Q2. すでに実行中のプロセスの優先度を後から変更するコマンドはどれですか?

解説: nice は新規にプロセスを起動する際に優先度を指定するのに対し、renice は実行中のプロセスの優先度を変更します。

Q3. uptimeコマンドで確認できるロードアベレージが表す内容はどれですか?

解説: ロードアベレージは、直近1分・5分・15分におけるCPUの実行待ちプロセス数の平均で、システム負荷の指標として使われます。

Q4. プロセスの親子関係をツリー形式で視覚的に表示するコマンドはどれですか?

解説: pstree はプロセスの親子関係をツリー形式で表示し、どのプロセスが何を起動したかを視覚的に把握できます。