最近在学习bootloader制作,学到内存管理单元(MMU)时,对其原理不甚了解,遂在网上搜索了部分相关文章,读到该篇时,发现其对MMU中虚拟地址与物理地址之间的映射讲解比较清晰,易于理解,但在访问权限方面的介绍不是很容易理解。故本文只转载了文章关于地址映射的内容,如需要学习MMU中访问权限相关的知识,可参考原文链接:S3C2410 MMU技术精解
一、MMU的产生
许多年以前,当人们还在使用DOS或是更古老的操作系统的时候,计算机的内存还非常小,一般都是以K为单位进行计算,相应的,当时的程序规模也不大,所以内存容量虽然小,但还是可以容纳当时的程序。但随着图形界面的兴起还有用户需求的不断增大,应用程序的规模也随之膨胀起来,终于一个难题出现在程序员的面前,那就是应用程序太大以至于内存容纳不下该程序,通常解决的办法是把程序分割成许多称为覆盖块(overlay)的片段。覆盖块0首先运行,结束时它将调用另一个覆盖块。虽然覆盖块的交换是由OS完成的,但是必须先由程序员把程序先进行分割,这是一个费时费力的工作,而且相当枯燥。人们必须找到更好的办法从根本上解决这个问题。不久人们找到了一个办法,这就是虚拟存储器(virtual memory).虚拟存储器的基本思想是程序,数据,堆栈的总的大小可以超过物理存储器的大小,操作系统把当前使用的部分保留在内存中,而把其他未被使用的部分保存在磁盘上比如对一个16MB的程序和一个内存只有4MB的机器,OS通过选择,可以决定各个时刻将哪4M的内容保留在内存中,并在需要时在内存和磁盘间交换程序片段,这样就可以把这个16M的程序运行在一个只具有4M内存的机器上了。而这个16M的程序在运行前不必由程序员进行分割。
任何时候,计算机上都存在一个程序能够产生的地址集合,我们称之为地址范围。这个范围的大小由CPU的位数决定,例如一个32位的CPU,它的地址范围是0~0xFFFFFFFF (4G)而对于一个64位的CPU,它的地址范围为0~0xFFFFFFFFFFFFFFFF (64T),这个范围就是我们的程序能够产生的地址范围,我们把这个地址范围称为虚拟地址空间,该空间中的某一个地址我们称之为虚拟地址。与虚拟地址空间和虚拟地址相对应的则是物理地址空间和物理地址,大多数时候我们的系统所具备的物理地址空间只是虚拟地址空间的一个子集,这里举一个最简单的例子直观地说明这两者,对于一台内存为256MB的32bit x86主机来说,它的虚拟地址空间范围是0~0xFFFFFFFF(4G),而物理地址空间范围是0x000000000~0x0FFFFFFF(256MB)。
在没有使用虚拟存储器的机器上,虚拟地址被直接送到内存总线上,使具有相同地址的物理存储器被读写。而在使用了虚拟存储器的情况下,虚拟地址不是被直接送到内存地址总线上,而是送到内存管理单元——MMU(主角终于出现了)。它由一个或一组芯片组成,一般存在与协处理器中,其功能是把虚拟地址映射为物理地址。
二、MMU工作过程
大多数使用虚拟存储器的系统都使用一种称为分页(paging)的技术。虚拟地址空间划分成称为页(page)的单位,而相应的物理地址空间也被进行划分,单位是页框(frame).页和页框的大小必须相同。接下来配合图片我以一个例子说明页与页框之间在MMU的调度下是如何进行映射的:
在这个例子中我们有一台可以生成16位地址的机器,它的虚拟地址范围从0x0000~0xFFFF(64K),而这台机器只有32K的物理地址,因此他可以运行64K的程序,但该程序不能一次性调入内存运行。这台机器必须有一个达到可以存放64K程序的外部存储器(例如磁盘或是FLASH)以保证程序片段在需要时可以被调用。在这个例子中,页的大小为4K,页框大小与页相同(这点是必须保证的,内存和外围存储器之间的传输总是以页为单位的),对应64K的虚拟地址和32K的物理存储器,他们分别包含了16个页和8个页框。
我们先根据上图解释一下分页后要用到的几个术语,在上面我们已经接触了页和页框,上图中绿色部分是物理空间,其中每一格表示一个物理页框。橘黄色部分是虚拟空间,每一格表示一个页,它由两部分组成,分别是Frame Index(页框索引)和位p(present 存在位),Frame Index的意义很明显,它指出本页是往哪个物理页框进行映射的,位p的意义则是指出本页的映射是否有效,如上图,当某个页并没有被映射时(或称映射无效,Frame Index部分为X),该位为0,映射有效则该位为1。
我们执行下面这些指令(本例子的指令不针对任何特定机型,都是伪指令)
例1:
MOVE REG, 0 //将0号地址的值传递进寄存器REG.
虚拟地址0将被送往MMU,MMU看到该虚地址落在页0范围内(页0范围是0到4095),从上图我们看到页0所对应(映射)的页框为2(页框2的地址范围是8192到12287),因此MMU将该虚拟地址转化为物理地址8192,并把地址8192送到地址总线上。内存对MMU的映射一无所知,它只看到一个对地址8192的读请求并执行它。MMU从而把0到4096的虚拟地址映射到8192到12287的物理地址。
例2:
MOVE REG, 8192
被转换为
MOVE REG, 24576
因为虚拟地址8192在页2中,而页2被映射到页框6(物理地址从24576到28671)
例3:
MOVE REG, 20500
被转换为
MOVE REG, 12308
虚拟地址20500在虚页5(虚拟地址范围是20480到24575)距开头20个字节处,虚页5映射到页框3(页框3的地址范围是 12288到16383),于是被映射到物理地址12288+20=12308。
通过适当的设置MMU,可以把16个虚页映射到8个页框中的任何一个,但是这个方法并没有有效的解决虚拟地址空间比物理地址空间大的问题。从上图中我们可以看到,我们只有8个页框(物理地址),但我们有16个页(虚拟地址),所以我们只能把16个页中的8个进行有效的映射。我们看看例4会发生什么情况
例4:
MOV REG, 32780
虚拟地址32780落在页8的范围内,从上图中我们看到页8没有被有效的进行映射(该页被打上X),这时又会发生什么?MMU注意到这个页没有被映射,于是通知CPU发生一个缺页故障(page fault).这种情况下操作系统必须处理这个页故障,它必须从8个物理页框中找到1个当前很少被使用的页框并把该页框的内容写入外围存储器(这个动作被称为page copy),随后把需要引用的页(例4中是页8)映射到刚才释放的页框中(这个动作称为修改映射关系),然后重新执行产生故障的指令(MOV REG,32780)。假设操作系统决定释放页框1,那么它将把虚页8装入物理地址的4-8K,并做两处修改:首先标记虚页1未被映射(原来虚页1是被映射到页框1的),以使以后任何对虚拟地址4K到8K的访问都引起页故障而使操作系统做出适当的动作(这个动作正是我们现在在讨论的),其次他把虚页8对应的页框号由X变为1,因此重新执行MOV REG,32780时,MMU将把32780映射为4108。
我们大致了解了MMU在我们的机器中扮演了什么角色以及它基本的工作内容是什么,下面我们将举例子说明它究竟是如何工作的(注意,本例中的MMU并无针对某种特定的机型,它是所有MMU工作的一个抽象)。
首先明确一点,MMU的主要工作只有一个,就是把虚拟地址映射到物理地址。
我们已经知道,大多数使用虚拟存储器的系统都使用一种称为分页(paging)的技术,就像我们刚才所举的例子,虚拟地址空间被分成大小相同的一组页,每个页有一个用来标示它的页号(这个页号一般是它在该组中的索引,这点和C/C++中的数组相似)。在上面的例子中0~4K的页号为0,4~8K的页号为1,8~12K的页号为2,以此类推。而虚拟地址(注意:是一个确定的地址,不是一个空间)被MMU分为2个部分,第一部分是页号索引(page Index),第二部分则是相对该页首地址的偏移量(offset). 。我们还是以刚才那个16位机器结合下图进行一个实例说明,该实例中,虚拟地址8196被送进MMU,MMU把它映射成物理地址。16位的CPU总共能产生的地址范围是0~64K,按每页4K的大小计算,该空间必须被分成16个页。而我们的虚拟地址第一部分所能够表达的范围也必须等于16(这样才能索引到该页组中的每一个页),也就是说这个部分至少需要4个bit。一个页的大小是4K(4096),也就是说偏移部分必须使用12个bit来表示(2^12=4096,这样才能访问到一个页中的所有地址),8196的二进制码如下图所示:
该地址的页号索引为0010(二进制码),既索引的页为页2,第二部分为000000000100(二进制),偏移量为4。页2中的页框号为6(页2映射在页框6,见上图),我们看到页框6的物理地址是24~28K。于是MMU计算出虚拟地址8196应该被映射成物理地址24580(页框首地址+偏移量=24576+4=24580)。同样的,若我们对虚拟地址1026进行读取,1026的二进制码为0000010000000010,page index="0000"=0,offset=010000000010=1026。页号为0,该页映射的页框号为2,页框2的物理地址范围是8192~12287,故MMU将虚拟地址1026映射为物理地址9218(页框首地址+偏移量=8192+1026=9218)。以上就是MMU的工作过程。
三、S3C24XX的MMU工作过程
下面我们针对s3c2410的MMU进行讲解。
S3c2410总共有4种内存映射方式,分别是:
1.Fault (无映射)
2.Coarse Page (粗表)
3.Section (段)
4.Fine Page (细表)
我们以Section(段)进行说明。
ARM920T是一个32bit的CPU,它的虚拟地址空间为2^32=4G。而在Section模式,这4G的虚拟空间被分成一个一个称为段(Section)的单位(与我们上面讲的页在本质上其实是一致的),每个段的长度是1M (而我们之前所使用的页的长度是4K)。4G的虚拟内存总共可以被分成4096个段(1M*4096=4G),因此我们必须用4096个描述符来对这组段进行描述,每个描述符占用4个Byte,故这组描述符的大小为16KB (4byte*4096),这4096个描述符构为一个表格,我们称其为Tralaton Table.
上图是描述符的结构
Section base address:段基地址(相当于页框号首地址)
AP: 访问控制位Access Permission
Domain: 访问控制寄存器的索引。Domain与AP配合使用,对访问权限进行检查
C:当C被置1时为write-through (WT)模式
B: 当B被置1时为write-back (WB)模式(C,B两个位在同一时刻只能有一个被置1)
(注意:笔者认为此处关于C位和B位的讲解并不妥,C位应当是cache的使能位,B位为buffer的使能位,当进行内存虚拟地址和物理地址之间的映射时,C位和B位一般置1)
下面是s3c2410内存映射后的一个示意图:
我的s3c2410上配置的SDRAM大小为64M,该SDRAM的物理地址范围是0x3000 0000~0x33FF FFFF(属于Bank 6),由于1个Section的大小是1M,所以该物理空间可以被分成64个物理段(页框).
在Section模式下,送进MMU的虚拟地址被分为两部分(这点和我们上面举的例子是一样的),这两部分为 Descriptor Index(相当于上面例子的Page Index)和 Offset,descript index长度为12bit(2^12=4096,从这个关系式你能看出什么?),Offset长度为20bit(2^20=1M,你又能看出什么?)。观察一下一个描述符(Descriptor)中的Section Base Address部分,它长度为12 bit,里面的值是该虚拟段(页)映射成的物理段(页框)的物理地址前12bit,由于每一个物理段的长度都是1M,所以物理段首地址的后20bit总是为0x00000(每个Section都是以1M对齐),确定一个物理地址的方法是 物理页框基地址+虚拟地址中的偏移部分=Section Base Address<<20+Offset ,呵呵,可能你有点糊涂了,还是举一个实际例子说明吧。
假设现在执行指令MOV REG, 0x30000012,虚拟地址的二进制码为00110000 00000000 00000000 00010010,前12位是Descriptor Index= 00110000 0000=768,故在Translation Table里面找到第768号描述符,该描述的Section Base Address="0x0300",也就是说描述符所描述的虚拟段(页)所映射的物理段(页框)的首地址为0x3000 0000(物理段(页框)的基地址=Section Base Address左移20bit=0x0300<<20=0x3000 0000),而Offset=000000 00000000 00010010=0x12,故虚拟地址0x30000012映射成的物理地址=0x3000 0000+0x12=0x3000 0012(物理页框基地址+虚拟地址中的偏移)。你可能会问怎么这个虚拟地址和映射后的物理地址一样?这是由我们定义的映射规则所决定的。在这个例子中我们定义的映射规则是把虚拟地址映射成和他相等的物理地址。我们这样书写映射关系的代码:
void mem_mapping_linera(void) { unsigned long descriptor_index, section_base, sdram_base, sdram_size; sdram_base=0x30000000; sdram_size=0x 4000000; for (section _base= sdram_base, descriptor_index = section _base>>20; section _base < sdram_base+ sdram_size; descriptor_index+=1;section _base +=0x100000) { *(mmu_tlb_base + (descriptor_index)) = (section _base>>20) | MMU_OTHER_SECDESC; } }上面的这段段代码把虚拟空间0x3000 0000~0x33FF FFFF映射到物理空间0x3000 0000~0x33FF FFFF,由于虚拟空间与物理空间空间相吻合,所以虚拟地址与他们各自对应的物理地址在值上是一致的。当初始完Translation Table之后,记得要把Translation Table的首地址(第0号描述符的地址)加载进协处理器CP15的Control Register2(2号控制寄存器)中,该控制寄存器的名称叫做Translation table base (TTB) register。
---------------------------------------------------------------分割线-------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
四、MMU裸机内存映射代码
/******************************************************************* *版权所有(C)2015,ZJU * *文件名称:mmu.c *内容摘要:关于MMU的配置 *其它说明:开发板型号: TX2440 *当前版本:V1.0 *作 者:Frank *完成日期:2015.11.30 * *******************************************************************/ //重定义数据类型 typedef unsigned char UINT8; typedef unsigned int UINT32; typedef signed int INT32; typedef unsigned long UINT64; //MMU使用段式转换的一些宏定义 #define MMU_FULL_ACCESS (3 << 10) //访问权限 #define MMU_DOMAIN (0 << 5) //属于哪个域 #define MMU_SPECIAL (1 << 4) //第4位必须是1 #define MMU_CACHEABLE (1 << 3) //cacheable,缓存使能选择位 #define MMU_BUFFERABLE (1 << 2) //bufferable,缓冲使能选择位 #define MMU_SECTION (2 << 0) //段描述符(只有为2才表示采用段式转换) #define MMU_SECDESC (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | MMU_SECTION) #define MMU_SECDESC_WB (MMU_FULL_ACCESS | MMU_DOMAIN | MMU_SPECIAL | MMU_CACHEABLE | MMU_BUFFERABLE | MMU_SECTION) /******************************************************************* *函数名称:create_page_table() *功能描述:MMU进行虚拟地址和物理地址之间的映射 *输入参数: *输出参数: *返 回 值: *其他说明: *******************************************************************/ void create_page_table(void) { //MMU进行虚拟地址和GPIO物理地址之间的映射 UINT64 *ttb = (UINT64 *)0x30000000; //定义页表转换基地址为 0x3000 0000 UINT64 vaddr, paddr; vaddr = 0xA0000000; //定义虚拟地址vaddr的基地址为 0xA000 0000 paddr = 0x56000000; //定义物理地址paddr的基地址为 0x5600 0000 *(ttb + (vaddr >> 20)) = (paddr & 0xfff00000) | MMU_SECDESC; //MMU进行虚拟地址和Memory物理地址之间的映射 vaddr = 0x30000000; paddr = 0x30000000; while(vaddr < 0x34000000) //对Memory进行64M映射 { *(ttb + (vaddr >> 20)) = (paddr & 0xfff00000) | MMU_SECDESC_WB; vaddr += 0x100000; paddr += 0x100000; } } /******************************************************************* *函数名称:mmu_enable() *功能描述:设置TTB,设置权限检查,使能MMU *输入参数: *输出参数: *返 回 值: *其他说明: *******************************************************************/ void mmu_enable() { __asm__( /*设置TTB(将TTB写入CP15的C2寄存器)*/ "ldr r0, =0x30000000\n" "mcr p15, 0, r0, c2, c0, 0\n" /*不进行权限检查*/ "mvn r0, #0\n" "mcr p15, 0, r0, c3, c0, 0\n" /*使能MMU*/ "mrc p15, 0, r0, c1, c0, 0\n" "orr r0, r0, #0x0001\n" "mcr p15, 0, r0, c1, c0, 0\n" : : ); } /******************************************************************* *函数名称:mmu_init() *功能描述:mmu初始化 *输入参数: *输出参数: *返 回 值: *其他说明: *******************************************************************/ void mmu_init() { create_page_table(); //对虚拟地址和物理地址进行映射 mmu_enable(); //mmu使能 }