linux内核是如何实现分页机制的

注意:请使用谷歌浏览器阅读(IE浏览器排版混乱)


【摘要】

 本文主要介绍linux源代码中,是如何实现分页机制的。内存分页管理是arm架构中MMU的重要组成部分,理解它大有裨益。本文重点不在讲解理论知识,旨在通过源码的剖析,带你走进linux内存管理的世界。
【写作原因】
 主要原因:后续介绍cache和缺页异常时都将以本文为根基进行展开.为以后介绍起来方便,专门写一篇文章.
【正文分析】
本文以linux3.18.20,armv7为例介绍.
一. 何时会创建页表?
首先谈一下用户常用的获取内存方式,以下几种常用的内存申请为例:
1) 内核态:kmalloc , kmalloc的内存从何而来?
kmalloc是从slab缓存区中申请的内存,而slab缓存区的内存实际上是从操作系统的低端内存申请到的。kmalloc过程其实并未创建页表,以后也不用再创建页表,这是因为系统启动过程已经通过map_lowmem->create_mappping为所有的低端内存创建好了页表。后文会对这一过程进一步描述。
2) 用户态:malloc
malloc:malloc过程其实大部分人都很了解,它主要通过brk或mmap拓展进程的虚拟地址空间,malloc过程本身并未申请物理内存,也未创建页表。可以理解为malloc申请了一段用户态的虚拟地址区间,然后当用户真正使用这段地址时,会触发arm的缺页异常,缺页异常处理函数handle_pte_fault会通过set_pte_at->set_pte_ext->cpu_v7_set_pte_ext 一系列调用 来实现页表创建。事实上,malloc申请的内存,来源也和kmalloc一样,也是来自低端内存。不过与kmaollc不同的是,缺页异常中对这段低端内存对应的物理内存,又做了二次映射,即重新创建了页表。发现了么,同一物理地址其实可以对应多个虚拟地址进行映射,即存在多个页表项,这其实也是多进程的实现基础,但同时,使用过程中也可能引入cache问题,可以参考我的另一篇介绍cache的博文.
ps:可以通过/proc/$pid/smaps或/proc/$pid/maps查看一个进程的地址空间.
3)高端内存:vmalloc
vmalloc实际上是为[VMALLOC_START,VMALLOC_END]地址区间创建页表.注意,我们申请到的低端内存对应的物理内存,不能再映射到其他的低端地址,却可以通过vmap映射到高端内存区[VMALLOC_START,VMALLOC_END].
4)io地址的映射:ioremap
驱动中经常使用ioremap 将io地址映射到指定区间,实际上,这一过程的本质也是创建页表.
以上几种申请内存的方式,最终都是通过cpu_v7_set_pte_ext配置页表项。

二  如何创建页表:

通过上面分析,我们了解了几种创建页表的时机,其实无论是kmalloc还是malloc,最后申请到的内存,都是通过cpu_v7_set_pte_ext来创建页表的。万法同源,我们以低端内存的映射为例,详细介绍下页表的创建.

1 初始页全局目录创建过程
.globl  swapper_pg_dir
.epu    swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE;//页全局描述符起始虚拟地址:0x80004000
1)#grep "TEXT_OFFSET" ./ -r --exclude-dir=" *debug* "
 -DTEXT_OFFSET:=0x0008000  --内核代码段在内核线性地址空间(0x80000000)中的偏移;
PAGE_OFFSET:物理地址从此开始映射,内核空间虚拟地址即线性地址的起始地址.
经常定义为0xc0000000或0x80000000,如下例子:
#define PAGE_OFFSET  CONFIG_PAGE_OFFSET
- - 关键地址,用户态和内核态虚拟地址分界点,有的平台使用0xc0000000 (arch/arm/include/asm/memory.h)
#define CONFIG_PAGE_OFFSET  0x80000000  (autoconf.h) 
- - 关键地址,用户态和内核态虚拟地址分界点,有的平台使用0xc0000000
2) 物理地址的起始地址 PHYS_OFFSET(如0x200000).该地址映射到线性地址起始地址处,即 PAGE_OFFSET .
  PHYS_OFFSET+TEXT_OFFSET- PG_DIR_SIZE=0x204000
#define KERNEL_RAM_VADDR   (PAGE_OFFSET + TEXT_OFFSET)    (arch/arm/kernel/head.s)
 - - 0x80008000内核代码段起始虚拟地址,uImage代码段从此开始;页全局描述符地址,据此算出.
#define PG_DIR_SIZE=0x4000     (arch/arm/kernel/head.s)

ENTRY(stext)
bl __create_page_tables
ENDPROC(stext)
__create_page_tables
pgtbl  r4,r8     /* r8=PHYS_OFFSET;r4=PHYS_OFFSET+TEXT_OFFSET-PG_DIR_SIZE 如:0x204000页全局目录起始物理地址 */
__create_page_tables创建初始页表,物理地址起始地址(如0x200000)映射到内核线性地址空间的起始地址(如0x80000000)
线性地址0x80004000(对应物理地址0x204000)上保存了页全局目录起始地址.

