本节讲清楚用户态看到的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_nodestructdrm_vma_offset_node{rwlock_tvm_lock;structdrm_mm_nodevm_node;structrb_rootvm_files;void*driver_private;};它内部有两个核心部分。2.1 vm_nodefake 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_SHIFTstaticinline__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 不是裸奔的全局入口即使用户态猜到了某个 offsetdrm_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 offsetstaticinlineu64amdgpu_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_nodeAMDGPU BO 创建时会先分配struct amdgpu_bo然后初始化里面的 GEM 基类bokvzalloc(bp-bo_ptr_size,GFP_KERNEL);if(boNULL)return-ENOMEM;drm_gem_private_object_init(adev_to_drm(adev),bo-tbo.base,size);bo-tbo.base.funcsamdgpu_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_filesRB_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 offsetAMDGPU 随后调用rttm_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 offsetif(bo-typettm_bo_type_device||bo-typettm_bo_type_sg){retdrm_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_deviceramdgpu_gem_object_create(adev,size,args-in.alignment,initial_domain,flags,ttm_bo_type_device,resv,gobj,fpriv-xcp_id1);dumb buffer 创建路径也是一样ramdgpu_gem_object_create(adev,args-size,0,domain,flags,ttm_bo_type_device,NULL,gobj,fpriv-xcp_id1);所以对于 AMDGPU 用户态可 mmap 的普通 BOfake 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 offsetttm_bo_type_sg导入/共享类 BO也会分配 fake offsetttm_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走到同一个 helperintamdgpu_mode_dumb_mmap(structdrm_file*filp,structdrm_device*dev,uint32_thandle,uint64_t*offset_p){structdrm_gem_object*gobj;structamdgpu_bo*robj;gobjdrm_gem_object_lookup(filp,handle);if(!gobj)return-ENOENT;robjgem_to_amdgpu_bo(gobj);if(amdgpu_ttm_tt_get_usermm(robj-tbo.ttm)||(robj-flagsAMDGPU_GEM_CREATE_NO_CPU_ACCESS)){drm_gem_object_put(gobj);return-EPERM;}*offset_pamdgpu_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 已经可以访问的地址”。用户态还必须继续调用cpummap(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_VRAMAMDGPU VRAM manager 管理当前实现主要使用drm_buddyTTM_PL_TTGTT/system memory 相关路径GTT address 管理中可见drm_mmTTM_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 offsetAMDGPU 用户态普通 BO 是ttm_bo_type_device因此会在ttm_bo_init_reserved()中分配 fake offsetamdgpu_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 区间。