Chinaunix首页 | 论坛 | 博客
  • 博客访问: 229964
  • 博文数量: 18
  • 博客积分: 3295
  • 博客等级: 少校
  • 技术积分: 431
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-26 19:11
文章分类
文章存档

2010年(18)

分类: LINUX

2010-11-08 19:59:46

MMUMemory Management Unit)内存管理单元,它负责虚拟地址到物理地址的映射,并提供硬件机制的内存条件检查。

以前的程序是很小的,可以全部装入内存中运行,而随着技术的发展,出现下面两个问题:

1.       有的程序很大,它所要求的运行空间大于了物理内存的容量;

2.       多任务系统中需要同时执行多个程序,这些程序的内存总数超过了物理内存的总量;

实际上,一个程序在运行之前,没有必要将整个程序全部装入内存,而是将当前要运行的程序装入内存,其余的部分需要用的时候再装入内存,而当内存容量耗尽时,将暂时不用的部分调入磁盘,这样就可以实现一个大的程序在小的内存空间中运行,也可以实现多任务程序同时运行。这就是虚拟存储器的实现过程。类似于我们在PC机上所说的虚拟内存。这一章涉及的概念很多,请大家反复阅读并查阅后面我提到的参考资料。 

、地址的分类及相关概念

l  对于S3C2440来说,它有27条地址线,再加上它有nGCS0---nGCS78个片选,实际上它的寻址能力可以达到1GB0---0x4000 0000)。

l  虚拟存储器从逻辑上对内存的容量进行了扩充,S3C244032CPU,因此,对应的虚拟地址范围是0----0xFFFFFFFF,这部分被称为虚拟地址空间,其中的某个地址被称为虚拟地址。

l  虚拟地址(VA,virtual Address)、变换后的虚拟地址(MVA,Modified Virtual Address)、物理地址(PA,Physical Address)。

l  页表(Page Table):在虚拟地址转换到物理地址时,通常是用表格来存储虚拟地址所对应的物理地址和访问权限,这个表格就称为页表,页表是由的一个个条目(也称为页表项,Entry)组成的。

在没有启动MMU的时候,所有的部件(CPUCacheMMU、外设)都使用的是物理地址,访问的是物理内存空间。启动MMU后,CPU发出的是VA,经过CP15协处理器的C13寄存器转化为MVA(硬件自动完成)供CacheMMU使用,最后经过映射,变成PA,用PA来访问实际的设备。

CP15协处理器的作用比较复杂,我在这里就不一一列出了,建议大家参考手册(ARM920T P29页),如果疑问,希望大家和我交流。

、地址映射的方式

S3C2440 CPU采用页表(Page Table)的方法进行地址映射,最多支持两级页表以对应不同的映射方式:

Ø  段(Section)的方式映射:只采用一级页表即可。段大小为1MB

Ø  页(Page)的方式映射:就要采用二级页表。页大小分为三种:大页(Large Page64KB,小页(Small Page4KB,极小页(Tiny Page1KB

、地址映射的过程(以段的方式)

S3C2440支持段、页两种方式的地址映射,这里我只介绍一下段的映射过程,页方式映射过程和段的方式类似,具体映射过程详见参考资料。下图为S3C2440的两种方式的地址转换过程图。

其中TTB base代表一级页表基址,将它写入CP15C2寄存器(页表基址寄存器)即可。一级页表的地址必须是16K对齐的。上面提到了虚拟地址空间的大小为4GB,而段的大小为1MB,那么,如果按段的方式映射,一级页表中就会有4096个描述符(段描述符),存放的是物理段的起始地址;如果按页的方式映射一级页表里面存放的就是4096个二级页表项。

如下图所示,使用

MVA[3120]来索引一级页表项,即得到一个描述符(段描述符),每个描述符占4个字节。如下图。

根据其最低两位来区别描述符的类型:

1)        0b00:无效。

2)        0b01:粗页表。共有256个条目,每个条目表示大小4KB,共1MB

3)        0b10:段描述符。其中[3120]为段基址,其低20位填充0就是一块1MB大小的物理地址空间的起始地址。利用MVA[19:0]来在这1MB的空间寻址。由描述符的[3120]MVA[190]构成了虚拟地址所对应的物理地址。

