• 回复
  • 收藏
  • 点赞
  • 分享
  • 发新帖

ARM的一些探讨

ARM经历了几十年的发展,其核心架构已经从最初商用的ARMv4发展到了如今的ARMv7,据说ARMv8正处于开发之中,各个版本的内核大同小异,掌握了ARM的思想,其实看着都差不多,就是可配置模块不太一样,或者在指令集,实现方式上做些改变,从软件工程师的角度来看,基本都是一样的,当然如果你是做验证级别的软件工程师,还是要掌握非常细节的部分。

 

在ARM的官方文档中,对RISC的特性描述如下(当然ARM也都实现了):

1.     较大的寄存器文件(换句话说就是有较多的通用寄存器)

2.     load/store架构,就是所有除load/store的数据操作都是在寄存器之间完成,不能够对内存中的数据直接进行。写过单片机程序的人应该有体会,51,96这些都是可以直接对内存中的数进行数学操作的。

3.     简单地寻址模式,所有的load/store地址都是通过寄存器内容和指令算出来的。这点也是区别于CISC的复杂寻址模式,总之就是一条,什么事都尽量交给寄存器。

 

  ARM还对基本的RISC架构进行了如下扩展:

1.    增加了算术移位指令

2.    地址自增和地址自减模式以优化循环操作

3.    多重load/store指令以增大数据处理速度

4.    几乎所有指令都是可以条件执行的,以增加指令处理速度。

 

  关于ARM到底属于什么类型的CPU,一直以来说法不一,有说他们是RISC(至少他们自己这么认为,公司名字就是Advanced RISC Machines的简写)。

  有说他们是类RISC的,说他们是类RISC的理由是ARM的指令并不是定长的,而且并没有实现真正的流水线。当然这些区别现在已经变得越来越不重要,甚至各领域之间也在互相渗透,比如MIPS的14K和24K系列也已经引入了16位的指令,支持16/32位指令(ARM/THUMB interworking)混编,而较新的ARM架构中也已经实现了对乱序执行的支持。

可能有人会反驳,怎么可以这么说ARM没有实现流水线呢?ARM7中就已经实现了三级流水线!请注意,这里所说的是真正的流水线,如果对MIPS有所了解或者看过MIPS阵营的经典著作《See MIPS Run》,再结合AMBA文档中对AHB和APB的描述,就能明白真正的流水线的含义。下面我们先对其他架构的流水线结构做一个分析。

提到流水线就要了解一个概念叫做delay slot,也就是我们所说的延迟槽,出现这个概念的原因是因为在RISC中,绝大多数指令的执行时间是可预测的,一般为一个时钟周期。而有些指令譬如load,jmp之类的指令无法在一个周期内完成,这样就造成一个问题,在执行jmp,load时,处于译码阶段的指令就要多等一个周期,等前面的指令执行结束它才能进入执行阶段(早期的ARM就是采用这种方式)。而在真正的流水线架构中,遇到这种情况,可以把jmp,load之后的指令在jmp,load进入执行阶段准备,但是没有真正执行的时候提前执行,这样就节省了之前被浪费的那个周期。处于延迟槽中被提前执行的指令必须是对后续指令无害的,有害还是无害一般是由编译器来决定,判断有害还是无害的标准主要是看延迟槽中的指令结果是否和它前面的指令有关联,当无法插入无害指令时,编译器会向其中插入NOP。千万不要小看这一个周期,因为在真正的软件系统运行过程中,内存读写以及跳转操作随处可见,所占的比例相当大,就这一点点效率的提升就导致了MIPS在整数运算性能上的优势,即使是在如今ARM如此盛行的时代,高端的路由器交换机以及大型图形服务器等依然还是MIPS占据优势(情况可能有变,ARM已经收购了MIPS的64位技术)。

个人曾经遇到过几次关于延迟槽的陷阱,举一个最近的例子来说,在MIPS的bootloader中,一般会在汇编代码前有.set noreorder选项,也就是禁止编译器将代码按照编译器所认为的优化执行顺序重新排序,这主要是因为bootloader相对敏感,并且对那么几行代码重排序也没必要。在bootloader中有一段代码拷贝的地方:

1:  addui t1,4

    Addui t2,4

    Sw  t1,[t2]

    Jne t2,t4,1b

    Mfl t2,$14

麻烦就出在了最后一句,按照顺序思维,应该是执行完了循环再执行它,可是由于他在延迟槽中,每次循环都会去执行它,于是修改了t2的值,造成了地址错误的异常。在ARM中你就不用担心这个陷阱,因为ARM没有延迟槽一说,该等你就得等…。

