内核态内存映射
内核页表
在系统初始化的时候,我们就要创建内核页表了。
内核页表的根 swapper_pg_dir, 在 arch/x86/include/asm/pgtable_64.h 中就能找到它的定义。swapper_pg_dir 指向内核最顶级的目录 pgd,同时出现的还有几个页表目录。其中 XXX_ident_pgt 对应的是直接映射区,XXX_kernel_pgt 对应的是内核代码区,XXX_fixmap_pgt 对应的是固定映射区。
在汇编语言的文件里面的 arch\x86\kernel\head_64.S初始化这些页表的值,具体的汇编语言看不懂,呵呵。知道他们定义在__INITDATA 里面就可以了。页表的根其实是全局变量,这就使得我们初始化的时候,甚至内存管理还没有初始化的时候,很容易就可以定位到。最后定义成如下 一个树形的结构:
内核页表定义完了,一开始这里面的页表能够覆盖的内存范围比较小。例如,内核代码区 512M,直接映射区 1G。这个时候,其实只要能够映射基本的内核代码和数据结构就可以了。可以看出,里面还空着很多项,可以用于将来映射巨大的内核虚拟地址空间,等用到的时候再进行映射。
用户态进程页表,会有 mm_struct 指向进程顶级目录 pgd,对于内核来讲,也定义了一个 mm_struct,指向 swapper_pg_dir,名为init_mm。
在初始化的时候会调用setup_arch初始化init_mm的成员,创建虚拟地址和物理地址的映射页表。
vmalloc 和 kmap_atomic 原理
在虚拟地址空间里面,有个 vmalloc 区域,从 VMALLOC_START 开始到 VMALLOC_END,可以用于映射一段物理内存。
从vmalloc的函数注释可以看出, 这个函数会从页分配器中分配足够大小的页,然后映射到vmalloc区域
1 | /** |
内核的临时映射函数 kmap_atomic
如果是 32 位有高端地址的,就需要调用 set_pte 通过内核页表进行临时映射;如果是 64 位没有高端地址的,就调用 page_address,里面会调用 lowmem_page_address。其实低端内存的映射,会直接使用 __va 进行临时映射。
内核态缺页异常
kmap_atomic 发现,没有页表的时候,就直接创建页表进行映射了。而 vmalloc 没有,它只分配了内核的虚拟地址。所以,访问它的时候,会产生缺页异常。内核态的缺页异常还是会调用 do_page_fault,然后调用vmalloc_fault, 关联内核页表项。
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。