一、先区分两大类信号
Linux 里的信号,工程上通常分:
| 类型 | 特点 |
|---|---|
| 异步信号(Asynchronous Signal) | 外部事件产生 |
| 同步信号(Synchronous Signal) | 当前线程执行错误导致 |
这是最重要的分类。
二、什么是异步信号
所谓:异步
意思是:信号什么时候来,和当前线程执行什么代码没直接关系。
典型异步信号
| 信号 | 来源 |
|---|---|
| SIGINT | Ctrl+C |
| SIGTERM | kill pid |
| SIGQUIT | Ctrl+\ |
| SIGHUP | 终端断开 |
| SIGUSR1 | 用户自定义 |
| SIGUSR2 | 用户自定义 |
| SIGCHLD | 子进程退出 |
| SIGALRM | 定时器 |
| SIGPIPE | 管道断开(有争议,半同步) |
特点
这些信号:任何时刻都可能来
所以:
最适合:signal-thread + sigwait()
统一处理。
三、什么是同步信号
同步信号:当前线程执行某条指令时立即产生。
即:错误由当前线程自己造成
典型同步信号
| 信号 | 原因 |
|---|---|
| SIGSEGV | 非法内存访问 |
| SIGBUS | 总线错误 |
| SIGILL | 非法指令 |
| SIGFPE | 除0、浮点异常 |
| SIGTRAP | 调试断点 |
特点
这些信号:必须发给出错线程
因为:CPU 当前执行流已经出错。
四、工程里通常怎么屏蔽信号
标准模型
1)main线程启动后:
先屏蔽一批异步信号
例如:
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGQUIT);
pthread_sigmask(SIG_BLOCK, &set, NULL);
五、为什么 main 线程先 block
因为:
后面创建的新线程:会继承父线程 signal mask
这是 Linux/POSIX 规定。
所以,这样做后:所有 worker thread 默认都 block 这些信号。
避免:信号随机打断工作线程
六、然后创建 signal-thread (信号处理线程,专门处理信号)
例如:pthread_create(&tid, NULL, signal_thread, NULL);
七、signal-thread 干什么
它专门:sigwait()
等待信号。
例如:
void *signal_thread(void *arg)
{
int sig;
while (1)
{
sigwait(&set, &sig);
switch(sig)
{
case SIGINT:
case SIGTERM:
g_stop = 1;
break;
}
}
}
工作线程:
while (!g_stop)
{
工作...
}
然后:
停止接收新任务
保存数据
flush 文件
关闭 socket
回收资源
八、为什么 sigwait 能收到 block 的信号
因为:sigwait() 本质上就是: "同步地取走 pending signal"
所以,信号必须:先 block
否则:内核会直接异步递送 signal。sigwait() 就抢不到了。
九、一般统一屏蔽哪些信号
工程里通常屏蔽:
常见统一管理信号
| 信号 | 用途 |
|---|---|
| SIGINT | Ctrl+C |
| SIGTERM | 优雅退出 |
| SIGHUP | 重载配置 |
| SIGQUIT | 退出+core |
| SIGUSR1 | 用户控制 |
| SIGUSR2 | 用户控制 |
| SIGCHLD | 子进程状态变化 |
| SIGPIPE | 管道断开 |
十、SIGPIPE 为什么常 block
这是服务器开发经典问题。
例如:客户端断开 , 线程:write(socket, ...)
默认:内核发 SIGPIPE
默认动作:终止进程 ,这太危险。
所以服务器通常:
方案1(最常见)
直接忽略:signal(SIGPIPE, SIG_IGN);
方案2
统一 block。
十一、哪些信号通常不 block
同步异常信号
通常:不能靠 sigwait 处理
包括:
| 信号 |
|---|
| SIGSEGV |
| SIGBUS |
| SIGILL |
| SIGFPE |
原因:
这些信号:必须立即打断出错线程
否则, CPU 都无法继续执行。
十二、为什么不能用 signal-thread 处理 SIGSEGV
例如:
线程:
*p = 100;
CPU:
page fault
此时:
当前线程:已经无法继续执行
所以,必须:立即进入 SIGSEGV handler
而不是:排队等 signal-thread
十三、Linux 实际工程经验
通常:
signal-thread 只处理:"进程控制类异步信号"
例如:
| 信号 | 用途 |
|---|---|
| SIGTERM | 退出 |
| SIGINT | Ctrl+C |
| SIGHUP | reload |
| SIGUSR1 | 控制 |
| SIGUSR2 | 控制 |
| SIGCHLD | 子进程管理 |
而同步异常:
通常:
- 不特殊处理
- 或只打印 crash log
- 然后整个进程退出
十四、真正的大型程序会怎么做
例如:
- JVM
- Redis
- Nginx
- PostgreSQL
通常:
异步信号
signal-thread:统一管理
崩溃信号
注册:sigaction(SIGSEGV, ...)
用于:
- backtrace
- dump
- core
- crash log
然后:abort / exit
十五、一个非常重要的总结
异步信号 特点:来自外部事件
工程处理:signal-thread + sigwait
同步信号 特点:当前线程执行错误
工程处理:只能由出错线程处理
通常:记录崩溃信息后退出
十六、一个"工业级口诀"
异步信号:
block + sigwait + signal-thread
同步异常:
fault thread only
SIGSEGV:
crash fast, don't recover 迅速崩溃,不要恢复
这是 Linux/UNIX 世界非常核心的哲学。