以段的方式进行地址映射过程如下图:

A.        TTB[3114]MVA[3120]组成一个低两位为032位地址,MMU利用这个地址找到段描述符。

B.        取出段描述符的位[3120]------即段基址,它和MVA[190]组成一个32位的物理地址,这就是MVA对应的PA

4)        0b11:细页表。共有1024个条目,每个1KB,总大小也为1MB

MMU除了进行地址映射外,还有一项重要的工作就是:内存访问权限的检查。简单的说就是检查一块内存是否可读、可写。这也是带MMUCPU的“高级”之处,大家都知道INTERCPU有实地址模式和保护模式,其功能与此处类似,这样就可以使我们的代码更“安全”的运行。

权限检查是由CP15寄存器C3(域访问控制)、描述符的域(Domain)、CP15C1寄存器的R/S/A位以级描述符的AP位联合作用。

其中CP15C1寄存器A位决定是否进行对齐检查。无论MMU是否开启,都可以进行对齐检查。CPU读取指令前不进行对齐检查,以字节为单位访问时也不进行对齐检查。对齐检查在MMU的权限检查、地址映射之前进行。

S3C2440共有16个域,CP15C3寄存器每两位对应一个域,用来表示这个域是否进行权限检查。具体如下图。

(以段描述符为例)

在段描述符中,有4位的Domain用来表示该段属于哪个域,如0b0000表示属于域0,如果此时域0的权限设置为00,即不可访问,这时如果访问该段内存就会产生“domain fault”;如果域访问权限设为01,则代表使用描述符中的AP位为进行检查。其中AP位要联合CP15C1寄存器中的R/S位一起来设置。具体内容见手册,在此就不再赘述了。

TLB的使用

CPU有了MMU的部件后,可以使我们从用户的角度上看,内存空间大了许多,同时也可以进行访问权限的检查,这样也安全了许多。但是,访问物理内存的过程也复杂了,如:使用一级页表进行映射时,对内存需进行两次访问,第一次访问一级页表以得到物理内存地址,第二次才是真正在物理内存上进行读、写数据;如果使用二级页表进行映射,那么需要三次访问才可以。这样就大大降低了CPU的使用效率。

出现了以上问题后,人们开发研究,后发现:程序中所用的指令、数据大多数都局限在一个很小的范围内,其中的地址、数据经常多次使用,这也称为“程序的局限性”。因此,我们可以把近期使用页表条目缓存在一个很小的、高速的存储器中,以避免每次都到主内存(物理内存)中去查找,这样就可以大幅度的提高CPU的性能,而这个存储器就是TLBTranslation Lookaside Buffers)。

这样CPU访问内存空间的过程就变成:CPU首先发出虚拟地址,MMU首先访问TLB,如果TLB中有所需要的虚拟地址描述符,则直接利用此描述符进行地址转换和检查;如果TLB中没有找到该描述符,MMU则访问页表找到该描述符进行地址转换和权限检查,并将此描述符填入TLB中以备下次使用,如果TLB已满,则利用round-robin算法找到一个条目,覆盖它。

用法注意:使用TLB时,一定要注意保证TLB中的内容与页表一致。因此,在启动MMU之前,使无效整个TLB,改变页表时,使无效所无改的虚拟地址所对应的TLB中的条目。

CacheWrite Buffer Cache的整体绍

正如上面所说,程序是有局限性的,因此利用TLB缓存一些页表中的条目,可以大大增加CPU的使用效率。同样基于程序访问的局限性,在CPU和内存之前增加一个高速、容量相对较小的存储器,把正在执行的指令附近的一些指令和数据从主存调入这个存储器,供CPU在一段时间内使用,这也可以大大提高程序的运行速度。这个介于内存和CPU之间的高速存储器就称为“cache”。

启动Cache后,如果在Cache中找到想要的数据或指令,则直接返回使用;如果没能在Cache中找到,则从内存中读取,并将其存放Cache中,以便下次使用时能在Cache中使用。

启用Cache后,CPU“写”数据时有写穿式(Write Through)和写回式(Write Back)两种:

1.         写穿式:从CPU发出写信号送到Cache的同时,也写入内存,以确保数据同步的更新。

其优点是操作简单,但由于主存的速度较慢,降低了系统的效率和占用总线的时间。

