第 5 回 (2002.05.24) -- プロセス制御

unix はマルチタスクシステムであり, ユーザはその恩恵を最大限に利用するべきである. しかし,gui では直感的に理解できるマルチタスクも cui ではその状態が見えにくい. そのため,cui 初心者状態ユーザはせっかくのマルチタスクを生かせずに dos のようなシングルタスク環境とあまり変わらない状態で unix cui 環境を利用していることがまま見受けられる. こうした状況は大いなる時間の無駄といえる.
そこで今回の授業では,ユーザがプロセスを制御するとはどういうことかを学び, cui 環境でも大いにマルチタスクの恩恵を受けられるようになることを目指す.

プロセス & ジョブ制御

ユーザから見た場合,プロセス制御とジョブ制御はほとんどシームレスに繋がっており, 区別する必要は余りない. しかしその為にかえって理解が深まらない可能性は高い.
そこでプロセス制御とジョブ制御を明確に区別して学習し,理解してから それらを使いこなすことを目的とする.

プロセスとは

マルチタスク
同時に複数の処理をこなすこと,もしくはそれらの処理. コンピュータの場合,この処理とはだいたいプログラムのことだと思ってよいだろう.
# マルチタスクなシステムとしては,unix, windows, mac os などが挙げられるだろう. 逆に,dos などはシングルタスクシステムであり,(実質的には) 一度に一つのプログラムしか動作させられない.
unix システムがマルチタスクシステムであることから, 以下に解説するプロセス & ジョブ制御が必要となってくる.

プロセス
unix のようなマルチタスクシステムでは,同時に多くのプログラムが動作している. しかし,一つのプログラムを良くみると様々な機能から構成されている. そこで,こうした機能毎に動作を分離することで,様々な処理を能率良く,かつ より分かりやすく安全に行なうことができる.
この時の,コンピュータ上で動作する最小単位をプロセスと言う(言った). (つまり,一つのプログラムは複数もしくは一つのプロセスからなる).
# 実際は,動作時間やリソース(Resource, 資源)をさらに効率的に用いるために,プロセスをさらに細分化した「スレッド」という概念がある.
# ただし,スレッドはそれ自体ではあまり独立した存在ではないため, ユーザが制御できる最小単位はプロセスということになる.


よって,コンピュータの動作は, 「多くの(ほぼ独立な)プロセスが同時に動作している」と理解すればよい.

プロセス制御
unix 上ではほぼ常に多数のプロセスが(擬似的に同時に)動作している. この時,個々のプロセスを(一時)停止したり, 優先順位を上下させたりすることができないと, 何かトラブルが起きたときなどにシステムを停止するしか手段がないことになる.
個々のプロセスの動作とシステムそのものの動作を独立にするために提供される手段, それがプロセス制御である.
また,プロセス制御を適切に行なうことにより,システム全体のパフォーマンスを上げることも可能である.

プロセスの親子関係
unix では,全てのプロセスはなんらかのプロセスによって生成される. これを「親子関係」と見なして,もとのプロセスを新しいプロセスの 「親プロセス」と呼ぶ.
「子プロセス」の「死体処理(ゾンビ状態のプロセスを消滅させること)」をするのは親プロセスの役目であるため, 子プロセスが親プロセスよりも先に死ななければいけない.
よって,何らかの理由により親プロセスが先に死んでしまった場合, ゾンビになった子プロセスの処理が難しくなることがある.

プロセスの制御方法

プロセスを作る,動作させる

プロセスとは上に述べたようにプログラムを構成する単位であるため, ユーザから見たとき,プログラムを実行することは即ちプロセスを生成することである.
逆に,プログラムの実行が完全に終わったときには, そのプログラムによって作られたプロセスは全て消滅した,ということになる.

プロセスの状態を知る

プロセスの状態を知るには,ps コマンドを用いる.
ただし,そのままだと「自分が実行した」プロセスしか表示されない. 他人が実行しているプロセスなども見たいときは, 適切なオプションを用いる必要がある.
お勧めはとりあえず ps -axu である. より詳しい情報を知りたいときは,man ps でオンラインマニュアルをひくこと.

注1) 便宜のために記しておくと,ps -O キーワード でそのキーワードに相当する情報を二番目のヘッダとして得られる.
注2) ps の他に top というコマンドでもプロセスの様子を見ることができる. 面白いので一度実行してみるとよい.

■ ps コマンドの使い方による違いの例 ■
コマンド 結果例
ps
  PID TTY          TIME CMD
