摘要libhsakmt 最初的设计是一个进程 一个 KFD 上下文。全局变量hsakmt_primary_kfd_ctx持有进程唯一的/dev/kfd文件描述符所有线程共享同一份拓扑、内存、队列、事件状态。这个模型简单高效但无法满足多租户 / 容器化 / 调试器隔离等新兴需求。本篇分析 KFD Context 机制的引入动机、数据结构设计以及新旧 API 的衔接策略。1. 原始模型单进程单上下文在 1.1 篇中我们已经详细分析过hsaKmtOpenKFDCtx()对/dev/kfd只执行一次open()调用后续所有请求都通过引用计数返回同一个全局上下文// openclose.cHsaKFDContext hsakmt_primary_kfd_ctx{.fd-1,.hsakmt_is_primary_ctxtrue,.hsakmt_is_svm_api_supportedfalse,};这个全局变量就是整个进程的 GPU “身份证”。所有 ioctl 都通过hsakmt_primary_kfd_ctx.fd发往内核所有子系统拓扑、内存、队列、事件、调试、性能计数器的状态都挂在这个结构体下。在这个模型中进程 KFD 上下文 内核侧kfd_process三者是一一对应的。这也是 KFD 内核驱动长期以来的假设 —— 它以pid为粒度管理 GPU 资源每个进程拥有独立的 GPU 虚拟地址空间GPUVM、页表、队列和事件。这个设计干净利落但有一个根本限制同一个进程内无法拥有多个相互隔离的 GPU 资源域。2. 为什么需要多上下文2.1 容器化与多租户 GPU 共享在云计算和容器化场景中多个租户容器 / 虚拟机 / 用户进程可能通过同一个宿主进程间接访问 GPU。例如一个 GPU 虚拟化代理进程需要同时为多个容器提供隔离的 GPU 服务。如果 libhsakmt 只有一个全局上下文所有容器的 GPU 资源就混在一起 —— 队列共享、内存可互相访问、事件可互相干扰。这显然不能满足隔离需求。2.2 调试器隔离GPU 调试器如 ROCgdb需要在不干扰目标进程正常 GPU 操作的前提下建立自己的 KFD 通信通道来设置断点、检查队列状态、读取 GPU 内存。如果调试器和被调试程序共享同一个上下文调试操作与计算操作之间的状态干扰将难以避免。2.3 DRM 层面的 VM 隔离当一个进程内的多个应用需要独立的 GPU 虚拟地址空间时每个应用需要自己的 DRM file handle。这在 GPU 虚拟化VirtIO-GPU场景中尤为关键 —— 宿主侧的代理进程需要为每个 Guest 维护独立的 VM 空间以实现 Guest 之间的地址空间隔离和隐式同步。3. HsaKFDContext上下文数据结构为了支持多上下文AMD 工程师引入了HsaKFDContext结构体将原本散落在全局变量中的所有子系统状态收归其下// kfdcontext.htypedefstruct_HsaKFDContext{intfd;// KFD 文件描述符bool hsakmt_is_primary_ctx;// 是否为 Primary 上下文bool hsakmt_is_svm_api_supported;// SVM API 支持标志structhsa_kfd_topology_context*topology_context;// 拓扑信息structhsa_kfd_queue_context*queue_context;// 队列资源structhsa_kfd_fmm_context*fmm_context;// 内存管理FMMstructhsa_kfd_event_context*event_context;// 事件structhsa_kfd_debug_context*debug_context;// 调试structhsa_kfd_perf_context*perf_context;// 性能计数器}HsaKFDContext;这是一个典型的聚合根设计 ——HsaKFDContext本身只持有文件描述符和几个标志位实际的资源状态通过六个指针分别指向各子系统的上下文结构。每个子上下文都是按需分配的。以 FMM 上下文为例// fmm.cstructhsa_kfd_fmm_context{gpu_mem_t*gpu_mem;// 每个 GPU 节点的内存 apertureunsignedintgpu_mem_count;gpu_mem_t*first_gpu_mem;uint32_tall_gpu_id_array_size;uint32_t*all_gpu_id_array;void*dgpu_shared_aperture_base;// dGPU 共享 aperture 基址void*dgpu_shared_aperture_limit;svm_tsvm;// SVM 管理manageable_aperture_tcpuvm_aperture;// CPU VM aperturemanageable_aperture_tmem_handle_aperture;intdrm_render_fds[128];// DRM render 节点 fdstructamdgpu_device*amdgpu_handle[128];// AMDGPU 设备句柄};这意味着每个HsaKFDContext拥有自己独立的 GPU 内存视图、DRM 设备句柄和 aperture 地址空间 —— 不同上下文之间的内存分配互不可见。头文件中的注释明确了隔离原则Multiple HsaKFDContext instances can coexist simultaneously, each maintaining its own independent set of resources. These contexts are fully isolated from one another and must not have their resources mixed. If resources need to be shared between contexts, they must be explicitly exported and imported using the appropriate APIs.多个上下文可以同时存在各自维护独立的资源集。上下文之间完全隔离资源不得混用。如需跨上下文共享必须通过显式的导出/导入 API。4. Primary vs Secondary两种上下文类型4.1 Primary ContextPrimary Context 就是原来的全局上下文由hsaKmtOpenKFDCtx()创建全进程唯一// openclose.c — hsaKmtOpenKFDCtx()if(hsakmt_kfd_open_count0){fdopen(kfd_device_name,O_RDWR|O_CLOEXEC);hsakmt_kfdcontext_init_context(fd,hsakmt_primary_kfd_ctx);hsakmt_kfd_open_count1;*pCtxhsakmt_primary_kfd_ctx;}它的特点全局静态分配生命周期与进程相同引用计数管理支持多次 Open/Close支持 SVM、调试等全部功能DRM 设备初始化使用amdgpu_device_initialize()启用设备去重4.2 Secondary ContextSecondary Context 由hsaKmtOpenSecondaryKFDCtx()创建可以有多个// openclose.c — hsaKmtOpenSecondaryKFDCtx()kfd_fdopen(kfd_device_name,O_RDWR|O_CLOEXEC);// 新的 fdstructkfd_ioctl_create_process_argsargs{};hsakmt_ioctl(kfd_fd,AMDKFD_IOC_CREATE_PROCESS,args);// 内核侧创建新进程上下文new_ctxcalloc(1,sizeof(HsaKFDContext));hsakmt_kfdcontext_init_context(kfd_fd,new_ctx);new_ctx-hsakmt_is_primary_ctxfalse;new_ctx-hsakmt_is_svm_api_supportedfalse;*pCtxnew_ctx;关键区别在三处① 独立的文件描述符。每个 Secondary Context 都会执行一次open(/dev/kfd)获得自己的 fd。加上AMDKFD_IOC_CREATE_PROCESSioctl内核侧会为这个 fd 建立独立的进程上下文。② 独立的 DRM VM 空间。初始化 DRM 设备时使用amdgpu_device_initialize2(fd, false, ...)第二个参数false禁用设备去重使得每个上下文获得自己的 DRM file handle 和独立的 GPU 虚拟地址空间// fmm.c — 初始化 DRM 设备if(ctx-hsakmt_is_primary_ctx)dev_init_retamdgpu_device_initialize(fd,...);elseif(hsakmt_fn_amdgpu_device_initialize2)dev_init_rethsakmt_fn_amdgpu_device_initialize2(fd,false,...);③ 功能限制。Secondary Context 不支持调试操作和 userptr 映射这个可能是暂时的后面有可能被支持。实现与primay context一样的效果。// debug.c — 调试接口拒绝 Secondary Contextif(!ctx-hsakmt_is_primary_ctx)returnHSAKMT_STATUS_NOT_SUPPORTED;// fmm.c — Secondary 禁用 userptrif(!ctx-hsakmt_is_primary_ctx)fmm_ctx-svm.userptr_for_paged_memfalse;4.3 生命周期对比维度Primary ContextSecondary Context创建hsaKmtOpenKFDCtx()hsaKmtOpenSecondaryKFDCtx()数量全进程唯一可创建多个存储全局静态变量calloc动态分配引用计数有hsakmt_kfd_open_count无fd 关闭进程退出或 fork 后清理hsaKmtCloseSecondaryKFDCtx()显式关闭DRM 初始化amdgpu_device_initializeamdgpu_device_initialize2(fd, false)调试支持完整支持不支持SVM / userptr支持禁用销毁 Secondary Context 时资源清理更加彻底 —— 因为没有引用计数保护关闭即意味着完全释放// openclose.c — hsaKmtCloseSecondaryKFDCtx()hsakmt_clear_events_page(ctx);hsakmt_destroy_counter_props(ctx);hsakmt_destroy_device_debugging_memory(ctx);hsakmt_fmm_clear_all_aperture(ctx);close(ctx-fd);// 关闭 fd → 内核侧释放进程上下文hsakmt_kfdcontext_clear_context(ctx);// 释放六个子上下文free(ctx);// 释放结构体本身5. 新旧 API 的衔接Ctx 后缀模式引入多上下文后每个操作都需要知道在哪个上下文上执行。但 libhsakmt 已有大量不带上下文参数的遗留 API不能破坏兼容性。解决方案是双层 API 设计┌──────────────────────────────────┐ │ hsakmt.h遗留 API无上下文参数│ ← 上层调用者使用 ├──────────────────────────────────┤ │ hsakmtctx.hCtx API带上下文 │ ← 新场景使用 ├──────────────────────────────────┤ │ 具体实现各模块 .c 文 │ └──────────────────────────────────┘Ctx API定义在hsakmtctx.h是真正的实现入口每个函数的第一个参数都是HsaKFDContext *ctx// hsakmtctx.hHSAKMT_STATUS HSAKMTAPIhsaKmtAllocMemoryCtx(HsaKFDContext*ctx,HSAuint32 PreferredNode,HSAuint64 SizeInBytes,HsaMemFlags MemFlags,void**MemoryAddress);HSAKMT_STATUS HSAKMTAPIhsaKmtCreateQueueExtCtx(HsaKFDContext*ctx,HSAuint32 NodeId,HSA_QUEUE_TYPE Type,unsignedintQueuePercentage,...);遗留 API定义在hsakmt.h变成了简单的转发层自动传入 Primary Context// queues.cHSAKMT_STATUShsaKmtCreateQueueExt(...){returnhsaKmtCreateQueueExtCtx(hsakmt_primary_kfd_ctx,...);}HSAKMT_STATUShsaKmtDestroyQueue(HSA_QUEUEID QueueId){returnhsaKmtDestroyQueueCtx(hsakmt_primary_kfd_ctx,QueueId);}// topology.cHSAKMT_STATUShsaKmtAcquireSystemProperties(HsaSystemProperties*SystemProperties){returnhsaKmtAcquireSystemPropertiesCtx(hsakmt_primary_kfd_ctx,SystemProperties);}这个设计的优雅之处在于完全向后兼容—— 不修改任何现有调用者的代码遗留 API 行为不变无代码重复—— 实现逻辑只存在于*Ctx()函数中遗留 API 是零逻辑的转发渐进式迁移—— 新代码可以直接使用 Ctx API老代码保持不变自然过渡测试框架也通过一个巧妙的宏来在两套 API 之间切换// KFDTestUtil.hpp#ifdefHSAKMT_CTX#defineHSAKMT_CALL(func,ctx,...)func##Ctx(ctx,##__VA_ARGS__)#else#defineHSAKMT_CALL(func,ctx,...)func(__VA_ARGS__)#endif编译时定义HSAKMT_CTX所有测试自动切换到 Ctx API 路径再加上HSAKMT_SECONDARY_CTX测试可以运行在 Secondary Context 上验证隔离性。6. 小结要素说明原始模型单进程单上下文全局hsakmt_primary_kfd_ctx所有线程共享新需求容器化多租户隔离、调试器隔离、DRM VM 空间隔离核心数据结构HsaKFDContext聚合 fd 六个子上下文指针topology / queue / fmm / event / debug / perfPrimary Context全局唯一引用计数管理功能完整Secondary Context动态分配独立 fd AMDKFD_IOC_CREATE_PROCESS独立 VM 空间部分功能受限API 兼容策略双层设计 ——hsakmtctx.hCtx API为实现层hsakmt.h遗留 API为转发层隔离粒度内存 aperture / DRM 设备句柄 / GPU VM 空间 / 队列 / 事件各上下文完全独立KFD Context 的引入是 libhsakmt 架构的一次重要演进。它在保持向后兼容的前提下将原本进程 上下文的刚性绑定松开使得同一进程内可以划分出多个相互隔离的 GPU 资源域。ROCm的虚拟化是一个很长的技术栈涉及到guest/host的双系统的栈。如果大家对此感兴趣可以关注下AMD工程师的相关其他软件库的提交。下一篇 11.2 将深入分析六个子上下文的内部结构与按需初始化机制。文章审核中…