[Android稳定性] 第036篇 [原理篇] 【深入内核】理解中断上下文、进程上下文以及进程调度之间的关系

一、三者概念的梳理

1.1 进程上下文(Process Context)

当内核代码是在为一个具体进程执行某项任务(比如响应系统调用)时,就是在“进程上下文”。

  • 是普通用户或内核线程运行的上下文。
  • 可以被调度、休眠、阻塞。
  • 拥有完整的进程信息(task_struct)。
  • 可以执行阻塞操作,比如 sleep()mutex_lock()schedule()

✔️ 能被调度、能睡眠、可切换 CPU、参与 CFS 调度

1.2 中断上下文

在 Linux 内核中,中断上下文(interrupt context) 是指内核在响应硬件中断或软中断(如 tasklet、softirq)时运行的上下文。这种上下文不是在代表某个用户进程执行代码,而是在处理外部或内部事件时抢占当前 CPU 的执行流运行的代码。

1.2.1 ✅ 中断上下文的特点

特征 描述
不能睡眠 绝不能调用可能导致阻塞的函数,比如schedule()mutex_lock()
没有用户上下文 不是为任何用户进程服务,current指针虽然存在,但不能用于调度或睡眠
响应快速 中断处理函数要尽可能短,避免阻塞整个系统
可以抢占进程上下文 中断可以打断用户态或内核态代码
可以嵌套 高优先级中断可以嵌套低优先级中断(根据中断控制器配置)arm平台不支持中断嵌套

1.2.2 ✅ 中断上下文的类型

  1. 硬中断(Hard IRQ)
  • 是由硬件触发的,比如网卡收到数据、串口完成发送等。
  • 使用 request_irq() 注册处理函数。
  • 入口是 do_IRQ()irq_handler_t
  1. 软中断(SoftIRQ)
  • 内核中定义的一种中断延迟机制,用于将某些耗时的中断处理移出硬中断。
  • do_softirq() 中调度,比如网络协议栈处理。
  1. Tasklet
  • 更轻量级的软中断,用于驱动程序将中断工作推迟执行。
  • 比如网卡驱动在硬中断中收包,然后用 tasklet 处理数据包。

1.2.3 ✅ 为什么不能在中断上下文中睡眠?

中断上下文是“抢占”而来的,它没有独立的进程堆栈,也不是某个进程的调度实体。如果你在中断上下文里睡眠,就会导致:

  • 系统调度器无法正确恢复中断处理后的上下文;
  • 某些 CPU 核心会 hang;
  • 触发 BUG()WARNING: sleeping function called from invalid context 等错误。

1.3 Linux 进程调度器(如 CFS)

  • 只调度 进程上下文,不调度中断上下文。
  • 中断发生时,会中断当前进程的执行,处理完后继续。
  • 如果中断处理引起某个进程状态变化(如唤醒某个阻塞的进程),会在下次调度点由调度器进行切换。

二、三者之间的“关联性”

✅ 2.1 中断上下文触发调度事件

中断常用于 I/O、定时器等场景,可能导致以下行为:

行为 影响调度
中断处理程序唤醒某个阻塞的进程(比如唤醒poll() 是(设置need_resched
定时器中断触发调度器周期调度(如 CFS 周期) 是(周期性调度)
硬件中断抢占当前进程执行 否(中断返回后,仍由调度器决定是否切换进程)

💡 中断上下文本身不参与调度,但它可以触发调度。

中断上下文“不能被调度” ≠ 它“不能触发调度”

✅ 2.2 进程上下文中主动调用调度器

例如:

  • schedule():手动触发调度器,当前进程让出 CPU。
  • mutex_lock():若锁不可用,会调用调度器阻塞当前进程。
  • wait_event():等待事件触发,当前进程睡眠,唤醒后重新调度。

✅ 2.3 软中断 / Tasklet 是中间层

  • 软中断SoftIRQ:在中断处理后尽快处理的数据包收发、调度等逻辑。
  • Tasklet:软中断的封装形式,运行在 softirq 上下文中。

它们是中断和进程之间的桥梁,将部分不能在中断上下文中完成的任务延迟执行,同时不阻塞调度器。

三、总结对比标

特性 进程上下文 中断上下文 SoftIRQ/Tasklet
是否有task_struct ✅ 有 ❌ 无 ❌ 无
是否能睡眠 ✅ 可 ❌ 不可 ❌ 不可
是否能被调度 ✅ 可 ❌ 不可 ❌ 不可
优先级 低(相对中断) 中等
能否触发调度器 ✅ 可主动调度 ✅ 可被动触发 ✅ 可唤醒进程

四、举几个不能在中断上下文中调用的函数/行为

函数/行为 说明
mutex_lock()/down() 可能阻塞,绝对禁止
schedule() 主动调度,只有进程上下文可用
copy_to_user()/copy_from_user() 可能睡眠,且依赖用户地址空间
msleep()/usleep_range() 显式睡眠,不可调用
kmalloc(GFP_KERNEL) 可能睡眠,需改用GFP_ATOMIC
vfs_*/file_operations中的阻塞 I/O 禁止,可能阻塞

总结一句话:

中断上下文里不要睡觉(sleep),也不要等锁(block),否则系统会直接💥炸掉或 hang。

五、总结

在中断上下文中绝对禁止使用可能引起睡眠或阻塞的函数

如果我们遇到某个 kernel hangwatchdog timeout造成的死机问题,可以怀疑是某个中断 handler 没有及时退出或调用了阻塞函数。

可以查看这个案例:[Android稳定性] 第033篇 [问题篇] 在中断上下文进行进程调度造成系统卡死

这个案例就是一个典型的在中断处理函数中pm8941_pwrkey_irq中调用了内核通知链去执行BIO的操作,schedule导致了进程阻塞。