所谓信号,就简单场景来说,启动一个前台进程,用户按下Ctrl_C可将进程终止。
在这呢,简单说说后台进程能否用Ctrl_C终止?
一个命令后面加个&便可在后台执行。这样Shell不必等待进程结束就可以新的命令,启动新的进程。Shell可以同时执行一个前台进程和多个后台进程,只有前台进程才能接到Ctrl_C控制键产生的信号。
若在前台执行死循环,可用Ctrl_C,kill -9 id 将进程终止
若在后台执行死循环,Ctrl_C不会将进程杀死,kill -9 id可以将进程终止,kill -11 id也可以将进程终止,但是产生段错误。
一、产生信号
基本概念:
1. 如何产生信号呢?
<1> 键盘。当某个进程正在运行时,按下Ctrl_C便可终止进程。
<2> 命令。如kill -9 3212 将id为3212的进程终止。
<3> 函数。如kill,alarm,abort函数。
<4> 操作系统捕捉。软件,硬件异常
2. 如何处理信号?
<1> 忽略信号
<2> 执行该信号的默认动作,一般是将该进程终止
<3> 捕捉。自定义函数。
3. 当进程在收到信号时,并不是立即处理,而是在合适的时候处理。应先将所接受的信号先保存在自己 的PCB中。
几个函数:
kill函数:给指定的进程发送指定的信号
函数原型:int kill(pid_t pid,int signo)
返回值:成功为0,失败为-1
rasie函数:给当前进程发送指定的信号
函数原型:int raise(int signo)
返回值:成功为0,失败为-1
abort函数:当前进程接受到SIGABRT信号而异常终止
函数原型:void abort(void)
alarm函数:在seconds秒后给当前进程发SIGALRM信号,默认终止当前进程
unsigned int alarm(unsigned int seconds)
返回值:返回0或者设定闹钟剩下的时间
当seconds为0时,表示取消该闹钟,并返回设定闹钟剩下的时间。
signal函数:设置某一信号的对应动作
函数原型:
检验当seconds为0时,取消闹钟,函数返回以前设定的闹钟剩下的时间
二、阻塞信号
基本概念:
实际执行信号的处理动作称为信号抵达。忽略是递达的一种。
信号从产生到递达之间的状态称为信号未决
进程可以选择阻塞某一信号。被阻塞的信号将保持在未决状态,直到进程解除对此信号的阻塞,才能执行递达的动作。
信号在内核中的表示示意图:
每个信号都有两个标志位分别为block(阻塞)和pending(未决),还有一个函数指针表示处理动作。
SIGUP:信号未产生也为阻塞,当它递达时执行默认处理动作
SIGINT:信号产生,但是阻塞。它的处理动作是忽略。阻塞不解除,都不会执行处理动作。
SIGQUIT:没有信号产生,一旦产生就会被阻塞,处理动作是用户自定义函数sighandler。
每个信号只有一个bit的未决标志和阻塞标志,非0即1,不记录该信号产生的次数。因此,未决和阻塞标志可以用相同的数据类型sigset_t储存,sigset_t称为信号集。这个类型表示信号的有效和无效。在阻塞信号集(信号屏蔽字)中即block表,1表示信号阻塞,0表示不阻塞。在未决信号集即pending表,1表示信号产生未决状态,0表示没有产生信号。总而言之,信号集为能够表示信号类型的0,1序列。
注:不可使用位操作操作信号集,有专有的函数操作
信号集操作函数:
sigemptyset:初始化set所指向的信号集,使其中所有的信号对应的bit清零
sigfillset:初始化set所指向的信号集,使其中所有的信号对应的bit置1
sigaddset:在该信号集中添加某一信号
sigdelset:在该信号集中删除某一信号
sigismember:判断一个信号集的有效信号中是否包含某种信号,若包含返回1,不包含返回0,出错返 回-1
sigprocmask函数:读取或更改进程的信号屏蔽字(阻塞信号集)
返回值:成功为0,失败为-1.
set:非空,更改进程的信号屏蔽字,参数how指示如何更改
set,oset:非空,则先将原来的信号屏蔽字备份到oset,然后根据set,how更改信号屏蔽字
how参数的取值:(假设信号屏蔽字为mask)
SIG_BLOCK:set包含了添加到当前信号屏蔽字的信号,相当于mask = mask | set
SIG_UNBLOCK:set包含从当前信号屏蔽字中解除阻塞的信号,相当于mask = mask&~set
SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask = set
sigpending函数:读取当前进程的未决信号集
int sigpending(sigset_t *set);
返回值:成功为0,出错为-1
三.捕捉信号
信号捕捉过程<4次权限转换>:
前面曾提过当一个进程收到信号时,并不是立即处理,而是在合适的时候,所谓的合适的时候就是
从内核态返回用户态时切换处理信号。当内核处理完异常或中断时,会先检查当前进程中是否有可以被递达的信号,有,如果信号的处理方式捕捉:从内核态调到用户态执行代码,之后返回内核态,从内核态返回用户态即上次被中断或者异常的地方。若信号的处理方式为默认,则终止进程,若为忽略,从Pending表中删除该信号,即将1变为0,直接跳到用户态。
sigaction函数:读取和修改与指定信号相关联的处理动作。
返回值:成功为0,出错为-1.
signo:指定信号的编号
act:非空。根据act修改该信号的处理动作
oact:非空。传出该信号原来的处理动作
act和oact指向如下结构体:
sa_hander:若赋值为SIG_IGN 表示忽略信号;SIG_DFL表示执行默认动作;赋值为一个函数指针表示自定义函数捕捉信号。这个函数不是被main函数调用,而是被系统函数所调用。
sa_mask:需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字
sa_flags:包含一些选项
pause函数:使调用进程挂起直到有信号递达
函数原型:int pause(void) 返回值:只有出错的返回值
信号的处理动作为终止进程,则进程终止,不执行pause
信号的处理动作为忽略,进程继续挂起,pause不返回
信号的处理动作为捕捉,则调用了信号处理函数后返回-1
实现sleep函数
测试结果:每个1秒打印一个hello bit
测试31个普通信号中那些能被捕捉,那些不能被捕捉。
测试结果:
分析:首先对每个信号都设置对应的行为,当信号为9时,直接终止进程,若将信号9除外,当信号为19 时,进程终止。若将信号9,19除外,则每个信号都可以被捕捉。