想象你是深夜食堂的主厨老周。灶上炖着汤、烤着鱼,你把每口锅都当成一个“任务”。正忙得不可开交时,门铃响了——订餐电话、烤箱定时器、煤气报警器轮番登场,它们都是“中断”:硬件强行打断你手头的活,让你先去处理更紧急的事。有的铃声要立刻响应(煤气报警),有的可以稍等片刻(外卖订单),于是就有了“优先级”。更麻烦的是,老周有一本“秘制酱料配方本”,每次改良配方都要写上几笔——写配方时绝不能被打断,否则写一半去开门,回来就忘了该放两勺盐还是三勺盐,酱料全毁。这段“绝不能被打断”的时间,在 RTOS 里就叫临界区,处理办法是先锁门(关中断)、写完再开门(开中断)。本文用老周的厨房串起 FreeRTOS 的中断优先级分组、中断嵌套、临界区进出、ISR 安全 API、中断延迟与竞态死锁,让你彻底看懂嵌入式系统如何在“兵荒马乱”中保住数据一致。

岗位关联

对 AI 工程师而言,机器人、自动驾驶、边缘设备都依赖实时中断链路——理解临界区与中断延迟,才能让 AI 模型拿到“不撕裂”的真实数据,避免系统跑起来就翻车。

目录
  1. 可视化演示
  2. 中断基础概念
  3. 优先级分组机制
  4. 中断嵌套管理
  5. 临界区保护机制
  6. ISR安全API调用
  7. 中断延迟分析
  8. 竞态与死锁防护
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

交互展示RTOS中断响应流程、临界区进出与中断屏蔽及优先级翻转场景,揭示关键资源保护机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

中断响应流程 IRQ触发上下文切换 ISR执行后返回 延迟=响应+ISR 上下文保护现场

中断基础概念

深夜食堂最热闹的时候,老周要同时照顾三口锅、两条鱼——每个灶台就是一个“任务”。突然门铃响了,他必须放下锅铲去开门:这就是中断(Interrupt)。门铃、电话、烤箱定时器就像硬件发给 CPU 的“电报”:喂,先别忙手里的事,来办我的事!CPU 收到电报后暂停当前任务、把现场压进栈、跳去执行中断服务函数(ISR, Interrupt Service Routine),办完再回到原来的任务继续干。

和普通任务函数不同,ISR 不是被调度器安排运行的,而是被硬件强行“请”出来的。整个流程像老周开门:放下铲子(保存现场)、跑去开门(执行 ISR)、回来拿起铲子继续炒(恢复现场)。既然是插队办事,就不能磨蹭——ISR 第一条铁规矩是短小快速,像开门 30 秒解决,绝不能在门口聊十分钟天;第二条是绝不阻塞等待,不能站在门口等外卖员慢慢找零钱。

中断的现场要压在栈上,所以任务栈的大小必须预留中断嵌套的余量。栈空间需求可以用下面的式子估算:

老周心里有数:门铃再急,锅里的菜也不能烧糊;ISR 再忙,也得先保住“现场”才能去处理。这就是中断处理的第一课——先安顿好手头的活,再去接客。

Sstack=Sapp+Ndeep×Sisr\displaystyle S_{stack} = S_{app} + N_{deep} \times S_{isr}
任务栈总需求 = 任务自身代码的栈需求 + 最多嵌套层数 × 每层中断现场占用的栈空间。
  1. S_stack(任务栈总需求)任务运行时栈空间要满足的总量,栈溢出会导致系统静默崩溃,就像厨房里锅碗堆得太高会砸下来。
    SstackS_{stack}
  2. S_app(应用栈需求)任务自身函数调用、局部变量所占的栈。
    SappS_{app}
  3. N_deep(中断嵌套层数)可能同时嵌套的最大中断层数,每多一层就要多压一份现场。
    NdeepN_{deep}
  4. S_isr(单层 ISR 栈需求)一次中断现场保存(寄存器、返回地址等)所需的栈空间。
    SisrS_{isr}

优先级分组机制

门铃和煤气泄漏报警同时响起,老周当然先关煤气——因为报警比外卖重要得多。处理器也面临同样的选择:多个中断同时到达,先响应谁?答案就是优先级。但嵌入式世界有个反直觉的约定,像火警编号一样:1 号火警比 9 号补货通知更紧急,所以数值越小,优先级越高。

