云计算百科
云计算领域专业知识百科平台

7.19 同一个 GTT BO 的两条访问路径:用户态与内核的映射差异

7-18《GART 与 GPUVM 对比:一颗 GPU 上两套页表的分工》 从六个维度横向对比了两套页表,说明它们按 VMID 分工、并存于同一颗 GMC。对比之后最容易冒出的一个实际问题是:一个用户态申请的 GTT 域 BO,交给 GPU 访问时到底走哪套页表?"GTT"这个名字里带着 aperture 的含义,很容易让人以为它必然经过 GART。

本篇锁定同一个 GTT BO,追踪它被两类访问者使用时的地址转换路径,得出一个反直觉但有源码依据的结论:用户进程访问 GTT BO 走的是每进程 GPUVM,叶子 PTE 直接编码系统内存的 DMA 地址,并不经过 GART;GART 映射服务的是内核自身对该 BO 的访问。同一份物理页因此同时挂在两套页表下,按访问者分流。


目录

  • 1 概述:一个 BO,两个访问者
  • 2 用户态路径:从 GEM_VA 到 pages_addr
  • 3 关键一步:GPUVM 叶子 PTE 直填 DMA 地址
  • 4 内核路径:同一 BO 绑进 GART
  • 5 谁在使用 GART 地址
  • 6 两套映射并存与一致性
  • 7 小结

1 概述:一个 BO,两个访问者

考虑一个典型场景:用户态通过 DRM_IOCTL_AMDGPU_GEM_CREATE 申请一块 GTT 域 BO,其物理页由内核从系统内存按页分配,物理上通常离散。这块 BO 会被两类主体访问,二者用的是完全不同的地址转换机制。

第一类是用户进程自己提交的 GPU 命令。它运行在该进程的 GPUVM 上下文(VMID 1–15),使用进程私有的 48 位虚拟地址,经四级页表翻译。第二类是内核驱动自身,例如用 SDMA 搬运这块 BO 的内容、或在初始化路径中读写它。内核走的是 VMID 0 的 GART 上下文。

VMID 的分工在 7-18 已借 gmc_v9_0.c 的注释说明:VMID 0 保留给 System(内核),VMID 1 及以上分配给图形/计算与 KFD。本篇要回答的是:同一个 GTT BO 在这两个上下文里,PTE 里填的物理地址分别是什么、是否都经过 GART。

2 用户态路径:从 GEM_VA 到 pages_addr

用户态要让 GPU 通过 GPUVM 访问 BO,先经 DRM_IOCTL_AMDGPU_GEM_VA 把 BO 映射到进程 VM 地址空间,建立 amdgpu_bo_va 与 mapping(这一步的接口语义见 7-5《BO 到 VA 的映射》)。真正把物理地址写进页表,发生在 amdgpu_vm_bo_update() 中。

该函数在取得 BO 当前所在的 TTM 资源后,对 GTT(TTM_PL_TT)与可抢占(AMDGPU_PL_PREEMPT)域,直接取出 BO 各页的 DMA 地址数组:

//drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
mem = bo->tbo.resource;
if (mem && (mem->mem_type == TTM_PL_TT ||
mem->mem_type == AMDGPU_PL_PREEMPT))
pages_addr = bo->tbo.ttm->dma_address;

bo->tbo.ttm->dma_address 是 TTM 为该 BO 每个页建立的 DMA 映射地址数组——即设备可直接寻址的系统内存物理地址。这个 pages_addr 随后被传入 amdgpu_vm_map_range(),成为写 PTE 时的地址来源。注意这里没有任何一步把地址换算成 GART aperture 内的地址(gart_start + offset);用户态路径从一开始拿到的就是原始 DMA 地址。

3 关键一步:GPUVM 叶子 PTE 直填 DMA 地址

pages_addr 最终如何变成叶子 PTE 里的物理地址?答案在 amdgpu_vm_map_gart():

//drivers/gpu/drm/amd/amdgpu/amdgpu_vm.c
uint64_t amdgpu_vm_map_gart(const dma_addr_t *pages_addr, uint64_t addr)
{
uint64_t result;

/* page table offset */
result = pages_addr[addr >> PAGE_SHIFT];

/* in case cpu page size != gpu page size*/
result |= addr & (~PAGE_MASK);

result &= 0xFFFFFFFFFFFFF000ULL;

return result;
}

这个函数名字里带 gart,极易造成误解,但它做的事与 GART aperture 无关:它以 addr >> PAGE_SHIFT 为下标,从 pages_addr 数组里取出对应页的 DMA 地址,拼上页内偏移、对齐到页边界后返回。返回的是系统内存的 DMA 物理地址,不是 gart_start + offset。

SDMA 更新路径中,这个返回值直接被编码为叶子 PTE:

//drivers/gpu/drm/amd/amdgpu/amdgpu_vm_sdma.c
for (i = 0; i < nptes; ++i, addr += incr) {
u64 oflags = flags;

pte[i] = amdgpu_vm_map_gart(p->pages_addr, addr);

if (p->override_pte)
amdgpu_gmc_override_vm_pte_flags(p->adev, p->vm, pte[i], &oflags);

pte[i] |= oflags;
}

pte[i] 由 DMA 地址与标志位或合而成。也就是说,用户进程通过 GPUVM 访问 GTT BO 时,GMC 硬件遍历这棵四级页表,在叶子 PTE 里读到的是 BO 页的 DMA 地址,直接寻址系统内存,中间不再经过 GART 这一层。GART 的单层重映射表在这条路径上完全不参与。

