未知 / 系统语义

内核:从地址翻译开始

先看清总路线:程序使用虚拟地址,硬件执行翻译,内核准备映射并处理失败。DDR、GPU、IOMMU 和 NUMA 都在后面的分支上,不是今天必须一起拿下的前置知识。

讨论整理:2026-07-23 定位:学习模型 边界:不等于内核实现经验
本页记录当前能够重新推导的结构,也明确哪些地方仍只停留在概念层。它是一张路线图,不是完整操作系统课程。
本页目录

先固定一句话

内核不替每一次访存计算地址。内核建立页表、配置翻译入口并定义权限;MMU 在每次访存时自动执行翻译。翻译无法继续时,硬件把现场交给内核,内核决定修复还是拒绝。

01

从程序到内存颗粒

high-level 软件通常只需要面对进程虚拟地址。下面的层次并没有消失,只是被运行时、操作系统和硬件共同遮蔽。

一次普通访存经过的层次 越往右,越靠近硬件实现
程序指针 进程看到的地址
虚拟地址 VPN + offset
MMU / 页表 翻译与权限检查
物理地址 PPN + offset
内存控制器 通道、颗粒与互联

应用开发主要停在左侧;基础内核必须进入中间;DDR/GDDR 电气与封装细节位于更右侧。

当前必须建立的模型

  • 虚拟地址如何拆成页号和页内偏移;
  • 页表项如何给出物理页号和权限;
  • TLB、页表遍历和 page fault 的分工;
  • 内核如何建立、修改和撤销映射。

暂时可以后置

  • DDR 与 GDDR 的电气时序;
  • GPU 独立显存管理;
  • IOMMU、DMA 与设备地址空间;
  • NUMA 放置和异构缓存一致性。
02

地址不是内存条的编号

“物理地址”仍然是一个地址空间里的编号。多根 DIMM、多个通道和许多 DRAM 颗粒,可以被内存控制器交错组织成一套 CPU 物理地址空间。

一套物理地址空间,不等于只有一块物理内存。同样,“有多块物理内存”也不自动意味着软件必须看见多个互不相干的地址空间。

名称 回答的问题 谁主要处理
虚拟地址 这个进程认为数据在哪里? 编译器、运行时、内核、MMU
线性地址 x86 分段以后、分页以前的地址是多少? x86 分段单元
物理地址 请求最终落入 CPU 可寻址物理空间的哪个位置? MMU、互联、内存控制器
设备地址 / IOVA DMA 设备使用什么地址访问内存? 驱动、IOMMU、设备
GPU 地址 GPU 页表和内存域怎样解释地址? GPU MMU、驱动、运行时

因此,“physical”不是宇宙中唯一绝对的坐标。进入 DMA、独立 GPU 或异构系统以后,还会出现不同发起者各自的地址域,以及它们之间的映射。

03

分页的最小闭环

分页机制最核心的动作,就是用虚拟页号查到物理页号;页内偏移保持不变。

地址和页表项

4 KiB 页面下的抽象结构 低 12 位是页内偏移
VPN 虚拟页号
offset 页内偏移
查 TLB / 页表 VPN 作为查找线索
PTE PPN + R/W/X/U 等权限
PPN 物理页号
原 offset 数值不变
VA = VPN | page_offset
PTE[VPN] = PPN | permissions
PA = PPN | same_page_offset

TLB 与页表遍历

CPU 发出 VA
查询 TLB 近期翻译缓存
未命中 硬件遍历页表
得到 PA 并缓存翻译

TLB miss 不等于 page fault。TLB 没有缓存时,硬件仍可能从页表中找到有效 PTE;只有映射不存在、格式非法或权限不允许时,才需要产生异常。

04

缺页:硬件交接,内核决策

page fault 是当前指令触发的同步异常,不是网卡、时钟等外设异步送来的普通中断。

硬件负责

  1. 按页表规则自动翻译;
  2. 发现 PTE 无效或权限冲突;
  3. 保存故障地址、原因和指令现场;
  4. 切换到内核异常入口。

内核负责

  1. 判断地址是否合法;
  2. 分配页、读文件或执行 CoW;
  3. 更新 PTE 并处理 TLB;
  4. 重试指令,或通知 / 终止进程。
一次可恢复的缺页 原指令最终重新执行
load / store
page fault 映射或权限不满足
内核处理 补页、CoW 或拒绝
恢复现场 重新执行原指令

可能修复

  • 合法匿名页尚未分配;
  • 文件映射需要按需读入;
  • 写只读共享页触发 Copy-on-Write;
  • 栈在允许范围内增长。

不能修复

  • 地址不属于合法虚拟区域;
  • 用户态访问内核专属页;
  • 执行不可执行页面;
  • 权限冲突且没有合法恢复语义。
05

x86 与 Linux 0.11 的历史层次

逻辑地址 selector : offset
分段 segment base + offset
线性地址
分页 页表映射
物理地址
linear = segment_base + offset
physical = page_translate(linear)

为什么经常感觉它们“重叠”

现代 x86-64 普通代码中,大多数段基址被设为零,于是 offset 与线性地址在数值上相同。分页仍然存在,所以线性地址通常不等于物理地址。

Linux 0.11 不能完全按现代平坦模型理解

  • 内核代码段和数据段的基址接近零,内核里的 offset 与线性地址容易看成同一个数;
  • 每个用户任务通过自己的 LDT 获得不同段基址,放入不同的 64 MiB 线性区域;
  • 用户程序从零开始使用逻辑偏移,但分段后的线性地址并不一定从零开始;
  • 线性地址再通过页表落到实际物理页。

