Var*_*ani 37 kill signals architecture
在一般情况下,终止进程,我们产生这样的信号SIGKILL,SIGTSTP等等。
但是如何知道谁订购了该特定信号,谁将其发送到特定进程,以及信号通常如何执行其操作?信号在内部如何工作?
der*_*ert 40
50,000 英尺的视图是:
信号是由内核内部生成的(例如,SIGSEGV当访问无效地址SIGQUIT时,或者当您点击Ctrl+ 时\),或者由使用kill系统调用(或几个相关的调用)的程序生成。
如果是通过系统调用之一,则内核确认调用进程具有足够的权限来发送信号。如果不是,则返回错误(并且信号不会发生)。
如果它是两个特殊信号之一,则内核无条件地对其进行操作,而无需来自目标进程的任何输入。两个特殊信号是 SIGKILL 和 SIGSTOP。下面所有关于默认操作、阻塞信号等的内容与这两者无关。
接下来,内核计算出如何处理信号:
对于每个进程,都有一个与每个信号相关联的动作。有很多默认值,程序可以使用sigaction、signal等设置不同的默认值。这些包括“完全忽略它”、“终止进程”、“使用核心转储终止进程”、“停止进程”、等等。
程序还可以逐个信号地关闭信号的传递(“阻塞”)。然后信号保持挂起直到解除阻塞。
程序可以请求内核以同步(使用sigwait、等或signalfd)或异步(通过中断进程正在执行的任何操作,并调用指定函数)的方式将信号传递给进程,而不是内核本身采取某些操作。
还有第二组信号称为“实时信号”,它没有特定的含义,也允许多个信号排队(当信号被阻塞时,普通信号只排队一个)。这些用于多线程程序中,以便线程相互通信。例如,有几个用于 glibc 的 POSIX 线程实现。它们还可用于不同进程之间的通信(例如,您可以使用多个实时信号让 fooctl 程序向 foo 守护进程发送消息)。
对于非 50,000 英尺的视图,请尝试查看man 7 signal内核内部文档(或源代码)。
Tyl*_*den 30
信号实现非常复杂并且是特定于内核的。换句话说,不同的内核会以不同的方式实现信号。简单的解释如下:
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.
通过适当地检查挂起的信号值,内核可以确定进程是否正在处理所有信号,如果不是,将采取适当的行动,这可能是让进程休眠或杀死它或其他动作,具体取决于信号。
K_K*_*K_K 20
虽然已经回答了这个问题,但让我发布一个详细的 Linux 内核事件流程。
这完全是从Linux 帖子中复制的:Linux 信号 -
在 sklinuxblog.blogspot.com 上的“Linux 帖子”博客中的内部。
让我们从编写一个简单的信号用户空间 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;
};
Run Code Online (Sandbox Code Playgroud)
此代码为 SIGINT 信号分配一个新的处理程序。可以使用Ctrl+C组合键将SIGINT 发送到正在运行的进程。当按下Ctrl+ 时C,异步信号 SIGINT 被发送到任务。也相当于kill -INT <pid>在其他终端发送命令。
如果您执行 a 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
Run Code Online (Sandbox Code Playgroud)
还可以使用以下组合键发送特定信号:
如果您编译并运行上述 C 程序,您将获得以下输出:
[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop
Run Code Online (Sandbox Code Playgroud)
即使使用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
Run Code Online (Sandbox Code Playgroud)
所以发送信号的主要函数调用如下:
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.
Run Code Online (Sandbox Code Playgroud)
现在一切都已设置完毕task_struct,并对流程进行了必要的更改。
当进程从系统调用返回或从中断返回完成时,该信号由进程检查/处理。系统调用的返回存在于文件中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;
Run Code Online (Sandbox Code Playgroud)
“慢”系统调用,例如阻塞读/写,将进程置于等待状态:
TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE。
状态中的任务TASK_INTERRUPTIBLE将更改为TASK_RUNNING通过信号状态。TASK_RUNNING意味着可以安排一个进程。
如果执行,它的信号处理程序将在“慢”系统调用完成之前运行。这syscall默认情况下不会完成。
如果 SA_RESTART设置了标志,syscall则在信号处理程序完成后重新启动。
| 归档时间: |
|
| 查看次数: |
24776 次 |
| 最近记录: |