linux 信号 使用与实现

参考

  1. Linux中的信号处理机制 [一]
  2. Linux中的信号处理机制 [二]
  3. Linux中的信号处理机制 三 信号的接收、阻塞、屏蔽
  4. Linux中的信号处理机制 [四]

信号接口

旧版接口 signal
signal的简单示例signal简单说明
有三种预制的回调函数,SIG_DFLSIG_IGNSIG_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函数给出了实践样例。

  1. 这里有两种回调可以设置,分别是sa_handler和sa_sigaction(为sa_flags设置SA_SIGINFO后即可触发该函数,而不触发sa_handler),
  2. sa_mask指定了回调时要阻塞的信号
    • 类型是bitmap形式的sigset_t类型,设置方式见下文。
    • 为什么要阻塞呢?因为在回调函数执行到一半时,有可能触发了其它信号的回调,导致函数的行为会改变。被阻塞的信号不会消失。在当前回调结束后,会撤销阻塞,触发回调。
  3. 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_struct中这些成员变量都有据可查(Task_Structure即task_struct)。

其中红框部分,存储了关于该进程的信号,分两部分,用于存储可靠信号的链表队列list,和用于存储不可靠信号的signal:

信号存储
pending的详细结构
signal.h sigpending

struct sigpending {
	struct list_head list;
	sigset_t signal;
};

types.h list_head

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, &current->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里了吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值