Linux内存细节 内核临时页表 - linux内存管理(二)

__create_page_tables与__enable_mmu(ARM920芯片)说明临时页表的创建

linux内核初始化做完芯片类型检查和CPU启动以后就进入到MMU的启动,这么早启动MMU主要是为了后面建立虚拟映射循序渐进的提前做好部分准备工作。在MMU信息使能初期,首先要建立映射表,这个是这篇文章的两个主题__create_page_tables与__enable_mmu。关于ARM芯片页表概念之前需要了解cache-mmu-sdram内存管理硬件概念,s3c2440芯片裸机文章通过基本实验了解基本概念。

汇编代码head.S文件中创建临时页表,使用的是arm的L1主页表,L1主页表也称为段页表(section page table,说白了就是采用段式管理而不是页式管理),它将4GB的地址空间分成若干个1MB的段(section),因此L1页表包含4096个页表项(section entry);每个页表项是32 bits(4 bytes), 所以L1页表占用 4096*4 = 16k的内存空间;L1页表,实际是给4G的虚拟地址空间中每个1M空间的段,映射其对应的物理内存地址是哪里,通过在对应的页表项写入物理地址值及MMU相关内容实现,在临时页表中,并没有写满所有表项,为这时需考虑的只有内核代码。

1. 初始化跳转页表宏

1.1. 初始化调用接口

  • (*1)跳转__create_page_tables完成MMU映射页表创建
  • (*2)打开MMU,开启虚拟地址的映射
ENTRY(stext)
	msr	cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
						@ and irqs disabled
	mrc	p15, 0, r9, c0, c0		@ get processor id      @ c0 is source reg, r9 is des reg
	bl	__lookup_processor_type		@ r5=procinfo r9=cpuid
	movs	r10, r5				@ invalid processor (r5=0)?
	beq	__error_p			@ yes, error 'p'
	bl	__lookup_machine_type		@ r5=machinfo
	movs	r8, r5				@ invalid machine (r5=0)?
	beq	__error_a			@ yes, error 'a'
	bl	__create_page_tables              -------------------- (*1)

	/*
	 * The following calls CPU specific code in a position independent
	 * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of
	 * xxx_proc_info structure selected by __lookup_machine_type
	 * above.  On return, the CPU will be ready for the MMU to be
	 * turned on, and r0 will hold the CPU control register value.
	 */
	ldr	r13, __switch_data		@ address to jump to after 将列表__switch_data存到r13中后面会跳到该列表出
						@ mmu has been enabled 将程序段__enable_mmu的地址存到lr中。
	adr	lr, __enable_mmu		@ return (PIC) address   -------------------- (*2)
	// 此命令将导致程序段__arm920_setup的执行,后面会将到。
    // r10中存放的基地址是从__lookup_processor_type中得到的,如上面movs r10, r5
	add	pc, r10, #PROCINFO_INITFUNC @ __arm920_proc_info->__arm920_setup 文件:proc_arm920.S文件中

2. __create_page_tables宏

/*
 * Setup the initial page tables.  We only setup the barest
 * amount which are required to get the kernel running, which
 * generally means mapping in the kernel code.
 *
 * r8  = machinfo
 * r9  = cpuid
 * r10 = procinfo
 *
 * Returns:
 *  r0, r3, r6, r7 corrupted
 *  r4 = physical page table address
 */
	.type	__create_page_tables, %function
__create_page_tables:
	pgtbl	r4				@ page table address       ----------------- (1)

	/*
	 * Clear the 16K level 1 swapper page table,4096entry*4byte=16k
	 */                                                ----------------- (2)
	mov	r0, r4                                                ---------- (2.1)
	mov	r3, #0                                                ---------- (2.2)
	add	r6, r0, #0x4000                                       ---------- (2.3)
