3.2 fake offset 的创建:BO 创建路径中的 vma_node
2026/6/9 21:23:01 网站建设 项目流程

本节讲清楚用户态看到的offset存储在哪里,是有谁管理以及怎么生成的。本节会涉及到amdgpu bo的创建流程的上半部分—初始化gem部分,可以用来理解前两章的kgd_mem的层级关系。

3.1 已经把用户态访问 BO 的 CPU VA 链路搭起来了:

handle -> fake offset -> mmap -> CPU VA -> fault -> BO backing

其中 fake offset 是mmap()找回 GEM object 的关键。现在问题往前推进一步:

  • 它保存在 BO 的哪个字段里?
  • fake offset 什么时候创建?
  • fake offset 什么时候销毁?

本节围绕 AMDGPU 的 BO 创建路径展开,但主题仍然限定在 CPU mmap 这条线:

amdgpu_gem_create_ioctl() -> amdgpu_gem_object_create() -> amdgpu_bo_create() -> drm_gem_private_object_init() -> drm_vma_node_reset(&obj->vma_node) -> ttm_bo_init_reserved() -> drm_vma_offset_add(&bo->base.vma_node)

先说结论:fake offset 存储在vma_node字段中。

  • vma_nodedrm_gem_object的公共字段,不是 AMDGPU 私有字段;
  • 对 AMDGPU/TTM 的普通用户态 BO 来说,fake offset 通常在 BO 初始化阶段就被加入 VMA offset manager,而不是等到真正mmap()时才临时创建。

1. vma_node 在哪里

vma_node位于 GEM 对象基类struct drm_gem_object中:

structdrm_gem_object{...structdrm_vma_offset_nodevma_node;...};

它的注释已经把职责说得很清楚:

/** * @vma_node: * * Mapping info for this object to support mmap. Drivers are supposed to * allocate the mmap offset using drm_gem_create_mmap_offset(). The * offset itself can be retrieved using drm_vma_node_offset_addr(). * * Memory mapping itself is handled by drm_gem_mmap(), which also checks * that userspace is allowed to access the object. */structdrm_vma_offset_nodevma_node;

这里有三个关键点。

第一,vma_node是为了support mmap,不是为了描述 BO 的真实物理位置。

第二,mmap offset 可以通过drm_vma_node_offset_addr()取出。

第三,真正的 mmap 建立由drm_gem_mmap()处理,它会检查用户态是否有权限访问该对象。

所以vma_node的职责可以概括为:

给一个 GEM object 挂上一段 fake offset 区间,使用户态后续可以通过mmap(fd, offset)找回这个对象。

2. vma_node 内部是什么

vma_node的类型是struct drm_vma_offset_node

structdrm_vma_offset_node{rwlock_tvm_lock;structdrm_mm_nodevm_node;structrb_rootvm_files;void*driver_private;};

它内部有两个核心部分。

2.1 vm_node:fake offset 区间

structdrm_mm_nodevm_node;

这个drm_mm_node记录的是对象在 fake offset 空间中的区间:

vm_node.start -> fake offset 的页号起点 vm_node.size -> 这个对象占用多少页 offset 空间

注意这里的start是 page-based,不是 byte-based。返回给用户态 mmap 的 byte offset 时,需要左移PAGE_SHIFT

staticinline__u64drm_vma_node_offset_addr(structdrm_vma_offset_node*node){return((__u64)node->vm_node.start)<<PAGE_SHIFT;}

面纱终于揭开,所以用户态看到的 fake offset 就是来自:

obj->vma_node.vm_node.start << PAGE_SHIFT

2.2 vm_files:访问权限

这里提一下这个vm_files,虽然不是这里的重点。

structrb_rootvm_files;

这个红黑树记录哪些drm_file被允许 mmap 这个 node。也就是说,fake offset 不是裸奔的全局入口;即使用户态猜到了某个 offset,drm_gem_mmap()还会检查当前 file 是否被授权。

权限相关 API 包括:

intdrm_vma_node_allow(structdrm_vma_offset_node*node,structdrm_file*tag);voiddrm_vma_node_revoke(structdrm_vma_offset_node*node,structdrm_file*tag);booldrm_vma_node_is_allowed(structdrm_vma_offset_node*node,structdrm_file*tag);

所以vma_node同时承担两个任务:

vma_node -> vm_node : fake offset 区间 -> vm_files : 哪些 drm_file 可以 mmap

3. AMDGPU BO 的继承关系

AMDGPU 的 BO 不是直接继承drm_gem_object,而是通过 TTM 多包了一层:

struct amdgpu_bo -> struct ttm_buffer_object tbo -> struct drm_gem_object base -> struct drm_vma_offset_node vma_node

因此在 AMDGPU 中访问vma_node的路径通常是:

bo->tbo.base.vma_node

而不是:

bo->vma_node

这点很重要。因为它说明vma_node属于 GEM 公共对象层,而不是 AMDGPU VRAM/GTT resource 层。

AMDGPU 提供了一个小封装来返回 mmap offset:

