一、信号的基本概念
当在shell下启动一个前台的进程时,用户可以通过键盘上ctrl+c使这个进程结束,即产生了一个硬件中断。当cpu在执行这个进程的代码时,该进程的用户空间代码暂停执行,cpu从用户态切换到内核态处理硬件中断。终端驱动程序将键盘上输入的ctrl+c解释成一个
SIGINT信号, 记录在进程的PCB中。当某个时刻要从内核返回该进程的用户空间代码继续执行之前,需要首先处理PCB中记录的信号。发现有SIGINT信号待处理,这个信号默认处理动作是终止进程,所以直接终止进程而不在返回它的用户空间代码执行。
1.信号的产生方式
- 1.键盘。用户在终端按下某些键时,终端驱动程序会发送信号给前台进程(注意是前台进程,后台进程不能用键盘发送信号)。
- 2.操作系统或者编译器检查到异常并通知内核,内核向当前进程发送适当信号
- 3.一些命令或函数。当进程调用kill函数可发送信号给另一个进程。alarm函数超时也会产生SIGALRM信号,abort函数使当前进程收到SIGABRT信号而终止。
2.信号的处理动作
- 1.忽略信号
- 2.执行该信号默认处理动作
- 3.提供一个信号处理函数,要求内核在处理该函数时切换到用户态执行这个处理函数,即信号捕捉
二、阻塞信号
1.基本概念
实际执行信号的处理动作称为
信号递达,信号从产生到递达之间的状态称为
信号未决, 进程可以选择阻塞某个信号,被阻塞的信号产生时将保持在未决状态,知直到进程解除对信号的阻塞才执行递达的动作。
*阻塞和忽略是不同的,阻塞不会递达,而忽略是在递达后的一种处理动作。
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,知道信号递达时才清除该标志位。
信号在内核中的示意图
其中block表中1表示阻塞,0表示未阻塞,pending表中1表示产生信号,0表示未产生信号
图中:SIGHUP信号未阻塞也未产生,当递达时执行默认处理动作
SIGINT信号产生了,但是被阻塞,所以暂时不能递达,它的处理动作是SIG_IGN表示忽略。但在没解除阻塞之前,不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
SIGQUIT信号未产生过,一旦产生则被阻塞,处理动作是用户自定义函数sighandler
*若在进程解除对某型号阻塞之前这个信号产生多次将如何处理?
常规信号在递达之前产生多以只记录一次,pending表只有一个bit表示未决标志。
2.信号集操作函数
信号集不是信号量集。信号集是在用户空间描述信号,信号集可以表示pending表,又可称为pending信号集,用0、1表示,但是不允许使用位操作操作,系统提供对应的接口操作信号集,pending信号集和pending表对应。block表表示信号屏蔽字,也是信号集(block信号集)。
sigset_t类型对于每种信号用一个bit表示有效或无效的状态。以前是一些函数,可以调用他们来操作sigset_t变量。(注意这些信号量集的每个bit不能用位操作符改变)
注意:在使用sigset_t类型变量之前一定要调用sigemptyset或sigfillset做初始化使信号处于确定的状态。
sigprocmask函数
sigprocmask函数读取或更改信号屏蔽字(阻塞信号集),成功返回0,出错返回-1
如果oset是⾮非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是⾮非空指 针,则 更改进程的信号屏蔽字,参数how指⽰示如何更改。如果oset和set都是⾮非空指针,则先 将原来的信号 屏蔽字备份到oset⾥里,然后根据set和how参数更改信号屏蔽字。假设当前的 信号屏蔽字为mask,下表说明了how参数的可选值。
若调用sigprocmask解除了对当前若干个未决信号的阻塞,则在函数返回前,至少将其中一个信号递达
how参数:
igpending函数
sigpending函数读取当前进程的未决信号集,通过set参数传出,调用成功返回0,出错返回-1
例:sigpending读取当前进程的未决信号集,通过set参数传出。调⽤用成功则返回0,出错则返回 -1
运行:
三、捕捉信号
1.相关概念
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
对于一个用户自定义的信号处理函数,处理过程如下(以上图SIGQUT信号为例):
- 1.用户程序注册了SIGQUIT信号处理函数sighandler
- 2.当前正在执行main函数,此时发生中断或异常切换到内核态
-
3.中断处理完毕后返回用户态main函数之前需检查信号,发现有SIGQUIT信号递达
-
4.内核决定返回用户态后不是恢复main函数上下文继续执行而是执行sighandler函数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用关系,是两个独立的控制流程
-
5.sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态
-
6.若没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行
2.相关函数
- sigaction函数
sigaction函数可以读取和修改与指定信号相关联的处理动作,调用成功返回0,出错返回-1.
对于参数signum是指定信号的编号,若act指定非空,则根据act写该该信号的处理动作。若oldact非空,则通过其传出该信号原来的处理动作
其中sigaction的结构体为
sa_handler赋值为SIG_IGN传给sigaction表示忽略此信号。赋值为SIG_DEL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,这个自定义处理函数返回值为void,可以带一个int参数,通过参数可以知道当前信号的编号,这样就可以用同一个函数处理多信号。(这个函数也是一个回调函数,是被系统所调用)
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,保证了处理某个信号时,若这种信号再次产生,则阻塞到房钱处理结束为止
- pause函数
pause函数使调用进程挂起直到有信号递达。若信号的处理动作是终止进程,则进程终止,pause函数没机会返回;若信号的处理动作是忽略,则继续挂起,pause函数不返回;若信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,error设置为EINTR,pause函数只有出错的返回值,错误码EINTR表示信号被中断。
sleep()函数的实现
- 1、main函数调⽤用mysleep函数,后者调⽤用sigaction注册了SIGALRM信号的处理函数 sig_alrm。
- 2. 调⽤用alarm(nsecs)设定闹钟。
- 3. 调⽤用pause等待,内核切换到别的进程运⾏行。
- 4. nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。
- 5. 从内核态返回这个进程的⽤用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm。
- 6. 切换到⽤用户态执⾏行sig_alrm函数,进⼊入sig_alrm函数时SIGALRM信号被⾃自动屏蔽, 从sig_alrm函数返回时SIGALRM信号⾃自动解除屏蔽。然后⾃自动执⾏行系统调⽤用 sigreturn再次进⼊入 内核,再返回⽤用户态继续执⾏行进程的主控制流程(main函数调⽤用 的mysleep函数)。
- 7. pause函数返回-1,然后调⽤用alarm(0)取消闹钟,调⽤用sigaction恢复SIGALRM信号以前的处理 动作