Linux的信号(转)
信号(Signal)是一种软件中断,比如Ctrl+C的退出命令实质上就是使用了信号。信号在Linux操作系统中提供了一种处理异步事件的方法,可以很好地在多个进程之间进行同步和简单的数据交互。
kill -9 不可捕获
Linux的信号机制
信号机制是一种使用信号来进行进程之间的传递消息的方法,其中信号的全称为软中断信号,简称软中断。
软中断信号(Singal,又简称为信号)用来通知进程发生了异步事件,进程之间可以互相通过系统调用kill函数来发送软中断信号,而Linux内核也可以因为内部时间而给进程发送信号,通知进程发生了某个事件。
note:信号只是用来通知进程发生了什么事件,但并不给改进程传递任何数据。
每个信号都有一个名字,这些名字都义三个字符SIG开头,在头文件<signal.h>中,这些信号都被定义为正整数,称为信号编号。
note:没有编号为0的信号,kill函数对编号0有特殊的应用,在POSIX.1规范中,将此信号编号值称为空信号。
Linux内核支持64种不同的信号,这些信号种的大部分都有了预先定义好的意义,但是都支持自定义动作,并且还提供了类似SIGUSR1这样由应用程序来定义的信号。
1. 信号的产生
Linux中信号可以由以下几种方式产生:
当用于按下某些终端按键之后引发终端产生的信号,例如在程序运行中按下“Ctrl + \”组合键将终止程序的运行。
硬件产生的一个异常信号,例如除数为0,无效的内存引用等,这种异常信号通常会由硬件检测得到并将其通知Linux内核,然后内核为该条件发生时正在运行的进行产生适当的信号。
进程调用系统调用kill函数可以给一个进程或者进程组发送一个信号,需要注意的是此时发送和接收信号的进行/进程组的所有者必须相同
用户也可以调用kill命令将信号发送给其它进程
当检测到某种软件条件已经发生,并应将其通知有关进程的时候也会产生一个信号,例如SIGURG信号就是在接收到一个通过网络传送的外部数据时产生的。
2. 信号的处理方式
Linux的每一个信号都有一个缺省的动作,典型的缺省动作是终止进程,当一个信号到来的时候收到这个信号的进程会根据信号的具体情况提供以下三种不同的处理方式:
类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。
忽略某个信号,对该信号不做任何处理,就像从未发生过一样。
对该信号的处理保留系统的默认值,这种缺省操作大多数是使得进程终止。进程通过系统调用signal函数来指定进程对某个信号的处理行为。
3. 信号的缺陷
作为一种进程交互机制,信号有一些局限性:
信号的系统开销太大。
发送信号的进程要进行系统调用。
内核要终端接收信号的进程,而且要管理它的堆栈,同时还要调用处理程序,之后恢复执行被中断的进程。
信号的数量非常有限,因为为只存在有限的不同信号。
信号能传送的信息量十分有限,用户产生的信号不可能发送附加信息以及各种参数。
所以,在实际使用中,信号机制常常用于进程之间的事件通知,而不应用于复杂的交互操作。
4. 信号的执行过程
一个典型的执行过程是使用“Ctrl + C”组合键来中断一个车进程的运行,对其操作部分说明如下,需要注意的是只有在前台运行的程序才能接收到“Ctrl + C"组合键的输入:
01)用户输入命令,在Shell下启动一个前台进程。
02)用户按下“Ctrl + C”,这个键盘输入产生一个硬件中断。
03)如果处理器当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行,CPU从用户态切换到内核台处理硬件中断。
04)终端驱动程序将“Ctrl + C“解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。
05) 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程,而不再返回它的用户空间代码。
Linux内核给一个进程发送软中断信号的方法是:在进程所在的进程表项的信号域设置对应于该信号的位(内核通过在该进程的struct task_struct 结构体中设置相应的位来实现向一个进程发送信号)。
如果信号发送给一个正在睡眠的进程,那么要看该进程进入睡眠的优先级,如果进程睡眠在可被中断的优先级之上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。这一点比较重要,因为进程检查是否收到信号的时机是:一个进程在即将从内核态返回到用户态时;或者,在一个进程要进入或离开一个是适当的低调度优先级睡眠状态时。
内核处理一个进程收到的信号的时机是:在一个进程从内核态返回用户态时。所以,当一个进程在内核态下运行时,软中断信号并不立即起作用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。
内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。
Linux中处理信号有三种类型:进程接收到信号后退出;进程忽略该信号;进程收到信号后执行用户自定义的使用系统调用signal注册的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号并继续运行。如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数,而且执行用户定义的函数方法很巧妙,内核是在用户栈上创建一个新的层,该层将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回栈顶时就返回到用户定义的函数处,从函数返回在弹出栈顶时,才返回原先进入内核的地方。这样操作的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)。
5. 信号处理的注意事项
在信号的处理方法中有几点特别要引起注意:
在一些系统中,当一个进程处理完中断信号返回用户态之前,内核清楚用户区中设定的对该信号的处理例程的地址,即将下一次进程对该信号的处理方法改为默认值,除非在下一次信号到来之前再次使用signal系统调用。
如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断短优先级上,这时该信号引发进程调用longjmp函数跳出睡眠状态,返回用户态并执行信号处理例程;当从信号处理例程返回时,进程就像从系统调用返回一样,但返回了一个错误代码,指出该系统调用曾被中断。
若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不再longjmp函数调用,一般是继续睡眠,一般情况下e,用户感觉不到进程曾经被唤醒,而是像没有产生国信号一样。
内核对用子进程终止(SIGCLD)信号的处理方法与其他信号有所区别。
如果一个进程调用signal系统,并设置了SIGCLD的处理方式,且该进程有子进程处于僵死状态,则内核将向该进程发送一个SIGCLD信号。
6. 信号的分类
Linux下的信号分为如下几大类
与进程终止相关的信号:当进程退出,或者子进程终止时,发出这类信号。
与进程例外事件相关的信号:如进程越界,或企图写入一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。
与在系统调用期间遇到不可恢复条件相关的信号:如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。
与执行系统调用时遇到非预测错误条件相关的信号:如执行一个并不存在的系统调用。
在用户态下的进程发出的信号:如进程调用kill向其他进程发出信号。
与终端交互相关的信号:如用户关闭一个终端,或按下”Break“键等情况。
跟踪进程执行的信号。
在Linux下可以使用“kill -l”命令来查看当前系统支持的全部信号,通常来说有64个默认的信号。
除了使用“kill -l”命令之外,在Linux的帮助手册中对信号也有详细的定义,可以使用 man 7 signal 命令来查看更为详尽的说明,其中第1列为信号名称,第二列为对应的信号编号,第3列为对信号的处理方式,而第4列是对信号的详细说明。
使用 kill -l 和 man 7 signal命令中可以查看到信号的具体说明
————————————————
版权声明:本文为CSDN博主「凉冰难消一腔热血」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45676049/article/details/109256132