ARM处理器启动过程
ARM处理器的上电和复位操作过程类似,都是从处理器的低端复位向量地址0的位置读取第一条指令,由于ARM处理器的异常向量地址是连续排列的,所以异常中端入口指令一般都是存放的一条跳转指令。跳转到哪里呢?至少要跳转过异常中端向量区,然后按照程序流来执行。
ARM处理器系统初始化过程
每次系统上电复位启动时,处理器都处于一种最低性能的基本功能状态,此时,只能从复位向量地址去取指令,其他的功能都处于禁止状态。一旦处理器读取到第一条指令开始执行,那么从软件的角度来讲,处理器和整个系统都处于软件可控的状态。系统的整个流程将由软件来决定。
系统初始化的一般顺序为:
ARM处理器系统初始化过程注意事项
在上述的初始化过程中有两点需要注意,如果处理不好就会出现处理器跑飞的现象
ARM处理器的工作模式与异常中断处理
ARM处理器的工作模式
ARM处理器共有七种工作模式,除了用户模式之外,其他工作哦模式都可以称之为特权模式。在特权模式下,处理器可以访问系统下的所有资源。也可以任意的切换模式。特权模式中,除了system模式之外,其他的五种模式又称为异常模式。
处理器模式可以通过软件控制进行切换,也可以通过外部中端或者内部异常处理程序来进行切换。运行在用户模式下的程序,不能访问一些受操作系统保护的资源。也不能直接进行处理器模式的切换。如果想要进行模式的切换,可以产生异常处理,在异常处理过程中进行模式的切换,这种体系结构可以使操作系统控制整个系统的资源。每一种异常模式中,都有一组寄存器供异常处理器程序使用,这样可以保证在进入异常模式时,用户模式下的寄存器不被破坏。
系统模式不是通过异常过程进入的,它和用户模式拥有完全相同的寄存器,系统模式属于特权模式,可以访问所有的系统资源。也可以直接进行处理器的模式切换,主要是给操作系统内核使用的模式。通常操作系统内核需要访问所有的资源,同时该进程仍然使用用户模式下的寄存器,而不是异常模式下的寄存器,这样可以保证当异常发生的时候,内核进程不被破坏。
以下是ARM处理器的工作模式:
ARM处理器的中断向量和优先级
异常:是指由处理器执行指令导致的程序的终止,异常与指令运行相关,是CPU执行程序所产生的,是同步的。其中异常可分为精确异常和非精确异常,异常处理程序遵循严格的程序执行顺序,不能嵌套(中断可以),只有当第一个异常处理完之后,才能继续处理下面的异常。
中断:是属于异常的一种,但是中断不是由于CPU的程序所引起的,而是外部硬件所触发的,是异步的。
下面是ARM处理器的异常向量表和对应的优先级
低端向量地址 名称 系统模式
0x00000000 复位 用户模式
0x00000004 未定义指令终止 未定义指令模式
0x00000008 软中断(swi) 超级用户模式
0x0000000c Prefetch abort 数据访问终止
0x00000010 Data abort 数据访问终止
0x00000014 Reserved Reserved
0x00000018 IRQ 外部中断模式
0x0000001c FIQ 快速中断模式
说明:
上面的低端向量地址的高4位变成ffff,低4位不变的地址就是高端向量地址,高端向量是ARM架构的可选配置,可以通过硬件的外部输入管脚来配置系统采用低端向量还是高端向量,不能通过指令来改变向量的地址。但是如果ARM芯片内部有标准的协处理器,那么协处理器CP15的寄存器C1的bit13可以用来切换低端向量还是高端向量,bit13为0,表示低端向量,为1表示高端向量。
ARM处理器的异常中断处理
主要介绍进入异常中断处理程序都做了什么,和退出的时候都做了什么?
ARM处理器发生异常中断,则处理器进入如下异常中断自动处理过程(假设发生的异常中断对应的模式为mode)
A:将当前程序状态寄存器CPSR的值保存到SPSR_mode中
B:将CPSR中模式位设置城mode模式,将CPSR中的bit7设置为1(禁止中断IRQ),如果是FIQ中断则再将CPSR中的bit6设置为1(禁止FIQ中断)
C:将返回地址传给lr_mode
D:将该异常向量的地址传送给程序计数器PC,从而进入异常中断处理程序。
当要从异常中断处理程序返回的时候,要做以下两步操作(假设发生的异常中断对应的模式为mode)
A:将保存在SPSR_mode的值回复到当前程序状态寄存器CPSR中
B:返回到异常中断指令的下一条指令处开始执行,也就是将lr_mode寄存器的值适当地址返回到程序计数器pc中。
软件人员只需要做好上述第二步就好,第一步是由处理器自动完成的。
ARM处理器存储访问的一致性问题
什么是存储访问一致性?
当储存系统中引入了cache和write buffer的时候,同一地址单元的数据可能在系统中有多个副本,内存中有一份,cache和write buffer中各有一份,如果系统中采用了独立的数据cache和指令cache,同一地址单元的数据还可能在数据cache和指令cache中有不同的版本。位于不同物理位置的统一地址单元的数据可能不同,使得数据读操作可能不是系统中“最新的”数据,这样带来了存储访问一致性的问题。在ARM存储系统中,数据不一致的问题需要通过程序设计时遵守一定的规则来避免不一致的问题。
当系统中使用了MMU时,就建立了虚拟地址到物理地址的映射关系,如果查询cache的时候进行的相连比较使用的是虚拟地址,则当系统中虚拟地址到物理地址的映射关系发生变化的时候,可能造成cache中的数据和内存中的数据不一致的情况。在虚拟地址到物理地址的映射关系变化之前,如果虚拟地址A1所在的数据块已经被预取到cache中,当虚拟地址到物理地址的映射关系发生变化后,如果虚拟地址A1对应的物理地址发生了改变,则CPU访问A1时,再使用cache中的数据就会得到错误的数据。同样系统使用的是write buffer,系统向虚拟地址A1写数据的时候,并没有真正的将数据写入到内存,而是写入到了write buffer中,一段时间之后,write buffer会自动向内存总写入,如果在write buffer向内存写入数据之前,虚拟地址A1映射到物理地址的位置发生变化,则write buffer中的数据将被写入到变化后的物理地址上,会造成数据错误。
为了避免这种数据不一致的情况发生,在系统中的虚拟地址和物理地址变化之前根据系统的情况执行下面的操作中的一种或者几种。
A:如果数据cache为write buffer类型,清空该数据的cache
B:使cache中相对应的数据块无效
C:将write buffer中被延迟的写操作提前执行
D:有些情况需要将存储区域设置城非缓冲的
当系统中采用独立的数据cache和独立的指令cache的时候,下面的操作序列可能造成指令不一致的问题:
A:读取地址为A1的指令,从而包含该指令的数据块被预取到指令cache中
B:与A1在同一个数据块中的地址为A2的存储单元的数据被修改,这个数据写操作可能影响数据cache write buffer和内存中地址为A2的存储单元的内容,但是不影响指令cache中地址为A2的存储单元的内容。
C:地址A2存放的是指令,当该指令执行的时候就可能发生指令不一致的问题,如果地址A2所在的块还是在指令cache中,系统将执行修改前的指令,如果地址A2所在的块不在指令cache中,系统将执行修改后的指令。
为了避免指令不一致的问题,在上面的A和B之间插入下面的操作序列:
A:对于使用统一的数据cache和指令cache的系统,不需要任何操作
B:对于使用独立的数据cache和指令cache的系统,需要使指令cache的内容无效
C:对于使用独立的数据cache和指令cache的系统,如果数据cache是write back类型的,清空数据cache,当数据操作修改了指令的时候,最好执行上面的操作序列,保证指令的一致性。当可执行文件加载到内存后,在程序执行到入口点执行之前,先执行上面的操作序列,就能够保证下面指令是新加载的可执行文件的代码,而不是指令中原来的旧代码。
3)DMA造成的数据不一致问题
DMA操作直接访问内存,而不会更新cache和write buffer中对应的数据。这样很可能产生数据不一致的问题。如果DMA从内存中读取的数据已经包含在cache中,而且cache中对应的数据已经被更新,这样DMA读到的将不是系统中最新的数据。同样DMA写操作直接更新内存中的数据,如果该数据已经包含在cache中,则cache中的数据将会比内存中的数据老,也将产生数据不一致的问题。为了避免这种情况发生,根据系统中的情况,执行下面的操作中的一种或几种:
A:将DMA访问的内存设置城uncached或者unbuffered
B:将DMA访问的内存所涉及的数据在cache中的块无效,或者清空cache
C:清空write buffer(执行wite buffer中延迟的所有写操作)
D:在DMA操作期间限制处理器访问DMA所访问的区域。
Linux中解决存储访问一致性的问题的方法
在linux中用宏barrier()来解决存储访问的一致性问题,barrier()的定义如下所示:
#define barrier() __asm__ __volatile__(“”:::”memory”)
另外在barrier()的基础上还衍生出了很多类似的定义,如:
#define mb() __asm__ __volatile__(“”:::”memory”)
#define rmb() mb()
#define wmb() mb()
#define smp_mb() barrier()
#define smp_rmb() barrier()
#define smp_wmb() barrier()
Barrier是内存屏蔽的意思,CPU越过内存屏障后,将刷新自己对寄存器的缓冲状态,barrier()宏定义这条语句实际上不生成任何代码,但是在gcc编译的时候,在barrier()后刷新寄存器对变量的分配具体分析如下:
__asm__:告诉编译器下面的代码是汇编代码
__vilatile__:告诉编译器该对象可能在程序之外被修改,严禁将此处的汇编语句与其他的语句重组和优化,即按照原来的样子处理这里的汇编。在C语言中通过使用关键字volatile声明存储器映射的I/O空间来防止编译器在优化时删掉有用的存储访问操作。
Memory:告诉编译器所有内存单元都被汇编指令修改过,registers和cache中的已经缓存的数据将被作废,barrier()之后的程序CPU必须重新从内存中读取数据而不是使用过期的registers和cache中的数据。
“”:::表示这是个空指令,barrier()不用在此插入一条串化行汇编指令,所谓串行化指令也就是同步指令,即将cache,write buffer和内存中的数据同步更新。
总的来说barrier()有两个作用:
第一:告诉编译器不要优化这部分代码,保持代码原来的顺序执行。
第二:告诉CPU执行完barrier()的代码后要进行同步操作,更新registers和cache以及写缓存和内存中的内容,使用数据全部从内存中重新读取。