02 - SVM相关的Linux内核基础

难度 : 🟢🟡 入门到进阶
预计学习时间 : 1-2小时
前置知识: 操作系统基础、了解指针和内存概念


📋 概述

要深入理解AMDGPU SVM(Shared Virtual Memory)实现,我们需要掌握Linux内核的几个核心概念。本章将介绍虚拟内存管理、页表机制、MMU工作原理以及HMM框架等基础知识。这些知识是理解SVM如何在内核层面工作的关键。

不用担心,我们会用通俗的语言和大量图示来解释这些看似复杂的概念。


2.1 虚拟内存管理基础

为什么需要虚拟内存?

想象一下,如果程序直接使用物理地址会怎样:

复制代码
问题1: 程序A使用地址0x1000,程序B也想使用 → 冲突!
问题2: 物理内存用完了怎么办?
问题3: 如何保护程序之间不互相干扰?

虚拟内存解决了这些问题。

虚拟地址空间

每个进程都有自己独立的虚拟地址空间:

复制代码
进程A的视图:                进程B的视图:
+----------------+          +----------------+
| 0x7FFF_FFFF    |          | 0x7FFF_FFFF    |  ← 栈
|     ...        |          |     ...        |
| 0x0040_0000    |          | 0x0040_0000    |  ← 代码段
+----------------+          +----------------+
        ↓                          ↓
    (通过页表转换)              (通过页表转换)
        ↓                          ↓
+----------------------------------------+
|         物理内存 (共享)                  |
|  [进程A数据] [进程B数据] [内核] [空闲]     |
+----------------------------------------+

Linux虚拟内存布局(x86-64)

复制代码
0xFFFFFFFFFFFFFFFF  ┌──────────────────┐
                    │   内核空间        │  (内核代码和数据)
0xFFFF800000000000  ├──────────────────┤
                    │   空洞            │  (不可访问)
0x00007FFFFFFFFFFF  ├──────────────────┤
                    │   栈 (向下增长)    │  ← Stack
                    │        ↓         │
                    │       ...        │
                    │        ↑         │
                    │   堆 (向上增长)    │  ← Heap (malloc)
                    ├──────────────────┤
                    │   数据段 (.data)  │  ← 全局变量
                    ├──────────────────┤
                    │   代码段 (.text)  │  ← 程序代码
0x0000000000400000  └──────────────────┘

关键概念

1. 虚拟地址 (Virtual Address)

程序使用的地址,不是真实的物理地址。

c 复制代码
int *ptr = malloc(4);  // ptr是虚拟地址,如0x7f8a2c001000
2. 物理地址 (Physical Address)

实际的RAM或设备内存地址。

复制代码
物理地址: 0x8000_1000 → 对应DDR4的某个芯片的某个位置
3. 页 (Page)

内存管理的基本单位,通常是4KB(4096字节)。

复制代码
虚拟内存空间 = N个页面
一个页面 = 4096字节
页面对齐: 地址的低12位为0

例如: 0x12345000 是页面起始地址
     0x12345001 不是(在页面内的偏移1)
4. 页框 (Page Frame)

物理内存中的一个页大小的块。


2.2 页表和地址转换

页表的作用

页表是虚拟地址到物理地址的映射表:

复制代码
虚拟地址 --[页表查询]--> 物理地址

多级页表结构(x86-64四级页表)

为了节省内存,现代系统使用多级页表:

复制代码
虚拟地址 (64位):
┌──────┬──────┬──────┬──────┬──────┬──────────┐
│未使用 │ PGD  │ PUD  │ PMD  │ PTE  │  Offset  │
│16 bit│ 9bit │ 9bit │ 9bit │ 9bit │  12bit   │
└──────┴──────┴──────┴──────┴──────┴──────────┘
   ↓      ↓      ↓      ↓      ↓       ↓
       页表索引 (每级9位)      页内偏移

地址转换过程

复制代码
1. CR3寄存器 → 指向PGD (Page Global Directory)
2. 用虚拟地址的PGD部分索引 → 找到PUD地址
3. 用虚拟地址的PUD部分索引 → 找到PMD地址  
4. 用虚拟地址的PMD部分索引 → 找到PTE地址
5. 用虚拟地址的PTE部分索引 → 找到物理页框地址
6. 加上页内偏移 → 得到最终物理地址

图示

复制代码
虚拟地址: 0x00007F8A_2C001234
         
