☰
谈谈64 位的 ARM 架构
2026/9/25 5:26:42 网站建设 项目流程

开篇:一场悄无声息的换代

2013 年,iPhone 5s 发布。

发布会上苹果说了一句当时没什么人在意的话:

"A7 是世界第一款 64 位手机芯片。"

当时的普遍反应是:

"手机内存才 1GB,要 64 位干什么?" "32 位能寻址 4GB,还远没用满啊。"

十年后回头看:

★ 苹果全线产品从 x86 换成了自研 ARM ★ 安卓强制要求 64 位应用 ★ 亚马逊、阿里、华为都在做 ARM 服务器芯片 ★ Windows 出了 ARM 版

而那个"64 位",从来就不只是"能用更多内存"。

这篇文章讲清楚:ARM64 到底改了什么,为什么这些改动如此重要。


第一部分:先理清名字

一、一堆容易混的术语

这是入门第一个坑:同一件事有好几个名字。

名字指什么
ARMv8-A⭐架构版本(2011年发布的规范)
AArch64⭐64 位执行状态(ARM 官方叫法)
AArch3232 位执行状态(兼容老代码)
arm64⭐ 苹果/LLVM 的叫法
aarch64⭐ Linux/GCC 的叫法
ARM64微软的叫法

💡arm64=aarch64=ARM64,★ 都是同一个东西。

只是不同厂商起的名字不同。


二、⭐ 关键概念:执行状态

ARMv8 处理器可以在两种"状态"下跑:

┌────────────────────────────────────────┐ │ ARMv8 处理器 │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ │ │ AArch64 │ │ AArch32 │ │ │ │ ★ 64位状态 │ │ 兼容32位 │ │ │ │ │ │ │ │ │ │ A64 指令集 │ │ A32 / T32 │ │ │ └──────────────┘ └──────────────┘ │ │ ↑ ↑ │ │ └── ★ 可以切换 ────┘ │ └────────────────────────────────────────┘

⭐ 最重要的一点:A64 是全新指令集

❌ 常见误解: "ARM64 就是 ARM32 加了个 64 位模式" ✅ 实际情况: ★ A64 是【重新设计】的指令集 ★ 和 A32 的编码格式完全不同 ★ 不是扩展,是另起一套

🔥对比一下 x86 的做法:

x86-64(AMD64):★ 在 x86 上打补丁扩展 → 保留了所有历史包袱 ARM64(A64): ★ 借 64 位的机会推倒重来 → 扔掉了 ARM32 的历史包袱

这是 ARM64 能做得这么干净的根本原因。


第二部分:最直观的变化

三、寄存器:从 16 个到 31 个

ARM32 的寄存器

R0 ~ R15 ★ 只有 16 个,而且: R13 = SP(栈指针) R14 = LR(返回地址) R15 = PC(程序计数器) ↓ ★ 真正能自由用的只有 13 个

ARM64 的寄存器

X0 ~ X30 ★ 31 个 64 位通用寄存器 (W0 ~ W30 是它们的低 32 位) SP ★ 独立的栈指针(不再占用通用寄存器) PC ★ 独立的程序计数器(★ 不能直接读写了) XZR / WZR ★ 零寄存器(读出来永远是 0)

⭐ 为什么寄存器多这么重要

寄存器少 → 变量放不下 → ★ 频繁存取内存 ↓ 访问内存比访问寄存器慢几十倍 ↓ ★ 性能损失

举个例子:

// 一个函数用了 20 个局部变量【ARM32】只有13个可用寄存器 ↓ ★ 有7个变量必须放栈上(内存) ★ 每次用都要 load/store 【ARM64】有31个 ↓ ★20个变量全部放寄存器 ★ 整个函数几乎不碰内存

⭐这是 ARM64 性能提升的一个主要来源。

★ 经验数据:仅寄存器数量翻倍这一项,就能带来 5~15% 的性能提升。


寄存器命名规则

X0 ★ 64 位访问 W0 ★ 32 位访问(就是 X0 的低 32 位) ★ 写 W0 时,X0 的高 32 位会被自动清零
mov x0, #0xFFFFFFFFFFFFFFFF // x0 = 全 1 mov w0, #1 // ★ x0 变成 0x0000000000000001 // 高 32 位被清零,不是保留

💡这个设计避免了"部分寄存器写入"带来的依赖问题。
★ x86 上mov al, 1会保留 eax 的高位,造成隐藏依赖,是性能陷阱。


四、⭐ 指令集的简化

ARM32 的两个"特色"(其实是包袱)

