信号概念
信号其实就是一个软件中断,操作系统通过信号告诉进程发生了某个事件,从而打断进程当前的动作,去处理这个事件。
linux下信号的产生有两种方式:
- 硬件:ctrl+c/z/|
- 软件:kill/raise(int signum)/abort()/alarm(int seconds)…
Linux下命令的分类
其中1-31是非可靠信号(普通信号) 34-64信号是可靠信号(实时信号)。62种(kill -l)
1-31号从unix借鉴而来,有具体对应的系统事件, 34-64后期扩充。
没有32 33号信号。
信号的"工作方式"
首先,信号有自己的生命周期。
产生 >注册 >注销 >处理就可以称为一个信号的生命周期。
产生
操作系统通过硬件或者软件方式产生信号,本质都是62种信号中的一个。
注册
信号在进程中注册,pcb中通过一个结构体struct sigpending, 结构体中有一个结构体叫做struct sigset_t 结构体中有一个数组,数组实现了一个位图,这个位图从1-64 一一对应了一个信号(0,32,33没有),我们将这个位图就叫做pending位图,这个位图就是未决信号集合,也就是收到了信号但是没有处理的集合。
每当操作系统向进程发送了一个信号,因为每个信号对应一个位图的位,就会将对应的位置为1,表示收到了这个信号,但是这样不能表示收到了几个这样相同的信号,于是操作系统还有一个sigqueue链表,每次产生一个信号,操作系统就会新建一个sigqueue节点放入这个链表中,这样通过位图判断有没有该信号,通过链表判断到底有几个相同信号,就可以做到信号的注册了。
而不可靠信号就是当位图已经为一的时候,并不会添加节点进sigqueue,可靠信号不管位图是不是1都会在sigqueue中添加节点。
注销
操作系统为了防止一个信号被处理多次,进行先注销后处理,具体操作是:
注销操作:
- 非可靠信号:位图置为0,删除节点。
- 可靠信号:删除节点,并判断还有没有相同的节点,没有就将位图置为0.
处理。
信号处理有三种方式:
- 默认处理:操作系统自己定义的处理方式
- 忽略处理:不处理
- 自定义处理(捕捉信号):用户自己定义的处理方式。
pending位图对应着一个函数指针数组–handler,它们一一对应,这个数组存的就是操作系统系统的信号处理函数的函数指针,用户可以通过替换函数指针达到自定义处理的目的。
操作系统进行了一个函数指针的类型重命名。
typedef void(*sighandler_t)(int)
用户可以通过这个函数进行自定义处理。
sighandler_t signal(int signum, sighandler_t handler)
- 参数一:是操作系统信号种类。
- 参数二:有三个选项
SIG_DFL默认处理
SIG_IGN忽略
用户定义的信号处理函数的函数指针。
当用户自定义时,每当操作系统发送该信号时,这个函数就会调用用户自己定义的函数进行处理。
信号被默认处理的时候是发生在内核,但是一旦用户自定义处理的时候,就会发生用户态和内核态的切换。 信号回调执行完毕后返回内核直到没有信号可以处理才会重新返回用户态。
代码操作:
代码执行情况,当我们键盘输入ctrl+c 时就会执行我们定义的函数。
信号阻塞
在某些特殊情况下需要用到信号阻塞,信号阻塞是指将被阻塞的信号暂时不处理。
信号阻塞的原理:
信号阻塞的原理其实是在pending和handler中间加了一个block位图,当某个信号被阻塞时,就将block位图中对应的位置为1,这样对应的信号就暂时不处理,直到将block这个位置为0,才去处理,在这个期间,pending仍然正常接收信号并且在sigqueue中创建节点。
接口说明:
int sigprocmask(int how, sigset_t *set, sigset_t *old);
how:
- SIG_BLOCK:将set中的信息添加到block中,old保存原来block信息,old可为NULL。
- SIG_UNBLOCK:在bolck中,将与set相同的信息移除。
- SIG_SETMASK:将block中信息置换为set中的信息。
关于set如何设置的一些接口。
int sigemptyset(sigset_t *set);//初始化时清空set这个空间
int sigaddset(sigset_t *set, int signum);//向set中添加信号
int sigfillset(sigset_t *set);//将所有信号添加进去
int sigdelset(sigset_t *set, int signum);//移除指定信号
int sigismember(const sigset_t * set, int signum);//判断该信号是否在集合中
注意:
SIGKILL 九号信号 和SIGSTOP 十九号信号, 不可被阻塞不可被忽略,不可被重定义。
代码示例:
执行结果:
阻塞情况下,我们无法使用ctrl+c,直到解除阻塞。
信号具体使用示例
以一个等待子进程退出的例子来说明信号的是如何在具体场景下进行应用的。
我们可以通过waitpid来等待一个子进程退出,但是我们也可以通过信号来操作,具体操作就是我们将捕捉子进程退出的信号,然后调用的自定义的处理方式–waitpid, 这样就可以实现,有子进程退出,我们就可以捕捉到退出信号,然后等待子进程。