CR3 → PGD[127] → PUD[57] → PMD[88] → PTE[1] → 页框 + 0x234
                                                    ↓
                                         物理地址: 0x8000_1234

页表项(PTE)的结构

一个页表项不仅包含物理地址,还有很多标志位:

复制代码
PTE (64位):
┌─────────────────────┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│  物理页框号 (PFN)     │X│D│A│U│W│P│...  │
│     (52位)          │N│ │ │/│/│ │     │
└─────────────────────┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
                       │ │ │ │ │ └─ P: Present (是否在内存)
                       │ │ │ │ └─── R/W: 读/写权限
                       │ │ │ └───── U/S: 用户/内核
                       │ │ └─────── A: Accessed (已访问)
                       │ └───────── D: Dirty (已修改)
                       └─────────── XN: Execute Never

关键标志位

  • Present (P): 页面是否在物理内存中(如果不在,访问会触发缺页异常)
  • Read/Write (R/W): 是否可写
  • User/Supervisor (U/S): 用户态是否可访问
  • Accessed (A): 是否被访问过(用于LRU算法)
  • Dirty (D): 是否被修改过(用于回写)

SVM相关的页表

在SVM中,有两套页表:

复制代码
CPU侧:                        GPU侧:
┌──────────────┐             ┌──────────────┐
│  CPU页表      │             │  GPU页表     │
│  (MMU管理)    │             │  (IOMMU管理) │
└──────────────┘             └──────────────┘
       ↓                            ↓
    同一个虚拟地址 (0x7f8a2c001000)
       ↓                            ↓
┌──────────────┐             ┌──────────────┐
│系统RAM物理地址 │   或者       │ VRAM物理地址  │
└──────────────┘             └──────────────┘

挑战:如何保持两套页表的一致性?→ 这就是SVM驱动要解决的问题!


2.3 MMU和页面异常

MMU (Memory Management Unit)

MMU是CPU中负责地址转换的硬件单元:

复制代码
CPU指令: load R1, [0x7f8a2c001234]
          ↓
       虚拟地址
          ↓
┌──────────────────┐
│       MMU        │
│  ┌────────────┐  │
│  │ TLB (缓存)  │  │  ← 快速查找最近使用的转换
│  └────────────┘  │
│  ┌────────────┐  │
│  │页表遍历器    │  │  ← TLB miss时查询页表
│  └────────────┘  │
└──────────────────┘
          ↓
       物理地址
          ↓
       访问内存
TLB (Translation Lookaside Buffer)

TLB是页表的缓存,加速地址转换:

复制代码
访问地址 → 查TLB
           ↓
      TLB命中? 
     ↙        ↘
   Yes         No
    ↓           ↓
使用缓存    遍历页表 → 填充TLB
    ↓           ↓
  访问内存 ←───┘

性能影响

  • TLB命中:几个时钟周期
  • TLB未命中:几十到上百个时钟周期

页面异常(Page Fault)

当MMU发现页表项的Present位为0时,触发页面异常:

复制代码
CPU访问地址
    ↓
MMU查页表
    ↓
Present = 0?
    ↓ Yes
触发Page Fault异常
    ↓
陷入内核
    ↓
内核Page Fault处理程序
    ↓
分配物理页框 / 从磁盘加载 / 等
    ↓
更新页表: Present = 1
    ↓
返回用户态
    ↓
CPU重新执行访问 → 成功

页面异常的类型

1. Minor Page Fault(次要缺页)

页面在物理内存中,但页表未建立映射。

c 复制代码
// 例如:malloc后首次访问
char *p = malloc(4096);
p[0] = 'A';  // 触发minor fault,建立映射
2. Major Page Fault(主要缺页)

页面不在物理内存中,需要从磁盘加载。

c 复制代码
// 例如:访问被swap到磁盘的页面
// 或者mmap文件后首次访问
3. Segmentation Fault(段错误)

访问非法地址,权限不足等。

c 复制代码
int *p = NULL;
*p = 42;  // Segmentation Fault!

GPU的页面异常

GPU也有类似的机制(需要硬件支持,如AMD的XNACK):

复制代码
GPU执行: store [0x7f8a2c001234], R1
           ↓
    IOMMU查GPU页表
           ↓
    Present = 0?
           ↓ Yes
    触发GPU Page Fault
           ↓
    发送中断到CPU
           ↓
    驱动处理: svm_range_restore_pages()
           ↓
    迁移页面 / 建立映射
           ↓
    GPU重试访问 → 成功

