背景
fatal_signal_pending(p) 用来判断"这个 task 上有没有致命信号 (主要是 SIGKILL 那类不可忽略的)正在等待处理"。
它常用于长循环、I/O 或复杂内核路径中主动检查 "进程是不是已经要被杀了",好尽早退出、释放资源,配合 TASK_KILLABLE 等等待状态使用。
1. fatal_signal_pending 的函数声明
函数定义大致是:
c
bool fatal_signal_pending(struct task_struct *p);
语义可以理解为:
-
返回 true :
这个 task 的信号队列中有会导致它终止的信号 排队了(比如
SIGKILL,以及经处理后确定为"致命"的信号),通常意味着:再继续干活没有意义,应该尽快 abort / 清理然后返回错误。
-
返回 false :
没有致命信号在排队;
哪怕有
SIGINT、SIGTERM等,但因为被屏蔽/忽略/有 handler,不一定被视为"fatal"。
它和 signal_pending(p) 的区别就是:
signal_pending(p):只要有任何 pending 信号(可忽略、停止、继续等)就返回真;fatal_signal_pending(p):只关心"最终会导致这个 task 结束"的那类信号。
2. 内核里是怎么判断"fatal"的?
-
先看当前 task 是否有 pending 信号(包括线程自己的和整个线程组的);
-
过滤掉一些不会终止的信号(比如
SIGSTOP、SIGCONT、被忽略的可捕获信号等); -
检查是否有:
- 无法屏蔽/忽略的信号,比如:
SIGKILL; - 配合线程组退出(group exit)的标记,例如 OOM killer、
do_group_exit()触发的退出;
- 无法屏蔽/忽略的信号,比如:
-
如果上述条件满足,就认为
fatal_signal_pending(p) == true。
这也是为什么 kill -9(SIGKILL)几乎总能把 TASK_KILLABLE 等等待打断:
因为 SIGKILL 一旦 pending,fatal_signal_pending() 就会变真,后续检查都会"认怂"。
3. 典型使用场景
3.1 长循环/大任务里主动"认怂"
很多内核路径会有类似模式(伪代码):
c
while (还有很多工作要干) {
if (fatal_signal_pending(current)) {
ret = -EINTR; // 或 -ERESTARTSYS / -EAGAIN,看调用语义
goto out;
}
// 干一点活
do_some_work_chunk();
}
例如:
- 大量 page 的扫描;
- 长时间的重试循环;
- 大量 I/O 或校验工作。
这样做的意义:
- 进程被
SIGKILL/某些 fatal 信号打中后,不会一直死撑着做完所有循环; - 减少 OOM / kill 之后资源被长期占用的问题;
- 让上层调用能早点拿到错误码,做收尾或退出。
3.2 配合 TASK_KILLABLE 等等待状态
典型 killable 等待模式:
c
DEFINE_WAIT(wait);
for (;;) {
prepare_to_wait(&wq, &wait, TASK_KILLABLE);
if (条件已经满足)
break;
if (fatal_signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
schedule();
}
finish_wait(&wq, &wait);
__set_current_state(TASK_RUNNING);
关键点:
TASK_KILLABLE的意思:只会对 fatal 信号(尤其是 SIGKILL)响应 ,普通SIGINT/SIGTERM不会打断。- 所以 wakeup 通常是:条件满足 OR
fatal_signal_pending(current)变真。
这样可以构建一种"几乎不可被打断,但还能被 kill -9 强制收尸"的等待:
- 避免被各种用户空间 SIGINT 搞醒;
- OOM killer 或管理员
kill -9时又能把它干掉。
3.3 页回收、IO 等复杂路径
在内存回收、reclaim、写回、文件系统操作这些路径里,经常能看到类似:
c
if (fatal_signal_pending(current))
goto out;
意思是:
- 系统在极端情况下,回收/写回可能很"脏",做很久;
- 如果这个进程已经被 SIGKILL 盯上,就不要再浪费时间帮它做事了。
4. 和 signal_pending / signal_pending_state 的区别与配合
4.1 signal_pending(p)
-
只要有任何未决信号(包括可捕获/非致命的),就返回 true;
-
常用于
TASK_INTERRUPTIBLE的等待,如:cif (signal_pending(current)) return -ERESTARTSYS; // 或 -EINTR
这种等待会被普通信号打断 (SIGINT / SIGTERM / SIGUSR1 等):
适合需要响应 Ctrl+C、用户中断的普通 syscalls。
4.2 signal_pending_state(state, p)
-
综合了 task 当前 state 和 pending 信号类型;
-
对于不同 state 行为不同:
TASK_INTERRUPTIBLE:任何 pending 信号都算;TASK_KILLABLE:通常只看 fatal 信号,内部会用fatal_signal_pending逻辑;
-
是 wait 宏族的底层基础。
例如 wait_event_interruptible() 等宏内部就会用它,区别不同状态对信号的敏感程度。
4.3 fatal_signal_pending(p)
-
不考虑 task 当前的 sleep state;
-
纯粹回答"有没有致命信号在排队";
-
常用于忙循环/非 schedule 型逻辑中主动检查:
- 比如某个函数里没有使用
wait_*宏,只是在 for/while 里 spin / 重试,这时候用fatal_signal_pending来提供一个出路。
- 比如某个函数里没有使用
简单记忆:
- 我要"被任意信号打断" →
signal_pending/TASK_INTERRUPTIBLE/wait_event_interruptible- 我要"只对 SIGKILL 等致命信号让步" →
fatal_signal_pending/TASK_KILLABLE/wait_event_killable
5. 使用 fatal_signal_pending 的注意事项 / 坑
5.1 不要滥用:只在"适合被杀掉"的路径用
fatal_signal_pending() 体现的是一种上游已经放弃这个进程的信号语义:
- OOM killer 选中了你;
- 管理员 / 用户给了
SIGKILL; - 或者线程组已经调用了
do_group_exit()。
在这种状态下:
- 你可以(也应该)尽快结束当前操作,返回错误,让系统回收资源;
- 但要保证:提前退出不会破坏系统一致性(比如文件系统中途写了一半)。
坑:
不要在"必须保证写完整/更新完整"的关键路径随便
if (fatal_signal_pending()) goto out;,会有可能让数据结构处于半更新状态。
一般做法:
-
设计好 error path,把中途退出的场景考虑进来:
- 回滚状态;
- 标记为"需要修复/重试";
- 避免持锁返回。
5.2 注意调用者对返回值的期待
你在子函数里因为 fatal_signal_pending 返回时,通常需要:
-
返回一个标准的"被信号打断"的错误码:
- 比如
-ERESTARTSYS(内核会决定是否自动重启 syscall); - 或
-EINTR(上层看到是被信号中断);
- 比如
-
不能随便用
-EFAULT/-EINVAL这种语义完全不相关的错误码。
否则用户空间看到的行为会很奇怪:
"明明是 kill -9 的结果,怎么返回 EINVAL 之类莫名其妙的错误"。
5.3 不要当作通用"取消"机制用(信号不是 cancel API)
很多人想用信号(尤其 SIGUSR1)当作"取消 IO / 取消任务"的手段,
然后在大循环里写:
c
if (fatal_signal_pending(current))
return -EINTR;
这是错用:
- SIGUSR1 之类不会被视为 fatal ,
fatal_signal_pending()看不到它; - 你应该用的是
signal_pending()或signal_pending_state(TASK_INTERRUPTIBLE, ...)。
fatal_signal_pending 专门是"已经判了死刑"的那种情况,不要用来处理正常业务上的"取消""停止"需求。
5.4 对非 current 任务调用要谨慎
虽然函数允许:
c
fatal_signal_pending(some_task);
但现实里大多数安全的用法是:
-
只在
current上调用; -
对其它 task 调用时:
- 必须保证你已经拿到了合适的锁(比如 tasklist_lock、RCU、或者引用计数);
- 防止这个 task 正在 exit 或 signal 结构被释放。
否则容易踩 use-after-free / 数据竞态等坑。
5.5 它只是一个"快照",不是强约束
fatal_signal_pending(current) 的结果只是"此刻"的状态:
-
调用返回之后,随时可能:
- 又有新的信号进来;
- 现有信号被处理/被线程组改变。
所以:
if (fatal_signal_pending(current))之后,不能做太激进的假设;- 通常用法只是"决定是否提前 bail out",而不是"现在一定要以某种方式退出"。