Linux 内核源码分析-内存分页机制
准备
内核版本: 4.20.1
在Linux中,所有进程共享内核虚拟地址空间,每个进程都有独立的虚拟地址空间,即逻辑地址,数据的寻址是将逻辑地址经过MMU
硬件设备转换为物理地址,物理地址即为物理内存的实际地址。在逻辑地址与物理地址之间还存在一个线性地址,这是体系结构中的分段机制设计,为了简化,Linux中的逻辑地址和线性地址的地址值相同的。
Linux的分页机制用来实现以页(Page)为单位的虚拟内存系统,而具体的寻址方法则是逻辑地址经过分页机制的处理转换为物理地址。
硬件基础
Linux的分页机制离不开硬件的支持,而其中最重要的部分就是下面CPU中的几个控制寄存器:
CR0
:11个标志位,每个bit为代表不同的意义,具体详情:wikiCR1
:Intel预留的控制器,暂无任何作用。CR2
:页故障线性地址寄存器,保存最后一次出现页故障的地址。CR3
:页目录基址寄存器,保存页目录表的物理地址。
分页机制
CR0
的第31位假如为1
,即开启分页机制:
If 1, enable paging and use the CR3 register, else disable paging.
四级页表结构:
我们以最常用的x86
体系结构为例,它采取的分级策略: arch/x86/Kconfig
1 | config PGTABLE_LEVELS |
分别有2,3,4,5级结构,而最常用的x86_64
是4级页表结构,我们后面的介绍全部以x86_64
的4级页表结构为例。
四种类型的页表:
- 页全局目录(Page Global Directory)
- 页上层目录(Page Upper Directory)
- 页中间目录(Page Middle Derectory)
- 页表(Page Table)
下图是x86_64
的四级页表模型
其中Page Global Directory包含Page Upper Directory的地址,而Page Middle Derectory又包括Page Middle Derectory的地址,Page Middle Derectory包含Page Table的地址,其中每个Page Table对应一个Page Frame即物理页. 因此一个线性地址被分为5个部分。
四种类型的页表数据结构
pgd_t
,pud_t
,pmd_t
,pte_t
分别是四种页面的数据结构,定义如下:
1 | typedef struct { pgdval_t pgd; } pgd_t; |
其中pgdval_t
,pudval_t
,pmdval_t
,pteval_t
的类型全部为unsigned long
.
分页机制寻址过程
查询页表,把虚拟地址转换成物理地址的过程如下:
每个进程都有独立的页表, 进程的mm_struct
的成员pgd
指向页全局目录.
- 由
pgd
指向的页全局目录和页全局目录索引得到页全局目录项. - 由页上层目录索引得到页上册目录项.
- 由页中间目录索引得到页中间目录项.
- 由页表索引得到页表项.
- 由页内偏移得到具体的物理页.
通过逻辑地址查找页表Page Table
下面是基于x86
体系结构,通过逻辑地址address
查找Page Table
指针的过程:
1 | static int __follow_pte_pmd(struct mm_struct *mm, unsigned long address, |
- 假如返回的目录项不存在,
pgd_none()
,pud_none
和pmd_none
返回1
. pte_present
宏的值为 1 或 0,表示_PAGE_PRESENT
标志位。如果页表项不为 0,但标志位pte_present()
的值为 0,则表示映射已经建立,但所映射的物理页面不在内存。
Page Table转为物理地址
1 | int follow_phys(struct vm_area_struct *vma, |
页表是一个元素为页表条目(Page Table Entry, PTE)的集合,每个虚拟页在页表中一个固定偏移量的位置上都有一个PTE. 所以两个虚拟地址是存在可能映射到同一个物理地址的.
更新CR3寄存器
当进程切换时,Linux内核会更新CR3
寄存器的值:
1 | /* Force ASID 0 and force a TLB flush. */ |
总结
我们通过内核代码与硬件机制结合的方式介绍了Linux内核的分页机制,根据分页机制的寻址过程,我们可以更直观的了解如何通过4级的分页映射从逻辑地址得到物理地址。