FreeRTOS 基于 Cortex-M 内核,中断优先级用 4 位表示,可配置 0~15 共 16 级。其中,configMAX_SYSCALL_INTERRUPT_PRIORITY 是系统画下的一条“管理分界线”:只有优先级数值大于等于这个值(即优先级不高于这条线)的中断,才允许调用 RTOS 的 API;比这条线更紧急的中断(数值更小)被留给硬实时需求,严禁触碰调度器内部的数据结构。

这条分界线相当于老周的规定:送外卖的(低优先级中断)可以用对讲机通知后厨备菜(调用 FromISR API);但煤气检修工(高于阈值的中断)进门后严禁打内线电话遥控厨房——他比调度器还急,必须先干完手头的安全活。判断方法用公式表达就是:

划清“谁能动调度器”的界限,是保证 RTOS 内部链表、队列不被高优先级中断搅乱的第一道闸门。老周只要把对讲机发给“不高于安全级别”的人,厨房秩序就乱不了。

Pirq≥Plimit⇒API-safe\displaystyle P_{irq} \ge P_{limit} \Rightarrow \text{API-safe}
当中断优先级数值不小于系统阈值数值时,该中断被认为“在系统可管理范围内”,可以安全调用 RTOS API。
  1. P_irq(中断优先级数值)该中断在 NVIC 中配置的优先级数字,范围 0~15。
    PirqP_{irq}
  2. P_limit(系统调用阈值)由 configMAX_SYSCALL_INTERRUPT_PRIORITY 定义的分界数值。
    PlimitP_{limit}
  3. ≥(数值比较)数值大于等于阈值,表示该中断的紧急程度不高于系统可管理的上限。
    ≥\ge
  4. API-safe(判定结果)满足条件时,ISR 内可以调用带 FromISR 后缀的 API。
    ⇒API-safe\Rightarrow \text{API-safe}

中断嵌套管理

老周正在门口听外卖员核对订单,厨房的烟雾报警器突然炸响——他立刻撇下外卖员冲回厨房。这就是中断嵌套:正在执行 ISR 时,一个更高优先级的中断到来,强行抢占当前 ISR。

Cortex-M 硬件自动完成了嵌套的现场保存:每次进入新的 ISR,CPU 会把当前正在执行的 ISR 的寄存器再压一层栈,然后切换到新 ISR;返回时逐层弹栈,一层一层恢复。就像老周先扑火、再回头听外卖员把话说完,账要一笔一笔结清。此外,内核借助 BASEPRI 寄存器实现“选择性屏蔽”:只屏蔽那些能调用 API 的中断(优先级数值不低于阈值者),而更紧急的硬实时中断始终畅通无阻——老周可以对门口喊一句“级别低于火灾的呼叫先别进来”,但煤气报警永远能冲进来。

嵌套是有代价的:每嵌套一层,栈就多压一份现场,深度越大栈消耗越大。抢占条件可以写成:

嵌套不是无限叠加——硬件保底、内核分层、栈空间兜底,三层约束让老周的厨房在“连环夺命 call”中依然井然有序。

Pnew<Pcur⇒preempt\displaystyle P_{new} < P_{cur} \Rightarrow \text{preempt}
新到达中断的优先级数值小于当前正在执行 ISR 的优先级数值时(数值小=更紧急),新中断抢占当前 ISR,形成嵌套。
  1. P_new(新中断优先级)刚刚触发的中断的优先级数值。
    PnewP_{new}
  2. P_cur(当前ISR优先级)此刻正在执行的中断服务函数的优先级数值。
    PcurP_{cur}
  3. <(小于号)数值更小代表紧急程度更高,具备嵌套抢占资格。
    <<
  4. preempt(抢占)硬件自动压栈并跳转到新的 ISR,原 ISR 暂停等待恢复。
    ⇒preempt\Rightarrow \text{preempt}

临界区保护机制

老周有一本“秘制酱料配方本”,每次改良配方都要翻开写上几笔。写配方这个动作绝对不能被打断:写一半去开门,回来就忘了该放三勺盐还是五勺盐,酱料全毁。这个“写配方的时刻”就是临界区(Critical Section)——访问共享资源的代码段,必须原子完成,不许任何人插队。

