cortex-m3
- 操作模式
- 寄存器组
- 异常类型
- 堆栈
- 中断
- 参考
操作模式
处理器的操作模式:为了区别正在执行代码的类型。复位后,处理器进入线程模式、特权级。
处理者模式(handler mode):异常服务例程的代码 ,包括中断服务例程的代码。handler 模式总是特权级的。
线程模式(thread mode):普通应用程序的代码。
特权级:在特权级下,程序可以访问所有范围的存储器、可以执行所有指令。运行主应用程序时(线程模式),既可以使用特权级,也可以使用用户级;但是异常服务例程必须在特权级下执行。
用户级:从用户级到特权级的唯一途径就是异常:如果在程序执行过程中触发了一个异常,处理器总是先切换入特权级,并且在异常服务例程执行完毕退出时,返回先前的状态,也可以手工指定返回的状态。
用户级到特权级:执行一条系统调用指令(SVC),触发 SVC 异常,然后由异常服务例程接管,如果批准了进入,则异常服务例程修改 CONTROL寄存器,才能在用户级的线程模式下重新进入特权级。
寄存器组
R0‐R12:都是 32 位通用寄存器,用于数据操作。
R0‐R7 ,低组寄存器,所有指令都能访问它们,字长全是 32 位,复位后的初始值不可预料。
R8‐R12,高组寄存器,只有很少的 16 位 Thumb 指令能访问它们,32 位的指令则不受限制,它们也是 32 位字长,且复位后的初始值不可预料。
R13:堆栈指针 SP,有两个堆栈指针MSP、PSP,任一时刻只能使用其中的一个。运行在线程模式的用户代码使用 PSP, 而异常服务例程则使用 MSP。
MSP:主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程)。
PSP:进程堆栈指针(PSP):由用户的应用程序代码使用。
R14:连接寄存器(LR):当呼叫一个子程序时,由 R14 存储返回地址。把返回地址直接存储在寄存器中,提高了子程序调用的效率。
R15:程序计数寄存器(PC):指向当前的程序地址。
特殊功能寄存器:它们只能被专用的 MSR 和 MRS 指令访问,而且它们也没有存储器地址。
程序状态字寄存器组(xPSR):记录 ALU 标志(0 标志,进位标志,负数标志,溢出标志),执行状态,以及当前正服务的中断号。
其中的EPSR,它里面含 T 位,在 CM3 中 T 位必须是 1。
中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)
这里面的PRIMASK 和 BASEPRI 对于暂时关闭中断是非常重要的。
MSR BASEPRI, R0 ;写入 R0 到 BASEPRI 中
控制寄存器(CONTROL)
control[1]:handler 模式中,CONTROL[1]总是 0。在线程模式中则可以为 0 或 1。当处于特权级的线程模式下,此位才可写。
control[0]:仅当在特权级下操作时才允许写该位。一旦进入了用户级,唯一返回特权级的途径,就是触发一个(软)中断,再由服务例程改写该位。
指令集:
Thumb状态:在 Thumb 状态下,指令是 16 位的。
异常类型
SVCall:系统服务调用。用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。这使得用户程序无需在特权级下执行。
SVC 异常通过执行”SVC”指令来产生。该指令需要一个立即数,充当系统调用代号。
SVC 异常是必须立即得到响应的,若无法立即响应,将上访成硬fault。
PendSV:为系统设备而设的“可悬挂请求”(pendable request)。
PendSV可以像普通的中断一样被悬起,OS 可以利用它缓期执行一个异常—直到其它重要的任务完成后才执行动作。悬起 PendSV 的方法是:手工往 NVIC 的 PendSV 悬起寄存器中写 1。
上下文切换时(在不同任务之间切换)可以用到PendSV。
ISR:中断服务例程。
IRQ:中断请求(通常是指外部中断的请求)
硬fault :总线 fault、存储器管理 fault 以及用法 fault 上访的结果。
如下图所示,在产生 SysTick 异常时正在响应一个中断请求IRQ,则 SysTick 异常会抢占其 ISR(中断服务例程)。在这种情况下,OS 不得执行上下文切换(将使中断请求IRQ被延迟,实时系统不能容忍),OS 在某中断活跃时尝试切入线程模式,将触犯用法 fault 异常。
PendSV 异常会自动延迟上下文切换的请求, 直到其它的 ISR (中断服务例程)都处理完才放行。为实现这个机制,需要把 PendSV 编程为最低优先级的异常。如果 OS 检测到某 IRQ 正在活动并且被 SysTick 抢占,它将悬起一个 PendSV 异常, 以便缓期执行上下文切换。
下图,1.任务A叫SVC(系统服务调用)来请求任务切换。2.OS收到请求,做好上下文切换准备,pend一个PendSV异常。3.CPU退出SVC,立即进入PendSV,执行上下文切换。4.PendSV执行完,返回任务B,进入线程模式。5.发生一个IRQ(中断),ISR(中断服务程序)开始执行。6.ISR执行过程中,发生SysTick异常,抢占该ISR。7.OS执行必要操作,pend起PendSV异常做好上下文切换准备。8.SysTick退出,回到先前被抢占的ISR中,ISR继续执行。9.ISR执行完并退出后,PendSV服务例程执行,在里面执行上下文切换。10.PendSV执行完,回到任务A,系统再次进入线程模式。
这个简而言之,PendSV其实起到了啥作用呢,如果产生 SysTick 异常时(为了进行上下文切换)正在响应一个中断请求IRQ,则 SysTick 异常会抢占其 ISR,用了PendSV的话,SysTick退出后不会立即执行上下文切换,而是在SysTick退出后继续执行ISR,执行完ISR之后再执行PendSV服务例程,进行上下文切换。这样好处就是不会使中断请求被延迟,有助于增加系统的实时性。
Cortex‐M3 一部分异常类型如下。
向量表:为了决定 handler 的入口地址,CM3使用了向量表查表机制。
如果发生了异常11(SVC),则NVIC会计算出偏移移量是11x4=0x2C,然后取出服务例程的入口地址并跳入。
堆栈
堆栈:由一块连续的内存,以及一个栈顶指针组成。堆栈操作就是对内存的读写操作,其地址由 SP 给出。功能就是把寄存器的数据放入内存,以便将来能恢复之。
当 PUSH/POP 指令执行时,SP 指针的值也根着自减/ 自增。
一个例子如下,执行 Fx1 的功能,中途就算改变 R0-R2 的值,回来的时候R0-R2 的值又变回来了,这就是恢复的功效。
实现堆栈:栈是后进先出的,使用的是“向下生长的满栈”模型。堆栈指针 SP 指向最后一个被压入堆栈的 32 位数值。
入栈时,SP 先自减 4,再存入新的数值。如下图。
出栈时,先读出上一次被压入的值,再把 SP 指针自增 4。
双堆栈机制:
当 CONTROL[1]=1 时,进程堆栈指针选用PSP,handler堆栈指针选用MSP。
在特权级下,可以指定具体的堆栈指针,而不受当前使用堆栈的限制。
MRS R0, PSP ; 读取进程堆栈指针到 R0
MSR PSP, R0 ; 写入 R0 的值到进程堆栈中
通过读取 PSP 的值,OS 就能够获取用户应用程序使用的堆栈,进一步地就知道了在发生异常时,被压入寄存器的内容,而且还可以把其它寄存器进一步压栈(使用STMDB和LDMIA 的书写形式)。OS 还可以修改 PSP,用于实现多任务中的任务上下文切换。
中断
CM3开始响应一个中断时,进行以下操作:
入栈: 把8个寄存器的值压入栈。取向量:从向量表中找出对应的服务程序入口地址。 选择堆栈指针MSP/PSP,更新堆栈指针SP,更新连接寄存器LR,更新程序计数器PC。
入栈:是自动保存现场的必要部分。把xPSR, PC, LR, R12以及 R3‐R0由硬件自动压入适当的堆栈中。响应异常时,代码正使用PSP,使用线程堆栈;否则使用主堆栈。进入服务例程,一直使用主堆栈。
入栈顺序和入栈后堆栈中的内容如下图。假设入栈开始时,SP的值为N。下图的地址从上到下看,是由高到低的。
数据总线(系统总线)进行入栈操作,与此同时,指令总线(I‐Code总线)从向量表中找出正确的异常向量,然后找出对应的服务程序入口地址。
入栈和取向量工作做完,执行服务例程之前,会更新一系列的寄存器。
SP,在入栈中把堆栈指针更新到新位置。PSR,IPSR位段更新为新响应的异常编号。PC,向量取完后,PC将指向服务例程入口地址。LR,值更新为EXC_RETURN,在异常返回时使用。在NVIC中,新响应异常的悬起位将被清除,同时其活动位将被置位。
异常服务例程执行完毕后,要启动中断返回序列,为的是恢复先前的系统状态,使被中断的程序继续执行。
有3种途径可以触发异常返回序列(都需要用到先前储的LR的值),在CM3中,是通过把EXC_RETURN往PC里写来识别返回动作的。
启动了中断返回序列后,就进行下面操作:恢复先前压入栈中的寄存器,内部的出栈顺序与入栈时的相对应,堆栈指针的值也改回去;更新NVIC寄存器,活动位被硬件清除。(倘若中断输入再次被置为有效,悬起位也将再次置位,进行新一轮的中断响应)
EXC_RETURN:这 是一个高28位全为1的值,只有[3:0]的值有特殊含义。当异常服务例程把这个值送往PC时,就会启动处理器的中断返回序列。合法的EXC_RETURN值共3个:
参考
Cortex-M3 权威指南 Joseph Yiu 著 宋岩 译