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(如堆、栈)。

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

相关推荐

昨天 18:39
已编辑
蚌埠坦克学院 C++
笔试(感觉算一面):开共享屏幕,手写 std::vector。  只学过理论,没手写过,结果全用 C 实现了,用的全是 memset 那一坨,只支持平凡类型。最后面试官提醒我才想到,不过没给时间重新写了。---次日一面,问得挺深的,很多追问。  面试官没开摄像头。最后没给反问、没提公司,感觉也不是太满意,应该是寄了。- 复盘笔试。- vector 扩容机制?- 对存储的类的构造函数有什么要求?(何时移动,何时拷贝)- TCP 和 UDP 的区别。- 模板实例化时机。- 模板与宏的编译有什么区别? - 拷贝构造函数。    无限递归(循环拷贝)问题。没答上来。问实习:- 为什么离职?- 觉得比较有意义的产出。- 有没有用 git 多人协作经验。- git rebase 和 git merge 的区别。- 介绍下用的某个库。项目(webserver):点开仓库看代码,有点拷打。- 发送的时候,缓冲区满了怎么办?- 接收的时候,缓冲区满了怎么办?- epoll 水平触发和边缘触发。- onWrite 和 onRead 干了啥?- EAGAIN 和 EWOULDBLOCK 的处理。    有点看不懂当时怎么写的了,面试官怀疑我是直接抄的...- 怎么没有 client 实现?- 怎么处理粘包和拼接?- 有没有自己实现一套新的协议?- 线程池实现。有没有用信号量?    答没有,但面试官表示有,指里面的条件变量给我看。我以为说的 semaphore... 更怀疑我是直接抄的了。- 线程池构造和析构逻辑?怎么优雅退出?最后:- 问我简历上写的 io_uring 是什么,给他介绍了一下。      面试官表示这个应该没什么用。
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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