参考文档
- Cortex-M3 权威指南
- STM32F10xxx/20xxx/21xxx/L1xxxx Cortex®-M3 programming manual
- RT-Thread 内核实现与应用开发实战指南 —基于野火 STM32 全系列(M3/4/7)开发板
- ARM体系结构与编程(第2版)
register定义变量
register
定义的变量,优先使用CPU寄存器,当没有适当CPU寄存器时会使用内存存储。
-
通过汇编可以看出,
register
定义的变量i
使用到了r0
寄存器进行存储。
-
register
定义的变量i
使用了r0
寄存器进行存储,而同样使用register
定义的数组j
则在存在在内存中。
0x08000372 MOVS r0,#0x00 ;执行 i=0 操作,变量 i 使用寄存器 r0 进行存储,所以将 0 加载到 r0
0x08000374 B 0x08000380 ;跳转到地址 0x08000380
0x08000376 MOV r1,#0xAAAAAAAA ;将需要初始化的值 0xAAAAAAAA 加载到 r1
0x0800037A STR r1,[sp,r0,LSL #2] ;LSL(Logic Shift Left)
;将 r1 的值加载到 sp(sp加上r0左移两位)指向的地址
;当 r0==0 时,加载 r1 到地址 0x20000620+0<<2 = 0x20000620,所以该地址存储的是 j[0]
;当 r0==1 时,加载 r1 到地址 0x20000620+1<<2 = 0x20000624,所以该地址存储的是 j[1]
;后续的以此类推
0x0800037E ADDS r0,r0,#1 ;r0 加 1
0x08000380 CMP r0,#0x10 ;将 r0 与 0x10 进行比较
0x08000382 BLT 0x08000376 ;LT(Less Than),r0 小于 0x10 则跳转到 0x08000376
PC寄存器
发现运行到指令0x0800034E
(函数 rt_system_scheduler_init)时,其下一条指令为0x0800352
,所以当跳转到函数 rt_system_scheduler_init 时,LR 寄存器应该存放的是0x08000352
,但实际仿真过程中发现 LR 寄存器存放的是0x08000353
,为什么呢?通过查看权威指南找到答案。
程序计数器 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 异
常。
栈帧结构定义
1. 异常栈帧结构体中的CPU寄存器顺序依据什么来定义?
struct exception_stack_frame
{
/* 异常发生时,自动加载到 CPU 寄存器的内容 */
rt_uint32_t r0;
rt_uint32_t r1;
rt_uint32_t r2;
rt_uint32_t r3;
rt_uint32_t r12;
rt_uint32_t lr;
rt_uint32_t pc;
rt_uint32_t psr;
};
struct stack_frame
{
/* 异常发生时,需手动加载到 CPU 寄存器的内容 */
rt_uint32_t r4;
rt_uint32_t r5;
rt_uint32_t r6;
rt_uint32_t r7;
rt_uint32_t r8;
rt_uint32_t r9;
rt_uint32_t r10;
rt_uint32_t r11;
struct exception_stack_frame exception_stack_frame;
};
根据寄存器的入栈顺序来定义。
2. 异常发生时,哪些寄存器是自动加载,哪些需要手动加载?
首先了解CPU寄存器都有哪些:
自动入栈寄存器:
When the processor takes an exception, unless the exception is a tail-chained or a late-arriving exception, the processor pushes information onto the current stack. This operation is referred as stacking and the structure of eight data words is referred as stack frame. The stack frame contains the following information:
• R0-R3, R12
• Return address
• PSR
• LR.
所以最终需要手动入栈的寄存器是:R4 - R11
状态寄存器PSR
构造线程栈帧的时候为什么将psr
赋值0x01000000L
?
stack_frame->exception_stack_frame.psr = 0x01000000L;
PendSV_Handler使用到的汇编
STMFD r1!, {r4 - r11}
LDMFD r1!, {r4 - r11}
LDM and STM
Load and store multiple registers.
Syntax
op{addr_mode}{cond} Rn{!}, reglist
where:
• ‘op’ is either LDM (load multiple register) or STM (store multiple register)
• ‘addr_mode’ is any of the following:
IA: Increment address after each access (this is the default)
DB: Decrement address before each access
• ‘Rn’ is the register on which the memory addresses are based
• ‘!’ is an optional writeback suffix. If ‘!’ is present, the final address that is loaded from or
stored to is written back into Rn.
• ‘reglist’ is a list of one or more registers to be loaded or stored, enclosed in braces. It
can contain register ranges. It must be comma-separated if it contains more than one
register or register range.
LDM andLDMFD
are synonyms forLDMIA
. LDMFD refers to its use for popping data from
full descending stacks.
STMFD
is s synonym forSTMDB
, and refers to its use for pushing data onto full descending
stacks
For LDM, LDMIA,LDMFD
, STM, STMIA, and STMEA the memory addresses used for the accesses are at 4-byte intervals ranging fromRn
toRn + 4 * ( n -1)
, where n is the number of registers in reglist. The accesses happen in order of increasing register numbers, with the lowest numbered register using the lowest memory address and the highest number register using the ighest memory address. If the writeback suffix is specified, the value of Rn + 4 * ( n -1) is written back to Rn.
For LDMDB, LDMEA, STMDB, andSTMFD
the memory addresses used for the accesses are at 4-byte intervals ranging fromRn
toRn - 4 * ( n -1)
, where n is the number of registers in reglist. The accesses happen in order of decreasing register numbers, with the highest numbered register using the highest memory address and the lowest number register using the lowest memory address. If the writeback suffix is specified, the value Rn - 4 * ( n -1) is written back to Rn.
通过上面可以看出,
STMFD r1!, {r4 - r11}
将r4
到r11
的值存储到r1
到r1-4*(n-1)
的地址空间,r4-r11为8个寄存器,所以n=8,并且高寄存器的值存储在高内存地址,低寄存器的值存储在低内存地址,并且r1
的值被更新为r1-4*(8-1)
。
假设r1
的值为0x20000400
,则执行该指令后内存存储情况以及r1的变化如下:
内存地址or寄存器 | 值 |
---|---|
0x20000400 | r11 |
0x200003FC | r10 |
0x200003F8 | r9 |
0x200003F4 | r8 |
0x200003F0 | r7 |
0x200003EC | r6 |
0x200003E8 | r5 |
0x200003E4 | r4 |
r1 | 0x200003E4 |
LDMFD r1!, {r4 - r11}
将r1
到r1+4*(n-1)
的地址空间的值加载到r4
到r11
,r4-r11为8个寄存器,所以n=8,并且高寄存器的值存储在高内存地址,低寄存器的值存储在低内存地址,并且r1
的值被更新为r1+4*(8-1)
。
假设r1
的值为0x20000400
,则执行该指令后内存存储情况以及r1的变化如下:
内存地址or寄存器 | 值 |
---|---|
r11 | 0x2000041C 地址空间的值 |
r10 | 0x20000418 地址空间的值 |
r9 | 0x20000414 地址空间的值 |
r8 | 0x20000410 地址空间的值 |
r7 | 0x2000040C 地址空间的值 |
r6 | 0x20000408 地址空间的值 |
r5 | 0x20000404 地址空间的值 |
r4 | 0x20000400 地址空间的值 |
r1 | 0x2000041C |
PendSV_Handler异常处理
为什么进入PendSV_Handler
的时候,LR
寄存器的值是0xFFFFFFF9
,并且退出的时候还对LR
寄存器或上0x04
的操作?
通过阅读手册知道异常触发时,在入栈的同时向量表中获取异常处理函数的起始地址,入栈完成后执行异常处理函数,与此同时处理器向LR
寄存器写入EXC_RETURN
的值,该值指定了进入异常前处理器是处理者模式还是线程模式,并且指定了使用的是栈指针是MSP
还是PSP
。
In parallel to the stacking operation, the processor performs a vector fetch that reads the
exception handler start address from the vector table. When stacking is complete, the
processor starts executing the exception handler. At the same time, the processor writes an
EXC_RETURN value to the LR. This indicates which stack pointer corresponds to the stack
frame and what operation mode the was processor was in before the entry occurred.
EXC_RETURN
实际上只有三个值:
0xFFFFFFF1
:返回处理者模式,异常返回(出栈)时从MSP
获取状态,并且退出异常后栈指针使用MSP
。
0xFFFFFFF9
:返回线程模式,异常返回(出栈)时从MSP
获取状态,并且退出异常后栈指针使用MSP
。
0xFFFFFFFD
:返回线程模式,异常返回(出栈)时从PSP
获取状态,并且退出异常后栈指针使用PSP
。
异常退出的时候需要将EXC_RETURN
的值加载到PC
寄存器即可触发。
Exception return occurs when the processor is in Handler mode and executes one of the following instructions to load the EXC_RETURN value into the PC:
• A POP instruction that includes the PC
• A BX instruction with any register.
• An LDR or LDM instruction with the PC as the destination
所以,退出PendSV_Handler
前对LR
寄存器或上0x04
的操作然后跳转到LR
,目的就是触发异常返回流程,并且返回的是线程模式,异常出栈栈指针使用PSP
,异常退出后栈指针继续使用PSP
。