1:	str	r3, [r0], #4                                          ---------- (2.4)
	str	r3, [r0], #4                                          ---------- (2.5)
	str	r3, [r0], #4                                          ---------- (2.6)
	str	r3, [r0], #4                                          ---------- (2.7)
	teq	r0, r6                                                ---------- (2.8)
	bne	1b                                                    ---------- (2.9)

	ldr	r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags        ---------- (2.10)

	/*
	 * Create identity mapping for first MB of kernel to
	 * cater for the MMU enable.  This identity mapping
	 * will be removed by paging_init().  We use our current program
	 * counter to determine corresponding section base address.
     * 下面三行设置了kernel的section的页表项,将起始物理地址为0x30008000 
     * 的1M内存空间映射到虚拟地址为0x30008000。
	 */                                                ----------------- (3)
	mov	r6, pc, lsr #20			@ start of kernel section     ---------- (3.1)
	orr	r3, r7, r6, lsl #20		@ flags + kernel base         ---------- (3.2)  
	str	r3, [r4, r6, lsl #2]		@ identity mapping        ---------- (3.3)

	/*
	 * Now setup the pagetables for our kernel direct
	 * mapped region.
	 */
	/*
     * 因为KERNEL_START是内核的起始虚拟地址(0xC0008000),KERNEL_END为内核
     * 的结束虚拟地址,所以下面的代码实际上是将物理地址为kernel的起始地址
     * (0x30008000)的一段内存空间(大小为内核映像文件的大小)映射到虚拟地址
     * 0xC0008000。
     */                                               ----------------- (4)
	add	r0, r4,  #(KERNEL_START & 0xff000000) >> 18          ---------- (4.1)
	str	r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!        ---------- (4.2)
	ldr	r6, =(KERNEL_END - 1)                                ---------- (4.3)
	add	r0, r0, #4                                           ---------- (4.4)
	add	r6, r4, r6, lsr #18                                  ---------- (4.5)
1:	cmp	r0, r6                                               ---------- (4.6)
	add	r3, r3, #1 << 20                                     ---------- (4.7)
	strls	r3, [r0], #4                                     ---------- (4.8)
	bls	1b                                                   ---------- (4.9)

	/*
	 * Then map first 1MB of ram in case it contains our boot params.
	 */
	/*
     * 下面的代码用来设置RAM中起始地址为0x30000000、大小为1M虚拟地址的页表,之所以
     * 要设置这个页表项的原因是该区域起始地址为0x30000100存储着boot params。因此需
     * 要为它建立map,这样开启MMU后就可以访问这些参数了。
     */                                              ----------------- (5)
	add	r0, r4, #PAGE_OFFSET >> 18                          ---------- (5.1)
	orr	r6, r7, #(PHYS_OFFSET & 0xff000000)                 ---------- (5.2)
	.if	(PHYS_OFFSET & 0x00f00000)                          ---------- (5.3)
	orr	r6, r6, #(PHYS_OFFSET & 0x00f00000)                 ---------- (5.4)
	.endif                                                  ---------- (5.5)
	str	r6, [r0]                                            ---------- (5.6)

	mov	pc, lr                                       ----------------- (6)  
	.ltorg

页表映射过程包含了四个隔离宏,针对不同类型的宏开关决定是否将对应的代码包含进来,上面代码直接删除需要自己分析。函数调用之前第一步处理结果将R8存储machine_desc的物理地址,R9存储CPU ID,R10存储procinfo。函数返回值r4保存了转换表的在内存中物理地址,打开MMU开关是需要告知其这个参数的值。具体的处理流程说明如下:

  1. 宏pgtbl将r4设置成页表的物理地址,KERNEL_RAM_PADDR - 0x4000 = 0x4000,用来存放临时页表。head.S文件前面的代码有pgtbl宏声明的地方如下:.macro     pgtbl, rd  -> ldr    \rd, =(KERNEL_RAM_PADDR - 0x4000)。对于s3c2440,KERNEL_RAM_PADDR是内核其实地址,也就是0x30008000,计算以后临时页表防止位置是0x30004000。
  2. (2.1 - 2.3)主要完成清楚前面变量的初始给定。r0保存表现开始地址0x30004000,r6保存表现结束位置地址0x30008000,r3保存空值0。(2.4 - 2.9)将r3中的0值分别放到0x30004000 - 0x30008000中间的位置,按照每4个4直接的寄存器作为一组进行。(2.10)把R10保存的地址再加PROCINFO_MM_MMUFLAGS后,取该地址的值赋给R7,R10先前已保存了procinfo函数指针数组首地址,再加8就是proc_info_list结构变量的__cpu_mm_mmu_flags成员地址,取该成员的值赋给R7。__cpu_mm_mmu_flags这个位置主要是保存了MMU段描述符中后面对本映射的一个管理相关的内容。
  3. 是把L1页表中当前运行地址所在的段(当前以汇编代码运行的内核代码段,运行在物理内存起初的部分,取值应在0x0到0x00100000范围内,可以看看KERNEL_RAM_PADDR值为0x00008000,可见从L1页表的分段角度,在第一个段即地址0x0-0x00100000段范围内)的表项写入映射关系,事实上重点是理解为什么要把这里也做映射。之所以要为这个物理地址的段也做映射,是因为现在MMU没开启,过一会就会开启MMU,按说内核代码段的内容在做了映射后,CPU给MMU的PC值是虚拟地址,但由于CPU流水线的预取指功能,还是会有一些PC值还不是虚拟地址而仍然是物理地址,这样的话如果MMU内部没有相应的映射关系,将不知道访问实际物理内存哪部分,导致出现问题,所以这里需要把这部分物理地址在L1页表的相应表项也写入,本质是PA = PA(很多参考文章写PA = VA,其实道理是一样的,但那样表述容易给人以误解,因为开启MMU后,CPU发出的访问按说都是虚拟地址,但确实有一些其实还是物理地址,如果不做映射将让MMU无从知道该访问哪里,所以这里写L1页表的相关物理页表项,其实是做PA = PA的映射给MMU)。(3.1)将当前PC运行地址的低20bit抹去,只保留高12bit。(3.2)将PC高位地址段和__cpu_mm_mmu_flags位置的段控制信息或形成最终的实际的一个entry内容。(3.3)最后将(3.2)步骤的结果放置在从0x30004000开始和当前实际物理地址有关的某个偏移处即可。
  4. 下面这堆内容,核心目的是为kernel镜像(即内核代码段)做L1页表映射,即KERNL_START到KERNEL_END建立内存映射,这两个宏是编译时产生的,所以它们是虚拟地址,而内核代码段在物理内存的实际位置是从物理内存起初的部分(L1页表角度看就是在第一个段内),后续要开启MMU了,即CPU发给MMU的地址将是虚拟地址了,所以这里要先把内核代码做映射,映射方式方法和前面是一样的,把虚拟的0xc0008000到KERNEL_END(一般内核代码长度为3-4M,所以KERNEL_END值估计为0xc03XXXXX),映射到物理地址的0x0到0x3段内(假定内核代码为3-4M长度大小)。(4.1)R4已保存了页表物理起始地址,对于(KERNEL_START & 0xff000000),这是为了获取高12bit位的段值(为什么不直接以0xfff00000去与?),所谓右移18位,其实就是先右移20位获取到段值,然后左移2位取得所在表项在L1页表的偏移值(因为每个表项占4个字节),最后加上R4即L1页表物理基地址,得出该表项实际的物理地址保存在R0(实际值为0x20004000+0xC00*4 = 0x20007000)(4.2)把R3即内核物理起始地址段值和PROCINFO_MM_MMUFLAGS的或运算结果即映射结果写入内核起始虚拟地址所在的L1表项,对于我们这里就是写入的是0x0与PROCINFO_MM_MMUFLAGS的或运算结果,这里的疑问是:为什么不一开始就用0xfff00000去与运算,而是分两次?(4.3)r6为内核代码的尾部虚拟地址(4.4)R0保存下一个即将要填写的L1页表项的物理地址(4.5)R6本身是内核代码的尾部虚拟地址,右移18位(右移20再左移2,先获取段值然后算出所在L1页表项的偏移),再加上R4值,最后R6值是内核代码结尾的所在L1页表的表项的物理地址。(4.6)-(4.9)循环实现内核区域的一次映射,每一个entry映射时1M(1<<20)
  5. 通常kernel的启动参数由bootloader放到了物理内存的第1个M上,所以需要为RAM上的第1个M建立映射下面这堆内容,核心目的就是确保RAM的第1个M建立映射; 事实上上面已为PHYS_OFFSET + TEXT_OFFSET建立了映射(如果TEXT_OFFSET小于0x00100000即1MB的话),我们这里TEXT_OFFSET值为0x8000,即也为SDRAM的第一个M建立了映射;         说白了,这里的用意就是说无论上面代码如何,均为SDRAM的第一个M建立映射,事实上是为了考虑TEXT_OFFSET值大于0x00100000即1MB的情况,即内核起始代码超出了第一个段的范围的情况
  6. mov pc, lr 返回调用

映射结束以后并且映射了如下部分的页表映射: 物理内存第一MB(自己映射自己,因为开启MMU后仍有一些访问是以物理地址发到MMU)、内核代码段(基本是0xc00段到0xc03段,映射至0x0段到0x003段), 后续准备开启MMU。而寄存器结果是:R4: 页表起始处物理地址;R8: machine info,struct machine_desc的基地址;R9: cpu id;R10: procinfo,struct proc_info_list的基地址

2.3. 涉及宏

默认值

定义

KERNEL_RAM_VADDR

0xC0008000

内核在内存中的虚拟地址

PAGE_OFFSET

0xC0000000

内核虚拟地址空间的起始地址

TEXT_OFFSET

0x00008000

内核起始位置相对于内存起始位置的偏移

PHYS_OFFSET

构架相关

物理内存的起始地址

3. 开启MMU

MMU开启过程包含两部分代码,其中第一部分代码首先是逐渐MMU的常见公共控制位放在r0寄存器中。然后将r4表现其实地址和r5域控制分别赋值给协寄存器c2和c3。然后将r0寄存器结果赋值给c1寄存器之后可以打开MMU了。

/*
 * Setup common bits before finally enabling the MMU.  Essentially
 * this is just loading the page table pointer and domain access
 * registers.
 */
	.type	__enable_mmu, %function
__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAP
	orr	r0, r0, #CR_A
#else
	bic	r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
	bic	r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
	bic	r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
	bic	r0, r0, #CR_I
#endif
	mov	r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
		      domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
		      domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
		      domain_val(DOMAIN_IO, DOMAIN_CLIENT))
	mcr	p15, 0, r5, c3, c0, 0		@ load domain access register
	mcr	p15, 0, r4, c2, c0, 0		@ load page table pointer
	b	__turn_mmu_on

