Cortex-M3权威指南Cn第七章——笔记

个人学习的笔记,希望帮助能到和我一样阅读Cortex-M3权威指南Cn遇到困难的人。
强烈建议先阅读Cortex-M3权威指南Cn第七章在来观看笔记



在cortex m3中,向量表是什么

向量表(Vector Table)是一个中断服务程序入口地址的数组,存储在内存的固定位置。当异常或中断发生时,处理器会自动从这里查找对应异常的处理程序地址。

向量表的结构

向量表包含以下内容(按顺序):

  1. 初始主堆栈指针(MSP)值 - 第一个字(4字节)
  2. 复位向量 - 程序开始执行的地址
  3. 异常向量 - NMI、HardFault等系统异常
  4. 外部中断向量 - IRQ0、IRQ1等中断服务程序地址
    典型向量表示例:
txt 复制代码
地址       内容                      说明
0x00000000 0x20001000               初始堆栈指针(MSP)
0x00000004 0x08000009               复位向量(Reset_Handler)
0x00000008 0x08000123               NMI_Handler
0x0000000C 0x08000145               HardFault_Handler
...
0x0000003C 0x08000234               SysTick_Handler
0x00000040 0x08000567               WWDG_IRQHandler (IRQ0)
0x00000044 0x08000789               PVD_IRQHandler  (IRQ1)
...

在非Bootloader + Application模式下,如何指向向量表?

在非Bootloader + Application模式下,即单一应用程序运行时,向量表通常固定在Flash的起始位置(例如0x08000000)。这种情况下,指向向量表的方式如下:

  1. 硬件自动指向:Cortex-M3内核在上电复位后,会自动从地址0x00000000处读取向量表。在大多数微控制器中,Flash的起始地址(如0x08000000)会被映射到0x00000000,因此向量表实际上位于Flash的起始位置。
  2. 启动文件设置:在工程中的启动文件(通常是汇编文件,如startup_stm32f10x.s)中,会定义向量表,并将其放置在Flash的起始段。链接器脚本(.ld文件)会确保向量表被链接到Flash的起始地址(即0x08000000)。
  3. 无需手动设置VTOR:在单一应用程序中,通常不需要重定位向量表,因此可以不设置VTOR寄存器,使用默认的0x00000000地址(即映射后的Flash地址)。或者,为了代码清晰,可以在系统初始化时明确将VTOR设置为Flash的起始地址(例如0x08000000),但一般都会怎么做。
  4. 系统初始化代码 :在系统初始化函数(如SystemInit)中,会设置VTOR。例如,在STM32的标准库中,SystemInit函数可能会包含如下代码:
    SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
    其中,FLASH_BASE是Flash的起始地址(如0x08000000),VECT_TAB_OFFSET通常是0。这样,向量表就被明确设置为从Flash起始地址开始。
  5. 中断处理:当发生异常或中断时,处理器会自动根据VTOR所指向的向量表(如果已设置)或默认的向量表地址(0x00000000)来获取中断服务程序的入口地址。

在Bootloader + Application模式下,如何指向向量表?

在Bootloader + Application模式下,向量表的指向分为两个阶段:Bootloader阶段和Application阶段。具体步骤如下:

  1. 系统上电/复位
    • Cortex-M3处理器从地址0x00000000读取初始主堆栈指针(MSP),从0x00000004读取复位向量(Reset_Handler地址),并跳转到复位向量执行。
    • 在大多数微控制器中,Flash的起始地址(如0x08000000)被映射到0x00000000,因此实际上处理器访问的是Bootloader的向量表。
  2. Bootloader初始化
    • Bootloader的启动代码首先执行,初始化系统环境(如时钟、必要的外设)。
    • 在Bootloader的初始化代码中(通常在SystemInit函数或类似函数中),会设置向量表偏移寄存器(VTOR)指向Bootloader自己的向量表起始地址(例如0x08000000)。这一步确保在Bootloader运行期间,任何异常或中断发生时,处理器能正确跳转到Bootloader的中断服务程序。
  3. Bootloader运行
    • Bootloader执行其主要功能,例如检查是否需要更新Application、验证Application完整性等。
    • 在此期间,所有中断都使用Bootloader的向量表。
  4. 跳转到Application前的准备
    • 当Bootloader决定跳转到Application时,它首先确保Application是有效的(例如,检查Application向量表的第一个字是否在合理的栈地址范围内)。
    • 然后,Bootloader执行以下关键步骤:
      a. 禁用全局中断,防止在切换过程中发生中断。
      b. 将VTOR寄存器重新设置为Application向量表的起始地址(例如0x08004000)。这样,当跳转到Application后,中断将使用Application的向量表。
      c. 从Application向量表的第一个字(地址0x08004000)读取初始主堆栈指针值,并更新MSP寄存器。
      d. 从Application向量表的第二个字(地址0x08004004)读取Application的复位向量(即Application的Reset_Handler地址)。
  5. 跳转到Application
    • Bootloader将Application的Reset_Handler地址转换为函数指针,并跳转到该地址执行。
    • 此时,处理器已经使用Application的堆栈指针,并且VTOR指向Application的向量表。
  6. Application初始化
    • Application的启动代码开始执行,进行Application的系统初始化(如时钟、内存、外设等)。
    • 在Application的初始化代码中,通常也会设置VTOR指向自己的向量表(尽管Bootloader已经设置过,但为了确保一致性,Application会重新设置一次)。
  7. Application运行
    • Application正常运行,所有中断服务程序由Application提供,处理器根据VTOR指向的Application向量表来查找中断处理函数。
  8. 异常情况
    • 如果在Application运行过程中发生复位,处理器会再次从0x00000000(映射到Bootloader的向量表)开始执行,进入Bootloader。Bootloader可以再次决定是否跳转到Application。
      注意事项
  • VTOR寄存器设置时,向量表起始地址必须按照向量表大小对齐(通常为128字节对齐)。
  • 在跳转前禁用中断,跳转后再启用,可以避免在切换过程中出现不可预料的中断行为。
  • 有些Bootloader设计可能不会在跳转前禁用中断,而是确保在切换VTOR和堆栈指针的过程中不会发生中断(如通过优先级或确保没有中断发生)。
    通过这种方式,实现了Bootloader和Application各自拥有独立的向量表,并且在运行时可以正确切换。

以下为Cortex‐M3 权威指南原文:

当发生了异常并且要响应它时,CM3需要定位其处理例程的入口地址。这些入口地址存储在所谓的"(异常)向量表"中。缺省情况下,CM3认为该表位于零地址处,且各向量占用4字节,因此每个表项占用4字节。

因为地址0处应该存储引导代码,所以它通常是Flash或者是ROM器件,并且它们的值不得在运行时改变。然而,为了动态重分发中断,CM3允许向量表重定位------从其它地

址处开始定位各异常向量。这些地址对应的区域可以是代码区,但也可以是RAM区。在RAM区就可以修改向量的入口地址了。为了实现这个功能,NVIC中有一个寄存器,称为"向量表偏移量寄存器"(在地址0xE000_ED08处),通过修改它的值就能定位向量表。但必须注意的是:向量表的起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上增大到是2的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有32个中断,则共有32+16(系统异常)=48个向量,向上增大到2的整次幂后值为64,因此地址地址必须能被64*4=256整除,从而合法的起始地址可以是:0x0,0x100,0x200等。

如果需要动态地更改向量表,则对于任何器件来说,向量表的起始处都必须包含以下向量:

  • 主堆栈指针(MSP)的初始值
  • 复位向量
  • NMI
  • 硬fault服务例程

