ARM Overview

来自osdev
Zhang3讨论 | 贡献2022年1月20日 (四) 07:47的版本
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

ARM是由单个公司-ARM Holdings开发的基于RISC体系结构的指令集体系结构家族。

因为ARM是一个体系结构家族,而不是单一的体系结构,所以可以在大量电子设备中找到它 (从带有ARM mcu的简单小型嵌入式系统,到智能手机,平板电脑和MP3播放器,再到低功耗服务器)。 当你查看带有ARM处理单元的设备时 (术语处理单元(processing unit)更准确,因为可以在微控制器和微处理器中都可以找到ARM),有两个关键字很重要: 体系结构和核心。

到目前为止 (2014年第三季度),已经发布或宣布了8个ARM架构 (其中一些有扩展版本),其中7个是32位,最后一个是64位,但是用户空间兼容32位指令集 (因此可以运行32位用户进程,但没有虚拟化的32位操作系统)。 非常宽泛地说,在每个新的体系结构版本中,所有内核 (有例外) 都添加了一些新功能,这些功能已经在以前的体系结构的某些内核中进行了尝试。 并非以前架构的所有功能在下一个架构中都可以再次可用,并且并非以前架构中添加的所有新版本的技术都必须与旧的兼容。 之所以这样最简单的原因是,设计处理器不像编写软件: 当程序决定是处理旧文件格式还是新文件格式时,它会做出判断,然后只执行其中一个代码,但是 (例如指令) 格式的向后兼容性可能意味着更多的晶体管,而更多的晶体管通常 (总是?) 导致更多的发热量。

你一定知道你不能像购买英特尔abc或AMD xyz那样购买ARM处理器。 ARM Holdings是一家设计架构,编写 “架构参考手册”,然后设计核心并编写 “技术参考手册” 的公司。 最后,它向一家芯片制造公司出售设计核心,并向公众发布手册。 再往后由一家芯片制造公司来设计一个处理单元-微控制器 (MCU),片上系统 (SoC),FPGA等-它可以自己制造硅片或从半导体制造商订购数千个芯片。 最终你在平板电脑或智能手机中看到的所有处理器都是soc,它们充当带有处理器的主板。 它们包含用于驱动外围设备 (以太网,USB,SD/mmc卡,SPI,I2C,音频) 的逻辑,它们可能包含用于图形协处理的GPU或用于自定义逻辑的FPGA。

ARM有许多使用场景,也意味着它很少有多功能 (并且在此之前是复杂且耗电的) 单设备的情况,或者许多仅适用于这种操作的简单设备。 ARM处理器作为RISC设备选择简单性而不是复杂性,因此它们走上了一条不同内核使用不同指令的道路。 为了只有 “一种汇编程序来管理它们”,ARM定义了统一的汇编语言,可以将其针对 “任何” ARM内核进行翻译。

ARM核心以最新版本分为三个主线:

  • Cortex-M内核,用于非常小的设备,通常具有片上存储器和更简单的操作
  • Cortex-R核心,用于实时设备
  • Cortex-A cores,用于智能手机、电视或电脑等多功能设备的应用。

苹果机使用自定义ARM内核,一些Nvidia主板也使用定制内核。

概述

ARM处理器在各种模式下运行。 包含User、Fast Interrupt、Interrupt、Supervisor、Abort和System。 每种模式都有自己的堆栈和运行环境。

虽然ARM手册区分了这些模式,但划界针对不是在其相关的特权级别上进行的,而是在其目的上进行的。 ARM中基本上有两个特权级别: 手册将它们称为 “特权方式” 或 “非特权方式”。

在上面列出的模式中,“User” 是唯一的非特权模式,而其他的则是体系结构的特权模式。

为什么有这么多模式? “Interrupt” 模式和 “Fast Interrupt” 模式之间有什么区别? 难到我将不得不编写一种特殊的抽象,将内核的中断处理与其操作的其余部分分开? 其实并没有。

这些模式只是当发生特定类型的异常或特定类型的IRQ时处理器处于的状态。

