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 设计目标
- 统一页面管理:为系统内存页面提供统一的分配、映射、释放接口
- 地址空间映射:将系统内存映射到 GPU 可访问的地址空间
- 缓存策略控制:管理页面的缓存属性(cached/uncached/write-combined)
- Swap 支持:支持页面换出到交换空间以释放内存压力
- 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
设计原则总结
- 最小化假设:bdev 指针支持多 GPU 系统
- 分离关注点:func 分离策略和实现
- 灵活性优先:pages 数组支持不连续内存
- 状态可追溯:state + page_flags 完整记录生命周期
- 跨系统集成:sg 表对接 DMA-BUF 框架
- 性能与功能平衡:caching_state 提供多种策略
- 资源管理: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 内存管理框架的核心组件,负责:
- 管理系统内存页面的分配与释放
- 建立 GPU 地址映射,使设备能访问系统内存
- 处理缓存一致性,协调 CPU/GPU 访问
- 支持多种内存源(GTT普通分配/userptr/dma-buf)
- 实现内存换出,支持超额分配
在 AMD 驱动中,amdgpu_ttm_tt 进一步扩展了功能,增加了 userptr 和 HMM 支持,使得 GPU 能够直接访问用户空间内存,为统一内存架构提供了基础。
下一篇: 《TTM ttm_tt 技术分析3:AMD 驱动实现详解》将深入分析 AMD 驱动中 ttm_tt 的具体实现细节。
ttm_tt核心原理解析
537

被折叠的 条评论
为什么被折叠?



