1. 生活角度的信号
当你正在看电视的时候,手机铃声突然响了。这就说明有一个事件发生,你收到了一个通知。(信号产生)
即使电话还没响之前,你也知道如何接电话,以及接电话后该怎么办。例如:接听电话、挂断电话、静音不管。也就是说,处理方式在电话来之前就已经确定好了。(预定义处理动作)
电话打进来的事件是不确定的,你无法预测别人什么时候给你打电话。对你而言,这是一个异步事件。(异步机制)
电话响起时,你可能正在做更重要的事件。例如:开会、打游戏等。因此你不会立刻接电话,而是先让电话继续响着。(在合适的时间处理)
从电话响起到你真正接听电话之间,存在一个时间窗口。在这段时间里:电话已经来了;你知道有人给你打电话;但你还没处理。本质是就是:你记录了一个待处理事件。(信号保存)
当你有空之后,开始处理这个电话。常见的处理方式有:1.接听电话(默认处理)2.直接挂断(忽略处理)3.根据来点人采取不同处理方式(自定义捕捉)(信号处理)
基本结论
| 手机来电 | Linux信号 |
|---|---|
| 电话打进来 | 信号产生 |
| 未接电话记录 | 信号保存 |
| 接电话 | 信号处理 |
| 不知道什么时候来电 | 异步事件 |
| 接听/挂断/特殊处理 | 不同信号处理方式 |
补充:
- 进程为什么能识别信号?
Linux 预先定义了信号编号,内核和进程共同遵守这套规则,因此进程能够识别不同信号所表示的含义。
- 进程为什么能知道处理对应的信号方法?
Linux 预先定义了每种信号对应的默认处理方式,同时进程也可以注册自己的处理函数,因此信号到来后能够按照预先设定好的方式进行处理。
信号产生后,不一定会被立即处理,而是在满足特定条件时由进程处理。
同步和异步
同步:某个事件什么时候发生,由当前执行流程主动决定。
异步:某个事件什么时候发生,不由当前执行流程决定,而是由外部决定。
信号异步的原因:进程正在执行自己的逻辑,突然就会收到信号。对于进程而言,信号什么时候到来是无法提前预测的,因此信号本质是一种异步事件通知机制。
理解了信号的异步通知特性之后,接下来我们先认识 Linux 中的信号。
2 Linux 中的信号
2.1 信号的基本概念
信号是 Linux 为进程提供的一种异步事件通知机制,用于告知进程某个特定事件已经发生。
当特定事件发生时,内核会向目标进程发送对应信号,进程收到信号后再按照预先设定好的方式进行处理。进一步解释:
信号本质是一种软件层面的中断机制。与硬件中断通知 CPU 某个事件发生类似,信号用于通知进程某个软件事件已经发生。
信号的基本特征
- 信号是一种通知机制
信号只负责通知事件发生,不负责传递大量数据
- 信号是异步的
进程无法提前预测信号何时到来,信号的产生往往由外部事件决定。
- 信号由内核发送
当特定事件发生时,内核会为目标进程产生并发送对应信号。
- 信号具有预定义含义
每种信号都对应一种特定事件。
- 信号处理方式提前确定
默认处理 忽略 自定义捕捉
在了解信号的基本概念后,接下来先看看 Linux 为我们定义了哪些信号。
2.2 查看信号
Linux 为每种信号都分配了唯一编号和名称,可以通过kill -l查看系统支持的信号列表。

这些信号的名称和编号在 signal.h 头文件中进行了定义。
cpp
/* Signals. */
#define SIGHUP 1 /* Hangup (POSIX). */
#define SIGINT 2 /* Interrupt (ANSI). */
#define SIGQUIT 3 /* Quit (POSIX). */
#define SIGILL 4 /* Illegal instruction (ANSI). */
#define SIGTRAP 5 /* Trace trap (POSIX). */
#define SIGABRT 6 /* Abort (ANSI). */
#define SIGIOT 6 /* IOT trap (4.2 BSD). */
#define SIGBUS 7 /* BUS error (4.2 BSD). */
#define SIGFPE 8 /* Floating-point exception (ANSI). */
#define SIGKILL 9 /* Kill, unblockable (POSIX). */
#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */
#define SIGSEGV 11 /* Segmentation violation (ANSI). */
#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */
#define SIGPIPE 13 /* Broken pipe (POSIX). */
#define SIGALRM 14 /* Alarm clock (POSIX). */
#define SIGTERM 15 /* Termination (ANSI). */
#define SIGSTKFLT 16 /* Stack fault. */
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX). */
#define SIGCONT 18 /* Continue (POSIX). */
#define SIGSTOP 19 /* Stop, unblockable (POSIX). */
#define SIGTSTP 20 /* Keyboard stop (POSIX). */
#define SIGTTIN 21 /* Background read from tty (POSIX). */
#define SIGTTOU 22 /* Background write to tty (POSIX). */
#define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */
#define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */
#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
#define SIGPOLL SIGIO /* Pollable event occurred (System V). */
#define SIGIO 29 /* I/O now possible (4.2 BSD). */
#define SIGPWR 30 /* Power failure restart (System V). */
#define SIGSYS 31 /* Bad system call. */
编号34以上的是实时信号,本文只讨论编号 1 ~ 31 的普通信号,不讨论实时信号。
通过 man 7 signal 可以查看信号的产生条件以及信号的默认处理动作。

