Linux でのシグナルのメカニズム: プロセス間通信と制御にシグナルを使用する方法

WBOY
リリース: 2024-02-12 12:40:03
転載
1043 人が閲覧しました

Linux 下的信号机制:如何使用信号进行进程间通信和控制

Signal は、Linux システムのプロセス間通信と制御に一般的に使用される方法で、あるプロセスが別のプロセスに単純なメッセージを送信して、何かが起こったことを通知することができます。またはステータス。信号の機能は、異常または緊急の状況に対処するためにシステムの応答性と柔軟性を向上させることです。 Linux システムには、SIGINT、SIGTERM、SIGKILL など、さまざまな種類のシグナルがあります。それぞれに独自の意味と機能があり、さまざまなシナリオやニーズに適しています。しかし、Linux での信号のメカニズムを本当に理解していますか? Linux でプロセス間通信と制御にシグナルを使用する方法をご存知ですか? Linux でシグナルを処理および無視する方法を知っていますか?この記事では、Linux での信号メカニズムに関する関連知識を詳しく紹介します。これにより、Linux でのこの強力なプロセス間通信および制御方法をよりよく使用し、理解できるようになります。

1. 信号の基本概念

このセクションでは、まず信号の基本概念をいくつか紹介し、次にいくつかの基本的な信号タイプと信号に対応するイベントを示します。基本概念は、シグナルを理解して使用するため、またシグナル伝達メカニズムを理解するために特に重要です。信号とは何かを見てみましょう。

1. 基本概念

ソフト割り込みシグナル (シグナル、シグナルとも呼ばれます) は、非同期イベントが発生したことをプロセスに通知するために使用されます。プロセスは、システム コール kill を通じてソフト割り込み信号を相互に送信できます。カーネルは、内部イベントによりプロセスにシグナルを送信し、イベントが発生したことをプロセスに通知することもできます。シグナルは、どのようなイベントが発生したかをプロセスに通知するためにのみ使用され、プロセスにデータは渡されないことに注意してください。

信号を受け取るプロセスは、信号ごとに異なる処理方法を持っています。処理方法は 3 つのカテゴリに分類でき、1 つは割り込みのようなハンドラーであり、処理が必要な信号に対して、プロセスはそれを処理する処理関数を指定できます。 2 番目の方法は、信号を無視し、まるで何も起こらなかったかのように信号に対して何もしないことです。 3 番目の方法は、信号を処理するためのシステムのデフォルト値を保持することです。このデフォルトの動作は、ほとんどの信号のデフォルトの動作であり、プロセスを終了します。プロセスは、システム コール信号を使用して、特定の信号に対するプロセスの処理動作を指定します。

プロセス テーブルのエントリにはソフト割り込み信号フィールドがあります。このフィールドの各ビットは信号に対応します。信号がプロセスに送信されると、対応するビットが設定されます。このことから、プロセスは異なるシグナルを同時に保持できることがわかりますが、同じシグナルについては、プロセスは処理前に何個来たかを知りません。

2. 信号の種類

シグナルを送信する理由は数多くありますが、さまざまなシグナルを理解するために、シグナルを送信する理由に応じた簡単な分類を以下に示します。

(1) プロセスの終了に関連するシグナル。このタイプのシグナルは、プロセスが終了するか、子プロセスが終了するときに発行されます。

(2) プロセス例外イベントに関連する信号。たとえば、プロセスが境界を越えたり、読み取り専用メモリ領域 (プログラム テキスト領域など) に書き込もうとしたり、特権命令やその他のさまざまなハードウェア エラーを実行したりします。
(3) システムコール中に発生した回復不可能な状況に関連する信号。たとえば、システム コール exec を実行すると、元のリソースは解放され、現在のシステム リソースは使い果たされます。
(4) システム コールの実行中に発生した非予測エラー条件に関連する信号。存在しないシステムコールを実行するなど。
(5) ユーザーモードのプロセスによって送信されるシグナル。たとえば、プロセスはシステム コール kill を呼び出して、他のプロセスにシグナルを送信します。
(6) 端末の相互作用に関連する信号。たとえば、ユーザーが端末を閉じるか、ブレーク キーを押します。
(7) プロセス実行の信号を追跡します。

