首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

内核寄存器

我们已经知道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的值到组合寄存器中

状态寄存器的位定义如下:

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寄存器进行写操作时,有两点需要注意:
  1. 对于有FPU的Cortex-M4处理器,执行浮点数指令时,FPCA位会被自动置位。如果写操作清除了该位,紧接着产生了中断的话,浮点运算单元中的数据将不会被保存, 在中断处理过程中可能修改相关的寄存器,导致中断返回时数据不能正常恢复。
  2. 修改了CONTROL寄存器后,需要执行一个Instruction Synchronization Barrier(ISB)指令,以保证修改能够应用到以后需要执行的代码中。而Cortex-M4做了简化, 不执行这一操作也没有影响。

3. 总结




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1