一、什么是信号?
Signal:
信号
是 Unix/Linux 中一种经典的:
异步事件通知机制。
它主要用于:
通知进程或线程:某个事件已经发生。
例如:
Ctrl + C
↓
SIGINT
↓
Process
↓
执行默认动作 / Signal Handler
又例如:
非法内存访问
↓
SIGSEGV
↓
Process
或者:
kill -TERM PID
↓
SIGTERM
↓
Process
因此 Signal 的核心不是:
“传输大量数据。”
而是:
异步通知某个事件已经发生。
二、Signal 与其他 IPC 的区别
例如消息队列:
Process A
↓
Message
↓
Process B
重点是:
传递消息数据
共享内存:
Process A
↓
Shared Memory
↑
Process B
重点是:
共享大量数据
而 Signal:
Process A
↓
SIGUSR1
↓
Process B
重点是:
事件通知
因此可以简单记:
Message Queue
→ 传消息
Shared Memory
→ 共享数据
Semaphore
→ 同步 / 互斥
Signal
→ 异步事件通知
三、Linux 中有哪些信号?
可以使用:
kill -l
查看当前系统支持的信号。
常见信号:
SIGHUP
SIGINT
SIGQUIT
SIGILL
SIGABRT
SIGFPE
SIGKILL
SIGSEGV
SIGPIPE
SIGALRM
SIGTERM
SIGCHLD
SIGCONT
SIGSTOP
SIGTSTP
SIGUSR1
SIGUSR2
...
不同 CPU 架构和系统环境中,部分信号编号可能存在差异。
因此:
程序设计中通常应该优先使用
SIGINT、SIGTERM这样的符号名称,而不是死记数字。
四、信号的完整生命周期
一个信号从产生到最终处理,可以概括成:
Signal Generation
信号产生
↓
Signal Pending
信号未决
↓
检查 Signal Mask
↓
是否被 Block?
┌──┴──┐
YES NO
│ │
│ ▼
│ Delivery
│ 信号递达
│ ↓
│ Disposition
│ 信号处理
│
└→ 暂时保持 Pending
因此最重要的几个概念:
Generation
→ 信号产生
Pending
→ 信号已经产生,但尚未完成递达处理
Blocked
→ 当前暂时阻塞该信号递达
Delivery
→ 信号真正递达目标执行流
Disposition
→ 信号递达后如何处理
五、Blocked 和 Ignored 不是一回事
这是原笔记中最需要纠正的一点。
不能写成:
“进程可以阻塞信号、忽略信号,导致信号无法抵达。”
因为:
Blocked
和:
Ignored
是两个完全不同的概念。
六、Blocked:阻塞信号
Blocked 表示:
这个信号暂时不要递达给我。
例如:
SIGINT 产生
↓
SIGINT 当前被 Block
↓
Pending
↓
等待解除 Block
↓
Delivery
因此 Block 通常是:
暂时延迟处理。
不是简单把信号删除。
七、Ignored:忽略信号
Ignored:
SIG_IGN
表示:
进程对这个信号的 disposition 设置为忽略。
即信号到来时:
Signal
↓
SIG_IGN
↓
不执行默认动作
不执行自定义 Handler
所以:
Blocked
→ 暂时不递达
Ignored
→ 处理策略就是忽略
二者不能混淆。
八、信号产生的常见方式
Signal 可以通过很多方式产生。
常见来源可以分为:
① 终端事件
② 硬件异常 / CPU Exception
③ Shell 命令
④ 程序调用系统接口
⑤ Kernel 事件
九、终端产生信号
例如:
Ctrl + C
通常由终端驱动向前台进程组发送:
SIGINT
即:
Ctrl + C
↓
Terminal Driver
↓
SIGINT
↓
Foreground Process Group
注意:
Ctrl+C 本身不是硬件直接向某个进程发送 SIGINT。
更准确地说,是终端输入机制识别控制字符,然后由内核终端驱动向前台进程组产生相应信号。
十、Ctrl + Z
例如:
Ctrl + Z
通常产生:
SIGTSTP
用于暂停前台作业。
例如:
sleep 100
然后:
Ctrl + Z
Shell 可能显示:
Stopped
十一、CPU / 硬件异常产生信号
例如程序非法访问内存:
int *p = NULL;
*p = 100;
可能导致:
Memory Access Fault
↓
Kernel
↓
SIGSEGV
↓
Process
又例如某些算术异常可能产生:
SIGFPE
因此信号也可以是:
Kernel 将同步异常转换成对进程的信号通知。
十二、kill 命令
Shell 中可以:
kill -TERM 1234
向 PID 1234 发送:
SIGTERM
例如:
kill -INT 1234
发送:
SIGINT
十三、kill 默认不是 SIGKILL
这是非常常见的误区。
执行:
kill 1234
默认发送:
SIGTERM
不是:
SIGKILL
只有:
kill -KILL 1234
或者常见写法:
kill -9 1234
才是发送:
SIGKILL
十四、kill() 函数
程序中可以调用:
#include <signal.h>
int kill(pid_t pid, int sig);
例如:
kill(1234, SIGTERM);
表示:
向指定目标发送 SIGTERM。
kill() 不仅能针对单个 PID,在不同 pid 参数形式下还可以作用于:
进程
进程组
调用者所在进程组
满足条件的一组进程
所以它比“给指定进程发送信号”这个描述更广。
十五、raise()
函数:
int raise(int sig);
例如:
raise(SIGUSR1);
用于:
向调用者自身产生指定信号。
可以简单理解:
Current Process / Thread
│
│ raise(SIGUSR1)
▼
SIGUSR1
│
└────→ 自己
十六、alarm()
函数:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
例如:
alarm(5);
表示:
安排一个 alarm,在大约指定秒数之后向当前进程产生 SIGALRM。
所以:
alarm(5)
↓
约 5 秒以后
↓
SIGALRM
↓
Process
十七、alarm() 不等于“程序还能运行几秒”
你原来的描述:
alarm(5) 表示当前程序还能运行 5 秒。
不够准确。
准确来说:
alarm(5)
只是:
安排 5 秒后产生 SIGALRM。
程序最终会不会终止,要看:
SIGALRM 的 disposition
如果没有修改处理方式:
SIGALRM 默认动作
→ Terminate
所以看起来像:
5 秒后程序终止
但如果安装 Handler:
signal(SIGALRM, handler);
那么:
5 秒
↓
SIGALRM
↓
handler()
程序不一定终止。
十八、alarm() 的返回值
例如:
unsigned int left = alarm(5);
返回值并不是:
“新设置的 5 秒倒计时剩余时间。”
而是:
之前已经存在的 alarm 还剩多少秒。
例如:
alarm(10);
sleep(3);
unsigned int left = alarm(5);
第二次调用:
取消 / 替换原来的 10 秒 alarm
返回的 left 大约是:
7
然后重新安排:
新的 5 秒 alarm
十九、alarm(0)
执行:
alarm(0);
表示:
取消当前尚未触发的 alarm。
返回:
之前 alarm 剩余的秒数
所以:
alarm(N)
→ 设置 / 替换 alarm
alarm(0)
→ 取消 alarm
二十、pause()
函数:
int pause(void);
作用:
挂起当前调用线程,直到捕获到一个信号并且对应的信号处理函数返回,或者信号导致进程终止。
可以概念化:
Process
│
│ pause()
▼
Sleep / Wait
│
│ Signal
▼
Handler
│
▼
pause() 被中断
如果信号 Handler 执行后返回:
pause()
返回:
-1
并通常设置:
errno = EINTR
二十一、signal()
可以使用:
#include <signal.h>
signal(SIGINT, handler);
修改进程对某个信号的 disposition。
例如:
void handler(int sig)
{
write(STDOUT_FILENO, "SIGINT\n", 7);
}
int main(void)
{
signal(SIGINT, handler);
while (1)
pause();
}
当:
Ctrl + C
产生 SIGINT 后:
SIGINT
↓
handler()
而不是执行 SIGINT 的默认终止动作。
二十二、信号的三种基本处理方式
一个信号递达以后,通常存在三种 disposition:
① Default Action
② Ignore
③ Catch / Handler
即:
SIG_DFL
→ 默认处理
SIG_IGN
→ 忽略
handler
→ 执行自定义 Signal Handler
二十三、默认动作
不同信号的默认动作不同。
可能包括:
Terminate
→ 终止进程
Core Dump
→ 终止并可能产生 core dump
Stop
→ 停止进程
Continue
→ 恢复进程
Ignore
→ 默认忽略
所以不能认为:
“收到 Signal 就一定终止。”
二十四、SIGKILL 和 SIGSTOP 很特殊
两个特别重要的信号:
SIGKILL
SIGSTOP
不能:
Catch
Block
Ignore
也就是说,程序不能安装 Handler 来“拦住”它们。
例如:
kill -9 PID
发送:
SIGKILL
目标进程无法通过普通 signal handler 拒绝这个信号。
二十五、signal() 与 sigaction()
虽然:
signal()
简单方便,但现代 POSIX 程序通常更推荐:
sigaction()
因为它能够更精确地控制:
Signal Handler
Signal Mask
Flags
Signal Behavior
例如:
struct sigaction sa = {0};
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
因此可以记:
signal()
→ 简单接口
sigaction()
→ 更完整、更可控的 POSIX 信号处理接口
二十六、Signal Set
Linux/POSIX 提供:
sigset_t
表示:
Signal Set(信号集合)。
它可以包含:
SIGINT
SIGTERM
SIGUSR1
SIGUSR2
...
注意:
sigset_t 本身只是一个“信号集合数据结构”。
创建一个集合:
sigset_t set;
此时通常还需要初始化。
二十七、sigemptyset()
函数:
sigemptyset(&set);
作用是:
把
set这个信号集合初始化为空集合。
不是:
“把当前进程的信号集合清空。”
这是原笔记中的重要错误。
即:
sigemptyset(&set)
↓
修改变量 set
并不会直接修改当前进程/线程真正的 Signal Mask。
二十八、sigfillset()
如果:
sigemptyset(&set);
表示:
set = {}
那么:
sigfillset(&set);
表示:
将可表示的信号加入这个集合。
概念上:
set = {很多可表示 Signal}
二十九、sigaddset()
向集合中加入信号:
sigaddset(&set, SIGINT);
例如:
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
得到概念上的:
set = {
SIGINT,
SIGTERM
}
三十、sigdelset()
删除:
sigdelset(&set, SIGINT);
三十一、sigismember()
判断:
sigismember(&set, SIGINT);
可以检查:
SIGINT 是否存在于 set
三十二、Signal Mask
每个执行线程都有:
Signal Mask
也叫:
Blocked Signal Mask
用于决定:
哪些信号暂时不允许递达。
例如:
Signal Mask
SIGINT → Blocked
SIGUSR1 → Blocked
SIGTERM → Unblocked
如果 SIGINT 此时产生:
SIGINT
↓
Blocked?
↓ YES
Pending
等解除阻塞以后才有机会递达。
三十三、sigprocmask()
单线程程序中经常使用:
sigprocmask()
修改调用线程的 Signal Mask。
函数:
int sigprocmask(
int how,
const sigset_t *set,
sigset_t *oldset
);
例如:
sigprocmask(
SIG_BLOCK,
&set,
NULL
);
三十四、SIG_BLOCK
例如:
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(
SIG_BLOCK,
&set,
NULL
);
表示:
将
set中的信号加入当前 Signal Mask。
因此:
SIGINT
→ Blocked
三十五、SIG_UNBLOCK
解除:
sigprocmask(
SIG_UNBLOCK,
&set,
NULL
);
表示:
从 Signal Mask 中移除 set 中指定的信号。
如果 SIGINT 此时已经:
Pending
那么解除阻塞后:
SIGINT
↓
可以 Delivery
三十六、SIG_SETMASK
例如:
sigprocmask(
SIG_SETMASK,
&set,
NULL
);
表示:
直接使用 set 替换当前的 Signal Mask。
因此三种操作:
SIG_BLOCK
→ 添加阻塞项
SIG_UNBLOCK
→ 删除阻塞项
SIG_SETMASK
→ 整体替换 Mask
所以不能简单说:
“sigprocmask 就是把自定义集合替换系统集合。”
只有:
SIG_SETMASK
才是替换。
三十七、查看 Pending Signals
可以使用:
sigpending()
例如:
sigset_t pending;
sigpending(&pending);
然后:
sigismember(
&pending,
SIGINT
);
判断:
SIGINT 是否处于 Pending 状态
三十八、普通信号为什么可能“合并”?
传统标准信号通常:
不排队保存每一次重复发生。
例如:
SIGINT 被 Block
此时连续产生:
SIGINT
SIGINT
SIGINT
SIGINT
SIGINT
Pending 状态可能只是:
SIGINT = Pending
而不是:
SIGINT Pending Count = 5
因此解除阻塞后,通常不会因为期间发生 5 次就保证调用 Handler 5 次。
可以理解:
普通 Signal Pending
更像:
“这个信号至少发生过一次”
而不是:
“这个信号精确发生了 N 次”
三十九、不要简单称现代 Linux 普通信号为“不可靠信号”
历史 Unix 文献中确实存在:
Reliable Signals
Unreliable Signals
这样的术语。
但是在现代 POSIX/Linux 中,更准确的学习方式是区分:
Standard Signals
vs
Real-Time Signals
核心差异之一就是:
Standard Signal
→ 同一种 Pending Signal 通常不排队
Real-Time Signal
→ 支持排队
因此比简单说:
1~31 不可靠
34~64 可靠
更加准确。
四十、Standard Signals
Linux 中传统标准信号包括:
SIGINT
SIGTERM
SIGSEGV
SIGCHLD
SIGUSR1
SIGUSR2
...
它们的重要特点之一:
同一种标准信号在尚未递达期间重复产生时,通常不会为每一次都保存独立队列项。
例如:
Block SIGUSR1
kill -USR1 PID
kill -USR1 PID
kill -USR1 PID
kill -USR1 PID
Unblock SIGUSR1
不能期待 Handler:
一定执行 4 次
四十一、Real-Time Signals
Linux/POSIX 还提供:
Real-Time Signals
范围通常使用:
SIGRTMIN
~
SIGRTMAX
而不是在程序中硬编码:
34
~
64
原因之一是:
实际可用实时信号编号范围可能受到 libc / threading implementation 等因素影响。
因此程序应该:
SIGRTMIN
SIGRTMIN + 1
...
SIGRTMAX
而不是死记固定数字。
四十二、Real-Time Signal 支持排队
例如某个 Real-Time Signal 被阻塞期间产生:
RT Signal
RT Signal
RT Signal
RT Signal
它们可以排队:
Pending Queue
RT Signal
RT Signal
RT Signal
RT Signal
解除阻塞以后,可以按相应规则依次递达。
因此:
Standard Signals
→ 同种信号通常不排队
Real-Time Signals
→ 可以排队
这是最重要的区别之一。
四十三、sigqueue()
实时信号还可以配合:
sigqueue()
发送额外数据。
例如:
union sigval value;
value.sival_int = 100;
sigqueue(
pid,
SIGRTMIN,
value
);
接收端配合:
sigaction
+
SA_SIGINFO
可以取得发送者和附带值等信息。
所以 Real-Time Signal 比普通 Signal 提供了更丰富的排队和信息传递能力。
四十四、Signal Handler 是异步执行的
假设程序正在:
main()
↓
funcA()
↓
funcB()
突然 SIGINT 递达:
funcB()
│
│ Signal
▼
Signal Handler
│
│ return
▼
恢复原来的执行流
因此 Signal Handler:
可能在程序正常执行过程中的某个时刻异步插入执行。
这也是信号编程比较复杂的原因。
四十五、Signal Handler 里不能随便调用所有函数
由于 Handler 是异步执行的,所以:
不是所有 libc 函数都可以安全地在 Signal Handler 中调用。
例如很多复杂库函数不是:
Async-Signal-Safe
因此 Handler 中应该尽量只调用 POSIX 明确保证 async-signal-safe 的函数。
例如常见:
write()
而不要想当然地在 Handler 中大量执行:
printf()
malloc()
free()
等复杂操作。
一个常见设计是:
Signal Handler
↓
只做最少工作
↓
设置 flag / write pipe
↓
正常程序逻辑再处理真正任务
四十六、信号与系统调用 EINTR
假设进程正在阻塞:
read()
此时信号递达并执行 Handler。
某些情况下系统调用可能:
被 Signal 中断
然后返回:
-1
并设置:
errno = EINTR
因此 Linux 系统编程中经常看到:
if (errno == EINTR) {
...
}
sigaction() 中的某些 flags,例如:
SA_RESTART
还会影响部分系统调用在信号之后是否自动重新开始。
四十七、进程和线程中的 Signal
到了多线程程序,信号模型会更加复杂。
因为信号存在:
Process-Directed Signal
Thread-Directed Signal
有些信号针对整个进程产生,然后由符合条件的线程接收。
有些信号则明确针对某个线程。
四十八、每个线程有自己的 Signal Mask
在多线程程序中:
每个线程拥有自己的 Signal Mask。
例如:
Process
Thread A
Mask:
SIGUSR1 Blocked
Thread B
Mask:
SIGUSR1 Unblocked
因此进程级 SIGUSR1 到来时,内核会根据相关规则选择能够接收它的线程。
四十九、pthread_sigmask()
多线程 POSIX 程序中,通常使用:
pthread_sigmask()
来管理线程的 Signal Mask。
例如:
pthread_sigmask(
SIG_BLOCK,
&set,
NULL
);
而不是把多线程场景简单理解为:
整个进程只有一个统一 Signal Mask
五十、同步等待信号
除了安装异步 Handler,还可以:
sigwait()
同步等待指定信号。
例如多线程服务器中一种常见设计:
所有工作线程
↓
Block 某些 Signal
专门的 Signal Thread
↓
sigwait()
↓
集中处理信号
这样可以减少复杂的异步 Signal Handler 逻辑。
五十一、Signal 和条件变量不是一回事
你原笔记最后写:
“线程 A 不满足条件就等待,线程 B 满足条件以后通知 A,这就是条件变量。”
这个概念本身基本方向正确,但它属于:
线程同步
而不是本章 Unix Signal。
条件变量:
pthread_cond_t
通常和:
pthread_mutex_t
配合使用。
例如:
Thread A
│
条件不满足
↓
pthread_cond_wait()
↓
Wait
Thread B
│
修改共享状态
↓
pthread_cond_signal()
↓
Wake Thread A
这里的:
pthread_cond_signal()
虽然函数名也有 signal:
但它和 Unix/Linux 的 SIGINT、SIGTERM、SIGUSR1 等 Signal 完全不是同一种机制。
五十二、Signal 与 Condition Variable 对比
Unix Signal
SIGINT
SIGTERM
SIGUSR1
SIGALRM
...
用途:
进程 / 线程异步事件通知
API:
kill()
raise()
sigaction()
sigprocmask()
sigwait()
...
Condition Variable
pthread_cond_t
用途:
线程之间等待某个共享状态变化
API:
pthread_cond_wait()
pthread_cond_signal()
pthread_cond_broadcast()
一定不要因为:
signal
这个英文单词相同,就把两套机制混在一起。
五十三、条件变量的正确基本模型
假设:
ready = false
Thread A:
lock(mutex)
while (!ready)
pthread_cond_wait(...)
处理数据
unlock(mutex)
Thread B:
lock(mutex)
ready = true
pthread_cond_signal(...)
unlock(mutex)
这里:
Condition Variable
本身不是:
“保存条件”。
真正的条件通常是:
ready
queue_not_empty
buffer_has_space
...
Condition Variable 的作用是:
让线程高效地等待共享状态发生变化,并由其他线程通知等待者重新检查条件。
这应该单独放在线程同步章节学习。
五十四、常用 Signal 总结
SIGINT
→ Ctrl+C 常见产生的中断信号
SIGTERM
→ 请求进程正常终止
→ kill 默认发送
SIGKILL
→ 强制终止
→ 不能 catch / block / ignore
SIGSTOP
→ 强制停止
→ 不能 catch / block / ignore
SIGTSTP
→ Ctrl+Z 常见产生
SIGCONT
→ 继续执行
SIGSEGV
→ 非法内存访问等导致
SIGALRM
→ alarm() 定时到期
SIGCHLD
→ 子进程状态发生相关变化时通知父进程
SIGUSR1
SIGUSR2
→ 用户程序自定义用途
五十五、Signal API 总结
最值得记住:
signal()
→ 简单修改 Signal Disposition
sigaction()
→ 推荐的、更完整的 Signal Handler 配置
kill()
→ 向进程 / 进程组等目标发送 Signal
raise()
→ 给自己产生 Signal
alarm()
→ 定时产生 SIGALRM
pause()
→ 等待 Signal
sigemptyset()
→ 初始化空 Signal Set
sigfillset()
→ 初始化满 Signal Set
sigaddset()
→ 加入 Signal
sigdelset()
→ 删除 Signal
sigismember()
→ 判断 Signal 是否在 Set
sigprocmask()
→ 修改 Signal Mask
sigpending()
→ 查看 Pending Signal
sigwait()
→ 同步等待 Signal
pthread_sigmask()
→ 多线程程序管理线程 Signal Mask
五十六、信号状态完整模型
可以把一个信号理解成:
Signal Generated
│
▼
Is Blocked?
/ \
YES NO
│ │
▼ ▼
Pending Delivery
│ │
│ ▼
│ Disposition
│ / | \
│ / | \
│ ▼ ▼ ▼
│ Default Ignore Handler
│
│
Signal Unblocked
│
└────────────→ Delivery
其中:
Standard Signal
→ 同一种 Pending Signal 通常不会逐次排队
Real-Time Signal
→ 支持排队
五十七、Linux Signal 的整体知识体系
最终可以整理成:
Linux Signal
│
├── Signal Generation
│ ├── Terminal
│ ├── Hardware / CPU Exception
│ ├── Kernel Event
│ ├── kill()
│ ├── raise()
│ └── alarm()
│
├── Signal State
│ ├── Pending
│ ├── Blocked
│ └── Delivered
│
├── Signal Disposition
│ ├── SIG_DFL
│ ├── SIG_IGN
│ └── Handler
│
├── Signal Set
│ ├── sigemptyset()
│ ├── sigfillset()
│ ├── sigaddset()
│ └── sigdelset()
│
├── Signal Mask
│ ├── SIG_BLOCK
│ ├── SIG_UNBLOCK
│ └── SIG_SETMASK
│
├── Standard Signals
│ └── 同种 Pending 通常不排队
│
└── Real-Time Signals
└── 支持排队
五十八、一句话总结
Linux Signal 是一种异步事件通知机制。信号产生后,如果当前被阻塞,则会进入 Pending 状态,待解除阻塞后再递达;信号递达后,根据该信号的 disposition 执行默认动作、忽略或调用自定义 Handler。
sigaction()用于配置更可靠、可控的信号处理方式,sigset_t用于描述信号集合,sigprocmask()/pthread_sigmask()用于管理阻塞掩码。传统 Standard Signal 的同种未决实例通常不会逐次排队,而 Real-Time Signal 支持排队。SIGKILL 和 SIGSTOP 是特殊信号,不能被捕获、阻塞或忽略。
浙公网安备 33010602011771号