Linux内核6.6 内存管理(4)页表映射

3.本文主要讨论arm64架构

1.概念:

页表映射:虚拟内存到物理内存转换的机制。将连续的虚拟地址空间映射到离散的物理内存页(内存隔离/保护/高效利用)

虚拟地址:进程看到的地址空间(64位系统的0x0到0xffff ffff ffff ffff)

物理地址:实际内存芯片的地址

页(page):内存管理基本单位(4KB/2MB/1GB),虚拟地址空间和物理地址空间都被划分为固定大小的页

页表(page table):存储虚拟页到物理页的映射关系

2.页表结构基础

ARM64 采用 四级页表结构(VA 64 位,默认配置下常用三级或四级):

TTBR0_EL1:用户空间页表基址寄存器(EL1 特权级)。

TTBR1_EL1:内核空间页表基址寄存器(EL1 特权级,可选,Linux 内核通常只用 TTBR0_EL1)。

页表级别:PGD(页目录)、PUD(页上层目录)、PMD(页中间目录)、PTE(页表项)。

—————————————————————————页表,页表项,物理地址—————————————————————

每级页表的表项(如 PGD 表项、PUD 表项等 ),核心作用是存储下一级页表的物理基地址,构成 “指引链”。比如:

  • PGD 基地址:存储在 CPU 的TTBR0_EL1/TTBR1_EL1 寄存器中,指向顶级页表(PGD)的物理地址。

PGD物理地址(需要被虚拟地址转化)+L0索引 = PGD表项

  • PGD 页表项:存放下一级 PUD 页表的物理基地址
  • PUD 页表项:存放下一级 PMD 页表的物理基地址
  • PMD 页表项:存放下一级 PTE 页表的物理基地址
  • PTE 页表项:存放物理页框号(PFN)(最终映射物理地址 )

流程类似 “找地图”:PGD 表项告诉你 “PUD 页表在物理内存的 XX 地址”,PUD 表项告诉你 “PMD 页表在 XX 地址” ,直到 PTE 表项告诉你 “物理页在 XX 地址”

4级页表(普遍)地址转换过程

虚拟地址 =     [PGD索引]      [PUD索引]    [PMD索引]      [PTE索引]                          [页内偏移]
   |            |                 |           |             |                                   |
   v            v                 v           v             v                                   v
 PGD表项      --->    PUD表项  ---> PMD表项  --->  PTE表项  --->物理页号(PFN)+左移12位(4KB)后+页内偏移

3.缺页映射创建pte

这个是缺页中断(10)那篇文章中缺页异常的架构,这个是按需映射,当进程首次访问新映射的虚拟地址时,触发缺页异常时,会分配物理页并建立映射

do_page_fault(处理物理页的错误)
	handle_mm_fault
		__handle_mm_fault(处理进程的整个虚拟内存错误)
			handle_pte_fault(处理PTE错误)
				do_pte_missing 处理页表项(PTE)缺失错误
					do_anonymous_page(匿名页面缺页)
						── 检查是否为共享映射(VM_SHARED)
						│   └── 是 → 返回 VM_FAULT_SIGBUS
						├── 分配 PTE 表 pte_alloc
						├── 是否为读操作且允许零页?
						│   ├── 是 → 映射到零页 → 跳转到 setpte
						│   └── 否 → 分配物理页(alloc_anon_folio)
						├── 创建 PTE 并设置权限 mk_pte/pte_sw_mkyoung/pte_mkwrite(可写、脏标志)
						├── 锁定并获取 PTE 指针(支持批量映射) pte_offset_map_lock
						├── 更新 TLB
						└── 返回成功
					do_fault(文件页面缺页)

4.页表操作底层实现

页表项类型
typedef struct { unsigned long pgd; } pgd_t;//现在6.6用的是pgdval_t pgd,本质就是u64
typedef struct { unsigned long pud; } pud_t;
typedef struct { unsigned long pmd; } pmd_t;
typedef struct { unsigned long pte; } pte_t;

//页表初始化
pgd_alloc(struct mm_struct *mm)//分配页目录(PGD)表项,初始化用户空间页表的顶级结构。
pud_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long addr)//根据pud表项和虚拟地址,返回pud表的物理基地址
pmd_alloc
pte_alloc(struct mm_struct *mm, pmd_t *pmd, unsigned long addr)//返回pte物理基地址

//计算索引
pgd_offset_k
pud_offset
pmd_offset
set_pte_at

// 设置页表项
set_ptes 是 Linux 内核中高效处理连续页映射的核心函数
set_pte_at 是 set_ptes的特化,单页
用户虚拟地址
├── 计算PGD索引 ── pgd_offset_k()
│   └── 检查PGD存在?
│       ├── 是 ── 获取PGD项
│       └── 否 ── 分配PGD页表(pgd_alloc())
├── 计算PUD索引 ── pud_offset()
│   └── 检查PUD存在?
│       ├── 是 ── 获取PUD项
│       └── 否 ── 分配PUD页表(pud_alloc())
├── 计算PMD索引 ── pmd_offset()
│   └── 检查PMD存在?
│       ├── 是 ── 获取PMD项
│       └── 否 ── 分配PMD页表(pmd_alloc())
├── 计算PTE索引 ── pte_offset()
│   └── 检查PTE存在?
│       ├── 是 ── 获取PTE项
│       └── 否 ── 分配PTE页表(pte_alloc())
├── 设置PTE内容 ── set_pte_at()
├── 刷新TLB ── flush_tlb_page()
└── 完成映射

相同步骤比较多,只拿pgd->pud举例

pgd_index就是计算索引的,pgd_offset_pgd() 的返回值是:

PGD 表中某个条目的地址,这个条目(pgd_t 类型)通常指向下一级页表,也就是 PUD(Page Upper Directory)。

3.有关虚拟页的数据结构

3.1 vm_area_struct (VMA) 连续的虚拟地址空间及其属性

内核将进程的虚拟地址空间划分为多个VMA,每个VMA代表一个逻辑上的内存区域(.text .data stack 映射文件等)

3.2 mm_struct 进程的所有虚拟内存区域

每个进程都有一个mm_struct,通过它可以访问进程的所有虚拟内存区域和页表

3.3 page table 虚拟地址到物理地址的映射

4.内存映射的两种类型

4.1文件映射(File Mapping)

将文件内容直接映射到虚拟地址空间,读写内存相当于读写文件。

4.2匿名映射(Anonymous Mapping)

不关联具体文件,分配的内存初始化为 0(如堆、栈)。

#嵌入式##嵌入式笔面经分享##牛客在线求职答疑中心##牛客创作赏金赛#
全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务