[Linux内核源码阅读笔记] 4 物理内存的管理

本文深入探讨Linux内核如何管理物理内存,从页表的结构、TLB管理到伙伴系统和slab分配器的工作原理。通过分析页表的分层结构和附加信息,了解地址映射和权限控制。接着,文章介绍TLB的管理和页表项的更新。随后,详述伙伴系统如何高效地分配和回收物理页,以及slab分配器在小内存分配中的角色。最后,讨论了不连续页分配器在处理内存碎片时的重要性。
摘要由CSDN通过智能技术生成

1 物理内存的管理

1.1 页表

页表用于记录虚拟地址到物理地址的一一映射关系。每一个页表项除了存放物理页帧的指针,多余的比特位还记录页的访问权限、状态等附加信息。最原始的页表实现思路就是有多少个物理页帧就直接定义多少个页表项,这导致会耗费大量的内存空间来存储这些页表项,然后其中可能有很多根本就不会用到。层次化的页表用于支持对大地址空间的快速、高效的管理。

早期Linux内核把页表分为2级,后来扩展到4级,4.11以后版本把页表扩展到5级,这5级分别为:

  • PGD(Page Global Directory)——页全局目录
  • P4D(Page 4th Directory)——页四级目录
  • PUD(Page Upper Directory)——页上层目录
  • PMD(Page Middle Directory)——页中间目录
  • PTE(Page Table)——直接页表

这里以4级页表为例进行一些简单的内核源码分析,且依然以arm64架构为例。

虚拟地址分解

4级页表将一个虚拟地址分解成5个部分,其中4个表项用于选择页,1个索引表示页内偏移,如下图:
在这里插入图片描述

内核中有关于上面各个部分的类似XXX_SHIFTXXX_SIZEXXX_MASK的定义,如:

/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT		CONFIG_ARM64_PAGE_SHIFT	// 内页偏移的位数
#define PAGE_SIZE		(_AC(1, UL) << PAGE_SHIFT)	// 一个页的大小(即一个页下有多少个地址),一个PTE项一个页
#define PAGE_MASK		(~(PAGE_SIZE-1))		// 位掩码用于提取各分量
#define PMD_SIZE		(_AC(1, UL) << PMD_SHIFT)	// 一个PMD项下有多少个地址
#define PMD_MASK		(~(PMD_SIZE-1))
#define PUD_SIZE		(_AC(1, UL) << PUD_SHIFT)
#define PUD_MASK		(~(PUD_SIZE-1))
#define PGDIR_SIZE		(_AC(1, UL) << PGDIR_SHIFT)
#define PGDIR_MASK		(~(PGDIR_SIZE-1))

个人对于用位掩码用于提取各分量还是有疑惑的。将一个虚拟地址vaddr和某个表项的位掩码做与运算得到的并不是地址中该表项所占位置的值。如vaddr & PMD_MASK得到的并不是PMD位置的值。要提取PMD位置的值应该用pmd_index()函数。

此外还有PTRS_PER_PGD指定了页全局目录中项的数目,PTRS_PER_PUD对应于页上层目录中项的数目,PTRS_PER_PMD对应于页中间目录中项的数目,PTRS_PER_PTE则是直接页表中项的数目。

内核还定义了4个数据结构(定义在page.h中)来表示页表项的结构。只不过在arm64架构下他们都是unsigned long long

typedef u64 pteval_t;
typedef u64 pmdval_t;
typedef u64 pudval_t;
typedef u64 pgdval_t;
/* These are used to make use of C type-checking.. */
typedef struct {
    pteval_t pte; } pte_t;
typedef struct {
    pmdval_t pmd; } pmd_t;
typedef struct {
    pudval_t pud; } pud_t;
typedef struct {
    pgdval_t pgd; } pgd_t;

使用struct而不是基本类型,以确保页表项的内容只能由相关的辅助函数处理,而决不能直接访问。

页表分析

内核源码还提供这些函数以方便进行页表的分析,以快速查找到虚拟地址对应的页表项:
在这里插入图片描述
可以简单分析一下xxx_indexxxx_offset函数。以pgd为例,pgd_index(addr)提取出该地址的bit位中PGD所占位置的值,借助pgd_index(addr),函数pgd_offset()可直接得出该地址在PGD目录下的下一级表项的虚拟空间地址(即PUD目录的地址)。之后再下级目录项的查找函数如pud_offset需要先找到目录所在物理地址再转换回虚拟地址,这种转换的调用__va()是采用线性映射。(可以理解为,页表项所在地址的映射不应依赖于页表本身,所以必须用线性映射)

/* to find an entry in a page-table-directory */
#define pgd_index(addr)		(((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
#define pgd_offset_raw(pgd, addr)	((pgd) + pgd_index(addr))
#define pgd_offset(mm, addr)	(pgd_offset_raw((mm)->pgd, (addr)))

#define pud_index(addr)		(((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1))
#define pud_offset_phys(dir, addr)	(pgd_page_paddr(*(dir)) + pud_index(addr) * sizeof(pud_t))
#define pud_offset(dir, addr)		((pud_t *)__va(pud_offset_phys((dir), (addr))))

#define pmd_index(addr)		(((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
#define pmd_offset_phys(dir, addr)	(pud_page_paddr(*(dir)) + pmd_index(addr) * sizeof(pmd_t))
#define pmd_offset(dir, addr)		((pmd_t *)__va(pmd_offset_phys((dir), (addr))))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值