src/scripts/Makefile.lib :quiet_cmd_uimage= -a 0x208000 -e0x208000 其中0x200000是物理地址起始地址;

此处表明在uImage头中指定uImage代码段需要加载到的物理地址,boot中将uImage去头后读取到此地址,

内核态下0x208000映射到线性地址空间0x80008000即代码段所在物理地址对应的线性地址;

.text: 0x80008000-0x803cfea0 (3872kB--uImage代码段大小)


2 创建页表过程
1)为低端内存创建页表:
setup_arch->paging_init->map_lowmem()->create_mapping
2)为io内存映射创建页表,ahb、apb等地址的映射。iotable_init->create_mapping
可见无论低端内存还是io地址,系统为他们创建页表项都是通过create_mapping
3 create_mapping介绍:
static void __init create_mapping(struct map_desc *md)
{
	unsigned long addr, length, end;
	phys_addr_t phys;
	const struct mem_type *type;
	pgd_t *pgd;

	if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
		printk(KERN_WARNING "BUG: not creating mapping for 0x%08llx"
		       " at 0x%08lx in user region\n",
		       (long long)__pfn_to_phys((u64)md->pfn), md->virtual);
		return;
	}

	if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
	    md->virtual >= PAGE_OFFSET &&
	    (md->virtual < VMALLOC_START || md->virtual >= VMALLOC_END)) {
		printk(KERN_WARNING "BUG: mapping for 0x%08llx"
		       " at 0x%08lx out of vmalloc space\n",
		       (long long)__pfn_to_phys((u64)md->pfn), md->virtual);
	}
	/* mem_type中保存了页表属性和页中间目录的属性 */
	type = &mem_types[md->type];
	addr = md->virtual & PAGE_MASK;
        /*phys对应物理地址,本函数实际上就是把phys映射到addr*/
        phys = __pfn_to_phys(md->pfn);
	length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));

	if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {
		printk(KERN_WARNING "BUG: map for 0x%08llx at 0x%08lx can not "
		       "be mapped using pages, ignoring.\n",
		       (long long)__pfn_to_phys(md->pfn), addr);
		return;
	}
	/* 页表创建过程如下:*/
	pgd = pgd_offset_k(addr);
	end = addr + length;
	do {
		unsigned long next = pgd_addr_end(addr, end);

		alloc_init_pud(pgd, addr, next, phys, type);

		phys += next - addr;
		addr = next;
	} while (pgd++, addr != end);
}
注意:低端内存的页表属性已经在mem_types][MT_MEMORY_RW]中定义好。如果想要通过kamlloc获取到的内存是只读的,可以在此修改。
4 页表创建过程
static void __init create_mapping(struct map_desc *md)
{
/*
1 init_task进程的页全局目录地址:swapper_pg_dir
以后每个进程都从这里拷贝页全局目录到各自的pgd里dup_mm->pgd_alloc中实现,cpu_switch_mm中切换。
*/
	pgd = pgd_offset_k(addr);
	end = addr + length;
	do {
		unsigned long next = pgd_addr_end(addr, end);
		/* 每个页全局目录 都要初始化页二级目录 */	
		alloc_init_pud(pgd, addr, next, phys, type);

		phys += next - addr;
		addr = next;
	} while (pgd++, addr != end);
}
->
2 页二级目录初始化:
static void __init alloc_init_pud(pgd_t *pgd, unsigned long addr,
				  unsigned long end, phys_addr_t phys,
				  const struct mem_type *type)
{
/*
页二级目录地址,因为不使用页二级目录,所以页二级目录地址等于页全局目录地址。
为实现软件兼容性所以代码中还保留了页二级目录的处理流程,只不过它的地址即是页全局目录
*/
	pud_t *pud = pud_offset(pgd, addr);
	unsigned long next;

	do {
		next = pud_addr_end(addr, end);
		/*页三级目录初始化*/
		alloc_init_pmd(pud, addr, next, phys, type);
		phys += next - addr;
	} while (pud++, addr = next, addr != end);
}
->
3页三级目录初始化:
static void __init alloc_init_pmd(pud_t *pud, unsigned long addr,
				      unsigned long end, phys_addr_t phys,
				      const struct mem_type *type)
{
/*
页三级目录地址,因为不使用页三级目录,所以页三级目录地址等于页二级目录,当然也等于页全局目录地址。
为实现软件兼容性所以代码中还保留了页三级目录的处理流程,只不过它的地址即是页全局目录
*/
	pmd_t *pmd = pmd_offset(pud, addr);
	unsigned long next;

	do {
		next = pmd_addr_end(addr, end);
	
		/*if;else都有可能执行到*/
		/* 当我们创建页表项的虚拟地址区间是1M对齐时 */
		if (type->prot_sect && ((addr | next | phys) & ~SECTION_MASK) == 0) {

			__map_init_section(pmd, addr, next, phys, type);
		} 
		/* 当我们创建页表项的虚拟地址区间非1M对齐时 */
		else {

			alloc_init_pte(pmd, addr, next,__phys_to_pfn(phys), type);
		}

		phys += next - addr;

	} while (pmd++, addr = next, addr != end);
}

