Go to the first, previous, next, last section, table of contents.


1 プロセス制御

1.1 新しいプロセスの生成: fork()

1.1.1 fork()は何をするのですか?

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

fork()関数は存在しているプロセスから新しいプロセスを生成するために使用さ れます。新しいプロセスを子プロセスといい、すでに存在していたプロセスは親 プロセスといいます。fork()の戻り値をチェックすると、この両者を区別できま す。親プロセスには子プロセスのプロセスIDが返されますが、子プロセスには 0が返されるのです。したがって、基本は次の簡単なコードのようになります。

pid_t pid;

switch (pid = fork())
{
case -1:
    /* pidが-1, forkは失敗 */
    /* プロセステーブルのスロットや */
    /* 仮想メモリの不足などが考えられる */
    perror("The fork failed!");
    break;

case 0:
    /* pidが0なのは、子プロセス */
    /* 子プロセスの仕事は? */
    /* ... */
    /* 終了時にはこのようにする: */
    _exit(0);

default:
    /* 0より大きいpidは子プロセスのpidが戻った親プロセス */
    printf("Child's pid is %d\n",pid);
}

もちろん、switch () の代わりに if () ... else ...を使うこともできま すが、上の形は定石として使えます。

このとき役に立つのは、子プロセスには何が継承されて何が継承されないのか を知ることです。このリストはUnixの実装によって変わるかも知れませんが、大 まかなところはこのとおりです。子プロセスはこれらのコピーを得るのであっ て、そのものを得るのではないことに注意してください。

親プロセスから子プロセスに継承されるもの:

子プロセス独自のもの:

1.1.2 fork()とvfork()の違いは何ですか?

vfork()システムコールを備えたシステムがあります。このシステムコールは本 来fork()の低オーバーヘッドバージョンとして設計されました。fork()は プロセスのアドレス空間全体のコピーを行い、またそれが高くつくため、 vfork()関数が導入されたのです (3.0BSDにて)。

しかしながらvfork()が導入された後、 fork()の実装は劇的に進化します。 その最も顕著なものは 「copy-on-write」の導入です。これは親子両方のプロセスが 変更を加えない限り同じ物理メモリを参照することを許すという仕組みで、透過 的です。 これはvfork()の存在意義を大きく失わせました。実際、多くのシステムが現在 vfork()の本来の機能を実装していません。ただし互換性のため、全ての vfork()セマンティクスのエミュレーションを放棄して、fork()呼び出しとして 残していることはあります。

結果として、fork()vfork()のいかなる違いも、 その違いを実際に利用しようというのは全く賢明なことではありません。 実際、どうしてそうしたいのかを正確に知っているのでなければ、 vfork()は全く使うべきではないでしょう。

両者の基本的な違いは、vfork()で新しいプロセスが生成されたとき、 親プロセスは一時的に停止し、子プロセスが親プロセスのアドレス空間を借用することです。 この奇妙な状態は子プロセスが終了するかexecve()を呼ぶまで続き、 その時点で親プロセスは再開します。

これは、vfork()による子プロセスは不用意に親プロセスの変数を変更しないよう 注意しなければならないということを意味します。特に、子プロセスは vfork()を呼び出した関数から抜けてはならず、また、exit()を呼び出しては いけません(終了する必要があれば、_exit()を使います。 実際には、これは通常のfork()による子プロセスについても言えます)。

1.1.3 forkによる子プロセスを終了するときにexitよりも_exitを使うのはなぜですか?

exit()_exit()とは、fork()、 特にvfork()が使われているときに幾つかの違いが生じます。

exit()_exit()の基本的な違いは、前者はライブラリのユーザモードでの構築 に関するクリーンアップを行い、ユーザの定義したクリーンアップ関数を実行す るのに対して、後者はプロセスのカーネルクリーンアップのみが行われる点にあ ります。