Linux でサポートされているシグナルのリストは次のとおりです。多くの信号はマシンのアーキテクチャに関連しています。最初にリストされている信号は、POSIX.1 にリストされている信号です:

シグナル値処理アクションシグナル送信の理由

—————————————————————-
リーリー

次のシグナルは POSIX.1 にはリストされていませんが、SUSv2 にはリストされています

シグナル値処理アクションシグナル送信の理由

—————————————————————–
リーリー

その他のシグナルは次のとおりです

シグナル値処理アクションシグナル送信の理由

—————————————————————-
リーリー

(ここで、 - はシグナルが実装されていないことを意味します。意味を考えると 3 つの値があります。通常、最初の値は Alpha と Sparc で有効で、中央の値は i386、ppc、sh に対応します。最後の値は mips に対応します。信号 29 は、Alpha では SIGINFO/SIGPWR、Sparc では SIGLOST です。)

処理アクション項目内の文字の意味は次のとおりです
A デフォルトのアクションはプロセスを終了することです
B デフォルトのアクションは、この信号を無視することです
C のデフォルトのアクションは、プロセスを終了し、コア イメージ ダンプ (ダンプ コア) を実行することです。 D デフォルトのアクションはプロセスを停止することです
E 信号をキャプチャできません
F信号は無視できません

上記で紹介した信号は、一般的なシステムでサポートされています。デフォルトでの各種信号の名称、機能、処理動作を表形式で紹介します。さまざまなデフォルトの処理アクションの意味は次のとおりです: プログラムの終了はプロセスの終了を意味します。シグナルの無視はシグナルを処理せずに破棄することを意味します。プログラムの停止はプログラムがハングし、停止状態に入った後、通常は再起動できることを意味します。デバッグ (ptrace システム コールなど); カーネル イメージ ダンプとは、メモリ内のプロセス データのイメージとプロセスのカーネル構造に格納されているコンテンツの一部を特定の形式でファイル システムにダンプすることを指し、プロセスは実行を終了します。この方法の利点は、プログラマがプロセスの実行中にデータ値を取得できる利便性を提供し、ダンプの原因を特定し、プログラムをデバッグできることです。

シグナル SIGKILL および SIGSTOP は捕捉することも無視することもできないことに注意してください。信号 SIGIOT と SIGABRT は 1 つの信号です。同じ信号が異なるシステムでは異なる値を持つ可能性があることがわかるため、信号の値を直接使用するのではなく、信号に定義された名前を使用することをお勧めします。

2. 信号伝達メカニズム

シグナルの基本概念は前のセクションで紹介しましたが、このセクションではカーネルがシグナル メカニズムを実装する方法を紹介します。つまり、カーネルがプロセスにシグナルを送信する方法、プロセスがシグナルを受信する方法、プロセスがシグナルへの応答を制御する方法、カーネルがプロセスによって受信されたシグナルをいつどのように処理するか、ということです。 setjmpとlongjmpがシグナルにおいて果たす役割についても紹介したいと思います。

1. カーネルによる基本的な信号処理方法

カーネルがプロセスにソフト割り込み信号を送信する方法は、プロセスが配置されているプロセス テーブル エントリの信号フィールド内の信号に対応するビットを設定することです。ここで追加すべき点は、シグナルがスリープ状態のプロセスに送信される場合、スリープ状態に入るプロセスの優先度に依存するということです。プロセスが中断できる優先度でスリープしている場合は、プロセスをウェイクアップします。それ以外の場合は、設定のみを行います。プロセスをウェイクアップせずに、プロセステーブルフィールドの信号に対応するビットを返します。プロセスがシグナルを受信したかどうかを確認するタイミングは、プロセスがカーネル モードからユーザー モードに戻ろうとしているとき、またはプロセスが適切に優先順位の低いスリープ状態に入るとき、またはスリープ状態から抜け出すときであるため、これは重要です。

