信号概述
信号是 Linux进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个丘在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

发往进程的诸多信号,通常都是源于内核
。引发内核为进程产生信号的各类事件如下:
- 对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C(9号信号)通常会给进程发送一个中断信号。
- 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被О除,或者引用了无法访问的内存区域
- 系统状态变化,比如 alarm 定时器到期将引起 SIGALRM 信号,进程执行的CPU时间超限,或者该进程的某个子进程退出。
- 运行 kill 命令或调用 kill 函数。
使用信号的两个主要目的是:
- 让进程知道已经发生了一个特定的事情。
- 强迫进程执行它自己代码中的信号处理程序。
信号的特点:
- 简单
- 不能携带大量的信息
- 满足某个特定条件才能发送
- 优先级比高
查看系统定义的信号列表:kill -l
前31个信号为常规信号,其余为实时信号。
信号一览表
编号 |
信号名称 |
对应事件 |
默认动作 |
1 |
SIGHUP |
用户退出shell时,由该shell启动的所有进程将 收到这个信号 |
终止进程 |
2 |
SIGINT |
当用户按下了组合键时,用户终端向正 在运行中的由该终端启动的程序发出此信号(CTRL + C) |
终止进程 |
3 |
SIGQUIT |
户按下组合键时产生该信号,用户终 端向正在运行中的由该终端启动的程序发出些信号 (CTRL + \) |
终止进程 |
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 |
程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号 |
终止进程 |
16 |
SIGSTKFLT |
Linux早期版本出现的信号,现仍保留向后兼容 |
终止进程 |
17 |
SIGCHLD |
子进程结束时,父进程会收到这个信号 |
忽略这个信号 |
18 |
SIGCONT |
如果进程已停止,则使其继续运行 |
继续/忽略 |
19 |
SIGSTOP |
停止进程的执行。信号不能被忽略,处理和阻塞 |
为终止进程 |
20 |
SIGTSTP |
停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号 |
暂停进程 |
21 |
SIGTTIN |
后台进程读终端控制台 |
暂停进程 |
22 |
SIGTTOU |
该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生 |
暂停进程 |
23 |
SIGURG |
套接字上有紧急数据时,向当前正在运行的进程发出些信号,报 告有紧急数据到达。如网络带外数据到达 |
忽略该信号 |
24 |
SIGXCPU |
进程执行时间超过了分配给该进程的CPU时间,系统产生该信号并发送给该进程 |
终止进程 |
25 |
SIGXFSZ |
超过文件的最大长度设置 |
终止进程 |
26 |
SIGVTALRM |
虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间 |
终止进程 |
27 |
SGIPROF |
类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间 |
终止进程 |
28 |
SIGWINCH |
窗口变化大小时发出 |
忽略该信号 |
29 |
SIGIO |
此信号向进程指示发出了一个异步IO事件 |
忽略该信号 |
30 |
SIGPWR |
关机 |
终止进程 |
31 |
SIGSYS |
无效的系统调用 |
终止进程并产生core文件 |
34 ~64 |
SIGRTMIN ~ SIGRTMAX |
LINUX的实时信号,它们没有固定的含义(可以由用户自定义) |
终止进程 |
信号的 5 种默认处理动作
查看信号的详细信息:man 7 signal
信号的 5 中默认处理动作
Term 终止进程
Ign 当前进程忽略掉这个信号
Core 终止进程,并生成一个Core文件
Stop 暂停当前进程
Cont 继续执行当前被暂停的进程
信号的几种状态:产生、未决、递达
SIGKILL 和 SIGSTOP 信号不能被捕捉、阻塞或者忽略,只能执行默认动作。
core
0代表不会生成core文件 图2为设置core文件

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
#include <stdio.h> #include <string.h>
int main() { char *buf; strcpy(buf, "hello"); return 0; }
|
kill、raise、abort函数
因为会抢占cpu的资源不确定执行几次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
#include <stdio.h> #include <sys/types.h> #include <signal.h> #include <unistd.h>
int main() {
pid_t pid = fork(); if(pid == 0) { for(int i = 0; i < 5; i++) { printf("child process\n"); sleep(1); } } else if(pid > 0) { printf("parent process\n"); sleep(2); printf("kill child process now\n"); kill(pid, SIGINT); }
return 0; }
|
alarm 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
#include <stdio.h> #include <unistd.h>
int main() {
int seconds = alarm(5); printf("seconds = %d \n", seconds);
sleep(2);
seconds = alarm(2); printf("seconds = %d \n", seconds);
while(1) {
}
return 0; }
|
计算机一秒中电脑能数多少个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
#include <stdio.h> #include <unistd.h>
int main() { alarm(1);
int i = 0; while(1) { printf("%d\n", i++); } return 0; }
|
建议 g++ xxx.c ./a.out >> a.txt
setitimer 定时器函数 - 周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
|
#include <stdio.h> #include <sys/time.h> #include <stdlib.h>
int main() {
struct itimerval new_value; new_value.it_interval.tv_sec = 2; new_value.it_interval.tv_usec = 0;
new_value.it_value.tv_sec = 3; new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &new_value, NULL); printf("定时器开始了!\n");
if(ret == -1) { perror("setitimer"); exit(-1); }
getchar();
return 0; }
|
signal 信号捕捉函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
#include <stdio.h> #include <sys/time.h> #include <stdlib.h> #include <signal.h>
void myalarm(int num) { printf("捕捉到了信号的编号是: %d \n", num); printf("xxxxxx\n"); return ; }
int main() {
signal(SIGALRM, myalarm);
struct itimerval new_value; new_value.it_interval.tv_sec = 2; new_value.it_interval.tv_usec = 0;
new_value.it_value.tv_sec = 3; new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &new_value, NULL); printf("定时器开始了!\n");
if(ret == -1) { perror("setitimer"); exit(-1); }
getchar();
return 0; }
|
信号集
许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为 信号集的数据结构来表示,其系统数据类型为 sigset_t。
在 PCB 中有两个非常重要的信号集。一个称之为 “阻塞信号集” ,另一个称之为 “未决信号集” 。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我 们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数 来对 PCB 中的这两个信号集进行修改。
信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间。
信号的 “阻塞” 是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号, 所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。
阻塞信号集和未决信号
未决信号集:信号没被处理之前都会在未决信号集
阻塞信号集:我们需要对某几个信号进行阻塞

