上节回顾:我们讨论了结构体直接封包的对齐与填充隐患,强调了跨平台/跨编译器环境下,结构体内存布局不应直接作为网络协议字节流。正确做法是逐字段序列化与字节序转换。
1. 主题原理与细节逐步讲解
1.1 信号的基本机制
- **信号(signal)**是UNIX/Linux下实现异步事件通知的一种机制。例如:
SIGINT(Ctrl+C)、SIGCHLD(子进程退出)、SIGALRM(定时器超时)。 - 信号通过内核向进程(或线程)发送异步通知,进程可以注册信号处理函数(handler)来响应。
1.2 信号的状态
- 未决(pending):信号已产生但尚未被内核递送到进程。
- 屏蔽(blocked):进程临时阻止某些信号递送(通过信号掩码)。
- 递送(delivered):信号被内核传递,进程执行信号处理函数。
1.3 信号丢失的经典场景
- 非实时信号(传统信号,编号1~31):同类信号不会排队(pending位只占1bit),如果相同信号多次发生且未及时处理,只有一次会被递送,多余的会“丢失”。
- 实时信号(编号大于等于SIGRTMIN):支持排队,不会丢失。
2. 相关C语言典型陷阱/缺陷说明及成因剖析
2.1 信号丢失的成因
- 信号屏蔽期间/处理未及时:如果信号已到来但被屏蔽,解除屏蔽后只会处理一次,多次发生只保留一个pending位。
- 信号处理函数执行期间再次收到同类信号:同样只会递送一次,多余的被丢弃。
2.2 典型误用
- 在高并发场景下依赖信号计数(如用SIGUSR1通知事件发生次数),非实时信号会导致计数不准。
- 在信号处理函数内未恰当处理可重入性问题,或未及时解除屏蔽,导致信号丢失。
3. 规避方法与最佳设计实践
3.1 避免用非实时信号计数
- 如果需要“每次信号都响应”,必须用实时信号(
SIGRTMIN及以上),或用其他IPC机制(如管道、事件fd)。
3.2 正确设置信号掩码
- 用
sigprocmask、sigaction的sa_mask精确控制信号屏蔽区间,避免不必要的阻塞。 - 尽量缩短信号处理函数时间,快速处理或设置标志,主循环再处理实际逻辑。
3.3 信号安全的事件通知方式
- 信号仅用于“轻量级通知”,实际信息或计数建议通过原子变量、管道、信号量等IPC机制传递。
4. 典型错误代码与优化后正确代码对比
错误示例:依赖信号个数计数(信号丢失)
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
volatile int counter = 0;
void handler(int signo) {
counter++; // 多次SIGUSR1同时到来,counter可能小于实际次数
}
int main() {
signal(SIGUSR1, handler);
while (1) {
pause();
printf("counter: %d\n", counter);
}
}
问题:如果在pause期间收到多次SIGUSR1,只有一次被递送,counter不准确。
正确示例:用实时信号或主进程主动轮询
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
volatile sig_atomic_t flag = 0;
void handler(int signo) {
flag = 1; // 只设置标志
}
int main() {
struct sigaction sa = {0};
sa.sa_handler = handler;
sigaction(SIGUSR1, &sa, NULL);
while (1) {
if (flag) {
// 主循环处理实际业务
printf("SIGUSR1 received!\n");
flag = 0;
}
// 其他业务处理
}
}
解释:只作为“通知”,由主循环处理计数/业务。
用实时信号避免丢失
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handler(int signo, siginfo_t *info, void *ucontext) {
printf("SIGRTMIN+1 received: %d\n", signo);
}
int main() {
struct sigaction sa = {0};
sa.sa_sigaction = handler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGRTMIN+1, &sa, NULL);
// 发送多次
for (int i = 0; i < 5; ++i) {
kill(getpid(), SIGRTMIN+1);
}
sleep(1);
return 0;
}
输出:会收到5次信号,不会丢失。
5. 底层原理补充
- 非实时信号pending只占1bit:同类信号同时到达时只记录“有”或“没有”,不计次数,所以会丢失。
- 实时信号维护队列:即使同类信号多次发生,也会全部递送。
- 信号屏蔽:进程通过信号掩码,可以临时阻断信号递送,未决信号解除屏蔽后才递送,但依然只递送一次。
6. SVG辅助图:非实时信号丢失原理

7. 总结与实际建议
- 非实时信号不会排队,同类信号多次发生只递送一次,多余的会丢失。
- 信号屏蔽和信号处理未及时,会加剧信号丢失。
- 用信号做计数或高频通知极易出错,建议用实时信号或IPC机制。
- 信号仅作“通知”用途,实际业务处理应放在主循环或安全的IPC通道。
正确理解信号的递送、屏蔽和丢失机制,是编写健壮可靠系统级C程序的基础。信号设计只做“轻通知”,不要依赖其计数和频度!
公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。
个人博客 | blog.boringhex.top
1011

被折叠的 条评论
为什么被折叠?