RTOS 用 taskENTER_CRITICAL() 进入临界区、taskEXIT_CRITICAL() 退出临界区。进入时,内核通过 BASEPRI 把“可管理的中断”全部屏蔽——相当于锁上厨房门、挂上“勿扰”牌;退出时重新打开。关键细节是嵌套计数:进入两次就要退出两次,内核用计数器记录深度,只有所有层级都退出、计数器归零,中断才真正恢复。少退一次,门就永远锁死,整个系统响应瘫痪。

临界区不是万能的特效药,它也有成本:进入和退出本身要时间,屏蔽期间所有管理范围内中断都被推迟。所以保护策略的选择铁律是——能短则短。访问共享变量、操作链表、读写 FIFO 时进临界区,办完立刻出来。

配方本上的字要一气呵成,共享数据的读写也要一气呵成——这就是临界区存在的全部意义。老周的“勿扰”牌挂得越短,客人(中断)的等待就越少。

Toverhead=Tenter+Tcritical+Texit\displaystyle T_{overhead} = T_{enter} + T_{critical} + T_{exit}
一次临界区保护的总开销 = 进入时间 + 临界区内代码执行时间 + 退出时间。前两项越长,对中断响应的影响越大。
  1. T_enter(进入开销)执行 taskENTER_CRITICAL() 保存现场、设置屏蔽寄存器的时间。
    TenterT_{enter}
  2. T_critical(临界区执行时间)真正操作共享数据的那段代码耗时,这是延迟的“大头”。
    TcriticalT_{critical}
  3. T_exit(退出开销)执行 taskEXIT_CRITICAL() 恢复中断状态的时间。
    TexitT_{exit}
  4. 求和关系(总开销)三项相加决定了一次保护对系统实时性的总体影响。
    ToverheadT_{overhead}

ISR安全API调用

老周在门口收外卖时,最忌讳转身回厨房颠勺炒菜——手里只有一个“传话机”,只能告诉厨房里的帮厨(任务)去干活。ISR 里的规则一模一样:能通知,不能等待,更不能亲自干重活。

FreeRTOS 为 ISR 专门准备了一批带 FromISR 后缀的 API:xQueueSendFromISR、xSemaphoreGiveFromISR 等。它们不会阻塞,只做两件事:往队列或信号量里放数据,然后通过传出参数 xHigherPriorityTaskWoken 报告“我唤醒了一个比当前任务更重要的任务”。如果这个标志变成 pdTRUE,ISR 结尾就应该调用 portYIELD_FROM_ISR() 请求立即切换,让被唤醒的高优先级任务马上上灶。

反面教材也很典型:有人在 ISR 里调用 vTaskDelay 睡一觉,或者等信号量——这等于在门口睡着了,厨房全乱套,轻则系统卡死,重则触发断言。ISR 的正确姿态是“传完话就走”,繁重计算一律丢给任务处理。

ISR 只传递“有活干了”的信号,重活留给任务干——传话的人不该替厨房做菜。老周把对讲机放到门口,厨房的活儿照样在帮厨手里井井有条。

xHigherPriorityTaskWoken=pdTRUE⇒portYIELD_FROM_ISR()\displaystyle xHigherPriorityTaskWoken = pdTRUE \Rightarrow portYIELD\_FROM\_ISR()
当 FromISR 类 API 的传出参数被置为 pdTRUE,说明有更高优先级任务被唤醒,应调用 portYIELD_FROM_ISR() 请求立即执行上下文切换。
  1. xHigherPriorityTaskWoken(唤醒标志)ISR 调用 FromISR API 时传入的布尔指针,函数内部会根据是否唤醒高优先级任务改写它。
    xHigherPriorityTaskWokenxHigherPriorityTaskWoken
  2. pdTRUE(真值)标志被置真,表示被唤醒任务优先级高于当前运行任务。
    pdTRUEpdTRUE
  3. ⇒(那么)条件成立后的必然动作,如同火警响了必须拉闸。
    ⇒\Rightarrow
  4. portYIELD_FROM_ISR()(切换请求)请求调度器在 ISR 返回后立即切换上下文,而不是等到下一个时钟节拍。
    portYIELD_FROM_ISR()portYIELD\_FROM\_ISR()