/*
 * Enable the MMU.  This completely changes the structure of the visible
 * memory space.  You will not be able to trace execution through this.
 * If you have an enquiry about this, *please* check the linux-arm-kernel
 * mailing list archives BEFORE sending another post to the list.
 *
 *  r0  = cp#15 control register
 *  r13 = *virtual* address to jump to upon completion
 *
 * other registers depend on the function called upon completion
 */
	.align	5
	.type	__turn_mmu_on, %function
__turn_mmu_on:
	mov	r0, r0
	mcr	p15, 0, r0, c1, c0, 0		@ write control reg
	mrc	p15, 0, r3, c0, c0, 0		@ read id reg
	mov	r3, r3
	mov	r3, r3
	mov	pc, r13                     @ __switch_data位置开始执行命令

imx6ull ARM-V8内核临时页表

 前面几节,我们已经看到了x86的分段和分页硬件单元把逻辑地址转换为线性地址,再由线性地址转换到物理地址的基本原理,那么这几章我们来主要是内核是怎么实现页表的创建,本章基于imx6ull和qemu来学习整个过程,其主要内容如下:

imx6ull的映射机制
内核启动主要概述
内核临时页表
1. imx6ull的映射机制
在之前页式存储管理中,我们主要是针对x86处理器来描述虚拟地址到物理地址的转换,对于ARM来说原理基本类似,下面是我们使用armv7架构图,这张图展示了ARM使用不同方式映射的地址查找过程