后两者也是必需的,因为有可能在引导过程中发生这两种异常。

可以在SRAM中开出一块用于存储向量表。然后在引导完成后,就可以启用内存中的向量表,从而实现向量可动态调整的功能。



在cortex m3中,优先级分组,抢占优先级、子优先级(亚优先级)

定义:
优先级分组 → 定义拆分规则
抢占优先级 → 决定中断嵌套能力
亚优先级 → 决定相同抢占优先级下的执行顺序

详细解释:

  1. 优先级分组(Priority Grouping)
  • 通过设置 PRIGROUP 字段(位于AIRCR寄存器的bits[10:8])来定义
  • 决定如何分配优先级位给抢占优先级和亚优先级
  • 共有8种分组方案(0-7)
    2. 抢占优先级(Preemption Priority)
  • 也称为主优先级(Group Priority)
  • 高抢占优先级的中断可以打断低抢占优先级的中断(嵌套中断)
  • 数值越小,优先级越高
    3. 亚优先级(Subpriority)
  • 也称为次要优先级
  • 当两个中断的抢占优先级相同时,亚优先级决定谁先执行
  • 亚优先级不能引起中断嵌套(相同抢占优先级的中断不能互相打断)

实际的工作方式:

每个中断的优先级由8位表示(但Cortex-M3通常只实现高4位[7:4],即16个优先级等级)。
优先级分组决定了这4位如何拆分

  • 例如:分组2 → 2位用于抢占优先级,2位用于亚优先级
    • 抢占优先级:0-3(4级)
    • 亚优先级:0-3(4级)
  • 例如:分组3 → 3位用于抢占优先级,1位用于亚优先级
    • 抢占优先级:0-7(8级)
    • 亚优先级:0-1(2级)

以下为Cortex‐M3 权威指南原文:

在CM3中,优先级对于异常来说很关键的,它会影响一个异常是否能被响应,以及何时可以响应。优先级的数值越小,则优先级越高。CM3支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。有3个系统异常:复位,NMI以及硬fault,它们有固定的优先级,并且它们的优先级号是负数,从而高于所有其它异常。所有其它异常的优先级则都是可编程的(但不能编程为负数)。

原则上,CM3支持3个固定的高优先级和多达256级的可编程优先级,并且支持128级抢占(128的来历请见下文分解------译注)。但是,绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数会更少,如8级,16级,32级等。它们在设计时会裁掉表达优先级的几个低端有效位,以达到减少优先级数的目的(可见,不管使用多少位,优先级号是以MSB对齐的------译注)。

通过让优先级以MSB对齐,可以简化程序的跨器件移植。比如,如果一个程序早先在支持4位优先级的器件上运行,在移植到只支持3位优先级的器件后,其功能不受影响。但若是对齐到LSB,则会使MSB丢失,导致数值大于7的低优先级一下子升高了,甚至会反转小于等于7的高优先级。如,8号优先级因为损失了MSB,现在反而变成0号了!


补充:
MSB对齐(最高有效位对齐,Most Significant Bit Alignment)

  • MSB(最高有效位) :指一个二进制数中权重最大的位(最左边的位)
  • MSB对齐 :将数据的最高有效位对齐到寄存器的最高位位置
    LSB对齐(最低有效位对齐, Least Significant Bit Alignment)
  • LSB(最低有效位) :指一个二进制数中权重最小的位(最右边的位)
  • LSB对齐:将数据的最低有效位对齐到寄存器的最低位位置

实际示例

假设我们有一个8位寄存器,要存储数值 5(二进制 0101):
LSB对齐

txt 复制代码
寄存器位: 7 6 5 4 3 2 1 0
存储的值: 0 0 0 0 0 1 0 1  ← 5的二进制
           ↑             ↑
          MSB          LSB(对齐到bit 0)

数值直接放在低位,高位补0。大多数处理器(如x86, ARM)使用LSB对齐。
MSB对齐

txt 复制代码
寄存器位: 7 6 5 4 3 2 1 0
存储的值: 0 1 0 1 0 0 0 0  ← 5左移4位
           ↑             ↑
          MSB(对齐到bit 7) LSB

Cortex-M3的中断优先级使用MSB对齐:

  • Cortex-M3的优先级寄存器是8位宽,但通常只实现高4位[7:4]
  • 优先级数值必须左移到高4位

以下为Cortex‐M3 权威指南原文:

抢占优先级决定了抢占行为:当系统正在响应某异常L时,如果来了抢占优先级更高的异常H,则H可以抢占L。亚优先级则处理"内务":当抢占优先级相同的异常有不止一个悬起时,就优先响应亚优先级最高的异常。

这种优先级分组规定:亚优先级至少是1个位。因此抢占优先级最多是7个位,造成了最多只有128级抢占的现象。

但是CM3允许从比特7处分组,此时所有的位都表达亚优先级,没有任何位表达抢占优先级,因而所有优先级可编程的异常之间就不会发生抢占------相当于在它们之中除能了CM3的中断嵌套机制。当然还有凌架于法律之上的三位老大:复位,NMI和硬fault。它们无论何时出现,都立即无条件抢占所有优先级可编程的"平民异常"。

如果优先级完全相同的多个异常同时悬起,则先响应异常编号最小的那一个。如IRQ#3会比IRQ#5先得到响应。

虽然优先级分组的功能很强大,但是粗心地更改会使它变得很暴力,尤其是在设计硬实时系统的时候,这简直就是在玩火------常常会改变系统的响应特性,导致某些关键任务有可能得不到及时响应,凶多吉少的意外随时可能猛烈发作。其实在绝大多数情况下,优先级的分组都要预先经过计算论证,并且在开机初始化时一次性地设置好,以后就再也不动它了。只有在绝对需要且绝对有把握时,才小心地更改,并且要经过尽可能充分的测试。



在cortex m3中,中断(异常)状态、响应条件、转换行为

一、中断(异常)的四种状态
非活动状态 (Inactive)

  • 中断未触发,也未悬起
  • 中断源未发出请求,或请求已被完全处理
    悬起状态 (Pending)
  • 中断源已发出请求,但处理器尚未开始执行对应的中断服务程序(ISR)
  • 悬起状态被记录在NVIC的悬起寄存器中
  • 即使中断源随后取消了请求,悬起状态仍保持
    活动状态 (Active)
  • 处理器正在执行该中断的ISR
  • 中断的"活动"状态位被置位
  • 此时该中断不能再次被响应(除非是NMI或可重入中断)
    活动且悬起状态 (Active and Pending)
  • 处理器正在执行该中断的ISR(活动状态)
  • 同一中断源在此期间又发出了新的请求(悬起状态)
  • 只有当前ISR执行完毕后,才会再次响应这个中断

二、状态转换条件

状态转换图的关键路径:
非活动 → 悬起

  • 条件:中断源发出请求(硬件触发或软件写悬起寄存器)
  • 行为:NVIC将对应悬起位置1
    悬起 → 活动
  • 条件:同时满足所有响应条件(见下文第三部分)
  • 行为:
    a. 处理器保存上下文(自动压栈)
    b. 从向量表获取ISR入口地址
    c. 清除该中断的悬起位(边沿触发中断)
    d. 设置中断的活动状态位
    e. 开始执行ISR
    活动 → 非活动
  • 条件:ISR执行完毕,执行异常返回指令(BX LR等)
  • 行为:
    a. 处理器恢复上下文(自动出栈)
    b. 清除中断的活动状态位
    c. 返回到被中断的程序
    活动 → 活动且悬起
  • 条件:当前中断正在执行时,同一中断源再次触发
  • 行为:设置悬起位,但保持活动状态
    活动且悬起 → 活动
  • 条件:当前ISR执行完毕,但悬起位仍为1
  • 行为:再次响应同一中断(重新进入ISR)
    悬起 → 非活动
  • 条件:软件主动清除悬起位
  • 行为:中断被取消,不会得到响应