2.         写回式:当CPU写数据时,只是将写信号发送到Cache一级,只更新Cache,并在Cache中设置一标志地址及数据的新旧信息,当Cache的数据换出或清空时,再将数据一次性的写入主存。

S3C2440 CPU内置了16KB的指令CacheICache)和16KB的数据CacheDCache)。

Write Buffer

  S3C2440 CPU内置了16-word的数据Buffer4-address的地址Buffer。可以通过软件设置使其有效。其功能主要是和DCache配合使用。

Cache的操作

1.         清空:把Cache或者Write Buffer中已将修改的但并未写入内存中的数据写入内存。

2.         使无效:使其不能再使用,但不将数据同步到内存中。

ICache

ICacheDCache部分我只把需要注意的地方强调一下,具体的操作步骤详见手册。

系统刚上电的时候ICache默认为无效状态,内容为无效,功能是关闭的。向CP15C1的第12位写1可以启动ICacheICache一般在MMU启动后开启(MMU未启动也可以开启ICache)。应尽早开启ICache以提高性能。

DCacheWrite Buffer

ICache一样,刚上电时,DCache的内容也是无效,功能也是关闭的,Write Buffer内容也是无效的。向CP15C1的第2位写1可以开启DCache,而Write Buffer是配合DCache使用的,自动开启、关闭,无需程序员手动设置。

TLB类似,使用Cache时需要保证DCacheWrite Buffer中的内容和内存中数据的一致性,需要遵循以下两原则:

1.         清空DCache,使得内存中的数据和DCacheWrite Buffer中一致。

2.         使无效DCache,使CPU取指时重新读取内存。

使用方法注意:

1.         开启MMU之前,使无效ICacheDCacheWrite Buffer

2.         关闭MMU之前,清空ICacheDCache

3.         如果代码有变,使无效ICache,以保证CPU取指时重新读取内存。

4.         使用DMA操作可以被Cache的内存时,将内存的数据发送出去时,要清空Cache;将内存的数据读入时,使无效Cache

5.         改变页表的映射关系时,要重新考虑内存是否被Cache

6.         开启Cache时要保持Cache和内存的数据一致性。

7.         对于I/O地址空间,不能用Cache,可以使用volatite关键字。

、协处理器的操作指令

{cond}  p#,,Rd,cn,cm{,}

MRC               从协处理器获取数据传给ARM920T CPU的寄存器;

MCR                  ARM920T CPU寄存器获取数据传给协处理器;

{cond}      执行条件,省略时表示无条件执行;

p#       协处理器序号;

  一个常数;

Rd                            ARM920T CPU寄存器;

cncm      协处理器的寄存器;

          一个常数。

expression1expression2 具体数值根据操作对象不同而不同,具体命令详见手册《ARM920T》第二章。

 

、存储器小结

 

存储器类 

位于哪里

存储容量

半导体工艺

访问时间

如何访问

CPU

寄存器

CPU执行单元中

CPU一般有几个到几十个不等的寄存器,每个寄存器的大小由CPU的字长决定,因此,总大小一般为几十字长到几百字长不等。

寄存器这个名字就是一种数字电路的名字,它由一组触发器(Flip-flop)组成,每个触发器保存一个Bit的数据,可以做存取和移位等操作。计算机掉电时寄存器中保存的数据会丢失。

寄存器是访问速度最快的存储器,典型的访问时间是几纳秒。

使用哪个寄存器,如何使用寄存器,这些都是由指令决定的。

Cache

MMU一样位于CPU核中。

Cache通常分为几级,最典型的是两级Cache,一级Cache更靠近CPU执行单元,二级Cache更靠近物理内存,通常一级Cache有几十到几百KB,二级Cache有几百KB到几MB

Cache和内存都是由RAMRandomAccessMemory)组成的,可以根据地址随机访问,计算机掉电时RAM中保存的数据会丢失。不同的是,Cache通常由SRAMStaticRAM,静态RAM)组成,而内存通常由DRAMDynamicRAM,动态RAM)组成。DRAM电路比SRAM简单,存储容量可以做得更大,但DRAM的访问速度比SRAM慢。

典型的访问时间是几十纳秒。