fork()の子プロセスでは、exit()の使用は通常適切ではありません。なぜなら それはstdioバッファが2度フラッシュされ、一次ファイルが予期せず削除される ことになりかねないからです。C++コードでは状況はさらに悪化します。なぜな ら静的オブジェクトのデストラクタが不正に実行されかねないためです。 (一般的でない場合もあります。例えばデーモンでは、子プロセスではなく 親プロセスが_exit()を呼び出します。原則として、ほとんどの場合では、 main()の実行に対して一度だけexit()が呼び出されるべきです)

vfork()による子プロセスでは、exit()の使用はさらに危険となります。なぜな らプロセスの状態に影響を与えるからです。

1.2 環境変数

1.2.1 どうすればプログラム内で環境変数の値を取得・設定できますか?

環境変数の値を得るにはgetenv()を使います。

#include <stdlib.h>

char *getenv(const char *name);

環境変数の値を設定するにはputenv()を使います。

#include <stdlib.h>

int putenv(char *string);

putenvに渡される文字列は、解放されたり無効になったりしてはいけません。 なぜなら、putenv()により、その文字列へのポインタが保持されるようになるか らです。このことは、その文字列が、静的なバッファかヒープから確保されたメ モリになければならないことを意味します。別のputenv()呼び出しによってその 環境変数が再定義されたか削除された場合には、その文字列を解放できます。

環境変数は継承されることを思い出してください。それぞれのプロセスは環境変 数のコピーを別々に持っています。その結果、他のプロセス、例えばシェルの、 環境変数を変更することはできないようになっています。

環境変数TERMの値を得たいとしましょう。それには次のコードを使います。

char *envvar;

envvar=getenv("TERM");

printf("The value for the environment variable TERM is ");
if(envvar)
{
    printf("%s\n",envvar);
}
else
{
    printf("not set.\n");
}

ここで、新しい環境変数MYVARMYVALという値で作りたいとします。それはこう します。

static char envbuf[256];

sprintf(envbuf,"MYVAR=%s","MYVAL");

if(putenv(envbuf))
{
    printf("Sorry, putenv() couldn't find the memory for %s\n",envbuf);
    /* MYVARなしでは進めないなら、exit()または何らかの処理をする */
}

1.2.2 どうすれば全ての環境変数を調べられますか?

環境変数の名前を知らなければ、getenv()関数はたいして役に立ちません。この 場合、環境変数がどのように保存されているかについてもっと深く立ち入る必要があります。

グローバル変数environは、"NAME=value"というかたちの環境文字列への ポインタの配列へのポインタを保持しています。NULLポインタは配列の最後を示すため に使われます。(printenvのように)現在の環境を表示するちょっとしたプログラ ムがこれです:

#include <stdio.h>

extern char **environ;

int main()
{
    char **ep = environ;
    char *p;
    while ((p = *ep++))
        printf("%s\n", p);
    return 0;
}

一般に、変数environmain()への3つ目の、オプションの引数としても渡されます。つまり、上の例は次のようにも書けます:

#include <stdio.h>

int main(int argc, char **argv, char **envp)
{
    char *p;
    while ((p = *envp++))
        printf("%s\n", p);
    return 0;
}

しかしながら、ほぼ全てのシステムでサポートされているにも関わらず、この方 法は現実にはPOSIX標準では定められていません(一般に、先の例ほど有用でない と言えます)。

1.3 どうすれば一秒未満のsleepができますか?

sleep()関数は全てのUnixで利用できますが、秒単位でしか時間を指定できませ ん。より細かい指定をしたければ、別の方法を探す必要があります。

上記のうち、select()がおそらく最も移植性のある方法でしょう (また、奇妙なことに、usleep()や、itimerによる方法よりも ずっと効率がよいこともよくあります)。ですが、休止中にシグナルが受け取られ たときは振る舞いが異なるかも知れません。このことはアプリケーションによって 問題になるかも知れませんしならないかも知れません。

いずれを採るとしても、システムのタイマー分解能によって制約されることを理 解しておくことが重要です(ごく短い間隔で指定できるシステムもあれば、例え ば精度が数10ms単位で、それにあわせて丸めてしまうシステムもあります)。ま た、sleep()に関して言えば、指定した遅延は最低値でしかありません。 指定した時間が過ぎた後、プロセスが次にスケジュールされるまでに不確定な遅延があります。