三、中断响应条件(必须同时满足)
中断使能条件

  • 全局中断使能:PRIMASK=0, FAULTMASK=0
  • 该特定中断的使能位被置位(NVIC_ISER寄存器)
    优先级条件
  • 该中断的优先级高于当前执行程序的优先级
  • 该中断的优先级高于BASEPRI寄存器设置的值(如果使用了BASEPRI)
  • 该中断的优先级是当前所有悬起中断中最高的
    状态条件
  • 中断处于悬起状态(悬起位=1)
  • 中断不处于活动状态(除非是可重入的特殊情况)
    处理器状态条件
  • 处理器未在处理不可中断的操作(如某些原子操作)
  • 处理器未处于锁定状态(如某些调试状态)

具体行为:
中断响应序列

  • 压栈:自动保存xPSR, PC, LR, R12, R3-R0到当前堆栈
  • 取向量:从向量表读取ISR入口地址
  • 更新寄存器:更新PC, LR, SP, PSR
  • 执行ISR
    中断退出序列
  • 出栈:自动恢复R0-R3, R12, LR, PC, xPSR
  • 更新NVIC状态:清除活动状态位
  • 继续执行被中断的程序

以下为Cortex‐M3 权威指南原文:

当中断输入脚被 assert 后,该中断就被悬起。即使后来中断源取消了中断请求,已经被标记成悬起的中断也被记录下来。到了系统中它的优先级最高的时候,就会得到响应。

但是,如果在某个中断得到响应之前,其悬起状态被清除了(例如,在 PRIMASK 或FAULTMASK 置位的时候软件清除了悬起状态标志),则中断被取消。

简单来说,这就像有人按了你家的门铃。

  1. 按门铃(中断触发)
    有人按了门铃(中断输入脚被触发)。这时,门铃的"呼叫灯"会亮起(中断被标记为"悬起"状态),表示"有人叫过门"。
  2. 记录呼叫(保持悬起)
    即使这个人马上松手不按了(中断源取消请求),"呼叫灯"依然亮着(悬起状态被记录)。这个"有人叫过门"的事实已经被系统记住了。
  3. 准备开门(等待响应)
    当你手头的事忙完了,并且这个门铃的优先级最高时(比如没有其他更紧急的事),你就会去响应------准备开门。
  4. 关键情况:灯被提前关掉了(清除悬起状态)
    然而,如果在你去开门之前 ,有人(可能是你自己,也可能是其他程序)手动把"呼叫灯"给关掉了(在PRIMASK/FAULTMASK屏蔽中断时,软件清除了悬起标志),那么会发生什么?
  5. 中断被取消(响应失效)
    当你终于准备去开门时,却发现"呼叫灯"是灭的。你会认为"没人按过门铃",于是就不会去开门了。这个中断就此消失,永远不会得到响应,即使门铃确实被按过。

以下为Cortex‐M3 权威指南原文:

当某中断的服务例程开始执行时,就称此中断进入了"活跃"状态,并且其悬起位会被硬件自动清除。在一个中断活跃后,直到其服务例程执行完毕,并且返回(亦称为中断退出,第九章详细讨论)了,才能对该中断的新请求予以响应(即单实例)。当然,新请求的响应亦是由硬件自动清零悬起标志位。中断服务例程也可以在执行过程中把自己对应的中断重新悬起(使用时要注意避免进入"死循环"------译注)

这就像一个客服电话的接听流程:
1. 接听电话(进入"活跃"状态)

  • 当客服代表(CPU)终于接起一个已经亮灯响铃的来电(中断)时,这个来电的状态就从 "等待中" (悬起)变成了 "通话中"(活跃)。
  • 关键动作 :代表接起电话的瞬间,"来电等待灯"会自动熄灭 (硬件自动清除悬起位)。这代表系统已经知道这个请求正在被处理,无需再记录"等待"状态。
    2. 通话过程中(保持"活跃"状态)
  • 在客服代表处理这个电话(执行中断服务例程)的整个期间,该电话都处于 "通话中" 状态。
  • 单实例规则 :在这通电话挂断之前,即使同一个客户再次打进来(同一中断源发出新请求),客服代表也不会接听 。新来的电话会再次亮起"等待灯"(新的悬起请求被记录),但必须等到当前通话结束后才会被处理。
    3. 挂断电话(中断退出)
  • 客服代表处理完毕,挂断电话。此时,这个来电的 "通话中" 状态结束。
  • 只有到了这个时候,如果"来电等待灯"还亮着(新的悬起标志存在),客服代表才会立刻接起下一通电话 (响应新的请求),并再次重复上述过程。
    4. 特殊情况:通话中自己"重拨"(软件重新悬起)
  • 这段话最后提到,客服代表(中断服务例程自己)可以在通话过程中,主动按下"重拨"键(软件将自己对应的中断重新悬起)。
  • 这意味着当前电话刚挂断,系统会立刻认为又有同一个电话打进来(悬起位被软件置1),从而可能让客服代表马上又开始处理一个相同的新请求。
  • 风险提示 :如果设计不当,这会导致"挂断 -> 重拨 -> 接听 -> 挂断 -> 重拨..."的无限循环,也就是编程中必须避免的死循环。'


在cortex m3中,SVC是什么

