1. 引言
在前面的文章中,我们已经了解了如何查询计数器属性和注册跟踪会话。本文将探讨访问权限管理机制,即 hsaKmtPmcAcquireTraceAccess() 和 hsaKmtPmcReleaseTraceAccess() 这对API的实现原理和使用场景。
虽然这两个函数在当前实现中相对简单,但它们在性能跟踪框架中扮演着重要的角色,为未来的扩展和多进程协调提供了基础。
2. API 概览
2.1 Acquire - 获取跟踪访问权限
HSAKMT_STATUS HSAKMTAPI hsaKmtPmcAcquireTraceAccess(
HSAuint32 NodeId, // 输入:GPU节点ID
HSATraceId TraceId // 输入:跟踪会话ID
);
功能:
- 获取对指定跟踪会话的访问权限
- 验证TraceId和NodeId的有效性
- 为未来的访问控制预留接口
2.2 Release - 释放跟踪访问权限
HSAKMT_STATUS HSAKMTAPI hsaKmtPmcReleaseTraceAccess(
HSAuint32 NodeId, // 输入:GPU节点ID
HSATraceId TraceId // 输入:跟踪会话ID
);
功能:
- 释放对指定跟踪会话的访问权限
- 验证TraceId的有效性
- 与Acquire配对使用
2.3 返回值
| 返回值 | 说明 |
|---|---|
HSAKMT_STATUS_SUCCESS | 操作成功 |
HSAKMT_STATUS_INVALID_PARAMETER | TraceId为0 |
HSAKMT_STATUS_INVALID_HANDLE | TraceId无效(魔数校验失败) |
HSAKMT_STATUS_INVALID_NODE_UNIT | NodeId无效(仅Acquire) |
3. 源码实现分析
3.1 hsaKmtPmcAcquireTraceAccess 详解
hsaKmtPmcAcquireTraceAccess()
↓
[1] 调试日志输出
↓
[2] 验证 TraceId != 0
├─ No → 返回 INVALID_PARAMETER
└─ Yes → 继续
↓
[3] TraceId 转换为 perf_trace 指针
↓
[4] 验证魔数 (magic4cc)
├─ 不匹配 → 返回 INVALID_HANDLE
└─ 匹配 → 继续
↓
[5] 验证 NodeId
├─ 无效 → 返回 INVALID_NODE_UNIT
└─ 有效 → 继续
↓
[6] 返回 SUCCESS
3.2 hsaKmtPmcReleaseTraceAccess 详解
hsaKmtPmcReleaseTraceAccess()
↓
[1] 调试日志输出
↓
[2] 验证 TraceId != 0
├─ No → 返回 INVALID_PARAMETER
└─ Yes → 继续
↓
[3] TraceId 转换为 perf_trace 指针
↓
[4] 验证魔数 (magic4cc)
├─ 不匹配 → 返回 INVALID_HANDLE
└─ 匹配 → 继续
↓
[5] 返回 SUCCESS
4. 魔数验证机制
4.1 魔数的作用
#define HSA_PERF_MAGIC4CC 0x54415348 // ASCII: "HSAT"
struct perf_trace {
uint32_t magic4cc; // 总是第一个字段
// ... 其他字段 ...
};
为什么使用魔数?
- 结构完整性检测: 确保内存未被破坏
- 类型安全: 验证指针指向正确的结构类型
- 调试辅助: 在内存dump中易于识别
- 快速失败: 立即发现无效的TraceId
4.2 魔数选择的艺术
0x54415348 的分解:
0x54 = 'T'
0x41 = 'A'
0x53 = 'S'
0x48 = 'H'
→ "TASH" (反向) → "HSAT" (HSA Trace)
好的魔数特征:
- 易于识别(ASCII可读)
- 不是常见的数值(0x00000000, 0xFFFFFFFF等)
- 在内存dump中突出显示
- 有意义的缩写
4.3 魔数验证流程
// 创建时设置魔数
trace->magic4cc = HSA_PERF_MAGIC4CC;
// 使用时验证魔数
if (trace->magic4cc != HSA_PERF_MAGIC4CC) {
// TraceId无效的可能原因:
// 1. TraceId从未注册(随机值)
// 2. 已被Unregister(内存已释放)
// 3. 内存损坏(缓冲区溢出等)
// 4. 指针错误(指向错误的内存)
return HSAKMT_STATUS_INVALID_HANDLE;
}
5. 使用模式与最佳实践
5.1 标准使用模式
HSAKMT_STATUS status;
HSAuint32 nodeId = 0;
HsaPmcTraceRoot traceRoot;
// 1. 注册跟踪
status = hsaKmtPmcRegisterTrace(nodeId, numCounters, counters, &traceRoot);
if (status != HSAKMT_STATUS_SUCCESS) {
handle_error(status);
return;
}
// 2. 获取访问权限
status = hsaKmtPmcAcquireTraceAccess(nodeId, traceRoot.TraceId);
if (status != HSAKMT_STATUS_SUCCESS) {
hsaKmtPmcUnregisterTrace(nodeId, traceRoot.TraceId);
handle_error(status);
return;
}
// 3. 分配缓冲区
void *buffer = malloc(traceRoot.TraceBufferMinSizeBytes);
// 4. 启动跟踪
status = hsaKmtPmcStartTrace(traceRoot.TraceId, buffer,
traceRoot.TraceBufferMinSizeBytes);
// 5. 执行工作负载
run_gpu_workload();
// 6. 查询数据
status = hsaKmtPmcQueryTrace(traceRoot.TraceId);
// 7. 停止跟踪
status = hsaKmtPmcStopTrace(traceRoot.TraceId);
// 8. 释放访问权限
status = hsaKmtPmcReleaseTraceAccess(nodeId, traceRoot.TraceId);
// 9. 注销跟踪
status = hsaKmtPmcUnregisterTrace(nodeId, traceRoot.TraceId);
// 10. 释放缓冲区
free(buffer);
6. 常见错误场景分析
6.1 使用无效的TraceId
// 错误场景1: 使用未初始化的TraceId
HSATraceId traceId; // 未初始化,包含随机值
hsaKmtPmcAcquireTraceAccess(0, traceId);
// 结果: INVALID_PARAMETER 或 INVALID_HANDLE
// 错误场景2: 使用已注销的TraceId
hsaKmtPmcUnregisterTrace(nodeId, traceId); // 释放了内存
hsaKmtPmcAcquireTraceAccess(nodeId, traceId); // 访问已释放的内存
// 结果: 未定义行为(可能崩溃或返回INVALID_HANDLE)
// 错误场景3: 使用错误的NodeId
hsaKmtPmcRegisterTrace(0, ...); // 在节点0上注册
hsaKmtPmcAcquireTraceAccess(1, traceId); // 使用节点1
// 结果: 可能成功(当前实现),但逻辑错误
6.2 忘记配对调用
// 错误: Acquire但忘记Release
hsaKmtPmcAcquireTraceAccess(nodeId, traceId);
// ... 使用跟踪 ...
hsaKmtPmcUnregisterTrace(nodeId, traceId); // 跳过了Release
// 当前影响: 无(未来可能导致资源泄漏)
// 错误: 多次Acquire
hsaKmtPmcAcquireTraceAccess(nodeId, traceId);
hsaKmtPmcAcquireTraceAccess(nodeId, traceId); // 重复
// 当前影响: 无(未来可能导致问题)
7. 常见问题 FAQ
Q1: 为什么需要显式调用Acquire/Release?
A: 虽然当前实现很简单,但这对API提供了:
- 清晰的资源管理语义
- 未来扩展的空间(引用计数、锁等)
- 与其他HSA实现的一致性
- 更好的代码可读性
Q2: Acquire失败会影响TraceId吗?
A: 不会。Acquire失败不影响已注册的跟踪会话:
hsaKmtPmcRegisterTrace(...); // TraceId已创建
hsaKmtPmcAcquireTraceAccess(...); // 失败
// TraceId仍然有效,可以重试Acquire或直接Unregister
Q3: 可以多次Acquire同一个TraceId吗?
A: 当前可以,但不推荐:
hsaKmtPmcAcquireTraceAccess(nodeId, traceId); // 第1次
hsaKmtPmcAcquireTraceAccess(nodeId, traceId); // 第2次 - 当前允许
未来版本可能引入引用计数,要求Release次数匹配Acquire次数。
Q4: Release之后还能使用TraceId吗?
A: 可以,Release不销毁TraceId:
hsaKmtPmcAcquireTraceAccess(nodeId, traceId);
hsaKmtPmcReleaseTraceAccess(nodeId, traceId);
// TraceId仍然有效
hsaKmtPmcAcquireTraceAccess(nodeId, traceId); // 可以再次Acquire
只有Unregister会销毁TraceId。
Q5: 如果忘记Release会怎样?
A: 当前没有影响,但:
- 违反了API契约
- 未来版本可能导致资源泄漏
- 最佳实践是始终配对调用
12. 总结
本文深入探讨了访问权限管理API:
核心要点:
- 验证为主: 主要执行TraceId和NodeId的有效性验证
- 配对使用: Acquire和Release应该成对调用
使用建议:
- 总是在Start之前调用Acquire
- 总是在Unregister之前调用Release
- 进行充分的错误检查
在下一篇文章中,将详细分析 hsaKmtPmcStartTrace()、hsaKmtPmcQueryTrace() 和 hsaKmtPmcStopTrace() 的实现,探讨实际的性能数据采集机制和Linux perf_event的集成。
811

被折叠的 条评论
为什么被折叠?