1.4 粒度の細かいalarm()はどうすれば得られますか?

現代的Unixではアラームをsetitimer()関数を用いて実装する傾向があります。 itimer()は単純なalarm()関数よりも高い分解能と多くのオプションを備えています。 一般に、alarm()setitimer(ITIMER_REAL)は同じタイマに基づいている と仮定すべきで、そのタイマに両方のやり方でアクセスすると混乱を招きかねません。

itimerは一回きりと連続したシグナルとどちらを実装するにも使えます。また、 普通、3つのタイマが別々に使えます:

ITIMER_REAL
現実(wall clock)時間をカウントし、SIGALRMシグナルを送ります
ITIMER_VIRTUAL
プロセス仮想時間(ユーザCPU時間)をカウントし、SIGVTALRMシグナルを送ります。
ITIMER_PROF
ユーザおよびシステムCPU時間をカウントし、SIGPROFシグナルを送ります。 インタプリタによるプロファイリングのための利用を想定しています。

ただし、itimerは多くの標準には入っていません。4.2BSD以降には存在し、 POSIXリアルタイム拡張も、似たような、但し異なる関数を定義して いるにも関わらずです。

1.5 どうすれば親プロセスと子プロセスの間で通信できますか?

親プロセスと子プロセスは通常のプロセス間通信の仕組み(パイプ、ソケット、 メッセージキュー、共有メモリ)のいずれを用いてでも通信できますが、 親子関係の利点を生かした特別な通信方法もあります。

最も明らかなことは、一つには、親プロセスは子プロセスの終了ステータスを得 ることができるということです。

子プロセスは親プロセスからファイルディスクリプタを引き継ぎますから、親プ ロセスはパイプの両端をオープンし、forkして、それから親プロセスは一方の端 をクローズし、子プロセスはパイプのもう一方の端をクローズします。これが、 popen()関数を呼んで他のプログラムを走らせたときに起こることです。すなわ ち、popen()が返すファイルディスクリプタに書けば子プロセスにとってはそれ が標準入力であり、ファイルディスクリプタから読めばプログラムが標準出力に 書いたものが分かるということです(popen()のmode引数によって どちらかが決まります。両方を実現したいときには、自分でパイプを引き回しても それほど大変ではありません)。

