分类: LINUX
2014-04-26 18:50:52
原文地址:(六)、MMU的使用 作者:machoe
MMU(Memory Management Unit)内存管理单元,它负责虚拟地址到物理地址的映射,并提供硬件机制的内存条件检查。
以前的程序是很小的,可以全部装入内存中运行,而随着技术的发展,出现下面两个问题:
1. 有的程序很大,它所要求的运行空间大于了物理内存的容量;
2. 多任务系统中需要同时执行多个程序,这些程序的内存总数超过了物理内存的总量;
实际上,一个程序在运行之前,没有必要将整个程序全部装入内存,而是将当前要运行的程序装入内存,其余的部分需要用的时候再装入内存,而当内存容量耗尽时,将暂时不用的部分调入磁盘,这样就可以实现一个大的程序在小的内存空间中运行,也可以实现多任务程序同时运行。这就是虚拟存储器的实现过程。类似于我们在PC机上所说的虚拟内存。这一章涉及的概念很多,请大家反复阅读并查阅后面我提到的参考资料。
、地址的分类及相关概念l 对于S3C2440来说,它有27条地址线,再加上它有nGCS0---nGCS7,8个片选,实际上它的寻址能力可以达到1GB(0---0x4000 0000)。
l 虚拟存储器从逻辑上对内存的容量进行了扩充,S3C2440是32位CPU,因此,对应的虚拟地址范围是0----0xFFFFFFFF,这部分被称为虚拟地址空间,其中的某个地址被称为虚拟地址。
l 虚拟地址(VA,virtual Address)、变换后的虚拟地址(MVA,Modified Virtual Address)、物理地址(PA,Physical Address)。
l 页表(Page Table):在虚拟地址转换到物理地址时,通常是用表格来存储虚拟地址所对应的物理地址和访问权限,这个表格就称为页表,页表是由的一个个条目(也称为页表项,Entry)组成的。
在没有启动MMU的时候,所有的部件(CPU、Cache、MMU、外设)都使用的是物理地址,访问的是物理内存空间。启动MMU后,CPU发出的是VA,经过CP15协处理器的C13寄存器转化为MVA(硬件自动完成)供Cache、MMU使用,最后经过映射,变成PA,用PA来访问实际的设备。
CP15协处理器的作用比较复杂,我在这里就不一一列出了,建议大家参考手册(ARM920T P29页),如果疑问,希望大家和我交流。
、地址映射的方式S3C2440 CPU采用页表(Page Table)的方法进行地址映射,最多支持两级页表以对应不同的映射方式:
Ø 段(Section)的方式映射:只采用一级页表即可。段大小为1MB。
Ø 页(Page)的方式映射:就要采用二级页表。页大小分为三种:大页(Large Page)64KB,小页(Small Page)4KB,极小页(Tiny Page)1KB。
、地址映射的过程(以段的方式)S3C2440支持段、页两种方式的地址映射,这里我只介绍一下段的映射过程,页方式映射过程和段的方式类似,具体映射过程详见参考资料。下图为S3C2440的两种方式的地址转换过程图。
其中TTB base代表一级页表基址,将它写入CP15的C2寄存器(页表基址寄存器)即可。一级页表的地址必须是16K对齐的。上面提到了虚拟地址空间的大小为4GB,而段的大小为1MB,那么,如果按段的方式映射,一级页表中就会有4096个描述符(段描述符),存放的是物理段的起始地址;如果按页的方式映射一级页表里面存放的就是4096个二级页表项。
如下图所示,使用
MVA[31:20]来索引一级页表项,即得到一个描述符(段描述符),每个描述符占4个字节。如下图。
根据其最低两位来区别描述符的类型:
1) 0b00:无效。
2) 0b01:粗页表。共有256个条目,每个条目表示大小4KB,共1MB。
3) 0b10:段描述符。其中[31:20]为段基址,其低20位填充0就是一块1MB大小的物理地址空间的起始地址。利用MVA[19:0]来在这1MB的空间寻址。由描述符的[31:20]和MVA的[19:0]构成了虚拟地址所对应的物理地址。
以段的方式进行地址映射过程如下图:
A. TTB的[31:14]和MVA的[31:20]组成一个低两位为0的32位地址,MMU利用这个地址找到段描述符。
B. 取出段描述符的位[31:20]------即段基址,它和MVA的[19:0]组成一个32位的物理地址,这就是MVA对应的PA。
4) 0b11:细页表。共有1024个条目,每个1KB,总大小也为1MB。
MMU除了进行地址映射外,还有一项重要的工作就是:内存访问权限的检查。简单的说就是检查一块内存是否可读、可写。这也是带MMU的CPU的“高级”之处,大家都知道INTER的CPU有实地址模式和保护模式,其功能与此处类似,这样就可以使我们的代码更“安全”的运行。
权限检查是由CP15寄存器C3(域访问控制)、描述符的域(Domain)、CP15的C1寄存器的R/S/A位以级描述符的AP位联合作用。
其中CP15的C1寄存器A位决定是否进行对齐检查。无论MMU是否开启,都可以进行对齐检查。CPU读取指令前不进行对齐检查,以字节为单位访问时也不进行对齐检查。对齐检查在MMU的权限检查、地址映射之前进行。
S3C2440共有16个域,CP15的C3寄存器每两位对应一个域,用来表示这个域是否进行权限检查。具体如下图。
(以段描述符为例)
在段描述符中,有4位的Domain用来表示该段属于哪个域,如0b0000表示属于域0,如果此时域0的权限设置为00,即不可访问,这时如果访问该段内存就会产生“domain fault”;如果域访问权限设为01,则代表使用描述符中的AP位为进行检查。其中AP位要联合CP15的C1寄存器中的R/S位一起来设置。具体内容见手册,在此就不再赘述了。
TLB的使用CPU有了MMU的部件后,可以使我们从用户的角度上看,内存空间大了许多,同时也可以进行访问权限的检查,这样也安全了许多。但是,访问物理内存的过程也复杂了,如:使用一级页表进行映射时,对内存需进行两次访问,第一次访问一级页表以得到物理内存地址,第二次才是真正在物理内存上进行读、写数据;如果使用二级页表进行映射,那么需要三次访问才可以。这样就大大降低了CPU的使用效率。
出现了以上问题后,人们开发研究,后发现:程序中所用的指令、数据大多数都局限在一个很小的范围内,其中的地址、数据经常多次使用,这也称为“程序的局限性”。因此,我们可以把近期使用页表条目缓存在一个很小的、高速的存储器中,以避免每次都到主内存(物理内存)中去查找,这样就可以大幅度的提高CPU的性能,而这个存储器就是TLB(Translation Lookaside Buffers)。
这样CPU访问内存空间的过程就变成:CPU首先发出虚拟地址,MMU首先访问TLB,如果TLB中有所需要的虚拟地址描述符,则直接利用此描述符进行地址转换和检查;如果TLB中没有找到该描述符,MMU则访问页表找到该描述符进行地址转换和权限检查,并将此描述符填入TLB中以备下次使用,如果TLB已满,则利用round-robin算法找到一个条目,覆盖它。
用法注意:使用TLB时,一定要注意保证TLB中的内容与页表一致。因此,在启动MMU之前,使无效整个TLB,改变页表时,使无效所无改的虚拟地址所对应的TLB中的条目。
Cache和Write 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的指令Cache(ICache)和16KB的数据Cache(DCache)。
、Write BufferS3C2440 CPU内置了16-word的数据Buffer和4-address的地址Buffer。可以通过软件设置使其有效。其功能主要是和DCache配合使用。
、Cache的操作1. 清空:把Cache或者Write Buffer中已将修改的但并未写入内存中的数据写入内存。
2. 使无效:使其不能再使用,但不将数据同步到内存中。
、ICacheICache和DCache部分我只把需要注意的地方强调一下,具体的操作步骤详见手册。
系统刚上电的时候ICache默认为无效状态,内容为无效,功能是关闭的。向CP15的C1的第12位写1可以启动ICache,ICache一般在MMU启动后开启(MMU未启动也可以开启ICache)。应尽早开启ICache以提高性能。
、DCache和Write Buffer与ICache一样,刚上电时,DCache的内容也是无效,功能也是关闭的,Write Buffer内容也是无效的。向CP15的C1的第2位写1可以开启DCache,而Write Buffer是配合DCache使用的,自动开启、关闭,无需程序员手动设置。
与TLB类似,使用Cache时需要保证DCache、Write Buffer中的内容和内存中数据的一致性,需要遵循以下两原则:
1. 清空DCache,使得内存中的数据和DCache和Write Buffer中一致。
2. 使无效DCache,使CPU取指时重新读取内存。
使用方法注意:
1. 开启MMU之前,使无效ICache、DCache和Write Buffer。
2. 关闭MMU之前,清空ICache和DCache。
3. 如果代码有变,使无效ICache,以保证CPU取指时重新读取内存。
4. 使用DMA操作可以被Cache的内存时,将内存的数据发送出去时,要清空Cache;将内存的数据读入时,使无效Cache。
5. 改变页表的映射关系时,要重新考虑内存是否被Cache。
6. 开启Cache时要保持Cache和内存的数据一致性。
7. 对于I/O地址空间,不能用Cache,可以使用volatite关键字。
、协处理器的操作指令MRC 从协处理器获取数据传给ARM920T CPU的寄存器;
MCR 从ARM920T CPU寄存器获取数据传给协处理器;
{cond} 执行条件,省略时表示无条件执行;
p# 协处理器序号;
Rd ARM920T CPU寄存器;
cn和cm 协处理器的寄存器;
expression1、expression2 具体数值根据操作对象不同而不同,具体命令详见手册《ARM920T》第二章。
、存储器小结
存储器类 型 |
位于哪里 |
存储容量 |
半导体工艺 |
访问时间 |
如何访问 |
CPU 寄存器 |
CPU执行单元中 |
CPU一般有几个到几十个不等的寄存器,每个寄存器的大小由CPU的字长决定,因此,总大小一般为几十字长到几百字长不等。 |
“寄存器”这个名字就是一种数字电路的名字,它由一组触发器(Flip-flop)组成,每个触发器保存一个Bit的数据,可以做存取和移位等操作。计算机掉电时寄存器中保存的数据会丢失。 |
寄存器是访问速度最快的存储器,典型的访问时间是几纳秒。 |
使用哪个寄存器,如何使用寄存器,这些都是由指令决定的。 |
Cache |
和MMU一样位于CPU核中。 |
Cache通常分为几级,最典型的是两级Cache,一级Cache更靠近CPU执行单元,二级Cache更靠近物理内存,通常一级Cache有几十到几百KB,二级Cache有几百KB到几MB。 |
Cache和内存都是由RAM(RandomAccessMemory)组成的,可以根据地址随机访问,计算机掉电时RAM中保存的数据会丢失。不同的是,Cache通常由SRAM(StaticRAM,静态RAM)组成,而内存通常由DRAM(DynamicRAM,动态RAM)组成。DRAM电路比SRAM简单,存储容量可以做得更大,但DRAM的访问速度比SRAM慢。 |
典型的访问时间是几十纳秒。 |
Cache缓存最近访问过的内存数据,由于Cache的访问速度是内存的几十倍,所以有效利用Cache可以大大提高计算机的整体性能。一级Cache是这样工作的:CPU执行单元要访问内存时首先发出VA,Cache利用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。
由于32位CPU虚拟地址为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.o和init.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编程一站式学习》