Cache缓存最近访问过的内存数据,由于Cache的访问速度是内存的几十倍,所以有效利用Cache可以大大提高计算机的整体性能。一级Cache是这样工作的:CPU执行单元要访问内存时首先发出VACache利用VA查找相应的数据有没有被缓存,如果Cache中有就不需要访问物理内存了,如果是读操作就直接将Cache中的数据传给CPU寄存器,如果是写操作就直接改写到Cache中;如果Cache没有缓存该数据,就去物理内存中取数据,但并不是要哪个字节就取哪个字节,而是把相邻的几十个字节都取上来缓存着,以备下次用到,这称为一个Cache Line,典型的Cache Line大小是32~256字节。如果计算机还配置了二级缓存,则在访问物理内存之前先用PA去二级缓存中查找。一级缓存是用VA寻址的,二级缓存是用PA寻址的,这是它们的区别。Cache所做的工作是由硬件自动完成的,而不是像寄存器一样由指令决定先做什么后做什么。

内存

位于CPU外的芯片,与CPU通过地址和数据总线相连。

典型的存储容量是几百MB到几GB

DRAM组成,详见上面关于Cache的说明。

典型的访问时间是几百纳秒。

内存是通过地址来访问的,在启用MMU的情况下,程序指令中的地址是VA,而访问内存用的是PA,它们之间的映射关系由操作系统维护。

硬盘

位于设备总线上,并不直接和CPU相连,CPU通过设备总线的控制器访问硬盘。

典型的存储容量是几百GB到几TB

硬盘由磁性介质和磁头组成,访问硬盘时存在机械运动,磁头要移动,磁性介质要旋转,机械运动的速度很难提高到电子的速度,所以访问速度很受限制。保存在硬盘上的数据掉电后不会丢失。

典型的访问时间是几毫秒,是寄存器访问时间的10^6倍。

由驱动程序操作设备总线控制器去访问。由于硬盘的访问速度较慢,操作系统通常一次从硬盘上读几个页面到内存中缓存起来,如果这几个页面后来都被程序访问到了,那么这一次读硬盘的时间就可以分摊(Amortize)给程序的多次访问了。

CPU经历了32位到64位、一级缓存到三级缓存、单核到多核的转变,内存也由DDR I转变为现在的DDR III,但硬盘的技术却变化不大,这也成为制约现代计算机性能的一个甁颈。

六、实例分析

本章采用韦东山老师的源代码,后面有完整的注释,请大家参照。具体的代码我不一一列出,我会马上传上来,供大家下载。下面我只讲一下需要注意的地方。

本章使用虚拟地址对TQ2440开发板进行操作,将Sdram物理地址0x30000000-0x33FFFFFF映射成虚拟地址0xB0000000-0xB3FFFFFF,将寄存器0x56000000-0x56100000映射成虚拟地址0xA0000000-0xA0100000

由于32CPU虚拟地址为4GB,需要4096个描述符,每个描述符占4个字节,因此,一级页表中的4096个描述符本身占16KB,将其存储在虚拟地址0xB0000000-0xB0004000处(对应Sdram中的0x30000000-0x30004000)。

程序采用一级页表,段映射方式。

程序首先执行head.s文件,该文件调用一些函数完成以下功能:关闭看门狗、设置堆栈指针、初始化sdram、代码搬运到sdram中,设置页表、启动MMU、重设栈指针、运行主函数。其调用的函数在init.c文件中实现。

init.c文件中,mmu_init函数中采用的是C内联汇编的方式,即在C代码中嵌入汇编语言。有的时候需要直接对硬件或某些特殊寄存器来操作,使用汇编语言比C语言效率更高。

C内联汇编:

__asm__

(

assembler template

 : output operands                 /* optional */

 : input operands                  /* optional */

: list of clobbered registers /* optional */

 );

这种格式由四部分组成,第一部分为汇编代码,即使用的指令;第二部分为汇编部分的输出(类似于返回值);第三部分为汇编语言的输入(类似于C语言中函数的参数);第四部分为被修改寄存器列表,告诉编译器调用该段代码时,哪些寄存器被修改。

 

 

 

 

 

 

 

 

 

 

 

 


