web-dev-qa-db-ja.com

信号は内部でどのように機能しますか?

一般に、プロセスを強制終了するために、SIGKILLSIGTSTPなどのシグナルを生成します。

しかし、その特定のシグナルを誰が注文し、誰が特定のプロセスに送信したか、そして一般にシグナルはどのように操作を実行するのか、どのように知られていますか?内部で信号はどのように機能しますか?

32
Varun Chhangani

50,000フィートのビューは次のとおりです。

  1. シグナルはカーネルによって内部的に生成されます(たとえば、無効なアドレスにアクセスした場合はSIGSEGV、ヒットした場合はSIGQUIT Ctrl+\)、またはkill syscall(またはいくつかの関連するもの)を使用するプログラムによって。

  2. Syscallのいずれかによる場合、カーネルは、呼び出しプロセスがシグナルを送信するための十分な特権を持っていることを確認します。そうでない場合、エラーが返されます(シグナルは発生しません)。

  3. 2つの特別な信号の1つである場合、カーネルは無条件にターゲットプロセスからの入力なしで動作します。 2つの特別なシグナルはSIGKILLとSIGSTOPです。デフォルトのアクション、ブロック信号などに関する以下のものはすべて、これら2つには関係ありません。

  4. 次に、カーネルはシグナルをどうするかを理解します。

    1. プロセスごとに、各シグナルに関連付けられたアクションがあります。デフォルトはたくさんあり、プログラムはsigactionsignalなどを使用してさまざまなデフォルトを設定できます。これらには、「完全に無視する」、「プロセスを終了する」、「プロセスを終了する」などが含まれます。コアダンプあり」、「プロセスを停止」など.

    2. プログラムは、信号ごとに信号の配信をオフにすることもできます(「ブロック」)。その後、信号はブロックが解除されるまで保留されます。

    3. プログラムは、カーネルが何らかのアクションをとるのではなく、同期的に(sigwaitなどとsignalfdを使用して)または非同期的に(シグナルを中断することによって)プロセスに信号を配信するように要求できます。プロセスが実行し、指定された関数を呼び出します)。

「リアルタイム信号」と呼ばれる2番目の信号セットがあります。これらの信号には特定の意味はなく、複数の信号をキューに入れることもできます(通常の信号は、信号がブロックされたときにそれぞれ1つだけをキューに入れます)。これらは、スレッドが相互に通信するためのマルチスレッドプログラムで使用されます。たとえば、いくつかはglibcのPOSIXスレッド実装で使用されています。これらは、異なるプロセス間の通信にも使用できます(たとえば、いくつかのリアルタイム信号を使用して、fooctlプログラムにfooデーモンにメッセージを送信させることができます)。

50,000フィート以外のビューについては、man 7 signalおよびカーネル内部のドキュメント(またはソース)。

35
derobert

信号の実装は非常に複雑で、カーネル固有です。つまり、カーネルが異なれば、信号の実装も異なります。簡単な説明は次のとおりです。

CPUは、特別なレジスタ値に基づいて、実際にはベクトルテーブルである「割り込み記述子テーブル」を見つけることを期待するメモリ内のアドレスを持っています。ゼロによる除算やINT 3(デバッグ)のようなトラップなど、起こりうるすべての例外に対して1つのベクトルがあります。 CPUが例外を検出すると、フラグと現在の命令ポインタをスタックに保存し、関連するベクトルで指定されたアドレスにジャンプします。 Linuxでは、このベクトルは常にカーネルを指し、例外ハンドラがあります。これでCPUが完了し、Linuxカーネルが引き継ぎます。

ソフトウェアから例外をトリガーすることもできます。たとえば、ユーザーは CTRL-C、その後、この呼び出しは、独自の例外ハンドラを呼び出すカーネルに送られます。一般に、ハンドラーにアクセスするにはさまざまな方法がありますが、同じ基本的なことが発生しても、コンテキストがスタックに保存され、カーネルの例外ハンドラーにジャンプします。

次に、例外ハンドラーがシグナルを受信するスレッドを決定します。ゼロ除算のような何かが発生した場合、それは簡単です。例外を引き起こしたスレッドがシグナルを取得しますが、他のタイプのシグナルの場合、決定は非常に複雑になり、場合によっては多かれ少なかれランダムなスレッドになる可能性があります信号を取得します。

カーネルが行うことをシグナルに送信するには、最初にシグナルのタイプを示す値SIGHUPなどを設定します。これは単なる整数です。すべてのプロセスには、この値が格納される「保留中のシグナル」メモリ領域があります。次に、カーネルは信号情報を使用してデータ構造を作成します。この構造には、デフォルト、無視、または処理されるシグナル「ディスポジション」が含まれます。次に、カーネルは独自の関数do_signal()を呼び出します。次のフェーズが始まります。

do_signal()は、最初にitが信号を処理するかどうかを決定します。たとえば、それがkillの場合、do_signal()はプロセスを強制終了し、ストーリーの終わりです。そうでなければ、それは気質を見ます。後処理がデフォルトの場合、do_signal()は、信号に依存するデフォルトのポリシーに従って信号を処理します。後処理がハンドルである場合、問題の信号を処理するように設計されたユーザープログラム内の関数があり、この関数へのポインターが前述のデータ構造内にあることを意味します。この場合、do_signal()は別のカーネル関数handle_signal()を呼び出します。この関数は、ユーザーモードに切り替えてこの関数を呼び出すプロセスを実行します。このハンドオフの詳細は非常に複雑です。プログラム内のこのコードは、通常、signal.hの関数を使用すると、プログラムに自動的にリンクされます。

保留中のシグナル値を適切に検査することにより、カーネルはプロセスがすべてのシグナルを処理しているかどうかを判断でき、シグナルが処理されていない場合は適切なアクションを実行します。

23
Tyler Durden

この質問には回答済みですが、Linuxカーネルでのイベントの詳細なフローを投稿させてください。
これは、sklinuxblog.blogspot.inの「Linuxの投稿」ブログの Linuxの投稿:Linux Signals-Internals から完全にコピーされます。

シグナルユーザースペース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シグナルに新しいハンドラーを割り当てます。 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
_

また、次のキーの組み合わせを使用して、特定の信号を送信できます。

  • Ctrl+C – SIGINTを送信します。デフォルトのアクションはアプリケーションを終了することです。
  • Ctrl+\– SIGQUITを送信します。デフォルトのアクションは、アプリケーションダンプコアを終了することです。
  • 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>_プロセスは終了しません。代わりに、シグナルハンドラーを実行して戻ります。

シグナルがプロセスに送信される方法

プロセスに送信する信号の内部を確認し、dump_stackを使用してJprobeを___send_signal_関数に配置すると、次の呼び出しトレースが表示されます。

_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_にあります。

関数int_signal関数は、関数do_notify_resume()を呼び出す_entry_64.S_から呼び出されます。

関数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が完了する前に実行されます。 syscallはデフォルトでは完了しません。

_SA_RESTART_フラグが設定されている場合、syscallはシグナルハンドラの終了後に再起動されます。

参考文献

15
K_K