->
4 页表初始化:
虚拟地址区间1M时,如下方式MMU映射:
static void __init __map_init_section(pmd_t *pmd, unsigned long addr,
			unsigned long end, phys_addr_t phys,
			const struct mem_type *type)
{
	pmd_t *p = pmd;

	do {			
		*pmd = __pmd(phys | type->prot_sect);
		phys += SECTION_SIZE;
	} while (pmd++, addr += SECTION_SIZE, addr != end);

	flush_pmd_entry(p);
}

虚拟地址区间非1M对齐时,如下方式MMU映射,即创建页表:
static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
				  unsigned long end, unsigned long pfn,
				  const struct mem_type *type)
{
        /* 此时申请页表的地址(一个页表项4byte,一个pmd有512个页表项)页表的基地址会赋值给pmd ,__pmd_populate中完成赋值*/
	pte_t *pte = early_pte_alloc(pmd, addr, type->prot_l1);
	do {
		/*为每一页配置页表属性,注意此时用到了mem_types定义的属性*/
		set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);
		pfn++;
	} while (pte++, addr += PAGE_SIZE, addr != end);
}
5 页表属性配置函数:
无论内核态还是用户态,linux最后都是通过cpu_v7_set_pte_ext接口设置页表属性的:
ENTRY(cpu_v7_set_pte_ext)
/*
r0:页表项地址
r1:页表属性|物理地址偏移
将页表属性r1配置给页表项r0
*/
	str	r1, [r0]			@ linux version

	bic	r3, r1, #0x000003f0
	bic	r3, r3, #PTE_TYPE_MASK
	orr	r3, r3, r2
	orr	r3, r3, #PTE_EXT_AP0 | 2

	tst	r1, #1 << 4
	orrne	r3, r3, #PTE_EXT_TEX(1)

	eor	r1, r1, #L_PTE_DIRTY
	tst	r1, #L_PTE_RDONLY | L_PTE_DIRTY
	orrne	r3, r3, #PTE_EXT_APX

	tst	r1, #L_PTE_USER
	orrne	r3, r3, #PTE_EXT_AP1

	tst	r1, #L_PTE_XN
	orrne	r3, r3, #PTE_EXT_XN

	tst	r1, #L_PTE_YOUNG
	tstne	r1, #L_PTE_VALID
	eorne	r1, r1, #L_PTE_NONE
	tstne	r1, #L_PTE_NONE
	moveq	r3, #0

 ARM(	str	r3, [r0, #2048]! )
 THUMB(	add	r0, r0, #2048 )
 THUMB(	str	r3, [r0] )
	ALT_SMP(W(nop))
	ALT_UP (mcr	p15, 0, r0, c7, c10, 1)		@ flush_pte
	bx	lr
ENDPROC(cpu_v7_set_pte_ext)
以上函数实际上就是把页表属性配置到页表项中,C/B位也在此设置。其中页表项各字段含义如下:
linux内核是如何实现分页机制的_第1张图片
注意:cpu_v7_set_pte_ext设置页表属性的时机一般为:
1)  驱动中io地址映射: ioremap->ioremap_pte_range->set_pte_ext
2)  用户空间mmap : mmap->mmap_mem->remap_pfn_range;
3)  系统初始化过程: map_lowmem/iotable_init->create_mapping
4)  等等,在此不逐一列举了。
5 在实际linux系统软件开发中如何修改页表属性,给出两种常见方式:

1) 可以通过配置全局变量mem_types属性修改低端内存cache的开启情况create_mapping中真正使用该配置。内核在初始化中的会为低端内存、io地址等地址空间设置页表属性,如果要修改这一部分内存的属性包括cache开启关闭情况,mem_types中定义了初始页表项属性,形如:
prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY
其实在内核启动过程的build_mem_type_table()函数里,还会根据不同arm版本做调整。

2) 可以在映射一段物理内存之前通过系统的标志宏来修改cache使用情况。

举例:/dev/mem驱动中实现物理内存的重新映射(即mmap函数的实现过程).
mmap_mem->remap_pfn_range中:vm_page_prot = pgprot_writecombine(pVma->vm_page_prot);这段代码就是配置cache属性的。

内存映射前关闭cache的宏:
#define pgprot_noncached(prot) \
    __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED)
#define pgprot_writecombine(prot) \
    __pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_BUFFERABLE)
关闭cache时使用的具体标志位:
#define L_PTE_MT_UNCACHED   (_AT(pteval_t, 0) << 2) /* strongly ordered */
#define L_PTE_MT_BUFFERABLE (_AT(pteval_t, 1) << 2) /* normal non-cacheable */

