# µC/OS 与 SYS/BIOS:中断进入/退出 与分层调度 这两个内核都把"中断参与调度"做成了**显式协议**,于是: ``` ISR 尾部突然发生上下文切换 → 合理行为,不是异常 task 已 ready,却要等中断退出后才运行 → 不是调度延迟 ``` **判断调度时机时,必须把"中断进入/退出"这一段算进控制流。** ## 一、Micrium µC/OS:ISR 必须通知内核 标准 ISR 形态: ``` 保存寄存器 → OSIntEnter() (或直接 OSIntNestingCtr++) → …处理… → OSIntExit() 恢复寄存器 → 从中断返回 ``` - `OSIntEnter()` / `OSIntExit()` **必须成对**,且 **`OSIntEnter()` 不能被任务级代码调用** - `OSIntEnter()` 递增全局**中断嵌套计数**,从而**保证 ISR 执行期间不做任务调度** - 实现细节:若内核未在运行则直接返回;嵌套计数达到上限(250)也返回 - **直接递增计数可省去函数调用开销**,但要求"递增时中断已关闭"——**并非所有架构都成立**,需要自己确认 **`OSIntExit()` 的决策序列**(这是恢复控制流的关键): ``` 内核未运行 → 返回 关中断 → 若嵌套计数为 0 → 开中断返回(防计数回绕) 递减嵌套计数 → 若仍 > 0(还有嵌套 ISR)→ 开中断返回 若调度器仍被锁 → 开中断返回 找出最高优先级就绪任务 → 与当前任务相同 → 开中断返回 否则更新切换计数并执行"中断级上下文切换" ``` - 即:**只有最后一个嵌套 ISR 退出时**才判断"是否需要切换到被 ISR 变为就绪的更高优先级任务" - 在 Cortex-M 上,该切换通常**只是触发 PendSV**(最低优先级异常),**真正的切换推迟到所有 ISR 完成之后** **所以**:`ready 但没运行` 常可解释为"还在 ISR 里 / 调度器被锁 / PendSV 尚未执行"。 ## 二、TI SYS/BIOS:四级线程 + Swi 禁用会连带禁用 Task 调度 线程层次(优先级由高到低): ``` Hwi(硬件中断) > Swi(软件中断) > Task > Idle ``` - **Swi 不能阻塞、总是运行到完成**;Task 可以阻塞,且会被任何更高优先级的线程抢占 - Swi 与 Task 都有优先级级别(数值越大优先级越高,0 最低) **关键的连带效应**: - **`Swi_disable()` 会同时禁用 Task 调度器**——等价于同时调用"禁 Task"与"禁 Swi" - 因此:`Swi_post()` 在临界区中不会立即运行;即使 `Semaphore_post()` 让更高优先级任务就绪,**当前任务也不会在 `Swi_restore()` 之前被抢占** - `Swi_restore()` 在**此前 Task 调度器未被禁用**的前提下,会重新启用并进入 Task 调度器 - **硬性约束**:Swi 调度器被禁用时**绝不能调用阻塞 API**(如信号量等待/门互斥进入),否则会"不可恢复地损坏"Task 调度器内部状态,行为"不可预测且通常是灾难性的" - 禁用某一层**不影响更高层抢占**(`Swi_disable()` 不阻止 Hwi 抢占) **栈模型**: - **Hwi 与 Swi 共用同一个系统中断栈(ISR 栈)**,一个程序一个;每个 Task 有私有栈 - 若系统里没有 Task,所有线程共用一个系统栈;默认系统栈大小 4096 字节 - **每多一个嵌套的 Hwi/Swi 优先级层次,栈需求就上升**;Swi 栈需求与所用 Swi 优先级数量成正比 **所以**: ``` 高优先级 Task 已就绪却不切换 → 先查 Swi 调度器是否被禁用(它会连带禁 Task 调度) → 不要先找死锁 ``` ## 三、µC/OS 的其余高频误判 **调度器锁与挂起是可嵌套的**: - `OSSchedLock()` 是可**嵌套**的:即使退出了 ISR,**只要调度器仍被锁住就不会重调度**;必须成对解除 - `OSTaskSuspend()` 同样可嵌套——**长期不就绪的高优先级任务先查这两者的嵌套层级**,再谈优先级反转 **信号量 vs 互斥量**: - 多个任务可同时共享的资源 → **信号量**;**独占资源 → 互斥量**(互斥量实现会处理优先级反转) - 因此出现"低优先级任务影响高优先级任务"时,**先确认作者是不是把信号量当互斥量用了** **软件定时器回调运行在定时器任务上下文**(这是很值钱的一条): - 回调**在 OS 内部的定时器任务上下文中执行**——**既不是中断,也不是创建/启动该定时器的任务** - **用的是定时器任务的栈**,所以定时器任务的栈要按回调需求配足 - **回调里绝不能做阻塞/等待**(延时、任何 `OS???Pend()`)——因为所有回调在定时器任务里**串行执行**,阻塞一个回调会把整个定时器子系统卡住 - 所以: ``` TimerCallback() → 内部有 blocking wait → 高价值的 bug 信号(不是"偶发卡顿") ``` **PendAbort:被唤醒 ≠ 拿到资源**: - 中止等待(针对任务内建消息队列或普通消息队列)的作用是**中止等待并让任务就绪**,而**不是正常投递** - **只能由任务调用(不能从 ISR 调)**;指针为 NULL 或指向调用者自己是无效用法 - 被唤醒的任务会**带着"等待被中止"的错误码返回** **所以**:任务被唤醒、但队列/信号量里并没有新消息——先看是不是中止等待路径,**"被唤醒"与"成功获取资源"是两件事**。 ## 决策树 ``` 拿到 µC/OS 或 SYS/BIOS 目标 ├─ 调度时机对不上 │ ├─ µC/OS:查 OSIntNestingCtr(是否还在 ISR 里)、调度器锁、PendSV 是否已执行 │ ├─ SYS/BIOS:查 Swi 调度器是否被禁用(连带禁用 Task 调度) │ └─ 别先判"调度延迟/死锁" ├─ ISR 内调用内核 API │ ├─ µC/OS:是否成对调用 OSIntEnter/OSIntExit │ ├─ SYS/BIOS:是否误用阻塞 API(Swi 禁用期间调用会损坏调度器状态) │ └─ 注意 SYS/BIOS 的 Swi 不能阻塞 ├─ 栈相关异常 │ ├─ SYS/BIOS:Hwi 与 Swi 共用 ISR 栈;嵌套层次与 Swi 优先级数影响栈需求 │ └─ 默认系统栈 4096 字节,注意配置项 └─ 看到"直接递增嵌套计数"的写法 └─ 只在"递增时中断已关闭"的架构上安全,需逐架构确认 ``` ## 工具与验证 - 中断协议:ISR 的进入/退出调用是否成对;嵌套计数的读写点 - 调度禁用:调度器锁、Swi/Task 禁用标志与恢复点 - 栈:系统(ISR)栈与任务栈的划分、嵌套层次与栈配置 - 验证:能同时说清「当前是否在 ISR 上下文 + 哪一层的调度被禁用 + 切换在何时真正发生」 ## 该平台的坑(汇总) - **把 ISR 尾部的切换当异常**:那是中断退出时的正常调度决策 - **忽略嵌套计数**:只有最后一个嵌套 ISR 退出才判断切换 - **以为 PendSV 触发就等于已切换**:真正的切换在所有 ISR 完成后 - **在 µC/OS 里让任务级代码调用 `OSIntEnter()`**:协议禁止 - **照搬"直接递增计数"的优化**:只在特定架构安全 - **忘记 `Swi_disable()` 会连带禁用 Task 调度**:这是 SYS/BIOS 最反直觉的一条 - **在 Swi 调度器禁用期间调用阻塞 API**:会不可恢复地损坏调度器状态 - **按"每个线程独立栈"估算 SYS/BIOS 栈需求**:Hwi 与 Swi 共用 ISR 栈