【Linux】信号的产生、保存、处理

Linux 中的信号(signal) 是进程间一种轻量级的异步通知机制,常被比喻为“软件中断”。下面按产生 → 保存 → 处理三个阶段给你系统梳理清楚整个流程(基于主流 Linux 内核实现,x86_64 架构为主)。

1. 信号的产生(谁/什么能产生信号)

产生方式典型例子内核函数(大致)是否可靠(可指定目标进程)
硬件异常/陷阱除0、段错误、非法指令、浮点异常do_trap() / do_page_fault()是(当前进程)
终端特殊按键Ctrl+C → SIGINT
Ctrl+\ → SIGQUIT
Ctrl+Z → SIGTSTP
tty 驱动 → send_sig_info()是(前台进程组)
软件主动发送kill(pid, sig)
raise(sig)
pthread_kill()
sys_kill / do_send_sig_info
定时/闹钟alarm()、setitimer()、timer_create()it_real_fn / posix_timer是(当前进程)
内核主动产生子进程退出 → SIGCHLD
OOM killer
do_notify_parent() 等
其他系统事件窗口大小改变 → SIGWINCH
电源事件
相应子系统通常是当前进程或进程组

最常见的产生路径总结为三类:

  1. 当前进程自己作死(硬件异常、alarm)
  2. 别人用 kill() 系列打你
  3. 终端、前台进程组相关的控制信号

2. 信号的保存(内核怎么记住“有信号来了”)

信号到达内核后,并不立即执行处理函数,而是先记录下来。记录的位置在进程的 task_struct(PCB)里,主要有三个关键位图/集合:

字段(内核名字)含义数据结构(现代内核)大小(常见64位系统)备注
pending.signal未决信号集(pending)struct sigpending → sigset_t通常2个 long(128位)还没处理的信号在这里“排队”
blocked被阻塞的信号集sigset_t同上sigprocmask / pthread_sigmask 设置
real_blocked临时阻塞(rt_sigaction用)sigset_t同上较少见

核心逻辑:

  • 信号来了 → 把对应 bit 置1 放入 pending.signal
  • 如果该信号没被阻塞(即 !sigismember(&blocked, sig)),则标记为可递送
  • 如果被阻塞,就只挂在 pending 里等待
  • 同一个普通信号(非实时)多次产生只记录一次(pending bit 只有0/1)
  • 实时信号(SIGRTMIN ~ SIGRTMAX)可以排队,带数据(siginfo),有计数

现代内核(5.x+)常用 sigpending 结构,包含:

struct sigpending {
    struct list_head list;          // 实时信号的 sigqueue 链表
    sigset_t signal;                // 位图(普通+实时信号的第一层)
};

普通实时信号会同时占用位图 + 挂到 list 上排队。

3. 信号的处理(什么时候、在哪里被处理)

信号真正被执行的时机只有一种

进程即将从内核态返回用户态时(最典型的就是系统调用返回、时钟中断返回、中断返回等)

检查流程(do_signal / exit_to_user_mode_loop 大致逻辑):

  1. 当前是否有未决且未阻塞的信号?(pending & ~blocked)
  2. 有 → 选一个优先级最高的信号(普通信号按编号小→大,实时信号按产生顺序)
  3. 根据进程对该信号的处理方式(sighandler_t sa_handler / sa_sigaction)分三种情况:
处理方式sa_handler 值行为是否清 pending bit
默认处理SIG_DFL大部分信号:终止进程
SIGCHLD:忽略
SIGURG等:忽略
忽略SIG_IGN直接丢弃该信号(部分信号不能被忽略,如 SIGKILL/SIGSTOP)
自定义捕捉函数指针保存现场 → 构造 ucontext → 修改栈 → 返回用户态时先执行信号处理函数是(执行完才清)

捕捉函数执行完后,内核会通过特殊的返回路径(通常是 rt_sigreturn 系统调用)恢复上下文,继续执行被打断的位置。

快速记忆口诀版

产生:硬件异常、终端按键、kill家族、定时器、内核事件
保存:task_struct → pending.signal(位图) + sigqueue(实时排队)
       被阻塞 → 只保存不处理
       未阻塞 → 标记待递送
处理时机:从内核返回用户态前一刻
处理方式:默认 / 忽略 / 捕捉 → 三选一
普通信号:只记一次(后来的覆盖)
实时信号:可排队、可带数据

常见面试/笔试高频考察点总结

  1. 信号在内核用什么数据结构保存?(sigset_t + sigpending)
  2. 为什么普通信号不能排队?(节省空间 + 历史原因)
  3. 信号处理函数在什么时候执行?(从系统调用/中断返回用户态时)
  4. SIGKILL 和 SIGSTOP 能被捕捉/忽略/阻塞吗?(不能)
  5. 子进程退出一定会给父进程发 SIGCHLD 吗?(是,但父进程可忽略或阻塞)
  6. 信号处理函数里能调用哪些函数?(async-signal-safe 函数)
  7. sigsuspend、sigwait、sigsuspend 的区别?

需要更深入的某一部分(比如实时信号排队、siginfo、内核函数调用路径、信号栈、SA_SIGINFO、SA_RESTART 等)可以直接告诉我,我继续展开。

文章已创建 4812

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部