【总结】

以上分析了linux系统实现分页机制的过程。为突出分页的主线,其他内存管理的知识直接给出了结论,未做源码上的细致分析。以后有时间可以详细介绍下linux系统的内存管理方法。
【拓展】
笔者曾经开发过两款调试工具:
一是可以通过命令查询一个内存地址是否打开cache。
二是可以查询一个地址被修改的情况,包括何时被修改,修改为何值等。
这两款调试工具的实现原理都和分页机制大有关联。

附录:
ARM中MMU工作原理 本文描述基于存储器管理单元的系统结构, 包含以下内容:
 · 关于存储器管理单元的结构
 · 存储器访问的顺序
 · 转换过程
 · 访问权限
 · 域
 · 异常
 · CP15 寄存器
 http://embedded.homeunix.org 30/06/2003
 Page 3 of 3
 3.1 关于存储器管理单元的结构
 MMU 存储器系统的结构允许对存储器系统的精细控制。大部分的控制细节由存在存
 储器中的转换表提供。这些表的入口定义了从1KB 到1MB 的各种存储器区域的属
 性。这些属性包括:
 虚拟地址到物理地址映射
 ARM 处理器产生的地址叫虚拟地址,MMU 允许把这个虚拟地址映射到一个不
 同的物理地址去。这个物理地址表示了被访问的主存储器的位置。
 它允许用很多方式管理物理存储器的位置,例如:它可以用具有潜在冲突的
 地址映射为不同的进程分配存储器,或允许具有不连续地址的应用把它映射
 到连续的地址空间。
 ------注------
 如果使用了快速上下文切换扩展(Fast Context Switch Extension),则在
 本文中的虚拟地址的意思应该是修改过的虚拟地址(Modified virtual
 address)
 ---------------
 存储器访问权限(permissions)
 这些控制对存储器区域的不可访问权限、只读权限、读写权限。当访问不可
 访问权限的存储器时,会有一个存储器异常通知ARM 处理器。
 允许权限的级别也受程序运行在用户状态还是特权状态影响,还受是否使用
 了域有关。
 高速缓存和缓冲位(Cachability and bufferability bits [C and B])
 这些在高速缓存和缓冲一节讲
 系统控制协处理器的寄存器允许对系统的高级控制,如转换表的位置。他们也用来
 为ARM 提供内存异常的状态信息。
 查找整个转换表的过程叫转换表遍历。它由硬件制动进行,并需要大量的执行时间
 (至少一个存储器访问,通常是两个)。为了减少存储器访问的平均消耗, 转换表
 http://embedded.homeunix.org 30/06/2003
 Page 4 of 4
 遍历结果被高速缓存在一个或多个叫作Translation Lookaside Buffers(TLBs)的
 结构中。通常在ARM 的实现中每个内存接口有一个TLB。
 · 有一个存储器接口的系统通常有一个唯一的TLB
 · 指令和数据的内存接口分开的系统通常有分开的指令TLB 和数据TLB
 如果系统有高速缓存, 高速缓存的数量也通常是由同样的方法确定的。所以在高
 速缓存的系统中,每个高速缓存一个TLB。
 当存储器中的转换表被改变或选中了不同的转换表(通过写CP15 的寄存器2),先
 前高速缓存的转换表遍历结果将不再有效。MMU 结构提供了刷新TLB 的操作。
 MMU 结构也允许特定的转换表遍历结果被锁定在一个TLB 中,这就保证了对相关的
 存储器区域的访问绝不会导致转换表遍历,这也对那些把指令和数据锁定在高速缓
 存中的实时代码有相同的好处。
 3.2 存储器访问的顺序
 当ARM 要访问存储器时,MMU 先查找TLB 中的虚拟地址表,如果ARM 的结构支持分
 开的地址TLB 和指令TLB,那么它用:
 · 取指令使用指令TLB
 · 其它的所有访问类别用数据TLB
 如果TLB 中没有虚拟地址的入口,则转换表遍历硬件从存在主存储器中的转换表中
 获取转换和访问权限,一旦取到,这些信息将被放在TLB 中,它会放在一个没有使
 用的入口处或覆盖一个已有的入口。关于转换表的信息和转换表遍历的实现参见转
 换过程一节。
 一旦为存储器访问的TLB 的入口被拿到,这些信息将被用于:
 1. C(高速缓存)和B(缓冲)位被用来控制高速缓存和写缓冲,并决定是否高速
 缓存。(如果系统中没有高速缓存和写缓冲,则对应的位将被忽略)
 2. 访问权限和域位用来控制访问是否被允许。如果不允许,则MMU 将向ARM 处理
 器发送一个存储器异常;否则访问将被允许进行。
 访问权限、域和异常几节有详细描述。
 http://embedded.homeunix.org 30/06/2003
 Page 5 of 5
 3. 对没有高速缓存的系统(包括在没有高速缓存系统中的所有存储器访问),物
 理地址将被用作主存储器访问的地址。
 对有高速缓存的系统,在高速缓存没有选中的情况下,物理地址将被用行取
 (line fetch)的地址。如果选中了高速缓存,则物理地址将被忽略。
 图3-1 说明了这种高速缓存系统
 访问控
 制硬件
 TLB
 ARM
 处理器
 高速缓
 存和写
 缓冲
 转换表遍历
 硬件
 高速缓
 存行取
 硬件
 主
 存
 储
 器
 虚拟地址
 异常
 域位
 C, B位
 物理地址
 图3-1 高速缓存的MMU存储器系统
 http://embedded.homeunix.org 30/06/2003
 Page 6 of 6
 3.2.1 允许和禁止MMU
 通过写系统控制协处理器的寄存器1 的第0 位可以允许和禁止MMU。在复位后这位
 是0,MMU 被禁止。
 当MMU 被禁止时,存储器访问将被按如下处理:
 1. 由具体的实现确定当MMU 被禁止时是否能够允许高速缓存和写缓冲。
 · 当MMU 被禁止时不能允许高速缓存和写缓冲时,C 和B 位不起作用。
 · 当MMU 被禁止时能允许高速缓存和写缓冲时:
 i. 访问数据时被认为没有高速缓存和写缓冲(C==0,B==0)
 ii. 取指令时:
 a) 当系统只有一个唯一的TLB 时,认为是没有高速缓存。(C==0)
 b) 当系统只有独立的指令TLB 时,认为是有高速缓存。(C==1)
 2. 没有存储器访问权限的检查,MMU 也不产生异常信号。
 3. 物理地址与虚拟地址相同(即所谓的平坦地址映射模式)。
 在允许MMU 之前,必须在内存中建立适当的转换表,并且所有相关的CP15 寄存器
 要被初始化正确。
 注:-------------------
 允许和禁止MMU 直接改变了虚拟地址到物理地址的映射(除非转换表被设定为平坦
 地址映射模式)。所以很可能在允许MMU 时所有的高速缓存需要被刷新。
 另外,如果允许MMU 的指令的物理地址和虚拟地址不同,取指令将变得复杂化。所
 以,强烈建议允许MMU 的指令具有相同的物理地址和虚拟地址。
 --------------------------
 http://embedded.homeunix.org 30/06/2003
 Page 7 of 7
 3.3 转换过程
 MMU 支持基于节或页的存储器访问:
 节(Section) 构成1MB 的存储器块
 支持3 中不同的页尺寸:
 微页(Tiny page) 构成1KB 的存储器块
 小页(Small page) 构成4KB 的存储器块
 大页(Large page) 构成64KB 的存储器块
 节和大页是支持允许只用一个TLB 入口去映射大的存储器区间。小页和大页有附加
 的访问控制:小页分成1KB 的子页,和大页分成16KB 的子页。微页没有子页,对
 微页的访问控制是对整个页。
 存在主存储器内的转换表有两个级别:
 第一级表 存储节转换表和指向第二级表的指针。
 第二级表 存储大页和小页的转换表。一种类型的第二级表存储微页转换表。
 MMU 把CPU 产生的虚拟地址转换成物理地址去访问外部存储器,同时继承并检查访
 问权限。地址转换有四条路径。路径的选取由这个地址是被标记成节映射访问还是
 页映射访问确定。页映射访问可以是大、小和微页的访问。
 然而,转换过程总是由下面所描述的那样由第一级表的获取开始。节映射的访问只
 需要读取第一级表,页映射的访问还需要读取第二级表。
 3.3.1 转换表基址
 当片上(on-chip)的TLB 中不包含被要求的虚拟地址的入口时,转换过程被启
 动。转换表基址寄存器(CP15 的寄存器2)保存着第一级转换表基址的物理地址。
 只有bits[31:14]有效,bits[13:0]应该是零(SBZ)。所以第一级表必须在16KB
 的边界。
 3.3.2 取第一级表
 http://embedded.homeunix.org 30/06/2003
 Page 8 of 8
 转换表基址寄存器的bits[31:14]与虚拟地址的bits[31:20]和两个0 位连接形成
 32 为物理地址,如图3-2。这个地址选择了一个四字节的转换表入口,它是第一级
 描述符或是指向第二级页表的指针。
 转换基址 SBZ
 转换基址 表索引 00
 31 14 13 0
 31 20 19 0
 31 14 13 2 10
 表索引 xxxxxxxxxxxxxxxxxxxxxx
 图3-2 访问转换表的第一级描述符
 http://embedded.homeunix.org 30/06/2003
 Page 9 of 9
 3.3.3 第一级描述符
 第一级表的每个入口是一个描述它所关联的1MB 虚拟地址是如何映射的描述符。见
 表3-1,根据bits[1:0]的组合,有四种可能:
 · 如果bits[1:0]==0b00,所关联的地址没有被映射,试图访问他们将产生一
 个转换错(fault)。因为他们被硬件忽略,所以软件可以利用这样的描述
 符的bits[31:2]做自己的用途。推荐为描述符继续保持正确的访问权限。
 · 如果bits[1:0]==0b10,这个入口是它所关联地址的节描述符。见节描述符
 和转换节参考中的细节。
 · 如果bits[0]==1,这个入口给出粗糙第二级表(bit[1]==0),或精细第二
 级表(bit[1]==1)。每一种类型的表描述了它所关联的1MB 存储区域的映
 射。粗糙第二级表较小,每个表1KB,每个精细第二级表4KB。然而粗糙第
 二级表只能映射大页和小页,精细第二级表可以映射大页、小页和微页。
 3.3.4 节描述符和转换节参考
 如果第一级描述符是节描述符,那么各个字段有如下的意义:
 Bits[1:0] 描述符类型标识(0b10 表示节描述符)
 Bits[3:2] 高速缓存和缓冲位
 Bits[4] 由具体实现定义
 Bits[8:5] 这个描述符控制的节的16 种域之一
 Bits[9] 现在没有使用,应该为零
 Bits[11:10] 访问控制,见表3-3
 Bits[19:12] 现在没有使用,应该为零
 Bits[31:20] 节基址,形成物理地址的高12 位
 忽略 00
 粗糙页表基址 sbz 域 imp 00
 节基址 SBZ AP sbz 域 imp C B 10
 精细页表基址 SBZ 域 imp 11
 31 20 19 12 11 10 9 8 5 4 3 2 10
 表 3-1 第一级描述符格式
 错
 粗糙页表
 节
 精细页表
 http://embedded.homeunix.org 30/06/2003
 Page 10 of 10
 图3-3 表示了节转换的完整过程。
 注:---------------
 访问权限必须在物理地址产生之前去检查,检查访问权限的顺序见访问权限一节。
 ---------------------
 表索引 节索引
 31 20 19 0
 虚拟地址
 转换基址 SBZ
 31 14 13 0
 转换表基址
 转换基址 表索引 00
 31 14 13 2 1 0
 第一级表地址
 节基址 SBZ AP sbz 域 imp C B 10
 31 20 19 12 11 10 9 8 5 4 3 2 1 0
 第一级表描述符
 节基址 节索引
 31 20 19 0
 物理地址
 图3-3 节转换
 First-level fetch
 http://embedded.homeunix.org 30/06/2003
 Page 11 of 11
 3.3.5 粗糙页表描述符
 如果第一级描述符是粗糙页表描述符,那么各个字段有如下的意义:
 Bits[1:0] 描述符类型标识(0b01 表示粗糙页表描述符)
 Bits[4:2] 由具体实现定义
 Bits[8:5] 这个描述符控制的页的16 种域之一
 Bits[9] 现在没有使用,应该为零
 Bits[31:10] 页表基地址是一个指向第二极粗糙页表的指针,它给出第二级表访问
 的基地址。而第二级粗糙页表必须在1KB 边界对齐。
 如果从第一级读取到的是二级粗糙页表描述符,那么会象图3-4 所示执行第二级描
 述符读取。
 第一级表索引 第二级表索引 xxxxx
 31 20 19 12 11 0
 虚拟地址
 转换基址 SBZ
 31 14 13 0
 转换表基址
 转换基址 第一级表索引 0 0
 31 14 13 2 1 0
 第一级描述符地址
 页表基址 sbz 域 imp 01
 31 10 9 8 5 4 2 1 0
 第一级描述符
 页表基址 第二级表索引 00
 31 10 9 2 1 0
 第二级描述符地址
 First-level fetch
 图3-4 访问粗糙页表第二级描述符
 http://embedded.homeunix.org 30/06/2003
 Page 12 of 12
 3.3.6 精细页表描述符
 如果第一级描述符是精细页表描述符,那么各个字段有如下的意义:
 Bits[1:0] 描述符类型标识(0b11 表示精细页表描述符)
 Bits[4:2] 由具体实现定义
 Bits[8:5] 这个描述符控制的页的16 种域之一
 Bits[11:9] 现在没有使用,应该为零
 Bits[31:10] 页表基地址是一个指向第二级精细页表的指针,它给出第二级表访问
 的基地址。而第二级精细页表必须在4KB 边界对齐。
 如果从第一级读取到的是二级精细页表描述符,那么会象图3-5 所示执行第二级描
 述符读取。
 第一级表索引 第二级表索引 xxxxx
 31 20 19 10 9 0
 虚拟地址
 转换基址 SBZ
 31 14 13 0
 转换表基址
 转换基址 第一级表索引 0 0
 31 14 13 2 1 0
 第一级描述符地址
 页表基址 sbz 域 imp 01
 31 12 11 9 8 5 4 2 1 0
 第一级描述符
 页表基址 第二级表索引 00
 31 12 11 2 1 0
 第二级描述符地址
 First-level fetch
 图3-5 访问精细页表第二级描述符
 http://embedded.homeunix.org 30/06/2003
 Page 13 of 13
 3.3.7 第二级描述符
 每个粗糙第二级表对映着以4KB 为单位的虚拟地址范围市怎么映射的,每个精细第
 二级表对映着以1KB 为单位的虚拟地址范围市怎么映射的。那些入口是页描述符,
 他们能够分别描述大于4KB 或1KB 的页。在这种情况下,这个描述符必须被重复足
 够次,以保证这个页始终使用相同的描述符,不论访问这个页中的哪个虚拟地址。
 对于一个第二级描述符,有四种可能,由描述符的bits[1:0]选择。见表3-2:
 · 如果bits[1:0]==0b00,说关联的虚拟地址没有被映射,任何对这些虚拟地
 址的访问将会导致转换错(fault)。软件可以利用这样的描述符的
 bits[31:2]做自己的用途,因为他们被硬件忽略。推荐为描述符继续保持正
 确的访问权限。
 · 如果bits[1:0]==0b01,这个入口是大页描述符,描述64KB 的虚拟地址。
 见转换大页参考。
 一个大页描述符在精细第二级表中必须被重复64 次,在粗糙第二级表中必
 须被重复16 次以保证所有的虚拟地址都被描述。
 · 如果bits[1:0]== 0b10,这个入口是小页描述符,描述4KB 的虚拟地址。
 见转换小页参考。
 一个小页描述符在精细第二级表中必须被重复4 次,以保证所有的虚拟地址
 都被描述。在粗糙第二级表中只有一个实例。
 · 如果bits[1:0]== 0b11,这个入口是微页描述符,描述1KB 的虚拟地址。
 见转换微页参考。
 在精细第二级表中只需要一个微页描述符的实例。微页描述符不能在粗糙第
 二级表中出现,如果出现了,结果不可预测。
 忽略 00
 大页基地址 SBZ AP3 AP2 AP1 AP0 C B 01
 小页基地址 AP3 AP2 AP1 AP0 C B 01
 微页基地址 SBZ AP C B 11
 表3-2 第二级描述符格式
 31 16 15 12 11 10 9 8 7 6 5 4 3 2 1 0
 错
 大页
 小页
 微页
 http://embedded.homeunix.org 30/06/2003
 Page 14 of 14
 大页描述符字段
 大页描述符的字段有如下意义:
 bits[1:0] 表示描述符的类型
 bits[3:2] 高速缓促和缓冲位
 bits[11:4] 访问权限位。这些为控制对页的访问。关于这些位的解释见表3-3。
 大页被分成4 各子页。
 AP0 编码对第一个子页的访问权限。
 AP1 编码对第二个子页的访问权限。
 AP2 编码对第三个子页的访问权限。
 AP3 编码对第四个子页的访问权限。
 bits[15:12] 现在没有使用,应该为零。
 bits[31:16] 用来形成物理地址的对应位。
 小页描述符字段
 小页描述符的字段有如下意义:
 bits[1:0] 表示描述符的类型
 bits[3:2] 高速缓促和缓冲位
 bits[11:4] 访问权限位。这些为控制对页的访问。关于这些位的解释见表3-3。
 小页被分成4 各子页。
 AP0 编码对第一个子页的访问权限。
 AP1 编码对第二个子页的访问权限。
 AP2 编码对第三个子页的访问权限。
 AP3 编码对第四个子页的访问权限。
 bits[31:12] 用来形成物理地址的对应位。
 微页描述符字段
 微页描述符的字段有如下意义:
 bits[1:0] 表示描述符的类型
 bits[3:2] 高速缓促和缓冲位
 bits[5:4] 访问权限位。这些为控制对页的访问。关于这些位的解释见表3-3 关
 于微页的解释。
 bits[9:6] 现在没有使用,应该为零。
 bits[31:10] 用来形成物理地址的对应位。
 http://embedded.homeunix.org 30/06/2003
 Page 15 of 15
 3.3.8 转换大页参考
 图3-6 显示了在粗糙第二级表中转换一个64KB 的大页的完整顺序。在精细第二级
 表中的转换顺序页相似,只是第二级描述符的地址如精细页表描述符一节所决定。
 注:-----------------------------
 页索引的高4 位和第二级表的低阶4 位重叠,在粗糙页表中大页的每个页表入口必
 须被重复16 次。在精细页表中大页的每个页表入口必须被重复64 次。
 -----------------------------------
 大页基址 页索引
 31 16 15 0
 物理地址
 大页基址 SBZ AP3 AP2 AP1 AP0 C B 0 1
 31 16 15 121110 9 8 7 6 5 4 3 2 10
 0
 第二级描述符
 页表基址 第二级表索引 00
 31 10 9 2 10
 第二级描述符地址
 页表基址 sbz 域 imp 01
 31 10 9 8 5 4 2 10
 第一级描述符
 转换基址 第一级表索引00
 31 14 13 2 10
 第一级描述符地址
 转换基址 SBZ
 31 14 13 2 10
 转换表基址
 第一级表索引 第二级表索引 页索引
 31 20 19 16 15 12 11 0
 虚拟地址
 Second-level fetch
 First-level fetch
 图3-6 粗糙第二级表中的大页转换
 http://embedded.homeunix.org 30/06/2003
 Page 16 of 16
 3.3.9 转换小页参考
 图3-7 显示了在粗糙第二级表中转换一个4KB 的小页的完整顺序。在精细第二级表
 中的转换顺序页相似,只是第二级描述符的地址如精细页表描述符一节所决定。
 注:-----------------------------
 当小页出现在精细第二级表中时,页索引的高2 位和第二级表的低阶2 位重叠,在
 精细页表中小页的每个页表入口必须被重复4 次。
 -----------------------------------
 图3-7 粗糙第二级表中的小页转换
 小页基址 页索引
 31 12 11 0
 物理地址
 小页基址 AP3 AP2 AP1 AP0 C B 10
 31 12 11 10 9 8 7 6 5 4 3 2 10
 0
 第二级描述符
 页表基址 第二级表索引00
 31 10 9 2 10
 第二级描述符地址
 页表基址 sbz 域 imp 01
 31 10 9 8 5 4 2 10
 第一级描述符
 转换基址 第一级表索引 00
 31 14 13 2 10
 第一级描述符地址
 第一级表索引 第二级表索引 页索引
 31 20 19 12 11 0
 虚拟地址
 转换基址 SBZ
 31 14 13 0
 转换表基址
 First-level fetch
 Second-level fetch
 http://embedded.homeunix.org 30/06/2003
 Page 17 of 17
 3.3.10 转换微页索引
 图3-8 显示了在精细第二级表中转换1KB 微页的完整过程。
 注:---------------------------
 微页不能出现在粗糙第二级表中。
 ---------------------------------
 转换基址 SBZ
 31 14 13 0
 转换表基址
 第一级表索引 第二级表索引 页索引
 31 20 19 10 9 0
 虚拟地址
 转换基址 第一级表索引 00
 31 14 13 2 10
 第一级描述符地址
 页表基址 sbz 域 imp11
 31 12 11 9 8 5 4 2 10
 第一级描述符
 微页表基址 SBZ AP C B 11
 31 10 9 6 5 4 3 2 10
 第二级描述符
 页表基址 第二级表索引00
 31 12 11 2 10
 第二级描述符地址
 微页表基址 页索引
 31 10 9 0
 物理地址
 Second-level fetch
 First-level fetch
 图3-8 精细第二级表中的微页转换
 http://embedded.homeunix.org 30/06/2003
 Page 18 of 18
 3.4 访问权限
 在节和页描述符中的访问权限位控制对相应的节和页的访问。访问权限由CP15 的
 寄存器1 的System(S)和ROM(R)位修改。表3-3 描述了访问权限位和S、R 位相互
 作用时的意义。如果访问了没有访问权限的存储器空间,将会产生权限错(见异常
 一节)。
 表3-3 MMU 访问权限
 AP
 S
 R
 Privileged permissions
 User permissions
 0b00 0 0 不能访问 不能访问
 0b00 1 0 只读 不能访问
 0b00 0 1 只读 只读
 0b00 1 1 不可预测 不可预测
 0b01 X X 读/写 不能访问
 0b10 X X 读/写 只读
 0b11 X X 读/写 读/写
 http://embedded.homeunix.org 30/06/2003
 Page 19 of 19
 3.5 域
 域是节、大页和小页的集合。ARM 结构支持16 个域。对域的访问由域访问控制寄
 存器的两个位字段控制。因为每个字段对访问对应的域的使能非常迅速,所以整个
 存储器区间能很快地交换进出虚拟存储器。这里支持2 种域访问方式:
 客户 域的用户(执行程序,访问数据),被形成这个域的节或页来监督访
 问权限。
 管理者 控制域的行为(域中的当前节和页,对域的访问),不被形成这个域
 的节或页来监督访问权限。
 一个程序可以是一些域的客户,也是另外一些域的管理者,同时没有对其它域的访
 问权限。这允许对程序访问不同存储器资源的非常灵活的存储器保护。表3-4 说明
 了域访问控制寄存器的位编码方式。
 表3-4 域访问的值
 值
 访问方式
 描述
 0b00 不能访问 任何访问都将导致一个域错(domain fault)
 0b01 客户 能否访问将根据节或页描述符中的访问权限位
 确定
 0b10 保留 使用这个值将导致不可预料的结果
 0b11 管理者 不根据节或页描述符中的访问权限位确定能否
 访问,所以不会产生权限错。(permission
 fault)
 http://embedded.homeunix.org 30/06/2003
 Page 20 of 20






你可能感兴趣的:(linux内核内存子系统,linux,kernel,arm,源码,内存管理,分页)