本节摘要:中断是系统与物理世界的接口,也是最容易破坏时间秩序的入口。FreeRTOS 的中断管理围绕三条铁律展开:服务程序要短、重活延迟到任务、中断里只用中断安全接口。本节先算清"长中断"的时间账,再给出顶半与底半分层的标准形态(三种搬运通道的选择),完整列出中断禁用清单与任务、中断两个上下文的接口对照,最后用一张"偶发丢字节"的工单验证全部规则。
第 4 章与第 6 章前两节反复引用过"中断安全接口""三步标准动作"这些结论,本节把原理讲全。中断的特殊性一句话说尽:它不属于任何任务,却又随时打断一切任务——这个"体制外"身份是所有规则的总根源。
**铁律一,服务程序要短。**中断服务程序执行期间,所有优先级不高于它的中断与全部任务都被冻结。短的标准不是绝对时长,而是"别挡别人的截止时间":一个百微秒的服务程序对按键系统无所谓,对要求十微秒响应的编码器中断就是灾难。工程习惯的量化上限:常规外设中断控制在几十微秒内,实在重的活按铁律二外移。
**铁律二,重活延迟到任务。**服务程序只做三件事:收数据(从外设寄存器取走)、发信号(通知或入队)、必要时请求切换。解析、计算、打印、协议处理全部归属任务。这不是教条而是分工学:任务有栈、能阻塞、有优先级可调;中断没有栈(用的是公用的中断栈)、不能阻塞、优先级由硬件定——重活在中断里做,等于让一个没有装备的人干重体力活。
**铁律三,只用中断安全接口。**内核接口分两个版本:任务版可以阻塞、内部假设"调用者是任务";中断版永不阻塞、内部带中断防护。中断里调任务版,等于绕过防护直接改内核数据结构——运气好被断言拦截,运气不好链表悄悄损坏,几天后以全册最诡异的一张工单收场。
三条铁律合成一个设计模式,业界称顶半与底半:顶半是中断里的最小动作(收发与标记),底半是任务里的完整处理。两者之间需要一条搬运通道,FreeRTOS 提供三种,按 4.3 节的选型逻辑挑:
| 搬运通道 | 携带信息 | 唤醒延迟 | 适用 |
|---|---|---|---|
| 任务通知 | 三十二位值或计数 | 最低 | 一对一,信息量小(序号、状态字) |
| 队列 | 定长元素 | 低 | 一对一或一对多,结构化数据 |
| 流或消息缓冲 | 任意字节或变长消息 | 低 | 高频字节流(串口、采样) |
/* 顶半:ADC 中断,三步标准动作的完整版 */ void ADC_IRQHandler(void) { BaseType_t xWoken = pdFALSE; uint16_t raw = adc_dr_read(); /* 收:硬件数据立刻取走 */ xQueueSendFromISR(xAdcQ, &raw, &xWoken); /* 发:中断安全入队 */ portYIELD_FROM_ISR(xWoken); /* 切:按旗子请求调度 */ } /* 底半:处理任务,带着完整装备干活 */ void adc_task(void *arg) { uint16_t raw; for (;;) { xQueueReceive(xAdcQ, &raw, portMAX_DELAY); filter_push(raw); if (filter_ready()) { report(filter_value()); } } }
把铁律三展开成一张禁用清单,每一项配一句病理说明。
禁用一,一切可能阻塞的接口:延时、带超时的收发、互斥量的取。中断没有"阻塞"的概念(2.2 节),调用它们等于让不存在的东西睡觉——断言拦截或未定义行为。禁用二,互斥量的任何操作:优先级继承要把"持锁任务"抬优先级,中断不是任务,抬无可抬;给也不行——归还时间不可预期。禁用三,任务版的创建与删除:它们内部要走完整任务路径。禁用四,打印与动态分配:格式化与堆操作耗时且可能阻塞,自诩"就打一行"的中断是丢数据的高发源。禁用五,事件组的直接置位:置位要扫等待链表,中断里不安全——它的中断版走守护任务代执行(4.3 节讲过的那一跳),不算"直接"。
对应的许可清单要短得多也更清晰:中断版的通知与队列接口、中断版的事件组置位(经守护任务)、读滴答计数、请求切换。许可清单之外皆禁区——这个"白名单思维"比黑名单可靠得多,黑名单永远有漏网。
还有一个容易忽视的上下文问题:中断嵌套。Cortex-M 允许高急中断打断低急中断,嵌套深度受优先级结构限制。内核只要求一件事:能调用内核中断接口的优先级范围是受限的(下一节讲配置),范围之外的中断连白名单接口也不许碰。把最急的硬件中断(如电机紧急刹车)配置在范围之外换取零内核依赖、零抖动,是实时设计的常用手段——它付出的代价是"只能用裸标志与任务通信",通信要靠任务侧轮询标志(配易失限定与屏障,3.3 节)。
背景。某网关的串口透传偶发丢字节,错误率随流量上升。串口中断本身写得无懈可击:三步标准动作、流缓冲区搬运。排查。逻辑分析仪盯串口线:字节确实到线上了,但芯片的接收移位寄存器偶尔来不及取走——过载错误。谁挡了接收中断?嫌疑落在另一个中断:温度采集的模数转换完成中断,服务程序里做了一次多点均值滤波(约两百微秒的循环计算),优先级配置得比串口高。机理:滤波期间串口中断被压住,连续到达的字节把接收硬件的缓冲挤爆。修复:滤波外移到任务(铁律二的迟到补课),中断里只取原始值;同时把串口中断优先级提到采集之上(优先级映射截止紧急度,2.3 节的翻译规则在中断世界同样成立)。结果:过载错误归零。解读:这张工单的教学价值在于病根不在受害者的代码里——串口中断写得再好也躲不过别人压它两百微秒。中断系统的时间账是全系统的账,审查任何"长中断"都要看它压住了谁。变式:若那次滤波确实必须在中断里立刻完成(比如硬件保护窗口),正确解法是把该功能升级为最高急中断并移出内核可屏蔽范围——用隔离换确定,第 7 章的安全设计会再遇这个思路。
💡 中断代码的三行自检:能删的都删了吗?剩下的每一行知道自己会压住谁吗?搬运通道按 4.3 节的表选了吗?三问过关,中断代码基本不会出事——剩下的问题都在配置层,那是下一节的主题。
中断代码的规则齐了,但规则生效的前提是优先级配置正确——下一节处理配置层:分组、两级阈值、数值方向,以及一次亲手做的时序实测演练。