29452 pts/0    00:00:00 sh
29475 pts/0    00:00:00 tcsh
31982 pts/0    00:00:00 ps
        
ps -axu
USER       PID %CPU %MEM   VSZ  RSS TTY      STAT    START   TIME COMMAND
root         1  0.0  0.0  1116   64 ?        S       May22   0:05 init [5]
root         2  0.0  0.0     0    0 ?        SW      May22   0:00 [kflushd]
root         3  0.0  0.0     0    0 ?        SW      May22   0:00 [kupdate]
root         4  0.0  0.0     0    0 ?        SW      May22   0:05 [kswapd]
        
…略…
ot-026fd 29452  0.0  0.8  1888 1132 pts/0    S       15:07   0:00 -sh
root     29455  0.0  0.0     0    0 ?        SW      15:07   0:00 [rpciod]
root     29456  0.0  0.0     0    0 ?        SW      15:07   0:00 [lockd]
ot-026fd 29475  0.0  1.0  2332 1328 pts/0    S       15:07   0:00 -csh
root     31202  0.0  7.3 35344 9412 ?        S       16:36   0:02 /etc/X11/X -au
root     31203  0.0  0.7  3188  952 ?        S       16:36   0:00 /usr/bin/gdm -
root     31211  0.0  3.6  6956 4684 ?        S       16:36   0:00 ruby /etc/ecip
root     31212  0.0  0.9  2320 1216 ?        S       16:36   0:00 /usr/bin/xpeng
gdm      31214  0.0  2.9  6688 3756 ?        S       16:36   0:00 /usr/bin/gdmlo
ot-026fd 31984  0.0  0.6  2460  796 pts/0    R       17:45   0:00 ps -axu
        

ps コマンドで見られる情報のうち,重要なものをいくつか解説しておこう.

■ ps コマンドで見られる情報の主なもの(指定できるキーワード) ■
キーワード 意味
pid プロセス ID. 各々のプロセスに固有な番号. 人間で言えば,個人名である.
プロセスの制御はこの PID を基本として行なわれる.
user ユーザ名. そのプロセスの利用者.
注) 通常ユーザは,自分が利用しているプロセスしか制御できない.
%cpu cpu 利用率. 通常は数〜数十パーセント程度であり, この数字が異様に高い(100% に近い)状態が長時間続き, かつ,負荷の高いプロセスを走らせているつもりがなければ, そのプロセスが暴走している可能性がある.
%mem メモリ使用率. これも暴走しているプロセスでは大きくなる傾向がある.
tty 制御端末名. そのプロセスがどの「端末」に属しているかを示す.
stat プロセスの状態. 一文字ずつある状態を意味する. 主要なものを示しておこう.

記号 意味
S プロセスは sleep 状態(キーボード入力完了待ち)である.
R プロセスは実行可能状態.
D プロセスはディスク入出力完了待ちである.
T プロセスは停止(stop)中である.
Z プロセスはゾンビ(zombie, "死んでいる")である.

この stat を見ればプロセスの状態がわかる. T や Z という状態は(思い当たる節がなければ)何か異常が起こっていることを示唆する. また,長時間 D のままという場合も,何かおかしい可能性が高い.
command コマンド名と引数
pri (スケジューリング)優先度. 0 〜 127 の数字で,小さい方が優先度が高い.
ni nice 値. この数字とプロセスの性格などから優先度が決定される. -20 〜 20 程度の数字で,小さい方が優先度を高くできる. デフォルトは 0.

プロセスの優先度を変更する

プロセスには「優先順位」というものがあり, 優先順位が高い方がより先に実行されるようになっている. この仕組みにより,様々なプロセスが同時に動作しても 人間にとって快適に扱えるようになっている.
このプロセスの優先順位を意識的に変更する方法があり, これを用いると unix をより快適に使えるようになるだろう.

ちなみに,プロセスの優先度と nice 値を見るには, ps l (← 小文字のL(エル))とするのが簡単だ.

■ プロセスの優先度を変更するコマンド ■
コマンド名 解説
nice プログラム起動時に nice 値を決定する.
注) csh 系のシェルには nice コマンドが組み込まれていることがあり, システムに既に存在する nice コマンド(/bin/nice)と混同しやすい.
どちらも働きは同じだが,実際の使い方は異なるため, 使うときは必ずオンラインマニュアルで調べる必要がある.

/bin/nice -数字 コマンド

