文章目录
批量Load-Store指令可以实现连续内存块和一组寄存器之间的数据交换,它通常使用在运行环境的保存、恢复,以及两个内存块之间的数据拷贝。
指令编码格式
- 寄存器列表
每个bit代表一个寄存器,如果为1表示对应寄存器参与传输,bit0代表R0,依此类推。 - Rn
指定了要操作的内存块的基地址。不能使用PC寄存器作为基址寄存器。 - Load/Store Bit
0为Store操作,1为Load操作。 - Write-Back Bit
指定指令执行后是否要更新基址寄存器Rn的值,0不更新,1更新。 - PSR & Force User Bit(S位)
0表示不操作CPSR和SPSR寄存器或者为用户模式,1表示操作程序状态寄存器或者为非用户模式。因为这两种情况是互斥的,所以可以用同一位来表示。更详细的说明见下方介绍。 - Up/Down Bit
0表示从基址寄存器递减偏移量,1表示递增。 - Pre/Post Indexing Bit
0表示在是每一次寄存器和内存之间的数据传输之后再根据U位更新当前偏移量(事后操作),1表示在数据传输之前更新偏移量(事前操作)。
特别注意:不要将事前事后关联到是否更新基址寄存器Rn上。
关于S位
首先S位应该只在特权模式下使用。指令是否设置S位要看指令中是否有"^"符号。S=1后,根据寄存器列表中是否有PC,分为如下几种情况:
- 如果PC在寄存器列表中,LDM指令和STM指令处理方式不同:
- 对于LDM指令,处理器会在设置PC寄存器时,同步将SPSR_mode的内容放到CPSR中;
- 对于STM指令,处理器会将用户模式的寄存器放入内存,而不是当前模式的寄存器。这种情况也不应该使W=1,即改变基址寄存器;注意:此时会将PC+12保存到内存中;
- PC不在寄存器列表中,LDM和STM处理方式相同,都是在用户模式寄存器和内存之间进行数据传输。出于安全考虑,应该在LDM指令之后插入一条NOP指令,然后再访问寄存器内容(不理解,应该和流水线有关系);
寻址方式
从指令编码格式的介绍中,可以看出,批量Load/Store指令的寻址方式应该是由基址寄存器Rn、U位、和P位一起决定的。
此处在理解起来有些绕,需要牢记下面两条原则,这些原则是无论使用哪种寻址方式都是需要遵守的。
- 数据传输过程中,一定是按照R0~R15的顺序传输的;
- 传输后,一定是R0在内存的低地址,R1在内存的高地址;
下面以一个示例来说明支持的4种寻址方式。示例假定基址寄存器Rn为0x1000,参与批量传输的寄存器列表为R1、R5、R7,且假定数据传输完毕后,都会更新Rn寄存器(W=1)。
在理解下面的4个场景时,不妨想象一下,将批量操作分解成一个个单独的Load/Store步骤,这样便于理解其区别。
事后递增方式IA
假设当前操作的内存地址为A(A的初始值为Rn),事后递增指的是先执行当前寄存器和地址A的数据传输,然后再递增A的值。
通用伪代码如下:
start_address = Rn
end_address = Rn + (Number_Of_Set_Bits_In(register_list) * 4) - 4
if CondPassed(cond) and W == 1 then
Rn = Rn + (Number_Of_Set_Bits_In(register_list) * 4)
事先递增方式IB
假设当前操作的内存地址为A(A的初始值为Rn),事先递增指的是先递增A的值,然后再执行当前寄存器和地址A的数据传输。
通用伪代码如下:
start_address = Rn + 4
end_address = Rn + (Number_Of_Set_Bits_In(register_list) * 4)
if CondPassed(cond) and W == 1 then
Rn = Rn + (Number_Of_Set_Bits_In(register_list) * 4)
事后递减方式DA
假设当前操作的内存地址为A(A的初始值为Rn-12+4),事后递减指的是先执行当前寄存器和地址A的数据传输,然后再递减A的值。
通用伪代码如下:
start_address = Rn - (Number_Of_Set_Bits_In(register_list) * 4) + 4
end_address = Rn
if CondPassed(cond) and W == 1 then
Rn = Rn - (Number_Of_Set_Bits_In(register_list) * 4)
事先递减方式DI
假设当前操作的内存地址为A(A的初始值为Rn-12),事先递减指的是先递减A的值,然后再执行当前寄存器和地址A的数据传输。
通用伪代码如下:
start_address = Rn - (Number_Of_Set_Bits_In(register_list) * 4)
end_address = Rn - 4
if CondPassed(cond) and W == 1 then
Rn = Rn - (Number_Of_Set_Bits_In(register_list) * 4)
批量访存指令
ARM有如下批量Load/Store指令。
助记符 | 说明 |
---|---|
LDM(1) | 批量字数据读取指令 |
LDM(2) | 用户模式的批量字数据读取指令 |
LDM(3) | 带状态寄存器的批量字数据读取指令 |
STM(1) | 批量字数据写入指令 |
STM(2) | 用户模式的批量字数据写入指令 |
这些指令的通用语法如下:
<LDM|STM>{cond}<FD|ED|FA|EA|IA|IB|DA|DB> Rn{!},<Rlist>{^}
- !:如果有该符号,表示指令执行后,修改基址寄存器Rn的值,即W=1;
- ^:如果有该符号,表示设置S位,此时指令会根据寄存器列表中是否有PC,进而让PSR参与数据传输,或者执行用户模式处理,具体见前面关于S位的介绍。
LDM(1)(批量字数据读取指令)
LDM{<cond>}<address_mode> <Rn>{!}, <registers>
if CondPassed(cond) then
address = start_address
for i in [0, 14]
if register_list[i] == 1 then
Ri = Mem[address, 4]
address += 4
if register_list[15] == 1 then
value = Mem[address, 4]
if (arch version 5 or above) then
pc = value & 0xFFFF_FFFE
T Bit = value[0]
else
pc = value & 0xFFFF_FFFC
address += 4
assert end_address == address - 4
LDM(2)(用户模式的批量字数据读取指令)
和LDM(1)功能类似,不同点在于当在特权模式下使用该指令时,内存系统将以一般用户模式身份进行内存访问,而且最终也是将内存单元中的数据存入用户模式的寄存器中。
LDM{<cond>}<address_mode> <Rn>, <registers_without_pc>^
if CondPassed(cond) then
address = start_address
for i in [0, 14]
if register_list[i] == 1 then
Ri_usr = Mem[address, 4]
address += 4
assert end_address == address - 4
LDM(3)(带状态寄存器的批量字数据读取指令)
该指令的寄存器列表必须包含PC。
LDM{<cond>}<address_mode> <Rn>{!}, <registers_and_pc>^
if CondPassed(cond) then
address = start_address
for i in [0, 14]
if register_list[i] == 1 then
Ri = Mem[address, 4]
address += 4
CPSR = SPSR
value = Mem[address, 4]
if (arch version 4T, 5 or above) and (T Bit == 1) then
pc = value & 0xFFFF_FFFE
else
pc = value & 0xFFFF_FFFC
address += 4
assert end_address == address - 4
STM(1)(批量字数据写入指令)
STM{<cond>}<address_mode> <Rn>{!}, <registers>
if CondPassed(cond) then
address = start_address
for i in [0, 15]
if register_list[i] == 1 then
Mem[address, 4] = Ri
address += 4
assert end_address == address - 4
STM(2)(用户模式的批量字数据写入指令)
STM{<cond>}<address_mode> <Rn>, <registers>^
if CondPassed(cond) then
address = start_address
for i in [0, 15]
if register_list[i] == 1 then
Mem[address, 4] = Ri_usr
address += 4
assert end_address == address - 4
栈操作
批量Load/Store指令经常还用于栈操作,由于栈的FIFO操作特性,首先定义4个概念:
- 如果栈指针指向最后一个入栈元素地址,那么称该栈为Full栈;如果指向最后一个元素的下一个空位置,那么称该栈为Empty栈;
- 如果栈向高地址方向增长,那么称该栈为Ascending栈;相反,如果向低地址方向增长,那么称该栈为Descending栈;
理解技巧:向栈中放数据时,如果可以先放数据再更新栈指针,那么为Empty栈,否则为Full栈,这意味着Full栈和事先寻址对应,Empty和事后寻址对应。
综上,共有FA、FD、EA、ED四种组合,这四种组合和前面的四种寻址方式是一一对应的,它们有如下的对应关系。而且指令中也是可以根据不同的上下文灵活切换使用的。