关键差异

  • CPU页面异常处理在内核中(同步)
  • GPU页面异常通过中断通知CPU(异步)

2.4 DMA和IOMMU

DMA (Direct Memory Access)

DMA允许设备直接访问内存,无需CPU参与,CPU不必逐字节搬运数据,提高效率。

复制代码
传统方式:                    DMA方式:
┌────┐                      ┌────┐
│CPU │ ← 读 ← 设备           │CPU │ (做其他事)
└────┘  				    └────┘
  ↓                           ↑(完成后告知CPU)
  写 → 内存              设备 ──DMA──> 内存

IOMMU (Input/Output Memory Management Unit)

IOMMU是设备的MMU,提供地址转换和保护,具有如下作用:

  1. 地址转换:设备使用虚拟地址

  2. 内存保护:设备只能访问授权的内存

  3. 支持SVM:设备可以使用进程的虚拟地址空间

    没有IOMMU:
    设备 → 使用物理地址 → 直接访问内存
    问题: 设备可以访问任何物理地址,不安全!

    有IOMMU:
    设备 → 使用IOVA → IOMMU转换 → 物理地址

    (I/O Virtual Address)

IOMMU在SVM中的角色

复制代码
进程虚拟地址: 0x7f8a2c001000
        ↓
CPU访问: MMU转换
        ↓
GPU访问: IOMMU转换 (使用PASID识别进程)
        ↓
可能指向不同的物理位置:
  - 系统RAM: 通过PCIe访问
  - GPU VRAM: 直接访问
PASID (Process Address Space ID)

PASID标识进程的地址空间:

复制代码
GPU发起访问时携带PASID:
┌──────────────────────────┐
│ PASID=123 | VA=0x7f8a... │
└──────────────────────────┘
        ↓
IOMMU根据PASID选择页表
        ↓
使用进程123的页表进行转换

作用:让GPU可以同时处理多个进程的任务,每个使用各自的地址空间。


2.5 HMM框架介绍

什么是HMM?

HMM (Heterogeneous Memory Management) 是Linux内核提供的框架,用于支持异构设备(如GPU)访问系统内存。

引入背景

  • 多种设备(GPU、FPGA、DSP等)需要SVM功能
  • 避免每个驱动重复实现相同的功能
  • 提供统一的接口和抽象

HMM的核心功能

复制代码
┌────────────────────────────────┐
│         HMM框架                 │
├────────────────────────────────┤
│ • 镜像CPU页表到设备              │
│ • 迁移页面 (RAM ↔ 设备内存)       │
│ • 处理页面异常                   │
│ • MMU Notifier集成              │
└────────────────────────────────┘
        ↓           ↓
   ┌────────┐  ┌────────┐
   │GPU驱动  │  │FPGA驱动│  ...
   └────────┘  └────────┘

HMM关键数据结构

1. hmm_range

表示一个地址范围:

c 复制代码
struct hmm_range {
    unsigned long start;      // 起始虚拟地址
    unsigned long end;        // 结束虚拟地址
    unsigned long *pfns;      // 输出:页框号数组
    ...
};
2. hmm_range_fault()

核心函数,用于查询CPU页表并迁移页面。

c 复制代码
// AMDGPU SVM中的使用示例
ret = hmm_range_fault(&range);
// 返回后,pfns数组包含每个页面的状态和物理地址

HMM的工作流程

复制代码
1. 驱动调用hmm_range_fault()
        ↓
2. HMM遍历CPU页表
        ↓
3. 对于每个页面:
   - 如果不在内存 → 触发minor fault
   - 如果只读但需要写 → 触发写时复制
   - 记录页面状态到pfns数组
        ↓
4. (可选) 迁移页面到设备内存
        ↓
5. 驱动建立设备页表映射

Device Private Memory

HMM支持将设备内存(如GPU VRAM)整合到Linux内存管理中:

c 复制代码
// 在内核中,VRAM页面也有struct page
struct page *vram_page = pfn_to_page(vram_pfn);

// 但标记为设备私有
page->zone_device_data = svm_bo;  // 指向SVM BO

好处

  • 系统可以统一管理所有内存
  • 支持页面在RAM和VRAM间迁移
  • 可以使用标准的内存管理API

MMU Notifier

HMM内部使用MMU Notifier监听CPU页表变化:

c 复制代码
// 在kfd_svm.c中
static const struct mmu_interval_notifier_ops svm_range_mn_ops = {
    .invalidate = svm_range_cpu_invalidate_pagetables,
};

