信号时一种从软件层面上对中断的模拟,很多重要的程序都需要处理信号,信号提供了一种处理异步事件的方法。比如,用户在终端按下
ctrl C
会终止一个进程,或者通过kill
命令来给特定的进程发送信号。
信号基本概念
每个信号都有一个名字,这些名字以SIG
开头,在头文件 <signal.h>
中,这些信号名被定义为正整数常量(信号编号)。我们可以通过 kill -l
查看系统定义的信号列表。如下:
如上:ctrl C
对应 SIGINT
信号,信号编号是2。
产生信号的条件:
- 当用户按下某些终端按键时,会产生终端信号。如上面提到的
ctrl C
; - 硬件异常产生信号:如除 0 异常, 无效的地址访问(对NULL指针解引用、内存越界访问),这些信号通常有计算机硬件检测到,并通知操作系统内核,内核再给触发异常的进程发送合适的信号,如:对无效内存的解引用的进程将受到
SIGSEGV
信号; - 通过系统调用函数:
int kill(pid_t id, int sig);
,下面是一个例子:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
int id = fork();
int count = 0;
if(id < 0){
perror("fork");
return -1;
}else if(id == 0){
//child
for(;;){
printf("%d\n", count++);
usleep(1000*200);
}
}else{
//father
sleep(3);
kill(id, SIGKILL);
wait(0);
}
return 0;
}
上面这段代码中,子进程做死循环打印动作,父进程睡眠 3 秒后给子进程发送信号,将其终止。
- 命令行中通过
kill [opt] <pid>
命令给指定进程发送 指定命令; - 当检测到某些软件条件产生时,应将其通知有关进程并产生信号。比如:
SIGALRM
(进程设置的定时器超时),SIGPIPE
(管道的读进程终止后,一个进程写管道)。
信号的处理方式:
- 忽略此信号。有两种信号不能忽略,它们是
SIGKILL
和SIGSTOP
,原因是:它们像内核和超级用户提供了是进程终止的可靠方法。 - 扑捉信号。通知内核在某种信号发生时,调用一个用户函数,在该函数中,执行用户希望对该信号处理的动作。如 一个子进程终止时,会想其父进程发送
SIGCHLD
信号,所以我们可以自定义信号的捕捉函数,在该函数中调用waitpid
以获取子进程的 ID 和退出状态; - 执行默认动作,大多数系统默认动作是终止该进程。
信号的底层机制
我们来看看,当我们在键盘上敲下 ctrl C
时操作系统都发生了什么。
可以看到,对某一个进程发送信号实际上是,将该进程 PCB 的某个字段设置一个值,那么这一点在 PCB 中更是怎样展现的呢?看下面这张图:
执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
可以将信号在 PCB 中的标识看为上图中的三张表:
- block 又称为信号屏蔽状态字,它是个 64bit 的位图,每个位代表一个信号,对应位为1,则表示信号被阻塞;
- pending 又称为信号未决状态字, 它也是个 64bit 的位图,每个位代表一个信号,对应位为1代表未决,0代表信号可以递达了;
- handler 表中记录了每种信号的处理方式,对于用户自定义的处理函数,该表对应位上为信号处理函数的地址。
注意:
- 比如向进程发送进程
SIGINT
信号,内核首先判断信号屏蔽状态字是否阻塞,如果该信号被设为为了阻塞的,那么信号未决状态字(pending)相应位制成1;若该信号阻塞解除,信号未决状态字(pending)相应位制成0;表示信号此时可以抵达了,也就是可以接收该信号了; - 信号设计的机制是:用户可以读写信号屏蔽状态字 ,但只能读信号未决状态字。
信号的捕捉:
信号的处理动作为用户自定义函数,在信号递达是就调用该函数,这个动作称为信号的捕捉。用户自定义的信号处理函数在用户空间内,所以其相应的处理就要涉及到 “用户态——内核态” 之间的相互切换,过程比较复杂。比如用户注册了 SIGINT
信号的处理函数 myHandler()
,那么当进程发生中断或者异常后,就切换到内核态,在处理完中断异常,在返回用户态之前,内核检查到有信号 SIGINT
递达,那么内核就返回至用户态执行 myHandler()
信号处理函数&#x