以下是从ARM7TDI资料表复制 (3.6 操作模式):

Mode 描述
User (usr) 正常ARM执行状态
FIQ (fiq) 旨在支持数据传输或通道处理
IRQ (irq) 用于通用中断处理
Supervisor (svc) 操作系统的保护模式
Abort mode (abt) 在数据或指令预取中止后输入
System (sys) 操作系统的特权用户模式
Undefined (und) 执行未定义指令时输入

ARMv4及up上的异常、irq和软件中断

ARMv4体系结构为该体系结构定义了七个向量,与x86的总共256个向量形成对比。 另外,请注意,内核没有选择将syscall条目存储到哪个中断向量的选择: ARM静态地为软件中断提供 'SWI' 指令,而没有 'INT N' 指令,因此syscalls静态地固定到特定向量。

现在我们尝试进入处理器的各种特权模式: 当执行SWI指令时,ARM处理器向量进入内核提供的向量表,并跳转到为SWI指令留出的硬件固定向量。 由于没有其他逻辑选择,内核有望在此向量上安装syscall捆扎代码。

但是,而不是像x86那样读取要应用于特定向量的特权级别或特权设置 (我指的是每个IDT条目中存在的x86的GDT选择器),而是采用了模式的思想,因此对于SWI指令,ARM CPU将自动进入 “主管” 模式。

请记住,从现在开始,“Supervisor” 模式是内核预期运行的标准模式。 “系统” 模式不会在任何公共矢量上切换到; 这就像,基于手册引用它的狡猾方式,以及没有定义的中断实际上切换到 “系统” 模式的事实,即PC上的 # SMI (系统管理中断,切换到系统管理模式)。

从 “系统” 模式,内核将处理SWI,然后返回用户空间。

处理器在遇到x86上的页面错误时切换到的中止模式。 处理器切换到特权模式,但特定模式 “中止”,上面列出的模式之一。 大多数ARM内核只是注意到处理器在内核enter上处于中止模式,然后切换到 “主管” 模式并处理异常。

