一般而言,要终止进程,我们会生成SIGKILL
,SIGTSTP
等信号。
但是,如何知道是谁订购了该特定信号,是谁将其发送到特定进程的,以及通常来说信号如何执行其操作呢?信号在内部如何工作?
一般而言,要终止进程,我们会生成SIGKILL
,SIGTSTP
等信号。
但是,如何知道是谁订购了该特定信号,是谁将其发送到特定进程的,以及通常来说信号如何执行其操作呢?信号在内部如何工作?
Answers:
50,000英尺的视图是:
信号是由内核内部产生的(例如,SIGSEGV
当访问无效地址SIGQUIT
时,或者当您按下Ctrl+时\),或者由使用kill
syscall 的程序(或几个相关的信号)产生。
如果是通过系统调用之一,则内核确认调用进程具有足够的特权来发送信号。如果不是,则返回错误(并且不会发生信号)。
如果它是两个特殊信号之一,则内核将无条件地对其进行操作,而无需来自目标进程的任何输入。这两个特殊信号是SIGKILL和SIGSTOP。下面关于默认操作,阻止信号等的所有内容与这两个无关。
接下来,内核找出信号的作用:
对于每个过程,都有一个与每个信号关联的动作。有很多默认值,程序可以使用sigaction
,signal
等等设置不同的默认值,包括“完全忽略它”,“杀死进程”,“使用核心转储杀死进程”,“停止进程”,等等
程序还可以在逐个信号的基础上关闭信号的传递(“阻塞”)。然后,信号将保持待处理状态,直到解除阻塞为止。
程序可以请求它而不是内核本身采取某些措施,而是以同步方式(通过sigwait
,等..或signalfd
)或异步方式(通过中断进程在执行的操作并调用指定的函数)将信号传递给进程。
还有第二组信号,称为“实时信号”,没有特定含义,并且还允许将多个信号排队(当信号被阻塞时,正常信号仅对每个信号排队)。这些用于多线程程序中,以便线程彼此通信。例如,在glibc的POSIX线程实现中使用了几种。它们还可以用于在不同进程之间进行通信(例如,您可以使用多个实时信号让fooctl程序将消息发送到foo守护程序)。
对于非50,000的外观,请尝试man 7 signal
并查看内核内部文档(或源代码)。
信号实现非常复杂,并且特定于内核。换句话说,不同的内核将以不同的方式实现信号。简化的解释如下:
CPU基于特殊的寄存器值,在内存中有一个地址,希望在该地址中找到实际上是向量表的“中断描述符表”。每个可能的异常都有一个向量,例如被零除或陷阱,例如INT 3(调试)。当CPU遇到异常时,它将标志和当前指令指针保存在堆栈中,然后跳转到相关向量指定的地址。在Linux中,此向量始终指向内核,该内核中有一个异常处理程序。现在,CPU完成了,Linux内核接管了。
请注意,您还可以从软件触发异常。例如,用户按CTRL- C,然后此调用转到内核,该内核调用其自己的异常处理程序。通常,有多种方法可以使用该处理程序,但是无论发生什么相同的基本事情:上下文都保存在堆栈中,内核的异常处理程序会跳转到该状态。
然后,异常处理程序决定哪个线程应该接收信号。如果发生除零之类的事情,那很容易:导致异常的线程获取信号,但是对于其他类型的信号,决策可能非常复杂,在某些不寻常的情况下,或多或少的随机线程可能得到信号。
要发送信号,内核首先要设置一个值,该值指示信号的类型SIGHUP
或其他类型。这只是一个整数。每个进程都有一个“待处理信号”存储区,该值存储在该存储区中。然后,内核使用信号信息创建一个数据结构。该结构包括信号“处置”,其可以是默认,忽略或处理的。然后,内核调用其自己的函数do_signal()
。下一阶段开始。
do_signal()
首先决定是否它会处理的信号。例如,如果它是kill,那么do_signal()
就杀死进程,直到故事结束。否则,它会查看处置。如果处置是默认设置,则do_signal()
根据依赖于信号的默认策略处理信号。如果处理是处理,则意味着用户程序中有一个功能旨在处理所讨论的信号,并且指向该功能的指针将位于上述数据结构中。在这种情况下,do_signal()调用另一个内核函数,handle_signal()
,然后经历切换回用户模式并调用此函数的过程。该切换的细节非常复杂。当您使用中的功能时,程序中的此代码通常会自动链接到您的程序中signal.h
。
通过适当地检查未决信号值,内核可以确定该进程是否正在处理所有信号,如果不是,则将采取适当的措施,这可能会使该进程进入休眠状态或终止该进程或其他行动,具体取决于信号。
尽管已经回答了这个问题,但让我在Linux内核中发布详细的事件流。
这完全从Linux帖子中复制而来:Linux
Signals-sklinuxblog.blogspot.in上“ Linux posts”博客中的Internals。
让我们从编写一个简单的信号用户空间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
(这是一个小写字母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>
该过程也不会终止。相反,它将执行信号处理程序并返回。
如果我们看到发送到进程的信号的内部并将Jprobe和dump_stack放在__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
,并对过程进行了必要的更改。
当信号从系统调用返回或从中断返回时,该信号将由进程检查/处理。系统调用的返回结果存在于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)这个问题虽然还不清楚,但似乎集中在参与者(用户和过程)如何发起(即发送)信号上。…(续)