ARM 学习笔记整理(一)

ARM 学习笔记整理,主要用于将来的研究开发参考。

一、ARM体系结构的版本

        处理器的体系结构就是其使用的指令集,而通常我们说的Soc结构的处理器可以理解为一个实现机器指令集的硬件内核,在这个内核周围集成各种功能模块,比如:图像处理、通信处理等模块,然后将这些全部封装在一起,并将各个功能模块和内核通过引脚从芯片封装中引出来供人们使用。我的理解是Soc结构基本上是内核+通信+人机交互所组成。比如S3C2440A就是有ARM920T核心处理器+外部存储控制器+LCD控制器+各种控制和通信接口组成,通过查询ARM920T的技术手册就可以知道ARM920T使用的是ARMv4T版本的指令集。

 
 
 
 二、ARM处理器系列

 

 
         ARM9系列微处理器有ARM920T和ARM922T两种类型。

 
 
 
 通过一、二两点的学习,对ARM的整体有了一个总体理解:首先ARM公司根据生活中的各种应用需求设计了各种版本的指令集(即ARM的体系结构),不同系列的ARM内核实现了不同版本的指令集,而各个厂家(比如三星公司、intel公司)使用不同的ARM内核加上各种外围应用模块设计出自家的Soc芯片,进而设计出各种产品满足社会生活的各种需要。

三、程序运行地址和加载地址-为什么使用位置无关指令 

        在嵌入式编程中,我们经常讲程序保存在 nand flash中。但是我们知道,nand flash的接口设计和 RAM 的接口设计是不一样的。他的数据线通常都是复用的,所以通常存取都是以块为单位(nor flash带有RAM接口,有足够的地址线来寻址,

所以可以访问内存中每一个字节)  ,这导致了nand flash不可以片内执行程序(nor flash可以,因为他能存取内存每一个字节)。

        对于 s3c2440 来说,当使用 nand flash 启动时,为了解决 nand flash 不能片内执行程序的问题(片内不能执行,那么程序烧进去不是不能运行嘛) 于是 s3c2440 在内部有一个 叫 Stepping Stone(垫脚石?)的东西,其实他就是一块 4KB 的RAM,当我们以 nand flash启动想运行烧写在上面的程序时,s3c2440会自动将 nand flash 前面 4KB 的内容拷贝到这个叫Stepping Stone的片内内存中, 然后 pc指针为0 从这个片内内存0地址开始运行(通常这个0地址是复位向量,即跳到复位中断处理函数执行一些初始化设置,比如设置一般的栈指针,中断处理模式中的栈指针,系统处理模式的栈指针等)。但是这个片内内存只有 4Kb 大小,如果我们烧到 nand falsh中的程序大于 4KB 那么只有一部分被拷贝到了片内内存中去执行。剩下的就不能执行了。但是我们不是可以接外设吗, s3c2440的BANK6 和 BANK7都可以接最大 128M的SDRAM。SDRAM是一个RAM(内存) 那么当程序大于 4k 的时候,当我们以 nand flash启动后,前面的4Kb 被拷贝到 片内RAM中去执行(自动完成)。 我们在这前4K的程序中初始化SDRAM(SDRAM 使用前需要初始化) ,然后将剩下的程序拷贝到 SDRAM中(不是只有4kb 被拷贝到片内RAM中执行了嘛) 然后跳转到 SDRAM中去执行剩下的程序。

        那么也就是说通常当程序大于 4kb的 时候,我们就需要把程序拷贝到SDRAM中去运行(程序小于4KB 那么也就可以不用拷贝了,以nand flash方式 启动后,程序全被拷贝到 片内4kb的 RAM中去运行)。那么也就是说 通常当程序大于 4kb的时候,我们就需要把程序拷贝到SDRAM中去运行(程序小于4KB 那么也就可以不用拷贝了,以nand flash方式启动后,程序全被拷贝到片内4kb的RAM中去运行,以nor flash方式启动不需要拷贝,因为nor flash可以寻址到字节)。那么,既然程序大于4kb的时候需要从nand flash中拷贝到 SDRAM中去运行。自然可以想到烧到nand flash中的程序前面一部分代码应该是初始化SDRAM(程序最终需要拷贝到SDRAM中去运行)和将NAND flash中的剩余的程序拷贝到SDRAM中去(全考过去也行,方便点),然后跳转到SDRAM中执行。

        下面我们就要详细说明,前面这一段跳转到 SDRAM去执行前在片内内存中运行的初始化代码的要点和细节。也就是 关于程序运行地址和加载地址以及位置无关指令的 一些注意点和细节先来看下程序运行地址和加载地址看个 随便写的简单的示例,这是一个连接脚本中的一段  first 0x30000000 : AT(0){main.o}我们只注意 0x30000000 和 AT(0) 这两处。