中断模式和快速中断模式几乎相同,只是FIQ模式被赋予了自己的一组寄存器 (寄存器实际上是从你所引用的寄存器中切换而来的,尽管它们仍然具有相同的名称 (例如,r8仍然是,在你的程序集中,称为r8; 但是,在FIQ模式下,你必须意识到,如果你在usermode中拥有r8中的信息,你不应该期望在FIQ模式下在名为r8的寄存器下找到相同的数据),这样它就可以执行IRQ处理程序,而不必过多地保存上下文。

本质上,像x86一样,你将拥有可以由内核配置的硬件。 内核知道它只有7个向量,并且其中两个仅指定用于IRQ,将给出2个IRQ向量之一 (IRQ或FIQ向量) 的IRQ编号。 对于被配置为在IRQ向量上通信号通知IRQ的设备,所述处理器在接收到IRQ向量上的中断信号时将进入特权模式,处于IRQ模式状态。

对于在FIQ向量上接收到的中断,处理器将进入特权模式,但处于FIQ模式状态。

“未定义” 模式在CPU遇到未定义异常时切换到。 然而,基于ARMv4手册中使用的语气,以及它们公然暗示这一点的事实,异常模式实际上是为了让内核模拟usermode进程的指令,然后返回。

关于 “幽灵” 漏洞的注意

ARM最近在其规范中添加了一条新的CPU指令,以减轻缓存推测旁通道漏洞利用。 你可以阅读有关此设计补丁的更多信息,并建议你采取有关Spectre的操作 (https://developer.arm.com/-/media/Files/pdf/Cache_Speculation_Side-channels.pdf?revision=8b5a5f33-c686-4b00-8186-187dd2910355 here)。

Page Description
http://www.embedded.com/design/prototyping-and-development/4006695/How-to-use-ARM-s-data-abort-exception 关于数据和指令中止异常的好文章

寄存器

ARM处理器的寄存器数量有些大。 例如,ARM7具有37个寄存器,其中31个是32位通用寄存器,其中6个是状态寄存器。 有些只能通过某些模式使用。

与x86不同,重要的操作寄存器可以通过一般通用寄存器清晰可见。 例如,r15是 “pc” 或 “程序计数器 program counter”,而r13是 “堆栈指针stack pointer” 或“ sp ”。

除通用寄存器外,还有CPSR寄存器,或 “当前程序状态寄存器”。 此寄存器跟踪当前操作模式,是否启用中断等。 操作系统可以使用MSR \ MRS指令读取和写入此寄存器。 (1472.html见这里) 例如,还有其他几个系统寄存器可用于 detect the ARM board

Mode R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 (SP) R14 (LR) R15 (PC)
User32/System R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13 R14 R15
FIQ32 R0 R1 R2 R3 R4 R5 R6 R7 R8FIQ R9FIQ R10FIQ R11FIQ R12FIQ R13FIQ R14FIQ R15FIQ
Supervisor32 R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13SVC R14SVC R15SVC
Abort32 R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13ABT R14ABT R15ABT
IRQ32 R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13IRQ R14IRQ R15IRQ
Undefined32 R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 R10 R11 R12 R13UNDEF R14UNDEF R15UNDEF

每种模式与其他模式共享一些寄存器。 通常,寄存器在ARM指令中指定,它们使用4位。 可以表示16个寄存器。 如你所见,你可以在每种模式下使用指令来引用16个寄存器。 指定了助记符名称 穿过顶部标头,如通过r15基本传染数。R13、R14和R15的别名为 在括号中指定,即SP、LR和PC。 所以使用助记符R15 与使用助记符PC相同,但请记住,这仅与汇编器/编译器有关。 ARM处理器只理解大多数指令中使用4位给出的值。 所代表的确切值 对每个助记符使用这4位可以通过简单地移除前缀字母R来获得。 使得R5由4个比特表示为0101,R6表示为0110,R7表示为0111。 后缀 “irq”,“undef”,“abt”,“svc” 和 “fiq” 的表示仅用于显示,仅与此表相关,并且不被任何汇编程序/编译器视为有效。 例如 “R13ABT” 、 “R13IRQ” 、 “R13UNDEF” 、 “R13SVC” 和 “R13FIQ” 都简单地表明 处理器在内部将R13 (SP) 映射到其寄存器文件中的不同地址。

致电惯例备忘单

以下是常见调用约定的快速概述。 请注意,调用约定通常比此处表示的更复杂 (例如,如何返回一个大结构?适合两个寄存器的结构如何? va_list的怎么样?)。如果要确定,请查找规格。 编写测试函数并使用gcc -S来查看编译器如何生成代码可能会很有用,这可能会提示应如何解释调用约定规范。

Return Value Parameter Registers Additional Parameters Stack Alignment Scratch Registers Preserved Registers Return address
R0, R1 R0, R1, R2, R3 stack (R13) 8 byte1 R0, R1, R2, R3, R12 R4, R5, R6, R7, R8, R9, R10, R11, R13 R14

Note 1: Stack is 8 byte aligned at all times outside of prologue/epilogue of non-leaf function. Leaf functions can have 4 byte aligned stacks. 8 byte alignment is required for ldrd/strd to function on the stack, which mostly only comes into play when using varargs with 64-bit integers. Care must be taken in interrupts and exceptions to align the stack to 8 byte before calling C code.

For details look at the EABI specs.

指令

Overview
指令长度是恒定的固定大小。32位A32模式。16位拇指32模式。
通过结合一些多个循环指令,偏离了RISC设计。
在指令中使用内联条件字段来减少管道刷新带来的性能损失。
与CISC设计相比,RISC设计可能会产生更大的代码大小,但支持Thumb32模式,该模式使用16位长度的指令 (更有限的指令集) 进行32位操作,从而产生更小的代码占用空间,降低性能。
大多数指令在几个时钟周期或单个周期内执行,使实时软件的开发更加容易。
A32和Thumb16模式可以即时切换 (值得注意!)

有趣的是,将大的立即值加载到寄存器中可能与x86/x64不同。 立即值是从字面上编码到指令中的值。 例如,x86/x64兼容的处理器支持将32位立即 (也称为常量) 加载到任意寄存器中。 ARM的A32和Thumb32指令集没有。 相反,必须使用一系列指令来获得相同的效果或将值存储在指令流之外的内存中。 本质上,可以考虑将指令流外部的值的存储与内部的存储相同,但是在许多方面确实存在差异,这是要记住的关于ARM的事情。 但是,任何编译器或汇编程序都可以并且可能会为你解决此问题。 例如,如果你正在编译C/C代码,那么这些问题对你来说是透明的,但是正如我所说的,这是了解和理解的好信息,尤其是在系统软件开发中。

例如,由GCC生成的用于加载具有32位值的寄存器的机器代码。 正如你可以注意到的那样,直接值在技术上是在指令流之外的。 在x86/x64上完成 值将被编码到指令中。 另外,请注意每个指令的长度是恒定的。 执行工作的指令是使用PC寄存器的LDR和立即偏移,以便将值0x12345678放入寄存器r3中。

00000000 <main>:
   0:	e1a0c00d 	mov	ip, sp
   4:	e92dd800 	push	{fp, ip, lr, pc}
   8:	e24cb004 	sub	fp, ip, #4
   c:	e59f300c 	ldr	r3, [pc, #12]	; 20 <main+0x20>
  10:	e1a00003 	mov	r0, r3
  14:	e24bd00c 	sub	sp, fp, #12
  18:	e89d6800 	ldm	sp, {fp, sp, lr}
  1c:	e12fff1e 	bx	lr
  20:	12345678 	.word	0x12345678

可以这么说,通过将值存储在指令流中来加载寄存器的示例。

              
mov	r8, #0x78
add	r8, r8, #0x56 << 8
add	r8, r8, #0x34 << 16
add	r8, r8, #0x12 << 24


几乎所有指令都支持直接编码到指令中的条件执行。 在x86/x64体系结构上,当需要有条件地执行一系列指令时,你可能会发现一个分支指令,该分支指令为处理器创建了一个完全独立的代码路径,以便分支发生。 ARM支持分支,但几乎所有单独的指令都支持有条件的执行,因此,它不会导致处理器潜在地刷新和重新填充管道,因为发生了分支,而是会将指令加载到具有特殊条件字段的管道中。 最多有16个指定的条件代码,其中一个保留条件构成15个可用条件。 因此,这些指令可以保留在执行路径中,而不是仅执行分支来执行几个指令,而不会执行或仅使用条件代码执行。

例如,以下是条件代码:

Mnemonic Binary Description
EQ 0000 Z flag set (equal)
NE 0001 Z flag clear (not equal)
HS 0010 C flag set (unsigned higher or same)
LO 0011 C flag clear (unsigned lower)
MI 0100 N flag set (negative)
PL 0101 N flag clear (non-negative)
VS 0110 V flag set (overflow)
VC 0111 V flag clear (no overflow)
HI 1000 C flag set and Z flag clear
LS 1001 C flag clear or Z flag set
GE 1010 N flag set and V set or N clear and V clear
LT 1011 N set and V clear or N clear and V set
GT 1100 Z clear with (either N or V set), or N clear and V set
LE 1101 Z set or (N set and V clear), or N clear and V set
AL 1110 always (no condition really)
NV 1111 reserved condition

内存

许多ARM处理器都配备了 MMU,用于其4GB地址空间的完整内存保护方案以及 TLB

分页

ARM处理器使用的分页方案使用2级页表。 第一级页表具有16kB的大小,每个条目覆盖1mb (部分) 的区域,可以直接映射或指向第二级页表。 第二级页表的大小为1kB,每个条目覆盖4KB (页) 的区域。TLB还支持16MB的部分和64kB的页面。 对于那些,必须设置第一级或第二级页表条目中的正确位,并且该条目重复16次。 用于任何重复条目的硬件页表行走,然后添加较大尺寸的TLB条目。 在ARMv4和ARMv5页面上,也可以分为手册中称为 “子页面” 的内容,对于4KB页面为1KB子页面,对于64KB页面为16KB子页面。 (我假设你意识到这两种情况下的子页面都1/4主页大小的事实)。 这在ARMv6上是不推荐使用的,并且可以配置一个备用页表格式,该格式没有子页,但具有额外的访问权限位和物理地址扩展的规定 (用于超过4GB的物理内存)。

ARM处理器支持2个独立的页表。 第一页表可以以2的幂从完整的16kB减少到128字节,并且用于虚拟地址空间的下部。 第二页表的大小始终为16KB,用于第一页表末尾以外的地址。 如果第一页表已经覆盖了所有虚拟地址空间,并且已经是完整大小 (16kB),则第二页表将永远不会使用,并且可以不设置。

体系结构参考手册建议使用已废除的第一页表来处理单个地址空间,并使内核的第二级页表静态。 对于小进程 (使用2GB或更少),这将导致更小的页表,因此每个进程的开销更少。

注意: ARMv6体系结构参考手册多次明确指出,子页面已被弃用,不应使用。 ARMv7体系结构参考手册中的子页面已被删除,不再可用。 因此,为了获得最大的可移植性,你可以决定坚持4KB和64KB的页面大小。

或者,你可以为ARM内存管理器端口设置两组抽象: 一个MM用于ARMv4和v5,一个用于ARMv6及以上。 一切都取决于你。

内存检测

如果你来自x86/x64体系结构的背景,则内存检测将大不相同。 ARM内核在许多嵌入式应用中使用,因此内核所在的系统板不需要过于复杂以与其他板兼容。 这是因为几乎所有带有ARM核心的生产板都可能是为此目的而定制的。 使用某些通用板是很有可能的,但是对于许多嵌入式应用程序,甚至可能没有操作系统。

因此,内存检测机制可能不存在,相反,你的操作系统可能会在编译时选择将值编码到其中,对于已知具有一定内存量的特定系统 (板),或者,你可能有一个特定的驱动程序,你的操作系统只为该板加载 (或在其中编译),你的内核可以查询该驱动程序的安装内存量。 有很多方法,这只是两个想法,但重要的部分是要了解,与x86/x64兼容的系统不同,你可能会发现绝对没有机制来 “适当地” 轮询内存量。

但是,可以使用处理器异常来探测内存并恢复。 这仍然可能无法提供有关内存区域是否为闪存,内存映射的I/O,RAM或ROM的信息,具体取决于系统板的设计方式,因为我怀疑某些ROM可能是外部的核心,并允许写入静默失败,再加上内存区域需要特殊的解锁序列才能写入的可能性,这将使你的内存自动检测代码变成潜在的情况。

但是,如果你 “认为” 可以做到,那么一定要尝试,因为你可能会找到一种方法。 这一部分只是为了给你一个关于你可能会进入的东西的良好的总体感觉,我并不是要让你可能拥有的任何好的想法成为有价值的东西。

Exceptions异常

”注意: 出于某种原因,ARM开发者使用“ interrupt中断 ”和“ exception异常 ”这两个术语,作为同一个意思。”

对于异常,ARM使用类似于实模式x86的IVT的表。该表由许多32位条目组成。 每个条目都是一条指令 (ARM指令长度为4个字节。),跳转到适当的处理程序。

请注意这一点,并了解它带来的设计影响: 在x86上,硬件矢量表保存处理程序例程的地址。 在ARM上,硬件矢量表保存实际指令。 这些指令必须适合4个字节。 这实际上没什么大不了的,因为所有的ARM指令 (假设ARM模式,而不是Thumb,或Jazelle) 实际上是4个字节。 一般的想法是模仿x86之类的东西的行为,并简单地将跳转指令放入实际的矢量表中,以便在索引到表中时,使ARM处理器的行为就像跳转到包含的地址一样在跳转指令中。 从那里获得一致性,并简化了可移植性。

还使用了各种设备来 “矢量” 中断。 通用中断控制器和矢量化中断控制器是两个。

编码陷阱

堆指针需要对齐

我目前无法说明有多少处理器本地支持未对齐内存访问,但是据我所知,未对齐内存访问比对齐更昂贵。 而且,在堆上分配了很多次,这些结构的字段大小大于一个字节。 因此,从这个你可以看到你可以接受多大的性能打击。 更不用说如果在处理器下运行,甚至不能优雅地处理未对齐的内存访问,就会引入的错误,我的意思是至少引发某种例外,这样代码就可以崩溃并显示问题。

缺少除法功能 (__ aeabi_uidivmod,__ aeabi_idiv)/没有除法支持

这是由于使用GCC而没有与「libgcc」链接造成的。使用GCC时,你 “需要” 与 “libgcc” 链接。 有关为什么要链接的信息和有关 “Libgcc” 的信息,请阅读 LibgccGCC_Cross-Compiler

你还必须确保使用已为目标机器/体系结构/平台编译的 “libgcc”。 有关更多信息,请参见 Libgcc。你可以通过阅读 GCC_Cross-Compiler 为GCC生成正确的库。

本节仅是你想要更深入的解释和讨论的时候。

这个问题实际上很复杂,因为不同的cpu可能不支持硬件划分,仅通过浮点操作,拇指模式,本机模式或组合来支持它。 此外,在某些情况下,你可能会发现使用不同的方法更快或更紧凑,因为你可能对代码大小或性能感兴趣。 “Libgcc” 将处理所有这些情况并提供所需的符号,但是这里有一些各种信息来源,可以使你更深入地了解:

以下链接讨论了处理此问题的不同方法: 
    http://forum.osdev.org/viewtopic.php?f=1&t=23857&p=212244
    http://stackoverflow.com/questions/8348030/how-does-one-do-integer-signed-or-unsigned-division-on-arm
另外,一些可能有用的额外信息:
    http://www.linkedin.com/groups/ARM-cores-hardware-division-85447.S.242517259
关于此部分的讨论,以及最后的libgcc版本的divide函数示例:
    http://forum.osdev.org/viewtopic.php?f=8&t=27767
libgcc的来源和除法仿真函数的来源:
    https://github.com/mirrors/gcc/blob/master/libgcc/udivmodsi4.c
    https://github.com/mirrors/gcc/blob/master/libgcc/
未对齐的内存访问和字节顺序
据说各种较新的ARM内核都支持未对齐的内存访问,但是必须在程序控制寄存器中设置和取消设置特定的或两个位。 我还没有检查QEMU对ARMv7的仿真是否支持这一点。 所以请记住这一点。

此外,建议看看这篇关于 [1],这将有助于澄清并可能提供第二个信息来源。

让我们想象一下,我们有一个8字节的RAM板,如下所示:

Value A B C D E F G H
Address 0 1 2 3 4 5 6 7

一台 “小端” 机器首先读取最低有效字节 (LSB)。 因此,如果我们在上面的存储器上的地址 “0” 处读取32位 (字大小),则会产生 “DCBA”。 在首先读取最高有效字节 (MSB) 的 “大端” 机器上,你将获得 “ABCD”。 这完全取决于你如何可视化内存地址-它们是流向右边 (大字节序),还是流向左边 (小字节序)。

一些CPU平台甚至支持任何一种模式。 例如,ARM7TDMI-S有一个大头针从小端切换到大端。 (尽管我找不到启用此功能的QEMU选项。)

ARM核心与x86/x64有另外两个不同:

  • 它们不以任何定义的方式处理未对齐的内存访问。 在 “4-32 ARM7TDMI-S数据表” 上指定,如果半字访问中的内存地址的位 “0” 为 “高”,则结果为 “未定义”。
  • 在小端序模式下访问半词将交换单词中的半词。

后者的示例:

uint32        *a;
uint16        *b;

a = (uint32*)0;
b = (uint16*)0;

a[0] = 0x12345678;
printf("%#04x --> %#04x\n", b[0], b[1]);

This gives 0x5678 --> 0x1234, while in big endian mode you would get 0x1234 --> 0x5678.

Little Endian Word Size (32-bit)
ED CB A9 87 78 9A BC DE
Offset Register(LittleEndian) Register(BigEndian)
00 0x87A9CBED 0xEDCBA987
01 UNALIGNED - UNDEFINED
10 UNALIGNED - UNDEFINED
11 UNALIGNED - UNDEFINED
Little Endian Half-Word Size (16-bit)
ED CB A9 87 78 9A BC DE
Offset Register(LittleEndian) Register(BigEndian)
00 0xA987 0xEDCB
01 UNALIGNED - UNDEFINED
10 0xEDCB 0xA987
11 UNALIGNED - UNDEFINED
Byte Size (8-bit)
ED CB A9 87 78 9A BC DE
Offset Register(LittleEndian) Register(BigEndian)
00 0xED 0xED
01 0xCB 0xCB
10 0xA9 0xA9
11 0x87 0x87

从阅读数据表中可以看出,如果在大端序模式下运行,半字访问将被逆转,变得更加自然,就像你在x86/x64体系结构上所期望的那样。 但是,由于我目前无法实际测试它,所以我希望我做对了。

可能会定义 “偏移量为“ 10b (0x2) ”的单词访问,但我不确定,因为它并没有真正声明。 但是,它可能会采用某些机制来加载半字。 (如果错误,需要有人来纠正它)

必须对齐内存访问的原因是,由于内存模块的工作方式 (请参见下面的DDR数据表),未对齐的访问需要额外的访问周期,并且会增加加载/存储操作 (以及处理器内核) 的复杂性。

你可以在ARM7TDMI-S上模拟未对齐的内存访问,但是你必须从两个内存位置进行单独的加载。 编译器可能会发出代码自动做到这一点,但检查是否访问未对齐与否将会减慢 all 内存访问. 当你不知道地址是对齐还是不对齐时,第 “4-35” 页上的 “ARM7TDMI-S数据表” 中提供了一些代码用于此类 “检查” 内存访问。

向量表中的分支指令

在使用ARMv4和兼容内核时,请记住,在矢量表中,每个条目都是32位 -- 并且每个插槽中的值 “不是” CPU也应该跳转的地址。

相反,它是一个指令。 当你第一次开始时,这可能会有点令人困惑,因为它似乎是一个地址,但它是一个实际的32位指令。 它可以是任何指令,但通常使用的是 “ldr” (数据加载) 指令,该指令从指令的4k字节中立即加载到PC中,或使用26位带符号偏移量的 “b” (分支)。

另外,如果你使用 “b” 指令。 请记住,它相对于指令指针。 因此,对于表中的每个 “索引”,从中减去 “索引 * 4”。 然后从中减去一个额外的 “8”,这样你就可以减去 “索引 * 4 8”。 此 “8” 来自预取 (已填充管道)。

如果没有意识到这一点,可能会导致一些服务例程的工作时间足够长,让你认为它们正在工作,然后在未来的几天里出错,让你浏览大量添加的代码。

#define ARM4_XRQ_RESET   0x00
#define ARM4_XRQ_UNDEF   0x01
#define ARM4_XRQ_SWINT   0x02
#define ARM4_XRQ_ABRTP   0x03
#define ARM4_XRQ_ABRTD   0x04
#define ARM4_XRQ_RESV1   0x05
#define ARM4_XRQ_IRQ     0x06
#define ARM4_XRQ_FIQ     0x07

/*
    Will install a branch instruction for the 
    interrupt vector for the ARM platform.
*/
void arm4_xrqinstall(uint32 ndx, void *addr)
{
    uint32      *v;
    
    v = (uint32*)0x0;
    v[ndx] = 0xEA000000 | (((uintptr)addr - 8 - (4 * ndx)) >> 2);
}

填充表的所有条目可能是一个明智的想法。 如果表用零填充,则每个向量将保存指令 “andeq基本传染数,基本传染数,基本传染数”,它不执行任何操作。 这意味着如果CPU跳到一个未填充的向量,它将有效地执行NOP并移动到下一个向量,这可能会导致代码中令人困惑的错误。 至少将任何未使用的向量指向一个虚拟函数,该函数将通知你发生未处理的异常!

使用AS (Binutils/GCC) 语法指定CPSR

你必须使用 “cpsr” 而不是 “% % cpsr” 或任何其他形式。

uint32 arm4_cpsrget()
{
    uint32      r;
    
    asm("mrs %[ps], cpsr" : [ps]"=r" (r));
    return r;
}

void arm4_cpsrset(uint32 r)
{
    asm("msr cpsr, %[ps]" : : [ps]"r" (r));
}

/* Bit 7 and 6 have to be logic-low/unset/zero/cleared to enable the interrupt type. */
void arm4_xrqenable_fiq()
{
    arm4_cpsrset(arm4_cpsrget() & ~(1 << 6));
}

void arm4_xrqenable_irq()
{
    arm4_cpsrset(arm4_cpsrget() & ~(1 << 7));
}
GCC (字符未签名)

在某些情况下,针对ARM体系结构的GCC可能无法像你预期的那样处理带符号值。

考虑以下情况:

void foo(char *a)
{
    *a = -1;
}

void bar()
{
    char      z;
    short     x;

    x = 0;
    foo(&z);

    x = x + z;
}

在两个补码系统中,“x” 的期望值为 “-1” 或 “0xffff”,但是当你获得 “255” 或 “0xff” 时,你可能最终会感到惊讶并花费大量时间。 这是解释。

  4c:	ebfffffe 	bl	0 <foo>
  50:	e55b300f 	ldrb	r3, [fp, #-15]
  54:	e1a02003 	mov	r2, r3
  58:	e15b30be 	ldrh	r3, [fp, #-14]
  5c:	e0823003 	add	r3, r2, r3

x86/x64目标架构会导致GCC将「chart」视为「signed chart」 [most预期的行为],但在针对 ARM你可能会惊讶地发现 “字符” 被视为 “无符号字符”。 你必须指定 “签名” 在「charn」之前,然后编译器会生成代码来进行正确的加法。

这个链接解释得更好。 另外,如果你决定跳过阅读。 本质上,假设 “char” 的代码应该是 “签名的字符” 可能被认为本质上是不正确的,而且是由一张海报指出的。 所以,保持 检查一下这是否具有便携性。

Mailing List Post

模拟器

针对多个基于ARM的设备 有关定位多个基于ARM的设备的信息,请参见 here

教程和起点

Page Brief Description
BeagleBoard 关于德州仪器BeagleBoard裸机 [OS] 开发的教程。 专门为BeagleBoard编写的-xM版本C。
Integrator Barebones This is a tutorial. 阅读入门和初学者的错误。 应该对手臂组装有合理的了解,但可能会少一些。
Integrator-CP QEMU PL110 16-Bit Color Example 快速,肮脏,并使QEMU PL110 16位彩色帧缓冲区正常工作。 可能无法在真正的硬件上工作,但会非常接近。 这只是为了让某人开始。 形成为教程,但很短。
PL050 PS/2 Controller 有关连接PS/2设备 (特别是鼠标) 的信息,并从使用pl050开始。如果PL050不感兴趣,请参阅底部的链接。
IRQ, Timer, And PIC 这演示了使用异常 (特别是IRQ异常) 、计时器和PIC。 它还为使用ARM和/或QEMU进行黑客攻击提供了良好的基础。
ELK Pages (Thin ARM) 实验学习内核页面旨在使某人逐步使用可能 (在系列的后期) 与某些领域的标准和常规设计不同的实验设计和实现来构建功能内核。
ARM_RaspberryPi Description and details on the commonly used Raspberry Pi boards
QEMU realview-pb-a board 本页为你提供了一些关于QEMU下realview-pb-a板编程的信息。 实际的硬件 “可能” 有所不同。 它还包含指向数据表的链接 (可能很难找到)。

非常有用的外部资源

de:ARM