システムの nice を用いて何かプログラムを起動する. 「数字」部分には nice 値を指定する. 通常ユーザは 0〜20 しか指定できない(つまり,優先度は下がる).
renice 既に動いているプロセスの nice 値を「増やす」=「優先度を下げる」.
動作中のプロセスのせいでコンピュータの反応が鈍くなるなどの場合に, プロセスの優先度を下げて作業を快適にする,等の目的で用いる.
一度優先度を下げたプログラムの優先度を元に戻すことは出来ない. つまり,いかなる場合でも通常ユーザは nice 値を増やすことしか出来ない.

スーパーユーザだけは,nice 値を減らす,つまり優先度を上げることができる.

プロセスを終了させる(プロセスを殺す)

unix のプロセスには「プロセス間通信」といってプロセス同士が通信する仕組みが備わっている. このプロセス間通信のうち,メッセージを伝えるだけの機能を持つものとして「シグナル」がある.
このシグナルをプロセス宛に送りつけることによって,プロセスを停止させたり,再起動させたり,殺したりできる. (ただし,自分の利用しているプロセスにしかシグナルは送れない)

シグナルをプロセスに送りつけるには,
kill -シグナル pid
とする. シグナルは後述する「シグナル名」か「シグナル番号」で指定する.
シグナルを省略すると TERM (=15) を指定したことになる.

■ kill コマンドで用いるシグナル ■
シグナル番号 シグナル名 シグナルの意味
15 TERM TERMinate. 終了. kill コマンドのデフォルト.
9 KILL KILL. 「強制」終了. このシグナルを受け取ったプロセスは必ず死ぬ.
2 INT INTerrupt. 端末から割り込み(CTRL-c)を受けた,という意味. このシグナルを受けるとプロセスは死ぬ.
3 QUIT QUIT. 端末から中断終了(CTRL-\)を受けた,という意味. このシグナルを受けるとプロセスは(core を吐いて)死ぬ.
18 TSTP TerminalSToP. 端末から停止(CTRL-z)を受けた,という意味. このシグナルを受けるとプロセスは(一時)停止する.
17 STOP STOP. 「強制」停止. このシグナルを受けるとプロセスは必ず停止する.
19 CONT CONTinue. 停止状態のプロセスの実行を再開する.
1 HUP HangUP. 端末回線が切れた,ということを意味する. 普通のプロセスはこのシグナルを受けると実行を中止するが, 再起動したり特別なモードに移行するプロセスも多いので注意が必要だ.

この表から分かるように,どうしてもそのプロセスを殺したい場合は,
kill -9 pid
とするか,
kill -KILL pid
とすればよい,ということになる.

注1) state が D のプロセスや,zombie プロセスには kill -KILL が有効でない場合がある. こういう場合は,特に支障がなければ放置(^-^),支障がありそうだったら 管理者に相談するのが良い.

注2) シグナル "HUP" や pid "1" "0" "-1" は特別な意味で使われることが多いので, よくわからない場合は使わないように注意すること.
酷い目にあってみたい,という酔狂な人は自宅の unix などでスーパーユーザになって, kill 1, kill -KILL -1 などといろいろやってみよう(^-^).

ジョブとは

ジョブとは,(シェルから見た)コンピュータ上で実行される実態の単位の概念で, 「一連の仕事をしている(複数の)プロセス」 のことである.
まあ簡単に言えば,シェルで命令した「(一行分の)命令の固まり」であり, 人間にとって一つの処理と見なせるもの,と思えばよい.
# シェルの history コマンドで「一回分」のコマンドと思ってもよい.

既にプロセスという概念があるのに, なぜわざわざ 新しくジョブという概念を持ち込むのか?

プロセスというのは,あくまで「コンピュータ側から見た」概念である. それは正確で確実な概念であるが,利用者との関係はほとんど考慮されていない.
そこで,「ユーザにとっての実行単位」としてジョブという概念を定義し, そのジョブ単位をユーザから「見えなくしたり見えるようにしたり」することで マルチタスクを使いやすくする,というのがジョブという概念の根底に流れる思想である.

フォアグラウンド,バックグラウンド

シェルは人間と「対話的」に動作するのが基本であるため, シェルの上で素直に複数のプログラムを同時に動作させるとキーボードの入力等が混乱するのはミエミエである(^-^).
しかし,マルチタスクシステムである unix であるのに, シェル中では同時に複数のプログラムを使えないというのも意味がない. そこで,「人間と対話しながら作業するジョブを一つだけ」と 「そうでないジョブ(複数)」 をわけることでマルチタスクを可能としている.

フォアグラウンド
シェルを通じて人間からのキーボード入力を受けるジョブをいう. つまり,通常使っている状態のジョブはフォアグラウンドにいるのである.
キーボードは一つしかないので,フォアグラウンドジョブは常に一つしか存在できないことに注意.

