← 返回 计组与微机控制

计组与微机控制

3.9 块移动和数据存储

3.9 ????????

引入:块移动是一次在寄存器组和内存之间移动多个字。

普通写法:

LDR r1, [r0], #4
LDR r2, [r0], #4
LDR r3, [r0], #4

块移动可以用更少指令完成。用多条 LDR 从连续内存加载到多个寄存器。

这种写法清楚但冗长。

规律:

每次读取一个字

指针加 4

下次读取下一个字

自动后索引 `[r0],#4` 很适合这种顺序访问。

多次 STR 实现块保存

页通常展示把多个寄存器写回内存。

规律:STR r1, [r0], #4

STR r2, [r0], #4
STR r3, [r0], #4

每次写一个寄存器,并更新指针。

这就是保存上下文的基础动作。

一.块移动指令:

LDMIA r0, {r3,r4,r5,r9}

STMIA r0, {r3,r4,r5,r9}

含义:

- LDMIA:从 r0 指向地址开始,连续加载多个字到寄存器;

- STMIA:把多个寄存器连续保存到 r0 指向内存。

注意:

LDMIA r0, {...}

如果没有 `!`,r0 不更新。

一般低编号寄存器对应低地址,高编号寄存器对应高地址。

3.9 ???????? ? 13.9 ???????? ? 2

2.栈操作其实就是特殊的块移动:

把一组寄存器保存到 SP 指向的栈区

或者从 SP 指向的栈区恢复一组寄存器

这就是 ARM 中:

STMFD sp!, {r4-r7,lr}

LDMFD sp!, {r4-r7,pc}

3.块移动模式

3.9 ???????? ? 3

ARM 常用 Full Descending。Full Descending 栈:

Full Descending 表示:

- 栈向低地址增长;

- SP 指向最后一个已用单元。

入栈时:SP 先减小,再写数据

出栈时:先读数据,SP 再增大

ARM 为 LDM/STM 提供栈操作别名。

常见:

STMFD sp!, {...}   ; 入栈 LDMFD sp!, {...}   ; 出栈

现代汇编中也常写:

PUSH {...} POP  {...}

本质都是多寄存器存取。

典型函数入口:

PUSH {r4-r7, lr}

典型函数出口:

POP {r4-r7, pc}

`POP {pc}` 会把返回地址装入 PC,实现返回。

二.数据存储

本页开启第 4 章。

“广度”指不同 ISA 的设计差异,例如 ARM、MIPS、CISC。

“深度”指从表面指令深入到:

- 栈帧;

- 参数传递;

- 递归;

- 特权模式;

- 异常;

- 数据传送高级操作。

1 .数据存储和栈

本页引入数据存储和栈。

程序运行时数据大致放在:

- 全局/静态区;

- 堆;

- 栈;

- 寄存器;

- 常量池。

栈用于保存生命周期短、随函数调用层次变化的数据。

本页强调栈和函数调用的关系。

每次函数调用,都可能需要一个新的运行环境:

参数 + 返回地址 + 保存寄存器 + 局部变量

这个运行环境就是栈帧。

2.SP和FP

SP 是栈指针,指向当前栈顶。

FP 是帧指针,指向当前栈帧的固定基准位置。

为什么需要 FP?

因为函数执行过程中 SP 可能变化,例如临时入栈、调用其他函数;用 FP 作为稳定基准,可以用固定偏移访问参数和局部变量。

SP 用来分配和释放栈空间

FP 用来稳定访问当前栈帧

典型访问:

LDR r0, [fp, #8]    ; 访问参数
STR r1, [fp, #-4]   ; 访问局部变量

正偏移和负偏移取决于栈帧布局。可以用栈指针来访问栈帧中的临时变量。下图,变量XYZ位于栈指针下12个字节处,可以通过有效地址[r13,#12]访问XYZ。因为栈指针会随着其他信息进入或离开栈而移动,最好构造一个带有独立于栈指针的固定指针的栈帧

3.9 ???????? ? 4

3.栈帧偏移

本页通常用图展示 FP 上下的内容。

常见约定:

- FP 附近保存旧 FP、LR;

- FP 正偏移访问调用者传来的参数;

- FP 负偏移访问当前函数局部变量。

但具体偏移不固定,要看 ABI 和编译器。

函数返回前必须释放栈帧。

如果没有恢复 SP:

- 返回地址可能读错;

- 调用者栈帧被破坏;

- 程序无法继续正常运行;

- 调试回溯也会失效。

栈帧操作必须成对。

常见对应:

SUB sp, sp, #N PUSH {...} STMFD sp!, {...}
ADD sp, sp, #N POP  {...} LDMFD sp!, {...}