プロセスが受信したシグナルをカーネルが処理するタイミングは、プロセスがカーネルモードからユーザーモードに戻ったときです。したがって、プロセスがカーネル モードで実行されている場合、ソフト割り込み信号はすぐには有効にならず、ユーザー モードに戻るまで待つ必要があります。プロセスは信号を処理した後にのみユーザー モードに戻り、ユーザー モードでは未処理の信号がプロセスに存在しません。

カーネルは、プロセスが受信したソフト割り込み信号をプロセスのコンテキストで処理するため、プロセスは実行状態にある必要があります。概念を紹介したときに前述したように、信号処理には 3 つのタイプがあります: プロセスはシグナルを受信した後に終了します。プロセスはシグナルを無視します。プロセスはシグナルを受信した後、ユーザーがシグナルを呼び出すように設定した関数を実行します。システム。プロセスが無視するシグナルを受信すると、プロセスはシグナルを破棄し、シグナルが受信されなかったかのように続行します。プロセスがキャプチャ対象のシグナルを受信した場合、プロセスがカーネル モードからユーザー モードに戻るときにユーザー定義関数が実行されます。さらに、ユーザー定義関数の実行方法も非常に巧妙で、カーネルはユーザースタック上に新しい層を作成し、この層では戻りアドレスの値がユーザー定義処理関数のアドレスに設定されるため、プロセスがカーネルから戻ってスタックの先頭をポップすると、ユーザー定義関数に戻り、関数から戻ってスタックの先頭をポップすると、最初にカーネルに入った場所に戻ります。 。その理由は、ユーザー定義の処理関数はカーネル モードでは実行できず、実行することも許可されていないためです (ユーザー定義関数がカーネル モードで実行される場合、ユーザーは任意の権限を取得できます)。

信号処理方法には注意すべき点がいくつかあります。まず、一部のシステムでは、プロセスが割り込み信号を処理してユーザー モードに戻るとき、カーネルはユーザー領域に設定されている信号処理ルーチンのアドレスをクリアします。つまり、次回プロセスが信号処理方式を変更するときです。次のシグナルが到着する前にシグナルシステムコールが再度使用されない限り、デフォルト値。これにより、プロセスがシグナルを呼び出す前にシグナルを取得し、プロセスが終了する可能性があります。 BSD では、カーネルはこのアドレスをクリアしなくなりました。ただし、このアドレスをクリアしないと、プロセスがシグナルを受け取る速度が速すぎて、スタック オーバーフローが発生する可能性があります。上記のような事態を避けるためです。 BSD システムでは、カーネルはハードウェア割り込みの処理方法をシミュレートします。つまり、割り込みを処理するときに、このタイプの新しい割り込みの受信を防ぎます。

2 番目に注意すべきことは、プロセスがシステム コール中にキャプチャ対象のシグナルが発生し、プロセスが割り込み可能な優先レベルでスリープしている場合、そのシグナルによりプロセスは longjmp を実行してスリープからジャンプすることになります。状態に戻り、ユーザーモードに戻り、信号処理ルーチンを実行します。シグナル処理ルーチンから戻るとき、プロセスはシステム コールから戻ったかのように動作しますが、システム コールが中断されたことを示すエラー コードを返します。 BSD システムでは、カーネルがシステム コールを自動的に再開できることに注意してください。

3 番目に注意すべきこと: プロセスが割り込み可能な優先レベルでスリープしている場合、無視されるシグナルを受信すると、プロセスは起動されますが、longjmp は実行されず、通常はスリープ状態を続けます。しかし、ユーザーはプロセスが目覚めたとは感じず、あたかも信号が発生していないかのように感じます。

4 番目に注意すべき点: カーネルは、子プロセス終了 (SIGCLD) 信号を他の信号とは異なる方法で処理します。プロセスが子プロセスを終了するシグナルを受信したことを確認すると、デフォルトではプロセスはシグナルを受信していないかのように動作し、親プロセスがシステムコール待機を実行すると、プロセスはシステムコールからウェイクアップします。 wait および wait 呼び出しに戻り、wait 呼び出しの一連のフォローアップ操作 (ゾンビ子プロセスの検索、子プロセスのプロセス テーブル エントリの解放) を実行してから、wait から戻ります。 SIGCLD シグナルの機能は、割り込み可能な優先レベルでスリープしているプロセスをウェイクアップすることです。プロセスがシグナルをキャッチすると、通常のシグナル処理と同様にハンドラー ルーチンに進みます。プロセスがシグナルを無視した場合、システム コール待機のアクションは異なります。SIGCLD の機能は、割り込み可能な優先レベルでスリープしているプロセスをウェイクアップすることだけであるため、待機コールを実行する親プロセスはウェイクアップされ、続行されます。 wait 呼び出しを実行し、後続の操作を実行して、他の子プロセスを待ちます。

