内核初始页表的建立(线性映射)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、内核启动主要概述

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

  1. 设置svc模式,关闭所有中断
  2. 获取CPU ID,提取相应的proc info
  3. 验证tags或者dtb
  4. 创建临时内核页表的页表项
  5. 配置r13寄存器,也就是设置打开MMU之后要跳转到的函数
  6. 使能MMU
  7. 跳转到start_kernel,也就是跳转到第二阶段

kernel里面的所有符号在链接时,都使用了虚拟地址值。在完成基本的初始化后,kernel代码将跳到第一个C语言函数start_kernl来 执行,这些虚拟地址必须能够对它所存放在真正内存位置,否则运行将为出错。为此,CPU必须开启MMU,但在开启MMU前,必须为虚拟地址到 物理地址的映射建立相应的面表。对应的各个宏的解释如下,为后面分析做好判断

默认值定义
KERNEL_RAM_VADDR0xc0008000内核在内存的虚拟地址
PAGE_OFFSET0xc0000000内核虚拟地址空间的起始地址
TEXT_OFFSET0x00008000内核起始位置相对于内存起始位置的偏移
PHYS_OFFSET0x80000000物理内存的起始地址

二、 创建内核临时页表

1.内核通过__create_page_tables创建临时页表项

__create_page_tables:
	pgtbl	r4, r8				@ page table address

首先就使用pgtbl,而这个是一个宏,定义如下:rd, phys是pgtbl的两个参数r4和r8

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

由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

adr r0, __turn_mmu_on_loc 这条命令把__turn_mmu_on_loc这个标号的物理地址装入r0,然后从这块内存中读3个word到r3,r5,r6 中。那么这3个word里存放的是什么呢?
从System.map 文件中可以知道,__turn_mmu_on_loc标号的虚拟地址是c0008138
c0008138 t __turn_mmu_on_loc

从 vmlinux 的objdump 出来的结果可以知道,在这个地方存放的是3个地址,分别是__turn_mmu_on_loc,__turn_mmu_on,__turn_mmu_on_end的虚拟地址,
c0008138 <__turn_mmu_on_loc>:
c0008138: c0008138 andgt r8, r0, r8, lsr r1
c000813c: c0008280 andgt r8, r0, r0, lsl #5
c0008140: c00082a0 andgt r8, r0, r0, lsr #5

那么 sub r0, r0, r3 @ virt->phys offset 的意义就很明确了,就是求出__turn_mmu_on_loc这个标号的物理地址和虚拟地址之间的偏移量。然后根据这个偏移量求出__turn_mmu_on的物理地址,再把这个物理地址放到地址映射表里去。这样,当MMU开始工作以后,物理地址和虚拟地址是相同的,不会导致PC+4以后取到非法地址的问题。
后面就是最关键的,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

  1. PAGE_OFFSET表示内核空间的偏移,这里是0xc0000000,也就是内核映射区的起始段的起始地址。将PAGE_OFFSET左移动(SECTION_SHIFT - PMD_ORDER)后得到该地址所在段的段页表项的地址偏移,最后将段页表项的地址偏移+临时内核页表地址得到0xc0000000所在段的段页表项的物理地址,并放到r0中,而r6中存放内核映射区的末尾地址。
  2. 将DDR起始物理地址(r8)或上MMU的表示(r7),得到0xc0000000所在段的段页表项内容,存放到r3中。
  3. 将内核映射区的末尾地址(r6)左移(SECTION_SHIFT - PMD_ORDER)后得到其所在段的段页表项的物理地址
  4. 将r3存入当前段页表项中([r0]),然后将r0加上4,得到下一个段页表项的地址,更新r3中的页表项值为下一个段的页表项值,也就是直接加上,判断是否已经到达内核映射区的末尾,如果不是就进入下一个循环。

从上面可以看出,这段主要是完成对kernel内核空间进行映射,我们可以通过内核的System.map文件可以看出内核的起始和结束地址为

c0008000 T _text
c10e8eec B _end

其相应在物理地址上的内存区域是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]

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

总结,create_page_table完成了3种地址映射的页表空间:

  1. turn_mmu_on所映射的1M空间的屏映射:那么为什么要做映射呢?在执行开启MMU指令之前,CPU取指是在0x80008000附件,如果只做kernel_image的映射,开启MMU后,CPU所看到的地址就全变了,那么就可能无法执行。完成平映射后,就可以完美解决从0x8xxxxxxx到0xcxxxxxxx的过渡。

  2. kernel_image的线性映射:kernel编译链接的入口地址在0xc0008000,但其物理地址不等于链接的虚拟地址,需要将物理地址映射到对应的虚拟地址空间。

  3. atags(DTB)所在的1M空间的线性映射:当MMU开启后,内核只能访问虚拟地址空间,无法访问物理地址空间,所以就需要做相应的映射。
    在这里插入图片描述

https://blog.csdn.net/u012489236/article/details/104595925
https://blog.csdn.net/abeldeng/article/details/80001771

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值