AMD rocr-libhsakmt分析系列8-1: user queue

1 user space queue

1.1 基本概念

 ROCm 中使用了user queue,这个提法是与之前的kmd中的kernel queue相对的。其核心思想是让用户进程直接管理向 GPU 引擎提交的任务,绕过驱动程序的 IOCTL 调用,从而降低开销并允许 GPU 自行提交任务。应用程序可跨多个 GPU 引擎构建任务工作图,无需通过 CPU 中转。

UMD通过每个应用的共享内存区域直接与固件交互,主要载体是Queue。队列是一个包含读指针(rptr)和写指针(wptr)的环形缓冲区,并配有CPU对GPU的通知机制--doorbell。

1.2 术语表

术语解释
MQD(内存队列描述符)由内核驱动进行管理。引擎固件利用 MQD,能够动态地将队列从固件的运行列表中映射或取消映射。MQD 是内存里的一种数据结构,其作用是存储队列的配置信息。GPU 上各引擎的固件,会借助 MQD 加载驱动所指定的队列状态,并且在队列从固件的运行队列中取消映射时,存储来自硬件的队列状态。MQD 会存储队列的虚拟地址、读写指针、队列优先级以及描述队列状态的寄存器。对于内核模式队列,KGD 会创建并配置 MQD,然后通过固件对其进行映射以供使用。而在用户模式队列的情形下,创建队列的 IOCTL 调用会到达 KGD,由 KGD 为上下文创建一个 MQD,并按照用户的请求对其进行配置
队列内存这是由 UMD 分配的、可供 GPU 访问的内存,用于存储引擎执行的队列。该内存是环形缓冲区的支撑,而环形缓冲区的作用是向引擎输送数据包
读取指针(RPTR)内存由 UMD 分配的、可供 GPU 访问的内存,用于存储读取指针
写入指针(WPTR)内存由 UMD 分配的、可供 GPU 访问的内存,用于存储写入指针
门铃内存UMD 可以分配的门铃 BAR 中的一个页面。门铃的用途是告知引擎环形缓冲区中有新数据需要执行。
门铃偏移量门铃 BAR 中的一个索引,其作用是告知固件用户队列中有工作需要执行。每个引擎都会被分配一个双字(64 位)索引到门铃 BAR 中。在用户模式端,偏移量是相对于分配的门铃页面而言的。在核端,该偏移量会被转换为门铃 BAR 中的绝对偏移量,用于对 MQD 进行编程

1.3 工作原理

用户先把数据包写入环形缓冲区,接着对写入指针进行写入操作,以此告知引擎写入环形缓冲区的数据量。之后,用户通过读取读取指针,就能了解引擎的执行进度。随着 GPU 对数据包的不断消耗,读取指针最终会赶上写入指针。当两者相等时,引擎就处于空闲状态。若要真正启动引擎执行,用户还得对分配给该队列的门铃进行写入操作。门铃的作用是唤醒固件,并告知它检查环形缓冲区,查看是否有新数据。

因此每个用户队列需要多个缓冲区,包括环形缓冲区本身、rptr 缓冲区(固件向用户空间映射 rptr 的位置)、wptr 缓冲区(应用写入 wptr 供固件获取)和doorbell缓冲区。doorbell缓冲区是设备 MMIO BAR 的一部分,可映射到特定用户队列。

当应用创建用户队列时,需分配队列所需的缓冲区(环形缓冲区、wptr 和 rptr、doorbell、上下文保存区等),这些缓冲区可独立或属于一个更大的缓冲区。应用将缓冲区映射到 GPUVM,并使用用户队列所需内存区域的 GPU 虚拟地址,同时为用户队列分配门铃页面。随后,应用在 IOCTL 结构的 MQD 中填入 GPU 虚拟地址和所需的门铃索引,还可指定用户队列的属性(优先级、队列类型:AQL 或 PM4、是否为安全队列等),并调用用户队列创建 IOCTL 以基于指定的 MQD 创建队列。内核驱动验证应用提供的 MQD,并将其转换为特定 IP 的引擎 MQD 格式,分配特定 IP 的 MQD 并将队列添加到调度固件的运行列表中。队列创建后,应用可直接向队列写入数据包、更新 wptr,并写入门铃偏移量以启动用户队列的任务。

当应用不再使用用户队列时,调用用户队列销毁 IOCTL。内核驱动会抢占该队列并将其从调度固件的运行列表中移除,随后释放特定 IP 的 MQD 并清理用户队列状态。

总结下Queue 的基本工作流程如下:

  1. 应用写入数据包:应用将待执行的命令写入 queue 的环形缓冲区。

  2. 更新写指针(wptr):应用更新写指针,告知 GPU 新数据的范围。

  3. 写门铃(doorbell):应用向门铃寄存器写入,通知 GPU 检查 queue。

  4. GPU 消费数据包:GPU 固件读取 queue,执行命令,并更新读指针(rptr)。

  5. 应用读取 rptr:应用可读取 rptr,了解 GPU 的执行进度。

  6. 队列空闲:当 rptr 追上 wptr,说明 queue 已被消费完毕,GPU 处于空闲状态。

1.3 IOCTL 接口

内核模式驱动需验证队列及相关数据(rptr、wptr、上下文保存区等)的 GPU 虚拟地址,防止用户指定无效地址。若用户提供无效 GPU 虚拟地址或门铃索引,IOCTL 应返回错误信息。驱动还应跟踪这些缓冲区,若用户尝试从 GPUVM 取消映射缓冲区,取消映射调用应返回错误。

  • INFO:用于查询用户队列所需元数据大小(如上下文保存区或映射缓冲区);

  • CREATE:创建用户队列,应用提供类似 MQD 的结构,指定队列类型及关联元数据和标志,返回队列 ID;

  • FREE:释放用户队列;

  • QUERY:查询队列状态,检查队列是否健康(如是否已重置);

  • USERQ_SIGNAL:USERQ_SIGNAL IOCTL 用于提供待信号通知的同步对象列表;

  • USERQ_WAIT:USERQ_WAIT IOCTL 用于提供待等待的同步对象列表。

2. KFD用户态实现

2.1 核心数据结构

typedef struct _HsaQueueResource
{
    HSA_QUEUEID     QueueId;    /** queue ID */
    /** Doorbell address to notify HW of a new dispatch */
    union
    {
        HSAuint32*  Queue_DoorBell;
        HSAuint64*  Queue_DoorBell_aql;
        HSAuint64   QueueDoorBell;
    };

    /** virtual address to notify HW of queue write ptr value */
    union
    {
        HSAuint32*  Queue_write_ptr;
        HSAuint64*  Queue_write_ptr_aql;
        HSAuint64   QueueWptrValue;
    };

    /** virtual address updated by HW to indicate current read location */
    union
    {
        HSAuint32*  Queue_read_ptr;
        HSAuint64*  Queue_read_ptr_aql;
        HSAuint64   QueueRptrValue;
    };

    volatile HSAint64* ErrorReason;  /** exception bits signal payload */
} HsaQueueResource;

可以看出,queue的资源数据结构与第一部分描述的完全一致:

1. 每个queue有一个QueueId;

2. 每个queue有一个用于通知GPU处理数据的doorbell;

3. 一个wptr和一个rptr。

该节就分析这么多,大家先记住这些概念,原理有个大概认识,后面会展示代码的具体实现。原理和代码反复对照看,相信一定会有收获。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeplyMind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值