
嵌入式系统要的是按时响应。Cortex-M33 把这件事拆成七个硬件机制谁可以立刻打断、什么时候检查该不该换任务、何时才真正切换、栈怎么分开、内存怎么隔离、空闲时怎么省电。FreeRTOS 负责策略处理器负责把策略执行得又快又稳。读完可以回答三个问题1.高优先级中断为什么不用等低优先级做完2.任务切换为什么不放在 SysTick 里面做完3.换任务时哪些寄存器是硬件压栈哪些要软件自己保存七个机制各自解决一件事机制它回答的问题FreeRTOS 里你能对上的动作NVIC谁可以立刻打断当前执行中断优先级很急的中断不调用内核 APISysTick什么时候看一眼「该不该换人」vTaskDelay、超时、时间片PendSV什么时候才做完整切换taskYIELD、portYIELD_FROM_ISRSVC任务怎样进入特权代码启动调度器MPU 端口里提权MSP / PSP内核栈和任务栈会不会搅在一起每个任务一块栈异常自动换栈MPU一个任务写飞了会不会弄垮别人FreeRTOS-MPU非法访问变成故障WFI / WFE没人就绪时还要不要空转空闲任务休眠长空闲再用 tickless下面按「先响应再决定后切换」的顺序讲。问实时性靠把主频做高吗主频决定同样一段代码能跑多快。能不能在截止时间前响应还要看四件事有没有人接住•打断高优先级事件到达时低优先级是不是马上让路。•决策系统是不是周期性地检查延时、超时和时间片。•切换换任务这件重活会不会插在紧急中断中间。•隔离与能耗一个任务出错会不会扩散没事做的时候 CPU 会不会一直耗电。这四件事分别由 NVIC、SysTick、PendSV以及 MPU、WFI 接住。所以同一颗 M33调度配得好不好差别不在主频数字上。问NVIC 怎样保证高优先级尽快得到响应NVIC 管的是中断优先级。数值越小越急。它和 FreeRTOS 的任务优先级是两层中断谁先执行由 NVIC 决定任务谁先占用 CPU要等内核在 PendSV 里再选。有三种时序解决的是三种不同的「赶时间」。抢占处理函数已经在跑低优先级中断的处理函数已经开始后更高优先级到达。硬件再压一层现场立刻去执行更急的处理函数做完再回到被打断的那个中断。中间不会先回到线程。高优先级等待的时间大约是「当前指令结束」而不是「等低优先级中断全部做完」。尾链两个中断背靠背一个异常正要退出另一个异常已经在等。传统路径是出栈、回到线程、再压栈、进入下一个中断。尾链把中间这三步合成一次直接跳转。线程的现场一直留在栈上直到最后一个中断结束才恢复。对 FreeRTOS 的直接好处SysTick 末尾如果已经挂起 PendSV两者常常尾链接上不会先把旧任务恢复出来再立刻切走。迟到压栈还没完成更高优先级来得更早一点低优先级异常正在压栈处理函数还没进入。硬件只压这一次栈向量改成更急的那个。低优先级改为挂起等更急的做完再用尾链接上。和抢占的差别只在时刻抢占发生在处理函数开始之后要多压一层迟到发生在进入处理函数之前不用为低优先级先完成这次进入。问SysTick 在每个节拍里做什么SysTick 是内核的心跳。FreeRTOS 用它推进全局节拍xTickCount并在这一拍里做调度判断。常见配置是 1 ms 一拍太慢延时和时间片变粗太快每秒进出中断的次数变多有效算力下降。vTaskDelay(n)的含义是「大约 n 个节拍后再就绪」任务会离开就绪表CPU 可以去跑别人。内核把这些任务按唤醒时刻放进延时链表。每个 Tick 只做两件轻的事节拍加一再看链表头到期了没有。到期的任务移回就绪表。它不是给系统里每个延时任务做一次减法。同优先级的时间片也在这一拍里判断。打开时间片轮转后当前任务这一拍用完且还有同优先级任务在就绪内核就要求切换。大家平等分到 CPU仍然让更高优先级随时能抢进来。判断结果如果是「要换人」SysTick只把 PendSV 挂起然后马上返回。问为什么上下文切换要交给 PendSV上下文切换要保存、选择、恢复一整套任务现场比多数中断处理都重。如果在 SysTick 或某个外设中断里把它做完这段时间里所有优先级更低的中断都进不来紧急事件的延迟会被调度本身拉长。PendSV 被放在和 SysTick 相同的最低中断优先级。谁发现该换人了都只做一件轻的事挂起 PendSV。更急的外设中断可以继续抢先做完。它们全部结束后PendSV 才执行真正的切换。三条常见路径最后都汇合到这里谁发现该换人典型来源当时做的事任务自己vTaskDelay、taskYIELD、队列上等待改就绪状态挂起 PendSV心跳SysTick →xTaskIncrementTick节拍、唤醒、时间片需要就挂起 PendSV中断xQueueSendFromISR一类 API不在中断里阻塞用portYIELD_FROM_ISR挂起 PendSV把一次「有更急外设中断插进来」的过程按时间摊开就是下面这张图。② 和 ③ 期间任务 A 一直停着但切换还没开始所以这些中断的执行过程里没有夹进「保存整个任务、再选出下一个」。若中间没有更急的外设中断SysTick 结束后 PendSV 通常直接尾链进入不会先把任务 A 恢复出来再切走。还有一条边界比configMAX_SYSCALL_INTERRUPT_PRIORITY更急的中断不要调用 FreeRTOS API。它们完全不进内核延迟不受调度影响需要通知任务时把工作留到优先级允许调用 API 的中断里再用FromISR接口唤醒任务。问换任务时栈和寄存器谁来保存M33 有两个栈指针•MSP处理异常也给内核路径用。•PSP每个任务一块自己的栈。线程模式下任务使用 PSP。异常一进入硬件自动做三件事把一组寄存器压到被打断者正在用的栈上把栈指针切到 MSP把 LR 写成EXC_RETURN告诉 CPU 返回时该回到哪种模式、用哪个栈。软件不用再保存这 8 个寄存器xPSR、PC、LR、R12、R3–R0。高地址上是xPSR当前 SP 指向最底下的R0。若打开了浮点并且使能了惰性压栈浮点寄存器可以先不压真的用到再压。这样普通中断不会为了用不到的浮点现场付一次完整开销。PendSV 里软件只补硬件没压的部分并换成下一个任务的栈1.把R4–R11保存到当前任务的 PSP 栈。2.把这个 PSP 记进当前任务的 TCB下次才知道栈在哪。3.调用vTaskSwitchContext()从就绪表选出最高优先级任务。4.取出新任务的 PSP。它的栈顶上留着上次被切走时的现场。5.弹出R4–R11执行EXC_RETURN。硬件再弹出R0–R3、PC等回到线程模式开始跑新任务。所以「切换很快」有两层原因连续异常靠尾链少做进出栈单次切换里一半现场是异常进入时硬件已经压好的。问SVC、MPU、睡眠分别补上什么这三件不负责「选下一个任务」它们保证进入内核、内存和能耗这三边是稳的。SVC一条受控的特权入口SVC 指令会进入一个固定的异常处理函数用来从线程跨进特权代码。在 M33 的 FreeRTOS 端口里它的典型用途是•启动调度器跑第一个任务。•使用 MPU 时非特权任务需要提权再碰内核数据。•使用 TrustZone 时申请或释放安全侧上下文。日常的vTaskDelay、队列操作在不开 MPU 的端口里是普通函数调用。多个任务不能同时改内核链表靠的是临界区把相关中断挡住而不是每个 API 都执行一次 SVC。换任务本身仍然走 PendSV。可以把它理解成门卫少数必须提权的路从这里过已经在特权里的内核代码用临界区保护数据。MPU故障留在当前任务里MPU 按区域规定谁能读、写、执行哪一段地址。任务访问了没有权限的内存会进入故障异常而不是悄悄改写别的任务或内核。需要任务之间隔离时使用带 MPU 的 FreeRTOS 端口在任务创建时配好它的区域。单个任务崩溃可以被拦住系统还有机会记录故障、重启这个任务或进入安全状态。这是安全相关产品要单独打开的能力不是默认调度路径的一部分。WFI / WFE没有就绪任务就别空转就绪表里只剩空闲任务时空闲任务做完钩子里的家务就可以执行等待指令•WFI等到中断再继续。空闲任务里最常用。•WFE等到事件再继续包括别的核或软件发来的事件。适合「要醒但不必为此进一次中断」的情况。如果空闲只会持续一两拍每个 Tick 醒一次也可以。如果空闲会持续很多拍再为了给空闲任务数数而反复进 SysTick电就白耗了。这时打开 tickless睡前按「下一个任务还要多久醒」改写 SysTick醒来后把跳过的节拍补回去。问项目里具体怎么配目标做法紧急的事真的紧急任务优先级拉开。不能等的工作放进高优先级中断中断里只做登记和唤醒重活留给任务中断延迟不被调度拖长PendSV 和 SysTick 保持在最低一档。比configMAX_SYSCALL_INTERRUPT_PRIORITY更急的中断不调用内核 API响应和开销平衡configTICK_RATE_HZ先用 10001 ms。延时要求更细再提高空转占比变高就降回来同优先级公平需要轮转就打开configUSE_TIME_SLICING。真正有先后的工作用不同优先级不要全堆在同一级再靠时间片抢省电空闲路径上执行 WFI。可预期的长空闲再打开configUSE_TICKLESS_IDLE任务之间要隔离换用 FreeRTOS-MPU给每个任务配访问区域并打开栈溢出检查三个最容易混在一起的概念中断抢占尾链迟到任务切换比的是中断优先级中断优先级中断优先级任务优先级发生时刻低优先级 ISR 已在执行一个 ISR 正要退出另一个已在等低优先级还在压栈函数未进入PendSV 得到执行时栈怎么动再压一层做完再弹回不出栈、不再压栈只压一次然后尾链硬件帧 软件保存R4–R11换 PSP谁在用NVICNVICNVICFreeRTOS经 PendSV串起来就是一条链SysTick 给出节奏 → 内核决定换不换 → 只挂起 PendSV → NVIC 让更急的中断先做完 → PendSV 用双栈完成切换。需要隔离时加上 MPU没有就绪任务时用 WFI 把 CPU 停下。SVC 只守住那些必须提权的入口。