中断延迟分析

老周最怕两件事:一是“门铃响后要多久才开门”,二是“开门时会不会耽误炒菜”。前者就是中断延迟(Interrupt Latency):从中断信号触发到 ISR 第一条指令开始执行的时间。延迟越小,系统实时性越好。

中断延迟由三部分构成:硬件响应时间(CPU 检测中断、完成当前指令、压栈,通常是纳秒级)、软件屏蔽时间(如果正处在某个临界区或更高中断 ISR 中,必须等它结束)、现场保存与分发时间(跳转到中断向量表对应 ISR 入口)。其中最大的变数来自软件:任何时刻正在执行的最长临界区,决定了中断最坏要等多久。所以 RTOS 通常把“最大关中断时间”作为性能指标写进文档。

实时性优化的核心思路因此非常清晰:砍临界区。把临界区里的代码精简到最少、避免在临界区里做浮点运算或日志打印、把非紧急逻辑挪到任务里做。老周的办法是:把配方本放在灶台边、字写快一点,或者干脆叫帮厨在门外先接手外卖。

想缩短“开门时间”,不是把门铃调得更响,而是让老周写配方的时间更短。延迟优化,本质上是跟自己的临界区较劲。

Tlatency=Thw+Tmask_max+Tdispatch\displaystyle T_{latency} = T_{hw} + T_{mask\_max} + T_{dispatch}
中断延迟 = 硬件响应时间 + 最坏情况下的软件屏蔽时间(最长临界区)+ 现场保存与分发时间。
  1. T_hw(硬件响应时间)从中断引脚有效到 CPU 完成当前指令并开始异常处理的固定耗时。
    ThwT_{hw}
  2. T_mask_max(最长屏蔽时间)系统中所有临界区或高优先级 ISR 中耗时最长的那段,决定最坏延迟。
    Tmask_maxT_{mask\_max}
  3. T_dispatch(分发时间)把程序计数器切换到对应 ISR 入口、完成向量跳转的时间。
    TdispatchT_{dispatch}
  4. 求和关系(总延迟)三项累加后,得到从事件发生到 ISR 真正动手处理的总等待。
    TlatencyT_{latency}

竞态与死锁防护

如果老周写配方时不锁门,帮厨小王趁他被门铃叫走的几十秒,往配方本上补了一笔“多放一勺盐”——等老周回来,配方已经不是他离开时的样子。这就是竞态条件(Race Condition):两个执行流交错访问同一共享数据,结果取决于谁先谁后,毫无确定性。

竞态的本质是“读-改-写”三步不原子:读变量、在寄存器里计算、写回,中间任何一个窗口被中断或另一个任务插进来,数据就撕裂了。比如共享计数器的自增,在处理器眼里是三步,不是一步。死锁比竞态更凶:ISR 里等待任务持有的锁、任务又等 ISR 释放信号量——互相等待,谁都无法前进,系统冻结成一座“冰雕”。想象两个厨师都举着锅等对方递盐,谁都不肯先放下手里的锅。

防护三板斧:第一,用临界区或关中断保证共享访问的原子性,让“读-改-写”一气呵成;第二,ISR 里绝不阻塞等待任何锁或信号量;第三,保持资源获取顺序一致、使用带超时的等待。竞态窗口可以用下面这个式子形象刻画:

竞态是“数据被撕坏”,死锁是“系统被冻住”。理解它们,老周才能安心经营这家深夜食堂——锁门要快、排队要守规矩、谁也不许堵在门口等钥匙。

Trace=twrite−tread\displaystyle T_{race} = t_{write} - t_{read}
竞态窗口 = 写回共享变量的时刻 − 读取共享变量的时刻。窗口越大,其他执行流插入破坏的机会越多。
  1. t_read(读取时刻)执行流第一次读共享变量的时间点,就像老周抬头看配方本那一瞬。
    treadt_{read}
  2. t_write(写回时刻)计算完成后把新值写回共享变量的时间点。
    twritet_{write}
  3. T_race(竞态窗口)两个时刻之间的时间差,这段窗口内任何并发访问都可能造成数据错乱。
    TraceT_{race}
  4. 差值越小越安全(优化目标)窗口趋近于零,竞态概率就趋近于零,这正是临界区要做的事。
    Trace→0T_{race} \to 0

