信号在内部如何工作?


31

一般而言,要终止进程,我们会生成SIGKILLSIGTSTP等信号。

但是,如何知道是谁订购了该特定信号,是谁将其发送到特定进程的,以及通常来说信号如何执行其操作呢?信号在内部如何工作?


这个问题有点难以理解。我很抱歉,并不表示不尊重。您是否想知道谁可能运行了杀死某个进程的命令,还是想进一步了解SIGKILL和SIGSTP?
pullsumo

@mistermister我想知道谁可能执行了杀死进程的命令,以及如何执行?
Varun Chhangani

Answers:


35

50,000英尺的视图是:

  1. 信号是由内核内部产生的(例如,SIGSEGV当访问无效地址SIGQUIT时,或者当您按下Ctrl+时\),或者由使用killsyscall 的程序(或几个相关的信号)产生。

  2. 如果是通过系统调用之一,则内核确认调用进程具有足够的特权来发送信号。如果不是,则返回错误(并且不会发生信号)。

  3. 如果它是两个特殊信号之一,则内核将无条件地对其进行操作,而无需来自目标进程的任何输入。这两个特殊信号是SIGKILL和SIGSTOP。下面关于默认操作,阻止信号等的所有内容与这两个无关。

  4. 接下来,内核找出信号的作用:

    1. 对于每个过程,都有一个与每个信号关联的动作。有很多默认值,程序可以使用sigactionsignal等等设置不同的默认值,包括“完全忽略它”,“杀死进程”,“使用核心转储杀死进程”,“停止进程”,等等

    2. 程序还可以在逐个信号的基础上关闭信号的传递(“阻塞”)。然后,信号将保持待处理状态,直到解除阻塞为止。

    3. 程序可以请求它而不是内核本身采取某些措施,而是以同步方式(通过sigwait,等..或signalfd)或异步方式(通过中断进程在执行的操作并调用指定的函数)将信号传递给进程。

还有第二组信号,称为“实时信号”,没有特定含义,并且还允许将多个信号排队(当信号被阻塞时,正常信号仅对每个信号排队)。这些用于多线程程序中,以便线程彼此通信。例如,在glibc的POSIX线程实现中使用了几种。它们还可以用于在不同进程之间进行通信(例如,您可以使用多个实时信号让fooctl程序将消息发送到foo守护程序)。

对于非50,000的外观,请尝试man 7 signal并查看内核内部文档(或源代码)。


“两个特殊信号是SIGKILL和SIGSTOP”,所以SIGCONT可能是……
Hauke Laging 2013年

@HaukeLaging SIGCONT是撤消SIGSTOP的信号。该文档没有将其列出为特殊项目...因此,我不确定在技术上是否可以将其设置为忽略,否则您将无法继续(仅对其进行SIGKILL)。
derobert

22

信号实现非常复杂,并且特定于内核。换句话说,不同的内核将以不同的方式实现信号。简化的解释如下:

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

通过适当地检查未决信号值,内核可以确定该进程是否正在处理所有信号,如果不是,则将采取适当的措施,这可能会使该进程进入休眠状态或终止该进程或其他行动,具体取决于信号。


15

尽管已经回答了这个问题,但让我在Linux内核中发布详细的事件流。
这完全从Linux帖子中复制而来:Linux Signals-sklinuxblog.blogspot.in上“ Linux posts”博客中的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信号分配了一个新的处理程序。可以使用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

以下按键组合也可以用于发送特定信号:

  • 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+ Ckill -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_INTERRUPTIBLETASK_UNINTERRUPTIBLE

状态中的任务TASK_INTERRUPTIBLETASK_RUNNING通过信号更改为状态。TASK_RUNNING表示可以安排流程。

如果执行,则其信号处理程序将在“慢速”系统调用完成之前运行。在syscall默认情况下不会完成。

如果SA_RESTART设置了标志,syscall则在信号处理程序完成后重新启动。

参考文献


感谢您为网站做出的努力,但(1)如果您要从其他网站复制材料(逐字逐字母,逐字母逐字母,包括语法和标点错误),则应该说您正在做因此,更加清楚。仅在必要时将来源列为“参考”是不够的。除非您是博客的作者(K_K = sk?),否则无需链接到该博客—但是,如果您这样做,则必须披露(即说)它是您的博客。…(续)
G-Man说“恢复莫妮卡”

(续)…(2)您的来源(您复制的博客)不是很好。提出问题已经四年了;您找不到更好的复制参考吗?(如果您是原始作者,对不起。)除了前面提到的语法和标点错误(通常是措辞松散,格式不正确)之外,这也是错误的。(2a)Ctrl + Z发送SIGTSTP,而不是SIGSTOP。(可以像SIGTERM一样捕获SIGTSTP;不能像SIGKILL一样捕获SIGSTOP。)…(续)
G-Man说'Reinstate Monica''Jun

(续)…(2b)外壳不发送Ctrl + C信号。该外壳程序在发送信号方面没有任何作用(除非用户使用kill命令,它是内置的外壳程序)。(2c)虽然}严格来说,函数关闭后的分号不是错误,但它们是不必要的,而且是非正统的。(3)即使一切正确,对这个问题也不是一个很好的答案。(3a)这个问题虽然还不清楚,但似乎集中在参与者(用户和过程)如何发起(即发送)信号上。…(续)
G-Man说“恢复莫妮卡”

(续)答案似乎集中在内核产生的信号(特别是键盘产生的信号)以及接收者进程对信号的反应上。(3b)问题似乎是在“有人杀死了我的进程,是谁干的,以及如何做的”这一级别。答案讨论了信号处理API,内核例程,内核调试(Jprobe?),内核堆栈跟踪以及内核数据结构。IMO,这是一个不适当的低级水平,尤其是因为它没有提供任何参考,读者可以从中进一步了解这些内部工作原理。
G-Man说“恢复莫妮卡”

1
这是我自己的博客..我自己的踪迹..多数民众赞成在我想要的..每个人都应该知道这样详细的流程..在空中交谈是没有意义的..即使违反了该社区准则,也请通过适当的方式删除我的答案通道..这是内核内部答案,而不是语法内部。
K_K
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.