TTM ttm_tt 技术分析系列2:概述与核心原理

ttm_tt核心原理解析

1. 概述

1.1 什么是 ttm_tt

ttm_tt 是 TTM 框架中的核心数据结构,负责管理系统内存/GTT内存对应的页表转换。

1.2 ttm_tt 的定位

在 GPU 内存管理体系中:

  • VRAM:显存,GPU 专用的高速本地内存
  • GTT (Graphics Translation Table):系统内存映射到 GPU 地址空间的区域
  • System Memory:普通系统 RAM

ttm_tt 专门管理不在 VRAM 中的 Buffer Object (BO),即存储在系统内存中但需要被 GPU 访问的对象。重要的事情说三遍:管理系统内存的bo,也就是上面的GTT和System Memory。大家都比较熟悉GTT,System Memory是啥?在AMD的驱动里,你就理解userptr bo,后文会专讲。

1.3 设计目标

  1. 统一页面管理:为系统内存页面提供统一的分配、映射、释放接口
  2. 地址空间映射:将系统内存映射到 GPU 可访问的地址空间
  3. 缓存策略控制:管理页面的缓存属性(cached/uncached/write-combined)
  4. Swap 支持:支持页面换出到交换空间以释放内存压力
  5. DMA 映射:处理 CPU 地址与设备 DMA 地址的转换

2. 核心数据结构

2.1 基础结构 struct ttm_tt

struct ttm_tt {
    struct ttm_bo_device *bdev;          // TTM 设备指针
    struct ttm_backend_func *func;       // 后端操作函数表
    struct page **pages;                 // 页面指针数组
    uint32_t page_flags;                 // 页面标志位
    unsigned long num_pages;             // 页面数量
    struct sg_table *sg;                 // scatter-gather 表 (用于 dma-buf)
    struct file *swap_storage;           // 交换存储文件
    enum ttm_caching_state caching_state; // 缓存状态
    enum {
        tt_bound,        // 已绑定到 aperture
        tt_unbound,      // 已分配但未绑定
        tt_unpopulated,  // 未分配页面
    } state;
};

前面说了ttm_tt负责管理系统内存里的bo,这是理解ttm_tt结构体设计的基础。系统内存的物理存储就是物理页面,所有字段的设计都围绕物理页面。下面花点篇幅分析下这些字段的设计需求,是磨刀不误砍柴功。

字段设计需求分析

1. struct ttm_bo_device *bdev - 设备上下文

需求背景:

  • ttm_tt 需要知道自己属于哪个 GPU 设备
  • 不同 GPU 有不同的内存管理策略(GART 大小、DMA 限制等)

设计理由:

  • 访问设备特定的内存管理器(VRAM/GTT manager)
  • 获取驱动回调函数(populate/bind 操作)
  • 查询硬件能力(DMA32 支持、IOMMU 状态等)

使用示例:

// 获取设备信息
struct amdgpu_device *adev = amdgpu_ttm_adev(ttm->bdev);
// 检查 DMA 限制
if (adev->need_dma32)
    page_flags |= TTM_PAGE_FLAG_DMA32;

2. struct ttm_backend_func *func - 驱动多态接口

需求背景:

  • 不同 GPU 厂商(AMD/NVIDIA/Intel)有不同的硬件页表机制
  • TTM 需要统一接口,但允许驱动自定义实现

设计理由:

  • 策略模式:TTM 核心只定义流程,具体操作由驱动实现
  • 灵活性:AMD 使用 GART,Intel 可能使用不同机制
  • 可扩展性:新硬件特性无需修改 TTM 核心代码

典型操作:

// TTM 核心调用
ttm->func->bind(ttm, bo_mem);    // 驱动实现 GART 绑定
ttm->func->unbind(ttm);          // 驱动实现 GART 解绑
ttm->func->destroy(ttm);         // 驱动释放私有资源

3. struct page **pages - 物理页面数组(核心)

需求背景:

  • GPU 需要访问系统内存中的数据
  • 系统内存可能是离散的物理页面
  • CPU 和 GPU 需要共享同一块物理内存