需要澄清 amdgpu_vm_map_gart 的命名由来:它源自"解析一个地址在页表映射下对应的物理页"这一含义(函数注释写的是 “Resolve gart mapping of addr”),是历史沿用的名字,与 VMID 0 的那套 GART 页表不是同一个概念。读代码时若把它当成"走 GART",就会得出错误结论。

4 内核路径:同一 BO 绑进 GART

用户态路径不经 GART,不代表这个 GTT BO 与 GART 无关。事实上,当 BO 被放置到 GTT 域时,TTM 的绑定回调 amdgpu_ttm_backend_bind() 会把它的物理页写进 GART 页表(这条链路见 6-2《GPU GART 绑定机制与 TLB 失效》)。绑定之后,这块 BO 在 GART aperture 中就有了一段连续地址,其起点由 amdgpu_ttm_domain_start() 给出:

//drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c
uint64_t amdgpu_ttm_domain_start(struct amdgpu_device *adev, uint32_t type)
{
switch (type) {
case TTM_PL_TT:
return adev->gmc.gart_start;
case TTM_PL_VRAM:
return adev->gmc.vram_start;
}

return 0;
}

对 TTM_PL_TT 域,GPU 起始地址正是 gmc.gart_start。BO 在 GART 内的地址即 gart_start + 资源起始偏移。这段地址属于 VMID 0 上下文,供内核使用,与用户进程的 GPUVM 地址空间彼此独立。

5 谁在使用 GART 地址

内核在哪些场景用这段 GART 地址?最典型的是通过 SDMA 引擎搬运 BO 内容。amdgpu_ttm_map_buffer() 的注释直接点明它在"设置 GART 窗口以访问一段内存,或对本地内存返回物理地址":

//drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c
/* Map only what can't be accessed directly */
if (!tmz && mem->start != AMDGPU_BO_INVALID_OFFSET) {
*addr = amdgpu_ttm_domain_start(adev, mem->mem_type) +
mm_cur->start;
return 0;
}

对可直接访问的内存(非加密、资源起始偏移有效),它直接返回 amdgpu_ttm_domain_start() + mm_cur->start,即 GTT 域的 GART 地址;只有无法直接寻址的情形才另设 GART window。内核 SDMA 拷贝随后就以这个 GART 地址作为源/目的地址发出命令。

另一处是内核经 SDMA 直接读写 BO 的调试/访问路径:

//drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c
src_addr = amdgpu_ttm_domain_start(adev, bo->resource->mem_type) +
src_mm.start;

同样以 domain_start(TT 域即 gart_start)为基址。这些都是 VMID 0 路径:内核不持有用户进程的 GPUVM 上下文,只能通过全局 GART aperture 访问 BO。

6 两套映射并存与一致性

综合前面几节,一个用户态 GTT BO 在被绑定与映射后,同时挂在两套页表下:

访问者上下文页表叶子表项里的物理地址
用户进程命令 VMID 1–15 GPUVM 四级页表 BO 页的 DMA 地址(经 amdgpu_vm_map_gart 取自 pages_addr)
内核驱动 VMID 0 GART 单层表 通过 gart_start + offset 再由 GART 转换到 DMA 地址

两套映射指向的是同一份系统内存物理页,但入口地址、页表结构、所属 VMID 各不相同。它们的维护也相互独立:GPUVM 侧的失效走 per-VMID 的 tlb_seq 与 vm_flush(见 7-12《TLB 管理与刷新》),GART 侧的失效走全局 amdgpu_gart_invalidate_tlb()(见 6-2)。

由此可以厘清"GTT 就走 GART"这一直觉的问题所在:GTT 描述的是 BO 的内存放置域(系统内存 + TTM 管理),而访问路径由访问者的 VMID 上下文决定,与内存域并非一一对应。同一份 GTT 物理页,用户进程经 GPUVM 直达,内核经 GART 中转。GART 在现代 GFX9 及以后主要服务内核 VMID 0 路径,用户态数据访问的主力是 GPUVM。

7 小结

本篇以一个用户态 GTT BO 为对象,追踪了它的两条 GPU 访问路径:

  • 用户进程访问走 GPUVM:GEM_VA 建立映射后,amdgpu_vm_bo_update() 对 TT 域取 pages_addr = bo->tbo.ttm->dma_address,经 amdgpu_vm_map_gart() 把 DMA 地址填进叶子 PTE,GMC 直接寻址系统内存,不经 GART。
  • amdgpu_vm_map_gart() 名字带 gart,实际只是从 pages_addr 数组按页取 DMA 地址,与 VMID 0 的 GART 页表无关,是常见的误读点。
  • 内核访问走 GART:同一 BO 被 amdgpu_ttm_backend_bind() 绑进 GART 表,amdgpu_ttm_domain_start(TT) 返回 gart_start;SDMA 搬运(amdgpu_ttm_map_buffer)与内核直接访问以该 GART 地址为基址。
  • 同一份物理页并存两套映射,按访问者的 VMID 分流,失效机制各自独立。"GTT 走 GART"的直觉不成立——内存放置域与访问路径由不同因素决定。

至此,GART 与 GPUVM 的关系已从静态对比(7-18)延伸到同一对象上的动态分流。下一章转向地址转换的第三层:DMA 地址空间与 IOMMU,分析 dma_address 本身是如何在系统侧建立的。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 7.19 同一个 GTT BO 的两条访问路径:用户态与内核的映射差异
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!