AI 应用场景

场景1:机械臂伺服控制器的中断与临界区

阶段:实时感知与控制(边缘 AI 数据采集阶段)

解决的问题:机械臂关节的电流采样中断每 50 微秒触发一次,采样值与轨迹规划任务共享“目标位置”变量。传统裸机轮询方式抖动大,且中断与任务同时读写变量会产生“半个值”的撕裂数据。利用 FreeRTOS 临界区保护共享变量,并把采样值通过 xQueueSendFromISR 传给控制任务,采样抖动从百微秒级降到个位数微秒。

具体案例:六轴机械臂的伺服驱动器中,电流采样 ISR 将结果写入队列后立即返回,FOC(磁场定向控制)任务阻塞接收;轨迹规划任务修改目标位置时用 taskENTER_CRITICAL 保护,确保中断永远不会读到写了一半的位置数据,机械臂运动平滑无抖动。

场景2:无人机飞控的传感器数据融合

阶段:多传感器时空对齐与融合预处理

解决的问题:IMU(惯性测量单元)以 1kHz 中断更新姿态数据,视觉避障任务(低优先级)并发读取共享姿态结构体,容易产生数据“撕裂”,导致 AI 误判障碍物位置。通过缩短临界区和 ISR 中只置标志位、由任务复制数据的策略,保证融合模块拿到时间戳一致的姿态快照。

具体案例:飞控代码中,IMU 中断只把最新数据写入“待发布”缓冲区并置标志;融合任务在临界区中快速复制整份姿态结构体后释放。配合延迟分析,临界区被压缩到 10 条指令以内,避障 AI 的输入永远完整一致。

场景3:医疗输液泵的安全监控

阶段:实时异常检测与安全管理

解决的问题:输液泵的堵管检测中断必须优先于显示、记录等任务,且中断内不能阻塞。利用 FromISR 后缀 API 通知高优先级安全任务,并通过 portYIELD_FROM_ISR 立即切换,保证危险情况在 10ms 内完成停机报警。

具体案例:压力传感器检测到堵管瞬间触发中断,ISR 调用 xSemaphoreGiveFromISR 唤醒安全停机任务,检查唤醒标志后请求立即切换上下文,安全任务随即执行关泵和声光报警,全程不依赖任何阻塞等待。

Python 演示

python63 行
import threadingimport time  class SharedRegister:    """模拟非原子的16位共享寄存器: 高8位和低8位分开读写"""     def __init__(self):        self.high = 0        self.low = 0     def read(self):        h = self.high        time.sleep(0.0001)          # 制造中断插入窗口        l = self.low        return h * 256 + l     def write(self, value):        self.high = value // 256        time.sleep(0.0001)          # 制造中断插入窗口        self.low = value % 256  interrupt_lock = threading.Lock()   # 模拟"关中断"进入临界区  def isr_increment(reg, protected):    for _ in range(200):        if protected:            with interrupt_lock:                v = reg.read()                reg.write(v + 1)        else:            v = reg.read()            reg.write(v + 1)  def task_increment(reg, protected):    for _ in range(200):        if protected:            with interrupt_lock:                v = reg.read()                reg.write(v + 1)        else:            v = reg.read()            reg.write(v + 1)  def run_demo(protected):    reg = SharedRegister()    t1 = threading.Thread(target=task_increment, args=(reg, protected))    t2 = threading.Thread(target=isr_increment, args=(reg, protected))    t1.start()    t2.start()    t1.join()    t2.join()    return reg.read()  unprotected = run_demo(False)protected_result = run_demo(True)print("无临界区保护最终值:", unprotected, "期望值: 400")print("临界区保护最终值:", protected_result, "期望值: 400")
目的:直观演示“读-改-写”不具备原子性时会产生竞态,而加锁(临界区保护)能保证结果正确,帮助理解 RTOS 中临界区的必要性。
  1. time.sleep(0.0001)人为拉大读高字节和读低字节之间的时间差,模拟非原子操作被中断插入的窗口。
  2. h = self.high` / `l = self.low分两次读取同一个 16 位寄存器,这就是“读”不原子的根源。
  3. with interrupt_lock:相当于 taskENTER_CRITICAL 与 taskEXIT_CRITICAL 之间的临界区,保证读-改-写一气呵成。
  4. threading.Thread(target=isr_increment, ...)一个线程模拟定时器中断服务函数,另一个模拟普通任务。
  5. run_demo(False)` 与 `run_demo(True)对比实验中唯一的变量是“是否加锁保护”,结果差异即竞态的代价。