对照本章的代码,__asm__()函数中间段为代码段,没有输出段和被修改的寄存器列表,只有一个输入寄存器 “r=ttb),这句代码的意思是为C语言传进来的ttb变量分配一个寄存器以便在汇编语言中使用(汇编代码中没有变量的概念,都是由寄存器来操作的),这里对应的寄存器为0%,至于具体使用哪个寄存器,由编译器自行分配。但这里注意:寄存器的编号要对应着后面的输出输入列表,本函数中没有输出段,只有一个输入寄存器,因此对应着0%,如果在输出寄存器处分配一个,那么ttb就对应1%了。

Makefile文件中我已增加了注释,唯一和前几章不同的是使用了链接脚本来进行链接。在大的程序中(以LINUX为例),大多采用链接脚本,这样使Makefile文件看起来简洁,易读性强,同时也可以清晰的从链接脚本看出整个程序的链接过程和运行地址。

Linux环境下的链接脚本采用的是GNU的标准链接,详见链接手册。这里我概括的说一下。

下面我们来分析一下本章的程序链接脚本。

SECTIONS {

  firtst    0x00000000 : { head.o init.o }    //定义第一段的运行地址为0,段中包括head.oinit.o

  second    0xB0004000 : AT(2048) { leds.o }  //定义第二段的加载地址为2048而运行地址为0xB0004000内容//leds.o

}

整个程序韦东山老师都为我们加了详细的注释,理解起来应该不困难,如果有疑问的欢迎大家和我交流。

执行make命令编译本程序,将.bin文件烧写到TQ2440后,可以看见led轮流点亮。对比上一章的实验现象可以发现,这次led变换的速度比上一章要快,这是由于开启了Cache

1.韦东山 《嵌入式LINUX应用开发完全手册》

2.三星  《S3C2440手册》

3.ARM  《ARM920T手册》

4.GNU  《GNU链接手册》

5.宋劲彬 《Linux C编程一站式学习》

阅读(8895) | 评论(9) | 转发(5) |
给主人留下些什么吧!~~

暴风lay2012-03-12 19:29:17

*(mmu_tlb_base+(virtuladdr>>20))=(physicaladdr&0xfff00000)|mmu_secdesc_wb;页表基址14~31和MVA31~20,低两位恒为00,为什么直接mmu_tlb_base+(virtuladdr>>20)相加,不用把MVA再向左移动两位,让低两位为00?

为什么不用再左移2位?因为*(mmu_tlb_base + (virtuladdr >> 20))
对于指针的加减操作会将(+1) 自动转换为 (+指针类型字节unsigned long 是四字节,so。。

暴风lay2012-03-10 22:01:12

对于S3C2440来说,它有27条地址线,再加上它有nGCS0---nGCS7,8个片选,实际上它的寻址能力可以达到1GB(0---0x4000 0000)。
是否应该是(0x0——0x3fffffff)?

machoe2010-12-09 10:30:41

whq20080808: 这个问题我也考虑过啊,但后面的代码 while (virtuladdr < 0xB4000000)
    {
        *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr &.....
virtuladdr每次+0x100000,怎么加也加不出01来啊,是右移20位。你再看一下,好好算算,肯定不会出01的。

whq200808082010-12-08 22:19:55

machoe: 这个问题问得挺好?按道理后两位应该是0.但是你看一下,前面,已经把mmu_tlb_base设置成了0x30000000,已经是32位的数了,而且后两位已经是0了,这里是一个取巧.....
这个问题我也考虑过啊,但后面的代码 while (virtuladdr < 0xB4000000)
    {
        *(mmu_tlb_base + (virtuladdr >> 20)) = (physicaladdr & 0xFFF00000) |  MMU_SECDESC_WB;
        virtuladdr += 0x100000;
        phy

machoe2010-12-08 21:40:02

whq20080808: *(mmu_tlb_base+(virtuladdr>>20))=(physicaladdr&0xfff00000)|mmu_secdesc_wb;页表基址14~31和MVA31~20,低两位恒为00,为什么直接mmu_tlb_base+(.....
这个问题问得挺好?按道理后两位应该是0.但是你看一下,前面,已经把mmu_tlb_base设置成了0x30000000,已经是32位的数了,而且后两位已经是0了,这里是一个取巧,你再移两位和不移效果是一样的。如果你把mmu_tlb_base设置成了0x30000011你要是不移的话,肯定就会出错了。