本节讲清楚用户态看到的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_node是drm_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_SHIFT2.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 可以 mmap3. 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.start和vma_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 memoryAMDGPU 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_node是drm_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_mm给vma_node分配一段不重叠的 fake offset 区间。