进程信号
概念:是一种事件通知机制。用于向进程通知某个事件的发生,打断进程当前的操作,去处理这个事件。
Linux中信号的种类:62种
-
1~31号:非可靠信号/非实时信号,有可能会造成事件丢失。
-
34~64号:可靠信号/实时信号,不会丢失事件。
多种信号同时到达,优先处理实时信号。
信号的生命周期
产生信号–》在进程pcb中注册信号–》注销信号–》处理信号
信号的产生
硬件产生:ctrl + c:2;ctrl + |:3 ;ctrl + z:20;
软件产生:kill命令发送信号给指定进程 kill -signum pid
产生信号
1.系统调用接口:int kill(pid_t pid, int sig); 给指定进程发送sig信号。
2.库函数:int raise(int sig); 给进程自身发送一个指定的信号。
3.unsigned int alarm(unsigned int seconds); secend秒之后,给进程发送一个时钟信号–SIGALRM。
4.void abort(void); 给进程发送一个SIGABRT信号,使异常的进程终止。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
int main ()
{
kill(getpid(), SIGINT);
while(1)
{
printf("-----\n");
sleep(1);
}
return 0;
}
程序并没有死循环打印,是因为收到了SIGINT信号,直接退出。
查看函数调用栈,可以看到程序是在kill这里退出。
注册信号
注册:在进程中注册一个信号,让进程知道自己收到了一个信号。
进程收到信号的实质,就是在一个进程的pcb中,有一个信号pending位图,存放的是未被处理的信号,用来标记是否收到了某个信号。sigqueue,是一个双向链表,表示有多少个信号,及信号的信息。
收到一个信号就会在位图中对应的位置 置1,并在sigquene双向链表中添加一个节点。
非可靠信号:1-31号,如果信号没有被注册,则注册;如果已经被注册,则什么都不做。链表中不会出现相同的非可靠信号信息节点。
可靠信号:34-64号,不管信号是否被注册,都会再注册一遍。链表中可以出现多个重复的可靠信号信息节点。
注销信号
将信号信息从进程pcb中移除。
非可靠信号:删除节点,修改位图为0。
可靠节点:删除一个信号节点,检查链表中是否还有相同的信号节点,没有则修改位图为0。
处理信号
信号的处理也叫信号的递达,实际上就是打断进程当前的操作,去执行进程的对应信号处理函数。
信号的处理方式:
- 默认处理方式
- 忽略处理方式:什么都不做
- 自定义处理方式:用户自己定义信号的处理回调函数。
接口:
sighandler_t signal(int signum, sighandler_t handler);
- signum:信号值,表示要修改哪个信号的处理方式
- handler:新的信号处理方式
SIG_DFL:默认处理方式;
SIG_IGN:忽略处理方式;
自定义函数的名称:自定义处理方式; - 返回值:成功则返回当前信号原来的处理方式;失败返回SIG_ERR。
信号的阻塞
阻塞:阻止一个信号被递达。一个信号被阻塞后,依旧会受到这个信号,依旧会被注册,但是暂时不处理。
进程pcb中除了有pending位图,存放未被处理的信号,还有一个block阻塞位图,用来标记一个信号被阻塞。
当pending位图中出现一个信号标记,系统就会去block位图中查看这个信号是否要被处理。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how:操作类型
- SIG_BLOCK:阻塞set集合中的信号
- SIG_UNBLOCK:将set集合中的信号解除阻塞
- SIGSETMASK:将set集合中的信号设置为素色集合的信号
两个比较特殊的信号:
SIGKILL和SIGSTOP信号,这两个信号不可被阻塞,不可被自定义,不可被忽略。就是无法修改处理方式。
信号的基本应用
SIGCHLD信号: 一个子进程退出后,给父进程发送的子进程状态改变信号。但是这个信号的默认处理方式就是什么都不做。
要避免子进程变成僵尸进程,就需要在父进程中调用wait接口阻塞等待,如果不想阻塞等待,就可以用信号来解决,自定义SIGCHLD信号的处理方式,在回调函数中调用waitpid这个接口。
SIGPIPE信号: 管道所有读端被关闭则write触发异常所对应的信号。默认处理方式就是退出进程,若不想退出则需要自定义或者忽略处理。
函数的重入与不可重入
可重入函数:一个函数,就算重入,也不会出现预期之外的结果。
不可重入函数:一旦函数重入,就有可能出现预期之外的结果。
判断基准点:一个函数内部如果对全局数据进行了不受保护的非原子操作,这个函数就是不可重入函数。