本章目标:
n 了解虚拟地址和物理地址的关系
n 掌握如何通过设置MMU来控制虚拟地址到物理地址的转化
n 了解MMU的内存访问权限机制
n 了解TLB、Cache、Write buffer的原理,使用时的注意事项
n 通过实例深刻掌握上述要点
u S
内存管理单元MMU负责虚拟地址到物理地址的映射,并提供硬件机制的内存访问权限检查。现代的多用户多进程操作系统通过MMU使得各个用户都拥有自己独立的地址空间:地址映射功能使得各进程拥有“看起来”一样的地址空间,而内存访问权限的检查可以保护每个进程所用的内存不会被其他进程破坏。
S
1) 与ARM V4兼容的映射长度、域、访问权限检查机制。
2) 4种映射长度:段(1MB)、大页(64KB)、小页(4KB)、极小页(1KB)。
3) 对每个段都可以设置访问权限。
4) 大页、小页的每个子页(sub-page,即被映射页的1/4)都可以单独设置访问权限。
5) 硬件实现16个域。
6) 指令TLB(含64个条目)、数据TLB(含64个条目)。
7) 硬件访问页表(地址映射、权限检查由硬件自动进行)。
8) TLB中条目的替换采用round-robin算法(也称cyclic算法)。
9) 可以使无效整个TLB。
10) 可以单独使无效某个TLB条目。
11) 可以在TLB中锁定某个条目,指令TLB、数据TLB互相独立。
本章重点在于地址映射:页表的结构与建立、映射的过程。对于访问权限、TLB、Cache只作粗略介绍。
u 地址的分类
ARM CPU上的地址转换过程涉及3个概念:虚拟地址(VA,Virtual Address),变换后的虚拟地址(MVA,Modified Virtual Address),物理地址(PA,Physical Address)。
没有启动MMU时,CPU核、cache、MMU、外设等所有部件使用的都是物理地址。
启动MMU后,CPU核对外发出虚拟地址VA;VA被转换为MVA供cache、MMU使用,在这里MVA被转换为PA;最后使用PA读写实际设备。(S
1) CPU核看到的、用到的只是虚拟地址VA,至于VA如何最终落实到物理地址PA上,CPU 核是不理会的。
2) 而caches和MMU也是看不见VA的,它们利用由MVA转换得到PA。
3) 实际设备看不到VA、MVA,读写它们时使用的是物理地址PA。
MVA是除CPU核外的其他部分看见的虚拟地址。
如果VA <
if(VA <
MVA =VA | (PID << 25) //VA <
else
MVA = VA //VA >=
利用PID生成MVA的目的是为了减少切换进程时的代价:不使用MVA而直接使用VA 的话,当两个进程所用的虚拟地址空间(VA)有重叠时,在切换进程时为了把重叠的VA映射到不同的PA上去,需要重建页表、使无效caches和TLSS等,代价非常大。使用MVA后,进程切换就省事多了:假设两个进程1、2运行时的VA都是0-(
u 虚拟地址到物理地址的转换过程
将一个虚拟地址转换为物理地址,一般有两个办法:用一个确定的数学公式进行转换或用表格存储虚拟地址对应的物理地址。这类表格称为页表(Page table),页表由一个个条目(Entry)组成,每个条目存储了一段虚拟地址对应的物理地址及其访问权限,或者下一级页表。
在ARM CPU中使用第二种方法,S
大概的转换过程如下,请参考图7.3、图7.4(图7.3是通用的转换过程,图7.4是针对ARM CPU细化的转换过程):
(1)根据给定的虚拟地址找到一级页表中的条目;
(2)如果此条目是段描述符,则返回物理地址,转换结束;
(3)否则如果此条目是二级页表描述符,继续利用虚拟地址在二级页表中找到下一个条目;
(4)如果这第二个条目是页描述符,则返回物理地址,转换结束;
(5)其他情况出错。
图7.3/7.4中的“TTB base”代表一级页表的地址,将它写入协处理器CP15的寄存器C2(称为页表基址寄存器)即可。一级页表的地址必须是16K对齐的(位[14:0]为0)。
下面先介绍一级页表。32位CPU的虚拟地址空间达到4GB,一级页表中使用4096个描述符来表示这4GB空间——每个描述符对应1MB的虚拟地址,要么存储了它对应的1MB物理空间的起始地址,要么存储了下一级页表的地址。使用MVA[31:20]来索引一级页表,得到一个描述符,每个描述符占据4字节。
根据一级描述符的最低两位,可分为以下4种:
(1)0b00:无效
(2)0b01:粗页表(Coarse page table)
位[32:10]称为粗页表基址(Coarse page table base address),此描述符的低10位填充0后就是一个二级页表的物理地址。此二级页表含256个条目(所以大小为1KB),称为粗页表(Coarse page table)。其中每个条目表示大小为4KB的物理地址空间,所以一个粗页表表示1MB的物理地址空间。
(3)0b10:段(Section)
位[31:20]称为段基址(Section base),此描述符的低20位填充0后就是一块1MB物理地址空间的起始地址。MVA[19:0]用来在这1MB空间中寻址。所以,描述符的位[31:20]和MVA[19:0]就构成了这个虚拟地址MVA对应的物理地址。
(4)0b11:细页表(Fine page table)
位[31:12]称为细页表基址(Fine page table base address),此描述符的低12位填充0后,就是一个二级页有的物理地址。此二级页表含1024个条目(所以大小为4KB),称为细页表(Fine page table)其中每个条目表示大小为1KB的物理地址空间,所以,一个细页表表示1MB的物理地址空间。
以大页、小页或极小页进行地址映射时,需要用到两级页表,二级页表有粗页表和细页表两种。
根据二级描述符的最低两位,可分为以下4种情况:
(1)0b00:无效
(2)0b01:大页描述符。
位[31:16]称为大页基址(Large page base address),此描述符的低16位填充0后就是一块64KB物理地址空间的起始地址。粗页表中每个条目只能表示4KB的物理空间,如果大页描述符保存在粗页表中,则连续16个条目都保存同一个大页描述符。类似的,细页表中每个描述符只能表示1KB的物理空间,如果大页描述符保存在细页中,则连续64个条目都保存同一个大页描述符。
(3)0b10:小页描述符
位[31:12]称为小页基址,此描述符的低12位填充0后就是一块4KB物理地址空间的起始地址。粗页表中每个条目表示4KB的物理空间,如果小页描述符保存在粗页表中,则只需要用一个条目来保存一个小页描述符。类似的,细页表中每个条目只能表示1KB的物理空间,如果小页描述符保存在细页表中,则连续4个条目都保存同一个小页描述符。
(4)0b11:极小页描述符
位[31:10]称为极小页基址,此描述符的低10位填充0后就是一块1KB物理地址空间的起始地址。极小页描述符只能保存在细页表中,用一个条目来保存一个极小页描述符。
u MMU使用实例:地址映射
本开发板SDRAM的物理地址范围处于0x30000000 - 0x33FFFFFF,S
本章的实例中,将开启MMU,并将虚拟地址空间0xA0000000 - 0xA0100000映射到物理地址空间0x56000000 - 0x56100000上,这样,就可以通过操作地址0xA0000010、0xA0000014来达到驱动这4个LED的同样的效果。
另外,将虚拟地址空间0xB0000000 - 0xB3FFFFFF映射到物理地址空间0x30000000 - 0x33FFFFFF上,并在连接程序时将一部分代码的运行地址指定为0xB0004000(这个数值有些奇怪,看下去就会明白),看看能否使程序跳转到0xB0004000处执行。
本章程序只使用一级页表,以段的方式进行地址映射。32位CPU的虚拟地址空间达到4GB,一级页表中使用4096个描述符来表示这4GB空间(每个描述符对应1MB的虚拟地址),每个描述符占用4字节,所以一级页表占16KB。本实例使用SDRAM的开始16KB来存放一级页表,所以剩下的内存开始物理地址为0x30004000。
将程序代码分为两部分:第一部分的运行地址设为0,它用来初始化SDRAM、复制第二部分代码到SDRAM中(存放在0x30004000开始处)、设置页表、启动MMU,最后跳到SDRAM中(地址0xB0004000)去继续执行;第二部分的运行地址设为0xB0004000,它用来驱动LED。
根据上面的描述,程序流程图如下所示:
程序源代码有3个文件:head.S、init.c、leds.c
(1)、head.S代码详解
head.S文件如下:
@*************************************************************************
@ File:head.S
@ 功能:设置SDRAM,将第二部分代码复制到SDRAM,设置页表,启动MMU,
@ 然后跳到SDRAM继续执行
@*************************************************************************
.text
.global _start
_start:
ldr sp, =4096 @ 设置栈指针,以下都是C函数,调用前需要 @ 设好栈
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
bl memsetup @ 设置存储控制器以使用SDRAM
bl copy_2th_to_sdram @ 将第二部分代码复制到SDRAM
bl create_page_table @ 设置页表
bl mmu_init @ 启动MMU
ldr sp, =0xB4000000 @ 重设栈指针,指向SDRAM顶端(使用虚拟地址)
ldr pc, =0xB0004000 @ 跳到SDRAM中继续执行第二部分代码
halt_loop:
b halt_loop
head.S中调用的函数都在init.c中实现。
值得注意的是,在第15行开启MMU后,无论是CPU取指还是CPU读写数据,使用的都是虚拟地址。
在第14行设置页表时,在create_page_table函数中令head.S、init.c程序所在内存的虚拟地址和物理地址一样,这使得head.S和init.c中的代码在开启MMU后能够没有任何障碍地继续运行。
(2)init.c代码详解。
init.c中的disable_watch_dog、memsetup函数实现的功能在前面两章已经讨论过,不再重复,下面列出代码方便阅读。
/*
* init.c: 进行一些初始化,在Steppingstone中运行
* 它和head.S同属第一部分程序,此时MMU未开启,使用物理地址
*/
/* WATCHDOG寄存器 */
#define WTCON (*(volatile unsigned long *)0x53000000)
/* 存储控制器的寄存器起始地址 */
#define MEM_CTL_BASE 0x48000000
/*
* 关闭WATCHDOG,否则CPU会不断重启
*/
void disable_watch_dog(void)
{
WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可
}
/*
* 设置存储控制器以使用SDRAM
*/
void memsetup(void)
{
/* SDRAM 13个寄存器的值 */
unsigned long const mem_cfg_val[]={ 0x22011110, //BWSCON
0x00000700, //BANKCON0
0x00000700, //BANKCON1
0x00000700, //BANKCON2
0x00000700, //BANKCON3
0x00000700, //BANKCON4
0x00000700, //BANKCON5
0x00018005, //BANKCON6
0x00018005, //BANKCON7
0x
0x000000B1, //BANKSIZE
0x00000030, //MRSRB6
0x00000030, //MRSRB7
};
int i = 0;
volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;
for(; i < 13; i++)
p[i] = mem_cfg_val[i];
}
copy_2th_to_sdram函数用来将第二部分代码(即由leds.c编译得来的代码)从Steppingstone中复制到SDRAM中,在连接程序时,第二部分代码的加载地址被指定为2048,重定位地址为0xB0004000,所以系统从NAND Flash启动后,第二部分代码就存储在Steppingstone中地址2048之后,需要把它复制到0x30004000处(此时尚未开启MMU,虚拟地址0xB0004000对应的物理地址在后面设为0x30004000)。Steppingstone总大小为4KB,不妨把地址2048之后的所有数据复制到SDRAM中,所以源数据的结束地址为4096。
copy_2th_to_sdram函数的代码如下:
/*
* 将第二部分代码复制到SDRAM
*/
void copy_2th_to_sdram(void)
{
unsigned int *pdwSrc = (unsigned int *)2048;
unsigned int *pdwDest = (unsigned int *)0x30004000;
while (pdwSrc < (unsigned int *)4096)
{
*pdwDest = *pdwSrc;
pdwDest++;
pdwSrc++;
}
}