进程信号

进程信号

信号是进程间通信的一种方式,本质是一个软中断,用来告诉进程发生了某些事在进程中我们可以通过发送信号来通知另一个进程,信号的生命周期是:信号的产生->信号的注册->信号的注销->信号的处理。当一个进程接收到信号的时候停止在做的事情处理信号,处理完毕后继续执行之前的事,在linux下我们用kill -l来查看一共有多少信号
这里写图片描述
我们可以看到很明显一号到三十一号是一类,三十四到六十四是一类,虽然有六十四个编号可是因为缺少三十二和三十三所以一共六十二个信号,第一类信号继承于unix,叫做非可靠信号(非实时信号),代表这个信号可能会丢失,当已经有相同的信后注册,那么这个信号有可能会消失。第二类信号是可靠信号,信号不会丢失。

在这里我们要引出中断的概念,中断是暂停目前正在进行的操作,去执行打断它的事件,当处理完毕后再处理原来的事件,中断分为硬中断和软中断,硬中断就是ctrl+z或者ctrl+\退出或者暂停前台进程,软中断就是类似于linux下发送一个信号之类。

信号的产生方式:
1.通过键盘的组合键产生,比如ctrl+c产生SIGINT信号,ctrl+\产生SIGQUIT信号,ctrl+z产生SIGTSTP信号
2. 硬件异常,这些条件由硬件检测并通知内核,然后内核向进程发送适当的信号,比如执行了除以零的指令,进程访问了非法内存地址,cpu的运算单元都会产生异常,内核将这个异常解释成一个个信号发送给进程
3. 个进程调用kill(2)函数可以发送信号给另一个进程。可以用kill(1)发送信号给某一个进程kill(1)也是用kill(2)实现的如果不清楚指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程,当内核检测到软件条件发生时也可以通过信号通知进程例如闹钟超时,会产生SIGALRM信号,向读端已经关闭的管道文件写数据时产生SIGPIPE信号,如果不想按照默认动作处理信号,用户可以调用sigaction(2)函数告诉内核如何处理某种信号
4.软件条件产生比如有 raise函数 kill函数 alarm定时器函数等

这里介绍几个产生信号的函数
int kill(pid_t pid, int sig);
pid是接收信号的进程PId,SIG是要接受什么信号
int raise(int sig)
给调用这个函数的进程发送sig信号

这里要特别注意,程序异常的时候,会记录一个核心转储文件,在这个文件中记录的程序的运行数据,当一个程序异常崩溃的时候,而这个错误只有偶尔发生,那么这种错误将非常难定位,因为我们也不知道什么时候崩溃,因此这个转储文件就很重要,他可以帮助我们使用gdb调试查看,定位错误,gdb运行可执行程序,然后在gdb中使用命令:core-file core.3996来加载程序运行数据,然后可以定位错误
但是程序核心转储功能默认关闭,转储大小默认0,因为运行数据中可能会有安全性信息,以及文件增多可能会占用资源

处理信号的方式:
1.忽略这个信号
2.按照默认处理方式处理
3.提供一个信号处理函数,要求内核在处理信号的时候切换到用户态处理
##阻塞信号
实际信号的处理动作叫做信号的递达,信号产生到递达之间的状态,叫做信号未决,进程可以选择阻塞哪个信号,被阻塞的信号产生时将保持在未决状态直到接触阻塞
信号的阻塞与忽略不是一个概念,信号只要被阻塞就不会递达,而信号的忽略是在信号递达之后选择的一种处理方式,当信号接触阻塞后依旧可以对信号做出处理。
在PCB中有两个sigset_t结构体,我们可以理解为位图,这两个位图分别是阻塞信号集(block),未决信号集(pending)。这两个信号集中有对应每一个信号的位图,不存在就是0,存在就是1,例如给一个进程发送信号,当在block对应的位置是1的时候代表被阻塞,当变成0代表不被阻塞,pending位图对应位置为0代表没有接收到这个信号,反之代表接收到了。

信号的注销:

从pending集合中将要处理的信号移除掉,但是这个移除分情况,分可靠信号还是非可靠信号
非可靠信号:非可靠信号注册的时候,是给signqueue链表添加一个信号节点,并且将pending对应位图置1,当这个信号注册的时候,如果位图已经置1.代表位图注册过了,因此不做任何操作,不会添加新节点。
注销的时候删除链表中节点并且将对应位图置0
可靠信号:对于可靠信号注册来说,给sigqueue链表添加一个节点,不管可靠信号是否已经注册都会添加一个新节点,如果没注册就添加新节点的同时位图置1
注销:删除一个节点,然后查看链表中还有没有相同信号的节点,如果有那么信号对应的位图依旧为1,代表还存在相同信号,如果每有相同节点就代表已经处理了,置0

这里介绍几个操纵信号集的函数
int sigemptyset(sigset_t *set)
初始化信号集,所有bit位全部置0
int sigfillset(sigset_t *set)
初始化信号集,所有bit位全部置1
int sigaddset(sigset_t *set,int signo)
添加有效信号
int sigdelset(sigset_t *set,int signo)
删除有效信号
int sigprocmask(int how, const sigset_t *set, sigset_t *oset)
读取或者改变进程中的信号屏蔽字
how是我们想要对这个信号集做出的事情
SIG_BLOCK 向集合中添加关键字
SIG_UNBLOCK向集合中解除关键字
SIG_SETMASK 设置信号屏蔽字为set指向的值
如果要设置新的屏蔽字集合就给set传参,如果要查看当前屏蔽字集就导出oset参数
这几个函数都是成功返回0,失败返回-1

信号的捕捉

如果信号的处理函数是自定义函数,那么在信号递达的时候调用这个函数,被叫做信号的捕捉

接下来我们来看看内核如何捕捉一个信号

1.用户注册了SIGQUIT信号的处理函数sighandler;
2.当前正在执行main函数,这时发生了中断或异常切换到内核态;
3.在中断处理完成后要返回到用户的main函数之前检查到有SIGQUIT信号递达;
4.内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函数,sighandler函数和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程;
5.sighandler函数返回后自动执行系统调用sigreturn再次进入内核态;
6.如果没有新的信号递达则返回用户态恢复main函数的上下文继续执行。

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact)
我们用这个函数实现对信号处理动作的修改
signo是要修改的信号,act是要修改的动作,oact是修改前的默认处理动作

当某个信号的处理函数被调⽤用时,内核⾃自动将当前信号加⼊入进程的信号屏蔽字,当信号处理函数返回时⾃自动恢复 原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产⽣生,那么 它会被阻塞到当前处理结束 为止。 如果在调⽤用信号处理函数时,除了当前信号被⾃自动屏蔽之外,还希望⾃自动屏蔽另外⼀一些信号,则用samask 字段说明这些需要额外屏蔽的信号 , 当信号处理函数返回时⾃自动恢复原来的信号屏蔽字。
act 和oact指针指向struct sigaction结构体 :
struct sigaction {
void (*sa_handler)(int);
void (sa_sigaction)(int, siginfo_t , void *);
sigset_t sa_mask; // 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)
int sa_flags; //默认设置为零
void (*sa_restorer)(void);
};
sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)
sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值