主线故事:一个进程通过 Thunk 层提交了多个 User Queue,在某个时刻需要知道"哪个 Queue 完成了"------围绕这一场景,分 4 篇文章逐层展开 Event 的机制、实现和应用。预计5月陆续上线,请订阅关注。
技术分析思路概览
第 1 篇(概念与架构)
↓ 读者建立全局视角
第 2 篇(内核实现)
↓ 读者理解硬件→内核如何触发/传递事件
第 3 篇(Thunk 层实现)
↓ 读者理解用户态如何封装/使用事件
第 4 篇(实战:多 Queue 完成检测)
→ 读者能结合前 3 篇写出/调试真实代码
第 1 篇:Event 机制全景------从 Doorbell 到 Signal
目标
让读者在不看代码的情况下,理解 Event 在 ROCm 中的定位、与 Doorbell 的对比,以及为什么需要它。
大纲
-
引言:GPU 异步编程的两个核心问题
- "活儿来了"------CPU → GPU 通知(Doorbell)
- "活儿干完了"------GPU → CPU 通知(Event)
-
Event 的设计目标
- 精确感知 Queue/Kernel 完成
- 异常上报(Page Fault、HW Exception)
- 多 Queue 间依赖编排(流水线)
-
Event 类型一览
-
结合
hsakmttypes.h中的枚举,逐一说明:类型 值 用途 HSA_EVENTTYPE_SIGNAL0 用户态 GPU signal,最常用 HSA_EVENTTYPE_NODECHANGE1 热插拔 HSA_EVENTTYPE_DEVICESTATECHANGE2 设备启停 HSA_EVENTTYPE_HW_EXCEPTION3 GPU shader 异常 HSA_EVENTTYPE_SYSTEM_EVENT4 GPU SYSCALL HSA_EVENTTYPE_DEBUG_EVENT5 调试信号 HSA_EVENTTYPE_PROFILE_EVENT6 性能分析 HSA_EVENTTYPE_QUEUE_EVENT7 Queue idle / EOP HSA_EVENTTYPE_MEMORY8 内存访问异常
-
-
三层架构概览
- 硬件层:中断(MSI-X)+ 内存原子写(
RELEASE_MEM) - 内核层:KFD Event 对象 + Signal Page + wait_queue
- 用户态层:Thunk API(
hsaKmtCreateEvent/hsaKmtWaitOnEvent)→ ROCrhsa_signal_t
- 硬件层:中断(MSI-X)+ 内存原子写(
-
Event 生命周期概览图
Create → Associate with Queue → GPU executes → Signal → Wait/Poll → Destroy -
与 Fence / Semaphore / Completion 的对比
- Linux kernel fence vs KFD event
- CUDA event vs HSA signal
关键代码引用
hsakmttypes.h:HSA_EVENTTYPE_*枚举定义kfd_events.h:struct kfd_event结构体(预览,详细在第 2 篇)
第 2 篇:内核实现------KFD Event 对象与中断处理
目标
深入 kfd_events.c 和 kfd_events.h,讲清楚内核如何管理 Event 对象、Signal Page、以及如何响应硬件中断并唤醒用户态。
大纲
-
核心数据结构
-
struct kfd_event:cstruct kfd_event { u32 event_id; u64 event_age; // 用于 age-based wait 优化 bool signaled; bool auto_reset; int type; spinlock_t lock; wait_queue_head_t wq; // 等待者链表 uint64_t __user *user_signal_address; // 用户态 signal slot 指针 union { memory_exception_data; hw_exception_data; }; }; -
struct kfd_signal_page:一整页 64-bit slot,每个 slot 对应一个 signal event -
struct kfd_event_waiter:封装wait_queue_entry_t,支持 multi-event wait
-
-
Signal Page 机制(重点)
- 分配:
allocate_signal_page()→__get_free_pages(),初始化为UNSIGNALED_EVENT_SLOT(0xFFFFFFFFFFFFFFFF) - 映射到用户态:通过
mmapKFD 设备 fd,用户态得到events_page指针 - GPU 写入:GPU 执行
RELEASE_MEMpacket 时,将 event_id 写入对应 slot - 容量:
KFD_SIGNAL_EVENT_LIMIT(默认 4096 或 256 兼容模式)
- 分配:
-
Event 创建流程
kfd_event_create()→ 区分 signal event 和 other event- Signal event:
create_signal_event()→allocate_event_notification_slot()→idr_alloc()分配 event_id(同时是 slot index) - Other event:
create_other_event()→ event_id 从KFD_FIRST_NONSIGNAL_EVENT_ID开始
-
中断触发路径(核心)
kfd_signal_event_interrupt(pasid, partial_id, valid_id_bits)← 硬件中断处理调用lookup_signaled_event_by_partial_id():通过 partial ID + signal page slot 值判断哪个 event 被 signal- 找到 event 后:
set_event(ev)→ev->signaled = true→wake_up_all(&ev->wq)
-
Wait 机制
kfd_wait_on_events()→ 为每个 event 创建kfd_event_waiter,加入ev->wq- 支持
WaitOnAll(所有 event 都 signal)和WaitOnAny(任一 signal) - 超时处理:
schedule_timeout()或KFD_EVENT_TIMEOUT_IMMEDIATE(立即返回 / 轮询语义) event_age优化:避免重复处理已经看过的 signal
-
Event 销毁与清理
destroy_event()→ 唤醒所有 waiter(返回失败)→idr_remove()→kfree_rcu()kfd_event_free_process()→destroy_events()+shutdown_signal_page()
-
异常事件特殊处理
HSA_EVENTTYPE_MEMORY:kfd_set_memory_exception_data()填充 VA、GPU ID、failure reasonHSA_EVENTTYPE_HW_EXCEPTION:填充 reset_type、reset_cause、memory_lost
关键代码引用
kfd_events.h: 完整结构体定义kfd_events.c:allocate_signal_page,create_signal_event,kfd_signal_event_interrupt,kfd_wait_on_events,destroy_eventkfd_chardev.c或kfd_ioctl.c: ioctl 入口(AMDKFD_IOC_CREATE_EVENT,AMDKFD_IOC_WAIT_EVENTS等)
第 3 篇:Thunk 层实现------libhsakmt 如何封装 Event
目标
从 libhsakmt/src/events.c 出发,讲清楚用户态如何通过 ioctl 与内核交互、如何管理 events_page、以及 Wait 的完整流程。
大纲
-
Thunk 层 Event API 总览
API 功能 hsaKmtCreateEvent()创建 event hsaKmtDestroyEvent()销毁 event hsaKmtSetEvent()CPU 端手动 signal hsaKmtResetEvent()重置 event hsaKmtWaitOnEvent()等待单个 event hsaKmtWaitOnMultipleEvents()等待多个 event hsaKmtWaitOnEvent_Ext()带 event_age 的扩展等待 -
Event 创建详解
hsaKmtCreateEventCtx()完整流程:- 分配
HsaEvent结构体 - 构造
kfd_ioctl_create_event_args(event_type, node_id, auto_reset) - dGPU 特殊处理 :首次创建时分配 events_page(GPU 可见内存),通过
hsakmt_allocate_exec_aligned_memory_gpu()或mmapKFD fd ioctl(AMDKFD_IOC_CREATE_EVENT)→ 内核创建 event 并返回 event_id 和 event_slot_index- 设置
HWData2 = &events_page[event_slot_index](用户态 signal 地址)
- 分配
-
Events Page 管理(重点)
- iGPU vs dGPU 差异 :
- iGPU:通过
mmap(fd, event_page_offset)映射内核 signal page - dGPU:先用
hsakmt_allocate_exec_aligned_memory_gpu()分配 GPU 可达内存,再通过fmm_get_handle告知内核
- iGPU:通过
- events_page 是一个
uint64_t[]数组,每个 slot 8 字节 - GPU 写入 slot → 用户态可直接读取(shared memory 语义)
- iGPU vs dGPU 差异 :
-
Wait 流程详解
hsaKmtWaitOnMultipleEvents_ExtCtx()是核心实现:- 构造
kfd_event_data[]数组,填充每个 event_id - 对 signal event 填入
last_event_age(优化避免重复唤醒) ioctl(AMDKFD_IOC_WAIT_EVENTS)→ 阻塞在内核的schedule_timeout- 返回后检查
wait_result:SUCCESS / TIMEOUT - 异常事件处理 :对 MEMORY 和 HW_EXCEPTION 类型,从
event_data中提取详细错误信息并填入HsaEvent结构
- 构造
-
异常事件的用户态分析
analysis_memory_exception():打印 VA、node_id、failure reason(NotPresent / ReadOnly / NoExecute)- 尝试
fmm_get_mem_info()查询指针信息 - 如果是 SVM 范围,调用
get_mem_info_svm_api()通过AMDKFD_IOC_SVM查询属性
-
Signal vs System Event 的区分
IsSystemEventType(): signal 和 debug 类型可被用户态 Set/Reset;其余为"系统事件",只能由内核/硬件触发
关键代码引用
libhsakmt/src/events.c: 完整文件linux/kfd_ioctl.h: ioctl 命令定义libhsakmt/include/hsakmt/hsakmttypes.h:HsaEvent,HsaEventDescriptor,HSA_EVENTTYPE_*
第 4 篇:实战------多 Queue 完成检测与同步模式
目标
回到开头的场景:"一个进程提交了很多 User Queue,如何知道某个 Queue 完成了?"结合前 3 篇知识,给出完整的代码流程和最佳实践。
大纲
-
场景复现
- 进程通过
hsaKmtCreateQueue()创建 N 个 User Queue - 每个 Queue 提交一批 PM4/AQL 命令包
- 问题:如何高效地知道哪个 Queue 的哪个 batch 完成了?
- 进程通过
-
方案一:Signal Event + EOP(End of Pipe)
- 每个 Queue 创建一个
HSA_EVENTTYPE_SIGNALevent - 在命令流末尾插入
RELEASE_MEMPM4 包:DATA_SEL = signal slot addressINT_SEL = send interrupt
- 当 GPU 执行到该包时:
- 写 event_id 到 signal page slot(GPU 直接写共享内存)
- 发送 MSI-X 中断
- 内核
kfd_signal_event_interrupt()→ 唤醒等待线程
- 用户态
hsaKmtWaitOnMultipleEvents(events[], N, WaitOnAny, timeout) - 返回后检查哪些 event 已 signaled
- 每个 Queue 创建一个
-
方案二:轮询 Signal Slot(低延迟路径)
- 不走 ioctl wait,直接在用户态循环读
events_page[slot_index] - 当 slot 值从
UNSIGNALED_EVENT_SLOT变为其他值 → 该 event 已 signal - 适用场景:对延迟极敏感的 HPC / 推理场景
- 注意:需要适当的内存屏障(
__atomic_load)
- 不走 ioctl wait,直接在用户态循环读
-
方案三:event_age 优化的增量等待
hsaKmtWaitOnEvent_Ext()传入event_age- 内核只在 event_age 增长时唤醒(避免重复处理同一 signal)
- 适用于 persistent queue 场景(Queue 长期存活,反复 signal)
-
多 Queue 依赖编排
- Queue A 完成后 signal Event X
- Queue B 在命令流头部插入
WAIT_REG_MEM包,等待 Event X 的 signal slot - 实现 GPU 内部的流水线(不需 CPU 介入)
-
异常处理集成
- 额外创建
HSA_EVENTTYPE_MEMORYevent - 在
WaitOnMultipleEvents中同时等待 signal + memory event - 如果返回的是 memory event → 调用
analysis_memory_exception处理
- 额外创建
-
完整代码示例(伪代码 + 注释)
c// 1. Create events for (i = 0; i < N; i++) hsaKmtCreateEvent(&desc_signal, false, false, &events[i]); hsaKmtCreateEvent(&desc_memory, false, false, &mem_event); // 2. Submit work with signal packets for (i = 0; i < N; i++) submit_work_with_signal(queue[i], events[i]); // 3. Wait for any completion or exception all_events[0..N-1] = events[]; all_events[N] = mem_event; while (completed < N) { status = hsaKmtWaitOnMultipleEvents(all_events, N+1, false, timeout); if (status == SUCCESS) { for (i = 0; i < N; i++) if (is_signaled(events[i])) { handle_completion(i); completed++; } if (is_signaled(mem_event)) handle_memory_fault(mem_event); } } // 4. Cleanup for (i = 0; i < N; i++) hsaKmtDestroyEvent(events[i]); -
性能对比与最佳实践
方式 延迟 CPU 开销 适用场景 ioctl Wait (中断) ~μs 低(阻塞) 通用 轮询 slot 极低 高(忙等) 超低延迟 event_age Wait ~μs 低 Persistent Queue -
调试技巧
- 用
strace观察 ioctl 调用序列 - 用
rocm-smi --showevents监控 SMI 事件 - 内核
ftrace:tracekfd_signal_event_interrupt的触发频率 - 检查
/sys/kernel/debug/kfd/proc/<pid>/events
- 用