目录
什么是信号?
技术层面的信号
信号保存
阻塞信号
捕捉信号(什么是合适的时候)
实际生活中的信号就好像是一条条信息,时间到了响了的闹钟,到饭点肚子咕咕的叫声,这些都是信号,告诉你该做某事了。
但信号不一定会立马执行,实际上你可能闹钟响了十分钟后,才醒来。肚子叫了半小时后才吃饭。而收到信号到执行这段时间叫做时间窗口,时间窗口内,任务并没有执行,而是说"你记住了有个任务要做",“在合适时间会去做”。
时间窗口结束后,就要处理任务,处理方式有三种,1.执行默认动作(比如起床)2.执行自定义动作(比如,伸懒腰)3.忽略任务(继续睡)。
那什么是技术层面的信号呢?
比如操作时输入CTRL + C 退出进程,这时CTRL + C 就是信号(下图中的二号信号),输入ls,就是显示当前目录文件,此时ls就是信号。
与生活中的信号类比,人是进程,操作系统是闹钟,声音,信号就是任务。
信号概念:信号是进程之间事件异步通知的一种方式,属于软中断。
kill -l命令可以察看系统定义的信号列表
共62个,前31个普通信号,后31个实时信号,今天关注前31个
九号信号不能被捕捉
信号处理方式
1. 忽略此信号。
2. 执行该信号的默认处理动作。
3. 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为捕捉(Catch)一个信号。
信号产生时
如何产生信号呢?
1. 通过终端按键产生信号——通过键盘产生
2. 调用系统函数向进程发信号——这里介绍三个系统调用函数kill,raise,abort
#include
int kill(pid_t pid, int signo);
int raise(int signo);
这两个函数都是成功返回0,错误返回-1。kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定
的信号(自己给自己发信号)。#include
void abort(void);
就像exit函数一样,abort函数总是会成功的,所以没有返回值abort函数使当前进程接收到信号而异常终止。
3. 由软件条件产生信号
alarm(seconds)
#include
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动
作是终止当前进程。4. 硬件异常产生信号(比较重要)
所有的信号都必须经过操作系统的手发出。
位图
00000000000 ... 00000111 ->32位
比特位的位置,代表的是,是谁 信号的编号
比特位的内容,代表的是,是否 是否受到信号
00100000000 ... 00000000 代表收到三号信号
如何实现:
1.信号是给进程发的,task_struct结构体
struct task_struct{
unsigned int sigbitmap = 0;(也就是后文的pending表)
}
2.信号是给操作系统发的,那操作系统如何发送呢?
3.操作系统给进程“发送”信号,实际是给进程写信号。
信号相关概念
实际执行信号的处理动作称为信号递达(Delivery)——抵达有三种
信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞 (Block )某个信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
信号内核中
进程描述时,有三个部分,就是说PCB中,有
block表:信号屏蔽字 记录信号被屏蔽/阻塞信息
是谁?是否?
pending位图 保存信号
bandler数组(函数指针数组)
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。
信号集操作函数
#include
int sigemptyset(sigset_t *set); 全为0
int sigfillset(sigset_t *set); 全为1
int sigaddset (sigset_t *set, int signo);向前面集合添加后面信号为1
int sigdelset(sigset_t *set, int signo);向前面集合添加后面信号为0
int sigismember(const sigset_t *set, int signo);判断信号是否在信号集当中
sigprocmask 修改信号屏蔽字
#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1三个how参数
增加SIG_BLOCK
屏蔽SIG_UNBLOCK
覆盖SIG_SETMASK
sigpending
#include
sigpending
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1
从内核态切换至用户态时,进行相关检查。