【rt-thread】学习rt-thread过程中的一些疑惑记录

文章详细探讨了在嵌入式系统中,如何使用register关键字定义变量,PC寄存器的工作原理,栈帧结构的定义,状态寄存器PSR的用途,以及在PendSV_Handler异常处理中涉及的汇编指令。通过实例解析了汇编代码如何与CPU寄存器交互,以及异常处理时的栈帧保存和恢复过程。
摘要由CSDN通过智能技术生成


参考文档

  • Cortex-M3 权威指南
  • STM32F10xxx/20xxx/21xxx/L1xxxx Cortex®-M3 programming manual
  • RT-Thread 内核实现与应用开发实战指南 —基于野火 STM32 全系列(M3/4/7)开发板
  • ARM体系结构与编程(第2版)

register定义变量

register定义的变量,优先使用CPU寄存器,当没有适当CPU寄存器时会使用内存存储。

  1. 通过汇编可以看出,register定义的变量i使用到了r0寄存器进行存储。
    在这里插入图片描述

  2. 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寄存器

PC寄存器1
PC寄存器2
发现运行到指令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 and LDMFD are synonyms for LDMIA. LDMFD refers to its use for popping data from
full descending stacks.
STMFD is s synonym for STMDB, 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 from Rn to Rn + 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, and STMFD the memory addresses used for the accesses are at 4-byte intervals ranging from Rn to Rn - 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}
r4r11的值存储到r1r1-4*(n-1)的地址空间,r4-r11为8个寄存器,所以n=8,并且高寄存器的值存储在高内存地址,低寄存器的值存储在低内存地址,并且r1的值被更新为r1-4*(8-1)
假设r1的值为0x20000400,则执行该指令后内存存储情况以及r1的变化如下:

内存地址or寄存器
0x20000400r11
0x200003FCr10
0x200003F8r9
0x200003F4r8
0x200003F0r7
0x200003ECr6
0x200003E8r5
0x200003E4r4
r10x200003E4

LDMFD r1!, {r4 - r11}
r1r1+4*(n-1)的地址空间的值加载到r4r11,r4-r11为8个寄存器,所以n=8,并且高寄存器的值存储在高内存地址,低寄存器的值存储在低内存地址,并且r1的值被更新为r1+4*(8-1)
假设r1的值为0x20000400,则执行该指令后内存存储情况以及r1的变化如下:

内存地址or寄存器
r110x2000041C 地址空间的值
r100x20000418 地址空间的值
r90x20000414 地址空间的值
r80x20000410 地址空间的值
r70x2000040C 地址空间的值
r60x20000408 地址空间的值
r50x20000404 地址空间的值
r40x20000400 地址空间的值
r10x2000041C

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值