一篇解析Linux paging_init
说明:
Kernel版本:4.14
ARM64处理器,Contex-A53,双核
使用工具:Source Insight 3.5, Visio
1. 介绍
从详细讲解Linux物理内存初始化中,可知在paging_init调用之前,存放Kernel Image和DTB的两段物理内存区域可以访问了(相应的页表已经建立好)。尽管物理内存已经通过memblock_add添加进系统,但是这部分的物理内存到虚拟内存的映射还没有建立,可以通过memblock_alloc分配一段物理内存,但是还不能访问,一切还需要等待paging_init的执行。最终页表建立好后,可以通过虚拟地址去访问最终的物理地址了。
按照惯例,先上图,来一张ARM64内核的内存布局图片吧,最终的布局如下所示:

2. paging_init
paging_init源代码短小精悍,直接贴上来,分模块来介绍吧。
mark 1
:分配一页大小的物理内存存放pgd
;mark 2
:将内核的各个段进行映射;mark 3
:将memblock子系统添加的物理内存进行映射;mark 4
:切换页表,并将新建立的页表内容替换swappper_pg_dir
页表内容;
代码看起来费劲?图来了:

下边将对各个子模块进一步的分析。
【文章福利】小编推荐自己的Linux内核技术交流群:【749907784】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)


3. early_pgtable_alloc
这个模块与FIX MAP
映射区域相关,建议先阅读前文(二)Linux物理内存初始化
先上图:

FIX MAP
的区域划分从图中可以看出来
本函数会先分配物理内存,然后借用之前的全局页表bm_pte
,建立物理地址到虚拟地址的映射,这次映射的作用是为了去访问物理内存,把内存清零,所以它只是一个临时操作,操作完毕后,会调用pte_clear_fixmap()
来清除映射。
early_pgtable_alloc
之后,我们看到paging_init
调用了pgd_set_fixmap
函数,这个函数调用完后,通过memblock_alloc
分配的物理内存,最终就会用来存放pgd table
了,这片区域的内容最后也会拷贝到swapper_pg_dir
中去。
4. map_kernel
map_kernel
的主要工作是完成内核中各个段的映射,此外还包括了FIXADDR_START
虚拟地址的映射,如下图:

映射完成之后,可以看一下具体各个段的区域,以我自己使用的平台为例:

这些地址信息也能从System.map
文件中找到。
aarch64-linux-gnu-objdump -x vmlinux
能查看更详细的地址信息。
5. map_mem
从函数名字中可以看出,map_mem
主要完成的是物理内存的映射,这部分的物理内存是通过memblock_add
添加到系统中的,当对应的memblock设置了MEMBLOCK_NOMAP
的标志时,则不对其进行地址映射。
map_mem
函数中,会遍历memblock中的各个块,然后调用__map_memblock
来完成实际的映射操作。先来一张效果图:

map_mem
都是将物理地址映射到线性区域中,我们也发现了Kernel Image
中的text, rodata
段映射了两次,原因是其他的子系统,比如hibernate
,会映射到线性区域中,可能需要线性区域的地址来引用内核的text, rodata
,映射的时候也会限制成了只读/不可执行
,防止意外修改或执行。
map_kernel
和map_mem
函数中的页表映射,最终都是调用__create_pgd_mapping
函数实现的:

总体来说,就是逐级页表建立映射关系,同时中间会进行权限的控制等。细节不再赘述,代码结合图片阅读,效果会更佳噢。
6. 页表替换及内存释放
这部分代码不多,不上图了,看代码吧:
简单来说,将新建立好的pgd页表内容,拷贝到swapper_pg_dir
中,也就是覆盖掉之前的临时页表了。当拷贝完成后,显而易见的是,我们可以把paging_init
一开始分配的物理内存给释放掉。
此外,在之前的文章也分析过swapper_pg_dir
页表存放的时候,是连续存放的pgd, pud, pmd
等,现在只需要复用swapper_pg_dir
,其余的当然也是可以释放的了。
原文作者:LoyenWang