设计理由:

  • 页面级管理:Linux 内存管理的最小单位是页(通常 4KB)
  • 灵活性:支持不连续的物理内存(减少碎片化)
  • 共享性:CPU 可通过 vmap() 映射,GPU 通过 GART 映射
  • DMA 映射:每个页面可独立建立设备地址映射

内存布局:

pages[0] ──> [物理页 0xF0000000] ──┐
pages[1] ──> [物理页 0xF0004000]   ├─> 可能不连续
pages[2] ──> [物理页 0xF0100000] ──┘
...

4. uint32_t page_flags - 页面属性控制

需求背景:

  • 不同场景需要不同的内存分配策略
  • 需要记录页面的特殊状态(已换出、使用 SG 表等)

5. unsigned long num_pages - 页面数量

需求背景:

  • 需要知道分配了多少页面(循环遍历、内存统计)
  • 计算总大小、DMA 映射范围

设计理由:

  • 简单高效:避免重复计算 size >> PAGE_SHIFT
  • 边界检查:防止数组越界访问
  • 资源统计:快速计算内存占用

6. struct sg_table *sg - DMA-BUF 集成

需求背景:

  • 跨设备共享内存(摄像头→GPU、GPU→显示控制器)
  • 避免数据拷贝,提高性能
  • Linux DMA-BUF 框架使用 scatter-gather 表

设计理由:

  • 标准接口:与 DMA-BUF 框架对接
  • 零拷贝共享:多个设备访问同一块物理内存
  • 灵活映射:支持 IOMMU 地址转换

工作流程:

外部设备导出 dma_buf
    ↓
dma_buf_map_attachment() 返回 sg_table
    ↓
ttm->sg = sg_table
    ↓
从 sg_table 提取页面地址
    ↓
GPU 通过 GART 访问

7. struct file *swap_storage - 内存超额分配

需求背景:

  • GPU 应用可能需要超过物理内存的总容量
  • 长期未使用的 BO 占用宝贵的 RAM
  • 类似 CPU 内存的 swap 机制

设计理由:

  • 内存超额:支持总 BO 大小 > 物理内存
  • 透明换入换出:应用无感知
  • 持久化支持:进程退出后保留数据(PERSISTENT_SWAP 标志)

Swap 流程:

// 内存压力时
ttm_tt_swapout(ttm, swap_file)
  └─> 将 pages 内容写入 swap_storage
  └─> 释放 pages
  └─> 标记 SWAPPED

// 需要时
ttm_tt_swapin(ttm)
  └─> 重新分配 pages
  └─> 从 swap_storage 读取数据
  └─> 清除 SWAPPED

8. enum ttm_caching_state caching_state - 缓存一致性

需求背景:

  • CPU 和 GPU 共享内存时需要缓存一致性
  • 不同访问模式需要不同的缓存策略
  • 性能和一致性的权衡

设计理由:

  • 性能优化:write-combined 模式下 CPU 写入速度提升 5-10 倍
  • 一致性保证:uncached 模式确保 CPU/GPU 看到相同数据
  • 灵活选择:根据使用场景选择最优策略

场景对应:

CPU 频繁读写 → tt_cached      (最快 CPU 访问)
CPU 写入为主 → tt_wc          (优化写入,GPU 读取友好)
强一致性需求 → tt_uncached    (牺牲性能保证正确性)

9. enum { tt_bound, tt_unbound, tt_unpopulated } state - 生命周期管理

需求背景:

  • 延迟分配:创建时不占用内存,使用时才分配
  • 动态迁移:BO 可以在 VRAM 和 GTT 之间移动
  • 资源追踪:避免重复操作和资源泄漏

设计理由:

  • 状态机清晰:每个状态对应明确的操作集合
  • 延迟分配:unpopulated 时无 pages,节省内存
  • 错误检测:防止在错误状态执行操作(如绑定未分配的页面)

状态转换规则:

tt_unpopulated:
  - pages == NULL
  - 允许操作: populate
  - 禁止操作: bind, unbind

