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


31

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

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


質問を理解するのは少し難しいです。私は謝罪し、無礼を意味しません。プロセスを強制終了したコマンドを実行した可能性のある人を知りたいですか、またはSIGKILLとSIGSTPについてもっと知りたいですか?
pullsumo

@mistermisterプロセスを殺したコマンドを誰がどのように実行したのか、どのように知りたいですか?
ヴァルンチャンガニ

回答:


35

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

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

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

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

  4. 次に、カーネルは信号をどう処理するかを判断します。

    1. プロセスごとに、各信号に関連付けられたアクションがあります。そこデフォルトの束があり、プログラムが異なるものが使用して設定することができsigactionsignal等が挙げられる。これらは、「プロセスの停止」、「コア・ダンプでプロセスを強制終了」、「プロセスを殺す」、「それを完全に無視する」のようなものが含まれます等

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

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

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

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


SIGCONTが...何であってもよいので、「二つの特別な信号は、SIGKILLとSIGSTOPある」
Hauke Laging

@HaukeLaging SIGCONTは、SIGSTOPを元に戻すシグナルです。ドキュメントには特別なものとしてリストされていません...したがって、技術的にプロセスが無視するように設定できるかどうかはわかりませんが、それを再開することはできません(SIGKILLのみ)。
デロバート

22

シグナルの実装は非常に複雑で、カーネル固有です。つまり、異なるカーネルは異なる方法でシグナルを実装します。簡単な説明は次のとおりです。

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

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


15

この質問には答えましたが、Linuxカーネルのイベントの詳細なフローを投稿させてください。
これは、Linux投稿から完全にコピーさ れます。LinuxSignals-「Linux投稿」ブログのsklinuxblog.blogspot.inの内部。

シグナルユーザースペース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_signalfunctionに配置すると、次の呼び出しトレースが表示されます。

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;

SYSTEM呼び出しとシグナル

読み取り/書き込みのブロック、プロセスを待機状態にするTASK_INTERRUPTIBLEなどの「遅い」システムコール: またはTASK_UNINTERRUPTIBLE

状態のタスクは、シグナルによって状態にTASK_INTERRUPTIBLE変更されTASK_RUNNINGます。TASK_RUNNINGプロセスをスケジュールできることを意味します。

実行された場合、そのシグナルハンドラは「遅い」システムコールの完了前に実行されます。syscallデフォルトでは完了しません。

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

参照資料


サイトに貢献する努力をしてくれてありがとう。(1)他のサイトから素材をコピーする場合(単語ごと、文字ごと、文法や句読点の誤りを含む)、あなたはやっていると言ってくださいだから、はるかに明確に。ソースを「参照」としてリストすることは、必要ではありますが、十分ではありません。(?K_K = SK) -あなたがしなければ、あなたは、しかし、あなたはブログの作者でない限り、その場合、あなたはそれにリンクする必要はありませんしなければならない(すなわち、言う)それはあなたのものであることを開示しています。…(続き)
G-Manが「Reinstate Monica」と言う

(続き)…(2)ソース(コピー元のブログ)はあまり良くありません。質問が出されてから4年が経ちました。コピー元のより良いリファレンスが見つかりませんでしたか?(元の作者の方はごめんなさい。)前述の文法と句読点のエラー(および一般的に、ずさんな言い回し、不適切な書式設定)に加えて、それは間違っています。(2a)Ctrl + Zは、SIGSTOPではなくSIGTSTPを送信します。(SIGTERMのようなSIGTSTPはキャッチできますが、SIGKILLのようなSIGSTOPはキャッチできません。)…(続き)
G-Manは「Reinstate Monica」と言います

(続き)…(2b)シェルはCtrl + Cシグナルを送信しません。シェルはシグナルを送信する役割を持ちません(ユーザーがkillシェル組み込みコマンドであるコマンドを使用する場合を除く)。(2c)}関数を閉じた後のセミコロンは、厳密に言えばエラーではありませんが、不要であり非常に非正統的です。(3)すべてが正しかったとしても、それは質問に対する非常に良い答えではないでしょう。(3a)質問はやや不明瞭ですが、アクター(ユーザーおよびプロセス)シグナルを開始送信)する方法に焦点を当てているようです。…(続き)
G-Manが「Reinstate Monica」と言う

(続き)…答えは、カーネルによって生成されたシグナル(特に、キーボードで生成されたシグナル)と、受信者プロセスがシグナルにどのように反応するかに焦点を当てているようです。(3b)質問は「誰かが私のプロセスを殺した-誰が、どのようにしたのか」というレベルにあるようです。答えは、シグナル処理API、カーネルルーチン、カーネルデバッグ(Jprobe?)、カーネルスタックトレース、カーネルデータ構造。IMO、これは不適切な低レベルです-特に、読者がこれらの内部の仕組みについてさらに学ぶことができる参考資料を提供していないためです。
G-Manが「Reinstate Monica」と言う

1
それは私自身のブログです..私自身の痕跡..それは私が望むものです。誰もがこのような詳細なフローを知っているはずです..チャンネル..これはカーネル内部の回答であり、文法内部ではありません。
K_K
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.