URMA Mental Model

导言

我刚开始接触 URMA 时,最容易卡住的问题不是某个函数不会调用,而是概念没有连成一条线:WQE 和 Jetty SQ 是什么关系,Segment 为什么要注册和导入,urma_post_jetty_send_wr() 提交之后又去了哪里。本文只做一件事:沿着一条 WRITE 请求的生命周期,把这些名词放回它们各自的位置。

系列位置

  1. URMA Mental Model:对象与数据路径
  2. URMA Write and Read:最小读写程序
  3. URMA Completion and Concurrency:完成、顺序与并发
  4. SHMEM UDMA Programming:对称内存与 put 接口
  5. AIV UDMA Direct Drive:st_dev、多 QP 与 Relay

源码截面

本系列的原生 URMA 类型、函数和样例核对到 openeuler/umdk@a2e11613f0f8174c0170413952ac4a5364a99a70。若本机 UMDK 版本不同,应优先相信随版本安装的头文件。

先记住一条主线

一次最普通的远端写,可以先理解成下面这条流水线:

1
2
3
4
5
6
7
8
9
10
11
12
13
应用填写 WR

URMA/Provider 编码为 WQE

WQE 写入 Jetty 的发送队列 SQ

更新生产者索引并敲 Doorbell

UDMA 设备执行远端写

设备向完成队列写入 CQE

应用从 JFC 轮询到 CR

这里最重要的判断是:WQE 不是与 SQ 并列的资源,WQE 是 SQ 中的一条工作项。

可以用餐厅来类比:

  • WR 是服务员写下的点菜单,仍是软件容易理解的格式。
  • WQE 是后厨系统识别的标准工单。
  • SQ 是等待后厨处理的一排工单槽位。
  • Doorbell 是通知后厨“有新单”的铃。
  • CQE 是后厨写回的完成小票。
  • JFC/CQ 是存放完成小票的队列。

这个类比只帮助建立直觉。编程时仍要回到对象、队列和内存权限三个维度。

对象关系

内存对象

概念 直观理解 在一次 WRITE 中的作用
本地缓冲区 普通虚拟地址 保存准备发送的数据
Segment 已向通信设备登记的一段内存 告诉设备地址范围和访问权限
urma_seg_t 可交换的 Segment 描述 通过带外通道发给对端
urma_target_seg_t 导入后的目标 Segment 句柄 真正用于访问本地或远端 Segment
SGE 一个地址、长度和 Segment 句柄 描述一段连续数据
SG 一组 SGE 描述可能不连续的完整数据

普通指针只对当前进程有意义。设备若要直接访问一块内存,还需要知道这块内存是否已注册、允许什么操作。因此,地址和 Segment 句柄必须配套使用

对于远端内存,还要分清两步:

  1. 对端把可交换的 urma_seg_t 描述发过来。
  2. 本端调用导入接口,把描述变成可提交给设备的 urma_target_seg_t *

不要把“收到远端描述”误认为“已经能访问远端内存”。

队列与端点

概念 全称 作用
JFS Jetty for Send 提供发送队列 SQ
JFR Jetty for Receive 提供接收队列 RQ
Jetty JFS 与 JFR 的组合端点 统一承载发送和接收能力
JFC Jetty for Completion 承载完成队列 CQ
SQ Send Queue 保存待设备执行的 WQE
RQ Receive Queue 保存接收方预先准备的接收 WQE
CQ Completion Queue 保存设备写回的 CQE

Jetty 是通信端点对象,SQ 是它内部的一条发送队列。 一个 Jetty 可以被应用拿来提交请求,但应用通常不是直接把任意字节写入 SQ,而是先构造 URMA WR,再交给 Provider 编码和提交。

请求与完成

概念 所在层次 说明
WR URMA 软件接口 应用描述 opcode、源、目标、标志和上下文
WQE 设备队列格式 Provider 根据 WR 编码出的硬件工作项
CQE 设备完成格式 设备写回的完成项
CR URMA 软件接口 应用从 JFC 轮询得到的完成结果

通常会把一个请求编号放入 wr.user_ctx。设备完成后,应用从 cr.user_ctx 取回同一个值,由此判断完成项属于哪个请求。这个编号只负责关联,不等于队列序号,也不自动保证数据顺序

WRITE 到底描述了什么