また、子プロセスは親プロセスによって匿名でmmapされた(または特殊ファイル `/dev/zero'をmmapされた)メモリセグメントを継承します。 これらの共有メモリセグメントは無関係なプロセスからはアクセスできません。

1.6 どうすればゾンビプロセスができることを防ぐことができますか?

1.6.1 ゾンビプロセスってなんですか?

プログラムがforkして、親プロセスより前に子プロセスが終了すると、 カーネルは親プロセスが必要としたとき - 例えば、親プロセスが子プロセスの終了ステータスをチェックするかも知れません - に備えて、子プロセスの情報を幾らか保持しています。 この情報を得るためには、親プロセスはwait()を呼びます。 これが起きると、カーネルは保持していた情報を破棄します。

子プロセスの終了から親プロセスのwait()呼び出しまでの間、子プロセスは 「ゾンビ」であるといわれます(psを実行すれば、 子プロセスの状態フィールドにはそのことを示す`Z'がついています)。 そのプロセスは動いてはいませんが、 まだプロセステーブル上のエントリを占めています(他の資源は消費しないのですが、 例えばCPU使用量などでユーティリティがでたらめな数字を示すこともあります。 これはそのプロセステーブルエントリのある部分がスペース節約のため アカウンティング情報で上書きされたためです)。

プロセステーブルには一定数のエントリしかなく、システムがプロセス数不足を 来たす可能性があるので、これは良くない状態です。システムが不足を来たさないとしても、 各ユーザが同時に起動できるプロセス数には制限があり、 それは通常システムの制限より小さいものです。 一つにはこの理由のために、 fork()はそれが失敗したか常にチェックすべきなのです。

親プロセスがwait()を呼ばずに終了してしまったら、子プロセスはinitによって 引き取られます。initは子プロセス終了後のクリーンアップに必要な作業を行い ます(これはプロセスIDが1の特別なシステムプログラム -- 実際、システムが起 動した後最初に実行されるプログラムです)。

1.6.2 どうすればゾンビプロセスになることを防げますか?

親プロセスはwait()(またはwaitpid()wait3()など)を 子プロセスの終了ごとに確実に呼ばなければなりません。 または、子プロセスの終了ステータスが必要でないことをシステムに指示することができるシステムもあります。

もう一つのアプローチはfork()2回行い、直接の子プロセスは直ちに終了す ることです。これによって孫プロセスは親プロセスを失い、initプロセスがその クリーンアップを引き受けることになります。このためのコードは、 サンプルセクションのfork2()関数を見てください。

子プロセスの終了ステータスを無視するには、次のことが必要です(これが機能するか、 システムのmanページで確かめてください)。

    struct sigaction sa;
    sa.sa_handler = SIG_IGN;
#ifdef SA_NOCLDWAIT
    sa.sa_flags = SA_NOCLDWAIT;
#else
    sa.sa_flags = 0;
#endif
    sigemptyset(&sa.sa_mask);
    sigaction(SIGCHLD, &sa, NULL);

これが成功すれば、wait()関数は機能しなくなります。その仲間のどれが呼ばれても、 全ての子プロセスが終了するまで待ち、errno == ECHILD として失敗を 返します。

他の手法はSIGCHLDシグナルを受信し、シグナルハンドラがwaitpid()wait3()を呼ぶ方法です。完全なプログラムはサンプルセクションを見てください。

1.7 プログラムをデーモンとして動かすにはどうすればいいですか?

「デーモン」プロセスは、通常、端末セッションに属さないバックグラウンドプ ロセスと定義されます。ネットワークサービスや印刷など、多くのシステムサー ビスがデーモンとして行われています。

単にバックグラウンドでプログラムを起動するだけではこれらの長時間動くプロ グラムには十分ではありません。それでは開始した端末セッションからプロセス を正しく切り放せないためです。また、通常デーモンは単に手動やrcスクリプト でコマンドを実行して起動します。ですから、デーモンは自分自身を バックグラウンドにすることが期待されるのです。

デーモンになる手順はこうです:

  1. fork()し、その後親プロセスは終了します。これによってコマンドラインまたは プログラムを起動したシェルに制御が戻ります。このステップは新しいプロセスが プロセスグループリーダーにならないことを保証するためです。 次のステップのsetsid()は、プロセスグループリーダーであると失敗してしまうのです。
  2. setsid()によりプロセスグループとセッショングループのリーダーになり ます。制御端末はセッションに関連づけられており、この新しいセッショ ンは制御端末を持たないため、このプロセスは制御端末を持たないことに なります。これはデーモンとして良い性質です。
  3. 再びfork()して、親プロセス(セッショングループリーダー)は終了します。 これは、デーモンが、セッショングループリーダーでないので、以後制御端末 を持つことがないことを意味します。
  4. chdir("/")してどのディレクトリも使用中でないことを確実にします。 これを忘れると、デーモンのカレントディレクトリであるために管理者がファイルシステムを アンマウント出来ないといった事態になります。 [同様に、デーモンの処理に必要なファイルのあるディレクトリに移動するという方法もあります]
  5. umask(0)として、書き出すあらゆるファイルのパーミッションを完全に制御下に置きます。 継承したumaskが幾つかは分からないためです。 [このステップはオプションです]
  6. ファイルディスクリプタ0、1、2をclose()します。これによって親プロセ スから引き継いだ標準入力、出力、エラー出力を解放します。これらのファ イルディスクリプタがどこかにリダイレクトされているかどうか分からな いためです。制限値_SC_OPEN_MAXを知るために多くのデーモンが sysconf()を用いていることに注意してください。_SC_OPEN_MAXはプロセス 当たりの最大ファイルオープン数です。次にループに入り、全ての有り得 るファイルディスクリプタをクローズします。この必要があるかは自分自 身で決めなければなりません。同時に使えるファイルディスクリプタの数 には制限があるため、オープンされているファイルディスクリプタがあり 得ると判断したら、それらをクローズすべきです。
  7. stdin、stdout、stderr用に新しいディスクリプタをオープンします。使う つもりがなくても、オープンしておくことは良い考えです。これらの細か な扱いは趣味の問題です。ログファイルがあれば、例えば、stdoutか stderrとしてそれをオープンし、stdinには`/dev/null'をオープンしようと 思うかも知れません。または、`/dev/console'をstderrと/またはstdoutとし てオープンし、`/dev/null'をstdinとする、そのほかあなたのデーモンにとっ て意味のあるあらゆる組合わせが可能でしょう。

あなたのデーモンがinetdから起動されるものならば、これはどれも必要でない (または望ましくない)でしょう。その場合、stdin、stdout、stderrは全てネッ トワーク接続に対して設定され、一連のfork()とセッション操作はすべきでは ありません(inetdを混乱させることを防ぐため)。 chdir()umask()のステップだけはそれでも有益でしょう。

1.8 どうすればpsのようにシステム中のプロセスを調べられますか?

あなたは本当はそうしたくはないでしょう。

最も、断然、汎用的な方法は、popen(pscmd,"r")として出力を解析する方法です (pscmdはSysVシステムでは`"ps -ef"'など。BSDシステムでは多くの表示オプションがありますのでどれかを選んでください)。

サンプルセクションには、この2つの完全なバージョンがあります。一つは SunOS4用で、root権限を必要とし、カーネルデータ構造から情報を読み取る `kvm_*'ルーチンを使用します。もう一つはSVR4システム(SunOS 5を含む)用で、 `/proc'ファイルシステムを使用します。

SVR4.2スタイルの`/proc'があるシステムではかなり簡単です。関心のあるPIDにつ いて、`/proc/PID/psinfo'ファイルからpsinfo_t構造体を読み出すだけです。 しかし、この方法は、最も明快ですが、またもっともサポートされていない方法で もあります (FreeBSDの`/proc'では、`/proc/PID/status'から半ば文書化されていな い文字列を読み出すことになります。Linuxでも似たようなものです)。

1.9 どうすればプロセスIDからそれが動いているプログラムかどうか分かりますか?

シグナル番号を0としてkill()を使います。

この呼び出しからは4通りの結果があり得ます:

最も良く使われる手法は、成功とEPERMつきの失敗の場合はそのプロセスが存在 するとみなし、他のエラーの場合は存在しないとみなすものです。

もう一つ別の方法もあります。`/proc'ファイルシステムをもつ、あるシステム専 用(またはそのようなシステム全て用)にプログラムを書いているのであれば、 `/proc/PID'の存在を調べる方法がうまくいくでしょう。

1.10 system/pclose/waitpidの戻り値は何ですか?

system()pclose()waitpid()の戻り値はプロセスの終了値ではないよう です…また、終了値が8ビット左シフトされています…どう扱えばいいですか?

manページが正しく、ですからあなたも正しいのです! waitpid()のmanページを 読めば、プロセスの終了コードは符号化されていることが分かるでしょう。プロ セスの返す値は通常上位16ビットで、残りは他の目的に用いられます。移植性の あるプログラムを書きたくないのでもない限り、このことに依存してはいけませ んから、提供されているマクロを用いるのが良いでしょう。それらのマクロは通 常はwait()wstatのところに説明されています。

このために(`<sys/wait.h>'で)定義されているマクロには次のようなものがありま す (statはwaitpid()の戻り値です):

WIFEXITED(stat)
子プロセスが正常終了すれば非零です。
WEXITSTATUS(stat)
子プロセスの返した終了コードです。
WIFSIGNALED(stat)
子プロセスがシグナルによって終了させられた場合非零です。
WTERMSIG(stat)
子プロセスを終了させたシグナルの番号です。
WIFSTOPPED(stat)
子プロセスが停止(stop)させられたとき非零です。
WSTOPSIG(stat)
子プロセスを停止させたシグナルの番号です。
WIFCONTINUED(stat)
再開された子プロセスのステータスであれば非零です。
WCOREDUMP(stat)
WIFSIGNALED(stat)が非零であれば、プロセスがコアダンプを残した場合非零になります。

1.11 どうすればプロセスのメモリ使用量を調べられますか?

あればgetrusage()を見てください。

1.12 どうしてプロセスはメモリサイズが減少しないのですか?

free()を使ってメモリを解放してヒープに返却したとき、ほとんどどのシステム でもプログラムのメモリ使用量は減りませんfree()されたメモリはいまだプロ セスのアドレス空間の一部のままとどまり、次のmalloc()要求に応えるために使われます。

本当に空きメモリをシステムに返却する必要があれば、mmap()を使ってプライベー トな匿名マッピングを試してください。これがアンマップされれば、メモリは実 際にシステムに返却されます。malloc()のある実装(例えばGNU C ライブラリに 含まれるもの)では使えるときには大きなメモリ確保には自動的にmmap()を用い るようになっています。そのブロックはfree()されるとシステムに返却されます。

もちろん、そうでないはずなのにサイズが増加しているという場合には、「メモ リリーク」(プログラムが使われていないメモリを適切に解放していないという バグ)がプログラムにあるのかも知れません。

1.13 どうすれば(psで見えるような)プログラムの名前を変更できますか?

BSD系のシステムでは、psプログラムは動いているプロセスのアドレス空間で実 際にargv[]を探してそれを表示します。これによりプログラムは単にargv[]を 書き換えるだけで「名前」を変更できます。

SysV系のシステムでは、コマンド名と引数の最初の通常80バイトはプロセスの u-areaに保存され、直接書き換えることはできません。これを変更するシステム コールがあるかも知れません(多分ないでしょう)が、それ以外の唯一の方法は、 exec()を実行することであり、そうでなければカーネルのメモリに書き込む(危 険であり、rootでのみ可能)しかありません。

幾つかのシステム(特にSolaris)には2つのバージョンのpsがあります。一つは `/usr/bin/ps'でSysV的に動作し、もうひとつは`/usr/ucb/ps'でBSD的に動作します。 これらのシステムでは、argv[]を変更すると、BSDバージョンのpsはそれを反映 しますが、SysVバージョンでは反映されません。

システムにsetproctitle()関数があるか調べてみましょう。

1.14 どうすればプロセスの実行ファイルを見つけられますか?

これは「よく答が返ってこない質問」のよい候補でしょうね。なぜなら、この質問をすると いうことは、大抵プログラムの設計が行き詰まっているということだからです。:-)

argv[0]の値を参照することで、「最良の推測」をすることができます。これが `/'を含んでいれば、おそらく実行ファイルの絶対パスか(プログラム開始時点の カレントディレクトリからの)相対パスでしょう。そうでなければ、シェルの PATH検索を真似してプログラムを探すことになります。しかしながら、 argv[0]に適当な値を入れてプログラムを実行することも可能なので、これが成 功するとは限りませんし、どの場合も、起動した後で実行ファイルがすでにリネー ムされたり削除されているかも知れません。

エラーメッセージとともに適切な実行コマンド名を表示したいだけなら、最も良 い方法はmain()argv[0]の値をグローバル変数に保存してプログラム中から使 えるようにすることです。argv[0]の値に意味があるとは限りませんが、ほとん どの場合これが最良の選択です。

人々がこの質問をする一番一般的な理由は、プログラムの設定ファイルの場所を 知るためです。実行ファイルのパスからそれを求めようというのは悪いやり方で す。実行ファイルのあるディレクトリには実行ファイル以外は何もないのが良く、 管理上の必要から、設定ファイルを実行ファイルとは別のファイルシステム に置きたいこともよくあるのです。

それほどにはありふれていませんがもっともな理由としては、自分自身を exec()させたいというのがあります。これは完全にプロセスを再初期化する(例 えばデーモンがSIGHUPを受信したとき)ために使われる方法です(例えばあるバー ジョンのsendmailで使われました)。

1.14.1 ではどこに設定ファイルをおけばいいでしょうか?

このための正しいディレクトリはお使いのUnixの固有の流儀によって違います。 `/var/opt/PACKAGE'`/usr/local/lib'`/usr/local/etc'、 その他幾つか有り得ます。 ユーザ固有の設定ファイルは通常$HOMEの下の隠し「ドットファイル」です(例えば `$HOME/.exrc')。

複数のシステム間で共通に利用可能であるべきパッケージから見ると、サイト単 位の設定ファイルの位置はデフォルトとして埋め込んでコンパイルされることに なります。おそらくは設定スクリプトの`--prefix'オプションを使うのでしょう (Autoconfのスクリプトはこれを行います)。これを実行時に環境変数によって上 書きできるようにしたいと思うかも知れません (設定スクリプトを使っていなけ れば、コンパイル時に`-D'オプションとしてデフォルトをMakefileに書くか、 `config.h'ヘッダファイルに設定するなどの方法になるでしょう)。

ユーザ固有の設定は$HOMEの下の一つのドットファイルか、または複数のファイ ルが必要ならドットサブディレクトリにします(ドットで始まるファイルやディ レクトリはデフォルトではディレクトリリストから省略されます)。$HOMEの下に 複数のエントリを作るのは、$HOMEをとても乱雑にすることになるので避けなけ ればなりません。また、この位置も環境変数でユーザが上書きできるようにもできます。 ユーザごとの設定が見つからなかった場合は、プログラムは常に慎重に 振る舞うべきです。

1.15 親プロセスが死んだときSIGHUPをプロセスが受け取らないのはなぜですか?

なぜなら、SIGHUPとはそういうものではないからです。

SIGHUPの意味するところは、簡単に言えば「端末ラインのハングアップ」です。 これは親プロセスとは何の関係もなく、ttyドライバによって発生します (そしてフォアグラウンドのプロセスグループに送られます)。

ところが、セッション管理システムの一部として、プロセスの死とともに SIGHUPが送られる場合というのが2つだけあります。

1.16 どうすればプロセスの子孫を全て殺すことができますか?

このための完全に一般的な手法はありません。psの出力を解析すればプロセス間 の関係が分かりますが、これはシステムのスナップショットでしかないという点 で、依存するには足りません。

しかしながら、自分自身のサブプロセスをさらに起動するようなサブプロセスを 起動するときに、起動されたジョブ全体を一回でkill出来るようにしたければ、 解決法としてそのサブプロセスを新しいプロセスグループに入れ、killするとき はそのプロセスグループをkillすることができます。

プロセスグループを作るのに望ましい関数はsetpgid()です。可能であれば setpgrp()よりもこちらを使うべきです。なぜなら、setpgrp()は システムによって異なるためです(あるシステムではsetpgrp()setpgid(0,0)と同等です。別のシステムでは、 setpgrp()setpgid()は同じものです)。

サンプルセクションのジョブ制御の例を見てください。

サブプロセスをそれ自身のプロセスグループにいれることは数々の影響がありま す。特に、新しいプロセスグループをフォアグラウンドにすることを明示しない 限り、次の状態になるまでバックグラウンドジョブとして扱われます:

多くのアプリケーションでは入力と出力はリダイレクトされるでしょうから、最 も大きな影響はキーボードシグナルを受信しないことでしょう。親アプリケーショ ンは最低限SIGINTSIGQUIT(と、望ましくはSIGTERMも) を受け取るようにし、必要であればバックグラウンドジョブをクリーンアップするべきでしょう。


Go to the first, previous, next, last section, table of contents.