Linux基础 -- 进程运行状态 之 fatal_signal_pending函数

背景

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

    没有致命信号在排队;

    哪怕有 SIGINTSIGTERM 等,但因为被屏蔽/忽略/有 handler,不一定被视为"fatal"。

它和 signal_pending(p) 的区别就是:

  • signal_pending(p):只要有任何 pending 信号(可忽略、停止、继续等)就返回真;
  • fatal_signal_pending(p):只关心"最终会导致这个 task 结束"的那类信号。

2. 内核里是怎么判断"fatal"的?

  1. 先看当前 task 是否有 pending 信号(包括线程自己的和整个线程组的);

  2. 过滤掉一些不会终止的信号(比如 SIGSTOPSIGCONT、被忽略的可捕获信号等);

  3. 检查是否有:

    • 无法屏蔽/忽略的信号,比如:SIGKILL
    • 配合线程组退出(group exit)的标记,例如 OOM killer、do_group_exit() 触发的退出;
  4. 如果上述条件满足,就认为 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 的等待,如:

    c 复制代码
    if (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 之类不会被视为 fatalfatal_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",而不是"现在一定要以某种方式退出"。
相关推荐
Zeku1 小时前
20251125 - 为什么Linux是非实时操作系统?
linux·服务器
Kyan.W1 小时前
shell好用的工具
linux·shell
n***84071 小时前
Redis基础——1、Linux下安装Redis(超详细)
linux·数据库·redis
apocelipes1 小时前
Linux的binfmt_misc机制
linux·c语言·c++·python·golang·linux编程·开发工具和环境
虾..1 小时前
Linux 进程控制
linux·运维·服务器
last demo1 小时前
pxe自动化安装系统实验
linux·运维·服务器·自动化
Zeku1 小时前
20251125 - Linux驱动开发Makefile文件介绍
linux·驱动开发·单片机
实心儿儿2 小时前
Linux —— 基础开发工具2
linux·运维·服务器
秋深枫叶红2 小时前
嵌入式第二十三篇——数据结构基本概念
linux·数据结构·学习·算法