// 注册监听
mmu_interval_notifier_insert(&prange->notifier, mm,
                             start, length,
                             &svm_range_mn_ops);

工作原理

复制代码
CPU修改页表 (如munmap, mprotect)
        ↓
内核调用mmu_notifier_invalidate_range()
        ↓
HMM通知所有注册的notifier
        ↓
AMDGPU驱动的回调: svm_range_cpu_invalidate_pagetables()
        ↓
驱动使GPU页表失效,保持一致性

💡 重点提示

  1. 页表是核心:理解页表结构是理解SVM的基础。CPU页表和GPU页表需要保持一致。

  2. 页面异常是关键机制:无论是CPU还是GPU,页面异常都是延迟分配和按需迁移的基础。

  3. IOMMU使SVM成为可能:没有IOMMU,设备只能使用物理地址,无法共享虚拟地址空间。

  4. HMM简化了实现:使用HMM框架,驱动不需要从头实现所有功能。

  5. 异步是挑战:GPU页面异常是异步的,需要复杂的同步机制。


⚠️ 常见误区

误区1:"虚拟地址转换很慢"

  • ✅ 正确理解:有TLB缓存,大多数情况下转换很快(几个周期)。

误区2:"IOMMU就是设备的MMU"

  • ✅ 正确理解:功能类似,但IOMMU在系统芯片组中,不在设备内。

误区3:"HMM是AMD专有的"

  • ✅ 正确理解:HMM是Linux内核通用框架,Nvidia、Intel等也使用。

误区4:"页表遍历需要4次内存访问"

  • ✅ 正确理解:现代CPU有页表缓存(page walk cache),通常更快。

📝 实践练习

  1. 地址转换练习

    给定虚拟地址 0x00007f8a_2c001234,画出四级页表查询过程。

  2. 思考题

    • 为什么需要多级页表而不是单级?
    • TLB的命中率对性能有多大影响?
    • GPU页表可以和CPU页表完全相同吗?
  3. 代码阅读

    bash 复制代码
    # 查看HMM相关代码
    ls mm/hmm.c
    grep -n "hmm_range_fault" mm/hmm.c
  4. 实验(需要root):

    bash 复制代码
    # 查看进程的内存映射
    cat /proc/self/maps
    
    # 查看页表统计
    cat /proc/meminfo | grep -i pagetable

📚 本章小结

  • 虚拟内存:每个进程有独立的虚拟地址空间,通过页表映射到物理内存
  • 页表:多级结构,每个PTE包含物理地址和标志位
  • MMU/IOMMU:CPU和设备的地址转换单元,支持虚拟地址
  • 页面异常:访问未映射页面时触发,是按需分配的基础
  • HMM框架:Linux内核提供的异构内存管理框架,简化SVM实现

这些基础知识是理解AMDGPU SVM实现的必备前提。


📖 扩展阅读


➡️ 下一步

掌握了Linux内核基础后,我们将在下一章探讨AMDGPU驱动的整体架构,了解SVM在驱动中的位置以及与其他组件的关系。


🔗 导航

相关推荐
DeeplyMind1 天前
01 - 什么是SVM
svm·amdgpu·rocm·kfd
DeeplyMind5 天前
AMD ROCm-SVM技术的实现与应用深度分析目录
svm·rocm·kfd
DeeplyMind22 天前
AMD KFD的BO设计分析系列8-7:TLB管理与刷新
amdgpu·tlb·kfd
越努力越幸运~1 个月前
AMD AI MAX +395迷你主机 架构1151安装 vllm部署大模型操作记录
ai·vllm·rocm·ai max+395
DeeplyMind1 个月前
AMD KFD的BO设计分析系列7-2:GPU GART 实现深度解析--绑定机制与性能优化
驱动开发·amdgpu·kfd·gart
七宝大爷2 个月前
AMD ROCm生态介绍:开源的GPU计算平台
开源·cuda·amd·rocm·gpu内核3
DeeplyMind2 个月前
AMD rocr-libhsakmt分析系列3-1: Apertures
linux·amdgpu·rocm·kfd·rocr
DeeplyMind2 个月前
AMD rocr-libhsakmt分析系列6-2:共享机制-import
linux·amdgpu·dma-buf·rocm·kfd·rocr
DeeplyMind2 个月前
ROCm GPU间 P2P 能力确定机制分析
p2p·hip·rocm