一条 WRITE WR 至少回答四个问题:

  1. 从哪里读:本地源 SGE。
  2. 向哪里写:远端目标 SGE。
  3. 通过哪个端点发出:目标 Jetty 句柄。
  4. 完成后如何识别:完成标志和 user_ctx

用结构关系表示:

1
2
3
4
5
6
7
8
9
10
urma_jfs_wr_t
├── opcode = URMA_OPC_WRITE
├── tjetty = 导入后的对端 Jetty
├── user_ctx = 请求 ID
├── flag.complete_enable = 1
└── rw
├── src = 本地 SG
│ └── SGE(addr, len, local_tseg)
└── dst = 远端 SG
└── SGE(addr, len, import_tseg)

complete_enable = 1 表示这个请求需要产生完成通知。若没有请求完成项,却一直轮询 JFC,程序不会得到预期结果。

单边与双边语义

URMA 的常见数据操作可以先分成两组:

操作 谁描述数据地址 接收端是否需要预投递
WRITE 发起端同时描述本地源和远端目标 不需要
READ 发起端同时描述远端源和本地目标 不需要
SEND/RECV 发送端描述发送数据,接收端描述接收缓冲区 需要先投递 RECV

因此,WRITE/READ 是单边操作:目标进程不必为每一条传输即时调用接收函数。不过,目标内存仍然必须提前注册、授权并把描述交换给发起端。

基础 API 地图

不必一次记住全部函数,先按生命周期分组:

  1. 发现设备并创建上下文

    • urma_init()
    • urma_get_device_list()urma_get_device_by_name()
    • urma_get_eid_list()
    • urma_query_device()
    • urma_create_context()
  2. 创建完成资源

    • urma_create_jfce():可选的完成事件对象。
    • urma_create_jfc():创建完成队列。
    • urma_poll_jfc():轮询完成结果。
  3. 创建通信端点

    • 分开创建:urma_create_jfs()urma_create_jfr()
    • 组合创建:urma_create_jetty()
    • 导入对端:urma_import_jfr()urma_import_jetty()
  4. 注册并交换内存

    • urma_register_seg():注册本地 Segment。
    • 通过 socket 等带外通道交换 Segment 描述和 token。
    • urma_import_seg():导入远端 Segment。
  5. 提交数据请求

    • 简便接口:urma_write()urma_read()urma_send()urma_recv()
    • 通用接口:urma_post_jfs_wr()urma_post_jfr_wr()
    • Jetty 接口:urma_post_jetty_send_wr()urma_post_jetty_recv_wr()
  6. 按相反顺序清理

    • 先确保请求已完成。
    • 再取消导入、注销 Segment、删除 Jetty/JFC。
    • 最后释放 Context 和设备列表。

提交不等于完成

urma_post_jetty_send_wr() 返回成功,只说明 WR 已被接受并提交到发送路径;它不表示远端数据已经可用。只有轮询到成功 CR,或者使用接口规定的其他完成机制后,才能按完成语义继续复用缓冲区或消费结果。

st_dev 在哪里

st_dev 不是 URMA 的 Host 侧公共 API。它是 Ascend C/CCE 的 Device 侧写指令,可由 AIV 核在直驱 UDMA 时向 Doorbell 地址写入新的队列 head。

两条路径不要混在一起:

1
2
Host 常规路径:WR → URMA API → Provider 填 WQE → CPU 写 Doorbell
AIV 直驱路径:AIV 构造 WQE → 写 SQ → st_dev 写 Doorbell

st_dev 只负责最后的 Doorbell 写入,不会替应用搬运 payload,也不会自动构造 WQE。AIV 直驱会在本系列第五篇单独展开。

学完本篇应能回答

  • WQE 是什么?——设备能执行的一条工作项。
  • Jetty SQ 是什么?——Jetty 内保存发送 WQE 的环形队列。
  • WR 和 WQE 有何区别?——前者是软件请求,后者是 Provider 编码后的设备格式。
  • 为什么要注册 Segment?——给设备提供地址范围、权限和可识别句柄。
  • 为什么还要 JFC?——请求异步执行,应用需要从完成队列确认结果。
  • st_dev 是 URMA API 吗?——不是,它属于 AIV 直驱路径中的 Device 指令。

下一步

带着这张概念图阅读下一篇。下一篇会从官方样例中抽出一条最小 WRITE,并逐行解释每个字段为什么存在。

参考资料

Author

Shaojie Tan

Posted on

2026-09-02

Updated on

2026-09-02

Licensed under