希望的事,迟早会实现,但实现的希望,总是变了味的!
目录
8. 信号Signal
8.1 概念
- 信号是进程之间实现异步通知的一种方式,对进程来说是软中断,前提是得识别这个信号。
8.2 信号分类
8.2.1 信号的查看
- 使用kill -l命令查看
8.2.2 信号的说明
- 每个信号都有一个编号和一个宏定义名称,这些宏定义可以在 signal.h 中找到,例如其中有定义 #define SIGINT 2
8.2.3 信号分类
(总共有62种)
可靠信号(实时信号)
34~64号信号
非可靠信号(非实时信号)
1~31号信号
8.3 信号的功能
- (1)通知进程发生了哪些事件,应该怎么处理;
- (2)信号实际上也可以归为一类进程间通信;
8.4 信号的生命周期
8.4.1 信号产生
(1)硬件产生(终端按键产生)
- ctrl +c(中断信号)
- ctrl + |(退出信号)
- ctrl +z(停止信号)
core dump - 定义:当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。
- 进程异常终止:通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做 Post-mortem Debug(事后调试)。
(2)软件产生
- 调用系统函数向进程发送信号
kill -signum pid 即 kill -SIGSEGV 4568 或 kill -11 4568
raise(sigmnum)
kill(pid,signum)
总结:kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号,raise函数可以给当前进程发送指定的信号(自己给自己发信号)。
2. 由软件条件产生信号
abort()
alarm(nsec)
sigqueue函数
3. 硬件异常产生信号
8.4.2 信号注册
- (1)在pcb中的未决信号集中进行标记,并且在sigqueue中添加节点;
- (2)可靠信号注册
不管信号是否已经注册,都会向链表中添加一个新的信号结点(事件不会丢失); - (3)非可靠信号注册
判断pcb中的 pending位图 中相应的信号是否已经注册(位图是否已经置为1);若已经注册,则不做任何操作(事件丢失);
8.4.3 信号注销
(1)从sigqueue链表中移除信号结点,修改未决信号位图
(2)可靠信号
- 结点可能有多个,注销就是删除一个结点,判断链表中是否还有相同信号的结点,若没有则删除,则位图置为0;否则位图不变依然需要标记有这个信号待处理。
(3)非可靠信号
- 结点只有一个,注销就是删除结点,位图置为0。
8.4.4 信号处理
(1)定义
- 如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。
(2)信号处理方式
-
- 默认处理方式
执行该信号的默认处理动作;
- 默认处理方式
-
- 忽略此信号
处理动作中什么都没做;
- 忽略此信号
-
- 自定义处理方式
signal(signum,handle);
sigaction(int signal,struct sigaction* act,struct sigaction* oact);
- 自定义处理方式
8.5 信号的阻塞
8.5.1 信号以及其他相关概念
- 信号的阻塞
在内核的 pcb 中有一个阻塞信号集合,标记哪些信号到来之后暂时不处理。 - 信号未决
信号从产生到递达之间的状态,称为信号未决(Pending)。 - 阻塞和忽略的区别
只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
8.5.2 信号在内核中的表示
- 信号的两个标志位
阻塞(block)和未决(pending),还有一个函数指针表示处理动作;
信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
8.5.3 sigset_t(信号集)
- 每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。
- 未决和阻塞标志可以用相同的数据类型 sigset_t 来存储,sigset_t 称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态;在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。
- 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。
8.5.4 信号集操作函数
(1)信号集操作函数
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
(2)sigemptyset
- 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应 bit 清零,表示该信号集不包含任何有效信号;
(3)sigfillset
- 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号;
(4)sigismember - 是一个布尔函数,用于判断一个信号集的有效信号中是否包含;
(5)返回值 - 这四个函数都是成功返回0,出错返回-1;
(6)注意 - 在使用 sigset_ t 类型的变量之前,一定要调用 sigemptyset 或 sigfillset 做初始化,使信号集处于确定的状态。初始化 sigset_t 变量之后就可以在调用 sigaddset 和 sigdelset 在该信号集中添加或删除某种有效信号。
8.6 函数的可重入与不可重入
- 重入概念
同时在多个执行流中进入同一个函数执行。 - 可重入
函数重入之后不会出现数据二义性等异常。 - 不可重入
函数重入之后会出现数据的二义性/逻辑混乱。