在这里插入图片描述

描述符的分类为:

Section: 20位,只支持一级页表,Linux中在建立临时页表的时候采用这种方式
Large pages:16位,64KB的页表大小,支持二级页表
Small pages:12位,页表大小为4Kb,Linux中在建立永久页表采用这种方式
SuperSection:24位,可选,主要是支持大物理地址扩展必须支持
对于4K的转换过程跟X86的转换过程基本一样,只是对于页全局目录表变成了其他的寄存器,其转换过程如下

在这里插入图片描述
根据TTBRCR寄存器和虚拟地址使用判断使用那个页表及地址寄存器(TTBR0或TTBR1),防止一级页表的基地址
处理器根据虚拟地址的bit[31:20]作为索引,在一级页表中查找页表项,一级页表一共又4096个页表项(4K个entry)
一级页表的表项中存放了二级页表的基地址,处理器根据虚拟地址bit[19:12]作为索引值,在二级页表中找到对应的表,二级页表一共256个表项
二级页表的页表项里面存放了4KB页的物理基地址,加上最后的偏移量bit[11:0],最终寻找到物理内存
在4KB的映射的一级页表和二级页表的表项其实跟x86基本类似,页包含了很多的其他会议

一级页表项:

在这里插入图片描述

二级页表项

在这里插入图片描述
2. 内核启动主要概述
当U-boot启动后,通过r1,r2来将启动参数传递给内核,arm的启动(arch/arm/kernel/head.S)中kernel热人口地址对应stext,其主要做了以下几件事情

