1. 信号的概念
Linux进程间通信的方式之一。信号也称为“软件中断”。
信号特点:
- 简单;
- 携带信息有限;
- 满足特定条件才发送信号;
- 可进行用户空间和内核空间进程的交互;
信号4要素:
(1)编号;(2)名称;(3)事件;(4)默认处理方式。
2. 信号的编号
kill -l // 查看信号编号
POSIX信号标准:
1~31为常规信号;34~64为实时信号(驱动编程、硬件相关)。
为保证代码可移植性,请使用信号名进行编程。
Linux常规信号介绍:
编号 | 信号 | 事件 | 默认处理 |
1 | SIGHUP | 用户退出shell时,由该shell启动的所有进程会收到SIGHUP信号。 | 终止进程 |
2 | SIGINT | Ctrl + c 触发该信号,由该终端启动的正在运行的进程会收到SIGINT信号。 | 终止进程 |
3 | SIGQUIT | Ctrl + \ 触发该信号,由该终端启动的正在运行的进程会收到SIGQUIT信号。 | 终止进程 |
4 | SIGILL | CPU检测到某些进程执行非法指令。 | 终止进程并产生core文件 |
5 | SIGTRAP | 由断点指令或其他trap指令产生。 | 终止进程并产生core文件 |
6 | SIGABRT | 调用abort函数时产生该信号。 | 终止进程并产生core文件 |
7 | SIGBUS | 非法访问内存地址、内存对齐出错。 | 终止进程并产生core文件 |
8 | SIGFPE | 发生致命运算错误。浮点运算错误、溢出、除数为0等。 | 终止进程并产生core文件 |
9 | SIGKILL | 无条件终止进程。该信号不能被忽略、处理、阻塞。 | 终止任意进程 |
10 | SIGUSE1 | 用户定义的信号。程序员可在程序中定义并使用该信号。 | 终止进程 |
11 | SIGSEGV | 进程进行了无效内存访问(段错误)。 | 终止进程并产生core文件 |
12 | SIGUSR2 | 用户定义的信号。程序员可在程序中定义并使用该信号。 | 终止进程 |
13 | SIGPIPE | Broken pipe向一个无读端的管道写数据。 | 终止进程 |
14 | SIGALRM | 定时器超时,超时时间由系统调用alarm设置。 | 终止进程 |
15 | SIGTERM | 终止进程,该信号可被阻塞、终止。通常用来通知程序正常退出。kill命令的缺省选项就是这个信号。 | 终止进程 |
16 | SIGSTKFLT | Linux早期版本的信号,使用极少。 | 终止进程 |
17 | SIGCHLD | 子进程结束时,父进程会收到该信号。 | 忽略 |
18 | SIGCONT | 使暂停的进程继续运行 | 继续/忽略 |
19 | SIGSTOP | 暂停进程。不能被忽略、处理、阻塞。 | 暂停进程 |
20 | SIGTSTP | Ctrl + z 触发该信号,暂停与终端交互的进程。 | 暂停进程 |
21 | SIGTTIN | 后台进程读终端控制台 | 暂停进程 |
22 | SIGTTOU | 类似于SIGTTIN,后台进程向终端输出数据时触发 | 暂停进程 |
23 | SIGURG | socket上有紧急数据时,向当前进程发出该信号 | 忽略 |
24 | SIGXCPU | 进程执行时间超过CPU时间的总量。(不是超过了时间片) | 终止进程 |
25 | SIGXFSZ | 超过文件最大长度 | 终止进程 |
26 | SIGVTALRM | 虚拟时钟产生的信号,类似于SIGALRM。该信号3计算该进程占用CPU的时间 | 终止进程 |
27 | SIGPROF | 类似于SIGVTALRM,计算进程占用CPU时间 + 系统调用时间 | 终止进程 |
28 | SIGWINCH | 窗口大小变化时触发 | 忽略 |
29 | SIGIO | 向进程发出一个异步IO事件 | 忽略 |
30 | SIGPWR | 关机 | 终止进程 |
31 | SIGSYS | 无效的系统调用 | 终止进程并产生core文件 |
31~64 | SIGRTMIN~SIGRTMAX | Linux实时信号,无固定含义,可由用户自定义 | 终止进程 |
3. 信号的状态
(1)产生状态
a)用户发出信号:
Ctrl + c :SIGINT;
Ctrl + \ :SIGQUIT;
Ctrl + z :SIGSTOP;
b)硬件异常:
除数为0、无效内存访问、溢出等,被硬件检测到通知内核,内核将对应的信号发给相应进程。
c)软件异常:
检测到某种软件信号(如定时器alarm),则通知相关进程。
d)系统调用:
如kill、raise、abort等系统调用会发出信号。
注意:接收信号进程与发送信号进程的所有者必须相同,或发送信号进程的所有者是root用户。
e)kill、killall等会发送信号。
(2)未决状态:信号未被处理。
(3)递达状态:信号被处理了。
4. 阻塞信号集 & 未决信号集
每个进程的PCB中有两个信号集合:阻塞信号集 & 未决信号集。
两个集合都是用位图表示信号的状态,1表示阻塞或未决。
仅可设置阻塞信号集;未决信号集由内核自动设置。
(1)阻塞信号集:
将某信号加入该阻塞信号集,该信号将被阻塞;
若被阻塞期间收到该信号,则不会被处理;
但在解除阻塞后,阻塞期间收到的那次信号仍然会被处理,相当于滞后处理该信号。
(2)未决信号集:
某信号产生,未决信号集中描述该信号的状态位被置为1,表示该信号为未决状态;当信号被处理,该信号对应的状态位被置为0。
信号产生后由于某些原因(主要为被阻塞)不能抵达,这些信号状态为未决状态。
信号被阻塞期间,一直处于未决状态。
5. 信号集函数
(1)信号集操作函数
#include<signal.h>
int sigemptyset(sigset_t* set); // 将set信号集置空
int sigfillset(sigset_t* set); // 将所有信号加入set信号集
int sigaddset(sigset_t* set, int signo); // 将signo信号加入set信号集
int sigdelset(sigset_t* set, int signo); // 将signo信号从set信号集移除
int sigismember(const sigset_t* set, int signo); // 判断set信号集中是否存在signo信号
/*sigset_t为二进制位图*/
信号集操作函数使用示例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>
void showSet(sigset_t* set) { // 显示信号集
for (int i = 1; i < 32; i++) {
if (sigismember(set, i)) {
printf("1");
} else {
printf("0");
}
}
putchar('\n');
}
int main(int argc, const char* argv[]) {
int i = 0;
// 定义信号集
sigset_t set;
// 清空信号集
sigemptyset(&set);
puts("sigemptyset后的信号集:");
showSet(&set);
// 将所有的信号加入信号集
sigfillset(&set);
puts("sigfillset后的信号集:");
showSet(&set);
// 将信号1,3从信号集中移除
sigdelset(&set, SIGHUP);
sigdelset(&set, SIGQUIT);
puts("sigdelset后的信号集:");
showSet(&set);
// 将信号1,3加入信号集
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGQUIT);
puts("sigaddset后的信号集:");
showSet(&set);
return 0;
}
运行结果:
(2)sigprocmask函数
#include<signal.h>
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
/*
功能:
根据how指定的方法对进程的阻塞信号集进行操作。
新的阻塞信号集由set指定,原先的阻塞信号集由oldset保存。
参数:
how:对阻塞信号集的操作方式:
SIG_BLOCK:向阻塞信号集中添加set信号集,新的信号集是set与oldset的并集。
相当于mask = mask | set;
SIG_UNBLOCK:从阻塞信号集中删除set集合。
相当于mask = mask & ~set;
SIG_SETMASK:将阻塞信号集设置为set。
相当于mask = set;
set:要操作的信号集地址。
若为NULL,则不改变阻塞信号集。仅将当前的阻塞信号集保存到oldset中。
oldset:保存原先阻塞信号集的地址。
返回值:
成功:0
失败:-1,失败时错误码只可能是EINVAL,表示参数how不合法。
*/
sigprocmask示例:
屏蔽SIGINT(Ctrl + C)信号。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>
void func0(int signum) {
printf("捕捉到信号:%d\n", signum);
}
void func1(int signum) {
printf("捕捉到信号:%d\n", signum);
}
int main(int argc, const char* argv[]) {
int ret = -1;
// 信号集
sigset_t set;
sigset_t old;
/*注册信号处理函数。*/
// SIGINT: Ctrl + c 2号信号
signal(SIGINT, func0);
/*SIGQUIT: Ctrl + \*/
signal(SIGQUIT, func1);
/*将SIGINT加入阻塞信号集*/
printf("按回车键阻塞信号2 SIGINT.\n");
// 只能输入回车,输入一个字符再回车,就相当于输入两个字符;第二个回车被下面的getchar接收。
getchar();
sigemptyset(&set); // 清空信号集
sigaddset(&set, SIGINT); // 将2号信号加入信号集
sigemptyset(&old); // 清空信号集
ret = sigprocmask(SIG_BLOCK, &set, &old); // 设置阻塞信号集
if (-1 == ret) {
perror("sigprocmask");
return 1;
}
printf("2号信号SIGINT屏蔽成功.\n");
printf("按下回车键解除2号信号的屏蔽.\n");
getchar();
/*将阻塞信号集设置为原先的集合*/
ret = sigprocmask(SIG_SETMASK, &old, NULL);
if (-1 == ret) {
perror("sigprocmask");
return 1;
}
getchar();
return 0;
}
运行结果:
(3)sigpending函数
获取未决信号集
#include<signal.h>
int sigpending(sigset_t* set);
/*
功能:
获取未决信号集存入set集合
参数:
set:存储未决信号集
返回值:
成功:0
失败:-1
*/
sigpending示例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<unistd.h>
int main(int argc, const char* argv[]) {
int ret = -1;
sigset_t new;
sigset_t old;
sigset_t set;
sigemptyset(&new); // 清空信号集set
sigemptyset(&old); // 清空信号集set
sigemptyset(&set); // 清空信号集set
/*将信号2、3放入信号集*/
sigaddset(&new, SIGINT);
sigaddset(&new, SIGQUIT);
ret = sigprocmask(SIG_BLOCK, &new, &old); // 将信号2、3放入阻塞信号集
if (-1 == ret) {
perror("sigprocmask");
return 1;
}
getchar();
ret = sigpending(&set); // 获取阻塞信号集
if (-1 == ret) {
perror("sigpending");
return 1;
}
for (int i = 1;i < 32;i++) { // 打印阻塞信号集
if (sigismember(&set, i)) {
printf("%d ", i);
}
}
putchar('\n');
return 0;
}
将信号2、信号3加入阻塞信号集后,按Ctrl + c、Ctrl + \ ,被阻塞,之后解除阻塞会捕捉到信号2、信号3.
运行结果: