欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux (八)进程信号(信号产生,阻塞信号,捕捉信号)

程序员文章站 2022-07-12 10:30:52
...

信号的基本概念

为了理解信号,举一个我们熟悉的例子
用户输入命令,在shell下启动一个前台进程
用户按下CTRL-C,这个键盘输入产生一个硬件终端
如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断
终端驱动程序将CTRL-C解释成一个SIGINT信号,记在该进程的PCB中,当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理。而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行

其中Ctrl-C产生的信号只能发给前台进程
shell可以同时运行一个前台进程和任意多个后台进程
信号相对于进程的控制流程来说是异步的
用Kill-l就可以查看系统定义的信号列表

产生信号的三种方式
1:通过终端按键产生信号
SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump,ulimit 命令改变了shell进程的Resource Limit,当Core Dump的时候我们就可以使用core文件了
我们用gdb-file core.core文件名,就可以直接调试出错误出现在哪里

2:调用系统函数向进程发信号
kill命令是调用kill函数实现的,kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前程序发送指定的信号

#include<signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
这两个函数都是成功返回0,错误返回-1
#include<stdlib.h>
void abort(void);
就像exit函数一样,abort函数总是会成功,所以没有返回值

3:由软件条件产生信号
SIGPIPE是一种由软件条件产生的信号,下面介绍alarm函数

#include<unistd.h>
unsigned int alarm(unsigned int seconds);

调用alarm函数可以设定一个闹钟,就是告诉内核seconds秒后给当前进程发一个SIGALRM信号,该信号的默认处理动作是终止当前进程

信号处理常见方式概览
忽略此信号;
执行该信号的默认动作;
提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号

阻塞信号

1:信号其他相关常见概念
实际执行信号的处理动作称为信号抵达(Delivery)
信号从产生到抵达之间的状态称为信号的未决(pending)
进程可以选择阻塞(block)某个信号
被阻塞的信号产生时保持在未决状态,直到进程接触对次信号的阻塞。才执行抵达的动作
注意,阻塞和忽略是不同的,知道信号被阻塞就不会抵达,而忽略是在抵达之后,可选的一种处理方式

信号在内核中表示示意图
Linux (八)进程信号(信号产生,阻塞信号,捕捉信号)
每个信号都有两个标志位,分别表示阻塞和未决,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,知道信号抵达才清楚该标志。在上面的图中,SIGHUP信号未阻塞也未产生过。当它抵达时执行默认处理动作。SIGINT信号产生过,但是正在被阻塞,所以暂时不能抵达,虽然它的处理动作是忽略,但是在没有解除阻塞之前是不能忽略这个信号,因为进程是有机会改变处理动作再解除阻塞的。SIGUIT信号未产生过,一旦产生这个SIGUIT信号将被阻塞,它的处理动作是用户自定义函数Sighandler。

信号集概念
信号的阻塞和未决标志都只用一个bit位来表示,即非零即一,所以我们可以用相同的数据类型来存储,它就叫做sigset_t信号集。在阻塞信号集中有效和无效表示该信号是否被阻塞。在未决信号集中有效和无效表示该信号是否处于未决状态
在使用sigset_t类型变量之前,首先要初始化,使信号集状态处于确定状态。初始化后就可以相关函数在该信号集中添加或这删除某种有效信号
sigprocmask
调用函数sigprocmask可以读取或更改进程的信号屏蔽字

#include<signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_ *oset);
返回值:成功返回0;失败返回-1

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出,如果set是非空指针,则更改进程的信号屏蔽字,参数how只是如何更改。如果oset和set都不是空指针,则先将原来的信号屏蔽字被分到oset里,然后根据set和how参数更改信号屏蔽字。

信号的捕捉

1。内核如何实现信号捕捉
Linux (八)进程信号(信号产生,阻塞信号,捕捉信号)

从图中可以看出来程序共在内核与用户之间切了四次

2。sigaction

#include<signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
sigaction 函数可以读取和修改与指定信号相关联的处理动作。调用成功返回0;失败返回-1

signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体

当某个信号的处理函数被调用时。内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动回复为原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这个信号再次产生,那么它就会被阻塞到当前处理结束为止。如果在调用信号处理函数时,除了当前信号被自动屏蔽外,还希望自动屏蔽另一些信号,就用sammask字段说明这些需要和外屏蔽的信号,当信号处理函数返回时自动回复原来的信号屏蔽字

3。pause

#include<unistd.h>
int pause(void);

pause 函数使调用进程挂起直到有信号抵达,如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;如果信号处理动作是捕捉,则调用信号处理函数之后pause返回-1,errno设置为EINTR所以pause只有出错的返回值(以前说过的替换函数exec)