内核寄存器
我们已经知道CPU内核的最核心部分可以看作是由算数逻辑运算单元(ALU)、指令流水线、内核寄存器、以及内存接口四个部分构成的。其中内核寄存器可以看作是面向程序员的接口, 我们通过操作内核寄存器来访问内存,获取ALU的运算结果并更具结果实现指令的跳转,装载正确的语句到指令流水线中,最终实现程序所要实现的各种功能。
我们使用C语言写程序时,更多的考虑是数据在内存中的存放方式,基本不用考虑处理器的寄存器,因为编译器已经帮忙搞定了。所以即使我们对内核一无所知,也是可以编写一些单片机应用的。 但是要实现一个嵌入式操作系统就不得不深入到寄存器的细节。比如说,实现进程切换的时候,我们就需要直接操作栈指针寄存器。直接通过C语言是不能访问内核寄存器的, 我们需要用到汇编语句,也就是处理器指令。
Cortex-M4的寄存器如右图所示。总体上可以分为通用寄存器、功能寄存器、特殊寄存器三个部分。其中通用寄存器和功能寄存器一起构成了Register Bank,它们主要用于数据的处理, 控制程序指令流,是我们最经常打交道的寄存器。特殊寄存器则提供了内核的状态、中断机制、以及内核的工作模式。下面我们详细介绍这些寄存器。
1. Register Bank
在ARM的架构中,处理器不能直接操作内存中数据,必须先把内存中的数据装载进寄存器,处理完后再写回内存。如果内核的寄存器数量充足的话,可以把一些常用的数据临时保存在寄存器中一段时间, 而不用每次都从内存中读取数据,这样可以有效的提高系统的工作效率。Cortex-M4有16个用于数据处理和程序控制的寄存器,称为Register Bank,依次被标记为R0~R15。
R0~R12是32位通用寄存器(general purpose register)。其中前8个寄存器R0~R7又被称为low register,因为访问空间的限制,大多数16位指令只能够访问low registers。 而high register(R8~R12)则能被32位和几个特殊的16位指令访问。
R13也被标记为SP,是栈空间指针(stack pointer)。它被PUSH和POP指令用来访问栈空间内存。物理上存在两个栈空间指针:Main Stack Pointer(MSP,以后翻译为主栈指针,一些文档中称之为SP_main)和Process Stack Pointer(PSP,进程栈指针,SP_process)。其中,MSP是默认的栈指针,在系统复位后或者运行在Handler模式下时, 通过R13访问的是MSP,而PSP只能在Thread模式下使用。至于使用的是哪个栈指针由CONTROL寄存器控制,但在同一时间只有一个栈指针工作。MSP和PSP都是32位的寄存器,但它们的低两位总是0, 向其中写1是没有效果的。因为Cortex-M的栈空间是32位对齐的,PUSH和POP指令也总是32位的。
R14也记为LR,链接寄存器(Link Register)。它被用于在调用函数或者执行子程序时记录返回地址,在调用函数执行完后,CPU根据LR中记录的值返回到原来的程序中。 在发生函数调用时,LR的值会被自动更新,如果被调用的函数在执行过程中仍需要调用其他函数,需要先把LR的值压栈,否则返回地址将丢失。处理中断函数时, LR会被写入一个特别的值EXC_RETURN,具体使用方法在中断处理时讲述。虽然在Cortex-M4中任何函数的返回地址都一定是偶数,但LR的第0位仍然是可以写1的, 标识着Thumb状态以满足一些指令的需要。
R15又称为程序计数器(Program Counter, PC),这是一个可读写的寄存器。读操作将返回当前指令地址+4,之所以会加4是由于流水线的设计以及对ARM7TDMI的兼容设计。 写操作将导致程序执行地址的跳转。因为指令必须半字或者整字对齐,所以PC的最低位总为0。但是通过跳转等语句更新PC时,应当保证新PC值的最低位为1,以标识Thumb状态。 使用C语言编程时,不需要考虑这一点,因为编译器已经帮我们完成了这一操作。
2 特殊寄存器
除了上述Register Bank中涉及到的16个寄存器外,Cortex-M4还有一些反应系统状态、控制内核工作模式、异常中断屏蔽掩码等特殊功能的寄存器。这些特殊的寄存器并没有分配地址映射, 可以通过MSR或者MRS读写这些寄存器。
2.1 状态寄存器
系统的状态寄存器(Program Status Register)一共涉及到三个寄存器,分别为:Application PSR(APSR)、Execution PSR(EPSR)和Interrupt PSR(IPSR)。如表1所示, 这三个寄存器可以组合为一个寄存器一起访问,标记为xPSR。
表1 Cortex-M4的状态寄存器
31 | 30 | 29 | 28 | 27 | 26:25 | 24 | 23:20 | 19:16 | 15:10 | 9 | 8 | 7 | 6 | 5 | 4:0 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
APSR | N | Z | C | V | Q | GE* | ||||||||||
IPSR | Exception Number | |||||||||||||||
EPSR | ICI/IT | T | ICI/IT | |||||||||||||
xPSR | N | Z | C | V | Q | ICI/IT | T | GE* | ICI/IT | Exception Number |
GE:只有基于ARMv7E-M架构的处理器才有,例如Cortex-M4。
在ARM的汇编器中,用PSR标识三个状态寄存器的组合。我们可以单独的访问每个状态寄存器,也可以通过PSR访问他们的组合,参考如下的代码。但是,需要注意的是, EPSR是不能够直接通过MRS和MSR读写的,而IPSR则是一个只读的寄存器。
MRS r0, PSR ; 读组合寄存器的值到r0中
MRS r0, APSR ; 读应用寄存器的值到r0中
MSR APSR, r0 ; 写r0的值到应用寄存器中
MSR PSR, r0 ; 写r0的值到组合寄存器中
状态寄存器的位定义如下:
- N: 负数标识位(Negative flag)
- Z: 零标识位(Zero flag)
- C: 进位或者无借位标识位(Carry or NOT borrow flag)
- V: 溢出标识位(Overflow flag)
- Q: Sticky saturation flag
- GE: 大于等于标识位(Greater-Than or Equal flags for each byte lane)
- ICT/IT: Interrupt-Continuable Instruction(ICT)bits, IF-THEN instruction status bit for conditional execution.
- T: Thumb状态标识位,始终为1;尝试清除该位将触发一个错误异常。
- Exception Number: 异常代号,标志着正在处理的异常。
2.2 异常/中断掩码寄存器
PRIMASK, FAULTMASK, 和BASEPRI三个寄存器被用于异常/中断掩码操作,它们只能在privileged的情况下访问。每个异常/中断都有一个优先级,取值越小优先级越高。 默认情况下,这些寄存器的值都为0,所有的中断都是关闭的。
PRIMASK只有最低位有定义,用于打开或者屏蔽除NMI(Non-Maskable Interrupt,不可屏蔽中断)以及硬件错误异常(HardFault Exception)外的所有中断。 同时会把当前正在处理的中断或者异常的优先级提升到level 0,可屏蔽中断的最高优先级。PRIMASK通常用于在处理有实时性要求(time critical)的程序时关闭所有中断, 实时程序处理完毕后清除PRIMASK,重新开启中断。
FAULTMASK与PRIMADSK类似,只是它可以关闭硬件异常(HardFault Exception),同时把当前异常的优先级提升至level -1。和PRIMASK不同的是,当中断服务程序返回后FAULTMASK将会自动清除。 虽然PRIMASK和FAULTMASK都是32位的寄存器,但是只有最低位有效,'1'表示关闭中断。
为了提供灵活的中断屏蔽机制,ARMv7-M架构提供了BASEPRI寄存器,可以根据优先级屏蔽中断或者异常。BASEPRI寄存器的可用位长由具体的CPU厂商实现, Cortex-M4通常有8个或者16个优先级,因此通常为3位或者4位。BASEPRI中的值为0时,没有作用。当BASEPRI非零时,将屏蔽具有相同和更低优先级的中断。
CMSIS-Core定义了一系列的C语言函数用来访问这三个中断屏蔽寄存器:
x = __get_BASEPRI();
x = __get_PRIMASK();
x = __get_FAULTMASK();
__set_BASEPRI(x);
__set_PRIMASK(x);
__set_FAULTMASK(x);
__disable_irq(); // 置位PRIMASK,关闭中断
__enable_irq(); // 清除PRIMASK,开启中断
也可以通过汇编语句访问这些寄存器:
MRS r0, BASEPRI ; 读BASEPRI值到r0
MRS r0, PRIMASK ; 读PRIMASK的值到r0
MRS r0, FAULTMASK ; 读FAULTMASK的值到r0
MSR BASEPRI, r0 ; 写r0的值到BASEPRI
MSR PRIMASK, r0 ; 写r0的值到PRIMASK
MSR FAULTMASK, r0 ; 写r0的值到FAULTMASK
另外,更改处理器状态(Change Processor State)指令提供了用于修改PRIMASK和FAULTMASK的简单指令:
CPSIE i ; 清除PRIMASK,使能中断
CPSID i ; 置位PRIMASK,关闭中断
CPSIE f ; 清除FAULTMASK,使能中断
CPSID f ; 置位FAULTMASK,关闭中断
2.3 控制CONTROL寄存器
CONTROL寄存器主要用来选择栈空间指针寄存器,控制系统的访问权限(privileged/unprivileged)。 我们选用的STM32F407有浮点运算单元(Floating Point Unit,FPU),因此还有一位标识着当前的上下文中是否使用了FPU。 CONTROL寄存器只能够在系统具有privileged级权限下被修改,它可以在任何情况下被读取,表2中列举了CONTROL寄存器的位定义。
表2 CONTROL寄存器的位定义
Bit | 功能 |
---|---|
nPRIV(bit 0) | 定义了Thread模式下系统的访问权限。 nPRIV=0(默认)时,系统具有privileged级的权限, nPRIV=1时,系统具有unprivileged的权限。 |
SPSEL(bit 1) | 定义了栈空间指针寄存器。 SPESEL=0(默认)时,系统使用MSP(Main Stack Pointer), SPSEL=1时,使用PSP(Process Stack Pointer)。 |
FPCA(bit 2) | FPCA(Floating Point Contex Active)用于标识上下文中是否使用了浮点运算单元。 FPCA=0(默认)时,上下文中没有使用过浮点运算单元,因此在上下文切换时不需要保存浮点运算寄存器的值。 FPCA=1时,使用过,需要保存浮点运算寄存器的值。 执行浮点指令时,FPCA会被自动地置1,当系统产生中断或者异常时会由硬件清除。 |
系统复位后, CONTROL的值为0,标识着系统以MSP作为栈空间指针寄存器,而且具有privileged级的访问权限。 priveleged的应用程序可以通过写CONTROL寄存器,切换栈空间指针和系统的访问权限。 但是一旦nPRIV置位后,系统将不能对CONTROL寄存器进行写访问。 也就是说,在unpriveleged级权限下的系统不能自由地变更系统的访问权限和栈空间指针。 这对于系统的安全性而言是有必要的,如果必须切换回privileged的权限,则需要借助中断机制。 在中断服务程序中清除nPRIV位,中断服务程序返回后系统就具有了privileged的权限。
对于使用CMSIS接口的处理器,我们可以通过如下的C语言代码访问CONTROL寄存器:
x = __get_CONTROL();
__set_CONTROL(x);
或者汇编的形式:
MRS r0, CONTROL ; 读CONTROL中的数据到r0中
MSR CONTROL, r0 ; 写r0的数据到CONTROL中
在对CONTROL寄存器进行写操作时,有两点需要注意:
- 对于有FPU的Cortex-M4处理器,执行浮点数指令时,FPCA位会被自动置位。如果写操作清除了该位,紧接着产生了中断的话,浮点运算单元中的数据将不会被保存, 在中断处理过程中可能修改相关的寄存器,导致中断返回时数据不能正常恢复。
- 修改了CONTROL寄存器后,需要执行一个Instruction Synchronization Barrier(ISB)指令,以保证修改能够应用到以后需要执行的代码中。而Cortex-M4做了简化, 不执行这一操作也没有影响。
3. 总结
- 在ARM的架构中,不能直接对内存进行操作。我们需要先通过汇编指令把内存中的数据装载到通用寄存器(R0-R12)中,处理完毕之后将保存在通用寄存器的结果再写回内存。
- R13,R14和R15是三个功能寄存器。R13是栈指针寄存器(SP),在不同的权限下由MSP和PSP可用。R14是链接寄存器(LR),主要用于函数返回时指向原程序。R15则是程序计数器(PC), 用于指示加载指令到流水线中。
- Cortex-M4通过系统状态寄存器(PSR)记录内核的运行状态,包括ALU的运算标志、指令状态和中断状态,它们分别用APSR、EPSR和IPSR记录。这三个寄存器可以通过xPSR组合访问。
- 内核通过PRIMASK来屏蔽除NMI和硬件异常中断之外的所有中断,FAULTMASK来屏蔽硬件异常中断。BASEPRI则可以用来屏蔽指定及更低优先级的中断。
- 控制寄存器CONTROL反映了系统的访问权限、使用的栈指针是MSP还是PSP,以及是否开启了浮点运算FPU。