プロセスがシグナル システム コールを呼び出して SIGCLD 処理メソッドを設定し、そのプロセスにゾンビ状態の子プロセスがある場合、カーネルはプロセスに SIGCLD シグナルを送信します。

2. setjmpとlongjmpの関数

先ほど信号処理機構を紹介した際、setjmpとlongjmpについては何度も言及しましたが、その機能や実装方法については詳しく説明されていませんでした。これについて簡単に紹介します。

シグナルを導入する際、多くの場所で、呼び出しが完了するのを待つのではなく、シグナルが受信されたことを確認した後、プロセスが元のシステム コールから直接戻る必要があることがわかりました。プロセスがそのコンテキストを突然変更するこの状況は、setjmp と longjmp を使用した結果発生します。 setjmp は保存されたコンテキストをユーザー領域に保存し、古いコンテキストで実行を継続します。つまり、プロセスはシステムコールを実行しますが、リソースなどの理由でプロセスがスリープ状態になると、カーネルはプロセスに対して setjmp を作成します。スリープ中にシグナルによってプロセスが目覚めて、プロセスが再びスリープ状態に移行できない場合は、カーネルがプロセスに対して setjmp を作成します。 , カーネルはプロセスに対してlongjmpを呼び出します。この操作は、カーネルが元のsetjmp呼び出しによってプロセスのユーザー領域に保存されたコンテキストを現在のコンテキストに復元し、プロセスが待機前の状態に戻ることができるようにするためのものです。リソースの場合は 1 を返し、カーネルは setjmp の場合は 1 を返すため、プロセスはシステム コールが失敗したことを認識します。それが彼らのやることなのです。

3. シグナル関連のシステムコール

信号に関する知識のほとんどは、前の 2 つのセクションで紹介されています。このセクションでは、これらのシステム コールについて学習します。このうち、システムコールシグナルはプロセスが特定のシグナルの処理方法を設定するために使用し、システムコールkillは指定されたプロセスにシグナルを送信するために使用されます。これら 2 つの呼び出しは、信号の基本的な動作を形成します。最後の 2 つの呼び出し、ポーズとアラームは、シグナルを通じて実装されたプロセスの一時停止とタイマーです。アラームの呼び出しは、シグナルを通じてタイマーの期限切れをプロセスに通知するために使用されます。そこで、ここではこれら 2 つの呼び出しについても紹介します。

1.シグナルシステムコール

システムコール信号は、ある信号の処理方法を設定するために使用されます。呼び出し宣言の形式は次のとおりです:
void (*signal(intsignum, void (*handler)(int)))(int);
この呼び出しを使用して、次のヘッダー ファイルをプロセスに追加します:
#含む###

上記の宣言形式は比較的複雑なので、使い方がわからない場合は、次の型定義形式(POSIX定義)でも使用できます。 typedef void (*signandler_t)(int);

sighandler_t シグナル(intsignum, sighandler_t ハンドラー);
ただし、この形式はシステムごとに型定義が異なるため、この形式を使用する場合はオンライン マニュアルを参照することをお勧めします。

呼び出しでは、パラメーターsignum は、メソッドを処理するために設定されるシグナルを示します。 2 番目のパラメーター ハンドラーは処理関数、または

SIG_IGN: パラメータsignumが指す信号を無視します。

SIG_DFL: パラメータsignumが指す信号の処理方法をデフォルト値に戻します。

