3、中断上下文:任务上下文与中断上下文、中断上下文切换开销、中断上下文限制

3.1 任务上下文与中断上下文

咱们先聊聊什么是上下文。说白了,上下文就是CPU干活时的“工作状态”。

任务上下文,就是普通任务(比如你写的应用线程)在运行时,CPU里那一堆寄存器的快照。包括程序计数器、堆栈指针、通用寄存器等等。我习惯把它理解成“任务的灵魂”。任务被切换出去时,灵魂得存好;切回来时,再还给它。

中断上下文呢?嗯,这个有点不一样。当硬件中断来了,CPU会暂停当前任务,跳去执行中断服务程序(ISR)。这时候,ISR运行的环境就是中断上下文。它不依赖任何任务,也不属于哪个进程。说白了,它是个“临时工”,干完活就走。

我在项目中遇到过一个问题:一个同事在ISR里调用了printf(),结果系统直接死机。为什么?因为printf()依赖任务上下文里的资源(比如锁、缓冲区),而中断上下文里根本没有这些。你想想看,一个临时工跑去用正式员工的工位,不出乱子才怪。

核心区别:
  • 任务上下文:有独立的堆栈、调度器管理、可以睡眠
  • 中断上下文:借用被打断任务的堆栈、不可睡眠、不可阻塞

3.2 中断上下文切换开销

每次中断发生,CPU都要做一次上下文切换。这个开销,比你想象的大。

我给你们拆解一下步骤:

  1. CPU自动压栈:PC、状态寄存器、某些通用寄存器
  2. 中断控制器裁决:哪个中断优先级高?
  3. 跳转到ISR入口:查中断向量表
  4. ISR手动压栈:保存更多寄存器(如果ISR用到了)
  5. 执行ISR逻辑
  6. 出栈恢复:逆序弹出所有寄存器
  7. 中断返回:恢复PC,继续执行原任务

你看,光是压栈出栈,就折腾了好几步。我记得有一次做ARM Cortex-M的优化,发现一个高频中断(1kHz)占了CPU 15%的时间。一查,全是上下文切换的锅。

具体开销有多大?我给你们一个参考数据:

操作 典型时钟周期数
中断响应(硬件压栈+跳转) 12~20 cycles
ISR手动保存寄存器 10~30 cycles
ISR执行(假设很短) 50~200 cycles
恢复+返回 15~25 cycles
总计 87~275 cycles

注意,这还只是单次中断。如果你的系统里中断嵌套了,那开销会成倍增加。我曾经在一个项目中,三层中断嵌套,CPU光切换就花了40%的时间。嗯,后来我硬是把三层嵌套压成了一层,才把性能救回来。

我的建议:中断服务程序要短、平、快。能做的处理尽量放到任务上下文里做。中断里只做最紧急的事,比如读硬件寄存器、清中断标志、发个信号量。

3.3 中断上下文限制

中断上下文里能做什么,不能做什么,这是有规矩的。我总结了几条铁律:

  • 不能睡眠:中断上下文没有调度器管理,你睡下去就没人叫得醒你。整个系统会挂掉。
  • 不能获取互斥锁:万一锁被别的任务持有了,你只能等。但中断里不能等,一等等出死锁。
  • 不能调用阻塞API:比如read()、write()、malloc()。这些函数都可能触发调度,中断里一调度就乱套了。
  • 不能访问用户空间:中断上下文属于内核空间,访问用户地址会触发缺页异常,而缺页异常又需要睡眠……死循环。
  • 堆栈空间有限:中断用的是被打断任务的堆栈,或者专用的中断栈。通常只有几KB。你递归调用一下,栈就爆了。
我曾经踩过的坑:在一个RTOS项目里,我在ISR中调用了vTaskDelay(),想着延迟1毫秒再处理。结果系统直接卡死。后来查手册才发现,FreeRTOS明确说了中断里不能调用vTaskDelay()。嗯,从那以后,我写ISR之前都会先翻一遍API文档的“中断安全”章节。

那中断上下文里到底能做什么?我列个清单:

  • 读写硬件寄存器(比如清中断标志)
  • 操作原子变量(比如atomic_inc())
  • 发送信号量或事件(用中断安全的API)
  • 更新时间戳或计数器
  • 把数据塞进无锁队列

说白了,中断上下文就是个“快递员”。它只管把包裹(数据)放到门口(队列),然后通知主人(任务)来取。千万别让快递员帮你拆包裹、做登记、写报告——那是主人该干的事。

3.4 知识体系结构图

下面这张图,是我梳理的中断上下文核心逻辑。你看一眼,就能把整个章节串起来。

中断上下文知识体系 任务上下文 vs 中断上下文 任务:有堆栈、可调度、可睡眠 中断:借用栈、不可睡眠、临时工 ISR 依赖硬件自动压栈 任务切换由调度器管理 中断上下文切换开销 硬件压栈:12~20 cycles ISR 保存寄存器:10~30 cycles ISR 执行:50~200 cycles 恢复+返回:15~25 cycles 中断上下文限制 ❌ 不能睡眠 ❌ 不能获取互斥锁 ❌ 不能调用阻塞API ❌ 堆栈空间有限 核心原则 ISR 只做最紧急的事,耗时操作交给任务上下文 实践1:ISR 短平快 只读寄存器、清标志 发信号量通知任务 实践2:避免嵌套 减少中断优先级层级 必要时关中断保护 实践3:使用无锁队列 ISR 写、任务读 避免锁和阻塞

这张图把三个核心知识点串起来了。你从左往右看:先搞清楚两种上下文的区别,再理解切换开销有多大,最后记住中断上下文里的禁忌。嗯,这三块搞明白了,中断处理这块你就入门了。

一个小技巧:写ISR之前,先在脑子里过一遍“我这个函数会不会睡眠?会不会拿锁?会不会触发调度?”三问都答“否”,再往下写代码。

可可精品资料库