常见误区

误区我看配置里某个中断 priority = 15,就觉得它最紧急,毕竟数字大代表级别高,像 VIP 等级一样。

正解FreeRTOS / Cortex-M 的规则恰好相反:数值越小优先级越高。0 是最紧急的中断,15 是最低。这就好比“1 号火警”远比“15 号补货通知”紧急。所以判断中断能否调用 API 时,看的是“数值是否不小于 configMAX_SYSCALL_INTERRUPT_PRIORITY”,而不是比大小谁“大”谁优先。

为何常见:日常生活中“数字大=级别高”(会员等级、工资档位)的直觉太强,与 ARM 硬件“数值小=紧急”的约定天然冲突,几乎每个初学者都会踩一次。

误区在中断服务函数里调用 `xQueueReceive` 等阻塞等待,或者直接 `vTaskDelay` 延时,觉得只要最终能返回就行。

正解ISR 没有任务那样的“阻塞-就绪”状态机,它一旦阻塞,CPU 就永远等在那里,系统直接卡死,FreeRTOS 会触发断言。ISR 里只能调用带 FromISR 后缀的非阻塞 API,只做“通知”不做“等待”,就像老周在门口只用对讲机传话,绝不站在门口等帮厨回话。

为何常见:把 ISR 误当成高优先级任务来写,忽略了中断上下文与任务上下文的本质差异。

误区我调用了 `taskENTER_CRITICAL()` 保护数据,但忘了配对 `taskEXIT_CRITICAL()`,代码跑起来好像也没什么异常。

正解进出必须严格配对。内核用嵌套计数器记录深度:enter 一次加一,exit 一次减一,只有计数器归零才真正恢复中断。少一次 exit,中断就永远被屏蔽,整个系统的实时响应瘫痪,表现为随机卡顿、外设超时。这就像锁了厨房门却把钥匙吞了,所有人都进不来。

为何常见:这类错误不会立即暴露,只有当中断全部失效、系统出现诡异卡顿时才被发现,排查成本极高。

误区既然中断优先,那就把浮点运算、日志打印、复杂业务逻辑全放 ISR 里,反正中断也响应了。

正解ISR 占用的每一微秒,都是其他所有中断的延迟来源。你在 ISR 里多待 1ms,优先级更低的中断就被拖慢 1ms,甚至错过采样周期。正确做法是 ISR 只做置标志、入队、给信号量,繁重计算放到任务里(延迟中断处理),像老周在门口只接单、回厨房再下锅。

为何常见:忽视了中断延迟的全局性——自己的 ISR 越长,整个系统的实时性越差,这是“一荣俱荣、一损俱损”的连锁反应。

面试题

Q1. 在 FreeRTOS 中,中断优先级数值越小表示什么?

单选题 · easy

  • A.实际优先级越低
  • B.与任务优先级相同
  • C.实际优先级越高← 正确答案
  • D.表示该中断被禁用

答案:C。Cortex-M/FreeRTOS 的约定是数值越小优先级越高,如同“1 号火警”比“9 号补货”紧急。A 说反了,B 混淆中断与任务两个独立优先级体系,D 是错误联想,禁用中断另有 API。

Q2. 以下哪个操作可以在 ISR 中安全执行?

单选题 · easy

  • A.调用 malloc 分配内存
  • B.调用 vTaskDelay 进行延时
  • C.调用 xQueueSendFromISR← 正确答案
  • D.阻塞等待互斥信号量

答案:C。带 FromISR 后缀的 API 是专门为中断准备的,不阻塞、只通知。ISR 中禁止延时和阻塞等待,malloc 在中断上下文中也不安全且耗时,会显著拉长中断延迟。

Q3. 关于 taskENTER_CRITICAL 与 taskEXIT_CRITICAL 的使用,正确的是?

