目录
[1. 什么是信号](#1. 什么是信号)
[2. 信号的种类](#2. 信号的种类)
[1. kill命令](#1. kill命令)
[2. 硬件产生](#2. 硬件产生)
[3. 系统调用](#3. 系统调用)
[4. 软件条件](#4. 软件条件)
[5. 程序异常](#5. 程序异常)
[1. 信号的捕捉](#1. 信号的捕捉)
[2. 信号的保存](#2. 信号的保存)
[1. 默认动作](#1. 默认动作)
[2. 忽略动作](#2. 忽略动作)
[3. 自定义动作](#3. 自定义动作)
**一、**信号的概念
1. 什么是信号
从一个故事理解信号
- 你在网上买了很多件商品,再等待不同商品快递的到来。但即便快递没有到来,你也知道快递来临时,你该怎么处理快递。也就是你能"识别快递"
- 当快递员到了你楼下,你也收到快递到来的通知,但是你正在打游戏,需5min之后才能去取快递。那么在在这5min之内,你并没有下去去取快递,但是你是知道有快递到来了。也就是取快递的行为并不是一定要立即执行,可以理解成"在合适的时候去取"。
- 在收到通知,再到你拿到快递期间,是有一个时间窗口的,在这段时间,你并没有拿到快递,但是你知道有一个快递已经来了。本质上是你"记住了有一个快递要去取" 当你时间合适,顺利拿到快递之后,就要开始处理快递了。而处理快递一般方式有三种:1. 执行默认动 作(幸福的打开快递,使用商品)2. 执行自定义动作(快递是零食,你要送给你你的女朋友)3. 忽略快递(快递拿上来之后,扔掉床头,继续开一把游戏)
- 快递到来的整个过程,对你来讲是异步的,你不能准确断定快递员什么时候给你打电话
信号是进程之间事件异步通知的一种方式,属于软中断。如:
- 输入命令,在Shell下启动一个前台进程。
- 用户按下Ctrl-C,键盘输入产生一个硬件中断。
- 如果CPU当前正在执行这个进程的代码,则该进程的用户空间代码暂停执行, CPU从用户态切换到内核态处理硬件中断。
- 终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程的PCB中(也可以说发送了一个SIGINT信号给该进程)。
- 当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。
2. 信号的种类
使用命令查看:kill -l
本文我们只探究1-31号信号。
1-31号信号的默认动作大部分都为终止进程。
二、信号的产生
1. kill命令
可以通过kill命令向指定进程发送指定信号。
如向pid为12345的命令发送9号命令SIGKILL:
kill -9 12345
2. 硬件产生
如在键盘上按ctrl + c,相当于向进程发送2号信号SIGINT。
3. 系统调用
kill函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig); //用法参考kill命令
abort函数
#include <stdlib.h>
void abort(void); //向系统发生6号信号SIGABRT
4. 软件条件
管道
但管道读端关闭时,os会向管道发送13号信号SIGPIPE,将管道销毁。
alarm函数
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//相当与一个闹钟,当alarm函数触发后,经seconds秒后os向系统发送14号信号SIGALRM
5. 程序异常
当程序出现异常时,os会向程序发送8号信号SIGFPE或13号信号SIGEGV,导致程序退出。
我们看到的表现形式为程序奔溃。
三、信号的捕捉与保存
1. 信号的捕捉
2. 信号的保存
进程的PCB中有一个专门用来保存信号的字段。信号保存的结构由block、pending两个位图和handler这一个函数指针数组三部分组成。
信号的编号本质就是数组下标。
当信号产生时,对应pending位便会由0置1。os定期扫描pending位图,如果扫描时发现有pending位为1,且block位不为1,责执行该位的handler方法。
block位图是记录信号阻塞的位图。一旦某一信号的对应block位为1,该信号就被阻塞,及收到信号后只进行记录而不执行。
handler数组记录的是信号动作。
信号集操作函数如下:
#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);
- sigset_t是一种位图数据结构
- 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有 效信号。
- 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系 统支持的所有信号。
- 注意,在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的 状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号
四、信号的执行
信号有三种动作,默认、忽略和自定义,详细介绍如下:
1. 默认动作
即执行信号的默认功能。
2. 忽略动作
忽略该信号,即收到信号后pending位置0但不执行任何函数。
#include <signal.h>
sighandler_t signal(int signum, SIG_IGN);
//signum为想忽略的信号
//如:signal(SIGINT, SIG_IGN);
3. 自定义动作
收到信号后执行用户自定义的方法。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
//signum为想忽略的信号,handler为函数指针
//如:
#include <iostream>
#include <signal.h>
void test(int signo)
{
std::cout << "test sig, signo : " << signo << std::endl;
}
int main()
{
signal(SIGINT, test);
while(1);
return 0;
}