而支持ARM属于RISC的理由主要在于四点:

1. 使用了精简指令集,Reduced Instruction Set Computer,虽然ARM的指令相对其他种类的RISC架构核心来比较还是算多的,但毕竟处于可接受范围,不像x86架构那样不断添加各种指令,浩瀚无穷。RISC的宗旨就是让硬件尽量简单,更多的处理交给编译器和软件,这样就可以让cpu的核心结构更加简单,比较容易实现低功耗高频率。

2. 实现了一定程度的流水线,学过51的人应该还记得51的工作方式,指令都是一条一条地取指,译码,执行,只有上一条指令执行结束,下一条指令才能进入取指阶段。也就是说cpu在相当长一段时间内只能为一条指令服务,而在RISC架构中,每一时刻基本都会有几条指令处于不同的阶段。

3.ARM核心中有较多的通用寄存器,其数目为31个(不算状态寄存器),要比CISC的通用寄存器数目多很多。但是这31个寄存器并不是同时存在的,有些寄存器是模式专有的,只有处理器处于那种模式才能够对其进行操作,术语称为Banked,在某种模式下可见的通用寄存器的个数是16个。

4.采用了Load-Store的工作方式,所谓的Load-Store方式,也就是说一切的计算操作都只针对寄存器,内存中的数据需要先load到寄存器,在寄存器中进行计算之后再store回内存。而在CISC中,内存数据是可以直接参与计算的,这是双方一个巨大的差别。

 

全部回复(1)
正序查看
倒序查看
xinzha
LV.1
2
2014-03-20 22:25

通常来说软件人员并不关心流水线的细节,尤其是上层软件程序员,甚至驱动工程师都不需要了解流水线,但是对于一个真正的系统底层工程师来说,了解流水线的一个比较重要的意义就是当出现问题时,能够精确定位到产生问题的那条指令,从而精确跟踪问题,还有就是异常处理程序执行完成后根据LR寄存器的值来正确返回也需要对流水线的了解。不过有些情况下体制就不能保证精确定位,比如说有write buffer的时候,你的写指令即使会发生错误(例如地址错误、权限错误、器件错误等),也是在数个周期之后,当写指令真正出现在总线上时才会激起异常,其原因是因为速度匹配问题,写数据需要通过write buffer来完成,发生异常的时刻已经离发出写指令的时刻不匹配了。

当发生异常的时候,犯罪分子和发生异常时所在模式的pc的匹配关系如下:

Data abort pc - 8,因为data abort只有进入执行阶段才能被发现,这时第三条指令已经被取指。IRQ pc - 8,IRQ发生时刻不可预知,在当前指令执行完之后响应,所以pc也是第三条的地址。FIQ pc - 8,原理同上。Prefetch Abort pc - 4,这个最绕,是预取指时发生错误造成的,但是如果这条指令不进入执行阶段就不会造成异常,比如说前面一条是jmp。所以我猜测在它进入执行之前,kernel已经知道出错,pc不再更新,保持为它下一条指令的位置。

SWI和UNDEF和上一条一样,都是在译码阶段kernel已经知道会发生异常,不再更新pc,但是进入执行阶段才会激起异常,所以异常地址也都是pc-4。

今天来讨论一下对齐问题,在ARM7,即arm v4中,规定如果访问int类型数据时给出的指针地址的低2位不为0的话,系统会自动将低两位的1抹平,即强制四字节对其,这样的问题就是如果你的数据偏偏就是不对齐的,cpu拿到的数据就是不正确的,bug由此产生。在ARM9(我接触的是arm926ejs)中,如果访问int型数据,低2位不为0,那么cpu直接挂住,一个data abort。在ARM11之后的版本中支持了非对齐访问,是在总线上拆分然后拼接来实现的,也就是说如果你访问int时给出的地址不是4字节对齐,那么总线上会出现两个int访问,然后把数据拼接起来送给cpu,这些对于cpu是不可见的,但是会导致速度下降,总线占用率上升。此功能可以通过修改cp15中来关闭,实现跟以前版本一样的对齐方式。需要说明的是这里的int是4字节而不是2字节。另外一点是对齐问题并不是专指四字节对齐,很多初学者或者有一定经验的人都会犯这个错误,认为只有四字节对齐才会出问题。实际上对齐指的是数据边界对齐,也就是说long long数据要8字节对齐,int要4字节对齐,short要2字节对齐,byte自然是怎么对都齐了。

 

0
回复