Linux シグナル シリーズのパート 1 では、Linux シグナルの背後にある基本的な概念について学びました。
前の部分に基づいて、この記事では、プロセス内のシグナルをキャッチする方法について学習します。 C プログラム コード スニペットを使用して、シグナル処理の実際的な側面を紹介します。
合図をキャッチ
前の記事で既に説明したように、プロセスが特定のシグナルを処理したい場合、コード内で、プロセスはシグナル処理関数をカーネルに登録する必要があります。
以下は、シグナル処理関数のプロトタイプです:
void <signal handler func name> (int sig)
シグナル ハンドラー関数には void 戻り値の型があり、処理する必要があるシグナルに対応するシグナル番号を受け入れます。
カーネルに登録されたシグナル ハンドラー関数を取得するには、シグナル ハンドラー関数ポインターが 2 番目の引数として「signal」関数に渡されます。シグナル関数のプロトタイプは次のとおりです:
void (*signal(int signo, void (*func )(int)))(int);
これは複雑な宣言に思えるかもしれません。デコードしようとすると:
- 関数には 2 つの引数が必要です。
- 最初の引数は、シグナル番号またはシグナル値を示す整数 (signo) です。
- 2 番目の引数は、引数として整数を受け取り、何も返さない (void) シグナル ハンドラ関数へのポインタです。
- 「シグナル」関数自体は、戻り値の型が void の関数ポインタを返します。
さて、物事を簡単にするために、 typedef を使用しましょう:
typedef void sigfunc(int)
というわけで、ここで新しい型「sigfunc」を作りました。この typedef を使用して、シグナル ハンドラのプロトタイプを再設計すると、次のようになります。
sigfunc *signal(int, sigfunc*);
これで、シグナル ハンドラー関数が整数と sigfunc 型の関数ポインターを受け入れ、sigfunc 型の関数ポインターを返すことを理解しやすくなりました。
シグナルをキャッチする C プログラムの例
Linux ユーザーのほとんどは、Linux でプロセスを終了するために Ctr+C キーの組み合わせを使用します。
この背後にあるものについて考えたことがありますか。 ctrl+c が押されるたびに、シグナル SIGINT がプロセスに送信されます。このシグナルのデフォルトのアクションは、プロセスを終了することです。しかし、この信号も処理できます。次のコードはこれを示しています:
#include<stdio.h> #include<signal.h> #include<unistd.h> void sig_handler(int signo) { if (signo == SIGINT) printf("received SIGINT\n"); } int main(void) { if (signal(SIGINT, sig_handler) == SIG_ERR) printf("\ncan't catch SIGINT\n"); // A long long wait so that we can easily issue a signal to this process while(1) sleep(1); return 0; }
上記のコードでは、無限 while ループを使用して長時間実行されるプロセスをシミュレートしています。
関数 sig_handler は、シグナル ハンドラとして使用されます。この関数は、main() 関数のシステム コール「signal」の 2 番目の引数として渡すことで、カーネルに登録されます。関数「signal」の最初の引数は、シグナル ハンドラが処理するシグナルで、この場合は SIGINT です。
ちなみに、関数 sleep(1) の使用には理由があります。この関数は while ループで使用されているため、while ループは一定時間 (この場合は 1 秒) 後に実行されます。そうしないと、乱暴に実行される無限 while ループが CPU の大部分を消費し、コンピューターが非常に遅くなる可能性があるため、これは重要になります。
とにかく、戻ってきて、プロセスが実行され、Ctrl+C を使用してプロセスを終了しようとすると:
$ ./sigfunc ^Creceived SIGINT ^Creceived SIGINT ^Creceived SIGINT ^Creceived SIGINT ^Creceived SIGINT ^Creceived SIGINT ^Creceived SIGINT
上記の出力では、キーの組み合わせ ctrl+c を数回試しましたが、プロセスが終了しなかったことがわかります。これは、信号がコードで処理され、これが各行で取得した出力から確認されたためです.
SIGKILL、SIGSTOP、およびユーザー定義シグナル
利用可能な標準シグナル(INT、TERMなど)の処理とは別に。送信および処理できるユーザー定義のシグナルを使用することもできます。以下は、ユーザー定義シグナル USR1 を処理するコードです:
#include<stdio.h> #include<signal.h> #include<unistd.h> void sig_handler(int signo) { if (signo == SIGUSR1) printf("received SIGUSR1\n"); else if (signo == SIGKILL) printf("received SIGKILL\n"); else if (signo == SIGSTOP) printf("received SIGSTOP\n"); } int main(void) { if (signal(SIGUSR1, sig_handler) == SIG_ERR) printf("\ncan't catch SIGUSR1\n"); if (signal(SIGKILL, sig_handler) == SIG_ERR) printf("\ncan't catch SIGKILL\n"); if (signal(SIGSTOP, sig_handler) == SIG_ERR) printf("\ncan't catch SIGSTOP\n"); // A long long wait so that we can easily issue a signal to this process while(1) sleep(1); return 0; }
上記のコードでは、ユーザー定義シグナル USR1 を処理しようとしていることがわかります。また、既知のように、KILL と STOP の 2 つのシグナルは処理できません。そのため、この場合に「シグナル」システム コールがどのように応答するかを確認するために、これら 2 つのシグナルの処理も試みました。
上記のコードを実行すると:
$ ./sigfunc can't catch SIGKILL can't catch SIGSTOP
したがって、上記の出力は、システム コール「signal」が KILL および STOP シグナルのハンドラを登録しようとするとすぐに、シグナル関数が失敗し、これら 2 つのシグナルをキャッチできないことを示していることを明確に示しています。
ここで、kill コマンドを使用してシグナル USR1 をこのプロセスに渡そうとします:
$ kill -USR1 2678
上記のプログラムが実行されている端末では、次のように表示されます:
$ ./sigfunc can't catch SIGKILL can't catch SIGSTOP received SIGUSR1
したがって、ユーザー定義シグナル USR1 がプロセスで受信され、適切に処理されたことがわかります。