tt_unbound:
  - pages != NULL
  - 允许操作: bind, unpopulate
  - 禁止操作: 重复 populate

tt_bound:
  - pages != NULL && 已映射到 GPU
  - 允许操作: unbind
  - 禁止操作: 重复 bind

设计原则总结
  1. 最小化假设:bdev 指针支持多 GPU 系统
  2. 分离关注点:func 分离策略和实现
  3. 灵活性优先:pages 数组支持不连续内存
  4. 状态可追溯:state + page_flags 完整记录生命周期
  5. 跨系统集成:sg 表对接 DMA-BUF 框架
  6. 性能与功能平衡:caching_state 提供多种策略
  7. 资源管理:swap_storage 支持内存超额分配

这种设计使 ttm_tt 能够适应从嵌入式 GPU 到高性能服务器 GPU 的各种场景。

2.2 DMA 扩展结构 struct ttm_dma_tt

struct ttm_dma_tt {
    struct ttm_tt ttm;              // 基础 ttm_tt 结构
    dma_addr_t *dma_address;        // DMA 总线地址数组
    struct list_head pages_list;    // 页面链表
};

用途:

  • 在需要 DMA 映射的场景中使用
  • 维护每个页面对应的设备总线地址
  • 支持 IOMMU/SWIOTLB 等地址转换机制

2.3 AMD 驱动扩展 struct amdgpu_ttm_tt

struct amdgpu_ttm_tt {
    struct ttm_dma_tt ttm;           // 继承 DMA 扩展
    struct drm_gem_object *gobj;     // GEM 对象指针
    u64 offset;                      // GTT 偏移地址
    uint64_t userptr;                // 用户空间指针 (userptr 功能)
    struct task_struct *usertask;    // 关联的用户进程
    uint32_t userflags;              // 用户标志
    struct hmm_range *range;         // HMM 范围 (用于 userptr)
};

AMD 特有扩展:

  • userptr 支持:允许将用户空间内存直接映射给 GPU 使用
  • HMM 集成:使用 Heterogeneous Memory Management 追踪用户页面
  • offset 管理:记录在 GTT 地址空间中的偏移位置

3. 页面标志位

#define TTM_PAGE_FLAG_WRITE           (1 << 3)  // 可写标记
#define TTM_PAGE_FLAG_SWAPPED         (1 << 4)  // 已换出
#define TTM_PAGE_FLAG_PERSISTENT_SWAP (1 << 5)  // 持久化 swap
#define TTM_PAGE_FLAG_ZERO_ALLOC      (1 << 6)  // 分配时清零
#define TTM_PAGE_FLAG_DMA32           (1 << 7)  // 限制在 32 位 DMA 范围
#define TTM_PAGE_FLAG_SG              (1 << 8)  // 使用 scatter-gather
#define TTM_PAGE_FLAG_NO_RETRY        (1 << 9)  // 分配失败不重试

4. 缓存状态管理

enum ttm_caching_state {
    tt_uncached,  // 不缓存 - 最慢但保证一致性
    tt_wc,        // Write-Combine - 写合并,适合 GPU 写入
    tt_cached     // 完全缓存 - 最快但需要显式刷新
};

缓存策略选择原则:

  • tt_uncached: 用于频繁读写且需要强一致性的场景
  • tt_wc: 用于 GPU 频繁写入,CPU 少量读取的场景(最常见)
  • tt_cached: 用于 CPU 密集访问的共享内存

5. 后端操作接口

struct ttm_backend_func {
    int (*bind)(struct ttm_tt *ttm, struct ttm_mem_reg *bo_mem);
    int (*unbind)(struct ttm_tt *ttm);
    void (*destroy)(struct ttm_tt *ttm);
};

操作语义:

  • bind: 将页面绑定到 GPU aperture,建立地址映射关系
  • unbind: 解除绑定,释放 aperture 资源
  • destroy: 销毁 ttm_tt 对象,释放所有资源

6. 工作原理

6.1 生命周期状态机