阻塞信号集和未决信号集
1.用户通过键盘 Ctrl + C, 产生2号信号SIGINT (信号被创建)
2.信号产生但是没有被处理 (未决)
- 在内核中将所有的没有被处理的信号存储在一个集合中 (未决信号集)
- SIGINT信号状态被存储在第二个标志位上
- 这个标志位的值为0, 说明信号不是未决状态
- 这个标志位的值为1, 说明信号处于未决状态
3.这个未决状态的信号,需要被处理,处理之前需要和另一个信号集(阻塞信号集),进行比较
- 阻塞信号集默认不阻塞任何的信号
- 如果想要阻塞某些信号需要用户调用系统的API
4.在处理的时候和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了
- 如果没有阻塞,这个信号就被处理
- 如果阻塞了,这个信号就继续处于未决状态,直到阻塞解除,这个信号就被处理
号集相关的函数 - 只对自定义的有效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| 以下信号集相关的函数都是对自定义的信号集进行操作 int sigemptyset(sigset_t *set); - 功能: 清空信号集中的数据, 将信号集中的所有的标志位置 清空 - 0 - 参数: set,传出参数,需要操作的信号集 - 返回值: - 成功返回 0 - 失败返回 -1 int sigfillset(sigset_t *set); - 功能: 将信号集中的所有的标志位置为 1 - 参数: set,传出参数,需要操作的信号集 - 返回值: - 成功返回 0 - 失败返回 -1 int sigaddset(sigset_t *set, int signum); - 功能: 向信号集中的某一个信号对应标志位为1,表示阻塞这个信号 - 参数: - set: 传出参数,需要操作的信号集 - signum: 需要设置阻塞的那个信号 - 返回值: - 成功返回 0 - 失败返回 -1 int sigdelset(sigset_t *set, int signum); - 功能: 向信号集中的某一个信号对应标志位为0,表示不阻塞这个信号 - 参数: - set: 传出参数,需要操作的信号集 - signum: 需要设置不阻塞的那个信号 - 返回值: - 成功返回 0 - 失败返回 -1 int sigismember(const sigset_t *set, int signum); - 功能: 判断某个信号是否阻塞 - 参数: - set: 需要操作的信号集 - signum: 需要判断的那个信号 - 返回值: - 1 signum 被阻塞 - 0 signum 不阻塞 - -1 signum 调用失败
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); int sigpending(sigset_t *set);
|
sigset - 设置未决信号集、阻塞信号集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
#include <stdio.h> #include <signal.h>
int main() {
sigset_t set;
sigemptyset(&set);
int ret = sigismember(&set, SIGINT); if(ret == 0) { printf("SIGINT 不阻塞\n"); } else if(ret == 1) { printf("SIGINT 阻塞\n"); } else if(ret == -1) { printf("调用失败\n"); }
sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT);
ret = sigismember(&set, SIGINT); if(ret == 0) { printf("SIGINT 不阻塞\n"); } else if(ret == 1) { printf("SIGINT 阻塞\n"); } else if(ret == -1) { printf("调用失败\n"); }
ret = sigismember(&set, SIGQUIT); if(ret == 0) { printf("SIGQUIT 不阻塞\n"); } else if(ret == 1) { printf("SIGQUIT 阻塞\n"); } else if(ret == -1) { printf("调用失败\n"); }
sigdelset(&set, SIGQUIT); ret = sigismember(&set, SIGQUIT); if(ret == 0) { printf("SIGQUIT 不阻塞\n"); } else if(ret == 1) { printf("SIGQUIT 阻塞\n"); } else if(ret == -1) { printf("调用失败\n"); }
return 0; }
|
sigprocmask - 阻塞信号集 - 内核

