信号概念
是一个软件中断,通知进程发生了某件事情,中断进程当前操作,让进程去处理这个事件。
信号有很多种:62种信号,两大类型:可靠信号:34~64/非可靠信号:1~31.进程必须识别这些信号。
查看信号种类:kill -l
kill杀死进程的原理:向进程发送信号--通知事件,让进程自己退出
信号生命周期:信号的产生--->在进程中注册--->在进程中注销--->信号的处理
信号的产生
硬件产生:ctrl+c ctrl+| ctrl+z
软件产生:kill -signum pid kill(pid, signum) raise(signum) abort() alarm(nsec)
具体代码如下:
信号在进程中的注册
在进程pcb中做标记,标记进程收到了哪些信号
未决(pending):是一种状态---表示信号从产生到处理之前所处的状态。
非可靠信号注册:判断pcb中的pending位图中相应信号是否已经注册(位图是否已经置1);若未注册,则位图修改为1,并向sigqueue链表中添加一个信号节点;若已经注册,则不作任何操作(事件丢失)。
可靠信号注册:不管信号是否已经注册,都会向链表中添加一个新的信号节点(事件不会丢失)
信号在进程中的注销
非可靠信号:节点只有一个,注销就是删除节点,位图置0.
可靠信号:节点可能有多个,注销就是删除一个节点,判断链表中是否还有相同信号的节点;若没有则将位图置0;否则位图不变仍然需要标记这个信号待处理。
信号的处理
信号的处理并不是立即被处理,而是选择一个合适的时机去处理信号。---->即进程的运行从内核态返回用户态的时候。
进程如何从用户态切换到内核态:发起系统调用、程序异常、中断。
程序运行的代码若是库函数或是用户自己写的函数,就说进程当前运行在用户态。
信号处理有多种方式:
- 默认处理方式:既定义好的处理方式
- 忽略处理方式:处理动作中什么都没做
- 自定义处理方式:用户自己确定信号如何处理----自定义信号的处理函数替换原有的处理函数。
如何修改信号处理方式:
sighandler_t signal( int signum, sighandler_t handler);
signum:信号编号-----替换signum这个信号的处理函数
handler:函数指针--用户传入的处理函数
SIG_DFL:信号的默认处理动作
SIG_IGN:信号的忽略处理动作
typedef void (*sighandler_t)(int);
在此段代码中,我们使用sigaction将信号SIGINT的处理动作替换成了我们自定义的函数sigcb,并将原处理方式保存在old_act中,当接收到信号SIGINT时,会打印信号编号,接下来我们将信号的处理方式又还原成默认处理方式,所以当我们第二次再给一个SIGINT信号的时候,程序就可以直接退出了,运行结果如下:
自定义信号处理方式的捕捉流程
内核如何实现信号的捕捉?
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:用户程序注册了SIGQUIT这个信号的处理函数sigcb,当前正在执行main函数,这时发生中断或异常切换到内核态。在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。内核决定返回用户态之后不是恢复main函数的上下文继续执行,而是执行sigcb函数,sigcb和main函数使用不用的堆栈空间,他们之间不存在调用和被调用的关系,是两个独立的控制流程。sigcb函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。
信号阻塞
阻止信号被递达------信号依然可以注册,只是暂时不处理。
注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
在pcb中还有一个集合----阻塞信号集合----标记哪些信号暂时不被处理。
在上图中,pending位图中标记了一个1号信号,和一个2号信号,2号信号被阻塞了,但1号没有被阻塞,成功递达并处理,而2号信号没能递达。
int sigprocmask( int how, const sigset_t *set, sigset_t *oldset);
how:
SIG_BLOCK:向阻塞集合中加入set集合中的信号 block = mask | set
SIG_UNBLOCK:从阻塞集合中移除set集合中的信号 block = mask & (~set)
SIG_SETMASK:将set集合的信号设置为阻塞集合 block = set
oldset:用于保存修改前,阻塞集合中的信号
具体代码如下:
程序运行后,所有的信号都会被阻塞,直到用户按下回车后,阻塞解除,相应信号处理。
运行结果:
在所有信号中,9号信号SIGKILL和19号信号SIGSTOP,无法被阻塞,无法被自定义,无法被忽略。
可重入函数与不可重入函数
函数的重入:多个执行流程同时执行进入相同的函数。
函数的可重入与不可重入:
可重入:多个执行流程同时执行进入相同的函数,不会造成数据二义性以及代码逻辑混乱。
不可重入:多个执行流程同时执行进入相同的函数,有可能造成数据二义性以及代码逻辑混乱。
当用户设计一个函数或使用一个函数的时候,在多个执行流中,那么这时候就需要考虑函数是否可重入情况。
如果一个函数符合以下条件之一则是不可重入的:
- 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
- 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
函数可重入与不可重入的关键点:
这个函数中是否对临界资源(全局数据)进行了非原子操作。