一般に、プロセスを強制終了するにはSIGKILL
、SIGTSTP
などのシグナルを生成します。
しかし、特定のシグナルを注文した人、特定のプロセスに送信した人、一般的にシグナルがどのように操作を実行するのかはどのように知られていますか?信号は内部的にどのように機能しますか?
一般に、プロセスを強制終了するにはSIGKILL
、SIGTSTP
などのシグナルを生成します。
しかし、特定のシグナルを注文した人、特定のプロセスに送信した人、一般的にシグナルがどのように操作を実行するのかはどのように知られていますか?信号は内部的にどのように機能しますか?
回答:
50,000フィートのビューは次のとおりです。
シグナルは、カーネルによって内部的に(たとえば、SIGSEGV
無効なアドレスにアクセスしたSIGQUIT
とき、またはCtrl+を押したときに\)、kill
syscall を使用するプログラム(またはいくつかの関連するアドレス)によって生成されます。
syscallの1つによるものである場合、カーネルは呼び出しプロセスがシグナルを送信するのに十分な特権を持っていることを確認します。そうでない場合、エラーが返されます(シグナルは発生しません)。
2つの特別なシグナルのいずれかである場合、カーネルはターゲットプロセスからの入力なしで、無条件にそのシグナルに作用します。2つの特別なシグナルはSIGKILLとSIGSTOPです。デフォルトのアクション、ブロック信号などに関する以下のすべては、これら2つには無関係です。
次に、カーネルは信号をどう処理するかを判断します。
プロセスごとに、各信号に関連付けられたアクションがあります。そこデフォルトの束があり、プログラムが異なるものが使用して設定することができsigaction
、signal
等が挙げられる。これらは、「プロセスの停止」、「コア・ダンプでプロセスを強制終了」、「プロセスを殺す」、「それを完全に無視する」のようなものが含まれます等
プログラムは、信号ごとに信号の配信をオフにすることもできます(「ブロック」)。その後、ブロックが解除されるまで、信号は保留のままになります。
プログラムは、カーネルが何らかのアクションを実行する代わりに、同期(、sigwait
などsignalfd
)または非同期(プロセスの実行を中断して指定された関数を呼び出す)でプロセスにシグナルを配信するように要求できます。
「リアルタイムシグナル」と呼ばれる2番目のシグナルセットがありますが、これには特定の意味はなく、複数のシグナルをキューに入れることもできます(通常のシグナルは、シグナルがブロックされるとそれぞれ1つだけをキューに入れます)。これらは、スレッドが相互に通信するためのマルチスレッドプログラムで使用されます。たとえば、いくつかはglibcのPOSIXスレッド実装で使用されます。また、異なるプロセス間の通信にも使用できます(たとえば、いくつかのリアルタイム信号を使用して、fooctlプログラムにfooデーモンにメッセージを送信させることができます)。
50,000フィート以外のビューについてはman 7 signal
、カーネル内部のドキュメント(またはソース)も試してください。
シグナルの実装は非常に複雑で、カーネル固有です。つまり、異なるカーネルは異なる方法でシグナルを実装します。簡単な説明は次のとおりです。
CPUは、特別なレジスタ値に基づいて、実際にベクターテーブルである「割り込み記述子テーブル」を見つけることができるメモリ内のアドレスを持っています。ゼロ除算などの考えられるすべての例外、またはINT 3(デバッグ)などのトラップごとに1つのベクトルがあります。CPUは例外を検出すると、フラグと現在の命令ポインターをスタックに保存し、関連するベクトルで指定されたアドレスにジャンプします。Linuxでは、このベクトルは常に例外ハンドラーがあるカーネルを指します。これでCPUが完了し、Linuxカーネルが引き継ぎます。
ソフトウェアから例外をトリガーすることもできます。たとえば、ユーザーがCTRL-を押すCと、この呼び出しはカーネルに送られ、カーネルは独自の例外ハンドラーを呼び出します。一般に、ハンドラーにアクセスする方法はいくつかありますが、基本的なことは同じですが、コンテキストはスタックに保存され、カーネルの例外ハンドラーにジャンプします。
次に、例外ハンドラーは、どのスレッドがシグナルを受信するかを決定します。ゼロ除算のようなものが発生した場合、それは簡単です:例外を引き起こしたスレッドはシグナルを取得しますが、他のタイプのシグナルの場合、決定は非常に複雑になる可能性があります。信号を取得します。
カーネルが行うことをシグナルに送信するには、まずシグナルのタイプなどを示す値を設定しますSIGHUP
。これは単なる整数です。すべてのプロセスには、この値が保存される「保留信号」メモリ領域があります。次に、カーネルは信号情報を使用してデータ構造を作成します。この構造には、デフォルト、無視、または処理の可能性のあるシグナル「ディスポジション」が含まれます。その後、カーネルは独自の関数を呼び出しますdo_signal()
。次のフェーズが始まります。
do_signal()
最初かどうかを決定し、それは信号を処理します。それがある場合たとえば、キル、そしてdo_signal()
ちょうどプロセス、物語の終わりを殺します。それ以外の場合は、性質を調べます。後処理がデフォルトの場合、do_signal()
信号に依存するデフォルトのポリシーに従って信号を処理します。処理がハンドルの場合、問題の信号を処理するように設計されたユーザープログラムに関数があり、この関数へのポインターは前述のデータ構造内にあることを意味します。この場合、do_signal()は別のカーネル関数を呼び出します。handle_signal()
、ユーザーモードに戻ってこの関数を呼び出すプロセスを実行します。このハンドオフの詳細は非常に複雑です。プログラム内のこのコードは、通常、で関数を使用するときにプログラムに自動的にリンクされますsignal.h
。
保留中のシグナルの値を適切に調べることにより、カーネルはプロセスがすべてのシグナルを処理しているかどうかを判断でき、そうでない場合は適切なアクションを実行します。
この質問には答えましたが、Linuxカーネルのイベントの詳細なフローを投稿させてください。
これは、Linux投稿から完全にコピーさ
れます。LinuxSignals-「Linux投稿」ブログのsklinuxblog.blogspot.inの内部。
簡単なシグナルユーザー空間Cプログラムの作成から始めましょう。
#include<signal.h>
#include<stdio.h>
/* Handler function */
void handler(int sig) {
printf("Receive signal: %u\n", sig);
};
int main(void) {
struct sigaction sig_a;
/* Initialize the signal handler structure */
sig_a.sa_handler = handler;
sigemptyset(&sig_a.sa_mask);
sig_a.sa_flags = 0;
/* Assign a new handler function to the SIGINT signal */
sigaction(SIGINT, &sig_a, NULL);
/* Block and wait until a signal arrives */
while (1) {
sigsuspend(&sig_a.sa_mask);
printf("loop\n");
}
return 0;
};
このコードは、SIGINTシグナルに新しいハンドラーを割り当てます。SIGINTは、Ctrl+ Cキーの組み合わせを使用して実行中のプロセスに送信できます。ときはCtrl+がC押され、その後、非同期シグナルSIGINTは、タスクに送信されます。またkill -INT <pid>
、他の端末でコマンドを送信することと同等です。
あなたが行う場合kill -l
(つまり、小文字だL
「リスト」の略で、)あなたは実行中のプロセスに送信することができ、各種の信号を知るようになるだろう。
[root@linux ~]# kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
また、次のキーの組み合わせを使用して特定の信号を送信できます。
上記のCプログラムをコンパイルして実行すると、次の出力が得られます。
[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop
でもでCtrl+ Cまたはkill -2 <pid>
プロセスが終了しません。代わりに、シグナルハンドラを実行して戻ります。
プロセスに送信するシグナルの内部を確認し、dump_stackでJprobeを__send_signal
functionに配置すると、次の呼び出しトレースが表示されます。
May 5 16:18:37 linux kernel: dump_stack+0x19/0x1b
May 5 16:18:37 linux kernel: my_handler+0x29/0x30 (probe)
May 5 16:18:37 linux kernel: complete_signal+0x205/0x250
May 5 16:18:37 linux kernel: __send_signal+0x194/0x4b0
May 5 16:18:37 linux kernel: send_signal+0x3e/0x80
May 5 16:18:37 linux kernel: do_send_sig_info+0x52/0xa0
May 5 16:18:37 linux kernel: group_send_sig_info+0x46/0x50
May 5 16:18:37 linux kernel: __kill_pgrp_info+0x4d/0x80
May 5 16:18:37 linux kernel: kill_pgrp+0x35/0x50
May 5 16:18:37 linux kernel: n_tty_receive_char+0x42b/0xe30
May 5 16:18:37 linux kernel: ? ftrace_ops_list_func+0x106/0x120
May 5 16:18:37 linux kernel: n_tty_receive_buf+0x1ac/0x470
May 5 16:18:37 linux kernel: flush_to_ldisc+0x109/0x160
May 5 16:18:37 linux kernel: process_one_work+0x17b/0x460
May 5 16:18:37 linux kernel: worker_thread+0x11b/0x400
May 5 16:18:37 linux kernel: rescuer_thread+0x400/0x400
May 5 16:18:37 linux kernel: kthread+0xcf/0xe0
May 5 16:18:37 linux kernel: kthread_create_on_node+0x140/0x140
May 5 16:18:37 linux kernel: ret_from_fork+0x7c/0xb0
May 5 16:18:37 linux kernel: ? kthread_create_on_node+0x140/0x140
したがって、信号を送信するための主要な関数呼び出しは次のようになります。
First shell send the Ctrl+C signal using n_tty_receive_char
n_tty_receive_char()
isig()
kill_pgrp()
__kill_pgrp_info()
group_send_sig_info() -- for each PID in group call this function
do_send_sig_info()
send_signal()
__send_signal() -- allocates a signal structure and add to task pending signals
complete_signal()
signal_wake_up()
signal_wake_up_state() -- sets TIF_SIGPENDING in the task_struct flags. Then it wake up the thread to which signal was delivered.
これですべてが設定されtask_struct
、プロセスに必要な変更が行われました。
シグナルは、システムコールから戻ったとき、または割り込みから戻ったときに、プロセスによってチェック/処理されます。システムコールからの戻り値はfileにありますentry_64.S
。
関数int_signal関数が呼び出され、entry_64.S
そこから関数が呼び出されますdo_notify_resume()
。
関数を確認しましょうdo_notify_resume()
。この関数は、以下にTIF_SIGPENDING
フラグが設定されているかどうかを確認しますtask_struct
。
/* deal with pending signal delivery */
if (thread_info_flags & _TIF_SIGPENDING)
do_signal(regs);
do_signal calls handle_signal to call the signal specific handler
Signals are actually run in user mode in function:
__setup_rt_frame -- this sets up the instruction pointer to handler: regs->ip = (unsigned long) ksig->ka.sa.sa_handler;
読み取り/書き込みのブロック、プロセスを待機状態にするTASK_INTERRUPTIBLE
などの「遅い」システムコール:
またはTASK_UNINTERRUPTIBLE
。
状態のタスクは、シグナルによって状態にTASK_INTERRUPTIBLE
変更されTASK_RUNNING
ます。TASK_RUNNING
プロセスをスケジュールできることを意味します。
実行された場合、そのシグナルハンドラは「遅い」システムコールの完了前に実行されます。syscall
デフォルトでは完了しません。
SA_RESTART
フラグが設定されている場合syscall
、シグナルハンドラの終了後に再起動されます。
kill
シェル組み込みコマンドであるコマンドを使用する場合を除く)。(2c)}
関数を閉じた後のセミコロンは、厳密に言えばエラーではありませんが、不要であり非常に非正統的です。(3)すべてが正しかったとしても、それは質問に対する非常に良い答えではないでしょう。(3a)質問はやや不明瞭ですが、アクター(ユーザーおよびプロセス)がシグナルを開始(送信)する方法に焦点を当てているようです。…(続き)