Action 列表示信号的默认处理方式:
Term:终止进程
Core:终止进程并生成 Core 文件
Stop:暂停进程
Cont:继续运行进程
Ign :忽略信号
虽然 Linux 定义了很多信号,
但目前不需要全部记住。
先通过一个简单例子观察信号的产生与处理过程。
2.3 一个样例
cpp
#include <iostream>
#include <unistd.h>
int main()
{
while(1)
{
std::cout << "I am process, I am waiting signal!" << std::endl;
sleep(1);
}
return 0;
}

用户输入命令,在Shell下启动一个前台进程。
用户按下 Ctrl + C ,键盘发生硬件中断,被 OS 获取并解释成信号,发送给前台进程。
前台进程收到信号,进行信号处理引起进程退出。
2.4 一个系统调用函数
cpp
NAME
signal - ANSI C signal handling
SYNOPSIS
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数说明:
signum:信号编号
handler:函数指针,表示更改进程对信号的处理动作,当收到对应的信号,就执行 handler 方法
函数功能:更改进程对指定信号的处理动作,使其不调用默认处理,而是调用用户自己写的处理方法(自定义捕捉)
上面的样例,Ctrl + C 的本质是向前台进程发送 SIGINT 信号,即 2 号信号。
验证过程
cpp
#include <iostream>
#include <unistd.h>
#include <signal.h>
void sighander(int signum)
{
std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signum <<std::endl;
}
int main()
{
signal(SIGINT,sighander);
std::cout << "我是进程: " << getpid() << std::endl;
while(1)
{
std::cout << "I am process, I am waiting signal!" << std::endl;
sleep(1);
}
return 0;
}

现象:在键盘中输入 Crtl + C ,只能向前台进程发送信号,不能使其退出了。
原因:用户对 2 号信号进行了捕捉,更改了它的默认处理动作,使得处理方式由用户来确定。
退出方式:1. kill -9 进程pid -> 向目标进程发送 9 号信号 2. Ctrl + \ 向前台进程发送 3 号信号
补充:
signal 函数只是设置了特定信号的处理方式,并不是直接调用处理方式。如果后续特定信号没有产生或者产生但被阻塞,设置的自定义捕捉函数不会被调用。
对于很多信号的默认处理方式都是终止当前进程,终止的方式有两种:Term 和 Core
前台进程:占用终端输入进程,能接收键盘输入(Ctrl + C、Ctrl + \ 、Ctrl + Z),一个终端同一时刻只能有一个前台进程。
cpp./test后台进程:不占用终端输入,在后台运行的进程。
后台进程分为两类:
(一) 作业式后台进程(终端关联后台进程)
cpp./test &进程可以在前台和后台之间进行切换。例如:当我们在命令行运行一个进程时,Shell 会阻塞变成后台进程,直到进程结束,才变为前台进程。
常用命令:
jobs:查看当前终端所有后台作业
fg %作业号:将后台进程拉回前台
bg %作业号:把暂停的前台进程放入后台继续运行
Ctrl + Z:向前台进程发送SIGTSTP(20) 信号,默认处理动作:暂停前台进程。
(二) 守护进程(完全脱离终端的后台进程)---- 网络部分说明
- SIGKILL(9) 和 SIGSTOP(19)信号不可捕捉、不可忽略、不可阻塞
2.5 信号处理
自定义捕捉
cpp
#include <iostream>
#include <unistd.h>
#include <signal.h>
void sighander(int signum)
{
std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signum <<std::endl;
}
int main()
{
signal(SIGINT,sighander);
std::cout << "我是进程: " << getpid() << std::endl;
while(1)
{
std::cout << "I am process, I am waiting signal!" << std::endl;
sleep(1);
}
return 0;
}
默认处理
cpp
#include <iostream>
#include <unistd.h>
#include <signal.h>
void sighander(int signum)
{
std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signum <<std::endl;
}
int main()
{
signal(SIGINT, SIG_DFL);
std::cout << "我是进程: " << getpid() << std::endl;
while(1)
{
std::cout << "I am process, I am waiting signal!" << std::endl;
sleep(1);
}
return 0;
}
忽略
cpp
#include <iostream>
#include <unistd.h>
#include <signal.h>
void sighander(int signum)
{
std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signum <<std::endl;
}
int main()
{
signal(SIGINT, SIG_IGN);
std::cout << "我是进程: " << getpid() << std::endl;
while(1)
{
std::cout << "I am process, I am waiting signal!" << std::endl;
sleep(1);
}
return 0;
}
cpp
#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
/* Type of a signal handler. */
typedef void (*__sighandler_t) (int);
// 其实SIG_DFL和SIG_IGN就是把0,1强转为函数指针类型