staticinlineu64amdgpu_bo_mmap_offset(structamdgpu_bo*bo){returndrm_vma_node_offset_addr(&bo->tbo.base.vma_node);}

这里也能看出:AMDGPU 返回给用户态的 offset,就是 GEM 基类中vma_node的 offset。

4.vma_node的初始化与赋值流程

4.1 初始化 GEM 基类并 reset vma_node

AMDGPU BO 创建时,会先分配struct amdgpu_bo,然后初始化里面的 GEM 基类:

bo=kvzalloc(bp->bo_ptr_size,GFP_KERNEL);if(bo==NULL)return-ENOMEM;drm_gem_private_object_init(adev_to_drm(adev),&bo->tbo.base,size);bo->tbo.base.funcs=&amdgpu_gem_object_funcs;

drm_gem_private_object_init()内部会做基础字段初始化,其中包括:

drm_vma_node_reset(&obj->vma_node);

drm_vma_node_reset()的作用是把 node 清成初始状态:

staticinlinevoiddrm_vma_node_reset(structdrm_vma_offset_node*node){memset(node,0,sizeof(*node));node->vm_files=RB_ROOT;rwlock_init(&node->vm_lock);}

这一步只表示:

vma_node这个字段可以安全使用了。

它还不等于:

这个 BO 已经有了 fake offset。

reset 之后,vma_node.vm_node.startvma_node.vm_node.size还没有被分配到drm_vma_offset_manager的 offset 空间里。真正把它加入 manager 的动作发生在后面的 TTM 初始化中。

4.2 TTM 为 device BO 分配 fake offset

AMDGPU 随后调用:

r=ttm_bo_init_reserved(&adev->mman.bdev,&bo->tbo,bp->type,&bo->placement,page_align,&ctx,NULL,bp->resv,bp->destroy);

ttm_bo_init_reserved()会根据 BO type 决定是否给它分配 VMA offset:

if(bo->type==ttm_bo_type_device||bo->type==ttm_bo_type_sg){ret=drm_vma_offset_add(bdev->vma_manager,&bo->base.vma_node,PFN_UP(bo->base.size));if(ret)gotoerr_put;}

这里就是 AMDGPU 普通用户态 BO 获得 fake offset 的关键点。

ttm_bo_type_device来说,TTM 注释是:

/** * @ttm_bo_type_device: These are 'normal' buffers that can * be mmapped by user space. Each of these bos occupy a slot in the * device address space, that can be used for normal vm operations. */

也就是说,普通用户态 BO 会占用 DRM 设备 address space 中的一段 slot。这个 slot 就是后续用户态 mmap 使用的 fake offset 区间。

AMDGPU 用户态 GEM 创建路径传入的正是ttm_bo_type_device

r=amdgpu_gem_object_create(adev,size,args->in.alignment,initial_domain,flags,ttm_bo_type_device,resv,&gobj,fpriv->xcp_id+1);

dumb buffer 创建路径也是一样:

r=amdgpu_gem_object_create(adev,args->size,0,domain,flags,ttm_bo_type_device,NULL,&gobj,fpriv->xcp_id+1);

所以,对于 AMDGPU 用户态可 mmap 的普通 BO,fake offset 的创建可以理解为:

drm_gem_private_object_init() -> reset vma_node ttm_bo_init_reserved(type = ttm_bo_type_device) -> drm_vma_offset_add(bdev->vma_manager, &bo->base.vma_node, PFN_UP(bo->base.size)) -> vma_node 获得 fake offset 区间

5. 哪些 BO 不会分配用户态 mmap offset

这个认知很重要,并不是所有 AMDGPU BO 都会被用户态 mmap。因为有些bo不需要被用户态访问。从TTM框架的视角,TTM 的 BO type 至少有三类:

enumttm_bo_type{ttm_bo_type_device,ttm_bo_type_kernel,ttm_bo_type_sg};

其中:

  • ttm_bo_type_device:普通用户态可 mmap BO,会分配 fake offset;
  • ttm_bo_type_sg:导入/共享类 BO,也会分配 fake offset;
  • ttm_bo_type_kernel:内核专用 BO,不面向用户态 mmap,不会在ttm_bo_init_reserved()中调用drm_vma_offset_add()

所以要区分两件事:

drm_vma_node_reset() -> 初始化 vma_node 字段 drm_vma_offset_add() -> 真正把 vma_node 加入 fake offset 空间

内核 BO 也会因为 GEM 基类初始化而 resetvma_node,但通常不会获得用户态 mmap offset。

6. 返回 fake offset 给用户态

AMDGPU 中查询 mmap offset 的路径可以通过 dumb mmap 或AMDGPU_GEM_MMAP走到同一个 helper:

