メモの日々


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;
}

やること

  • シュレッダー
  • 健康保険料
  • 請求書