所有的常规信号 - 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
|
#include <stdio.h> #include <signal.h> #include <stdlib.h> #include <unistd.h>
int main() {
sigset_t set; sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT);
sigprocmask(SIG_BLOCK, &set, NULL); int cnt = 0; while(1) { cnt++; sigset_t pendingset; sigemptyset(&pendingset); sigpending(&pendingset); for(int i = 1; i <= 31; i++) { if(sigismember(&pendingset, i) == 1) { printf("1"); }else if(sigismember(&pendingset, i) == 0) { printf("0"); } else { printf("sigismember"); exit(0); } } printf("\n"); sleep(1); if(cnt == 10) { sigprocmask(SIG_UNBLOCK, &set, NULL); } }
return 0; }
|
前台进程和后台进程
run sigprocmask.c 直接运行程序
CTRL + z 也只是让程序后台运行
run sigprocmask.c & // 让程序在后台运行
fg // 切换到前台
sigaction信号捕捉
尽量使用 sigaction
因为标准不一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
|
#include <stdio.h> #include <sys/time.h> #include <signal.h> #include <stdlib.h>
void myalarm(int num) { printf("捕获的信号的编号是: %d \n", num); printf("xxxxxxx\n"); return ; }
int main() {
struct sigaction act; act.sa_flags = 0; act.sa_handler = myalarm;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, NULL);
struct itimerval new_value;
new_value.it_interval.tv_sec = 2; new_value.it_interval.tv_usec = 0;
new_value.it_value.tv_sec = 3; new_value.it_value.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &new_value, NULL); printf("定时器开始了\n");
if(ret == -1) { perror("setitimer"); exit(-1); } while(1); return 0; }
|
内核实现信号捕捉的过程

SIGCHLD - 解决僵尸进程
SIGCHLD信号产生的条件
- 子进程终止时
- 子进程接收到 SIGSTOP 信号停止时
- 子进程处在停止态,接受到SIGCONT后唤醒时
以上三种条件都会给父进程发送 SIGCHLD 信号, 父进程默认会忽略该信号
创建20个子进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int main() {
pid_t pid; for(int i = 0; i < 20; i++) { pid = fork(); if(pid == 0) { break; } }
if(pid > 0) { while(1) { printf("parent process pid : %d \n", getpid()); sleep(2); } } else if(pid == 0) { printf("child process pid : %d \n", getpid()); }
return 0; }
|
父进程结束子进程就死了 wait、waitpid 一次只能回收一个子进程
点我带你去看wait - waitpid

段错误(核心内存以转换) - 代码清除僵尸进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
|
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h>
void myFun(int num) { printf("捕捉到的信号 : %d\n", num);
while(1) { int ret = waitpid(-1, NULL, WNOHANG); if(ret > 0) { printf("child die , pid = %d\n", ret); } else if(ret == 0) { break; } else if(ret == -1) { break; } } return ; }
int main() {
pid_t pid; for(int i = 0; i < 20; i++) { pid = fork(); if(pid == 0) { break; } }
if(pid > 0) {
struct sigaction act; act.sa_flags = 0; act.sa_handler = myFun; sigemptyset(&act.sa_mask); sigaction(SIGCHLD, &act, NULL);
while(1) { printf("parent process pid : %d \n", getpid()); sleep(2); } } else if(pid == 0) { printf("child process pid : %d \n", getpid()); }
return 0; }
|

sigaction(SIGCHLD, &act, NULL); 信号捕捉
信号捕捉还没有去注册成功 子进程就结束了
完了再去信号捕捉 就不能回收子进程了
回收改进
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
|
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h>
void myFun(int num) { printf("捕捉到的信号 : %d\n", num);
while(1) { int ret = waitpid(-1, NULL, WNOHANG); if(ret > 0) { printf("child die , pid = %d\n", ret); } else if(ret == 0) { break; } else if(ret == -1) { break; } } return ; }
int main() { sigset_t set; sigemptyset(&set); sigaddset(&set, SIGCHLD); sigprocmask(SIG_BLOCK, &set, NULL);
pid_t pid; for(int i = 0; i < 20; i++) { pid = fork(); if(pid == 0) { break; } }
if(pid > 0) {
struct sigaction act; act.sa_flags = 0; act.sa_handler = myFun; sigemptyset(&act.sa_mask); sigaction(SIGCHLD, &act, NULL);
sigprocmask(SIG_UNBLOCK, &set, NULL);
while(1) { printf("parent process pid : %d \n", getpid()); sleep(2); } } else if(pid == 0) { printf("child process pid : %d \n", getpid()); }
return 0; }
|