信号処理ルーチンに渡される整数パラメータは信号値であり、これにより 1 つの信号処理ルーチンが複数の信号を処理できるようになります。システムコールシグナルの戻り値は、指定されたシグナルサインムの前の処理ルーチン、またはエラーの場合に返されるエラーコード SIG_ERR です。簡単な例を見てみましょう:

#include \#include \#include void sigroutine(int dunno) { /* 信号处理例程,其中dunno将会得到信号的值 */ switch (dunno) { case 1: printf("Get a signal -- SIGHUP "); break; case 2: printf("Get a signal -- SIGINT "); break; case 3: printf("Get a signal -- SIGQUIT "); break; } return; } int main() { printf("process id is %d ",getpid()); signal(SIGHUP, sigroutine); //* 下面设置三个信号的处理方法 signal(SIGINT, sigroutine); signal(SIGQUIT, sigroutine); for (;;) ; }
ログイン後にコピー

其中信号SIGINT由按下Ctrl-C发出,信号SIGQUIT由按下Ctrl-发出。该程序执行的结果如下:

localhost:~$ ./sig_test process id is 463 Get a signal -SIGINT //按下Ctrl-C得到的结果 Get a signal -SIGQUIT //按下Ctrl-得到的结果 //按下Ctrl-z将进程置于后台 [1]+ Stopped ./sig_test localhost:~$ bg [1]+ ./sig_test & localhost:~$ kill -HUP 463 //向进程发送SIGHUP信号 localhost:~$ Get a signal – SIGHUP kill -9 463 //向进程发送SIGKILL信号,终止进程 localhost:~$
ログイン後にコピー

2、kill 系统调用

系统调用kill用来向进程发送一个信号。该调用声明的格式如下:
int kill(pid_t pid, int sig);
在使用该调用的进程中加入以下头文件:

\#include \#include
ログイン後にコピー

该 系统调用可以用来向任何进程或进程组发送任何信号。如果参数pid是正数,那么该调用将信号sig发送到进程号为pid的进程。如果pid等于0,那么信 号sig将发送给当前进程所属进程组里的所有进程。如果参数pid等于-1,信号sig将发送给除了进程1和自身以外的所有进程。如果参数pid小于- 1,信号sig将发送给属于进程组-pid的所有进程。如果参数sig为0,将不发送信号。该调用执行成功时,返回值为0;错误时,返回-1,并设置相应 的错误代码errno。下面是一些可能返回的错误代码:
EINVAL:指定的信号sig无效。
ESRCH:参数pid指定的进程或进程组不存在。注意,在进程表项中存在的进程,可能是一个还没有被wait收回,但已经终止执行的僵死进程。
EPERM: 进程没有权力将这个信号发送到指定接收信号的进程。因为,一个进程被允许将信号发送到进程pid时,必须拥有root权力,或者是发出调用的进程的UID 或EUID与指定接收的进程的UID或保存用户ID(savedset-user-ID)相同。如果参数pid小于-1,即该信号发送给一个组,则该错误 表示组中有成员进程不能接收该信号。

3、pause系统调用

系统调用pause的作用是等待一个信号。该调用的声明格式如下:
int pause(void);
在使用该调用的进程中加入以下头文件:
#include

该调用使得发出调用的进程进入睡眠,直到接收到一个信号为止。该调用总是返回-1,并设置错误代码为EINTR(接收到一个信号)。下面是一个简单的范例:

#include \#include \#include void sigroutine(int unused) { printf("Catch a signal SIGINT "); } int main() { signal(SIGINT, sigroutine); pause(); printf("receive a signal "); }
ログイン後にコピー

在这个例子中,程序开始执行,就象进入了死循环一样,这是因为进程正在等待信号,当我们按下Ctrl-C时,信号被捕捉,并且使得pause退出等待状态。

4、alarm和 setitimer系统调用

系统调用alarm的功能是设置一个定时器,当定时器计时到达时,将发出一个信号给进程。该调用的声明格式如下:
unsigned int alarm(unsigned int seconds);
在使用该调用的进程中加入以下头文件:
#include

系 统调用alarm安排内核为调用进程在指定的seconds秒后发出一个SIGALRM的信号。如果指定的参数seconds为0,则不再发送 SIGALRM信号。后一次设定将取消前一次的设定。该调用返回值为上次定时调用到发送之间剩余的时间,或者因为没有前一次定时调用而返回0。

