目录
注意!!!信号不是信号量
本节重点
信号:概念,信号的生命周期,函数的重入与不可重入,关键字volatile
- 掌握Linux信号的基本概念
- 掌握信号产生的一般方式
- 理解信号递达和阻塞的概念,原理。
- 掌握信号捕捉的一般方式。
- 重新了解可重入函数的概念。
1 信号概念
1.1 概念:
是一种事件通知机制,通知进程发生了某个事件,打断进程当前的操作然后去处理这个事件。
1.2 信号种类:
kill -l 查看linux中的信号种类—62种(中间没有32/33)
非可靠信号/非实时信号: 1~31;(是从unix系统借鉴来的)
可靠信号/实时信号: 34~64;(linux新增加的)
2 信号的生命周期
信号的产生, 信号的注册,信号的注销,信号的处理+ 阻塞
2.1 信号的产生
2.1.1 软件产生:
(1)kill -signum pid命令(kill杀死一个进程的原理就是给进程发送一个能够让进程退出的信号。)kill默认是15号信号。
(2)int kill(pid_ t pid, int sig);给指定进程发送指定信号
(3)int raise(int sig);给自己发送指定的信号.
(4)void abort(void);给自己发送SIGABRT信号
(5)unsigned int alarm(unsigned int s);s秒后发送SIGALRM给自己
2.1.2硬件:
通过命令:vi /usr/include/bits/signum.h 打开signum.h文件查看信号
这里插一条查找命令:当我们知道文件名字以及大致在哪个目录下时候输入命令:grep -R ‘SIGKILL’ /usr/include 回车查找。即可查找到正确目录~
(1)ctrl+c:中断当前操作。本质是给当前进程发送一个2号中断信号Interrupt(ANSI)
(2)ctrl+\ :本质给进程发送3号退出信号Quit(POSIX)
(3)ctrl+z:发送的是20号信号 Keyboard stop (POSIX)
2.2 信号的注册
让进程知道自己收到了某个信号/事件需要去处理
在进程中做标记;
在pcb中有个未决信号集合(位图) ,用于标记进程信号。
- 非可靠信号的注册:
1~31号信号如果没有注册过则注册,已经注册过则什么都不做(信号丢弃)。(不会同时注册第二次) - 可靠信号的注册:
34~64号信号,不管是否E经注册过,位图置1,向sigqueue链表中添加 一个节点。
2.3 信号的注销
信号的注销:删除待处理的未决信号(pending位图中)的信息。
一个是位图,还有一个是链表
位图:主要是为了标记是否收到了某个信号
链表:统计收到多少个相同信号
- 非可靠信号注销:
因为非可靠信号不会重复注册,链表中最多只有一个节点,因此删除一个节点, 并且对应位图置0 - 可靠信号的注销:
因为可靠信号有可能存在多个相同信号节点,因此删除一个节点后,若还有相同节点则位图不变,否则位图置0
2.4 信号的处理
信号的处理就是完成一个事件的处理,一个事件的处理其实就是一个功能的完成,一个功能的最小模块就是一个函数,信号处理说白了就是**运行信号的处理函数(**每个信号处理函数是不一样的)。信号的处理函数(处理方式)是可以修改的。
- 信号的处理方式种类:
(1)默认处理方式:系统中针对每个信号定义好的处理方式
(2)忽略处理方式:收到了信号处理方式就是忽略
(3)自定义处理方式:用户自己定义信号处理函数然后进行替换 - 替换函数
sighandler_t signal(int signum, sighandler_t handler);
signum:要修改处理方式的信号的值
handler:函数指针(SIG_ DFL-默认/ SIG_ IGN-忽略)
typedef void (*sighandler_ t)(int);
1.程序运行因为中断/异常/系统调用从用户态切换到内核态
2.完成内核处理功能后,在返回用户态主控流程之前检测是
否有信号待处理,有则处理信号
3.默认和忽略在内核中完成,自定义需要到用户态运行
4.信号的自定义处理函数运行完毕,返回内核态
一个用户进程无法直接访问内核空间,只能通过系统调用进行访问,当程序通过系统调用访问内核空间的过程称之为运行在内核态。
程序运行从用户态切换到内核态:系统调用接口,异常,中断
- 笔试题:以下那种方式可以切换到内核态运行?
memcpy(库函数、不会), fwrite(库函数、不会), read(系统调用,会切换), 10/0(除零异常,会切换)。如果当前运行的是用户自己写的代码或者库函数就运行在用户态,如果是系统调用,或者中断/异常的处理就是内核态。
2.5 信号的阻塞
2.5.1 定义
- 信号的阻塞:阻止当前未决信号的处理.
有信号注册了但是当前不处理,等到解除阻塞之后再处理。——在pcb中,标记有哪些信号到来了但是暂时不处理。在pcb中也有一个block阻塞信号集合。 将哪个信号添加到block集合中,就表示哪个信号就算到来了也暂时不处理。
2.5.2 接口函数
int sigprocmask(int how,sigset_t * new,sigset_t * old);
- 功能:对调用进程的阻塞信号集合进行操作
- how:
SIG BLOCK —— block |= new :向block集合添加new信号
SIG UNBLOCK——block &= ~ new:从block中移除new中信号
SIG SETMASK—— block = new:将new设置为block - new:要添加/移除阻塞的信号集合;
- old:用于接收修改前block中的信号(多数用于还原)
2.5.3 示例
思路:
1.先修改指定信号的处理方式 signal(sig, handler)
2.阻塞指定的/所有信号 sigprocmask(SIG_ BLOCK)
3.等待用户按下回车 getchar()
4.解除阻塞 sigprocmask(SIG_ UNBLOCK)
sigset_ t new;
会用到的辅助接口:
sigemptyset(sigset_ t *set); 清空
sigaddset(sigset_ t *set, int sig); 添加指定信号到集合
sigillset(sigset_ t *set);添加所有信号到集合中
sigdelset(sigset_ t *set, int sig);从集合中移除指定信号
sigismember(sigset t *set, int sig)判断信号是否在集合中
代码:
vi mask.c
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <signal.h> 5 6 void sigcb(int signo) 7 { 8 printf("recv signo:%d\n", signo); 9 } 10 int main() 11 { 12 signal(SIGINT, sigcb);//修改信号处理方式 13 signal(40, sigcb); 14 15 sigset_t set;//定义信号集合 16 sigemptyset(&set);//将集合清空 17 //sigaddset(&set, SIGINT);//将SIGINT信号添加到集合中 18 //sigaddset(&set, 40); 19 sigfillset(&set);//将所有信号添加到set集合中 20 //将set集合中的信号添加到block集合中进行阻塞 21 sigprocmask(SIG_BLOCK, &set, NULL); 22 23 printf("输入回车继续运行...\n"); 24 getchar();//等待一个回车,没有回车就一直卡在这里 25 26 sigprocmask(SIG_UNBLOCK, &set, NULL);//解除阻塞,观察信号处理 27 28 return 0; 29 }
makefile 内容
在这里插入代码片
make一下
在进程中,有两个信号是不可被阻塞,不可被修改处理方式的: SIGKILL SIGSTOP
一个进程kill杀不死原因是什么?1.僵尸进程; 2.信号被修改处理方式;3.信号被阻塞
- 应用:
1.僵尸进程的处理- -进程等待
子进程退出之后,父进程为什么没有关心到子进程退出状态
本质:子进程退出之后实际上给父进程发送了一个信号-SIGCHLD
然而SIGCHLD信号的默认处理方式就是忽略
相当于父进程实际收到了通知但是什么都没做
sigcb(){ while(waitpid(-1, NULL, WNOHANG)> 0); }
signal(SIGCHLD, SIG_ IGN);//用户的显式忽略
2.所有管道读端被关闭则write触发异常–SIGPIPE