设置svc模式,关闭所有中断
获取CPU ID,提取相应的proc info
验证tags或者dtb
创建临时内核页表的页表项
配置r13寄存器,也就是设置打开MMU之后要跳转到的函数
使能MMU
跳转到start_kernel,也就是跳转到第二阶段
kernel里面的所有符号在链接时,都使用了虚拟地址值。在完成基本的初始化后,kernel代码将跳到第一个C语言函数start_kernl来 执行,这些虚拟地址必须能够对它所存放在真正内存位置,否则运行将为出错。为此,CPU必须开启MMU,但在开启MMU前,必须为虚拟地址到 物理地址的映射建立相应的面表。对应的各个宏的解释如下,为后面分析做好判断

宏    默认值    定义
KERNEL_RAM_VADDR    0xc0008000    内核在内存的虚拟地址
PAGE_OFFSET    0xc0000000    内核虚拟地址空间的起始地址
TEXT_OFFSET    0x00008000    内核起始位置相对于内存起始位置的偏移
PHYS_OFFSET    0x80000000    物理内存的起始地址
3. 内核临时页表项
内核通过__create_page_tables创建临时页表项,我们来看以一下其处理流程

__create_page_tables:
    pgtbl    r4, r8                @ page table address

首先就使用pgtbl,而这个是一个宏,定义如下:

    .macro    pgtbl, rd, phys
    add    \rd, \phys, #TEXT_OFFSET
    sub    \rd, \rd, #PG_DIR_SIZE
    .endm

其实际上是这样

add    r4, r8, #TEXT_OFFSET
sub    r4, r4, #PG_DIR_SIZE

由r8为PHYS_OFFSET,那么r4的值为PHYS_OFFSET+TEXT_OFFSET-PG_DIR_SIZE=0x80000000+0x00008000-0x4000=0x80004000,将r4设置成页表的基地址,页表将4G的地址空间分成若干个1M的段,因此页表包含4096个页表项。每个页表项是4字节,那么页表就占用4096*4=16K的内存空间。之后就将这16K的页表项清0

    mov    r0, r4
    mov    r3, #0
    add    r6, r0, #PG_DIR_SIZE
1:    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    str    r3, [r0], #4
    teq    r0, r6
    bne    1b

