参考
信号接口
旧版接口 signal
signal的简单示例、signal简单说明
有三种预制的回调函数,SIG_DFL
、SIG_IGN
和SIG_ERR
,分别代表默认行为、忽略信号、失败。
#define SIG_DFL ((void (*) (int)) 0)
#define SIG_IGN ((void (*) (int)) 1)
#define SIG_ERR ((void (*) (int)) -1)
设置为
SIG_IGN
时,依然会接受信号,只是不作任何处理。这与阻塞、屏蔽信号是不同的。
// linux的信号signal函数:
#include <signal.h>
typedef void (*sighandler_t)(int); // 回调函数类型
sighandler_t signal(int signum, sighandler_t handler); // signal函数,用于注册回调
新接口 sigaction
signal是旧版的接口,功能简单,只能设置一个接收信号值的回调函数,而无法传递更多信息,或设置更多行为,因此有了新接口 sigaction。根据IBM文档指出,接口如下,用于设置/查询信号的回调:
#define _POSIX_SOURCE
#include <signal.h>
int sigaction(int sig, const struct sigaction *__restrict__ new,
struct sigaction *__restrict__ old);
其中sig是信号值, new中包含了要设置的回调,old是原有的回调设置。因此sigaction兼具查询和设置的功能,如果只设置不查询,将old参数留空即可,如果只查询不设置,则将new留空即可。
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int, siginfo_t *, void *);
};
信号学习之sigaction函数给出了实践样例。
- 这里有两种回调可以设置,分别是sa_handler和sa_sigaction(为sa_flags设置SA_SIGINFO后即可触发该函数,而不触发sa_handler),
- sa_mask指定了回调时要阻塞的信号。
- 类型是bitmap形式的sigset_t类型,设置方式见下文。
- 为什么要阻塞呢?因为在回调函数执行到一半时,有可能触发了其它信号的回调,导致函数的行为会改变。被阻塞的信号不会消失。在当前回调结束后,会撤销阻塞,触发回调。
- sa_flags可以作一些设置。如果要激活sa_sigaction,就传SA_SIGINFO。
由1、2可知,要么使用回调时保持可重入性,要么就设置回调期间阻塞某些可再次触发信号。
sigset_t
底层原理是bitmap。
sigemptyset(), sigfillset(), sigaddset(), or sigdelset()、sigismember(),可用来查询/设置sigset_t的信号。
sa_mask也通过这些函数来设置。
信号实现原理
Linux中的信号处理机制 [二]的图描述了task_struct中与信号相关的变量:
task_struct中这些成员变量都有据可查(Task_Structure即task_struct)。
其中红框部分,存储了关于该进程的信号,分两部分,用于存储可靠信号的链表队列list,和用于存储不可靠信号的signal:
pending的详细结构
signal.h sigpending
struct sigpending {
struct list_head list;
sigset_t signal;
};
struct list_head {
struct list_head *next, *prev;
};
可靠信号与不可靠信号
Linux信号(signal)机制
对于signal信号,绝大部分的默认处理都是终止进程或停止进程,或dump内核映像转储。 上述的31的信号为非实时信号,其他的信号32-64都是实时信号。
信号的传递
当pending的信号不止一个的时候,内核会调用do_signal()–>get_signal()循环地进行信号递送,其中由异常产生的"synchronous"信号将会被优先递送。
bool get_signal(struct ksignal *ksig)
{
signr = dequeue_synchronous_signal(&ksig->info);
if (!signr)
signr = dequeue_signal(current, ¤t->blocked, &ksig->info);
...
}
对于同为"synchronous"或者同为非"synchronous"的pending信号,按照信号值在bitmap中从小到大的顺序,依次递送给目标进程执行。
- 如果被进程响应的信号是不可靠信号,内核将把这个信号从进程对应的bitmap中移除;
- 如果是可靠信号,内核还会把该信号从进程对应的pending队列中移除。
内核将信号递送给进程后,进程会根据需要做出不同的接收处理,分别是默认操作/执行回调/忽略信号/屏蔽信号。在Linux中的信号处理机制 [三]已经说明。
信号屏蔽
关于sigprocmask屏蔽信号。一个信号是否能触发回调,取决于它是否在传递且没被屏蔽:
忽略、阻塞与屏蔽
- 忽略信号是指注册了预制回调的SIG_IGN,信号会被接收,但什么行为也没
- 屏蔽信号。task_struct中的blocked成员存储了会被屏蔽的信号。
- 阻塞信号。指sigaction中sig_mask成员,只在回调处理中阻塞信号触发。存储于task_struct的sighand成员中。
阻塞的实现原理
如Linux中的信号处理机制 [四]中所说,进程的sighand成员为每一种信号存储了回调设置。而后者的sa_mask成员存储了这类回调要阻塞的信号。
同一进程的不同线程的task_struct的"sighand"将指向同一个包含信号处理函数列表的sighand_struct。
…
但是,每个线程可以有单独的pending位图/队列和block位图。如果一个信号是发送给线程的,那么内核在递送该信号时,会将它放入线程私有的pending位图/队列中,之后根据目标线程的block位图的设置,直接由目标线程处理就可以了。
如果信号是发送给一个进程的,那么该信号在递送时将被内核放入进程的pending位图/队列中,由进程内的所有线程共享。接下来,内核会从进程的各个线程中,挑选一个block位图中没有屏蔽该信号的线程,来执行对应的信号处理函数,其中,进程的主线程将被内核优先选择。
进程共享的信号应该是发在task_struct->signal->shared_pending里了吧。