SVC(Supervisor Call)是Cortex-M3内核中的一种系统调用异常,它允许运行在非特权模式(用户模式)下的应用程序通过执行SVC指令来请求特权模式(处理器模式)下的操作系统服务。

  1. SVC指令
    • 当程序执行SVC指令时,会触发SVC异常,从而进入异常处理程序(即SVC服务例程)。
    • SVC指令带有一个8位的立即数(例如:SVC #0x03),这个立即数可以作为参数传递给SVC服务例程,用于区分不同的系统服务请求。
  2. SVC异常编号
    • 在异常向量表中,SVC异常的位置是11(即向量表偏移为0x2C)。
  3. 触发方式
    • 只能通过执行SVC指令触发,不能由外部硬件或软件写寄存器来触发。
  4. 优先级
    • SVC异常的优先级可以配置(通过设置系统异常优先级寄存器,地址0xE000ED1C)。通常,操作系统会将其配置为较高的优先级,但低于不可屏蔽中断(NMI)和硬错误(HardFault)。
  5. SVC服务例程
    • 当SVC异常被触发,处理器会进入SVC服务例程。在该例程中,操作系统可以根据SVC指令中的立即数(参数)来提供相应的服务。
    • 为了获取SVC指令中的立即数,SVC服务例程需要从堆栈中读取被中断的程序的程序计数器(PC),然后从该地址读取SVC指令,最后提取立即数。
  6. 使用场景
    • SVC通常用于实现操作系统的系统调用接口。应用程序运行在非特权模式,当需要操作系统提供的服务(如文件操作、任务创建等)时,就通过SVC指令切换到特权模式,由操作系统内核执行相应的服务。
  7. 注意点
    • 在SVC服务例程中,不能再次触发SVC异常(即不能嵌套),除非已经修改了SVC的优先级。通常情况下,SVC异常被设计为不可重入,因此在其执行过程中,即使有更低优先级的SVC请求,也不会被响应,直到当前SVC例程执行完毕。

下面我们使用比喻的方式来说明一下:

想象一家现代化的餐厅,这里有:

  • 顾客 = 用户程序(在普通区就餐,有限权限)
  • 服务员 = SVC机制(连接顾客和后厨的桥梁)
  • 厨师 = 操作系统内核(在后厨工作,有高级权限)
  • 后厨 = 特权模式区域(普通顾客不能进入)

操作流程:
1. 顾客要点餐(用户程序需要服务)

顾客坐在用餐区(非特权模式),想要点一份牛排。但顾客不能直接闯入后厨自己做,因为:

  • 后厨有危险设备(系统关键资源)
  • 需要专业厨师操作(特权操作)
  • 需要统一管理(系统安全)
    2. 按下服务铃(执行SVC指令)
    顾客按下桌上的服务铃按钮,这相当于执行:
c 复制代码
SVC #0x02  // 0x02代表"点餐服务"

同时,顾客在点餐单上写下:

  • 菜品编号:0x02(牛排)
  • 具体要求 :七分熟,黑椒酱(通过寄存器传递参数R0、R1等)
    3. 服务员响应(触发SVC异常)
    服务员听到铃声后:
  • 立即停下手头工作(处理器暂停当前任务)
  • 拿起点餐单(硬件自动保存现场到栈)
  • 进入后厨通道(切换到Handler模式/特权模式)
  • 将点餐单交给厨师 (跳转到SVC_Handler)
    4. 厨师处理订单(操作系统提供服务)
    厨师(操作系统内核)看到点餐单:
c 复制代码
void SVC_Handler(void)
{
    // 查看是什么服务请求
    switch(订单类型) {
        case 0x01: // 饮料服务
            break;
        case 0x02: // 牛排服务 <- 这个!
            cook_steak(R0, R1); // R0=熟度,R1=酱料
            break;
        case 0x03: // 甜点服务
            break;
    }
}

厨师使用后厨的所有设备和食材(特权资源)制作牛排。
5. 服务员送回餐点(返回结果)

服务员:

  • 把做好的牛排端给顾客(通过寄存器返回结果)
  • 按一下"服务完成"按钮(执行异常返回指令)
  • 顾客继续用餐(用户程序继续执行)

为什么需要这套系统?
安全控制

如果每个顾客都能随便进后厨:

  • 可能会弄伤自己(程序崩溃)
  • 可能偷拿食材(非法访问内存)
  • 可能搞乱后厨(破坏系统状态)
    统一管理
  • 所有点餐通过服务员登记(统一入口)
  • 厨师按顺序处理(避免竞争条件)
  • 后厨资源有序使用(资源管理)
    灵活扩展
    餐厅可以轻松增加新服务:
  • 新菜品 = 新的SVC服务编号
  • 服务员总能找到对应厨师(SVC_Handler分发)
  • 顾客无需知道怎么做菜(用户程序不关心实现)

特殊场景
1. 多点几个菜(多个参数)

顾客想同时点牛排、沙拉和饮料:

  • 填写多行点餐单(多个寄存器传参)
  • 服务员一次传递给厨师
  • 厨师按顺序制作
    2. 催菜服务(优先级)
    如果顾客按下紧急服务铃(更高优先级SVC):
  • 服务员会优先处理这个请求
  • 但不会打断正在做菜的厨师(SVC_Handler执行中不能被同优先级打断)
    3. 无效点餐(错误的SVC编号)
    顾客点了菜单上没有的菜(无效SVC编号):
  • 服务员无法处理
  • 叫来餐厅经理(触发HardFault)
  • 经理处理这个错误

以下为Cortex‐M3 权威指南原文:

SVC(系统服务调用,亦简称系统调用)和 PendSV(可悬起系统调用),它们多用于在操作系统之上的软件开发中。SVC 用于产生系统函数的调用请求。例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就会产生一个 SVC 异常,然后操作系统提供的 SVC 异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。

这种"提出要求------得到满足"的方式,很好、很强大、很方便、很灵活、很能可持续发展。首先,它使用户程序从控制硬件的繁文缛节中解脱出来,而是由 OS 负责控制具体的硬件。第二,OS 的代码可以经过充分的测试,从而能使系统更加健壮和可靠。第三,它使用户程序无需在特权级下执行,用户程序无需承担因误操作而瘫痪整个系统的风险。第四,通过 SVC 的机制,还让用户程序变得与硬件无关,因此在开发应用程序时无需了解硬件的操作细节,从而简化了开发的难度和繁琐度,并且使应用程序跨硬件平台移植成为可能。开发应用程序唯一需要知道的就是操作系统提供的应用编程接口(API),并且了解各个请求代号和参数表,然后就可以使用 SVC 来提出要求了(事实上,为使用方便,操作系统往往会提供一层封皮,以使系统调用的形式看起来和普通的函数调用一致。各封皮函数会正确使用 SVC指令来执行系统调用------译者注)。其实,严格地讲,操作硬件的工作是由设备驱动程序完成的,只是对应用程序来说,它们也是操作系统的一部分。

SVC 异常通过执行"SVC"指令来产生。该指令需要一个立即数,充当系统调用代号。SVC异常服务例程稍后会提取出此代号,从而解释本次调用的具体要求,再调用相应的服务函数。例如:

SVC 0x3 ; 调用 3 号系统服务

在 SVC 服务例程执行后,上次执行的 SVC 指令地址可以根据自动入栈的返回地址计算出。找到了 SVC 指令后,就可以读取该 SVC 指令的机器码,从机器码中萃取出立即数,就获知了请求执行的功能代号。如果用户程序使用的是 PSP,服务例程还需要先执行 MRS Rn,PSP 指令来获取应用程序的堆栈指针。通过分析 LR 的值,可以获知在 SVC 指令执行时,正在使用哪个堆栈(细节在第 8 章中讨论)。



在cortex m3中,PendSV是什么

在Cortex-M3中,PendSV(Pendable Service Call)是一种可挂起的异常,主要用于操作系统中的上下文切换(任务切换)。其特点是可以被挂起,直到系统没有其他重要的异常处理时才会执行。这使得PendSV非常适合于在多个任务之间进行切换,而不会影响到高优先级的中断处理。

PendSV的典型使用场景是在操作系统中,当需要执行任务切换时,不是立即切换,而是通过将PendSV异常挂起,然后在没有其他中断处理时再执行切换。这样可以避免在中断处理过程中进行上下文切换,从而使得中断响应更加及时,同时也能保证任务切换的顺利进行。

具体来说,PendSV的使用步骤通常如下:

  1. 设置PendSV的优先级为最低(这样它不会打断其他中断)。
  2. 当需要任务切换时,将PendSV异常挂起(即将中断控制和状态寄存器ICSR中的PENDSVSET位置1)。
  3. 当处理器退出所有中断处理程序后,它会检查挂起的PendSV异常,并进入PendSV异常处理程序。
  4. 在PendSV异常处理程序中,执行实际的上下文切换(保存当前任务的上下文,恢复下一个任务的上下文)。

由于PendSV的优先级最低,所以它会在所有高优先级中断处理完成后才执行,这保证了高优先级中断的响应性,同时将任务切换延迟到一个合适的时机。

在Cortex-M3中,PendSV的异常编号为14,可以通过系统控制块(SCB)中的寄存器来设置其优先级和挂起。


下面我们使用比喻来理解他:
比喻场景:医院的急诊调度系统

想象一家繁忙的医院:
角色对应

  • 急诊病人 = 硬件中断(紧急,必须立即处理)
  • 普通住院病人 = 操作系统任务(需要处理,但不紧急)
  • 护士 = 中断服务程序
  • 住院部医生 = 操作系统调度器
  • PendSV = "请医生稍后来查房"的调度机制

PendSV的关键特性
1. 延迟执行机制

  • 可挂起:请求可以被记录,但不立即执行
  • 优先级最低:PendSV的优先级通常设为最低(0xFF)
  • 等待时机 :等到所有更高优先级中断都完成后才执行
    2. 在RTOS中的典型应用
c 复制代码
// FreeRTOS中的PendSV使用(简化版)
void xPortPendSVHandler(void)
{
    __asm volatile (
        // 保存当前任务上下文
        "mrs r0, psp \n"
        "stmdb r0!, {r4-r11, lr} \n"
        
        // 保存当前任务栈指针
        "ldr r1, =pxCurrentTCB \n"
        "ldr r1, [r1] \n"
        "str r0, [r1] \n"
        
        // 选择下一个任务
        "bl vTaskSwitchContext \n"
        
        // 恢复下一个任务的上下文
        "ldr r1, =pxCurrentTCB \n"
        "ldr r1, [r1] \n"
        "ldr r0, [r1] \n"
        "ldmia r0!, {r4-r11, lr} \n"
        
        // 恢复栈指针
        "msr psp, r0 \n"
        
        // 异常返回
        "bx lr \n"
    );
}

3. 与SVC的对比

特性 PendSV SVC
触发方式 可挂起(异步) 立即执行(同步)
执行时机 延迟到合适时机 立即响应
优先级 通常设为最低 根据需要设置
主要用途 任务切换 系统调用
比喻 医生查房(等病人稳定后) 急诊手术(立即处理)

PendSV处理程序模板

assembly 复制代码
PendSV_Handler:
    CPSID I                   ; 禁用中断
    MRS R0, PSP              ; 获取当前任务的栈指针
    CBZ R0, PendSV_Handler_NoSave  ; 如果是第一次切换,无需保存
    
    ; 保存上下文(R4-R11)
    STMDB R0!, {R4-R11}
    
    ; 保存当前任务栈指针到TCB
    LDR R1, =CurrentTCB
    LDR R1, [R1]
    STR R0, [R1]
    
PendSV_Handler_NoSave:
    ; 选择下一个任务
    LDR R0, =NextTCB
    LDR R0, [R0]
    LDR R1, [R0]
    
    ; 恢复上下文(R4-R11)
    LDMIA R1!, {R4-R11}
    
    ; 更新PSP
    MSR PSP, R1
    
    ; 更新当前TCB指针
    LDR R1, =CurrentTCB
    STR R0, [R1]
    
    CPSIE I                   ; 启用中断
    BX LR                     ; 返回,开始新任务

FreeRTOS示例

c 复制代码
// FreeRTOSConfig.h中配置
#define xPortPendSVHandler PendSV_Handler

// 任务切换函数
void vPortYieldFromISR(void)
{
    // 从ISR中触发任务切换
    portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}

// PendSV处理程序(实际上下文切换代码)
__asm void xPortPendSVHandler(void)
{
    extern uxCriticalNesting;
    extern pxCurrentTCB;
    extern vTaskSwitchContext;
    
    PRESERVE8
    
    mrs r0, psp
    isb
    
    ldr r3, =pxCurrentTCB
    ldr r2, [r3]
    
    stmdb r0!, {r4-r11, r14}
    str r0, [r2]
    
    stmdb sp!, {r3}
    mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
    msr basepri, r0
    dsb
    isb
    bl vTaskSwitchContext
    mov r0, #0
    msr basepri, r0
    ldmia sp!, {r3}
    
    ldr r1, [r3]
    ldr r0, [r1]
    ldmia r0!, {r4-r11, r14}
    msr psp, r0
    isb
    bx r14
    nop
}

以下为Cortex‐M3 权威指南原文:

另一个相关的异常是 PendSV(可悬起的系统调用),它和 SVC 协同使用。一方面,SVC异常是必须立即得到响应的(若因优先级不比当前正处理的高,或是其它原因使之无法立即响应,将上访成硬 fault------译者注),应用程序执行 SVC 时都是希望所需的请求立即得到响应。另一方面,PendSV 则不同,它是可以像普通的中断一样被悬起的(不像 SVC 那样会上访)。OS 可以利用它"缓期执行"一个异常------直到其它重要的任务完成后才执行动作。悬起 PendSV 的方法是:手工往 NVIC 的 PendSV 悬起寄存器中写 1。悬起后,如果优先级不够高,则将缓期等待执行。

PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:

  • 执行一个系统调用
  • 系统滴答定时器(SYSTICK)中断,(轮转调度中需要)


在cortex m3中,SVC中断、PendSV中断、普通中断

为了方便理解这里将使用比喻来帮助我们理解他们:

比喻一:餐厅后厨工作流程

想象一个高效的餐厅后厨系统:
角色对应

  • 厨师(CPU):正在制作菜品(执行主程序)
  • 传菜口铃声(普通硬件中断):有新订单来了,必须立即处理
  • 经理呼叫按钮(SVC中断):厨师需要申请特殊权限(如开保险柜取高级食材)
  • 交班闹钟(PendSV中断) :换班时间到,但会等手头工作完成再交接
    三种中断的运作场景
    1. 传菜口铃声(普通硬件中断)
c 复制代码
// 情景:厨师正在切菜,传菜口突然响了
"叮咚!"  // 外部中断(如GPIO中断)

// 厨师反应:
1. 立即放下菜刀(保存当前状态)
2. 跑到传菜口(跳转到中断处理程序)
3. 拿取新订单(读取中断数据)
4. 确认收到(清除中断标志)
5. 回到原位继续切菜(恢复现场)

// 特点:必须立即响应,优先级最高

2. 经理呼叫按钮(SVC中断)

c 复制代码
// 情景:厨师需要取昂贵的松露,但保险柜只有经理能开
厨师按下"经理呼叫"按钮  // 执行SVC指令
"SVC #0x03"  // 3号服务:开保险柜

// 系统反应:
1. 厨师进入等待区(从用户模式切换到特权模式)
2. 经理过来验证身份(操作系统检查权限)
3. 经理打开保险柜(执行特权操作)
4. 厨师取出松露(获得请求的资源)
5. 厨师返回工作岗位(返回到用户模式)

// 特点:主动请求,同步执行,用于获取系统服务

3. 交班闹钟(PendSV中断)

c 复制代码
// 情景:下午4点换班时间到,但厨师A正在处理紧急订单
闹钟响起:"该换班了!"  // SysTick定时器到期

// 但实际情况:
1. 闹钟响起(SysTick中断触发)
2. 厨师A举手示意:"知道了,但请等我做完这道菜"
3. 系统记录:"需要换班,但可以等待"(挂起PendSV)
4. 厨师A继续完成紧急订单(处理更高优先级中断)
5. 所有紧急订单完成后(更高优先级中断都处理完)
6. 系统提醒:"现在可以换班了"(执行PendSV_Handler)
7. 厨师A与厨师B安全交接(任务切换)

// 特点:延迟执行,优先级最低,用于耗时的上下文切换

三者的互动

c 复制代码
// 餐厅一天的工作流程示例
1. 厨师A正在准备食材(主程序运行)
2. 突然多个订单同时来(多个硬件中断)
3. 厨师A处理紧急订单(响应硬件中断)
4. 厨师A需要特殊调料(按下经理呼叫按钮/SVC)
5. 经理送来调料(SVC处理程序执行)
6. 换班时间到(PendSV被挂起)
7. 但还有VIP订单(更高优先级中断)
8. 处理完VIP订单后(所有高优先级中断完成)
9. 厨师A与厨师B换班(PendSV执行)

比喻二:操作系统中的交通管制系统

想象一个智能城市交通管理系统:
角色对应

  • 交通指挥中心(CPU):管理整个城市的交通
  • 交通事故报警(普通硬件中断):突发事故,必须立即处理
  • 交警权限申请(SVC中断):交警需要特殊权限(如封锁道路)
  • 交通信号计划切换(PendSV中断) :早晚高峰信号灯计划切换
    三种中断的运作场景
    1. 交通事故报警(普通硬件中断)
c 复制代码
// 情景:十字路口发生车祸
报警电话响起:"中山路发生事故!"  // 外部中断触发

// 指挥中心反应:
1. 立即中断当前工作(保存现场)
2. 调取事故地点监控(读取中断源)
3. 派遣最近交警(执行中断处理程序)
4. 疏导交通(处理中断事务)
5. 返回原工作计划(中断返回)

// 特点:紧急、不可预测、必须立即响应

2. 交警权限申请(SVC中断)

c 复制代码
// 情景:交警需要临时封锁一条道路进行活动
交警使用对讲机:"请求封锁解放路"  // 用户程序执行SVC

// 系统流程:
1. 交警身份验证(从用户模式切换到特权模式)
2. 检查申请合法性(操作系统安全检查)
3. 授权封锁指令(执行特权操作)
4. 更新交通信号系统(修改系统状态)
5. 返回确认信息(SVC返回结果)

// 特点:主动、可控、用于系统服务调用

3. 交通信号计划切换(PendSV中断)

c 复制代码
// 情景:晚高峰时间(17:00)到了,需要切换信号灯计划
时钟到17:00:触发定时器中断  // SysTick中断

// 但实际处理:
1. 系统检测到17:00(SysTick中断发生)
2. 标记"需要切换信号计划"(挂起PendSV)
3. 但此时有交通事故正在处理(更高优先级中断)
4. 等待事故处理完毕(等待高优先级中断完成)
5. 所有紧急事件处理完后(无更高优先级中断)
6. 执行信号计划切换(PendSV_Handler)
7. 平滑过渡到晚高峰模式(任务切换)

// 特点:计划性、可延迟、优先级最低

操作系统中的具体代码对应

c 复制代码
// 交通管制系统代码示例

// 1. 事故报警处理(硬件中断)
void Accident_IRQHandler(void)  // 硬件中断处理程序
{
    // 立即响应
    dispatch_police();    // 派遣交警
    redirect_traffic();   // 疏导交通
    clear_accident_flag(); // 清除中断标志
}

// 2. 交警权限申请(SVC系统调用)
void Traffic_Officer_Request(void)
{
    // 用户程序(交警)请求
    asm volatile (
        "MOV R0, %0 \n"   // 参数:道路ID
        "SVC #0x05 \n"    // 5号服务:封锁道路
        : 
        : "r" (road_id)
    );
}

// SVC处理程序
void SVC_Handler(void)
{
    uint8_t svc_num = get_svc_number();
    if (svc_num == 0x05) {
        // 检查权限
        if (check_authorization()) {
            block_road(R0);  // 特权操作:封锁道路
            set_return_value(SUCCESS);
        }
    }
}

// 3. 信号计划切换(PendSV调度)
void SysTick_Handler(void)  // 定时器中断
{
    if (is_rush_hour()) {
        // 标记需要切换,但不立即执行
        SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
    }
}

void PendSV_Handler(void)  // 任务切换
{
    // 保存当前信号计划状态
    save_current_traffic_plan();
    
    // 切换到晚高峰计划
    switch_to_evening_plan();
    
    // 恢复相关状态
    update_all_traffic_lights();
}

三者的优先级与协作关系

c 复制代码
// 优先级排序(数值越小优先级越高)
1. 交通事故报警     优先级:0x00  // 最高
2. 特殊车辆通行     优先级:0x40  // 中等
3. 交警权限申请     优先级:0x80  // SVC,可配置
4. 信号计划切换     优先级:0xFF  // PendSV,最低

// 典型交互场景
下午16:59:50:
- 主程序:正常交通流动
下午16:59:55:
- 硬件中断:交通事故发生(优先级0x00)
- 立即处理事故
下午17:00:00:
- SysTick:晚高峰时间到(挂起PendSV)
- 但事故仍在处理,PendSV等待
下午17:00:10:
- 事故处理完毕
- 交警请求封锁旁路(SVC调用)
- SVC处理期间,又有救护车通过(更高优先级中断)
- 救护车优先通过
下午17:00:20:
- 所有紧急事件处理完毕
- 执行PendSV:切换信号计划

比喻三:医院急救中心与日常运营

背景设定

  • CPU:整个医院系统,包括急救中心、门诊部、住院部等。
  • 主程序:医院的日常运营(如查房、门诊接待、清洁、食堂等)。
  • 普通硬件中断:急诊病人到来、医疗设备报警等紧急事件。
  • SVC中断:医生请求进行特殊操作(如调用特殊设备、申请使用受限药品、调用专家会诊)。
  • PendSV中断:医院内部资源调度与交接班(如医生换班、病人转科、批量病人转移)。

详细场景
1. 普通硬件中断(急诊病人到来)
场景 :医院正在日常运营中,突然救护车送来一个重症病人(外部硬件中断触发)。
处理流程

  1. 立即响应:医院立即启动急救流程,暂停非紧急的日常事务(保存当前上下文)。
  2. 分诊处理:根据病人情况,分配急救室,调用急救医生(中断服务程序开始执行)。
  3. 处理过程中可能被打断:如果正在处理时,又来了一个更危重的病人(更高优先级的中断),则当前急救暂停,先去处理更紧急的。
  4. 处理完成:病人情况稳定,转入住院部(清除中断标志,中断返回)。
  5. 恢复日常运营 :但可能因为急救耽误了其他事情,需要调整计划。
    特点:必须立即响应,可能嵌套,优先级高。

2. SVC中断(医生请求特殊操作)
场景 :住院部医生在治疗病人时,发现需要调用一台特殊仪器(比如MRI),但该仪器需要特殊权限和操作流程。
处理流程

  1. 医生发出请求:医生按下请求按钮(执行SVC指令,带参数指明需要MRI服务)。
  2. 系统切换:医院系统从普通工作模式切换到特权管理模式(从用户模式切换到特权模式)。
  3. 权限验证:系统检查医生是否有权使用MRI,并检查MRI当前是否可用(操作系统安全检查)。
  4. 执行特权操作:安排技师操作MRI,为病人进行扫描(执行MRI服务例程)。
  5. 返回结果:将扫描结果返回给医生(SVC返回结果)。
  6. 医生继续治疗 :医生根据结果制定后续方案(继续执行用户程序)。
    特点:由医生主动发起,同步执行,用于获取需要特权或系统管理的资源。

3. PendSV中断(医院内部资源调度与交接班)
场景 :医院每天有固定的交班时间(如下午6点),同时可能有一些非紧急的资源调度需求(如病人转科、批量转移)。
处理流程

  1. 时间触发:下午6点,交班时间到(SysTick定时器中断触发)。
  2. 标记需求:系统标记"需要交班",但并不立即执行,因为可能还有急诊在处理(挂起PendSV)。
  3. 等待合适时机:继续处理当前的急诊和紧急事务(处理更高优先级中断)。
  4. 执行调度 :当所有紧急事务处理完毕,系统执行交班和资源调度(PendSV_Handler执行):
    • 保存当前班次医生的工作状态(保存上下文)。
    • 安排下一班医生接替(任务切换)。
    • 进行病人转科(内存或资源重新分配)。
  5. 平滑过渡 :交接完成后,新班次医生开始工作(新任务开始执行)。
    特点:延迟执行,优先级最低,用于耗时的资源调度和任务切换。

复杂互动场景

假设医院在下午5:55分发生以下事件:

  1. 主程序:医生正在查房(正常任务执行)。
  2. 硬件中断1 :救护车送来心脏病突发病人(高优先级中断)。
    • 立即抢救,进入急救室。
  3. 硬件中断2 :急救室的心电监护仪报警(更高优先级中断,嵌套)。
    • 医生立即处理报警,调整药物。
  4. SVC请求 :在抢救过程中,医生需要使用特殊药物,但需要药剂科授权(SVC调用)。
    • 医生按下授权请求,系统切换到特权模式,验证并发放药物,然后返回抢救。
  5. 定时器中断 :下午6点整,交班时间到(SysTick中断)。
    • 系统挂起PendSV,但因为有更高优先级的抢救任务,所以不立即执行。
  6. 硬件中断3 :又送来一个车祸重伤病人(另一个高优先级中断)。
    • 暂停当前抢救,分出一部分医生处理新病人。
  7. 所有紧急处理完毕:两个病人都情况稳定,转入住院部。
  8. 执行PendSV :现在执行交班和资源调度。
    • 保存当前所有医生的状态,安排下一班医生,转移病人到相应科室。
  9. 新班次开始:新医生开始查房、治疗等。

比喻四:国际太空空间站运营中心

想象一个高度复杂的国际太空空间站(ISS)运营系统:
角色对应

  • CPU:空间站主控电脑
  • 主程序:空间站的日常科研与维护计划表
  • 普通硬件中断:突发紧急事件(舱压泄露、设备故障等)
  • SVC中断:宇航员请求执行受控操作(出舱行走、实验设备使用)
  • PendSV中断:资源调度与任务轮换(实验项目切换、宇航员作息调整)

复杂交互场景:典型空间站工作周期
背景设定

  • 时间:第153天任务期,凌晨3点(空间站时间)
  • 当前执行:俄罗斯宇航员正在"星辰号"舱进行材料科学实验
  • 计划:2小时后将切换到美国舱段的生物实验
  • 系统状态:3个后台任务运行(环境监测、数据传输、电池管理)

第一部分:突发事件(普通硬件中断)

c 复制代码
// 场景:突发微流星体撞击警报
[警报!] "和谐号节点舱压力异常下降0.2kPa"  // 传感器硬件中断

// 中断处理流程:
void Pressure_Alarm_IRQHandler(void)
{
    // 1. 立即保存当前实验状态
    // (硬件自动压栈:实验数据、寄存器状态)
    
    // 2. 诊断问题来源(读取中断源)
    source = detect_pressure_leak_source();
    
    // 3. 优先级判断:这是紧急但非致命事件
    //    比火警低,但比常规实验高
    
    if (source == MICROMETEOROID_IMPACT) {
        // 4. 启动应急协议
        activate_emergency_patch(source);
        
        // 5. 通知地面控制(嵌套中断!)
        //    在压力处理中,通信系统可能触发更高优先级中断
        request_ground_confirmation();
    }
    
    // 6. 清除中断标志,返回实验
    //    但实验可能已被其他中断修改
}

嵌套中断的复杂性

c 复制代码
// 在处理压力警报时,发生更紧急事件:
void Pressure_Alarm_IRQHandler(void)
{
    // 正在定位泄漏点...
    // 突然:火灾警报触发(更高优先级中断)
    // 当前处理被暂停,先去处理火警
    
    // 火警处理完成后,才返回继续处理压力警报
    // 然后返回最初的材料科学实验
}

第二部分:受控系统请求(SVC中断)

c 复制代码
// 场景:宇航员需要切换到"哥伦布实验舱"的特殊显微镜
// 但该设备需要地面授权和系统级配置

void Astronaut_Workflow(void)
{
    // 正在进行的实验遇到瓶颈
    // 决定使用高分辨率电子显微镜
    
    // 但显微镜是共享资源,需要系统调配
    asm volatile (
        "MOV R0, %0 \n"      // 参数1:设备ID(显微镜#3)
        "MOV R1, %1 \n"      // 参数2:使用时长(120分钟)
        "MOV R2, %2 \n"      // 参数3:实验优先级
        "SVC #0x21 \n"       // 33号服务:请求专用设备
        : 
        : "r" (DEVICE_MICROSCOPE_3),
          "r" (120),
          "r" (PRIORITY_HIGH)
    );
    
    // SVC处理期间,可能发生:
    // 1. 系统检查使用权限(身份验证)
    // 2. 检查设备状态(是否被占用)
    // 3. 预留资源(内存缓冲区、电力分配)
    // 4. 重新配置设备参数
    // 5. 记录审计日志(特权操作记录)
}

// SVC处理程序的复杂性
void SVC_Handler(void)
{
    uint8_t service_id = extract_svc_number();
    
    switch(service_id) {
        case 0x21:  // 设备请求服务
            // 步骤1:切换到内核模式
            enter_kernel_mode();
            
            // 步骤2:验证宇航员权限
            if (!verify_astronaut_credentials()) {
                trigger_security_violation();
                return;
            }
            
            // 步骤3:检查资源冲突
            if (check_device_conflict(R0, R1)) {
                // 资源被占用,可能触发调度
                // 这里可能间接调用PendSV!
                mark_for_rescheduling();
                set_return_value(ERROR_BUSY);
            } else {
                // 步骤4:执行特权配置
                reconfigure_power_supply(R0, R1);
                allocate_memory_buffer(R2);
                update_scheduling_table();  // 修改全局计划表
                
                // 步骤5:返回成功
                set_return_value(SUCCESS);
            }
            
            // 步骤6:返回用户模式
            return_to_user_mode();
            break;
            
        // ... 其他50多种系统服务
    }
}

第三部分:计划性任务调度(PendSV中断)

c 复制代码
// 场景:预定实验时间结束,需要切换到下一个任务
// 但当前可能处于复杂的中断嵌套中

// 时间管理中断(每毫秒触发)
void SysTick_Handler(void)
{
    // 更新空间站任务时间表
    update_mission_timeline();
    
    // 检查是否需要任务切换
    if (current_experiment_time_expired()) {
        // 标记需要切换,但不立即执行
        // 因为可能有更紧急的中断正在处理
        
        SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
        
        // 同时,设置一些上下文信息
        set_next_experiment_type(BIOLOGICAL_STUDY);
        set_scheduled_astronaut(ASTRONAUT_JONES);
        
        // 立即返回,让紧急中断继续处理
    }
}

// PendSV真正执行时的复杂情况
__asm void PendSV_Handler(void)
{
    // 情况1:可能从不同的堆栈切换
    // 检查当前使用的是MSP(主堆栈)还是PSP(进程堆栈)
    MRS R0, CONTROL
    TST R0, #0x02
    BNE Using_PSP
    
    // 使用主堆栈(在中断处理中发生PendSV)
    MRS R0, MSP
    B Save_Context
    
Using_PSP:
    // 使用进程堆栈(在用户任务中发生PendSV)
    MRS R0, PSP
    
Save_Context:
    // 保存完整上下文(比普通中断更多寄存器)
    // 需要保存R4-R11,因为硬件不会自动保存这些
    STMDB R0!, {R4-R11, LR}
    
    // 保存浮点寄存器(如果使用FPU)
    // 空间站实验大量使用浮点计算
    VSTMDB R0!, {S16-S31}
    
    // 保存当前任务控制块
    LDR R2, =Current_TCB
    LDR R1, [R2]
    STR R0, [R1]
    
    // 复杂的调度决策
    BL SpaceStation_Scheduler
    // 调度器可能考虑:
    // 1. 实验优先级
    // 2. 宇航员疲劳度
    // 3. 设备可用性
    // 4. 地面指令优先级
    // 5. 能源分配情况
    
    // 加载下一个任务
    LDR R2, =Current_TCB
    LDR R1, [R2]
    LDR R0, [R1]
    
    // 恢复浮点上下文
    VLDMIA R0!, {S16-S31}
    
    // 恢复寄存器上下文
    LDMIA R0!, {R4-R11, LR}
    
    // 恢复正确的堆栈指针
    MRS R1, CONTROL
    TST R1, #0x02
    BNE Restore_PSP
    
    MSR MSP, R0
    B Exit_PendSV
    
Restore_PSP:
    MSR PSP, R0
    
Exit_PendSV:
    // 特殊返回:可能改变处理器模式
    BX LR
}

复杂交互:三中断同时发生的场景

c 复制代码
// 时间:凌晨4:30,空间站正进行复杂实验

// 初始状态:日本宇航员进行晶体生长实验
// 实验使用专用炉具,需要精确温控

// 事件序列:

// 1. 普通硬件中断:温度传感器异常
void Temperature_IRQHandler(void)
{
    // 炉温超过安全阈值
    // 立即降低功率,防止设备损坏
    reduce_heater_power(30%);
    
    // 在温度处理中...
    // 2. SVC中断:宇航员请求紧急协议覆盖
    //    宇航员判断需要维持高温,手动覆盖
    asm volatile("SVC #0x30");  // 紧急协议覆盖服务
    
    // SVC处理程序需要:
    // - 验证宇航员权限(3级授权)
    // - 记录覆盖原因(审计日志)
    // - 临时提高安全限制
    // - 通知地面控制(异步)
    
    // 3. 在处理SVC期间,PendSV挂起到期!
    //    预定实验切换时间到(4:35切换到生物实验)
    //    但PendSV优先级最低,等待当前处理完成
    
    // 4. 返回温度中断处理
    // 5. 返回晶体生长实验
}

// 6. 所有高优先级中断完成后
//    执行PendSV进行任务切换
//    但此时上下文极其复杂:
//    - 炉具还在特殊覆盖模式下
//    - 温度控制算法被修改
//    - 审计日志未完全写入
//    - 地面确认未收到

// PendSV处理程序必须:
// 1. 安全保存当前实验的所有状态
//    (包括特殊的覆盖模式设置)
// 2. 通知下一个实验关于设备状态
// 3. 可能需要延迟切换,等待关键操作完成

操作系统中的具体实现复杂性

c 复制代码
// 真实RTOS中的复杂中断交互
void Complex_Interrupt_Scenario(void)
{
    // 假设优先级配置:
    // 火警中断: 0x00 (最高)
    // 压力泄露: 0x40
    // 温度异常: 0x80
    // SVC: 0xC0 (可配置)
    // SysTick: 0xE0
    // PendSV: 0xFF (最低)
    
    // 场景:嵌套深度达4层
    // 1. 主程序运行
    // 2. 温度中断触发(优先级0x80)
    // 3. 在温度中断中,压力泄露触发(0x40,更高)
    // 4. 在压力处理中,宇航员执行SVC(0xC0,更低,被挂起)
    // 5. 火警触发(0x00,最高,打断一切)
    // 6. 火警处理中,SysTick到期(挂起PendSV)
    
    // 处理顺序实际为:
    // 主程序 → 温度中断 → 压力中断 → 火警中断
    // → 返回压力中断 → 返回温度中断
    // → 执行挂起的SVC → 返回主程序
    // → 最后执行PendSV
}

// 中断管理的关键数据结构
typedef struct {
    uint32_t saved_registers[16];
    uint32_t fpu_registers[16];
    uint8_t interrupt_nesting_level;
    uint8_t pending_svc_requests;
    uint8_t pending_pendsv;
    uint32_t resource_locks;
    void* current_tcb;
    void* next_tcb;
    uint32_t system_mode;  // 用户/特权/调试
} Interrupt_Context_t;

// 中断入口处理
__asm void Interrupt_Entry_Handler(void)
{
    // 判断中断来源
    // 如果是PendSV,检查是否允许切换
    // 如果是SVC,检查是否在中断上下文中(某些RTOS禁止)
    // 如果是普通中断,更新嵌套计数器
    
    // 维护中断嵌套计数器
    LDR R0, =interrupt_nesting_count
    LDR R1, [R0]
    ADD R1, R1, #1
    STR R1, [R0]
    
    // 如果嵌套深度>3,可能触发特殊处理
    CMP R1, #3
    BGT High_Nesting_Mode
    
    // 保存额外的上下文到任务控制块
    // 因为可能需要在中断中切换任务
}

关键复杂性体现在

  • 中断可能在任何点嵌套发生
  • 资源竞争需要在中断上下文中管理
  • 任务切换必须保存完整状态(包括FPU、特殊寄存器)
  • 系统必须保证实时性,同时不丢失任何关键事件

核心思想

  1. 普通硬件中断"快!有急事!" - 必须立即处理的事件
  2. SVC中断"我需要帮忙!" - 主动请求系统服务
  3. PendSV中断"该换班了,等手头工作完成" - 计划性、可延迟的调度
    这三个机制共同确保了
  • 实时性:紧急事件立即响应(硬件中断)
  • 安全性:特权操作受控访问(SVC)
  • 高效性:调度时机优化(PendSV)
相关推荐
xqqxqxxq2 小时前
《智能仿真无人机平台(多线程V2.0)技术笔记》(线程进阶: 无人机自动防空平台开发教程)
笔记·无人机·cocos2d
丝斯20112 小时前
AI学习笔记整理(56)——大模型微调
人工智能·笔记·学习
whale fall2 小时前
【雅思-口语】与豆包聊天:出国旅游日常聊天英文 + 中文对照合集
笔记·学习·旅游
暴风游侠3 小时前
IDC 学习笔记
笔记·学习
航Hang*3 小时前
计算机等级考试(二级WPS)---第1章:综合应用基础---第1节:WPS公共功能使用
笔记·学习·wps·计算机二级·计算机等级考试
AI视觉网奇3 小时前
https 证书 生成安装笔记
笔记·网络协议·https
myloveasuka3 小时前
3-8 译码器(正式型号74LS138、 74HC138、74HCT138 等))
笔记·算法·计算机组成原理·硬件
myloveasuka3 小时前
MREQ̅ 信号
笔记·算法·计算机组成原理
三伏5223 小时前
Cortex-M3权威指南Cn第八章——笔记
笔记·单片机·嵌入式硬件·cortex-m3