通常、プロセスを終了するには、次の信号を生成しSIGKILL
ますSIGTSTP
。
しかし、誰が特定のシグナルを発行したのか、誰が特定のプロセスにシグナルを送信したのか、シグナルが一般にどのように機能するのか、どうすればわかりますか?信号は内部的にどのように機能しますか?
答え1
50,000フィートでの視野は次のとおりです。
信号は、カーネル(
SIGSEGV
無効なアドレスにアクセスしたり+SIGQUIT
キーを押すなど)やシステムコール(または複数の関連コール)を使用するプログラムによって内部的に生成されます。Ctrl\kill
システムコールの1つによって実行されると、カーネルはコールプロセスにシグナルを送信するのに十分な権限があることを確認します。それ以外の場合はエラーが返されます(信号は発生しません)。
2つの特殊信号のうちの1つである場合、カーネルはターゲットプロセスの入力なしで無条件にその信号に対して機能します。これら2つの特殊信号はSIGKILLとSIGSTOPです。基本的な作業、信号の遮断などに関する以下のすべての内容は、これらのいずれかに関連していません。
次に、カーネルは信号で何をすべきかを調べます。
各プロセスについて、各信号には関連するアクションがあります。多くのデフォルト値があり、プログラムは
sigaction
他を使用して他のデフォルト値を設定できます。signal
これには、「完全に無視」、「プロセス終了」、「コアダンプを使用したプロセスの終了」、「プロセスの停止」などが含まれます。プログラムは信号ごとに信号伝達(「ブロック」)をオフにすることもできます。その後、信号はブロック解除されるまで保留のままです。
プログラムは、カーネルがそれ自体で特定の操作を実行せずに、代わりに同期(、待機、または
sigwait
使用signalfd
)または非同期(プロセスが実行しているすべての操作を中断し、指定された関数を呼び出す)としてプロセスにシグナルを渡すように要求できます。
「リアルタイム信号」と呼ばれる2番目の信号グループがあります。これは特に意味がなく、複数の信号をキューに入れることもできます(通常の信号は、信号がブロックされると各信号の1つだけがキューに入れられます)。スレッド間通信のためにマルチスレッドプログラムで使用されます。たとえば、glibc の複数の POSIX スレッド実装があります。また、異なるプロセス間の通信にも使用できます(たとえば、複数のリアルタイム信号を使用して、fooctlプログラムにfooデーモンにメッセージを送信させることもできます)。
50,000フィート以外のビューについては、man 7 signal
カーネル内部ドキュメント(またはソースコード)を見てください。
答え2
信号の実装は非常に複雑で、カーネルによって異なります。つまり、異なるカーネルは異なる方法で信号を実装します。簡単な説明は次のとおりです。
CPUは、特殊レジスタ値に基づいて実際にベクトルテーブルである「割り込み記述子テーブル」を見つけることが期待されるメモリアドレスを持っています。ゼロ除算やINT 3(デバッグ)などのトラップなど、可能な各例外のベクトルがあります。 CPUで例外が発生すると、フラグと現在の命令ポインタをスタックに格納し、関連するベクトルで指定されたアドレスにジャンプします。 Linuxでは、このベクトルは常に例外ハンドラを持つカーネルを指します。これでCPUが完了したので、Linuxカーネルが代わりになります。
ソフトウェアで例外をトリガーすることもできます。たとえば、ユーザーがCTRL- を押すとC、この呼び出しは独自の例外ハンドラを呼び出すカーネルに移動します。通常、ハンドラに到達する方法はさまざまですが、基本的に同じことが起こります。つまり、コンテキストがスタックに格納され、カーネルの例外ハンドラにジャンプされます。
その後、例外ハンドラは、どのスレッドがシグナルを受け取るべきかを決定します。ゼロで割るなどのことが起こると簡単です。例外を発生させたスレッドはシグナルを取得しますが、他のタイプのシグナルの場合、決定は非常に複雑になる可能性があり、いくつかの珍しい場合は、多少ランダムなスレッドがシグナルを取得する可能性があります。
シグナルを送信するために、カーネルはまずシグナルのSIGHUP
タイプまたは他の値を表す値を設定します。これは単なる整数です。各プロセスには、この値が保存される「保留中の信号」記憶領域があります。カーネルは信号情報を含むデータ構造を作成します。この構造には、デフォルト、無視、または処理できる信号「処理」が含まれます。その後、カーネルは独自の関数を呼び出しますdo_signal()
。次のステップが始まります。
do_signal()
かどうかを最初に決定してください。それ信号が処理されます。たとえば、次のような場合殺すその後、do_signal()
プロセスを終了するとストーリーが終了します。それ以外の場合は、構成を見てください。 dispositionがdefaultの場合、do_signal()
シグナルはシグナル依存のデフォルトポリシーに従って処理されます。ハンドルがハンドルの場合、その信号を処理するように設計された関数がユーザープログラムにあることを意味し、その関数へのポインタは上記のデータ構造に配置されます。この場合、do_signal()は別のカーネル関数を呼び出してhandle_signal()
からユーザーモードに戻り、その関数を呼び出すプロセスを経ます。このスイッチの詳細は非常に複雑です。プログラムのこのコードスニペットは、通常、.NETで関数を使用するとプログラムに自動的にリンクされますsignal.h
。
保留中の信号値を適切に確認することで、カーネルはプロセスがすべての信号を処理していることを確認できます。取ることができます。
答え3
この質問に対する回答はすでに示されていますが、Linuxカーネルの詳細なイベントフローを公開してみましょう。
これは正確にコピーされましたLinuxポスト:Linuxシグナル - 内部構造
sklinuxblog.blogspot.comの「Linuxポスト」ブログ。私もこのようなブログを書く。
シグナルユーザースペースCプログラム
簡単なシグナルユーザー空間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信号に新しいハンドラを割り当てます。Ctrl+Cキーの組み合わせを使用して、実行中のプロセスにSIGINTを送信できます。Ctrl+を押すと、C非同期信号SIGINTがジョブに送信されます。これはkill -INT <pid>
他の端末からコマンドを送信するのと同じです。
kill -l
a(「list」を意味する小文字)を実行すると、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
次のキーの組み合わせを使用して特定の信号を送信することもできます。
- Ctrl+ C– SIGINTを送信すると、デフォルトの動作はアプリケーションを終了することです。
- Ctrl+</kbd> – sends SIGQUIT which default action is to terminate the application dumping core.
- Ctrl+ Z- プログラムを一時停止するにはSIGSTOPを送信します。
上記のCプログラムをコンパイルして実行すると、次の結果が表示されます。
[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop
Ctrl+ Cを使用してもkill -2 <pid>
プロセスは終了しません。代わりにシグナルハンドラを実行して返します。
信号がプロセスに送信される方法
プロセスに送信される信号の内部構造を調べて、関数に__send_signal
dump_stackを持つJprobeを配置すると、次の呼び出しトレースを見ることができます。
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
プロセスに必要な変更が行われました。
信号処理
この信号は、プロセスがシステムコールから返されたとき、または割り込みから返されたときにプロセスによって確認/処理されます。システムコールの戻り結果はファイルにありますentry_64.S
。
entry_64.S
関数が呼び出されるint_signal関数が呼び出されます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
ハンドラが完了したら再起動してください。