Fixmap机制深入分析

2021-05-31 09:47:43 浏览数 (1)

作者简介

于浩进,linux内核爱好者,现就职于北京灵汐科技有限公司,任职BSP工程师,主要负责IP验证、多媒体驱动开发及一些bring up等工作。


01 背景介绍

Fixmap机制是kernel在启动过程中(start_kernel)临时的映射机制,目的是在真正页表建立之前用于完成对io设备的访问、device-tree的解析以及paging_init中的页表切换等。本文将对该机制做一个深入的分析。

02 环境说明

2.1 硬件环境

某SOC芯片,CPU为8核cortex-A53,其DDR物理地址为0x800000000,device-tree存放的物理地址为0x843000000。

2.2 kernel版本

4.19.83版本。

2.3 kernel相关配置介绍

  • 相关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_FDTbm_pmd是属于同一个entry,即可以用bm_pte做pte映射。

FIX_FDT~FIX_HOLE不属于该entry,即不可以用bm_pte做pte映射,也为后面device-tree的映射做了一个铺垫。

3.2 fixmap在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.3 fixmap在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.5 fixmap在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

armv8_arm.pdf,从ARM官网下载即可。

0 人点赞