包袱① 条件执行

ARM32 里几乎每条指令都能带条件:

; ARM32 CMP r0, #0 ADDEQ r1, r1, #1 ; ★ 相等才执行 SUBNE r1, r1, #1 ; ★ 不相等才执行 MOVGT r2, #5 ; ★ 大于才执行

看起来很酷——不用跳转就能做分支。

但代价是:

🔴 每条指令都要花 4 位编码条件 → ★ 浪费了大量指令编码空间 🔴 乱序执行的处理器很难优化 → ★ 每条指令都可能"不执行",依赖关系复杂 🔴 现代分支预测已经很准 → ★ 条件执行的优势不再明显

ARM64 的处理:

★ 砍掉了绝大部分条件执行 ★ 只保留少数几条:CSEL、CSINC、CSET 等 csel x0, x1, x2, eq ; ★ eq 成立取 x1,否则取 x2

包袱② PC 可以直接操作

ARM32 里 PC 就是 R15,可以当普通寄存器用:

; ARM32 MOV pc, r0 ; ★ 直接跳转到 r0 的地址 ADD pc, pc, r1 ; ★ 甚至能算出跳转目标 LDR r0, [pc, #8] ; ★ 相对 PC 取数据

问题:

🔴 任何写 R15 的指令都可能是跳转 → ★ 处理器无法提前判断控制流 → ★ 流水线和分支预测极难做

ARM64 的处理:

★ PC 不再是通用寄存器 ★ 只能通过专门的跳转指令改变 ★ 读 PC 也要用专门指令(adr / adrp)

⭐这两项简化的共同目标:

★ 让处理器更容易分析指令、更容易做乱序执行和流水线优化。

代价是指令数变多了一点,但换来了大得多的性能空间。


其他简化

ARM32ARM64
⭐LDM/STM(一条指令存取多个寄存器)⭐ 只保留LDP/STP(存取一对)
Thumb / Thumb-2 混合(16位+32位指令)⭐固定 32 位指令长度
移位可以内嵌在很多指令里保留但简化

💡为什么砍掉 LDM/STM?

一条LDM r0, {r1-r12}要访问 12 次内存。
★ 这种"微码级"的复杂指令,在乱序处理器上很难高效实现。


第三部分:地址空间

五、64 位地址空间有多大

32 位: 2³² = 4 GB 64 位: 2⁶⁴ = ★ 16 EB(约 170 亿 GB)

但实际上没人用满 64 位:

★ 目前主流实现只用 48 位 → 256 TB ★ 部分支持 52 位 / 57 位

为什么不用满?

① 用不完——256TB 对当前完全够用 ② ★ 页表层级越多,地址翻译越慢 ③ ★ 高位空出来,可以拿来放别的信息(见下文)

六、⭐ 高位空着的妙用

妙用① 用户/内核空间分离

0x0000_0000_0000_0000 ┐ ├─ ★ 用户空间(低位全 0) 0x0000_FFFF_FFFF_FFFF ┘ ... 巨大的空洞 ... 0xFFFF_0000_0000_0000 ┐ ├─ ★ 内核空间(高位全 1) 0xFFFF_FFFF_FFFF_FFFF ┘
★ 看地址最高位就知道是用户还是内核 ★ 硬件用两个独立的页表基址寄存器(TTBR0/TTBR1) ★ 用户态和内核态切换不用换页表

妙用② ⭐ 指针标记(Top Byte Ignore)

ARM64 有个特性叫 TBI:★ 地址的最高 8 位在访存时被硬件忽略。

0x AB 00_7F_12_34_56_78 ~~ ★ 这 8 位随便填,硬件访存时当它不存在

用途:

✅ 在指针里塞额外信息(类型标签、引用计数) ✅ ★ iOS 的 Objective-C "tagged pointer" ✅ ★ 内存调试工具(HWASan)用它做标记 ✅ JIT 编译器的对象头优化

💡这是 64 位带来的一个隐藏好处:

★ 地址位太多了,多出来的位可以当"免费的元数据空间"用。
32 位时代 4GB 地址全都要用上,根本没有余量做这种事。


妙用③ ⭐ PAC(指针认证)

ARMv8.3 引入的安全特性:

函数返回地址存进栈之前: ↓ ★ 用密钥算一个签名,塞进地址的高位 ↓ 0x AB CD _ 00007F1234 ~~~~~ ★ 签名 ↓ 返回时校验签名 ↓ ★ 如果攻击者改了返回地址,签名对不上 → 崩溃

🎯这直接打击了 ROP(返回导向编程)攻击。

★ 苹果 A12 之后的芯片全部启用了 PAC。


第四部分:其他重要改进

七、异常级别(Exception Level)

ARM32 的特权模式

ARM32 有 7 种处理器模式,混乱且不成体系 (User、FIQ、IRQ、Supervisor、Abort、Undefined、System)

⭐ ARM64 重新设计成四层

┌──────────────────────────────────────┐ │ EL0 ★ 用户程序 │ 最低权限 ├──────────────────────────────────────┤ │ EL1 ★ 操作系统内核 │ ├──────────────────────────────────────┤ │ EL2 ★ 虚拟机监控器(Hypervisor) │ ├──────────────────────────────────────┤ │ EL3 ★ 安全监控器(TrustZone) │ 最高权限 └──────────────────────────────────────┘

每一层都有独立的:

★ 栈指针(SP_EL0 ~ SP_EL3) ★ 异常返回地址(ELR_ELx) ★ 系统控制寄存器(SCTLR_ELx)

为什么这个设计重要

① ★ EL2 专门给虚拟化 → 手机上跑虚拟机、云服务器都需要 → ARM32 时代虚拟化要靠软件技巧,性能差 ② ★ EL3 专门给安全世界 → 指纹、支付、DRM 都跑在这里 → 即使内核被攻破也访问不到 ③ ★ 层级清晰,权限边界明确

💡这是 ARM 能进服务器市场的关键之一。
★ 没有硬件虚拟化支持,云计算根本没法做。


八、⭐ 内存模型:弱序

什么是内存序

多核 CPU 上,一个核写的内容,另一个核什么时候能看到?

【x86:强序(TSO)】 ★ 基本保证"先写的先被看到" ★ 硬件帮你维护顺序 ★ 代价:核间通信开销大,难扩展到很多核 【ARM64:弱序】 ★ 不保证顺序! ★ 需要程序员显式加内存屏障 ★ 好处:硬件简单、功耗低、易扩展

一个会出问题的例子

// 线程 A // 线程 Bdata=42;if(flag==1)flag=1;print(data);
★ 在 x86 上:基本不会有问题 ★ 在 ARM64 上:★ 线程 B 可能看到 flag=1,但 data 还是旧值!

原因:

ARM64 允许处理器/编译器重排这两条写操作 ↓ ★ flag = 1 可能先写出去 ↓ 另一个核看到 flag=1 时,data 还没写完

正确写法

// ✅ 用内存屏障data=42;__atomic_store_n(&flag,1,__ATOMIC_RELEASE);// ★ release 语义// 线程 Bif(__atomic_load_n(&flag,__ATOMIC_ACQUIRE))// ★ acquire 语义print(data);// ★ 保证能看到 data=42

ARM64 的屏障指令

指令作用
DMB数据内存屏障(保证访存顺序)
DSB数据同步屏障(更强,等待完成)
ISB指令同步屏障(刷新流水线)
⭐LDAR / STLR带 acquire/release 语义的访存(更高效)

🔥实战影响:

★ 从 x86 移植到 ARM64,最容易出的 bug 就是内存序问题。
★ 在 x86 上"碰巧能跑"的无锁代码,到 ARM64 上会随机出错。

而且这种 bug 极难复现——可能一百万次才出一次。


九、SIMD:NEON

什么是 SIMD

SIMD = Single Instruction, Multiple Data ★ 一条指令处理多个数据
【普通做法】 for (i = 0; i < 4; i++) c[i] = a[i] + b[i]; ★ 4 次加法 【SIMD】 ★ 一条指令同时算 4 个加法

ARM64 的 NEON

★ 32 个 128 位向量寄存器(V0 ~ V31) 比 ARM32 的 NEON 多一倍 一个 128 位寄存器可以看作: 16 × 8位 整数 8 × 16位 整数 4 × 32位 整数或浮点 2 × 64位 整数或浮点

例子:

; ★ 一条指令加 4 个 32 位浮点 fadd v0.4s, v1.4s, v2.4s

ARM64 对浮点的重要改进

【ARM32】 ★ 浮点是可选的(VFP 扩展) ★ 有些芯片没有,要用软件模拟 【ARM64】 ★ 浮点和 NEON 是【必选】的 ★ 所有 ARM64 处理器都有

💡这意味着编译器可以放心生成浮点和 SIMD 代码,不用考虑兼容性。


更新的 SVE

ARMv8.2 引入 SVE(Scalable Vector Extension):

★ 向量长度可变(128 ~ 2048 位) ★ 同一份代码,在不同硬件上自动用不同向量宽度
对比 x86 的做法: SSE(128) → AVX(256) → AVX-512(512) ★ 每次扩宽都要重写代码 SVE: ★ 写一次,硬件宽度变了自动适配

主要用在服务器和 HPC,手机上还不常见。


十、原子操作

ARM32/早期 ARM64 的做法

★ LL/SC(Load-Linked / Store-Conditional) ldxr w0, [x1] ; 独占加载 add w0, w0, #1 stxr w2, w0, [x1] ; 条件存储 cbnz w2, retry ; ★ 失败就重试

问题:

🔴 竞争激烈时不断重试 🔴 ★ 核数越多,冲突越多,性能越差

⭐ ARMv8.1 的 LSE

加入了真正的原子指令:

ldadd w0, w1, [x2] ; ★ 原子加,一条指令搞定 swp w0, w1, [x2] ; ★ 原子交换 cas w0, w1, [x2] ; ★ 比较并交换

🎯这对多核服务器至关重要。

★ 在 64 核、128 核的 ARM 服务器上,LSE 能带来数倍的锁性能提升。


第五部分:兼容性

十一、⭐ ARM64 怎么跑 32 位程序

AArch64 状态 ←──── ★ 只能在异常返回时切换 ────→ AArch32 状态

规则:

✅ 64 位内核可以运行 32 位应用 ❌ ★ 32 位内核不能运行 64 位应用 ❌ ★ 同一个进程内不能混用 32/64 位代码 ✅ 不同进程可以一个 32 位一个 64 位

十二、⭐ 32 位兼容正在消失

这是近年一个重要趋势:

事件时间
苹果 iOS 11 彻底停止支持 32 位 App2017
Google Play 要求必须提供 64 位版本2019
⭐ARM Cortex-A710 之后的大核不再支持 AArch322021
⭐部分新芯片完全移除 32 位支持2023+

为什么要移除?

① ★ 省晶体管、省功耗 → 一整套 32 位译码逻辑可以删掉 ② ★ 简化验证 → 少一半的状态组合要测试 ③ 生态已经完成迁移

🔥实际影响:

★ 如果你的应用还有 32 位的 so 库,在新手机上可能根本跑不起来。
必须提供 arm64-v8a 版本。


第六部分:为什么 ARM64 赢了

十三、⭐ 三个层面的原因

① 架构层:干净

x86-64:★ 在 40 年的历史包袱上打补丁 - 变长指令(1~15 字节) - 复杂的寻址模式 - 段寄存器、实模式等远古遗留 ↓ ★ 译码器极其复杂,占大量晶体管和功耗 ARM64: ★ 借 64 位机会推倒重来 - 固定 32 位指令长度 - 简单的寻址模式 - 没有历史包袱 ↓ ★ 译码简单 → 可以做很宽的解码(8 发射以上)

💡一个关键数据:

★ Apple M 系列能做到 8 宽解码,x86 目前普遍在 4~6 宽。
★ 原因就是变长指令让 x86 很难并行解码。


② 商业层:授权模式

【Intel/AMD】 ★ 卖芯片 → 你只能买现成的 【ARM】 ★ 卖授权 → 苹果、高通、华为、亚马逊都能自己设计核心 → ★ 针对自己的场景深度优化

结果:

★ 苹果为 macOS/iOS 定制 → 能效比极高 ★ 亚马逊为云服务定制 Graviton → 性价比高 ★ 高通为手机定制 → 兼顾性能和功耗

③ 场景层:从功耗敏感场景起家

ARM 起点:★ 嵌入式、手机(功耗是生死线) ↓ ★ 从一开始就把"每瓦性能"当第一目标 ↓ 十几年打磨 ↓ ★ 等数据中心开始关心电费时,ARM 已经准备好了

对比:

x86 起点:★ 桌面、服务器(性能优先,功耗其次) ↓ ★ 往低功耗方向走(Atom)一直不成功

🎯"从低功耗往高性能走"比"从高性能往低功耗走"容易。

这是 ARM 能上桌面和服务器,而 x86 进不了手机的根本原因。


第七部分:对开发者的实际影响

十四、⭐ 六个要注意的地方

① 内存序(最重要)

// ❌ 在 x86 能跑,在 ARM64 会随机出错volatileintflag=0;intdata;// 线程 Adata=42;flag=1;// 🔴 可能被重排到 data 之前// ✅ 用标准原子操作std::atomic<int>flag{0};data=42;flag.store(1,std::memory_order_release);

🔴规则:★ 多线程共享数据,必须用std::atomic或加锁。
不要依赖volatile——它不提供内存序保证。


② 数据对齐

ARM64 大部分访存支持非对齐 ↓ ★ 但性能可能变差 ★ 某些指令(如 LDP/STP、原子操作)★ 仍然要求对齐
// ✅ 显式对齐structalignas(16)Data{...};

③ 指针大小

// ❌ 假设指针是 4 字节inthandle=(int)ptr;// 🔴 64 位下截断// ✅intptr_thandle=(intptr_t)ptr;// 或用 uintptr_t / size_t

④ 类型大小变化

类型32位 ARM⭐ 64位 ARM(LP64)
int44(没变)
long4⭐8
pointer4⭐8
size_t4⭐8

🔥long从 4 变 8 是移植时最常见的坑。

★ Windows 上long仍然是 4(LLP64 模型),Linux/macOS 是 8(LP64)。
★ 想要固定宽度就用int32_t/int64_t。


⑤ 内联汇编 / SIMD 代码

★ x86 的 SSE/AVX 代码不能直接用 ★ 需要改写成 NEON // x86 __m128 a = _mm_add_ps(b, c); // ARM NEON float32x4_t a = vaddq_f32(b, c);

跨平台方案:

✅ 用 SIMD 抽象库(如 xsimd、Highway) ✅ 或者写标准 C,让编译器自动向量化

⑥ 移动端打包

必须提供: ★ arm64-v8a (主流,必须) ⚠️ armeabi-v7a (老设备,逐渐可省) ❌ x86 / x86_64 (只有模拟器需要)
// Android ndk { abiFilters 'arm64-v8a' // ★ 新项目只出 64 位 }

速查表

ARM64 vs ARM32 核心差异

项目ARM32⭐ ARM64
指令集A32 / T32⭐A64(全新设计)
通用寄存器16 个⭐31 个
指令长度32位 / 16位混合⭐固定 32 位
条件执行几乎所有指令⭐基本取消
PC 可直接写✅⭐❌
地址空间4 GB⭐48 位起(256TB)
特权模式7 种混乱模式⭐EL0~EL3 四层
浮点/SIMD可选⭐必选
向量寄存器16×128位⭐32×128位
硬件虚拟化弱⭐EL2 专门支持

版本特性速查

版本关键特性
ARMv8.0⭐ AArch64 / A64 指令集
ARMv8.1⭐LSE 原子指令
ARMv8.2⭐ SVE、半精度浮点
ARMv8.3⭐PAC 指针认证
ARMv8.4内存分区、增强虚拟化
ARMv8.5⭐ MTE 内存标记(抓内存 bug)
ARMv9⭐ SVE2、CCA 机密计算

移植 Checklist

  • ⭐多线程代码用std::atomic,不要靠volatile
  • ⭐ 不要假设指针是 4 字节
  • ⭐ 注意long在不同平台的大小
  • 用int32_t/int64_t代替int/long
  • 检查结构体对齐和 padding
  • ⭐ SIMD 代码要改写(SSE → NEON)
  • 检查内联汇编
  • ⭐ 打包时必须包含 arm64-v8a
  • ⭐ 在真机上跑多线程压力测试(内存序问题只在真机暴露)

收尾

回到 2013 年那个问题:“手机内存才 1GB,要 64 位干什么?”

现在答案清楚了——64 位从来不只是为了内存:

★ 寄存器从 16 个变 31 个 → 性能 ★ 扔掉条件执行和可写 PC → 流水线和乱序更好做 ★ EL0~EL3 四层特权 → 虚拟化和安全 ★ 浮点/SIMD 变成必选 → 编译器敢优化 ★ 地址高位空出来 → 指针认证、内存标记 ★ 固定 32 位指令 → 可以做 8 宽解码

苹果当年真正做的,不是"支持更多内存",
而是★ 借 64 位这个契机,把整个指令集重新设计了一遍**。**


⭐三句话总结:

一、ARM64 不是 ARM32 的扩展,是重新设计的新指令集。
★ 这是它能保持干净、进而在能效比上超越 x86 的根本原因。

二、"64 位"最大的收益不是地址空间,是寄存器数量和架构简化。
★ 地址空间只是顺带的,而且还没用满一半。

三、对开发者而言,最危险的差异是弱内存序。
★ x86 上"碰巧能跑"的多线程代码,到 ARM64 上会随机崩溃。

最后一句:

x86 用了 40 年往上加补丁,ARM 用一次机会重新出发。

★ 有时候最大的优势不是你做了什么,而是你没有背什么。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询