单选题 · mid

  • A.只需调用 taskENTER_CRITICAL
  • B.进入和退出必须成对出现← 正确答案
  • C.使用 portYIELD 进入临界区
  • D.退出不需要任何额外操作

答案:B。临界区进出必须严格配对,内核用嵌套计数器管理深度,退出次数不足会导致中断永久屏蔽。portYIELD 用于触发任务切换,与进入临界区无关。

Q4. 关于临界区嵌套使用,以下说法正确的是?

单选题 · mid

  • A.嵌套时只需一次退出即可
  • B.嵌套时层层进层层出← 正确答案
  • C.临界区严禁嵌套使用
  • D.嵌套会损坏中断屏蔽机制

答案:B。FreeRTOS 临界区支持嵌套,每层 enter 加一、exit 减一,归零才恢复中断。只退一次会让浅层临界区“失效”提前开锁,而非嵌套禁用或损坏机制。

Q5. configMAX_SYSCALL_INTERRUPT_PRIORITY 表示什么?

单选题 · single

  • A.系统最高任务优先级
  • B.空闲任务的最低优先级
  • C.系统的时钟中断优先级数值
  • D.可安全调 API 的中断优先级上限← 正确答案

答案:D。该宏划定一条分界线:优先级数值不小于它的中断(即不高于它的优先级)才允许调用 RTOS API。它属于中断优先级体系而非任务优先级,也不是时钟中断专属配置。

Q6. 中断延迟通常由哪些部分组成?

多选题 · mid

  • A.硬件中断响应时间← 正确答案
  • B.最长临界区屏蔽时间← 正确答案
  • C.现场保存分发所需时间← 正确答案
  • D.任务创建删除时间开销

答案:ABC。中断延迟指从事件触发到 ISR 开始执行的时间,包含硬件响应、软件屏蔽(最长临界区)和现场保存分发。任务创建与删除是系统调用开销,不发生在中断响应路径上。

Q7. 在 ISR 中唤醒了更高优先级任务后,正确的做法是?

多选题 · hard

  • A.检查 xHigherPriorityTaskWoken 标志← 正确答案
  • B.直接阻塞等待任务调度
  • C.调用 portYIELD_FROM_ISR 请求切换← 正确答案
  • D.立即调用 vTaskDelay 让出 CPU

答案:AC。FromISR API 通过传出参数报告是否唤醒高优先级任务,若为 pdTRUE 则应调用 portYIELD_FROM_ISR 请求立即切换。ISR 中禁止阻塞或延时,等待调度或 vTaskDelay 都会导致系统卡死。

Q8. 某中断优先级数值为 5,系统调用阈值数值为 3,则?

单选题 · mid

  • A.不可调用 API,数值超过阈值
  • B.可以调用 API,在系统管理范围← 正确答案
  • C.不可调用 API,该中断优先级太低
  • D.可以调用 API,数值越大优先级越高

答案:B。数值 5 大于阈值 3,意味着该中断优先级低于系统管理上限,处于可管理范围,可以调用 FromISR API。C 的说法反了,D 的“数值越大越优先”是错误观念,A 误解了阈值语义。

Q9. 任务与中断共享一个变量时,最直接的保护方式是?

单选题 · easy

  • A.将变量声明为 volatile
  • B.提高任务优先级即可
  • C.访问时进入临界区保护← 正确答案
  • D.将变量放入只读存储器

答案:C。对共享变量的“读-改-写”要保证原子性,最直接的办法是访问时进临界区(关中断)。volatile 只防编译器优化,不防执行流交错;提高任务优先级和只读存储都解决不了并发写问题。

Q10. 以下哪些情形可能引发系统死锁?

多选题 · extreme

  • A.ISR 中等待任务持有的互斥锁← 正确答案
  • B.两个任务互相等待对方信号量← 正确答案
  • C.临界区嵌套后未配对退出← 正确答案
  • D.在 ISR 中发消息通知任务处理

答案:ABC。ISR 等任务持有的锁会造成“中断等任务、任务被中断打断”的循环冻结;任务间循环等待信号量是经典死锁;临界区未配对退出导致中断永久屏蔽、系统瘫痪。D 是 FromISR 的正确用法,不会造成死锁。