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(如堆、栈)。
#嵌入式##嵌入式笔面经分享##牛客在线求职答疑中心##牛客创作赏金赛#