Linux信号
使用 kill -l 可查看所有的信号,其中1- 31是普通信号(对于两个以上的相同信号会丢失,因为只有一位位图保存);34 - 64是实时信号(用链表队列形式将实时信号进行保存,可以保存多份)。

Number | Name | Default action | Corresponding event |
---|---|---|---|
1 | SIGHUP | terminate | 终端线挂起 |
2 | SIGINT | terminate | 来自键盘的中断(Ctrl + c) |
3 | SIGQUIT | terminate | 来自键盘的退出(Ctrl + \) |
4 | SIGILL | terminate | 非法指令 |
5 | SIGTRAP | terminate and dump core | 跟踪陷阱 |
6 | SIGABRT | terminate and dump core | 来自abort函数的终止信号 |
7 | SIGBUS | terminate | 总线错误 |
8 | SIGFPE | terminate and dump core | 浮点异常( 计算1 / 0,Floating point exception) |
9 | SIGKILL | terminate* | 杀死进程(无法被signal自定义捕捉,系统级杀死进程) |
10 | SIGUSR1 | terminate | 用户定义的信号1 |
11 | SIGSEGV | terminate and dump core | 无效的存储器引用(野指针、segmentation fault) |
12 | SIGUSR2 | terminate | 用户定义的信号2 |
13 | SIGPIPE | terminate | 向一个没有读用户的管道写操作 |
14 | SIGALRM | terminate | 来自alarm函数的定时信号 |
15 | SIGTERM | terminate | 软件终止信号 |
16 | SIGSTKFLT | terminate | 协处理器上的栈故障 |
17 | SIGCHLD | ignore | 一个子进程暂停或终止 |
18 | SIGCONT | ignore | 继续进程如果该进程停止 |
19 | SIGSTOP | stop until next SIGCONT* | 不来自终端的暂停信号 |
20 | SIGTSTP | stop until next SIGCONT | 来自终端的暂停信号(Ctrl + z) |
21 | SIGTTIN | stop until next SIGCONT | 后台进程从终端读 |
22 | SIGTTOU | stop until next SIGCONT | 后台进程从终端写 |
23 | SIGURG | ignore | 套接字上的紧急情况 |
24 | SIGXCPU | terminate | CPU时间限制超出 |
25 | SIGXFSZ | terminate | 文件大小限制超出 |
26 | SIGVTALRM | terminate | 虚拟定时器期满 |
27 | SIGPROF | terminate | 剖析定时器期满 |
28 | SIGWINCH | ignore | 窗口大小变化 |
29 | SIGIO | terminate | 在某个描述符上可执行I/O操作 |
30 | SIGPWR | terminate | 电源故障 |
# 信号的产生
所有的信号都是由操作系统发送的,因为操作系统会考虑独立和安全
- 键盘按键输入,但是只能用来终止前台进程。
以下程序可以用来查看按键输入的信号对应的number
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void handler(int signo)
{
printf("receive signal: %d\n", signo);
exit(1);
}
int main()
{
for (int i = 1; i <= 31; i++)
{
signal(i, handler);
}
while(1)
{
printf("------------------------------\n");
sleep(1);
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 程序中的异常问题
本质是程序中存在异常问题导致软件或硬件的错误,被操作系统识别后,操作系统向对应的进程发送相应的信号。
Floating point exception(8) \ segmentation fault(11)
异常退出会设置退出码和退出信号,有时也会设置core dump标志位为1(不是所有情况)。
ulimit -a # 查看core的大小,若core file size = 0,则不允许core dump。
ulimit -c 1024 # 设置core的大小,使得运行core dump
gdb main
2
3

core.1331——1331是进程的PID。
(gdb) core-file core.1331 # 使用gdb事后调试,直接查看异常的位置

程序查看异常退出码以及core dump标志位:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int status = 0;
if(fork() == 0)
{
while(1)
{
printf("child PID:%d\n",getppid());
int *p = NULL;
*p = 10;
}
}
waitpid(-1, &status, 0);
printf("exit code: %d , exit signal: %d , core dump : %d\n",
(status>>8)&0xff, status & 0x7f, (status >> 7) & 0x01);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 系统函数产生
int kill(pid_t pid, int sig); //给进程发相应的信号
int raise(int sig); // 给自己发相应的信号
void abort(void); //SIGABRT
2
3
- alarm函数产生
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int ret = alarm(1); // 返回值为剩余的几时数
while(1)
{
printf("hello world\n");
alarm(0); // 取消闹钟
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 信号的存储与执行
进程收到信号后不会立即处理,会将信号以位图的形式存储在进程的PCB中;当进程从内核态返回用户态的时候,进行检测并处理。
用户的数据和代码会被加载到内存,对应的虚拟内存和物理内存的映射叫用户级页表;而操作系统的数据和代码也会被加载到内存,对应的映射关系叫系统级(内核)页表,而这个页面只有一份,所有进程共享一份操作系统的内存。进程可以通过CPU中相应的寄存器切换用户和内核的访问权限
内核态:执行系统的代码和数据,使用内核级页表
用户态:执行用户的代码和数据,使用用户级页表

信号的处理过程分为
- 实际执行——递达:
- 默认动作(SIG_DFL : 0)
- 忽略 ( SIG_IGN : 1)
- 自定义捕捉(handler)
- 产生到递达间——未决:在信号位图中暂存
- 信号阻塞
OS可以使用系统调用接口设置位图的数据结构:sigset_t
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
int main()
{
sigset_t isset, osset;
sigset_t pending;
//清空结构体内容
sigemptyset(&isset);
sigemptyset(&osset);
//设置3号信号的屏蔽字——block位图
sigaddset(&isset, 3);
//设置
sigprocmask(SIG_SETMASK, &isset, &osset);
while(1)
{
printf("hello world\n");
sigemptyset(&pending);
sigpending(&pending); // 读出pending的位图
int count = 0;
int i = 1;
for(; i <= 31; i++)
{
if(sigismember(&pending, i))
{
printf("1");
}
else
{
printf("0");
}
}
printf("\n");
count++;
if(count == 10)
{
sigprocmask(SIG_SETMASK, &osset, NULL); //将旧的信号屏蔽字写回
}
sleep(1);
}
return 0;
}
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
使用ctrl + \ 发现无法终止该程序,原因是3号信号被屏蔽了。另外使用kill -3 + PID也无法杀死该进程,但是会使得对应的pending位图置1。过10秒后,信号屏蔽字被重新置0,进程会默认立即终止,不会再执行后面的语句。
处理器通常在某些控制寄存器中使用模式位来限制应用程序可以执行的指令,以及它可以访问的地址空间部分。在内核模式下运行的进程可以执行指令集中的任何指令,并访问系统中的任何内存位置。未设置模式位时,进程以用户模式运行。用户模式下的进程不允许执行特权指令,这些指令执行诸如暂停处理器、更改模式位或启动I/O操作等操作。也不允许直接引用地址空间内核区域中的代码或数据。进程从用户模式更改为内核模式的唯一方法是通过异常,例如中断、故障或陷阱系统调用。当异常发生时,控制传递给异常处理程序,处理器将模式从用户模式更改为内核模式,处理程序以内核模式运行。当它返回到应用程序代码时,处理器将模式从内核模式更改回用户模式。
信号执行的过程可以总结为:
用户态进入内核态调用系统函数,从内核态返回用户态进行相应的信号检测。若无信号产生直接返回用户态,若产生信号,则执行信号的相应处理过程(默认、忽略、自定义捕捉),信号捕捉在用户态执行,执行完重新返回内核。
信号捕捉的接口函数:
sighandler_t signal(int signum, sighandler_t handler);
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
struct sigaction
{
void (*sa_handler) (int); // 捕捉函数
...
..
sigset_t sa_mask; //设置进程捕捉过程中的屏蔽字
}
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
void handler(int signo)
{
while(1)
{
printf("signal number:%d\n",signo);
sleep(1);
}
}
int main()
{
struct sigaction act;
memset(&act, 0, sizeof(act));
//act.sa_handler = SIG_IGN; //将某一个信号设置成忽略
//act.sa_handler = SIG_DFL; // 将某一个信号设置成默认
act.sa_handler = handler;
//在执行信号自定义捕捉时,系统会设置信号屏蔽字,防止自定义捕捉被调用多次
sigemptyset(&act.sa_mask);
//手动设置信号的屏蔽字,当执行3号信号捕捉时,该2号的信号被屏蔽
sigaddset(&act.sa_mask, 2);
sigaction(3, &act, NULL); // 注册3号信号
return 0;
}
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
# 函数的重入
函数一旦重入,有可能出现问题——函数不可被重入
同一个函数被main和handler调用时,可能会出现问题。
# volatile
编译器的优化
#include <stdio.h>
//int flag = 0; //编译时会直接优化到寄存器当中,而不是从内存中读取。程序进入死循环
volatile int flag = 0; //此时去优化,ctrl+c时可以正常退出
void handler(int signo)
{
flag = 1; //改变的是内存当中的flag,并不会影响寄存器当中的;贯穿读取内存,不读取中间缓存区寄存器的数据,保持内存的可见性
}
int main()
{
signal(2,handler);
while(!flag); //只检测,不会修改
printf("helo");
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gcc -o $@ $^ -03 # 使用优化编译
# SIGCHILD
int main()
{
signal(SIGCHILD,SIG_IGN); //显式设置忽略17号信号,子进程退出后自动释放僵尸进程。
//只在linux下有效
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt--)
{
printf("child\n");
cnt--;
}
exit(0);
}
while(1);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18