1. シグナルリスト
最も一般的なものは次のとおりです。
1.1. リアルタイム信号と非リアルタイム信号上記のように、kill はすべてのシグナルをリストします。リアルタイム信号と非リアルタイム信号は、信頼できる信号と信頼できない信号とも呼ばれます。 SIGRTMIN 以降の信号はリアルタイム信号であり、それ以前の信号は非リアルタイム信号です。違いは、リアルタイム信号は繰り返しキューイングをサポートするのに対し、非リアルタイム信号はサポートしないことです。デフォルトでは、非リアルタイム信号はキューに入れられたときに 1 回だけ表示されます。つまり、複数回送信されたとしても、最終的に受信されるのは 1 つだけです。キューを取り出す順序にも違いがあり、最初に取り出す信号はリアルタイム信号である必要があります。 追伸:
1.2 信号ステータス信号の「保留」状態は、信号の生成から信号の処理までの期間を指します。信号の「ブロック」はスイッチングアクションであり、信号が処理されるのを防ぐが、信号の生成を防ぐわけではないことを意味します。 たとえば、スリープする前に、sigprocmask を使用して終了信号をブロックし、スリープを実行し、スリープ処理中に終了信号を生成します。ただし、この時点では、終了信号はブロックされています (中国語の「ブロック」は状態と誤解されやすいです。実際には、スイッチに似た動作であるため、「ブロック」ではなく「ブロックされている」と言います)。そのため、「保留」状態になっています。スリープ後は、sigprocmask を使用して終了信号のブロック スイッチをオフにします。以前に生成された終了信号は保留状態にあったため、ブロック スイッチをオフにすると、すぐに「保留」状態を終了して処理されます。これらはすべて、sigprocmask が戻る前に発生します。 1.3 信号のライフサイクル信号の完全なライフサイクル(信号の送信から対応する処理機能の完了まで)は、次の 4 つの重要なイベントによって特徴付けられる 3 つの重要な段階に分けられます。 1. シグナルが誕生します。 2. 信号がプロセスに登録されます。 3. 信号はプロセス中に登録解除されます。 4. 信号処理機能が実行されます。隣接する 2 つのイベント間の時間間隔は、信号のライフ サイクルのステージを構成します。 以下は、4 つのイベントの実際的な重要性の説明です。
構造体 sigpending 保留中; 構造体の署名保留 { 構造体 sigqueue *head、**tail; sigset_t シグナル; }; プロセスにおけるシグナルの登録とは、シグナル値がプロセスの保留シグナル セット (sigpending 構造体の 2 番目のメンバー、sigset_t シグナル) に追加され、シグナルによって伝達される情報が保留シグナル情報チェーン内の sigqueue 構造体に保持されることを意味します。シグナルがプロセスの保留中のシグナル セット内にある限り、プロセスはこれらのシグナルの存在を認識しているが、それらを処理する時間がなかったか、シグナルがプロセスによってブロックされていることを意味します。 1. プロセス中に信号が登録解除されます。ターゲット プロセスの実行中に、処理を待機しているシグナルがあるかどうかがチェックされます (このチェックは、システム空間からユーザー空間に戻るたびに実行されます)。処理を待機している保留中のシグナルがあり、そのシグナルがプロセスによってブロックされていない場合、プロセスは対応するシグナル処理関数を実行する前に、保留中のシグナル チェーン内のシグナルによって占有されている構造を削除します。シグナルがプロセスの保留中のシグナル セットから削除されるかどうかは、リアルタイム シグナルと非リアルタイム シグナルで異なります。非リアルタイム シグナルの場合、保留シグナル情報チェーンで最大 1 つの sigqueue 構造体しか占有しないため、構造体が解放された後 (シグナルのキャンセルが完了した後)、シグナルはプロセスの保留シグナル セットから削除する必要があります。リアルタイム シグナルの場合、保留シグナル情報チェーンで複数の sigqueue 構造体を占有する可能性があるため、占有されている gqueue 構造体の数に応じて異なる処理を行う必要があります。占有されている sigqueue 構造体が 1 つだけの場合 (プロセスがシグナルを 1 回だけ受信する場合)、シグナルはプロセスの保留シグナル セットから削除する必要があります (シグナルのキャンセルが完了した後)。それ以外の場合、シグナルはプロセスの保留中のシグナル セットから削除されません (シグナルのキャンセルが完了します)。プロセスが対応するシグナル処理関数を実行する前に、まずプロセス内のシグナルを登録解除する必要があります。 2. 信号の寿命が尽きる。プロセスがシグナルをキャンセルした後、対応するシグナル処理関数が直ちに実行され、実行が完了すると、今回送信されたシグナルがプロセスに与える影響は完全に終了します。 1.4. シグナルの実行とキャンセルカーネルは、プロセスが受信したソフト割り込み信号をプロセスのコンテキストで処理するため、プロセスは実行状態にある必要があります。シグナルまたは通常のスケジューリングによって CPU を再び取得すると、カーネル空間からユーザー空間に戻るときに処理を待機しているシグナルがあるかどうかを検出します。処理を待機している保留中のシグナルがあり、そのシグナルがプロセスによってブロックされていない場合、プロセスは対応するシグナル処理関数を実行する前に、保留中のシグナル チェーン内のシグナルによって占有されている構造を削除します。マスクされていない信号がすべて処理されると、ユーザー空間に戻ることができます。マスクされた信号の場合、マスクが解除されると、ユーザー空間に戻るときに上記のチェックと処理フローが再度実行されます。 シグナル処理には、シグナルを受信後にプロセスが終了する、シグナルを無視する、シグナルを受信後にシステムコールシグナルを使用してユーザーが設定した機能を実行する、の 3 種類があります。プロセスが無視するシグナルを受信すると、プロセスはそのシグナルを破棄し、シグナルを受信しなかったかのように実行を続行します。プロセスがキャッチするシグナルを受信すると、プロセスがカーネル モードからユーザー モードに戻るときにユーザー定義関数が実行されます。さらに、ユーザー定義関数の実行方法は非常に巧妙です。カーネルはユーザー スタック上に新しいレイヤーを作成し、その戻りアドレスの値がユーザー定義処理関数のアドレスに設定されます。このように、プロセスがカーネルから戻ってスタックの先頭をポップすると、ユーザー定義関数に戻ります。関数から戻ってスタックの先頭を再びポップすると、カーネルに最初に入った場所に戻ります。その理由は、ユーザー定義の処理関数はカーネル モードで実行できず、また実行が許可されていないためです (ユーザー定義関数がカーネル モードで実行されると、ユーザーは任意の権限を取得できます)。 例えば: #include <assert.h> #include <stdio.h> #include <文字列.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> void myHandler(int num) { 戻り値: (SIGUSR1 == 数値)の場合 { sigset_t セット; ret = sigemptyset(&set); アサート(!(-1 == ret)); ret = sigaddset(&set, SIGINT); アサート(!(-1 == ret)); ret = sigaddset(&set, SIGRTMIN); アサート(!(-1 == ret)); ret = sigprocmask(SIG_UNBLOCK, &set, NULL); アサート(!(-1 == ret)); printf("受信信号番号のブロックを解除: %d\n", num); } そうでない場合 (num == SIGINT || num == SIGRTMIN) { printf("受信符号番号: %d\n", num); } それ以外 { printf("その他の信号受信信号番号: %d\n", num); } } int メイン(void) { pid_t pid; 戻り値: //コールバック関数を設定する struct sigaction act; act.sa_handler = myHandler; act.sa_flags = SA_SIGINFO; // 非リアルタイム信号の処理関数を登録します。 ret = sigaction(SIGINT, &act, NULL); アサート(!(-1 == ret)); // リアルタイム信号処理関数を登録します。ret = sigaction(SIGRTMIN, &act, NULL); アサート(!(-1 == ret)); // ユーザー定義シグナルを登録します ret = sigaction(SIGUSR1, &act, NULL); アサート(!(-1 == ret)); // ブロッキングステータスワードに SIGINT SIGRTMIN を追加します sigset_t set; ret = sigemptyset(&set); アサート(!(-1 == ret)); ret = sigaddset(&set, SIGINT); アサート(!(-1 == ret)); ret = sigaddset(&set, SIGRTMIN); アサート(!(-1 == ret)); ret = sigprocmask(SIG_BLOCK, &set, NULL); アサート(!(-1 == ret)); pid = fork(); アサート(!(-1 == ret)); (0 == pid)の場合 { ユニオン信号値; 値.sival_int = 10; 整数 i = 0; // 3 つの不安定な信号を送信します (i = 0; i < 3; i++) { ret = sigqueue(getppid(), SIGINT, 値); アサート(!(-1 == ret)); printf("信頼できない信号を送信しています。\n"); } // 3 つの安定した信号を送信します value.sival_int = 20; (i = 0; i < 3; i++) の場合 { ret = sigqueue(getppid(), SIGRTMIN, 値); アサート(!(-1 == ret)); printf("信頼性の高いシグナルを送信しました\n"); } // ブロックを解除するために親プロセスに SIGUSR1 を送信します。ret = kill(getppid(), SIGUSR1); アサート(!(-1 == ret)); } (1) { 睡眠(1); } 0を返します。 } 2. 信号マスクと信号処理機能の継承2.1. 信号処理機能の継承シグナル処理機能はプロセス属性なので、プロセス内の各スレッドのシグナル処理機能は同じです。 fork によって作成された子プロセスは、親プロセスのシグナル処理機能を継承します。 execve 後、処理対象として設定された信号処理関数はデフォルトの関数にリセットされ、無視対象として設定された信号は変更されません。つまり、親プロセスのシグナル設定が SIG_IGN の場合、子プロセスが exec のときに、このシグナルの処理は無視され、デフォルトの機能にリセットされません。 例えば: // test.c --> テスト #include <stdlib.h> typedef void (*sighandler_t)(int); 静的 sighandler_t old_int_handler; 静的sighandler_t old_handlers[SIGSYS + 1]; void sig_handler(int signo) { printf("signo %d を受信\n",signo); 古いハンドラ[signo](signo); } int main(int argc, char **argv) { old_handlers[SIGINT] = シグナル(SIGINT、SIG_IGN); old_handlers[SIGTERM] = シグナル(SIGTERM、sig_handler); 整数 戻り値; ret = fork(); 戻り値が 0 の場合 //子供 // ここで execlp は test2 を子プロセスとして実行します。 execlp("/tmp/test2", "/tmp/test2",(char*)NULL); }それ以外の場合 (ret > 0) { //親 (1) の間 { 睡眠(1); } }それ以外{ エラー(""); アボート(); } } ================================================ test2.c --> test2 #include <stdio.h> int main(int argc, char **argv) { (1) の間 { 睡眠(1); } 0を返します。 } 結論: test が test2 に変更された後も、SIGINT は無視され、SIGTERM はデフォルト モードにリセットされます。 2.2 信号マスクの継承シグナルマスクには次のルールがあります。 1. 各スレッドは独自のシグナル マスクを持つことができます。 2. フォークされた子プロセスは親プロセスのシグナル マスクを継承し、シグナル マスクは exec 後も変更されません。親プロセスがマルチスレッドの場合、子プロセスはメインスレッドのマスクのみを継承します。 3. プロセスに送信されたシグナルは、シグナルをブロックしていないすべてのスレッドによって受信されます。ランダムに受信されるのは 1 つのスレッドのみであることに注意してください。 Linux では、すべてのスレッドがシグナルを受信できる場合、シグナルはデフォルトでメインスレッドに送信されますが、POSIX システムではシグナルはランダムに送信されます。 4. fork 後、子プロセスで設定された保留中のシグナルは空に初期化され、exec は保留中のシグナル セットを保持します。 #include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h> typedef void (*sighandler_t)(int); 静的void *thread1(void *arg) { sigset_t セット; printf("スレッド1内\n"); sigemptyset(&set); sigaddset(&set, SIGTERM); pthread_sigmask(SIG_BLOCK, &set, NULL); (1) の間 { 睡眠(1); } } 静的 void sigset_print(sigset_t *set) { 整数 i; (i = 1; i <= SIGSYS; i++) の場合 { sigismember(set, i) の場合 { printf("シグナル%dはセット内にあります\n",i); } } } int main(int argc, char **argv) { 整数 戻り値; sigset_t セット; pthread_t pid; pthread_create(&pid, NULL, スレッド1, NULL); 睡眠(1); sigemptyset(&set); sigaddset(&set, SIGINT); pthread_sigmask(SIG_BLOCK, &set, NULL); ret = fork(); 戻り値が 0 の場合 //子供 pthread_sigmask(SIG_BLOCK、NULL、&set); sigset_print(&set); (1) の間 { 睡眠(1); } }それ以外の場合 (ret > 0) { //親 (1) の間 { 睡眠(1); } }それ以外{ エラー(""); アボート(); } } 結論: メインスレッドで設定されたマスクのみが子プロセスに継承されます。その理由は、Linux の fork は fork() を呼び出すスレッドのみをコピーするため、子プロセスでは親プロセスのメイン スレッドのみがコピーされ、もちろんシグナル マスクは親プロセスのメイン スレッドのシグナル マスクのコピーになるからです。これは、thread1 で fork が呼び出された場合、子プロセスのシグナル マスクが thread1 のコピーになることを再度証明しています。 2.3、sigwait とマルチスレッドSigwait 関数: sigwait は、指定された 1 つ以上のシグナルが発生するのを待機します。 それは次の 2 つのことだけを行います: まず、ブロックされた信号を聞きます。 次に、監視対象の信号が生成されると、その信号は保留キューから削除されます。 sigwait はシグナル マスクのブロッキングまたは非ブロッキング状態を変更しません。 POSIX 標準では、プロセスがシグナルを受信したときに、マルチスレッドの状況であれば、どのスレッドがシグナルを処理するかを判断できません。 Sigwait は、プロセス内の保留中のシグナルから指定されたシグナルを取得します。この場合、sigwait スレッドがシグナルを確実に受信するようにしたい場合、メインスレッドと sigwait スレッドを含むすべてのスレッドがシグナルをブロックする必要があります。なぜなら、自分自身がブロックしないと、シグナルが保留状態 (ブロックされた状態) にならないためです。他のすべてのスレッドがブロックしないと、シグナルが来たときに他のスレッドによって処理される可能性があります。 追伸: マルチスレッド コードでは、シグナルを処理するために常に sigwait や sigwaitinfo や sigtimedwait などの関数を使用します。 signal や sigaction などの関数の代わりに。スレッド内で signal や sigaction などの関数を呼び出すと、signal/sigaction を呼び出したスレッドのシグナル処理関数が変更されるだけでなく、すべてのスレッドのシグナル処理関数が変更されるためです。 2.4. 複数プロセスにおけるシグナルマルチプロセス モードでは、キーボードによってトリガーされた信号が現在のプロセス グループ内のすべてのプロセスに同時に送信されます。プログラムが実行中に複数の子プロセスをフォークする場合、キーによってトリガーされたシグナルはプログラムのすべてのプロセスによって受信されます。 ただし、マルチスレッドとは異なり、マルチプロセスでのシグナルマスクとシグナル処理機能は独立しています。各プロセスは、処理するかしないかを選択でき、独自のシグナル マスクを設定することもできます。 #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> int main(int argc, char **argv) { pid_t pid = fork(); シグナル(SIGCHLD、SIG_IGN); (pid < 0)の場合 printf("エラーフォーク\n"); そうでない場合 (pid == 0) { signal(SIGINT, SIG_IGN); // Ctrl+C の後も子プロセスが存続できるように SIGINT を無視します。設定されていない場合は、シグナルを受信すると終了します。printf("child gid = %ld\n", getpgid(getpid())); する { 睡眠(1); } 一方で (1); } それ以外 { printf("親 gid = %ld\n", getpgid(getpid())); する { 睡眠(1); } 一方で (1); } 0を返します。 } 上図の通り、親プロセスはSIGINTを受信すると終了し、子プロセスはSIGINTを無視するように設定されているため影響を受けないことがわかります。 APIについて3.1 信号生成機能1.kill(pid_t pid, int signum); 2. int sigqueue(pid_t pid, int sig, const union sigval 値); 3.pthread_kill(pthread_t tid, int signum); 4.raise(int signum); // 自分にシグナルを送る 5.voidアラーム(void); 6.void 中止(void); 7.int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); 追伸: sigqueue() は kill() よりも多くの追加情報を渡しますが、sigqueue() はプロセスにのみシグナルを送信でき、プロセス グループには送信できません。 signo=0 の場合、エラー チェックは実行されますが、シグナルは実際には送信されません。0 値のシグナルは、pid の有効性と、現在のプロセスがターゲット プロセスにシグナルを送信する権限を持っているかどうかを確認するために使用できます。 3.2 信号処理機能1.シグナル(int signum, void (*ハンドラ)(int signum)) 2.sigaction(int signum, struct sigaction* newact, sigaction* oldact) sigaction 行為; act.sa_handler = ハンドラー; act.sa_flags = SA_SIGINFO; // 信号処理関数を登録します sigaction(SIGINT, act, NULL); 3.3 信号マスク機能1.sigprocmask(int how, struct sigaction* set,struct sigaction* oldset) 2.pthread_sigmask(int how, struct sigaction* set,struct sigaction* oldset) sigprocmask はプロセスのシグナル マスクを設定するために使用され、pthread_sigmask はスレッドのシグナル マスクを設定するために使用されます。 2 つのパラメーターは同じです。最初のパラメータには 3.4. シグナル収集変数
3.5. 信号シールド機能1.int sigpending(sigset_t *set); // ブロックされたシグナルセットを返す 2.int sigsuspend(const sigset_t *マスク); sigsuspend は、シグナルマスクを一時的にマスクに設定し、シグナルが生成されるまでプロセスを一時停止することを意味します (マスクされていないシグナルはプロセスを起動または終了できます)。シグナル処理関数が戻ると、siguspend は以前のシグナルマスクを (一時的に) 復元します。 sisuspend がプロセスをブロックするときにシグナル A が生成され、A がマスク内のマスクされたシグナルではないと仮定すると、A のシグナル処理機能には 2 つの状況があります。 1: プロセスを直接終了します。この時点ではプロセスは存在しないため、sigsuspend は戻る必要はありません (プロセスが存在しない場合は、sigsuspend も関数スタックも存在しません)。 2. シグナル A の処理関数が戻ると、シグナル マスク ワードは sigsuspend 前の状態に復元されます (シグナル マスク ワードは sigsuspend が呼び出されたときにマスクに設定されるため、sigsuspend が呼び出される前の状態に復元する必要があります)。その後、sigsuspend は -1 を返し、エラーを EINTR に設定します。 上記は Linux シグナル メカニズムの詳細についての簡単な説明です。Linux シグナル メカニズムの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
<<: ウェブページのテキストデザインは、服を着た賢い女の子のようであるべきだ
>>: Vueはキャンバスの手書き入力を使用して中国語を認識します
1. 準備1.1 VMware 15 をダウンロードしてインストールするダウンロード リンク: h...
1. 内部結合クエリの概要内部結合は、アプリケーションで非常に一般的な結合操作であり、通常はデフォ...
目次まず効果を見てみましょう:成し遂げる:要約:まず効果を見てみましょう: 成し遂げる: 1. ナビ...
目次1. 本来の定義2. JS操作、幅の変更を例に3. 効果: 幅が変更されました 1. 本来の定義...
このケースはCentOS 7システムに基づいていますDockerの使用経験がある人に適していますLi...
最近、何人かの友人から、仮想マシンに CentOS をインストールした後、ifconfig コマンド...
アプリケーションシナリオデータ テーブルでは、アプリケーションは各データがいつ作成されたかを記録する...
目次1.まずネットワークカードの設定ディレクトリに入る2. ifcfg-ens33ネットワークカード...
1. 仮想マシン側1. MySQLの設定ファイルを見つける:sudo vim /etc/mysql/...
1. 従来のbinlogマスタースレーブレプリケーション、エラー報告をスキップする方法 mysql&...
目次MySQL 8 の隠しインデックス、降順インデックス、関数インデックス1. 隠しインデックス1....
この記事では、例を使用して、MySQL データベースの基本的な知識と操作について説明します。ご参考ま...
CSS 属性セレクターは素晴らしいです。大量のクラス名を追加することを回避し、コード内の問題を指摘す...
テキストシャドウテキストに影を追加します。テキストとテキスト装飾に複数のシャドウを追加することができ...
この記事では、参考までにMySQL 5.7.24圧縮パッケージのインストールチュートリアルを紹介しま...