注意,在使用时,alarm只设定为发送一次信号,如果要多次发送,就要多次使用alarm调用。

对于alarm,这里不再举例。现在的系统中很多程序不再使用alarm调用,而是使用setitimer调用来设置定时器,用getitimer来得到定时器的状态,这两个调用的声明格式如下:
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
在使用这两个调用的进程中加入以下头文件:
#include

该系统调用给进程提供了三个定时器,它们各自有其独有的计时域,当其中任何一个到达,就发送一个相应的信号给进程,并使得计时器重新开始。三个计时器由参数which指定,如下所示:
TIMER_REAL:按实际时间计时,计时到达将给进程发送SIGALRM信号。
ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达将发送SIGVTALRM信号给进程。
ITIMER_PROF:当进程执行时和系统为该进程执行动作时都计时。与ITIMER_VIR-TUAL是一对,该定时器经常用来统计进程在用户态和内核态花费的时间。计时到达将发送SIGPROF信号给进程。

定时器中的参数value用来指明定时器的时间,其结构如下:

struct itimerval { struct timeval it_interval; /* 下一次的取值 */ struct timeval it_value; /* 本次的设定值 */ }; 该结构中timeval结构定义如下: struct timeval { long tv_sec; /* 秒 */ long tv_usec; /* 微秒,1秒 = 1000000 微秒*/ };
ログイン後にコピー

在setitimer 调用中,参数ovalue如果不为空,则其中保留的是上次调用设定的值。定时器将it_value递减到0时,产生一个信号,并将it_value的值设 定为it_interval的值,然后重新开始计时,如此往复。当it_value设定为0时,计时器停止,或者当它计时到期,而it_interval 为0时停止。调用成功时,返回0;错误时,返回-1,并设置相应的错误代码errno:
EFAULT:参数value或ovalue是无效的指针。
EINVAL:参数which不是ITIMER_REAL、ITIMER_VIRT或ITIMER_PROF中的一个。

下面是关于setitimer调用的一个简单示范,在该例子中,每隔一秒发出一个SIGALRM,每隔0.5秒发出一个SIGVTALRM信号:

#include \#include \#include \#include int sec; void sigroutine(int signo) { switch (signo) { case SIGALRM: printf("Catch a signal -- SIGALRM "); break; case SIGVTALRM: printf("Catch a signal -- SIGVTALRM "); break; } return; } int main() { struct itimerval value,ovalue,value2; sec = 5; printf("process id is %d ",getpid()); signal(SIGALRM, sigroutine); signal(SIGVTALRM, sigroutine); value.it_value.tv_sec = 1; value.it_value.tv_usec = 0; value.it_interval.tv_sec = 1; value.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &value, &ovalue); value2.it_value.tv_sec = 0; value2.it_value.tv_usec = 500000; value2.it_interval.tv_sec = 0; value2.it_interval.tv_usec = 500000; setitimer(ITIMER_VIRTUAL, &value2, &ovalue); for (;;) ; }
ログイン後にコピー

该例子的屏幕拷贝如下:

localhost:~$ ./timer_test process id is 579 Catch a signal – SIGVTALRM Catch a signal – SIGALRM Catch a signal – SIGVTALRM Catch a signal – SIGVTALRM Catch a signal – SIGALRM Catch a signal –GVTALRM
ログイン後にコピー

通过本文,你应该对 Linux 下的信号机制有了一个深入的了解,知道了它的定义、原理、用法和优缺点。你也应该明白了信号机制的作用和影响,以及如何在 Linux 下正确地使用和处理信号。我们建议你在使用 Linux 系统时,使用信号机制来提高系统的响应性和灵活性。同时,我们也提醒你在使用信号机制时要注意一些潜在的问题和挑战,如信号丢失、信号屏蔽、信号安全等。希望本文能够帮助你更好地使用 Linux 系统,让你在 Linux 下掌握信号机制的使用和处理。

以上がLinux でのシグナルのメカニズム: プロセス間通信と制御にシグナルを使用する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:lxlinux.net
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!