初稿第3章Cortex-M3权威指南堆栈的PUSH与POP堆栈是一种存储器的使用模型。它由一块连续的内存,以及一个栈顶指针组成,用于实现“先进后出”的缓冲区。其最典型的应用,就是在数据处理前先保存寄存器的值,再在处理任务完成后从中恢复先前保护的这些值。通过POP操作来恢复用于备份寄存器当前值先前备份过的寄存器数值的堆栈PUSH操作寄存器寄存器的当前值被恢复数据处理PUSHPOP+(原先寄存器的值被毁坏)SP→内存内存图1.2堆栈内存的基本概念在执行PUSH和POP操作时,那个通常被称为SP的地址寄存器,会自动被调整,以避免后续的操作破坏先前的数据。本书的后续章节还要围绕着堆栈展开更详细的论述。在Cortex-M3中,有专门的指令负责堆栈操作一一PUSH和POP。它俩的汇编语言语法如下例所演示PUSH(RO);*(--R13)=RO。R13是1ong*的指针POP(RO);R0=*R13++请注意后面C程序风格的注释,ortex-M3中的堆栈以这种方式来使用的,这就是所谓的“向下生长的满栈”(本章后面在讲到堆栈内存操作时还要展开论述)。因此,在PUSH新数据时,堆栈指针先减一个单元。通常在进入一个子程序后,第一件事就是把寄存器的值先PUSH入堆栈中,在子程序退出前再POP曾经PUSH的那些寄存器。另外,PUSH和POP还能一次操作多个寄存器,如下所示:subroutine_1PUSH;保存寄存器列表(R0-R7, R12, R14);执行处理"POP(R0-R7,R12,R14);恢复寄存器列表;返回到主调函数BX R14在程序中为了突出重点,你可以使用SP表示R13。在程序代码中,bothMSP和PSP都被称为R13/SP。不过,我们可以通过MRS/MSR指令来指名道姓地访问具体的堆栈指针。MSP,亦写作SPmain,这是复位后缺省使用堆栈指针,服务于操作系统内核和异常服务例程;而PSP,亦写作SP_process,典型地用于普通的用户线程中。寄存器的PUSH和POP操作永远都是4字节对齐的一一也就是说他们的地址必须是Ox4,0x8,0xc.。这样一来,R13的最低两位被硬线连接到0,并且总是读出0(ReadAsZero)。39
Cortex‐M3 权威指南 初稿 第 3 章 堆栈的 PUSH 与 POP 堆栈是一种存储器的使用模型。它由一块连续的内存,以及一个栈顶指针组成,用 于实现“先进后出”的缓冲区。其最典型的应用,就是在数据处理前先保存寄存器的值, 再在处理任务完成后从中恢复先前保护的这些值。 图 1.2 堆栈内存的基本概念 在执行 PUSH 和 POP 操作时,那个通常被称为 SP 的地址寄存器,会自动被调整, 以避免后续的操作破坏先前的数据。本书的后续章节还要围绕着堆栈展开更详细的论 述。 在 Cortex‐M3 中,有专门的指令负责堆栈操作——PUSH 和 POP。它俩的汇编语言语法 如下例所演示 PUSH {R0} ; *(-R13)=R0。R13 是 long*的指针 POP {R0} ; R0= *R13++ 请注意后面 C 程序风格的注释,ortex‐M3 中的堆栈以这种方式来使用的,这就是所谓 的“向下生长的满栈”(本章后面在讲到堆栈内存操作时还要展开论述)。因此,在 PUSH 新 数据时,堆栈指针先减一个单元。通常在进入一个子程序后,第一件事就是把寄存器的值先 PUSH 入堆栈中,在子程序退出前再 POP 曾经 PUSH 的那些寄存器。另外,PUSH 和 POP 还 能一次操作多个寄存器,如下所示: subroutine_1 PUSH {R0-R7, R12, R14} ; 保存寄存器列表 . ; 执行处理 POP {R0-R7, R12, R14} ; 恢复寄存器列表 BX R14 ; 返回到主调函数 在程序中为了突出重点,你可以使用 SP 表示 R13。在程序代码中,both MSP 和 PSP 都 被称为 R13/SP。不过,我们可以通过 MRS/MSR 指令来指名道姓地访问具体的堆栈指针。 MSP,亦写作 SP_main,这是复位后缺省使用堆栈指针,服务于操作系统内核和异常服 务例程;而 PSP,亦写作 SP_process,典型地用于普通的用户线程中。 寄存器的 PUSH 和 POP 操作永远都是 4 字节对齐的——也就是说他们的地址必须是 0x4,0x8,0xc,.。这样一来,R13 的最低两位被硬线连接到0,并且总是读出0(Read As Zero)。 39
初稿第3章Cortex-M3权威指南连接寄存器R14R14是连接寄存器(LR)。在一个汇编程序中,你可以把它写作bothLR和R14。LR用于在调用子程序时存储返回地址。例如,当你在使用BL(分支并连接,BranchandLink)指令时,就自动填充LR的值。;主程序mainBL functionl;使用“分支并连接”指令呼叫functionl;PC=functionl,并且LR=main的下一条指令地址Functionl;function1的代码";函数返回(如果function1要使用LR,必须在使用前PUSH,BXLR;否则返回时程序就可能跑飞了一一译注)尽管PC的LSB总是O(因为代码至少是字对齐的),LR的LSB却是可读可写的。这是历史遗留的产物。在以前,由位0来指示ARM/Thumb状态。因为其它有些ARM处理器支持ARM和Thumb状态并存,为了方便汇编程序移植,CM3需要允许LSB可读可写。程序计数器R15R15是程序计数器,在汇编代码中你也可以使用名字“PC”来访问它。因为CM3内部使用了指令流水线,读PC时返回的值是当前指令的地址+4。比如说:0x1000:MOVRO,PC;R0=0x1004如果向PC中写数据,就会引起一次程序的分支(但是不更新LR寄存器)。CM3中的指令至少是半字对齐的,所以PC的LSB总是读回O。然而,在分支时,无论是直接写PC的值还是使用分支指令,都必须保证加载到PC的数值是奇数(即LSB=1),用以表明这是在Thumb状态下执行。尚若写了0,则视为企图转入ARM模式,CM3将产生一个fault异常。特殊功能寄存器组Cortex-M3中的特殊功能寄存器包括:·程序状态寄存器组(PSRs或日xPSR)·中断屏蔽寄存器组(PRIMASK,FAULTMASK,以及BASEPRI)。控制寄存器(CONTROL)它们只能被专用的MSR和MRS指令访问,而且它们也没有存储器地址。MRS;读特殊功能寄存器的值到通用寄存器<special_reg><gp_reg>,MSR;写通用寄存器的值到特殊功能寄存器<special_reg>,<gp_reg>40
Cortex‐M3 权威指南 初稿 第 3 章 连接寄存器 R14 R14 是连接寄存器(LR)。在一个汇编程序中,你可以把它写作 both LR 和 R14。LR 用于 在调用子程序时存储返回地址。例如,当你在使用 BL(分支并连接,Branch and Link)指令时, 就自动填充 LR 的值。 main ;主程序 . BL function1 ; 使用“分支并连接”指令呼叫 function1 ; PC= function1,并且 LR=main 的下一条指令地址 . Function1 . ; function1 的代码 BX LR ; 函数返回(如果 function1 要使用 LR,必须在使用前 PUSH, ; 否则返回时程序就可能跑飞了——译注) 尽管 PC 的 LSB 总是 0(因为代码至少是字对齐的),LR 的 LSB 却是可读可写的。这是历 史遗留的产物。在以前,由位 0 来指示 ARM/Thumb 状态。因为其它有些 ARM 处理器支持 ARM 和 Thumb 状态并存,为了方便汇编程序移植,CM3 需要允许 LSB 可读可写。 程序计数器 R15 R15 是程序计数器,在汇编代码中你也可以使用名字“PC”来访问它。因为 CM3 内部 使用了指令流水线,读 PC 时返回的值是当前指令的地址+4。比如说: 0x1000: MOV R0, PC ; R0 = 0x1004 如果向 PC 中写数据,就会引起一次程序的分支(但是不更新 LR 寄存器)。CM3 中的指 令至少是半字对齐的,所以 PC 的 LSB 总是读回 0。然而,在分支时,无论是直接写 PC 的值 还是使用分支指令,都必须保证加载到 PC 的数值是奇数(即 LSB=1),用以表明这是在 Thumb 状态下执行。倘若写了 0,则视为企图转入 ARM 模式,CM3 将产生一个 fault 异 常。 特殊功能寄存器组 Cortex‐M3 中的特殊功能寄存器包括: z 程序状态寄存器组(PSRs 或曰 xPSR) z 中断屏蔽寄存器组(PRIMASK, FAULTMASK,以及 BASEPRI) z 控制寄存器(CONTROL) 它们只能被专用的 MSR 和 MRS 指令访问,而且它们也没有存储器地址。 MRS <gp_reg>, <special_reg> ;读特殊功能寄存器的值到通用寄存器 MSR <special_reg>, <gp_reg> ;写通用寄存器的值到特殊功能寄存器 40
初稿第3章Cortex-M3权威指南程序状态寄存器(PSRs或日PSR)程序状态寄存器在其内部又被分为三个子状态寄存器:应用程序PSR(APSR)?中断号PSR(IPSR)执行PSR(EPSR)通过MRS/MSR指令,这3个PSRs即可以单独访问,也可以组合访问(2个组合,3个组合都可以)。当使用三合一的方式访问时,应使用名字“xPSR”或者“PSR”。313029282726:2523:2019:1615:10244:05APSRNOOZVIPSRExceptionNumberEPSRICI/ITICI/IT图3.3Cortex-M3中的程序状态寄存器(xPSR)26:2523:2019:1615:1054:0313029282724XPSRAZCaICI/ITICI/ITExceptionNumber图3.4合体后的程序状态寄存器(xPSR)PRIMASK.FAULTMASK和BASEPRI这三个寄存器用于控制异常的使能和除能。表3.2 Cortex-M3的屏蔽寄存器s名字功能描述PRIMASK这是个只有1个位的寄存器。当它置1时,就关掉所有可屏蔽的异常,只剩下NMI和硬fault可以响应。它的缺省值是o,表示没有关中断。FAULTMASK这是个只有1个位的寄存器。当它置1时,只有NMI才能响应,所有其它的异常,包括中断和fault,通通闭嘴。它的缺省值也是0,表示没有关异常。BASEPRI这个寄存器最多有9位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低)。但若被设成0,则不关闭任何中断,0也是缺省值。对于时间-关键任务而言,PRIMASK和BASEPRI对于暂时关闭中断是非常重要的。而FAULTMASK则可以被OS用于暂时关闭fault处理机能,这种处理在某个任务崩溃时可能需要。因为在任务崩溃时,常常伴随着一大堆faults。在系统料理“后事”时,通常不再需要响应这些fault-一人死帐清。总之FAULTMASK就是专门留给OS用的。要访问PRIMASK,FAULTMASK以及BASEPRI,同样要使用MRS/MSR指令,如:RO,读取BASEPRI到RO中MRSBASEPRI;似上MRSRO,FAULTMASK41
Cortex‐M3 权威指南 初稿 第 3 章 程序状态寄存器(PSRs 或曰 PSR) 程序状态寄存器在其内部又被分为三个子状态寄存器: z 应用程序 PSR(APSR) z 中断号 PSR(IPSR) z 执行 PSR(EPSR) 通过 MRS/MSR 指令,这 3 个 PSRs 即可以单独访问,也可以组合访问(2 个组合,3 个 组合都可以)。当使用三合一的方式访问时,应使用名字“xPSR”或者“PSR”。 图 3.3 Cortex‐M3 中的程序状态寄存器(xPSR) 图 3.4 合体后的程序状态寄存器(xPSR) PRIMASK, FAULTMASK 和 BASEPRI 这三个寄存器用于控制异常的使能和除能。 表 3.2 Cortex-M3 的屏蔽寄存器 s 名字 功能描述 PRIMASK 这是个只有 1 个位的寄存器。当它置 1 时,就关掉所有可屏蔽的异常,只剩下 NMI 和硬 fault 可以响应。它的缺省值是 0,表示没有关中断。 FAULTMASK 这是个只有 1 个位的寄存器。当它置 1 时,只有 NMI 才能响应,所有其它的异常, 包括中断和 fault,通通闭嘴。它的缺省值也是 0,表示没有关异常。 BASEPRI 这个寄存器最多有 9 位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈 值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号 越大,优先级越低)。但若被设成 0,则不关闭任何中断,0 也是缺省值。 对于时间‐关键任务而言,PRIMASK 和 BASEPRI 对于暂时关闭中断是非常重要的。而 FAULTMASK 则可以被 OS 用于暂时关闭 fault 处理机能,这种处理在某个任务崩溃时可能需 要。因为在任务崩溃时,常常伴随着一大堆 faults。在系统料理“后事”时,通常不再需要 响应这些 fault——人死帐清。总之 FAULTMASK 就是专门留给 OS 用的。 要访问 PRIMASK, FAULTMASK 以及 BASEPRI,同样要使用 MRS/MSR 指令,如: MRS R0, BASEPRI ;读取 BASEPRI 到 R0 中 MRS R0, FAULTMASK ;似上 41
初稿第3章Cortex-M3权威指南;似上MRSRO,PRIMASKRO写入RO到BASEPRI中MSRBASEPRI,;似上MSRFAULTMASK,RO;似上ROMSRPRIMASK,只有在特权级下,才允许访问这3个寄存器。其实,为了快速地开关中断,CM3还专门设置了一条CPS指令,有4种用法1;关中断CPSID;PRIMASK=1,CPSIEI; PRIMASK=0,;开中断F;关异常CPSID; FAULTMASK=1,F;开异常CPSIE;FAULTMASK=0控制寄存器(CONTROL)控制寄存器用于定义特权级别,还用于选择当前使用哪个堆栈指针。表3.3Cortex-M3的CONTROL寄存器位功能CONTROL[1]堆栈指针选择O=选择主堆栈指针MSP(复位后缺省值)1=选择进程堆栈指针PSP在线程或基础级(没有在响应异常一一译注),可以使用PSP。在handler模式下,只允许使用MSP,所以此时不得往该位写1。0=特权级的线程模式CONTROL[O]1=用户级的线程模式Handler模式永远都是特权级的。CONTROL[1]在Cortex-M3的handler模式中,CONTROL[1]总是0。在线程模式中则可以为0或1。仅当处于特权级的线程模式下,此位才可写,其它场合下禁止写此位。改变处理器的模式也有其它的方式:在异常返回时,通过修改LR的位2,也能实现模式切换。这将在第5章中展开论述。CONTROL[O]仅当在特权级下操作时才允许写该位。一旦进入了用户级,唯一返回特权级的途径,就是触发一个(软)中断,再由服务例程改写该位。CONTROL寄存器也是通过MRS和MSR指令来操作的:MRSRO,CONTROLROMSRCONTROL,操作模式Cortex-M3支持2个模式和两个特权等级。42
Cortex‐M3 权威指南 初稿 第 3 章 MRS R0, PRIMASK ;似上 MSR BASEPRI, R0 ;写入 R0 到 BASEPRI 中 MSR FAULTMASK, R0 ;似上 MSR PRIMASK, R0 ;似上 只有在特权级下,才允许访问这 3 个寄存器。 其实,为了快速地开关中断,CM3 还专门设置了一条 CPS 指令,有 4 种用法 CPSID I ;PRIMASK=1, ;关中断 CPSIE I ;PRIMASK=0, ;开中断 CPSID F ;FAULTMASK=1, ;关异常 CPSIE F ;FAULTMASK=0 ;开异常 控制寄存器(CONTROL) 控制寄存器用于定义特权级别,还用于选择当前使用哪个堆栈指针。 表 3.3 Cortex‐M3 的 CONTROL 寄存器 位 功能 CONTROL[1] 堆栈指针选择 0=选择主堆栈指针 MSP(复位后缺省值) 1=选择进程堆栈指针 PSP 在线程或基础级(没有在响应异常——译注),可以使用 PSP。在 handler 模式下, 只允许使用 MSP,所以此时不得往该位写 1。 CONTROL[0] 0=特权级的线程模式 1=用户级的线程模式 Handler 模式永远都是特权级的。 CONTROL[1] 在 Cortex‐M3 的 handler 模式中,CONTROL[1]总是 0。在线程模式中则可以为 0 或 1。 仅当处于特权级的线程模式下,此位才可写,其它场合下禁止写此位。改变处理器的模 式也有其它的方式:在异常返回时,通过修改 LR 的位 2,也能实现模式切换。这将在第 5 章中展开论述。 CONTROL[0] 仅当在特权级下操作时才允许写该位。一旦进入了用户级,唯一返回特权级的途径,就 是触发一个(软)中断,再由服务例程改写该位。 CONTROL 寄存器也是通过 MRS 和 MSR 指令来操作的: MRS R0, CONTROL MSR CONTROL, R0 操作模式 Cortex‐M3 支持 2 个模式和两个特权等级。 42
初稿第3章Cortex-M3权威指南特权级用户级异常handler的代码错误的用法handler模式主应用程序的代码线程模式线程模式图3.6操作模式和特权等级当处理器处在线程状态下时,既可以使用特权级,也可以使用用户级;另一方面,handler模式总是特权级的。在复位后,处理器进入线程模式十特权级。在线程模式十用户级下,对系统控制空间(SCS)的访问将被阻止一一该空间包含了配置寄存器s以及调试组件的寄存器s。除此之外,还禁止使用MSR访问刚才讲到的特殊功能寄存器一一除了APSR有例外。谁若是以身试法,则将fault伺候。在特权级下的代码可以通过置位CONTROL[OI来进入用户级。而不管是任何原因产生了任何异常,处理器都将以特权级来运行其服务例程,异常返回后将回到产生异常之前的特权级。用户级下的代码不能再试图修改CONTROL[O]来回到特权级。它必须通过一个异常handler,由那个异常handler来修改CONTROL[O],才能在返回到线程模式后拿到特权级。清零CONTROL[0]handler状态ExceptionException特权极置位CONTROL[0]handlerhandler线程状态PrivilegedStarting特权级podethreadExceptionException线程状态UserBSE用户级modemaa图3.7特权级和处理器模式的改变图把代码按特权级和用户极分开对待,有利于使架构更加安全和健壮。例如,当某个用户代码出问题时,不会让它成为害群之马,因为用户级的代码是禁止写特殊功能寄存器和NVIC中寄存器的。另外,如果还配有MPU,保护力度就更大,甚至可以阻止用户代码访问不属于它的内存区域。为了避免系统堆栈因应用程序的错误使用而毁坏,你可以给应用程序专门配一个堆栈,不让它共享操作系统内核的堆栈。在这个管理制度下,运行在线程模式的用户代码使用PSP,而异常服务例程则使用MSP。这两个堆栈指针的切换是全自动的,就在出入异常服务例程时由硬件处理。第8章将详细讨论此主题。如前所述,特权等级和堆栈指针的选择均由CONTROL负责。当CONTROL[O]=O时,在异常处理的始末,只发生了处理器模式的转换,如下图所示。43
Cortex‐M3 权威指南 初稿 第 3 章 图 3.6 操作模式和特权等级 当处理器处在线程状态下时,既可以使用特权级,也可以使用用户级;另一方面,handler 模式总是特权级的。在复位后,处理器进入线程模式+特权级。 在线程模式+用户级下,对系统控制空间(SCS)的访问将被阻止——该空间包含了配 置寄存器 s 以及调试组件的寄存器 s。除此之外,还禁止使用 MSR 访问刚才讲到的特殊功能 寄存器——除了 APSR 有例外。谁若是以身试法,则将 fault 伺候。 在特权级下的代码可以通过置位 CONTROL[0]来进入用户级。而不管是任何原因产生了 任何异常,处理器都将以特权级来运行其服务例程,异常返回后将回到产生异常之前的特权 级。用户级下的代码不能再试图修改 CONTROL[0]来回到特权级。它必须通过一个异常 handler, 由那个异常 handler 来修改 CONTROL[0],才能在返回到线程模式后拿到特权级。 图 3.7 特权级和处理器模式的改变图 把代码按特权级和用户极分开对待,有利于使架构更加安全和健壮。例如,当某个用户 代码出问题时,不会让它成为害群之马,因为用户级的代码是禁止写特殊功能寄存器和 NVIC 中寄存器的。另外,如果还配有 MPU,保护力度就更大,甚至可以阻止用户代码访问不属 于它的内存区域。 为了避免系统堆栈因应用程序的错误使用而毁坏,你可以给应用程序专门配一个堆栈, 不让它共享操作系统内核的堆栈。在这个管理制度下,运行在线程模式的用户代码使用 PSP, 而异常服务例程则使用 MSP。这两个堆栈指针的切换是全自动的,就在出入异常服务例程 时由硬件处理。第 8 章将详细讨论此主题。 如前所述,特权等级和堆栈指针的选择均由 CONTROL 负责。当 CONTROL[0]=0 时,在异 常处理的始末,只发生了处理器模式的转换,如下图所示。 43