如果程序是烧到nand flash中,这句话里面的意思就是a.o烧到nand flash 中从0地址(当然也可以是其他数)开始的地方,但他的运行地址是在从0x30000000地址开始的地方(为什么是0x30000000,因为 s3c2440的 bank6 和bank7 可以接sdram,bank6从地址0x30000000开始,我们的程序最终是要在SDRAM中运行的)。烧到 nand flash中从地址0 开始的地方应该比较好理解,就是说我程序是存储在nand flash中最开始的地方。那么运行地址呢怎么理解呢, 看一下汇编 :

  1 .text

  2 .global _start

  3 _start:

  4         mov     r0, #2

  5 loop:

  6         ldr     pc,=loop

上面这段汇编,我们将 他的运行地址分别设为 0x0 和0x30000000来看看反汇编后的情况,先将运行地址设为0x0:

  all: test.c head.s

  2         arm-linux-gcc -c -Wall -o head.o head.s

  3         arm-linux-ld -Ttext 0x0 -o main_elf  head.o

  4         arm-linux-objdump -D -m arm main_elf > main.dis

  5 

  6 clean:

  7         rm -rf *.o main.dis main_elf

反汇编 main.dis如下:

  6    00000000 <_start>:

  7    0:   e3a00002        mov     r0, #2  ; 0x2

  8 

  9     00000004 :

 10    4:   e51ff004        ldr     pc, [pc, #-4]   ; 8 <.text+0x8>

 11    8:   00000004        andeq   r0, r0, r4

将arm-linux-ld -Ttext 0x0 -o main_elf  head.o 改为 arm-linux-ld -Ttext 0x30000000 -o main_elf  head.o 也就是将运行地址设定为0x30000000 再看看它的反汇编

  6   30000000 <_start>:

  7   30000000:       e3a00002        mov     r0, #2  ; 0x2

  8 

  9   30000004 :

 10  30000004:       e51ff004        ldr     pc, [pc, #-4]   ; 30000008 <.text+0x8>

 11  30000008:       30000004        andcc   r0, r0, r4

注意最左边的数字,这就代表程序的运行时地址。也就是说程序的代码的地址是以运行地址为基址来标示的。什么意思呢, 看 head.s中的  ldr     pc,=loop  这段,这是想让 程序调回到 loop处(死循环) 当运行地址设定为 0x0 时从汇编代码我们看到 loop标号代表的地址为0000004 也就是说  ldr     pc,=loop 反汇编为 ldr     pc, [pc, #-4] 即pc值为pc-4地址里面放的值(4),就是跳转到 00000004 地址去。 那么把运行地址设定为0x30000000时, 从反汇编代码中我们看到这时 loop表号代表的地址是0x30000000 那么 ldr     pc,=loop 就是跳转到 地址 0x30000000。

        简单的理解就是程序运行地址就是计算机认为程序运行时应该处于的地址。所以运行地址设置为0 时,程序中的所有代码中的标号都是以0x0为基址的。计算机认为他运行的时候的地址是从 0x0开始的。运行地址设置为0x30000000 时,程序中的所有代码中的标号都是以0x30000000为基址的,计算机认为他运行的时候的地址是从0x30000000开始的。

        明白了运行地址 的概念后,我们来看看程序的启动过程:假设现在程序的加载地址(生成的映象文件中的偏移地址,因为映象文件是烧入到nand flash中的,也即烧到nand flash中的位置)为0 运行地址为0x30000000,前面说过程序是烧写在 nand flash中从 0地址开始的地方,那么以nand flash方式启动后,该程序被拷贝到片内ram中。这时候pc为0,并开始运行程序,也就是说,计算机现在从 片内内存地址0开始运行程序进行一些初始化操作并将sdram初始化后再跳转到sdram中去运行。但是我们上面不是说,程序运行地址被设置成了 0x30000000(SDRAM的起始地址)吗。但是程序在跳转到sdram之前却是在0x0地址开始运行。这就造成了前面这段还未跳转到sdram(从0x30000000开始)的程序的实际程序运行地址和设定的运行地址不符合。如果程序中没有用到地址有关代码,和全局变量静态变量之类地址有关变量,那么这段 在初始化SDRAM之前 的程序 其实还是可以运行下去的并在初始化SDRAM后,跳转到SDRAM中去运行,一切正常,这是因为上电初始化后pc寄存器的值为0,cpu从0地址处取指令开始运行程序,如果是32位cpu则后续pc寄存器的值分别为4、8、16等地址值,只要没有用到0x30000000为基地址的指令都可以正常运行,但是如果程序中用了这些代码,就不会运行成功了。

        原因很简单,因为现在我们设定的运行地址为 0x30000000,那么当我们执行地址有关代码 如 ldr pc,=A (A是个标号或函数名)就是使用了 绝对地址,那么 pc = 0x3000000+x (x为标号A相对于起始也就是前面设定的运行地址的偏移) 。那么这条指令执行之后,pc 指针将跳转到 0x30000000后的某处(SDRAM中)。但是 sdram 现在 还未初始化! 这就 造成了错误。同样如果有全局变量或静态变量也是。所以我们需要使用 b bl类的位置无关指令。如  b  A 、b bl跳转是基于 pc的 跳转。即相对跳转 ,比如执行 b A 这条指令时,假设现在pc =5, A标号相对当前的位置为2(在之后)那么 b A 后 pc =pc +2    即计算机不管当前pc指针是多少,他执行的是相对于当前位置的跳转。也就避免了 向上面的那样跳转到了0x30000000之后的未初始化的地址中去。

        通过第三点这个具体的实例说明弄清楚了多年来一直没有搞清楚的cpu上电处理过程,运行地址与加载地址的概念,位置无关指令与位置相关指令的概念。

       各种ARM内核的启动过程各不相同,上面举例的S3C2440是用的ARM9内核,像LPC1111系列用的是Cortex-M0系列,M0系列的上电启动过程就没有这么复杂,下图是LPC1111的内存映射图:

 

 从图中我们可以看到M0处理器内部自带了16K的boot ROM,上电复位的时候就是直接跳转到这个boot Rom执行,通过System memory remap register寄存器将地址重定向到用户flash模式,就可以执行烧入到内部flash中的用户代码。其boot Rom的处理流程图如下:

  

四、使用存储控制器访问外设原理

①S3C2410/S3C2440的地址空间

  

通过这点我们可以明白一个概念:通常说的8/16/32/64位处理器是指内部指令和数据寄存器宽度有8/16/32/64位,有8/16/32/64位的数据处理能力和寻址能力,但在寻址能力上肯定是不能超过内部寄存器的数据位宽的,但可以小于,比如上面说的S3C2440的外部地址线有27根,有128M的直接寻址能力,远小于4G的寻址能力;但可以通过不同的bank对不同的地址空间进行访问。而绝大多数的单片机基本不提供对外的地址接口,直接就封装在芯片内部,全部就是直接寻址。
 
 
 

②存储控制器与外设的关系
         从S3C2410/S3C2440的地址空间划分我们可以看出,ARM CPU在设计上就已经对地址空间按功能做了各自的划分,即各种外设占用一段地址空间,寄存器占用一段地址空间,在程序设计中通过对这些地址空间进行访问就可以对不同的外设和内部寄存器进行读写操作。而对这些外设和寄存器进行访问的地址译码、读写时序的产生都是由存储控制器进行管理和控制的。

        比如在S3C2440上电从nand flash启动的过程中,首先由SteppingStone控制器将nand flash的前面4kB数据加载到4kB的SRAM中,这个4kB的SRAM称为SteppingStone;接着初始化sdram,这个需要sdram控制器的参与;最后还要将nand flash中4k后面的部分也要拷贝到sdram中,这就需要nand flash控制器和sdram控制器的参与。

五、内存管理单元MMU

  
 
 

 

                                                                            通用的转换过程

                                                    S3C2410/S3C2440的地址转换图

图中的TTB Base代表一级页表的地址,将它写入协处理器CP15的寄存器C2(称为页表基址寄存器)即可,一级页表的地址必须是16K对齐的(位[13:0]为0)。为什么必须是16K对齐,这是因为32位CPU的虚拟地址空间为4G,一级页表使用4096个描述符来表示这4G空间,每个描述符占4个字节,所以一级页表需要16K的存储空间。

 

                                                           Translation table base register

S3C2410/S3C2440的地址转换图明确的表示出了以段的方式进行地址转换时只用到了一级页表,而以页的方式进行地址转换时用到了二级页表。 

 一级页表描述符格式如下:

         从一级页表描述符可以看出,段描述符提供了1M内存块的基地址[31:20];页表描述符提供了包含二级描述符页表的基地址,有两种大小的页表:

        ■粗页表基地址为[31:10],粗页表可寻址空间为[9:0]共1K的空间,按照32位CPU每个页表项占4个字节,粗页表包含256项,而由一级页表项决定了后续的页表能寻址的空间为1M,因此粗页表中每个页表描述符项可寻址的空间为4K;

        ■细页表基地址为[31:12],细页表可寻址空间为[11:0]共4K的空间,按照32位CPU每个页表项占4个字节,细页表包含1K个页表项,而由一级页表项决定了后续的页表能寻址的空间为1M,因此细页表中每个页表描述符项可寻址空间为1K。

        以段的方式进行映射时,虚拟地址MVA到物理地址PA的转换过程如下:

 页表基址寄存器位[31:14]和MVA[31:20]组成一个低两位为0的32位地址,MMU利用这个地址找到段描述符。从段描述符中取出段基地址[31:20],它和MVA[19:0]组成一个32位物理地址,这就是MVA对应的PA。

        如果一级页表的访问返回粗页表或者细页表描述符,那么返回的描述符中就包含了二级页表的基地址,通过访问二级页表就得到二级页表描述符,下图所示为二级页表描述符格式:

 二级页表描述符项定义了大页64K、小页4K、极小页1K的基地址。位[31:16]为大页基地址,[15:0]为大页64K的寻址空间,由前面的分析可知一级页表中的粗页表描述符决定了一个粗页表有256项,每项寻址4K的地址空间,如果大页描述符保存在粗页表中,要能寻址大页64k的地址空间,那么粗页表中连续16个条目都应该保存同一个大页描述符。同理,如果小页描述符保存在粗页表中,位[31:12]为基址,[11:0]为小页4K寻址空间,则粗页表中一个条目保存一个小页描述符。如果小页描述符保存在细页表中,由前面的分析可知一级页表中的细页表描述符决定了一个细页表有1K项,每项寻址1K的地址空间,那么细页表中连续的4个条目应该保存同一个小页描述符。如果大页描述符保存在细页表中,那么细页表中连续64个条目应该保存同一个大页描述符。如果是极小页描述符保存在细页表中,刚好一个细页表条目保存一个极小页描述符。

 
                                    上图为大页的地址转换过程(大页描述符保存在粗页表中)

   

         上图为小页的地址转换过程(小页描述符保存在粗页表中)

 

              上图为极小页的地址转换过程(极小页描述符保存在细页表中)

内存的访问权限检查:

 关于CP15协处理器的详细描述可以参考ARM920T内核技术手册。

 

  
 
 
 
 
 
 
 
 
 
 
 
 
 I/O地址空间的概念很重要,上面做了详细的说明。

TLB与cache的异同:

TLB:基于页表访问的特点,采用一种容量小(8~16个字),访问速度快(和通用寄存器访问速度相当)的存储器来存放页表中的地址变换条目.

cache:数据从主存到CPU之间传送的数据缓存,以块为单位(一个块大小为几个字).

无论从数据搜索方式,更新方式,分类方式,以及替换的形式,一开始很可能把这两种东西当做一样东西.

但是,他们确实是两种不同的机制:

1:控制寄存器并不相同.TLB用的是CP15的C2,C8,C10. cache用的是CP15的C1,C7,C9.

2:写入的单位大小不同.TLB只是单个的地址变换条目写入(4字节). cache是根据块的大小复制块大小的数据(有点像memcpy).虽然两者都是从内存写入各自的位置.

3:替换方式不同.虽然容量满的时候,都会发生替换,但是算法有点不同.TLB:p178(ARM体系结构与编程)根据一定的淘汰算法进行替换(既然有算法,那么应该是软件决定). cache:算法比较简单,随机替换法和轮转替换法.这两种效率都比较低下,我想主要原因是cache用的是纯硬件实现.但是还是有改进的空间,最好是根据时间戳或者使用频率决定(不能单纯地利用使用次数),要是硬件能实现,那么内存的利用率将会有大大的提高(因为是有目的的筛选). 这一点可能是TLB和cache不能融合的一个关键.

4:存储速度不同.因为用的两种不同机制,所以速度肯定会有不同.到底谁更快,没有确定结论.根据我的推测应该是TLB,首先TLB的使用更频繁,其次TLB的算法更具有优势. (要是能多出不少类似TLB的积存器,那么cache就可以被淘汰了,呵呵!)

5:数据过度方式不同. TLB:并不一定要与他打交道,可以先读取主存,然后在更新TLB,先斩后奏! cache:是主存和CPU通信的必经之路。

  一个MMU内存使用实例:

 
 
 
 
 
 
 
 
 
 
 
 
 
 

 

 
 
 
 
 
 
 
 
 
 
 
 

你可能感兴趣的:(网易博客搬迁之嵌入式方面)