ARM架构薄记2——ARM学习架构抓手(以ARMv7为例子)
ARM架构薄记2——ARM学习架构抓手(以ARMv7为例子)
架构学习需要学习哪一些部分呢?笔者接触过的架构有Intel-X86, AMD64,RISC-V和Arm架构(V7最多),笔者简单的翻了一些课本和教材,博客,官方文档。这里针对ARM架构,简单的分享一下ARMv7架构的一些学习要点。
本篇文章的一些要点
- 首先,我们要对ARMv7本身有一个大致的了解,比如说,ARM本身就是一个派生于RISC设计指令集的一个重要的架构,因此,理解RISC的设计理念和为什么诞生(对比的说,自然也就是Typical CISC了!)是非常重要的。
- 理解一些基本的模块,笔者翻阅的大部分架构,一定重叠的话题有:
- 数据处理模块:也就是跟寄存器和指令集打交道了:我们至少需要知道——ARM架构中的一些寄存器在做什么,执行对应的指令的时候,大致发生了啥(啊,别太细节,钻进细节容易出不来,掌握一个大致的流程即可)
- 数据传输模块:图灵机总是要有IO的,这个我相信大家不会有疑问。所以,我们的一个大Topic就是处理器(寄存器组)如何跟外界进行交互。处理器如何对外界进行寻址(这就牵扯到了立即寻址、寄存器寻址和基址加偏移等寻址方式了,我们学习的时候,就是要明白出现这些寻址的目的:把数据从内存中找到,或者找到数据要往哪里写!)
- 分支与控制模块:CPU自然要进行判断,做分支处理,换而言之,当我们写下if else while for的时候,流水线发生了什么?
- 系统控制:这里就是谈论的中断和其他更加细节的子系统上去了。这里设计到的就是谈论整个架构的状态的设计。对于控制后的结果,这就是系统的各个状态的含义,和他们拥有什么东西,在理解他们目的的基础上。比如说,中断和异常处理,然后再进一步学习安全啊等等的扩展,这样脉络会清晰。
- 基于上面的陈述,我们就要理解:
- 寄存器体系:每一个寄存器在干啥
- 状态寄存器(CPSR和SPSR)的作用,它们决定了程序的执行状态及条件码。
- 不同特权模式下寄存器Bank的切换,比如User、FIQ、IRQ、Supervisor、Abort、Undefined以及System模式。
- 每一个状态的含义,和在何种条件下切换,切换的时候发生了什么。有没有设计到指令集的形态问题,比如说在ARM下,是ARM和Thumb指令集:ARM指令通常为32位,而Thumb指令集为16位,适合提高代码密度。了
- 分支跳转的优化处理与流水线的议题
- 内存访问和寻址的议题
- 最高级的话题在理解了上面领域之后了:调试和性能优化,我们熟悉硬件调试支持(如调试寄存器、断点设置等)和性能监控工具。研究流水线设计、乱序执行(部分实现中可能采用)和缓存优化策略,以提升实际系统性能。
所以第一个问题:CISC和RISC的区别
我们马上就要回答第一个问题,也就是对架构概述的下,对指令集的归属。整个东西再理解了CISC和RISC后就不用每一次回顾了,大致想一下特点,可以帮助自己快速的入门。
毫无疑问的,在计算机诞生的20世纪50年代,如何高效的操作计算机是我们畅聊的话题(我没打错字,就是畅聊),这里就引导出来了ISA:指令集。指令集架构(Instruction Set Architecture, ISA)扮演着至关重要的角色,我们都知道,最后的最后,指令集规范了我们如何操作处理器到了电平时序层次。这就跟电路设计挂上钩了,对电路设计更感兴趣的朋友就需要参考模拟集成电路和数字集成电路设计了,这里不是我们的重点!
早期,我们的思路是——使用尽可能包办的指令集,让一条指令就把工作包办了!所以,设计出来的指令和对应的电路,自然就伴随着发展走向了复杂。这就是Complex Instruction Set Computer,大名鼎鼎的CISC。这可以理解,那时计算机硬件资源十分昂贵,设计者希望通过复杂的指令集来减少程序代码的长度,从而降低内存成本。CISC处理器采用大量功能强大的指令,单条指令就能完成较为复杂的操作,例如内存到内存的数据传送、复杂的算术逻辑操作等。毫无疑问,CISC在历史上留下了非常浓厚的一笔。
CISC的发展到了后期,伴随着80年代后半导体技术开始飞跃层级的发展,硬件制造的成本快速的,大幅度的降低,整个时候,我们发现,实际上20%的指令就可以把余下80%的指令组合出来,然后剩下的80%的指令却仍然占领我们的解释存储部分,这不利于我们芯片的进一步缩小和集成,而且,非常不利于指令的流水线化的执行。这到今天都成为了一个潮流。
所以,RISC(精简的指令集体系)应运而生:RISC架构提倡“指令越简单,执行越快”的设计思想,主张将每条指令设计得尽量简单,并尽可能在硬件层面实现高速流水线和并行处理。RISC理念的代表体系结构有ARM、MIPS和SPARC等。这些架构不仅在嵌入式系统中得到广泛应用,而且在高性能计算领域也展现出强劲竞争力。
差别1:设计理念
讨论差别,我们需要一个一个来。回顾上面的历史发展,很容易得到CISC和RISC在根源上的设计差异:
CISC设计理念
CISC指令集通常包含大量复杂且功能强大的指令。这些指令通常能够在一条指令中实现多个低级操作,如内存访问、算术运算、逻辑操作等。其设计初衷在于通过减少指令数来缩短程序代码长度,降低内存带宽需求。缺点是指令解码及执行过程复杂,硬件设计难度较大,流水线效率可能不高。
RISC设计理念
RISC架构则主张将指令设计得尽可能简单、统一,并且所有指令在固定长度内完成。大部分操作只需一条指令完成一个简单任务,借助流水线技术实现高速连续处理。这种设计使得处理器的硬件实现相对简单,容易实现深度流水线和高频率运行,从而在每个时钟周期内完成更多指令
差别2:实现难度
CISC处理器
由于CISC架构指令多样且复杂,硬件需要专门设计解码器来解析各类复杂指令,还要在指令内部实现多种功能单元的协同工作。这不仅增加了硬件成本,也使得芯片在实现深度流水线时面临更多挑战。此外,CISC处理器常常依赖微代码(microcode)来解释指令,这在一定程度上影响了执行速度。
RISC处理器
RISC架构的优势在于硬件实现相对简单。由于指令格式固定、操作简单,指令译码和执行流程可以大大简化。流水线设计能够更加直接和高效,每个流水段的逻辑相对固定,降低了设计和验证的复杂度。虽然简单指令可能需要更多条指令来完成同样的任务,但通过高频率和并行处理,整体性能可以得到显著提升。
差别3:指令集本身
实际上我本来想归类到第一点,但是第一点属于大纲的范畴,实在是不太好放(这样所有的说明直接扔进第一点好了)。所以这里的差别,谈论的是指令集本身
CISC处理器
CISC处理器支持大量种类的指令,指令长度和格式不统一,这使得同一指令可以执行复杂的操作。例如,一个CISC指令可能直接实现内存到内存的数据传输,或者在单条指令中完成加法、移位以及条件判断操作。这种灵活性使得编写汇编代码时,程序员可以依赖单条指令完成更多任务,从而使得代码行数减少。所以可以看到,当编译器决定按照空间优先的时候,他可能会考虑使用更加复杂的指令来做平替
RISC处理器
RISC指令集通常采用固定长度的指令格式,指令种类较少且操作非常简单。大部分指令只能处理寄存器之间的操作,数据传输通常被限定为加载(Load)和存储(Store)指令。虽然这样会使得同样的任务需要更多指令组合,但由于每条指令执行速度快,整体运行效率依然能够满足高性能计算需求。换而言之,流水线起来会很方便,挂起非常少!
差别4:编译器的看法
CISC与编译器
- 由于CISC指令较为复杂,编译器在生成机器码时往往依赖处理器内部的微代码去完成细节操作。编译器的优化难度相对较低,因为程序员或编译器可以依赖单条复杂指令完成多项任务,但同时也限制了指令级并行(Instruction-Level Parallelism, ILP)的发挥。
- 我们需要注意的是——复杂指令常常掩盖了底层硬件的并行执行能力。现代CISC处理器通过超标量、乱序执行等技术实现指令级并行,但由于指令间存在较强的依赖关系,使得并行度难以充分发挥。
- CISC由于依赖硬件内部的微程序控制,编译器优化相对简单,但在充分利用现代处理器内部并行度方面,往往难以与RISC系统相比。
RISC与编译器
- RISC的简单指令特性要求编译器在代码生成时承担更多优化责任。编译器需要将高级语言表达的复杂操作拆解为多个简单指令,同时还要合理安排指令顺序,以充分利用流水线和寄存器重命名技术。尽管这对编译器技术提出了更高要求,但随着编译器技术的发展,这一问题已经得到有效解决,同时也推动了指令级并行技术的进步。
- RISC体系结构要求编译器在生成代码时对指令组合、寄存器分配以及流水线冲突进行精细调度。现代编译器通过静态分析和动态预测技术,可以有效缓解指令级并行带来的挑战,提高整体系统性能。
- RISC架构凭借固定格式和简单操作,编译器可以更好地进行指令调度和流水线优化,充分利用处理器的并行计算资源。
差别5:性能问题和功耗权衡
CISC处理器
CISC处理器在某些应用场景下,通过复杂指令的实现可以减少内存访问次数,从而缩短程序代码的总体长度。但是,复杂指令的解码和执行往往需要较长的时钟周期,流水线效率不如RISC高,多功能指令在并行处理方面可能存在瓶颈,限制了超标量技术的发展。
内部电路结构较为庞大,功耗相对较高。在一些高性能应用中,复杂的指令译码和微代码控制逻辑会导致额外的能量消耗,这对于移动设备或需要低功耗设计的嵌入式系统来说是一个不小的挑战。
RISC处理器
RISC处理器的流水线设计和固定指令格式使得指令译码和执行速度非常快。在相同主频下,虽然单条指令完成的任务较为简单,但借助于高频率和深度流水线,整体性能可以显著提高。尤其在嵌入式设备和移动设备中,RISC架构由于其低功耗和高效率的特性,成为主流选择。
RISC架构因其硬件实现简洁,指令执行路径短,功耗通常较低。尤其是在当今对能源效率要求日益严格的移动互联网和嵌入式领域,RISC处理器(如ARM架构)已经成为主流选择。较低的功耗不仅延长了电池续航时间,同时也降低了系统的散热要求。
ARMv7架构的说明
概述
关于ARMv7架构的说明,实际上上一篇博客我说的差不多了,但是这里,我们对ARMv7还是重新的偏重于技术实现的层次上,再说一次ARMv7架构。这里,我将结合自己整理的要点抓手,带着过一次ARMv7架构。
ARM架构本身属于RISC架构,其ARMv7作为ARM架构的子类自然满足RISC架构的基本理念:简单、高效与低功耗。
寄存器
ARMv7架构的有16个通用的寄存器,分别按照数字进行命名:R0到R12被用作数据处理的通用寄存器;而R13、R14和R15则分别承担着堆栈指针、链接寄存器和程序计数器的功能。特别是R15,它不仅指示下一条执行指令的地址,还与流水线调度密切相关,为系统的高效运行提供了坚实的基础。
还有两个设计到了我们的状态寄存器与模式支持,也就是著名的CPSR(当前程序状态寄存器)和SPSR(保存的程序状态寄存器),他们记录和管理系统状态。CPSR保存了当前的条件码、控制位以及运行模式,而SPSR则用于在发生异常或中断时保存现场状态,便于异常处理结束后恢复正常执行。此外,不同的特权模式(如User、FIQ、IRQ、Supervisor等)通过寄存器银行的切换,实现了在中断或异常情况下的快速响应与安全隔离。
处理器状态
处理器的状态是我们的第二个话题。当然,这里的状态首先需要说明的是——我们习惯上先分析指令集有没有发生变动,这里,ARMv7是有ARM指令集状态和特别强调密度的Thumb状态,他们决定了处理器如何解释和执行存储器中的指令。所以,必须要优先考虑。
- ARM状态
- 指令格式:在ARM状态下,处理器采用32位定长指令。
- 特点:由于指令格式固定,译码过程简单且统一,适合于高性能流水线设计;丰富的指令集支持多种算术、逻辑、数据传输以及控制流操作。
- 执行优势:在需要高运算密集型处理的场景下,ARM状态能提供较高的执行效率。
- Thumb状态
- 指令格式:Thumb状态下采用16位定长指令,部分扩展的Thumb-2模式下还支持混合32位指令。
- 特点:相对于ARM状态,Thumb指令更为紧凑,能显著提高代码密度,减少存储空间的占用。
- 执行优势:在内存受限或者对功耗要求较低的嵌入式应用中,Thumb状态能够在保证足够性能的同时优化存储与功耗。
- Jazelle状态(ARMv7后)
- 硬件加速:通过专用硬件模块,Jazelle能够直接解释和执行Java字节码,从而提高Java应用的执行效率。
- 状态切换:与ARM和Thumb状态类似,处理器可以通过控制寄存器设置切换到Jazelle模式,不过这种模式的使用较为有限,主要面向嵌入式Java应用场景。
下一步,我们就可以说:我们的处理器在何种状态了。ARM处理器的状态比X86的复杂一些。请看:
- 用户模式(User Mode)
- 特点:为应用程序运行的常规模式,执行状态下对系统资源的访问权限有限,确保系统安全。
- 使用场景:大部分用户级应用均在此模式下运行。
- 特权模式
ARMv7定义了多个特权模式,用于响应异常和处理中断,常见的包括:- FIQ模式(Fast Interrupt Request Mode):用于处理高速中断,具有较高的优先级,并拥有专用寄存器组,保证中断处理时的执行效率。
- IRQ模式(Interrupt Request Mode):处理一般中断请求,允许在中断发生时快速切换到特权状态执行中断服务程序。
- Supervisor模式(SVC Mode):通常由操作系统内核使用,用于执行系统调用及管理任务。
- Abort模式、Undefined模式和**系统模式(System Mode)**也各自承担着处理异常、未定义指令及提供额外特权等职责。
对中断/异常的处理和恢复
这其实算处理器状态定义的一种,ARMv7处理器的执行状态还包括处理异常和中断的特殊状态,这些状态涉及到处理器如何响应外部和内部事件,并在响应后恢复正常执行。ARMv7设计了固定或可配置的异常向量表,每种异常都有独立的入口地址。当异常或中断发生时,处理器自动切换至相应的特权模式,并利用SPSR保存原有状态,确保处理完异常后能够顺利恢复正常运行。该设计不仅提高了系统响应速度,也为操作系统提供了高效而安全的异常管理机制。
- 异常向量与入口
- 异常向量表:在固定或可配置的内存区域中,存储着各种异常(如中断、未定义指令、数据异常等)的入口地址。
- 自动切换:当异常或中断发生时,处理器会自动切换至相应的特权模式,并跳转到对应异常向量进行处理。
- 状态保存与恢复
- SPSR的作用:在异常发生前,当前执行状态(包括模式和条件码)通过SPSR保存;异常处理结束后,利用SPSR中的信息恢复到异常前的执行状态。
- 现场保护:这一机制确保在处理外部事件或错误时,系统能保持数据和状态的一致性,防止因状态丢失而引发系统错误。
内存访问和寻址的方式
回到我们的第一大概述:ARMv7遵循严格的RISC设计思想,因此,ARM架构的内存访问就跟x86稍微有点不一样。
- Load/Store架构
- ARMv7采用纯Load/Store架构,所有算术逻辑运算仅限于寄存器之间进行,内存数据只能通过专门的加载和存储指令进行传输。这一设计简化了硬件实现,并有助于流水线的高效调度与优化。
- 数据大小与对齐要求
- 内存访问支持多种数据大小,如字节(8位)、半字(16位)和字(32位)。
- 为了保证高效访问,ARMv7要求数据通常按照其大小进行对齐(例如,字数据需要32位对齐),否则可能引发额外的访问延迟或异常处理。
- 分离指令与数据通道
- ARMv7通常采用哈佛结构,即指令缓存(I-cache)与数据缓存(D-cache)分离,这不仅提高了数据访问效率,还减少了互相干扰的可能性。
不过有意思的是,大部分寻址模式,熟悉X86的朋友不会陌生,因为找东西的思路是一样的!
-
立即数偏移(Immediate Offset Addressing)
- 工作原理:将基址寄存器中的值与一个固定的立即数(Immediate)相加或相减,得到访问地址。
- 应用示例:
LDR R0, [R1, #4]
:从地址 R1+4 处加载数据到 R0中。- 支持正负偏移,能够向上或向下调整基址。
- 优点:简单高效,适用于常量偏移的内存访问,便于编译器优化代码。
-
寄存器偏移(Register Offset Addressing)
-
工作原理:偏移量来自另一个寄存器的值,而非固定的立即数,允许在运行时动态计算偏移。
-
附加功能:通常支持对偏移寄存器内容进行位移(shift)操作,达到乘法因子的效果,方便访问结构化数据或数组。
应用示例:
LDR R0, [R1, R2, LSL #2]
:R2寄存器的值左移2位后与R1相加,用于访问数组元素(数组下标×元素大小)。
-
优点:提供动态灵活性,可根据程序运行时的实际数据计算访问地址,适合复杂数据结构的操作。
-
-
预索引与后索引寻址(Pre-indexed and Post-indexed Addressing)
- 预索引(Pre-indexed):
- 在访问内存之前,先对基址寄存器进行偏移计算,然后使用计算后的地址进行加载或存储操作。
- 可选择在完成内存操作后更新基址寄存器,如
LDR R0, [R1, #4]!
表示加载数据后将R1更新为 R1+4。
- 后索引(Post-indexed):
- 先使用当前基址寄存器的值进行内存访问,随后再将偏移值加到基址寄存器上,更新寄存器的内容。
- 例如,
LDR R0, [R1], #4
表示先从R1处加载数据,再将R1更新为 R1+4。
- 应用意义:这种机制在循环或连续内存访问时非常实用,减少了额外的指令数量,同时简化了指针更新的工作。
- 预索引(Pre-indexed):
-
加载/存储多个寄存器(Load/Store Multiple)
- 工作原理:一次性加载或存储一组连续寄存器的值。
- 寻址方式:通过基址寄存器和一个预定义的寄存器列表,计算连续内存区域的起始地址。
- 应用示例:
LDMFD R0!, {R1-R7}
:从内存中连续加载多个寄存器的值,并自动更新R0的值。
- 优点:极大地提高了数据搬移效率,常用于函数调用前保存现场或函数返回时恢复现场。
流水线,分支和跳转
高效的分支控制与异常处理机制是任何高性能处理器架构不可或缺的部分,ARMv7在这方面提供了多种优化设计,以保证在多任务或中断密集型应用场景下依然能够稳定运行。
ARMv7的大众式优化
下面的部分,属于其他架构多多少少也有的部分,所以,我们可以很熟悉的快速的掠过。
设计
这里,我们需要首先介绍一下流水线。在ARMv7处理器中,流水线被划分为多个连续的处理阶段,通常包括以下几个主要阶段(也算通用的处理器对流水线的定义啦):
- 取指(Fetch)阶段
- 从指令缓存中读取下一条或多条指令,保持流水线始终处于“满”状态。
- 为了提高取指效率,ARMv7通常集成有预取缓冲区,有效降低因内存延迟导致的空闲周期。
- 译码(Decode)阶段
- 对取回的指令进行译码,解析操作码、操作数及条件字段,为后续执行阶段做好准备。
- 固定长度的指令格式(例如ARM状态下为32位)使译码逻辑简化,提高了流水线的稳定性。
- 执行(Execute)阶段
- 进行算术、逻辑或地址计算,部分实现中还包含专用的移位和乘除运算单元。
- ARMv7在这一阶段通过集成高效的硬件运算单元,缩短执行延迟。
- 访存(Memory Access)阶段
- 对需要访问内存的指令(如加载、存储)进行数据交换。
- ARMv7采用独立的数据缓存,与指令缓存形成哈佛结构,从而大幅降低访存延迟。
- 写回(Write Back)阶段
- 将运算结果写回到寄存器,完成一条指令的整个生命周期。
ARMv7在传统流水线设计的基础上,通过多项技术手段优化了流水线效率和指令并行度。比如说:预取缓冲区(Prefetch Buffer)技术让ARMv7在取指阶段使用专门的预取缓冲区,将可能被执行的指令提前载入,从而有效减少由于内存延迟带来的流水线停顿。预取技术结合独立的指令缓存,使得流水线在遇到指令缓存未命中时依然能保持高效运转。,以及利用哈佛架构的优势,ARMv7的指令缓存与数据缓存分离,保证取指操作不会被数据访存延迟干扰。通过合理的缓存层次和替换策略,ARMv7进一步降低了取指延迟,保持流水线连续性。
遇到了分支预测与条件执行
ARMv7有分支预测单元(Branch Prediction Unit, BPU)。这个东西用于进行分支预测,它是流水线中防止分支跳转带来严重中断的关键技术。ARMv7集成了高效的分支预测机制,通过预测即将到来的分支方向和目标地址,尽可能在分支确定之前提前载入正确的指令路径,减少流水线刷新次数。
ARMv7大部分指令支持条件执行。这意味着在遇到简单的条件判断时,无需依赖传统分支跳转,而是通过在指令内直接判定条件是否成立来决定是否执行。这种设计大大降低了分支指令的数量,避免了由分支失误引起的流水线冲刷,保持了流水线的高效连续运行。
处理数据冒险与流水线调度
数据冒险和流水线调度是流水线中出现的常见问题: 在流水线操作中,后续指令可能依赖前一指令的结果。ARMv7通过数据前递技术(又称旁路传递)直接将执行结果从运算单元传递到需要它的下一条指令,从而避免因等待写回阶段而导致的流水线停顿。尽管ARMv7主要采用顺序执行,但内部依然配置了简单的冒险检测和调度机制,能够在检测到数据或控制冒险时,动态调整流水线执行顺序或插入必要的停顿周期(stall),以确保数据正确性和流水线稳定性。
ARMv7自己特殊化优化
下面的部分是ARMv7自己的特殊的优化。这是根据ARM架构自身的特点特化的结果
- 双指令集模式切换
ARMv7支持ARM和Thumb两种指令集,允许在高性能和高代码密度之间切换。流水线在处理不同指令集时,会根据指令格式自动调整取指和译码策略,保证两种模式下都能保持高效流水线运行。 - 早期分支决策(Early Branch Resolution)
为了减少分支指令对流水线的干扰,ARMv7在译码阶段就尝试解析分支条件,提前做出分支决策,从而在执行阶段能迅速确定下一条指令的地址,减少因分支判断不及时导致的流水线刷新损失。 - 流水线刷新与异常处理
当发生异常或中断时,流水线中的部分或全部指令可能需要被清空。ARMv7设计了高效的流水线刷新机制,在异常发生时能够迅速清除错误指令,同时保存当前状态以便后续恢复。 - 精细的状态保存与恢复机制
利用SPSR等状态寄存器,ARMv7在异常发生前记录流水线和执行状态,确保在异常处理完毕后能够无缝恢复执行,减少因状态丢失而带来的性能损失。
补充:流水线优化的实际意义
一些朋友可能不知道流水线优化带来的好处,这里Mark一下:
- 提高指令吞吐量
通过预取、分支预测和数据前递等技术,流水线能保持高度连续性,在每个时钟周期内完成更多指令的处理,从而显著提升整体性能。 - 降低能耗与热设计要求
高效的流水线设计减少了空闲和等待时间,使得处理器在较低频率下即可达到理想性能,同时降低了功耗和发热量,这对于移动设备和嵌入式系统至关重要。 - 增强实时响应能力
精细的异常处理和早期分支决策机制,使得ARMv7在面对外部中断或紧急事件时能够迅速响应,保证系统的实时性和稳定性。 - 提升编译器优化潜力
由于流水线中条件执行和分支预测技术的应用,编译器可以更灵活地安排指令序列,充分挖掘硬件并行度,从而进一步优化生成代码的性能。
Fin
我们这样就非常粗略的把一个ARM架构主干给说的差不多了,大致回顾一下,就是——
- 大概的设计范畴属于RISC还是CISC多一些
- 寄存器的作用和含义
- 处理器和外设(典型的是内存)的交互方式(寻址)
- 处理器的状态和分析,特别的,我们会关心:中断的处理和特权级变化的处理
- 流水线跳转和分支预测
Reference
笔者主要是整理上一篇博客的时候,顺藤摸瓜得到的整理,这里同上一篇博客的Reference基本一致:
ARM 高性能处理器早期发展史_arm架构发展史-CSDN博客
尝试梳理下ARM处理器的发展历史-CSDN博客
韦东山深度剖析ARM架构核心原理与实践 - OSCHINA - 中文开源技术交流社区
arm架构发展历程介绍 - ARM - 电子发烧友网
ARM 处理器版本介绍及其主要区别 – ARMFUN