所以“Linux 把分段设平”是方向性的概括,但放到 Linux 0.11 用户进程上不能直接理解为所有段基址都等于零。

06

RISC-V 为什么更适合建立第一张图

RISC-V 没有 x86 的通用分段路径。基础分页模型可以直接理解成虚拟地址经多级页表翻译为物理地址。

Sv39 虚拟地址结构 三级 VPN 索引 + 12 位 offset
VPN[2] 第一级索引
VPN[1] 第二级索引
VPN[0] 第三级索引
offset 12 位
satp 分页模式、ASID、根页表物理页号
VPN[2] → VPN[1] → VPN[0] 逐级读取 PTE,找到叶子映射
PPN + offset 形成最终物理地址
RISC-V 构件 作用 对应心智位置
satp 选择 Bare / Sv39 等模式并指向根页表 类似“分页入口”
sfence.vma 页表变化后约束地址翻译缓存 TLB 一致性
stval 保存 page fault 相关地址 故障对象
scause 记录 trap 的原因 故障类型
sepc 保存发生异常的指令地址 恢复位置
R/W/X/U/A/D 读、写、执行、用户、访问和脏页状态 PTE 权限与状态

satp.MODE = Bare 时不经过这套分页翻译。进入 S/U 模式分页以后,仍然需要多级页表、TLB、ASID、权限、大页和异常处理;它是结构更直接,不是没有复杂度。

07

UMA 文章为什么既兴奋又让人困惑

文章把几条会相互影响、但并不等价的轴画成了一条演进线。先把它们拆开,图就容易重新落地。

维度 问题 可能的答案
物理内存池 CPU 与 GPU 是否访问同一批物理页面? 共享一池 / 各有一池
虚拟地址 同一个指针能否被不同计算单元理解? 统一 / 独立
缓存一致性 一方写入后,另一方怎样看见? 硬件一致 / 软件同步
封装 内存颗粒放在哪里? DIMM / 板载 / on-package
局部性 访问不同内存域的代价是否相同? UMA / NUMA 特征

分立 GPU

CPU RAM 与 VRAM 分离,常见路径需要显式复制或由运行时迁移。

共享物理内存

CPU 与集成 GPU 使用系统内存,但地址和同步语义仍取决于硬件与 API。

一致性互联

物理内存仍可分成多个域,却通过共享寻址与一致性互联形成统一编程模型。

统一内存没有消灭边界。它把显式复制的成本,移动到缓存一致性、同步、带宽竞争、内存放置、封装价格和升级能力上。

基础内核首先处理 CPU 虚拟地址到 CPU 物理地址。做到设备、DMA、GPU 或 NUMA 时,才需要继续追问这个物理地址最终属于 DDR、GDDR、HBM,还是另一个可一致访问的内存域。

08

驱动不是统一难度的一门课

“写驱动要理解内存这么细”需要按设备类型回答。简单 UART 与现代 GPU 驱动并不处于同一个复杂度等级。

UART / GPIO MMIO、寄存器、中断、基本并发
网卡 / NVMe DMA、描述符环、内存屏障、IOMMU、队列生命周期
GPU 设备页表、显存、调度、命令提交、同步、固件、显示和电源管理

Linux 的驱动不是内核旁边的一套附属代码,它们就是内核的重要组成部分。核心分页、调度和系统调用的概念可以相对紧凑;真正把大量硬件、并发、热插拔、电源与失败状态接进同一系统,规模会迅速增长。

一个现代 GPU 驱动完全可能比教学内核复杂得多,但这不构成学习基础页表或 UART 的前置条件。

09

一条可以停下来的学习路径

先让每一层形成一个可运行、可解释的小闭环,再决定是否进入下一层。

  1. 启动与异常:看见系统从固件进入内核,能打印一次 trap 的原因和现场。
  2. 物理页管理:从固件内存图中识别可用区域,能分配并归还一个物理页。
  3. 页表:手工推演一个 VA,建立映射,验证权限并解释 TLB 刷新。
  4. 用户地址空间:让用户程序进入 U-mode,通过 syscall 回到内核。
  5. page fault:区分合法按需分配、CoW 和非法访问,验证恢复或终止。
  6. 进程与调度:再进入上下文保存、状态迁移、等待和回收。
  7. 文件与设备:根据兴趣选择文件系统块映射或一个简单 UART 驱动。

RISC-V 可以提供更干净的第一条路线;Linux 0.11 适合观察真实历史结构。二者不必先后通读,可以围绕同一个具体问题互相解释。

10

一页摘要

虚拟地址 = VPN + offset
PTE       = PPN + permissions
物理地址 = PPN + original offset
  • TLB 是翻译缓存;TLB miss 不等于 page fault。
  • 硬件执行页表遍历和权限检查;失败时产生同步异常。
  • 内核建立映射、处理 CoW / 文件读入,或拒绝非法访问。
  • x86 还要考虑逻辑地址经过分段形成线性地址;Linux 0.11 用户任务并非全部段基址为零。
  • RISC-V 没有通用分段,基础模型是 VA → satp / page table → PA
  • 物理地址空间不等于内存芯片数量;UMA 还涉及地址、物理池、一致性、封装和局部性多条轴。
  • 基础内核不要求先懂 GPU、IOMMU 和 NUMA;真实碰到对应设备和系统问题时再进入。

现在需要守住的不是全部寄存器名称,而是能够从“VPN 如何变成 PPN”重新推导硬件、页表和异常处理的分工。