第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 aux | BSD形式。全ユーザーの全プロセスをCPU/メモリ使用率つきで表示 |
ps -ef | UNIX形式。親プロセス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)していないために一覧に残り続けているものです。ゾンビプロセス自体はリソースをほとんど消費しませんが、大量に発生している場合は親プロセスの実装に問題がある可能性があります。
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もよく使われます。
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コマンドは名前に反して、実際には指定したシグナルをプロセスに送るコマンドです。
| シグナル | 番号 | 意味 |
|---|---|---|
| SIGHUP | 1 | 端末切断。デーモンでは「設定ファイルの再読み込み」の意味で使われることが多い |
| SIGINT | 2 | 割り込み(Ctrl+Cを押したときに送られるもの) |
| SIGKILL | 9 | 強制終了。プロセス側で無視・後処理ができない |
| SIGTERM | 15 | 正常終了の要求(killの既定値)。プロセスは後片付けをしてから終了できる |
| SIGSTOP | 19 | プロセスを一時停止させる(Ctrl+Zと同種の効果)。プロセス側で無視できない |
| SIGCONT | 18 | 停止中のプロセスを再開させる |
$ 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上での優先度制御の考え方を理解しておくと、コンテナ技術を学ぶ際にも応用が利きます。