Linuxでは、さまざまなシグナルが扱われます。
今回は、そんなシグナルの中でもSIGCHLD(シグチャイルド)に関して、C言語のプログラムで扱う方法を紹介します。
SIGCHLDを発生させるための子プロセスの生成方法(forkの使い方)から、シグナルをキャッチする関数(シグナルハンドラ)の設定までを実装サンプル付きで説明していますのでぜひご覧ください。
C言語でSIGCHLDを発報し、ハンドリングする方法を解説します
以下の記事では、AWSを無料で勉強する方法をまとめているので、あわせて参考にしてください。
上記の記事でも紹介していますが、侍テラコヤ
SIGCHLDとは
SIGCHLDとは、シグナルの1種で、fork()などによって生成された子プロセスが終了するときに発報(配送)されるシグナルです。
親プロセスが、子プロセスの終了を知る手段として使用します。
シグナルとは何か?に関しては以下の記事で解説しているので、あわせてご参照ください!
ちなみに、SIGCHLD自体はsignal.hで整数20でdefineされています。
#define SIGCHLD 20 /* to parent on child stop or exit */
SIGCHLDのシグナルハンドラを設定する方法
SIGCHLDが何なのかについて簡単に触れたところで、実際にSIGCHLDを発生させ、キャッチする(ハンドリングする)方法を紹介していきます。
fork関数の使い方(SIGCHLDを発生させる方法)
繰り返しになりますが、SIGCHLDシグナルはfork()関数で生成した子プロセスが終了したときに配送されます。
forkはunistd.hをインクルードすると使用可能で、小プロセスを生成する関数です。
引数はなしで、戻り値が少しややこしいです。
forkを呼び出した直後、実行していたプログラムのコピーである子プロセスが生成されます。
プログラムが親と子の2つに分離するイメージです。
そして、親プロセス側では、forkの戻り値は生成した子プロセスのプロセスID(0より大きい整数)となり、子プロセス側ではforkの戻り値は0になります。
基本的には、forkの戻り値に対して0か、0より大きいかをifで条件分岐し、親プロセスと子プロセスにやらせたい処理をそれぞれのif条件の中で実行します。
以下、forkを使った例です。
親プロセスでは自分のプロセスIDと、子プロセスのプロセスIDを表示し、子プロセスでは自分のプロセスIDと親プロセスのプロセスIDを表示します。(自分のプロセスIDはgetpid()関数、親プロセスのプロセスIDはgetppid()関数で取得できます)
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void){
/* プログラムのプロセスID */
printf("プロセスID=%d\n", getpid());
/* forkの戻り値を保存するための変数 */
pid_t pid;
/* forkの実行 */
pid = fork();
/* forkの戻り値で親か子を判断 */
if (pid == 0) {
/* forkの戻り値が0の場合は子プロセス */
printf("子プロセスです. 自分のプロセスID=%d. 親のプロセスID=%d\n", getpid(), getppid());
} else if (pid > 0) {
/* forkの戻り値が0より大きい場合は親プロセス(forkの戻り値=子プロセスのプロセスID) */
printf("親プロセスです. 自分のプロセスID=%d. 子のプロセスID=%d\n", getpid(), pid);
wait(NULL);
}
}
戻り値のpid_t型ですが、単なるintだと思って大丈夫です。
上記プログラムを実行すると、例えば以下のような出力が得られます。
プロセスID=15837
親プロセスです. 自分のプロセスID=15837. 子のプロセスID=15838
子プロセスです. 自分のプロセスID=15838. 親のプロセスID=15837
親プロセスの自分のIDと、子プロセスの親プロセスIDが一致しています(例だと15837)。
また、親プロセスでのforkの戻り値と、子プロセスの自分のプロセスIDも一致しています(例だと15838)。
ちなみに、wait(NULL)の部分ですが、普通にプログラムを実行すると親プロセスが先に終了する可能性があるので、親プロセスが子プロセスの終了を待機する関数です。
試しにwait(NULL)の部分を消して実行してみると、親のプロセスID=1になることがあります。
これは、親プロセスを失った(親が先に終了した)子プロセスは、プロセスID=1のプロセス(initプロセス)が親となることによりますが、ここでは詳しく触れません。
上記のプログラム例ですが、本来であればpidが負の値のときに適切なエラー処理が必要です。なぜなら、forkは失敗時に-1を返すからです。親プロセスの処理を、「forkの戻り値が0以外」で分岐している実装例を見かけますが、あれは厳密には正しくないです。親プロセスの処理は、「forkの戻り値が0より大きい」で判定するべきです。
SIGCHLDのシグナルハンドラの設定方法
waitがSIGCHLDのシグナルハンドラの1種だと説明しました。
続いて、SIGCHLDシグナルハンドラに自前の関数を設定する方法を説明します。
シグナルハンドラの設定方法はsignal関数を使う方法と、sigaction関数を使う方法の2種類があります。
今回は簡単に使えるsignal関数を使う方法で実装します。
signal、sigactionの使い方については以下の記事で解説していますので、ぜひご参照ください!
signal関数は、第一引数にハンドリングしたいシグナル(今回はSIGCHLD)、第二引数にシグナルハンドラにしたい関数名を指定します。
今回は、SIGCHLDが配送されたときに、「子プロセスが終了したよ!」と出力するハンドラを実装します。
シグナルハンドラにできる関数は戻り値なし、引数intの関数です。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int flag = 0;
/* シグナルハンドラ */
void sigchld_handler(int signum){
puts("子プロセスが終了したよ!");
flag = 1;
}
int main(void){
/* シグナルハンドラの設定 */
signal(SIGCHLD, sigchld_handler);
/* 子プロセスの生成 */
pid_t pid = fork();
/* forkの戻り値で親か子を判断 */
if (pid == 0) {
/* forkの戻り値が0の場合は子プロセス */
printf("子プロセスです. 自分のプロセスID=%d. 親のプロセスID=%d\n", getpid(), getppid());
} else if (pid > 0) {
/* forkの戻り値が0より大きい場合は親プロセス(forkの戻り値=子プロセスのプロセスID) */
printf("親プロセスです. 自分のプロセスID=%d. 子のプロセスID=%d\n", getpid(), pid);
while(1){
if (flag == 1){
puts("シグナルハンドラが起動したよ!");
break;
}
};
}
return 0;
}
今回は親プロセスではflagが1になるまで無限ループし、その間に子プロセスが終了するとシグナルハンドラが呼び出されます。
シグナルハンドラが呼び出されると、flagが1にセットされ、無限ループからbreakします。
出力は以下のようになります。
親プロセスです. 自分のプロセスID=18011. 子のプロセスID=18012
子プロセスです. 自分のプロセスID=18012. 親のプロセスID=18011
子プロセスが終了したよ!
シグナルハンドラが起動したよ!
まとめ
今回はSIGCHLDと、forkの使い方について説明しました。
以下、内容をまとめておきます。
- SIGCHLDは、fork関数によって生成された子プロセスが終了するときに配送(発報)されるシグナル
- fork関数は、子プロセス(親プロセスのコピー)を生成し、戻り値で親か子を判断する
- wait関数で子プロセスの終了を待機できる
- 子プロセスよりも先に親プロセスが終了した場合、子プロセスの親プロセスはプロセスIDが1のinitプロセスに引き継がれる
以下の記事では、AWSを無料で勉強する方法をまとめているので、あわせて参考にしてください。
上記の記事でも紹介していますが、侍テラコヤ
コメント