tt_unpopulated (初始状态)
    ↓ ttm_tt_populate()
tt_unbound (页面已分配,未绑定)
    ↓ ttm_tt_bind()
tt_bound (已绑定到 GPU 地址空间)
    ↓ ttm_tt_unbind()
tt_unbound
    ↓ ttm_tt_unpopulate()
tt_unpopulated
    ↓ ttm_tt_destroy()
   [销毁]

6.2 核心操作流程

(1) 创建流程
ttm_tt_create()
  └─> driver->ttm_tt_create()  [AMD: amdgpu_ttm_tt_create]
       └─> 分配 amdgpu_ttm_tt 结构
       └─> 初始化 ttm_sg_tt_init()
       └─> 设置 backend_func
(2) 填充流程(Populate)
ttm_tt_populate()
  └─> driver->ttm_tt_populate()  [AMD: amdgpu_ttm_tt_populate]
       ├─> 普通分配: ttm_populate_and_map_pages()
       │    └─> alloc_page() 逐页分配
       │    └─> dma_map_page() 建立 DMA 映射
       ├─> userptr: 处理用户态内存
       │    └─> hmm_range_fault() 获取用户页面
       └─> dma-buf: 映射外部 buffer
            └─> dma_buf_map_attachment()
(3) 绑定流程(Bind)
ttm_tt_bind()
  └─> backend_func->bind()  [AMD: amdgpu_ttm_backend_bind]
       └─> 计算 PTE 标志位
       └─> amdgpu_gart_bind()
            └─> 更新 GART 页表
            └─> 刷新 TLB

6.3 地址空间映射

物理内存页面 (Physical Page)
    ↓ DMA Mapping
设备总线地址 (DMA Address)
    ↓ GART/IOMMU 映射
GPU 虚拟地址 (GPU VA)
    ↓ 页表转换
GPU 物理地址

7. 与其他组件的关系

   ┌─────────────────┐
   │ Buffer Object   │  用户层抽象
   │  (amdgpu_bo)    │
   └────────┬────────┘
            │
   ┌────────▼────────┐
   │ TTM BO          │  TTM 层缓冲对象
   │(ttm_buffer_obj) │
   └────────┬────────┘
            │
    ┌───────▼┬──────────┐
    │        │          │
┌───▼───┐ ┌──▼──┐ ┌─────▼────┐
│ VRAM  │ │ GTT │ │  System  │ 内存类型
└───────┘ └──┬──┘ └────┬─────┘
             │         │
        ┌────▼─────────▼────┐
        │    ttm_tt         │  页表管理
        └───────────────────┘
                 │
        ┌────────▼────────┐
        │  Page Array     │  物理页面
        └─────────────────┘

8. 关键特性

8.1 延迟分配

  • 创建 ttm_tt 时不立即分配页面
  • 首次绑定时才执行 populate 操作
  • 减少内存占用,提高资源利用率

8.2 Swap 支持

  • 可将长期未使用的页面换出到 swap 文件
  • 支持持久化 swap(进程退出后保留)
  • 按需换入,透明支持超额分配

8.3 多场景适配

  • 普通分配: 使用 buddy allocator
  • DMA-BUF: 导入外部 buffer
  • Userptr: 映射用户空间内存
  • SG 表: 支持 scatter-gather DMA

9. 小结

ttm_tt 是 TTM 内存管理框架的核心组件,负责:

  1. 管理系统内存页面的分配与释放
  2. 建立 GPU 地址映射,使设备能访问系统内存
  3. 处理缓存一致性,协调 CPU/GPU 访问
  4. 支持多种内存源(GTT普通分配/userptr/dma-buf)
  5. 实现内存换出,支持超额分配

在 AMD 驱动中,amdgpu_ttm_tt 进一步扩展了功能,增加了 userptr 和 HMM 支持,使得 GPU 能够直接访问用户空间内存,为统一内存架构提供了基础。


下一篇:TTM ttm_tt 技术分析3:AMD 驱动实现详解》将深入分析 AMD 驱动中 ttm_tt 的具体实现细节。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值