intamdgpu_mode_dumb_mmap(structdrm_file*filp,structdrm_device*dev,uint32_thandle,uint64_t*offset_p){structdrm_gem_object*gobj;structamdgpu_bo*robj;gobj=drm_gem_object_lookup(filp,handle);if(!gobj)return-ENOENT;robj=gem_to_amdgpu_bo(gobj);if(amdgpu_ttm_tt_get_usermm(robj->tbo.ttm)||(robj->flags&AMDGPU_GEM_CREATE_NO_CPU_ACCESS)){drm_gem_object_put(gobj);return-EPERM;}*offset_p=amdgpu_bo_mmap_offset(robj);drm_gem_object_put(gobj);return0;}

这里的逻辑很清晰:

用户态传入 handle -> drm_gem_object_lookup(filp, handle) -> 得到 drm_gem_object -> 转成 amdgpu_bo -> 检查是否允许 CPU mmap -> amdgpu_bo_mmap_offset(robj) -> drm_vma_node_offset_addr(&bo->tbo.base.vma_node) -> 返回 fake offset

这个过程只返回 offset,不建立 CPU 页表,也不决定 BO 当前在 VRAM 还是 GTT。

换句话说:

AMDGPU_GEM_MMAP/ dumb map offset ioctl 返回的是“下一次 mmap 用的路由键”,不是“CPU 已经可以访问的地址”。

用户态还必须继续调用:

cpu=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,fake_offset);

mmap()成功后返回的cpu才是 CPU VA。

7. fake offset 的释放

fake offset 既然是分配出来的,也必须释放。

对 TTM BO 来说,释放路径中会调用:

drm_vma_offset_remove(bdev->vma_manager,&bo->base.vma_node);

它和创建时的:

drm_vma_offset_add(bdev->vma_manager,&bo->base.vma_node,PFN_UP(bo->base.size));

构成一对。

生命周期可以概括为:

BO 创建 -> drm_vma_node_reset() -> drm_vma_offset_add() -> fake offset 可被查询并用于 mmap BO 最后一个引用释放 -> drm_vma_offset_remove() -> fake offset 区间归还给 manager

这也说明 fake offset 的生命周期跟 BO 内核对象绑定,而不是跟某个 handle 完全绑定。handle 关闭后,BO 如果仍被其他引用持有,VMA offset 不一定立即消失;BO 真正释放时,offset 才会被移除。

8. vma_node 与真实 backing store 的边界

到这里需要再次把边界划清楚。

vma_node管的是:

用户态 mmap offset 空间

它不管:

BO 当前在 VRAM、GTT 还是 system memory

AMDGPU BO 创建时,ttm_bo_init_reserved()除了分配 VMA offset,还会调用ttm_bo_validate(),根据 placement 给 BO 分配真实 resource。这个 resource 可能是:

  • TTM_PL_VRAM:AMDGPU VRAM manager 管理,当前实现主要使用drm_buddy
  • TTM_PL_TT:GTT/system memory 相关路径,GTT address 管理中可见drm_mm
  • TTM_PL_SYSTEM或其他驱动私有 placement。

但是这些属于 TTM resource manager / AMDGPU memory manager 的职责,不属于vma_node的职责。

可以把两条线并排看:

CPU mmap 路由线: drm_gem_object.vma_node -> drm_vma_offset_manager -> fake offset -> drm_gem_mmap() 找回 BO BO backing 分配线: ttm_bo_validate() -> ttm_resource_manager.alloc() -> VRAM / GTT / SYSTEM resource -> fault 时映射真实 backing

两条线在同一个 BO 上汇合,但它们管理的是两个不同地址空间。

因此,一个 BO 即使当前是 VRAM BO,也仍然可以拥有vma_node和 fake offset。fake offset 只是让mmap()找到它;CPU 是否能真正访问,还要看 fault 时 BO backing 是否 CPU-visible,必要时是否能迁移到 visible VRAM 或 GTT。

9. 本篇小结

本节回答了 fake offset 是如何在 BO 创建路径中出现的。

核心链路是:

amdgpu_gem_object_create() -> amdgpu_bo_create() -> drm_gem_private_object_init() -> drm_vma_node_reset(&obj->vma_node) -> ttm_bo_init_reserved(type = ttm_bo_type_device) -> drm_vma_offset_add(&bo->base.vma_node) -> vma_node 获得 fake offset 区间

几个关键结论:

  • vma_nodedrm_gem_object的字段,AMDGPU 通过bo->tbo.base.vma_node使用它;
  • drm_vma_node_reset()只是初始化字段,drm_vma_offset_add()才是真正分配 fake offset;
  • AMDGPU 用户态普通 BO 是ttm_bo_type_device,因此会在ttm_bo_init_reserved()中分配 fake offset;
  • amdgpu_bo_mmap_offset()返回的是drm_vma_node_offset_addr(&bo->tbo.base.vma_node)
  • fake offset 的释放由drm_vma_offset_remove()完成;
  • vma_node管的是 mmap fake offset,不管 VRAM/GTT 物理资源分配。

接下来 3.3 我们继续下钻:fake offst 空间的大小如何给定;drm_vma_offset_add()内部如何依赖drm_mmvma_node分配一段不重叠的 fake offset 区间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询