接着上一节的SDRAM,这一节我们来学习下MMU。
内存管理单元(Memory Management Unit)简称MMU,主要负责虚拟地址到物理地址的映射和权限检查。本文主要讲物理地址映射过程
S3C2440的MMU提供如下特性:
- 4种映射长度:段(1MB)、大页(64kB)、小页(4kB)、极小页(1kB)
- 可无效整个TLB
- 可以单独无效某一个TLB条目
等特性
在讲具体的地址映射过程过程之前,有必要说明三种地址的概念:
- VA(Virtual address)虚拟地址,这个地址是CPU启动MMU后CPU看到的地址
- MVA(Modified Virtual address)变化后虚拟地址,这个地址是提供给MMU和CACHE使用的
- PA(Physical Address)物理地址,对应最终发生读写的地址
MVA是除CPU核外的其他部分看见的虚拟地址,VA与MVA之间的变换关系如下:
如果VA<32M,需要使用进程标识PID(通过读CP15的C13获得)来装换为MVA,当然这个由硬件自动完成
if (VA < 32M) then
MVA = VA | (PID << 25)
else
MVA = VA
虚拟地址到物理地址的转换
一般情况下有两种方式:用一个确定的数学公式进行转换或者用表格的形式,这类表格就被称为页表,页表由一个个条目(描述符)组成,每一个条目存储了一段虚拟地址对应的物理地址以及访问权限,或者下一级页表的地址。
在ARM CPU使用第二种方法,在S3C2440中,最多会用到两级页表。以段(section)的方式映射只要用到一级页表,以页的方式映射的话则需要用到两级页表,下面大致描述下转换过程:
- 根据给定的虚拟地址找到一级页表中的条目
- 如果此条目是段描述符,则返回物理地址,转换结束
- 否则如果此条目是二级页表描述符,继续利用虚拟地址在此二级页表中寻找下一个条目
- 如果这第二个条目是页描述符,则返回物理地址,转换结束
上图描述的是段映射方式地址转换的过程,TTB base代表一级页表的地址,它存储在协处理器CP15的寄存器C2(页表基址寄存器),一级页表的地址必须是16K对应的。
32位CPU的虚拟地址空间为4GB,一级页表中使用了4096个描述符,每个描述符对应1MB的虚拟地址。描述符中要么存储了它对应的物理地址,要么存储了下一级页表的页表基地址。
本文主要讲述段映射方式,当以段的方式进行映射,虚拟地址MVA到物理地址PA的转换过程如下:
- 页表基址寄存器[31:14]和MVA[31:20]组成一个低两位位0的32位地址,MMU利用这个地址找到段描述符
- 取出段描述符的位[31:20]—段基址,它和MVA[19:0]组成一个32位的物理地址—这就是MVA对应的PA
下面简单描述下MMU的权限检查功能:指一块内存是否允许读,是否允许写。具体由CP15寄存器C3(域访问寄存器)、描述符的域、CP15寄存器C1的R/S/A位以及描述符的AP位联合起作用。
CP15寄存器C1中A位表示是否需要对地址进行对齐检查
内存的访问权限检查可以概括位一下两点:
- “域”决定是否需要对某块内存进行权限检查
- “AP”绝对如何对某块内存做权限检查
CP15寄存器C3中共32位,每两位分为一个域,所以共16个域
每个域的两位有四种可能的值分别代表:
AP和R/S的组合决定如果对地址进行权限检查
段描述符的格式如下:
下面将以具体的代码来讲解下MMU是如何实现地址映射的,首先分析下链接脚本:
SECTIONS {
firtst 0x00000000 : { head.o init.o }
second 0xB0004000 : AT(2048) { leds.o }
}
可以看出head.o init.o 加载地址和运行的虚拟地址都是0,leds的加载地址是2048处,运行地
址为0xB0004000,所以必须将虚拟地址OxB0004000映射到相应的物理地址才能正常运行。
Head.s的片段如下
_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中继续执行第二部分代码
其中
/*
* 将第二部分代码复制到SDRAM
*/
void copy_2th_to_sdram(void)
{
unsigned int *pdwSrc = (unsigned int *)2048;
unsigned int *pdwDest = (unsigned int *)0x30004000;
while (pdwSrc < (unsigned int *)4096)/*拷贝4K字节*/
{
*pdwDest = *pdwSrc;
pdwDest++;
pdwSrc++;
}
}
在MMU开启之前程序运行在实际的物理地址,copy_2th_to_sdram将物理地址偏移2048处开始的4k
字节拷贝到0x30004000,我们知道s3c2440 sdram物理地址是从0x30000000开始的。
从0x30000000开始到0x30004000共16K字节,用于段映射的页表,每条段描述符长度为4个字节代
表物理1M空间,16K字节共4096条记录,刚好对应32位处理器的4G地址虚拟地址空间。
下面重点说下,创建段页表描述符条目的过程
/*
* 设置页表
*/
void create_page_table(void)
{
/*
* 用于段描述符的一些宏定义
*/
#define MMU_FULL_ACCESS (3 << 10) /* 访问权限 */
#define MMU_DOMAIN (0 << 5) /* 属于哪个域 */
#define MMU_SPECIAL (1 << 4) /* 必须是1 */
#define MMU_CACHEABLE (1 << 3) /* cacheable */
#define MMU_BUFFERABLE (1 << 2) /* bufferable */
#define MMU_SECTION (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)
#define MMU_SECTION_SIZE 0x00100000
unsigned long virtuladdr, physicaladdr;
unsigned long *mmu_tlb_base = (unsigned long *)0x30000000;
/*
* Steppingstone的起始物理地址为0,第一部分程序的起始运行地址也是0,
* 为了在开启MMU后仍能运行第一部分的程序,
* 将0~1M的虚拟地址映射到同样的物理地址
*/
virtuladdr = 0;
physicaladdr = 0;
*(mmu_tlb_base + (virtuladdr >> 20)) = \
(physicaladdr & 0xFFF00000) | MMU_SECDESC_WB;
/*
* 0x56000000是GPIO寄存器的起始物理地址,
* GPBCON和GPBDAT这两个寄存器的物理地址0x56000050、0x56000054,
* 为了在第二部分程序中能以地址0xA0000050、0xA0000054来操作GPFCON、GPFDAT,
* 把从0xA0000000开始的1M虚拟地址空间映射到从0x56000000开始的1M物理地址空间
*/
virtuladdr = 0xA0000000;
physicaladdr = 0x56000000;
*(mmu_tlb_base + (virtuladdr >> 20)) = \
(physicaladdr & 0xFFF00000) | MMU_SECDESC;
/*
* SDRAM的物理地址范围是0x30000000~0x33FFFFFF,
* 将虚拟地址0xB0000000~0xB3FFFFFF映射到物理地址0x30000000~0x33FFFFFF上,
* 总共64M,涉及64个段描述符
*/
virtuladdr = 0xB0000000;
physicaladdr = 0x30000000;
while (virtuladdr < 0xB4000000)
{
*(mmu_tlb_base + (virtuladdr >> 20)) = \
(physicaladdr & 0xFFF00000) | MMU_SECDESC_WB;
virtuladdr += 0x100000;
physicaladdr += 0x100000;
}
}