本页目录
信号处理用户态
覆盖: sigaction → sigframe (用户栈) → SA_RESTART → signal stack (sigaltstack) → signalfd → timerfd → real-time signals 适用: glibc + Linux, 用户态视角 (内核部分见 kernel 文档)
概述
信号是 Unix 最古老但仍在活跃使用的 IPC/通知机制。用户态编程中信号的关键问题不是"怎么发"(kill 系统调用),而是"信号什么时候被递送、handler 执行时栈是怎么样的、handler 返回后程序从哪里继续"。本文聚焦用户态信号 handler 的执行模型。
sigaction: 安装信号 handler
struct sigaction sa = ;
;
;
// sa_flags 关键位:
// SA_SIGINFO: 使用 3-参数 handler (获得 siginfo + ucontext)
// SA_RESTART: 被打断的 syscall 自动重试
// SA_NODEFER: handler 执行时不自动 block 本信号
// SA_RESETHAND: 一次性 handler (执行后恢复 SIG_DFL)
// SA_ONSTACK: 使用 sigaltstack (独立信号栈)
Signal Frame: 信号 handler 的"幽灵栈"
// 信号递送时,内核在用户栈上创建 sigframe:
//
// 正常栈 (往下增长):
// [user data] ← 原来 RSP
// [sigframe] ← 内核构造的"快照"
// ├─ 保存的寄存器: RIP, RSP, RFLAGS, 通用 regs
// ├─ siginfo_t (si_signo, si_code, si_addr, ...)
// └─ FPU state (XSAVE)
// ← 新的 RSP (信号 handler 从这里开始)
// 信号 handler 返回:
// sigreturn() → 从 sigframe 恢复所有寄存器 → 程序继续
关键: 信号 handler 跑在用户栈上(不是内核栈)——它是普通的用户态函数。只是内核在调用它之前保存了整个寄存器上下文,handler 返回后才恢复。
信号与系统调用
// 系统调用在"慢"操作 (read from pipe/socket) 中被信号打断:
ssize_t ret;
do while ; // 手动重试
// 或者: SA_RESTART → 内核自动重试 (但不是所有 syscall 都可重启)
// 可重启: read/write/wait/pause/select/poll/...
// 不可重启: sleep (返回剩余时间), connect (返回错误), accept
// pselect / ppoll / epoll_pwait:
// 原子操作: unblock signal → wait → block signal
// 避免 race: signal between unblock and wait
sigaltstack: 独立信号栈
// 问题: 栈溢出 → SIGSEGV → 信号 handler 需要在栈上运行 → 但栈已满!
// 解决: 给信号 handler 分配一个独立的栈 (sigaltstack)
stack_t ss = ;
;
struct sigaction sa = ;
;
// 用途:
// - SIGSEGV handler (安全记录 crash 信息)
// - Go runtime: goroutine 抢占 (SIGURG + 独立信号栈)
// - Profiling: SIGPROF handler
signalfd: 信号变 fd
// 传统信号的问题: 异步 → handler 只能调用 async-signal-safe 函数
// signalfd: 把信号当成 fd 读 (同步) → 可 poll/epoll
sigset_t mask;
;
;
;
; // 先 block (否则默认行为)
int sfd = ;
// event loop:
struct signalfd_siginfo fdsi;
;
;
// 与 epoll 配合 → 所有事件统一处理
timerfd: 定时器变 fd
int tfd = ;
struct itimerspec its = ;
;
// 每次到期 → fd 可读 → read 返回 1 (到期次数)
uint64_t expirations;
;
实时信号 (SIGRTMIN ~ SIGRTMAX)
POSIX 实时信号 vs 传统信号:
传统 (SIGINT, SIGTERM, ...): 多次发送 → 可能合并为一次递送 (排队=1)
实时信号 (SIGRTMIN+): 多次发送 → 按顺序排队 → 不会丢失
实时信号可以携带额外数据:
union sigval value;
value.sival_int = 42;
sigqueue(target_pid, SIGRTMIN, value);
调试
# 进程的信号状态
|
# SigBlk: blocked, SigIgn: ignored, SigCgt: caught (有 handler)
# SigPnd: pending per-thread, ShdPnd: pending per-process
# 发信号
# strace 看信号
参考
- man: sigaction(2), sigaltstack(2), signalfd(2), timerfd_create(2)
- 源码: glibc
signal/sigaction.c,sysdeps/unix/sysv/linux/signalfd.c - LWN: "Signalfd and timerfd", "POSIX real-time signals"
关键词: sigaction, sigframe, SA_RESTART, sigaltstack, SA_ONSTACK, signalfd, timerfd, real-time signals, SIGEV_THREAD