バックグラウンド
人間のキーボード入力を受けられない状態で動作している時,そのジョブは 「バックグラウンドで動作している」と言う. この状態で動作するジョブは複数あってもよい.

(停止状態)
プロセスの state の該当する部分を参照せよ.

ジョブの制御

ジョブ制御とは,ジョブを上の三状態のある状態から他の状態へと移動させることや, プロセス制御と同じくジョブを「殺したり」することを言う.

■ ジョブ制御の様子 ■
job control figure ジョブには大まかにいって左図のような三つの状態があり得る.
図中にあるようなコマンドを用いてジョブの状態を変遷させることが ジョブ制御の中心となる.
ジョブ制御の残りの部分は,プロセス制御と同じく,
  • ジョブを生成する
  • ジョブの状態を知る
  • ジョブを殺す
ということになる. 詳細は以下に述べる.

ジョブを作る

ユーザから見たとき,シェルを通じてプログラムを実行したとき, シェルに投入された一塊のコマンド(群)がジョブとして生成される.
この時,後で示すようなオプションを使わなければこのジョブは自動的にフォアグラウンドジョブとして扱われる.

ジョブの状態を知る

そのシェルから見たときにどれだけのジョブがどういう状態にあるかは,
jobs
コマンドで知ることができる. 例えば,阪大教育用計算機システムで jobs コマンドを実行してみると,

[1]  - 終了                          ls --color=auto -Fga -l |
       中断                          less
[2]  + 中断                          emacs
    
のような表示が得られる. この表示は左から,
ジョブ番号 状態 コマンドと引数
というリストになっている. ジョブ番号に「+」がついているのは,下の kill, fg, bg コマンドで %ジョブ番号 を省略した場合にデフォルトで使われる番号がこれであることを意味している. (だいたいは一番大きい番号だ)

ちなみに,この例では二つのジョブが「中断」状態,つまり「停止状態」であることがわかる.

ジョブを殺す

フォアグラウンドジョブ,即ち,人間と対話しながら動作しているジョブは CTRL-c などで殺すことができる. これは コマンド操作の基本 でも示した通りである.
ただし,シェルそのものだけはこれで死なないようになっている(例外措置).

そうでないジョブ(他の状態にあるジョブ)は,上の jobs コマンドでそのジョブ番号を調べて,
kill %ジョブ番号
とすればそのジョブを殺すことができる.
注) nice 同様, kill もシェルの組み込みコマンドとシステムのコマンドの両方がある. ただし,直感的な動作の違いは余りないので気にせずに済むだろう.

ジョブの状態変遷 (1)
フォアグラウンドとバックグラウンドの行き来

コマンド &
バックグラウンドで動作するジョブを(いきなり)シェルで起動する. 例えば,x window system が動作しているときに kterm 中で xemacs & とすると,xemacs が「別窓で」起動するのが見られるが,これがそうである.
注) フォアグラウンドジョブをバックグラウンドに直接持っていく方法はない. そうしたい場合は,一旦停止状態にしてから,バックグラウンド状態にする.

fg %ジョブ番号
そのジョブ番号をもつジョブをフォアグラウンドで動かす.

ジョブの状態変遷 (2)
フォアグラウンドと停止状態の行き来

CTRL-z
フォアグラウンドジョブを停止状態にする.
ただし,シェルそのものだけはこれで停止しないようになっている(例外措置).

fg %ジョブ番号
そのジョブ番号をもつジョブをフォアグラウンドで動かす.

ジョブの状態変遷 (3)
バックグラウンドと停止状態の行き来

bg %ジョブ番号
そのジョブ番号をもつジョブをバックグラウンドで動作させる.

stop %ジョブ番号
そのジョブ番号をもつジョブを停止状態にする.

課題

  1. 例えば, less .bashrc で kterm 中に less を起動するとこれはフォアグラウンドジョブである.
    こうしたフォアグラウンドジョブを停止状態にする方法を二つ述べよ. さらにこれをフォアグラウンドに戻す方法も(一つ)述べよ. また,これらの作業を実際にやってみよ.
  2. CTRL-z ではシェルそのものは停止しない. ではシェルを停止させるにはどうしたらよいか,調べよ.
  3. 通常のコマンドは HUP シグナルを受けると実行を中止するが,これを無視させるようにするにはどうしたらよいか調べよ.
    → これによって,実行中のコマンドがログアウトによって停止しないで動作し続けることが可能になる. 大規模な計算をするなど,unix を使いこなすには結構必要な知識である.