第3章 ARM7体系结构
3.12 存储器及存储器映射 I/O
3.12.1 简介
ARM7TDMI 处理器采用冯·诺依曼(Von Neumann)结构,指令和数据共用一条 32 位 数据总线。只有装载、保存和交换指令可访问存储器中的数据。
ARM7 的规范仅定义了处理器核与存储系统之间的信号及时序(局部总线),而现实的芯 片一般在外部总线与处理器核的局部总线之间有一个存储器管理部件将局部总线的信号和 时序转换为现实的外部总线信号和时序。因此,外部总线的信号和时序与具体的芯片相关,
不是 ARM7 的标准。具体到某个芯片的外部存储系统的设计需要参考其芯片的数据手册或 使用手册等资料。
ARM7TDMI 处理器将存储器看作是一个从 0 开始的线性递增的字节集合:
字节 0 到 3 保存第 1 个存储的字 字节 4 到 7 保存第 2 个存储的字 字节 8 到 11 保存第 3 个存储的字
ARM7TDMI 处理器可以将存储器中的字以下列格式存储:
大端(Big-endian)格式 小端(Little-endian)格式 3.12.2 地址空间
ARM 结构使用单个平面的 232个8 位字节地址空间。字节地址按照无符号数排列,从 0 到232-1。
地址空间可以看作是包含 230个 32 位字,地址以字为单位进行分配。也就是将地址除 以4。地址为 A 的字包含 4 个字节,地址分别为 A, A+1, A+2 和 A+3。
在ARM 结构 v4 及以上版本中(ARM7TDMI 基于 v4 版本),地址空间还可被看作包含 231个16 位半字。地址按照半字进行分配。地址为 A 的半字包含 2 个字节,地址分别为 A 和A+1。
地址计算通常通过普通的整数指令来实现。这意味着如果地址向上或向下溢出地址空 间,通常会发生翻转。也就是说计算的结果以232为模。但是如果地址空间在将来进行扩展,
- - 45 地址0xFFFFFFFF 的向前转移和穿过地址 0x00000000 的向后转移都不应使用。
另外,正常连续执行的指令实际上是通过计算:
(当前指令的地址) + 4
来确定下一条要执行的指令。如果该计算溢出了地址空间的顶端,结果同样不可预测。
换句话说,程序不应信任在地址0xFFFFFFFC 处的指令之后连续执行的位于地址 0x00000000 的指令。
对于小端(little-endian)存储器系统:
在小端格式中,一个字当中最低地址的字节被看作是最低位字节,最高地址字节被看作
- - 46 0xFFFFFFFE),而在字访问时使用公式(地址 AND 0xFFFFFFFC)。
对存储器访问本身忽略造成访问不对齐的低地址位,然后使用这些低地址位控制装 载数据的循环(该动作只适用于LDR 和 SWP 指令)。
这3 个选项中的哪一个适用于装载/保存指令取决于具体的指令(参考第 4 章 )。
- - 47
在将地址发送到存储器时,ARM 实现不要求将造成不对齐的低地址位清零。可以将装 载/保存指令计算出的地址不加改变地发送到存储器,并在半字访问或字访问时请求存储器 系统忽略地址位bit[0]或 bit[1:0]。
3.12.5 指令的预取和自修改代码
许多ARM 实现在前一条指令的执行尚未完成时将指令从存储器中取出。这个动作称为 指令的预取。指令的预取并不是实际执行指令。指令后来没有被执行有两种典型的情况:
当发生异常时,当前指令执行完毕,所有预取的指令都被丢弃,指令的执行从异常 向量开始。
当发生跳转时,预取的在分支指令后的指令将被丢弃。
ARM 实现可以自由选择预取指令比当前执行点提前多少(即半导体厂商在设计具体的 芯片时可以自由选择预取指令比当前执行点提前多少),甚至可以动态改变预取指令的数目。
最初的ARM 实现在当前执行的指令之前预取两条指令,不过现在可以选择多于或少于两条 指令。
注意:当指令读取PC 时,它得到的指令地址比它自身地址落后了两条指令:
对于 ARM 指令,得到的地址是它自身地址+8;
对于 Thumb 指令,得到的地址是它自身地址+4。
最初的ARM 实现曾经在 PC 读取的两指令偏移量和两指令预取之间存在关联。但这一 关联不是结构上的。一个预取不同数目指令的ARM 实现仍能保证读取 PC 所得到的地址比 它自身地址落后两条指令。
和自由选择多少条预取指令一样,ARM 实现可选择沿着哪条可能的执行路径进行预取。
例如,在一条分支指令之后,它可选择预取分支指令之后的指令或者是转移目标地址的指令。
这称为转移预测。
所有形式的指令预取都有一个潜在的问题,即存储器中的指令可能在它被预取之后,被 执行之前发生改变。如果发生这种情况,对存储器中的指令进行修改通常并不妨碍已取指的 指令备份执行完毕。
例如,在下面的代码序列中,STR 指令使用 ADD 指令的备份取代了它后面的 SUB 指 令:
LDR r0, AddInstr STR r0, NextInstr NextInstr
SUB r1, r1, #1 .
. . AddInstr
ADD r1, r1, #1
但是当代码第一次执行时,STR 指令之后执行的指令通常是 SUB 指令,因为 SUB 指令 在存储器中的指令发生改变之前已经被预取了。ADD 指令不会被执行,除非第二次执行该 代码序列。
其实,处理器不能保证按照上面所述的方式执行,因为:
当代码第一次执行时,在 STR 指令之后有可能立即产生一个中断,如果这样,已经 预取的SUB 指令将被丢弃。当中断处理程序返回时,位于 NextInstr 处的指令被再 次预取,而这次则执行ADD 指令。因此虽然 SUB 指令通常最有可能被执行,但也 有可能执行ADD 指令。
- - 48
如果指令被再次执行,ARM 处理器或存储器系统允许保持预取指令的备份并使用 这些备份而不是重新预取。如果发生这种情况,在代码序列按照第二及以下可能性 执行时,SUB 指令可被执行。
发生这种情况的主要原因是存储器系统包含独立的指令和数据缓存。但是也存在其它可 能性。例如,一些分支预测的硬件保存了分支后的指令。
总之,应当尽可能避免使用涉及自修改代码的编程技术。
指令存储器屏障(IMB)
在许多系统中,几乎不可能完全避免自修改代码的使用。例如,任何一个允许将程序装入存 储器然后执行的系统都使用自修改代码。
因此每个ARM 实现(可以理解为具体芯片)都定义了一系列的操作使自修改代码序列 可以可靠地执行。这一串代码称为指令存储器屏障(IMB),它通常同时取决于 ARM 处理 器的实现和存储器系统的实现(可以理解为具体的芯片)。
IMB 序列必须在新的指令已经保存到存储器之后而尚未执行时执行。例如,在程序被 装载之后并且在转移到它的入口之前。任何不以这种方式使用IMB 的自修改代码序列都可 能会执行不确定的动作。
根据IMB 所执行的确定的操作顺序取决于 ARM 和存储器系统的实现(可以理解为具 体的芯片)。建议在软件设计时使IMB 序列作为一个调用程序来替换与系统相关的模块,而 不是直接插入到需要的地方。这样易于移植到其它ARM 处理器和存储器系统。
另外在许多实现当中,IMB 序列包含了只能在特权模式下使用的操作,例如标准系统 控制协处理器提供的缓存清零和无效操作。为了允许用户模式程序使用IMB 序列,推荐将 其作为一个操作系统调用程序,由SWI 指令调用。
在SWI 指令使用 24 位立即数的系统中指定所要求的系统服务,推荐通过下面的指令来 请求IMB 序列:
SWI 0xF00000
这是一个无参调用,不返回结果,应当使用与带原型的C 函数调用相同的调用约定:
void IMB(void) ;
区别在于使用SWI 指令调用而不是 BL 指令。
有些实现可对已保存的新指令使用地址范围的知识来减少IMB 执行的时间。因此还推 荐执行第二个操作系统调用程序,该调用程序只根据指定的地址范围执行IMB。在 SWI 指 令使用24 位立即数的系统中指定所要求的系统服务,推荐通过下面的指令来请求:
SWI 0xF00001
应当使用与带原型的C 函数调用相似的调用约定:
void IMB_Range(unsigned long start_addr, unsigned long end_addr);
此处,地址范围从start_addr(包含)到 end_addr(不包含)。
注意:
当使用标准的 ARM 过程调用标准时,start_addr 在 R0 中传递,而 end_addr 则在 R1 中传递。
对于某些 ARM 实现来说,即使使用小地址范围,IMB 执行的时间也可能非常长(数 千个时钟周期)。对于自修改代码的小规模使用,这样很可能使性能上受到较大损 失。因此建议自修改代码只用于不可避免和/或有足够的执行时间裕量的情况。
IMB 的其它用途
有些存储器系统允许虚拟-物理的地址映射,其中物理存储器位置对应于ARM 处理器 产生的地址,它可以被改变。如果该地址映射在指令预取之后,执行之前被更改,指令的地 址会受到地址映射更改的影响,那么将执行错误的指令。
- - 49
这非常类似于在指令被预取但尚未执行时在指令地址发生保存的情况。在这两种情况 下,由于有一个值保存到该地址或者该地址关联到一个不同的物理存储器位置,保存在存储 器地址的指令被改变。当虚拟-物理地址映射发生改变时可以使用相同的解决办法。IMB 序列必须在虚拟-物理地址映射改变之后,并在指令执行之前执行。
另一种相似的情况是在指令预取和指令执行之间这段时间内发生存储器访问许可的变 更。如果在指令预取时不允许访问,而在指令执行时允许访问,可能会发生不期望的预取中 止异常。在相反的情况,即指令预取时允许访问而在指令执行时禁止访问,系统中可能存在 安全漏洞。
存储器访问许可的改变通常是因为将新的访问许可设定写入存储器或是因为存储器系 统对于用户模式、特权模式以及发生的下列情况支持不同的访问许可:
在用户模式下产生一次异常,导致处理器切换到特权模式。
在用户模式下产生一次异常,导致处理器切换到特权模式。