由于IMX不支持CONFIG_ARM_LPAE((Large Physical Address Extensions)大型物理地址扩展,那么就直接运行下面的

    ldr    r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags

    /*
     * Create identity mapping to cater for __enable_mmu.
     * This identity mapping will be removed by paging_init().
     */
    adr    r0, __turn_mmu_on_loc
    ldmia    r0, {r3, r5, r6}
    sub    r0, r0, r3            @ virt->phys offset
    add    r5, r5, r0            @ phys __turn_mmu_on
    add    r6, r6, r0            @ phys __turn_mmu_on_end
    mov    r5, r5, lsr #SECTION_SHIFT
    mov    r6, r6, lsr #SECTION_SHIFT

首先从proc_info_list结构体获取__cpu_mm_mmu_flags,该字段包含了存储空间访问权限等,并存储在r7中,然后取__turn_mmu_on_loc处的地址保存在r0,然后从这块内存中读取3个word到r3,r5,r6中,那么这3个word里面放的什么呢?

__turn_mmu_on_loc:
    .long    .
    .long    __turn_mmu_on
    .long    __turn_mmu_on_end

那么sub r0, r0, r3意义就很明确了,就是求出__turn_mmu_on_loc这个标号的物理地址和虚拟地址之间的偏移量。然后根据这个偏移量求出__turn_mmu_on的物理地址r5和__turn_mmu_on_end的物理地址r6,后面就是最关键的,mov r5, r5, lsr #SECTION_SHIFT通过r5的高12位,通过右移20位得到,最终得到kernel的section机制,r5存放起始地址的段序号,r6存放末地址的段序号。

1:    orr    r3, r7, r5, lsl #SECTION_SHIFT    @ flags + kernel base
    str    r3, [r4, r5, lsl #PMD_ORDER]    @ identity mapping
    cmp    r5, r6
    addlo    r5, r5, #1            @ next section
    blo    1b

该过程是将r7(也就是段的flag)和r5左移20位,也就是段页表项的内容,然后将段页表项的值写到对应的段页表项中,段页表项的地址=段页表起始地址(r4)+段序号r5*段页表项的size,最后通过判断是否写到__turn_mmu_on_end地址,如果没有写入,继续写入下一段,该过程主要是完成__turn_mmu_on代码的映射。

    /*
     * Map our RAM from the start to the end of the kernel .bss section.
     */
    add    r0, r4, #PAGE_OFFSET >> (SECTION_SHIFT - PMD_ORDER)        ----------------  (1)
    ldr    r6, =(_end - 1)
    orr    r3, r8, r7                                                  ------------------(2)
    add    r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)               ------------------(3)
1:    str    r3, [r0], #1 << PMD_ORDER                                  ------------------(4)
    add    r3, r3, #1 << SECTION_SHIFT
    cmp    r0, r6
    bls    1b

PAGE_OFFSET表示内核空间的偏移,这里是0xc0000000,也就是内核映射区的起始段的起始地址。将PAGE_OFFSET左移动(SECTION_SHIFT - PMD_ORDER)后得到该地址所在段的段页表项的地址偏移,最后将段页表项的地址偏移+临时内核页表地址得到0xc0000000所在段的段页表项的物理地址,并放到r0中,而r6中存放内核映射区的末尾地址。
将DDR起始物理地址(r8)或上MMU的表示(r7),得到0xc0000000所在段的段页表项内容,存放到r3中。
将内核映射区的末尾地址(r6)左移(SECTION_SHIFT - PMD_ORDER)后得到其所在段的段页表项的物理地址
将r3存入当前段页表项中([r0]),然后将r0加上4,得到下一个段页表项的地址,更新r3中的页表项值为下一个段的页表项值,也就是直接加上,判断是否已经到达内核映射区的末尾,如果不是就进入下一个循环。
从上面可以看出,这段主要是完成对kernel内核空间进行映射,我们可以通过内核的System.map文件可以看出内核的起始和结束地址为

c0008000 T _text
c10e8eec B _end
1
2
其相应在物理地址上的内存区域是0x80008000到0x810e8eec区域,因此就完成了创建物理区[0x80008000-0x810e8eec]到内核映射区[0xc0008000-0xc10e8eec]的内存映射。

接下来代码就完成了DTB的映射,其代码如下

    mov    r0, r2, lsr #SECTION_SHIFT                                           ---------------(1)
    movs    r0, r0, lsl #SECTION_SHIFT
    subne    r3, r0, r8
    addne    r3, r3, #PAGE_OFFSET                                             
    addne    r3, r4, r3, lsr #(SECTION_SHIFT - PMD_ORDER)                     ---------------(2)
    orrne    r6, r7, r0
    strne    r6, [r3], #1 << PMD_ORDER
    addne    r6, r6, #1 << SECTION_SHIFT
    strne    r6, [r3]

首先将dtb起始物理地址(r2)左移SECTION_SHIFT,存放在r0中,再将r0右移SECTION_SHIFT得到这个物理内存段的地址(和上一步简单理解就是把低20位清零),计算dtb物理内存段(r0)对应DRAM起始地址(r8)的偏移,存放在r3中,将偏移(r3)加上,内核空间起始地址PAGE_OFFSET,得到要映射到的虚拟地址
取要映射的虚拟地址的段的页表项的地址,存放在r3中,将物理内存段地址(r0)或上mmu标识(r7),得到对应页表项值,存放到r6中。将页表项值(r6)写入到页表项中([r3]),然后r3+4,获取到下一个页表项的地址,页表项值+0x100000,得到下一个页表项应该写入的页表项值,将页表项值(r6)写入到页表项中([r3])
总结,create_page_table完成了3种地址映射的页表空间:

turn_mmu_on所映射的1M空间的屏映射:那么为什么要做映射呢?在执行开启MMU指令之前,CPU取指是在0x80008000附件,如果只做kernel_image的映射,开启MMU后,CPU所看到的地址就全变了,那么就可能无法执行。完成平映射后,就可以完美解决从0x8xxxxxxx到0xcxxxxxxx的过渡。
kernel_image的线性映射:kernel编译链接的入口地址在0xc0008000,但其物理地址不等于链接的虚拟地址,需要将物理地址映射到对应的虚拟地址空间。
atags(DTB)所在的1M空间的线性映射:当MMU开启后,内核只能访问虚拟地址空间,无法访问物理地址空间,所以就需要做相应的映射。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值