实时操作系统在运行过程中需要对CPU的寄存器进行频繁操作。本书采用的是基于ARM Cortex-M系列内核的MCU。理解和掌握其CPU的主要寄存器,熟悉各个寄存器的含义和操作方式,是深入理解实时操作系统的必要前提条件。
以软件开发工程师视角来看,从底层学习一个CPU,理解其内部寄存器用途是重要一环。计算机所有指令运行均由CPU完成,CPU内部寄存器负责信息暂存,其数量与处理能力直接影响CPU的性能。本节先从一般意义上阐述寄存器基础知识及相关基本概念,第2.1.2节介绍ARM Cortex-M微处理器的内部寄存器。
从共性知识角度及功能来看,CPU内至少应该有数据缓冲寄存器、堆栈指针寄存器、程序指针寄存器、程序状态寄存器及其他功能寄存器。
CPU内数量最多的寄存器是数据缓冲寄存器,名字用寄存器的英文“Register”的首字母加数字组成,如R0、R1、R2等。不同的CPU,数据缓冲寄存器的种类也不同,如8086中的通用寄存器有8个,分别是AX、BX、CX、DX、SP、BP、SI、DI;Intel X86系列的通用寄存器也有8个,分别是EAX、EBX、ECX、EDX、ESP、EBP、ESI、EDI。
在计算机编程中,有全局变量与局部变量的概念。从存储器角度来看,对一个具有独立功能的完整程序来说,全局变量具有固定的地址,每次读写都是那个地址。而在一个子程序中开辟的局部变量则不同,用RAM中的哪个地址是不固定的,采用后进先出(Last In First Out,LIFO)原则使用一段RAM区域,这段RAM区域被称为栈区 。它有个栈底的地址,是一开始就确定的,当有数据进栈或出栈时,地址会自动连续变动 ,不然就放到同一个存储地址中。CPU中需要有个地方保存这个不断变化的地址,这个地方就是堆栈指针寄存器(通常称为堆栈指针)。
计算机的程序存储在存储器中,CPU中有个寄存器指示将要执行的指令在存储器中的位置,这就是程序指针寄存器。在许多CPU中,它的名字叫作程序计数寄存器,它负责告诉CPU将要执行的指令在存储器的什么地方。
CPU在进行计算过程中,会出现诸如进位、借位、结果为0、溢出等情况,CPU内需要有个地方把它们保存下来,以便下一条指令结合这些情况进行处理,这类寄存器就是程序状态寄存器。不同的CPU,其名称也不同,有的叫作标志寄存器,有的叫作程序状态寄存器等,大同小异。在这类寄存器中,常用单个英文字母表示其含义。例如,N表示有符号运算中结果为负(Negative),Z表示结果为零(Zero),C表示有进位(Carry),V表示溢出(Overflow)等。
不同的CPU,除了具有数据缓冲寄存器、堆栈指针、程序指针寄存器、程序状态寄存器(也称为程序状态字寄存器),还有表示浮点数运算、中断屏蔽 等寄存器。
ARM Cortex-M处理器的寄存器主要有R0~R15及三个特殊功能寄存器,如图2-1所示。
其中R0~R12为通用寄存器,R13为堆栈指针(Stack Pointer,SP),R14是连接寄存器,R15为程序计数(Program Counter,PC)寄存器(简称程序计算器)。特殊功能寄存器有预定义的功能,而且必须通过专用的指令来访问。
图2-1 ARM Cortex-M处理器的寄存器
R0~R12是最具“通用目的”的32位通用寄存器,用于数据操作,复位后初始值为随机值。32位的Thumb2 指令可以访问所有通用寄存器,但绝大多数16位Thumb指令只能访问R0~R7。因而R0~R7又被称为低位寄存器,所有指令都能访问它们。R8~R12被称为高位寄存器,只有很少的16位Thumb指令能访问它们,32位的指令则不受限制。在Mbed OS中,R12常用来存放函数地址。
R13是堆栈指针SP。在ARM Cortex-M处理器中共有两个堆栈指针:主堆栈指针MSP和进程堆栈指针PSP。若用户用到其中一个,另一个必须用特殊指令(MRS、MSR指令)来访问,因此任一时刻只能使用其中一个。MSP是CPU复位后默认使用的堆栈指针,它可由操作系统内核、中断服务程序及所有需要特权访问的应用程序代码来使用。PSP用于常规的应用程序代码(不处于中断服务程序中时),该堆栈一般供用户的应用程序代码使用。需要注意的是,并不是每个应用程序都要用到这两个堆栈指针,简单的应用程序只用MSP就够了,并且PUSH指令和POP指令默认使用MSP(有时MSP直接记为SP)。另外,堆栈指针的最低两位永远是0,即堆栈总是4字节对齐的。
当调用一个子程序时,由R14存储返回地址。与其他处理器不同,ARM为了减少访问内存的次数 ,把返回地址直接放入CPU内部寄存器中,这样足以使很多只有一级子程序调用 的代码无须访问内存(堆栈空间),从而提高子程序调用的效率。如果多于一级,那么需要把前一级的R14值压到堆栈里;在其他情况下,可以将R14作为通用寄存器使用。
R15是程序计数寄存器,其内容为将要执行指令的地址。如果修改它的值,就能改变程序的执行流程(很多高级技巧隐藏其中)。在汇编代码中也可以使用PC来访问它,因为ARM Cortex-M处理器使用了指令流水线,读PC时返回的值是当前指令的地址+4。ARM Cortex-M处理器中的指令至少是半字对齐的,所以PC的第0位总是0。然而,在使用一些跳转或读存储器指令更新PC时,都必须保证新的PC值是奇数(即第0位为1),用以表明这是在Thumb状态下执行的,若第0位为0,则被视为企图转入ARM模式,ARM Cortex-M处理器将触发错误异常。在理解实时操作系统运行流程时,关键点是要理解PC值是如何变化的,这是因为PC值的变化反映了程序的真实流程。
ARM Cortex-M处理器包括一组特殊功能寄存器,如程序状态寄存器(xPSR)、中断屏蔽寄存器(PRIMASK)和控制寄存器(CONTROL)。
1)程序状态寄存器
程序状态寄存器(xPSR)在内部分为以下几个子寄存器:APSR、IPSR、EPSR,用户可以使用MRS和MSR指令访问程序状态寄存器。三个子寄存器既可以单独访问,又可以两个或三个组合到一起访问。使用三合一方式访问时,把该寄存器称为xPSR,如表2-1所示。
表2-1 ARM Cortex-M处理器的程序状态寄存器(xPSR)
(1)应用程序状态寄存器(Application Program Status Register,APSR):存放算术运算单元(ALU)状态位的一些信息。负标志N:若结果最高位为1,相当于有符号运算中结果为负,则置1,否则清0。零标志Z:若结果为0,则置1,否则清0。进位标志C:若有向最高位进位(减法为借位),则置1,否则清0。溢出标志V:若溢出,则置1,否则清0。在程序运行过程中,这些位会根据运算结果而改变,在条件转移指令中也可能被用到。复位之后,这些位是随机的。
(2)中断程序状态寄存器(Interrupt Program Status Register,IPSR):该寄存器的D31~D6位为0,D5~D0位存放中断号(异常号)。每次中断完成之后,处理器会实时更新IPSR内的中断号字段,IPSR只能被MRS指令读写。进程模式下,值为0;Handler模式 下,存放当前中断的中断号。复位之后,寄存器被自动清0。复位中断号是一个暂时值,复位时是不可见的。
(3)执行程序状态寄存器(Execution Program Status Register,EPSR):T标志位指示当前运行的是否为Thumb指令,该位是不能被软件读取的,运行复位向量对应的代码时置1。如果该位为0,会发生硬件异常,进入硬件中断服务程序。
2)中断屏蔽寄存器
中断屏蔽寄存器(PRIMASK)的D31~D1位保留,只有D0位(记为PM)有意义,当该位被置位时,除不可屏蔽中断和硬件错误之外的所有中断都被屏蔽。使用特殊指令(如MSR、MRS)可以访问PRIMASK。除此之外,还有一条称为改变处理器状态的特殊指令CPS也能访问PRIMASK,只在实时线程中才会用到。对于可屏蔽中断,有开、关总中断的汇编指令:“CPSIDi”,将D0位置1(关总中断);“CPSIEi”,将D0位清0(开总中断),其中i代表IRQ中断,IRQ是非内核中断请求(Interrupt Request)的缩写。由于没有高级语言相关语句对应这两条指令,因此在编程中一般采用宏定义的方式来使用。
3)控制寄存器
控制寄存器(CONTROL)的D31~D2位保留,D1、D0位含义如下。
D1(SPSEL)——堆栈指针选择位。默认SPSEL=0,使用MSP为当前堆栈指针(复位后默认值);SPSEL=1,在进程模式下,使用PSP为当前堆栈指针。在特权、进程模式下,软件可以更新SPSEL位。在Handler模式下,写该位无效。复位后,控制寄存器清0。可用MRS指令读该寄存器,MSR指令写该寄存器。非特权访问无效。
D0(nPRIV)——如果权限扩展,在进程模式下定义执行特权。nPRIV=0,进程模式下可以特权访问;nPRIV=1,进程模式下无特权访问。在Handler模式下,总是特权访问。