2008年03月24日(月) [長年日記]
■ [unix] プロセスの関係についてメモ
Unixのプロセスについて、詳解UNIXプログラミングで勉強。
Ctrl-Cによる割り込みシグナルの送り先
シェルからプログラムを起動したとき、Ctrl-Cによる割り込みシグナルがどのプロセスに送られるのかを知りたかった。「第10章 シグナル」のSIGINTの説明に
端末の割り込みキー(しばしば、DELETEやControl-C)を押すと、端末ドライバが生成するシグナルである。このシグナルは、フォアグラウンドプロセスグループのすべてのプロセスに対し送られる
とあった。フォアグラウンドプロセスグループとは何か。
フォアグラウンドプロセスグループ
フォアグラウンドプロセスグループを理解するには、「プロセスグループ」「セッション」「制御端末」について知る必要がありそう。「第9章 プロセスの関係」に説明がある。
- プロセスグループ
- プロセスは必ずプロセスグループに属する。forkにより生まれたプロセスは、親プロセスと同じプロセスグループに属する。setpgid()により、プロセスを別のプロセスグループに属させることができ、また、新しいプロセスグループを作成することもできる。
- セッション
- プロセスグループは必ずセッションに属する。setpgid()により新しく作られたプロセスグループは、対象プロセスが属していたのと同じセッションに属する。setsid()により、新しいセッションとプロセスグループを作成しプロセスをそのセッションに属させることができる。
- 制御端末
- セッションは1つの制御端末を持つことができる。setsid()により新しく作られたセッションは、制御端末を持たない。制御端末を持つセッションの作り方は知らない。
以上の概念を使うと、フォアグラウンドプロセスグループの説明ができる。
- フォアグラウンドプロセスグループ
- 制御端末を持つセッションに属するプロセスグループの内の1つがフォアグラウンドプロセスグループになる。setpgid()により新しく作られたプロセスグループはフォアグラウンドではない。tcsetpgrp()により、プロセスグループをフォアグラウンドにすることができる。
試す
Linux 2.6.9 にて、以下に示す2つのプログラムを作って試してみた。2番目のプログラムの実行ファイル名を pausing として1番目のプログラムを実行すると、
- fork & execl
- fork & setpgid & execl
- fork & setsid & execl
の3通りが行われる。プロセスの状態は次のpsコマンドで確認できる。
ps o pid,ppid,pgid,sid,tpgid,tty,stat,command --forest
サンプルプログラムに続けてこのpsコマンドを実行すると、
PID PPID PGID SID TPGID TT STAT COMMAND 29135 29119 29119 29119 -1 ? S sshd: kenichi2@pts/6 29136 29135 29136 29136 31739 pts/6 Ss \_ -bash 31739 29136 31739 29136 31739 pts/6 S+ \_ Parent 31740 31739 31739 29136 31739 pts/6 S+ \_ only fork 31741 31739 31741 29136 31739 pts/6 S \_ do setpgid 31742 31739 31742 31742 -1 ? Ss \_ do setsid
という結果になった。PIDはプロセスID、PPIDは親プロセスID、PGIDはプロセスグループID、SIDはセッションID、TPGIDは当該端末に関連するフォアグラウンドプロセスグループID、TTは制御端末を表す。フォアグラウンドプロセスグループにはSTAT欄に+が表示される。意図した通りの結果になっているように見える。
プログラムを実行した端末でCtrl-Cを入力すると、
[PID=31739] Parent received signal 2. [PID=31740] only fork received signal 2.
と出力され、フォアグラウンドプロセスグループに対してだけSIGINTが配信されていることが確かめられた。このときにpsを実行すると
PID PPID PGID SID TPGID TT STAT COMMAND 29135 29119 29119 29119 -1 ? S sshd: kenichi2@pts/6 29136 29135 29136 29136 29136 pts/6 Ss+ \_ -bash 31742 1 31742 31742 -1 ? Ss do setsid 31741 1 31741 29136 29136 pts/6 S do setpgid
と出力され、フォアグラウンドはシェルのプロセスグループに変わっていた。更にシェルを終了させると
PID PPID PGID SID TPGID TT STAT COMMAND 29135 29119 29119 29119 -1 ? S sshd: kenichi2@notty 31742 1 31742 31742 -1 ? Ss do setsid 31741 1 31741 29136 -1 ? S do setpgid
となり、制御端末は無くなった。セッション29136は残っている。
また、以下のプログラムのコメントアウト部(tcsetpgrpを実行している部分)を生かすと、
PID PPID PGID SID TPGID TT STAT COMMAND 617 608 608 608 -1 ? S sshd: kenichi2@pts/6 618 617 618 618 693 pts/6 Ss \_ -bash 691 618 691 618 693 pts/6 S \_ Parent 692 691 691 618 693 pts/6 S \_ only fork 693 691 693 618 693 pts/6 S+ \_ do setpgid 694 691 694 694 -1 ? Ss \_ do setsid
のようにフォアグラウンドプロセスIDが変わっていることを確かめられる。
以下、プログラムのソース。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> static pid_t spawn(int flag) { pid_t pid = fork(); if (pid < 0) { perror("fork"); exit(1); } else if (pid == 0) { char* name; switch (flag) { case 0: name = "only fork"; break; case 1: // 新しいプロセスグループを作り本プロセスがそこに属する if (setpgid(0, 0) < 0) { perror("setpgid"); exit(1); } name = "do setpgid"; break; case 2: // 新しいセッションを作り本プロセスがそこに属する if (setsid() < 0) { perror("setsid"); exit(1); } name = "do setsid"; break; default: name = "Invalid"; break; } execl("./pausing", name, NULL); perror("execl"); exit(1); } else { // switch (flag) { // case 1: // if (tcsetpgrp(0, pid) < 0) { // perror("tcsetpgrp"); // exit(1); // } // break; // } } return pid; } int main() { spawn(0); spawn(1); spawn(2); execl("./pausing", "Parent", NULL); perror("execl"); exit(1); }
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> static volatile sig_atomic_t signal_number = 0; static void handle_signal(int signo) { signal_number = signo; } static void set_signal_hanlder(int signo) { if (signal(signo, handle_signal) == SIG_ERR) { fprintf(stderr, "signal() failed.\n"); exit(1); } } int main(int argc, char* argv[]) { set_signal_hanlder(SIGINT); set_signal_hanlder(SIGTERM); set_signal_hanlder(SIGHUP); set_signal_hanlder(SIGTSTP); set_signal_hanlder(SIGCONT); pid_t pid = getpid(); printf("[PID=%d] %s is ready.\n", pid, argv[0]); for (;;) { pause(); printf("[PID=%d] %s received signal %d.\n", pid, argv[0], signal_number); switch (signal_number) { case SIGINT: case SIGTERM: case SIGHUP: exit(0); default: continue; } } return 0; }
■ やること
- シュレッダー
- 健康保険料
- 請求書