信号
信号概念
信号是为了操作系统能控制进程正确的运行而产生的一种机制。它分为俩种类型,第一种是普通信号(标准信号),第二种是实时信号。通常进程产生普通信号有四种方式:
1 由键盘特殊的组合按键产生,该方式产生的信号发送给一个控制终端的前台作业。
2 由系统调用接口或者命令发出一个信号。(列如kill 命令,abort—异常终止信号)
3 由进程自身错误操作产生的异常导致的信号
4 软件条件产生的信号(列如 alarm , 还有管道中的SIGPIPE)
5 硬件产生 SIGBUS 和 却页中断
普通信号(Standard Signals)
Linux下普通信号为1~32。它由pending表/block表/handler表来管理。当有多个非同类型的普通信号产生时,信号抵达顺序是未定义的。有多个同类型的信号产生的时候,pending表只记录一个。还有一个就是Linux下的普通信号都是可靠信号。(这个意思就是多个同种类型信号产生的时候,系统会阻塞该信号,最终产生完毕后,接触阻塞最终只有一个信号产生)
实时信号(Run-time)
实时信号都是未定义的信号,在用户定义之前。它的抵达是有序抵达,以数字小的为最高优先级。当有多个同类型的信号产生时,按顺序抵达。使用应配合宏来一起使用SIGRTMIN+n ,但是不能超过 SIGRTMAX 。它的范围是64-33,刚好32个。使用宏的原因是,Linuxthreads线程库使用了3个实时信号,posix线程库使用了2个。我们用宏编译器会自动转换的。
当实时信号和普通信号同时产生时,Linux以先处理普通信号。
被信号中止的系统调用和库函数
epoll类的系统调用,system v 的系统调用,select poll epoll,已经设置了超时的网络系统调用的函数等系统调用被中止后返回 ENTER 错误。
read, readv, write, writev, ioctl,wait,waitpid等函数被中断后,如果由signal函数注册的自定义函数/sigaction函数设置了 SA_RESTART 标志的话,这些被中断的系统调用会自动重启。
信号的处理方式
信号一般由产生到未决再到递达。
产生:系统在该进程pcb的信号字段修改该进程的pending表中相应的有效位。
未决:pending表中有未处理的信号,该进程还没有对相应的信号进行处理。
递达:已经处理了相应的信号。递达有三种方式,忽略SIG_DFL,默认动作SIG_IGN,自定义handler的一个函数指针。
忽略也是递达的一种方式,如果信号被阻塞永远不会被递达。
信号阻塞与SIG_IGN
如果一个信号被阻塞,那么这个信号还是会在Pending表中,说明这个信号产生了,但是现在没办法处理。而SIG_IGN则是表示忽略,它也是信号递达(信号处理)的一种方式,如果该信号设置了SIG_IGN,那么它就会从Pending表中消失,表示已经处理过了。虽然它们俩个现象都是什么都不处理,但是本质是不同的。
下图是我另一篇博客中关于task_struct的信号字段的截图
进程中保存信号的数据结构
信号在pcb中的三个数据结构。
- block表:它又称信号屏蔽字,这个表中的有效位表示,即使信号处于未决状态也不能递达,该信号处于屏蔽状态
- pending表:它又称未决表,该表记录了进程未处理的信号。它的有效位表示该信号进程已收到但还未处理。
- handler表:表示信号的处理方式。
代码验证pending表与block表
#include <stdio.h>
#include <signal.h>
void handler(int signum)
{
printf("ctrl-c ----> SIGINT\n");
}
void printsigset(sigset_t * set)
{
size_t idx=0;
for(idx=0;idx<32;idx++)
{
if(0==sigismember(set,idx))
putchar('0');
else
putchar('1');
}
putchar('\n');
}
int main()
{
struct sigaction act,oact;
sigset_t mask,oldmask,pending;
act.sa_handler=handler;
act.sa_flags=0;
sigemptyset(&act.sa_mask);
sigemptyset(&mask);
sigemptyset(&oldmask);
sigemptyset(&pending);
sigaddset(&mask,SIGINT);
sigprocmask(SIG_BLOCK,&mask,&oldmask);
sigaction(SIGINT,&act,&oact);
int second=0;
while(second++<5)
{
sigpending(&pending);
printf("pending table:\n");
printsigset(&pending);
sleep(1);
printf("block table:\n");
printsigset(&mask);
}
sigsuspend(&oldmask);
printf("ok.. Now recovered block table\n");
sigprocmask(SIG_SETMASK,&oldmask,NULL);
sigpending(&pending);
printf("Now pending table\n");
printsigset(&pending);
sigaction(SIGINT,&oact,NULL);
return 0;
}
signal 与 sigaction api 区别
它们俩个区别在于通过signal API设置的信号,在handler执行完毕后,该信号会恢复默认行为,而sigaction则不会。
特殊的三个信号
SIGKILL
不能被阻塞、忽略、捕捉。
SIGSTOP
不能被阻塞、忽略、捕捉。
SIGABRT
不能被阻塞、忽略,但是可以被捕捉,虽然可以捕捉,但是在执行完自定义函数后进程还是会被终止。唯一的区别,如果捕捉,会自动在自定义函数中调用 fclose(NULL)然后关闭所有I/O流的同时刷新I/O流缓冲区。
SIGCHD 语义
默认SIGCHID信号是忽略的,但是如果我们手动调用sigaction 或者 signal函数置它为SIG_IGN 也就是主动置它为忽略式的处理方式,那么根据SIGCHD语义将不会产生僵尸进程,内核并不会为我们保留子进程终止时的信息
进程收到一个信号的处理时机与流程
一个进程收到信号不会立即去处理,除非在发送用户态到内核态的切换时才会触发信号处理。
信号表与线程
新产生的线程的pending表会被清除,handle与block表继承自主线程。