堆栈指针 R13
R13 是堆栈指针。在 CM3 处理器内核中共有两个堆栈指针,于是也就支持两个堆栈。
当引用 R13(或写作 SP)时,你引用到的是当前正在使用的那一个,另一个必须用特殊的指
令来访问(MRS,MSR 指令)。这两个堆栈指针分别是:
主堆栈指针(MSP),或写作 SP_main。这是缺省的堆栈指针,它由 OS 内核、异常服务
例程以及所有需要特权访问的应用程序代码来使用。
进程堆栈指针(PSP),或写作 SP_process。用于常规的应用程序代码(不处于异常服
用例程中时)。MSP和PSP 的含义是Main_Stack_Pointer 和Process_Stack_Pointer
堆栈的 PUSH 与 POP
堆栈是一种存储器的使用模型。它由一块连续的内存,以及一个栈顶指针组成,用
于实现“先进后出”的缓冲区。其最典型的应用,就是在数据处理前先保存寄存器的值,
再在处理任务完成后从中恢复先前保护的这些值。因此,在 PUSH 新数据时,堆栈指针先减一个单元。通常在进入一个子程序后,第一件事就是把寄存器的值先
PUSH 入堆栈中,在子程序退出前再 POP 曾经 PUSH 的那些寄存器。另外,PUSH 和 POP 还
能一次操作多个寄存器。可以使用 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)。
连接寄存器 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,
; 否则返回时程序就可能跑飞了——译注)
程序计数器 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 中的特殊功能寄存器包括:
- 程序状态寄存器组(PSRs 或曰 xPSR)
- 中断屏蔽寄存器组(PRIMASK, FAULTMASK,以及 BASEPRI)
- 控制寄存器(CONTROL)
它们只能被专用的 MSR 和 MRS 指令访问,而且它们也没有存储器地址。
- MRS
, ;读特殊功能寄存器的值到通用寄存器 - MSR
, ;写通用寄存器的值到特殊功能寄存器
程序状态寄存器(PSRs 或曰 PSR)
程序状态寄存器在其内部又被分为三个子状态寄存器:
- 应用程序 PSR(APSR)
- 中断号 PSR(IPSR)
- 执行 PSR(EPSR)
通过 MRS/MSR 指令,这 3 个 PSRs 即可以单独访问,也可以组合访问(2 个组合,3 个组合都可以)。当使用三合一的方式访问时,应使用名字“xPSR”或者“PSR”。
stmdb sp!, {fp, ip, lr, pc} //sp=sp-4,sp=pc;先压PC ,把内容压入下一个内存地址即原地址-4.
//sp=sp-4,sp=lr;再压lr
//sp=sp-4,sp=ip;再压ip
//sp=sp-4,sp=fp;再压fp
ldmia sp, {fp, sp, pc} //和stmdb成对使用,
//fp=sp,sp=sp+4;先弹fp //出栈还原+4
//sp=sp,sp=sp+4;先弹sp,此处的弹出不会 影响sp,因为ldmia是一个机器周期执行完的。
//pc=sp,sp=sp+4;先弹pc
LDRH R0, [R13, #0xC] //加载无符号半字数据,即低16位
LDRB R0, [R13, #0x4] //加载一字节数据,即低8位
在特权级下,可以指定具体的堆栈指针,而不受当前使用堆栈的限制
MRS R0, MSP ; 读取主堆栈指针到 R0
MSR MSP, R0 ; 写入 R0 的值到主堆栈中
MRS R0, PSP ; 读取进程堆栈指针到 R0
MSR PSP, R0 ; 写入 R0 的值到进程堆栈中
通过读取 PSP 的值,OS 就能够获取用户应用程序使用的堆栈,进一步地就知道了在发
生异常时,被压入寄存器的内容,而且还可以把其它寄存器进一步压栈(使用STMDB和LDMIA
的书写形式)。OS 还可以修改 PSP,用于实现多任务中的任务上下文切换。
堆栈指针 SP 指向最后一个被压入堆栈的 32
位数值。在下一次压栈时,SP 先自减 4,再存入新的数值。POP 操作刚好相反:先从 SP 指针处读出上一次被压入的值,再把 SP 指针自增 4。
- MRS 加载特殊功能寄存器的值到通用寄存器
- MSR 存储通用寄存器的值到特殊功能寄存器
- NOP 无操作
- SEV 发送事件
- WFE 休眠并且在发生事件时被唤醒
- WFI 休眠并且在发生中断时被唤醒
- ISB 指令同步隔离(与流水线和 MPU 等有关——译注) //隔离指令
- DSB 数据同步隔离(与流水线、MPU 和 cache 等有关——译注) //隔离指令
- DMB 数据存储隔离(与流水线、MPU 和 cache 等有关——译注) //隔离指令
常用的多重存储器访问方式
LDMIA Rd!, {寄存器列表} 从 Rd 处读取多个字。 每读一个字后 Rd 自增一次,16
位宽度
STMIA Rd!, {寄存器列表} 存储多个字到 Rd 处。 每存一个字后 Rd 自增一次,16
位宽度
LDMIA.W Rd!, {寄存器列表} 从 Rd 处读取多个字。 每读一个字后 Rd 自增一次,32
位宽度
LDMDB.W Rd!, {寄存器列表} 从 Rd 处读取多个字。 每读一个字前 Rd 自减一次,32
位宽度
STMIA.W Rd!, {寄存器列表} 存储多个字到 Rd 处。 每存一个字后 Rd 自增一次,32
位宽度
STMDB.W Rd!, {寄存器列表} 存储多个字到 Rd 处。 每存一个字前 Rd 自减一次,32
位宽度常用的存储器访问指令
示例 功能描述
LDRB Rd, [Rn, #offset] 从地址 Rn+offset 处读取一个字节到 Rd
LDRH Rd, [Rn, #offset] 从地址 Rn+offset 处读取一个半字到 Rd
LDR Rd, [Rn, #offset] 从地址 Rn+offset 处读取一个字到 Rd
LDRD Rd1, Rd2, [Rn, #offset] 从地址 Rn+offset 处读取一个双字(64 位整数)到 Rd1(低32 位)和 Rd2(高 32 位)中。
STRB Rd, [Rn, #offset] 把 Rd 中的低字节存储到地址 Rn+offset 处
STRH Rd, [Rn, #offset] 把 Rd 中的低半字存储到地址 Rn+offset 处
STR Rd, [Rn, #offset] 把 Rd 中的低字存储到地址 Rn+offset 处
LDRD Rd1, Rd2, [Rn, #offset] 把 Rd1(低 32 位)和 Rd2(高 32 位)表达的双字存储到地址 Rn+offset 处