本文约3500字,今天继续根据《一份靠谱的Linux终端产品应用层软件架构学习计划》这篇帖子中的计划,来梳理Linux 信号与 timerfd 定时器相关的知识体系。
关注公众号, 即可获得与Linux相关的电子书籍以及常用开发工具,文末有文档清单。
在 Linux 系统编程、高性能后端服务、嵌入式实时系统中,信号(Signal) 是异步事件通知的基石,而 timerfd 则是 Linux 独有的“文件描述符化”定时器方案。二者常用于进程间通知、超时控制、优雅退出及周期性任务调度。
本文将从 原理机制 → API 演进 → 屏蔽控制 → timerfd 实战 → 生产级架构,系统梳理这套知识体系。尤其针对开发中容易踩的坑(如信号中断、可重入性、定时器堆积)做了专项说明,适合系统学习与面试复盘。
信号是 Linux 内核提供的异步软中断机制。当事件发生(如非法内存访问、键盘中断、定时器超时),内核或其它进程向目标进程发送信号,进程在收到信号后会暂停当前执行流,转而执行预设的信号处理函数。
🧠 通俗理解:信号是系统发给进程的“紧急通知”,例如“立即终止”、“暂停运行”、“你访问了非法地址”。
| 异步性 | |
| 抢占性 | |
| 不可靠性(传统信号) | |
| 优先级 |
SIGINT | Ctrl+C | |||
SIGQUIT | Ctrl+\ | |||
SIGKILL | kill -9 | |||
SIGSEGV | ||||
SIGALRM | alarm() | |||
SIGTERM | kill | |||
SIGCHLD |
⚠️ 特别注意:
SIGKILL和SIGSTOP是内核保留的“终极武器”,任何程序都无法捕获或屏蔽。
signal() —— 老旧且不推荐#include<signal.h>typedefvoid(*sighandler_t)(int);sighandler_tsignal(int signum, sighandler_t handler);参数说明:
handler 可取值:SIG_IGN → 忽略该信号SIG_DFL → 恢复系统默认行为❌ 核心缺陷(生产环境不可用):
SIG_DFL,需手动重新注册,存在竞态窗口 | |
✅ 适用场景:仅限教学演示、临时测试脚本,正式项目禁止使用。
sigaction() —— 唯一推荐的生产级 APIsigaction() 是 POSIX 标准中解决 signal() 所有缺陷的替代方案,所有生产项目必须使用此接口。
#include<signal.h>intsigaction(int signum, const struct sigaction *act, struct sigaction *oldact);核心结构体:
structsigaction {void (*sa_handler)(int); // 普通处理函数void (*sa_sigaction)(int, siginfo_t *, void *); // 扩展处理函数(实时信号)sigset_t sa_mask; // 处理期间临时屏蔽的信号集int sa_flags; // 行为控制标志位};关键 sa_flags 配置:
SA_RESTART | read/write/epoll_wait) |
SA_SIGINFO | sa_sigaction,可获取信号详细信息 |
SA_NODEFER |
✅ 核心优势:
sa_mask 灵活控制信号处理期间的屏蔽策略SA_SIGINFO 可获取发送进程 PID、UID、错误地址等signal() | sigaction() | |
|---|---|---|
| 可靠性 | ||
| 信号屏蔽 | sa_mask | |
| 实时信号 | ||
| 系统调用重启 | SA_RESTART 可选 | |
| 扩展信息 | siginfo_t 包含 PID/UID/地址 | |
| 生产推荐 |
信号屏蔽用于临时阻塞信号传递,保护关键代码段(如临界区、事务操作)不被异步信号打断,确保数据一致性。
sigset_t):位图结构,每位代表一个信号。1 = 屏蔽,0 = 开放。SIGKILL、SIGSTOP 永久不可屏蔽。#include<signal.h>intsigemptyset(sigset_t *set); // 全部清 0intsigfillset(sigset_t *set); // 全部置 1intsigaddset(sigset_t *set, int sig); // 添加某个信号intsigdelset(sigset_t *set, int sig); // 删除某个信号intsigismember(constsigset_t *set, int sig); // 测试是否在集合中sigprocmask()intsigprocmask(int how, constsigset_t *set, sigset_t *oldset);how | |
|---|---|
SIG_BLOCK | set 中的信号追加到当前屏蔽字 |
SIG_UNBLOCK | set 中的信号 |
SIG_SETMASK | set覆盖当前屏蔽字 |
sigaction.sa_mask | 仅当前信号处理函数 | ||
sigprocmask() | 整个进程 |
sigset_t new_mask, old_mask;sigemptyset(&new_mask);sigaddset(&new_mask, SIGINT);sigaddset(&new_mask, SIGTERM);// 进入临界区前屏蔽关键信号sigprocmask(SIG_BLOCK, &new_mask, &old_mask);// === 执行不可打断的核心操作 ===update_shared_data();// 退出临界区后恢复原屏蔽字sigprocmask(SIG_SETMASK, &old_mask, NULL);传统的 alarm() + SIGALRM 方案存在精度低、无法循环、中断系统调用、无法与 epoll 协同等问题。而 timerfd(Linux 2.6.25 引入)将定时器事件抽象为文件描述符的可读事件,完美融入 IO 多路复用体系。
timerfd_create() —— 创建定时器#include<sys/timerfd.h>inttimerfd_create(int clockid, int flags);clockid:推荐 CLOCK_MONOTONIC(单调时钟,不受系统时间调整影响)flags:常用 TFD_NONBLOCK(非阻塞) | TFD_CLOEXEC(exec 时自动关闭)timerfd_settime() —— 启动/修改定时参数inttimerfd_settime(int fd, int flags,const struct itimerspec *new_value, struct itimerspec *old_value);结构体定义:
structitimerspec {structtimespecit_value;// 首次超时时间structtimespecit_interval;// 后续循环周期(0 = 单次)};it_value = 0 | 关闭 |
it_interval = 0 | 单次触发alarm) |
it_interval > 0 | 循环触发 |
定时器超时后,timerfd 变为可读,读取 8 字节 uint64_t 获得累计超时次数。
uint64_t expirations;ssize_t s = read(timer_fd, &expirations, sizeof(uint64_t));if (s == sizeof(uint64_t)) {// 处理 expirations 次超时(通常循环一次即可)}⚠️ 关键踩坑:若
read()返回EAGAIN(非阻塞模式)或未读取,timerfd会一直处于可读状态,导致 epoll 死循环触发事件。
alarmSIGALRM | timerfd | |
|---|---|---|
read/write,需 SA_RESTART | ||
| 纳秒级 | ||
| 生产推荐 |
当前主流的 Linux 高性能服务(如 Nginx、Redis 事件模型)均遵循:
epoll 管理所有 IO 事件 + timerfd 负责定时任务 + sigaction 处理退出信号 + sigprocmask 保护临界区
典型应用场景:
sigactionSIGINT/SIGTERM | |
sigprocmask | |
timerfd | |
epoll | signalfd) |
禁止在信号处理函数中调用非可重入函数
malloc / free / printf / std::cout / 锁操作(pthread_mutex_lock)write() / read() / socket 发送 / 设置全局 volatile sig_atomic_t 标志永远使用 sigaction() 代替 signal()
关键业务逻辑必须加信号屏蔽
定时任务优先选用 timerfd
alarm + 信号的组合,避免系统调用被中断的风险必须读取 timerfd 的超时计数值
epoll_wait 忙轮询,CPU 飙高本文核心内容可归纳为 三层能力模型:
| 基础层 | SIGTERM vs SIGKILL 区别 | |
| 实操层 | sigactionsigprocmask 保护临界区 + sa_mask 防嵌套 | |
| 进阶层 |
这套体系是 Linux 系统编程 / 嵌入式 Linux / 后端中间件开发 的核心基本功,吃透它,我们就能稳健解决优雅退出、定时调度、异步事件处理、高并发稳定性等关键问题。
“谢谢你看到这里”嵌入式Linux设备内部跨模块通信方案对比与统一消息架构设计
嵌入式Linux设备内部跨模块通信方案对比与统一消息架构设计
分享读书心得、工作经验,自我成长和生活方式。
希望我的文字能对你有所帮助