本页目录
信号与 ptrace
覆盖: 信号生命周期 (生成→递送→处理) → signal_struct/sighand_struct → 可靠/不可靠信号 → ptrace 机制 (PTRACE_ATTACH/PTRACE_SYSCALL/PTRACE_PEEKDATA) → seccomp 互斥 内核版本: 2.6 ~ 6.x
概述
信号是 Unix 最古老的 IPC 机制之一,也是一种统一的异常/事件通知框架。从用户敲 Ctrl-C 产生的 SIGINT,到除零触发的 SIGFPE,到子进程退出时的 SIGCHLD,都走同一套内核路径。
ptrace 则是调试器(gdb、strace)和沙箱(seccomp user notification)的基础系统调用,允许一个进程观察和控制另一个进程的执行。
信号数据结构
信号集 (sigset_t)
// include/linux/signal_types.h
// 信号集是一个位图,每个 bit 代表一个信号 (1~64)
typedef struct sigset_t;
// 常用操作
; // 清零
; // 添加
; // 删除
; // 测试
每进程的信号结构
// include/linux/sched.h
;
// include/linux/sched/signal.h
;
;
// include/linux/sched/signal.h
;
sigaction — 信号处理动作
// include/linux/signal_types.h
;
// sa_flags 关键位:
// SA_NOCLDSTOP: 不接收 SIGCHLD when child stops
// SA_NOCLDWAIT: child 退出自动回收,不产生僵尸
// SA_SIGINFO: 使用三参数 handler (sig, siginfo, context)
// SA_RESTART: 被信号打断的 syscall 自动重启
// SA_NODEFER: 处理信号时不自动 block 该信号
// SA_RESETHAND: 处理一次后恢复 SIG_DFL (一次性)
信号生命周期
生成 (generate) → 挂起 (pending) → 递送 (deliver) → 处理 (handle)
1. 生成: send_signal()
// kernel/signal.c
int
2. 不可靠信号 vs 可靠信号
传统信号 (1~31, "不可靠"):
- 同一个信号多次发送 → pending 位图只记录 1 次
- 如果内核发现位图已设 → 新的发送被丢弃
- 经典问题: SIGCHLD 多个子进程同时退出 → 只收到一个通知
实时信号 (SIGRTMIN=34 ~ SIGRTMAX=64, "可靠"):
- 每次发送都分配一个 sigqueue 排队
- 可以携带额外数据 (siginfo.si_value)
- 信号按编号顺序递送 (低编号优先)
- 同编号信号先进先出
3. 递送: get_signal() → handle_signal()
// arch/x86/kernel/signal.c
// 返回用户态之前 (exit_to_user_mode_loop) 调用:
bool
// 如果 handler != SIG_DFL/SIG_IGN:
static void
4. 信号返回: sigreturn()
// 信号 handler 执行完后,libc 调用 sigreturn()
// → sys_rt_sigreturn() → restore_sigcontext()
// 从 sigframe 恢复所有保存的寄存器
// → 进程从被信号打断的地方继续执行
sigframe 是信号机制的精髓——内核在用户栈上伪造了一个"快照",handler 跑完后 sigreturn 把快照恢复,进程仿佛什么也没发生过(除了 handler 的副作用)。
5. 信号与系统调用
系统调用被信号打断的三种处理:
SA_RESTART: 内核自动重试 (仅适用于 read/write/wait/... 等"慢"系统调用)
无 SA_RESTART: 返回 -EINTR,由用户空间决定是否重试
致命信号: 进程退出,不返回
ERESTARTSYS / ERESTARTNOINTR / ... :
内核内部使用的特殊错误码,在返回用户态之前被转换成:
ERESTARTSYS → SA_RESTART → 重试 / 否则 → EINTR
ERESTARTNOHAND → 阻塞了但无 handler → 重试
ERESTART_RESTARTBLOCK → 需要 restart_block 机制 (如 nanosleep)
SIGKILL 与 SIGSTOP 的不可覆盖性
// kernel/signal.c
// SIGKILL (9) 和 SIGSTOP (19) 不能被:
// - block (sigprocmask 会忽略它们)
// - ignore (SIG_IGN 被忽略)
// - 被 handler 捕获 (sa_handler 被忽略)
//
// 这是设计原则: 系统永远有最后手段恢复控制
// 即使进程把所有信号都 block 了,SIGKILL 仍然生效
// 例外: 处在 TASK_UNINTERRUPTIBLE (D 状态) 的进程
// 如果进程卡在 D 状态(如等磁盘 IO),SIGKILL 无法立即终止
// 必须等 IO 完成 → 进程回到可运行状态 → 信号递送生效
// TASK_KILLABLE (5.14+) 是改进: 允许被 SIGKILL 唤醒
ptrace
ptrace 允许一个 tracer 进程控制 tracee 进程的执行。gdb(断点/单步)、strace(syscall 跟踪)、seccomp user notification 都是基于 ptrace 构建的。
核心操作
// kernel/ptrace.c
long ;
// 关键请求:
PTRACE_ATTACH / PTRACE_SEIZE // 附加到目标进程
PTRACE_DETACH // 分离
PTRACE_SYSCALL // 在每次 syscall entry/exit 停止
PTRACE_SINGLESTEP // 单步执行一条指令
PTRACE_CONT // 继续执行
PTRACE_PEEKDATA / PTRACE_POKEDATA // 读写 tracee 内存
PTRACE_GETREGS / PTRACE_SETREGS // 读写寄存器
PTRACE_O_TRACESYSGOOD // 标记 syscall-stop 以便区分 (bit 7 of sig)
PTRACE_O_TRACEFORK / PTRACE_O_TRACEVFORK / PTRACE_O_TRACECLONE
PTRACE_GET_SYSCALL_INFO // 获取详细 syscall 信息 (5.3+)
内部机制
// 附加流程:
→
→ 设 task->ptrace = PT_PTRACED
→ 设 task->parent =
→ 发 SIGSTOP 给 tracee
→ tracee 进入 TASK_TRACED 状态
→ tracer 可以 获得停止通知
// 信号拦截:
// 当 tracee 收到信号,内核检查 task->ptrace:
// → PT_PTRACED: 信号先发给 tracer (tracer 可以修改或消费)
// → tracer 通过 waitpid() 获得信号信息
// → tracer 决定让信号通过 (PTRACE_CONT) 或忽略
// 系统调用跟踪:
// PTRACE_SYSCALL → 设置 TIF_SYSCALL_TRACE
// → syscall entry: syscall_trace_enter() → ptrace_report_syscall()
// → tracee 进入 TASK_TRACED,tracer 通过 waitpid 获得通知
// → tracer 返回 → tracee 执行 syscall 本体
// → syscall exit: syscall_trace_exit() → 再次 stop
// → tracer 可以看到 syscall 的返回值
ptrace 访问模式
// PTRACE_PEEKDATA: 通过 /proc/<pid>/mem 的底层接口
// 单次读一个 word (ptrace 传统 API, 效率低)
//
// 更高效的方式: /proc/<pid>/mem 直接读写 (不需要 ptrace attach!)
// → process_vm_readv() / process_vm_writev()
// → 但只能读写,不能控制执行
// 内核实现: ptrace_access_vm() → get_user_pages_remote()
// → 通过页表将 tracee 的虚拟地址映射到自己的地址空间
// → 直接内存访问 (不需要 copy_from_user)
ptrace 与 seccomp 的互斥
seccomp (SECCOMP_SET_MODE_FILTER):
一个进程要么被 ptrace,要么装 seccomp filter,不能两者兼有
原因: 安全 — seccomp 的假设是"只有自己控制自己的 syscall 策略"
如果被 ptrace,tracer 可以通过 POKEDATA 绕过 seccomp
SECCOMP_RET_USER_NOTIF (seccomp user notification):
这是"seccomp 框架下的 ptrace 替代"
→ 允许一个监督进程检查/批准被沙箱进程的 syscall
→ 不设 PT_PTRACED,可以与 seccomp 过滤器共存
→ 比 ptrace 轻量: 只拦截 syscall,不拦截信号
调试与观测
# 进程当前 blocked / pending / ignored 信号
|
# SigBlk: 被阻塞的信号
# SigIgn: 被忽略的信号
# SigCgt: 被捕获的信号 (有 handler)
# SigPnd: 每线程 pending
# ShdPnd: 线程组共享 pending
# 查看谁的信号 pending 最多 (可能被信号轰炸)
for; do
pend=
[ &&
done
# strace 看信号递送
# 发信号并看内核 trace
参考与延伸
- 内核文档:
Documentation/process/adding-syscalls.rst(信号与 syscall 交互), man 7 signal, man 2 ptrace - LWN:
- "A new API for signal handling" (lwn.net/Articles/414618/)
- "Seccomp user notification" (lwn.net/Articles/785746/)
- 源码文件:
kernel/signal.c— send_signal(), get_signal(), do_signal()kernel/ptrace.c— ptrace_attach(), ptrace_resume()arch/x86/kernel/signal.c— handle_signal(), setup_rt_frame()arch/x86/kernel/ptrace.c— 寄存器读写
关键词: sigaction, sigpending, SIGKILL, SA_RESTART, TIF_SIGPENDING, sigframe, rt_sigreturn, PTRACE_SYSCALL, TASK_TRACED, seccomp