作者简介
于浩进,linux内核爱好者,现就职于北京灵汐科技有限公司,任职BSP工程师,主要负责IP验证、多媒体驱动开发及一些bring up等工作。
文章大纲
1.背景介绍
2.环境说明
2.1 硬件环境
2.2 Kernel 版本
2.3 kernel 相关配置介绍
3.fixmap 机制介绍
3.1 虚拟空间拓扑
3.1.1 VA=39bit 下 kernel 虚拟地址空间拓扑
3.1.2 FIXMAP 地址空间拓扑
3.1.3 FIXMAP 初始化
3.2 fixmap 在 early ioremap 应用介绍
3.2.1 early_ioremap_setup()
3.2.2 __early_ioremap()
3.3 fixmap 在 early console 应用介绍
3.4 fixmap 在 device-tree 应用介绍
3.4.1 映射过程分析
3.5 fixmap 在 paging_init 中页表切换介绍
3.5.1 paging_init 函数简单分析
3.5.2 paging_init 中的 fixmap
4.小结
5.参考文章
01、背景介绍
Fixmap机制是kernel在启动过程中(start_kernel)临时的映射机制,目的是在真正页表建立之前用于完成对io设备的访问、device-tree的解析以及paging_init中的页表切换等。本文将对该机制做一个深入的分析。
02、环境说明
2.1硬件环境
某SOC芯片,CPU为8核cortex-A53,其DDR物理地址为0x800000000,device-tree存放的物理地址为0x843000000。
2.2kernel版本
4.19.83版本。
2.3kernel相关配置介绍
相关Config配置
相关宏配置
以下宏的值,只给出结果了:
03、虚拟空间拓扑
3.1虚拟空间拓扑
3.1.1 VA=39bit下kernel虚拟地址空间拓扑
图1详细展示了VA=39bit下kernel虚拟地址空间拓扑,里面展示了FIXMAP区域在整个虚拟地址空间所处的位置。
3.1.2 FIXMAP地址空间拓扑
Kernel对Fixmap区域做了进一步的划分,各区间是在enum fixed_addresses 枚举类型定义的(/arch/arm64/include/asm/fixmap.h)。
其各个区间的virtual address通过fix_to_virt(const unsigned int idx)函数获得,其定义是在/include/asm-generic/fixmap.h里面,这个函数后面会用到。
下图2详细展示了各个区间的base address。
Fix_to_virt的定义如下:
3.1.3 FIXMAP初始化
Bm_pte、bm_pmd、Bm_pte为三个全局数组,用于暂存pud、pmd、
pte的页表。
early_fixmap_init()函数完成了fixmap映射的基础框架,如下图3所示,bm_pte数组并没有填值,因为当前还不知道哪些物理地址需要映射,等需要映射时候再去填写bm_pte的entry。
经过分析代码,整理了fixmap各段虚拟地址与bm_pmd entry的关系,如下图4所示:
需要说明的是FIX_PGD~FIX_FDT在bm_pmd是属于同一个entry,即可以用bm_pte做pte映射。
FIX_FDT~FIX_HOLE不属于该entry,即不可以用bm_pte做pte映射,也为后面device-tree的映射做了一个铺垫。
3.2fixmap在early ioremap应用介绍
3.2.1 early_ioremap_setup()
该函数的比较简单,主要是依靠__fix_to_virt()给slot_virt[i]填入虚拟地址,其布局如下图5所示。
slot[i]是fix_map区域已经规划好的虚拟地址范围,任何I/O地址空间都可以向这7个slot空间做映射。
其中:slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i),__fix_to_virt()在之前已经介绍过。Slot_virt每个区间size为256K。
3.2.2 __early_ioremap()
有三个数组需要说明:
slot_virt[slot]:BTMAP区域各个区间虚拟地址;
prev_map[slot]:__early_ioremap()映射后的虚拟地址;
prev_size[slot]:__early_ioremap()要映射的size;
映射流程如下图6所示:
Figure 6 early ioremap映射流程图
图7展示了early ioremap页表转换过程,还是比较简单的。
3.3fixmap在early console应用介绍
Early console的映射与early ioremap的映射类似,通过__fix_to_vit(FIX_EARLYCON_MEM_BASE)获取虚拟地址,物理地址为UART在SOC的实际分配的地址(该物理地址来自于command line的earlycon=XXX),然后通过向bm_pte写入页表,即可以完成映射。
图8是函数调用关系。图9是页表的建立和转换过程。
3.4 fixmap在device-tree应用介绍
3.4.1 映射过程分析
Device-tree的映射和early-console、early-ioremap的映射原理有所不同,主要区别在于FIX_FDT空间对应的虚拟地址的pmd entry与FIXADDR_START对应的pmd entry是不同的。
通过分析kernel代码可知对于device-tree的映射需要建立一个2M的block entry即可,即在bm_pmd建立一个block entry。
如下图10所示,只需要找到pmdp,写入block entry的页表项即可。
那问题来了,pmdp的虚拟地址我们是知道的,对应的bm_pmd的entry的物理地址也能知道,但是两者之间的页表还未建立。
因此在用pmdp指针向bm_pmd写入block entry之前,必须要先建立pmdp的页表,这个页表建立过程就与early console的页表建立过程相同了。见下图11所示。
设备树页表的建立会调用到init_pmd()建立block entry,也就是下图12圈2对应的代码,圈1的代码就是对应上图11给pmdp建立页表的过程。
在写入block entry之后,pmdp也就无用了,圈3代码把刚才的pmdp的页表清除了,即把bm_pte对应的表项清除了。
最后再简单展示一下fixmap为设备树建立页表的函数调用关系,如下图13所示。
3.5fixmap在paging_init中页表切换介绍
3.5.1 paging_init函数简单分析
下图14是paging_init的代码分析。
圈1代码通过memblock分配器分配了一个物理页面,该页面暂存后面代码建立的页表;
圈2代码是通过fixmp机制把这个物理页面映射为虚拟地址;
圈3代码把kernel的镜像的一些代码段、数据做等做映射,在图1有说明;
圈4代码把memblock.memory类型的region区域做线性映射,比如设备树的memory节点的内存,会在此做线性映射,但是会排除代码段和只读数据段,具体细节,还请看源码;
圈5~圈7代码将暂存页表内容拷贝到swapper_pg_dir,同时切换ttbr寄存器,此后CPU发出的虚拟地址就可以通过这套新建的页表进行虚实转换了;
圈8清除pgdp的映射;
圈9代码释放刚才申请的物理页;
3.5.2 paging_init中的fixmap
上图 圈6代码是把临时页表拷贝到swapper_pg_dir,临时页表的物理页是memblock分配器获得的物理地址。
由于mmu已经开启,memcpy无法使用物理地址,所以必须要先用fixmap机制做该物理页面的映射,得到其虚拟地址,即pgd_set_fixmap(addr),其定义如下:
其是借助于fixmp的“FIX_PGD”区域做的映射,页表映射及转换过程如下图15所示。
04、小结
在进入start_kernel之前,head.S的“__primary_switch”已经开启mmu了,使能mmu之后CPU发出的ldr、str指令都为虚拟地址了,因此必须要提前建立好页表,mmu才能把虚拟地址转为物理地址,以访问真正的物理内存;
Fixmap用于在” earlyconsole”、” device-tree的解析”、” earlyioremap”、” paging_init的页表切换”等过程建立临时页表。
Fixmap机制实际就是为mmu做了相关的虚拟和物理地址的映射;
Bm_pmd、bm_pte是两个全局数组,用于存放pmd、pte的页表项;
05、参考文献
https://www.cnblogs.com/LoyenWang/p/11483948.html
https://www.cnblogs.com/LoyenWang/p/11440957.html
https://www.cnblogs.com/pengdonglin137/p/9157639.html