RDMA 关键技术研究:Doorbell, UAR

最后更新: 2026-04-16, 作者: HAO022

UAR, User Access Region

UAR, User Access Region

PCI 地址空间的一部分,内部包含设备内存、寄存器(用户可通过寄存器控制设备)。UAR 以 PCI BAR 的方式暴露其内存空间地址并以 IO-mapped 的方式映射到内核空间,UAR 通常占用一个系统页内存。驱动层为不同进程映射不同的 UAR 以便实现资源隔离,此外 CPU 可以直接访问映射后的内存,减少内核态用户态的上下文切换,降低延迟。UAR 非常典型的用途是门铃寄存器,用户态进程可以直接写门铃寄存器触发硬件操作。UAR 在网卡通信,存储服务,GPU计算等场景均有应用。mellanox 网卡 UAR Page format 布局主要组成:

  • Completion WQ DoorBells: 存储在 CQ 寄存器
  • Event WQ DoorBells: 存储在 EQ 寄存器
  • Send DoorBells: 存储在 Blue Flame 寄存器

UAR 内存布局

Blue Flame Reg

Mellanox 为了快速访问硬件资源而实现的一项技术,用户可以直接向设备写入 WQEs,因此设备无需再从主机拷贝内存,进而降低延迟。具体实现上,BlueFlame 由一组大小一致的寄存器组成,每寄存器 512B,寄存器又分成两个大小一致的 Blue Flame Buffer 缓存。

Doorbell Record

位于系统物理内存,并在创建 RQ/SQ 时将 Doorbell Record 内存地址赋值到硬件。DoorBell Record 主要存储 SQ sq_wqebb_counter 以及 RQ、SRQ wqe_counter,这些标记表示向硬件队列提交的 WQEs 数目。

向工作队列提交请求的过程

  1. Write WQE to the WQE buffer sequentially to previously-posted WQE (on WQEBB granularity).
  2. Update Doorbell Record associated with that queue by writing their sq_wqebb_counter or wqe_counter for send and RQ respectively
  3. For send request ring DoorBell by writing to the Doorbell Register field in the UAR associated with that queue. For performance-critical send WQEs DoorBell can be rang by using the BlueFlame mechanism.

注意 1. 对于发送请求而言执行第三步是为了确保WQE被执行。在第二步的时候 HW 已经执行了 WQE,对于已经执行的 WQE 再次敲门铃并无伤害,硬件会忽略该事件。2. 软件可以在第一步批量的提交 WQEs,在第二步更新的参数包含所有的 WQEs。

Doorbell Record 资源映射

资源的分配流程:ibv_create_qp … mlx5_create_qp … mlx5_alloc_dbrec … 内核 create_user_qp:

  • 分配 Doorbell Record 内存
  • 将该内存的虚拟地址传送给内核驱动
  • 驱动对这块内存进行物理分配,锁页,dma 映射等
  • 最后赋值到 QPC,创建硬件资源 QP

用户态代码:

providers/mlx5/verbs.c
mlx5_create_qp
        qp->db = mlx5_alloc_dbrec 返回一段虚拟内存,这块内存未来用于发送和接收的 Doorbell Record
        qp->db[MLX5_RCV_DBR] = 0;
        qp->db[MLX5_SND_DBR] = 0;

        cmd.db_addr  = (uintptr_t) qp->db; 传送给内核。
        ...

内核驱动代码:

mlx5_ib_create_qp create_user_qp
        mlx5_ib_db_map_user 分配物理内存,锁页,映射。
        MLX5_SET64(qpc, qpc, dbr_addr, qp->db.dma); 将该内存 dma 地址赋值至硬件。
        ...

mlx5_ib_db_map_user(unsigned long virt ...)
        page = kmalloc();
        page->user_virt = (virt & PAGE_MASK);
        page->umem = ib_umem_get();
        db->dma = sg_dma_address();
        ...

Doorbell Register 资源映射

将 BFREG 寄存器映射到用户态,主要涉及 ibv_open_device、ibv_create_qp 操作:

  • ibv_open_device … mlx5_alloc_context … 内核 mlx5_ib_alloc_ucontext:
    • 分配设备上下文
    • 硬件分配 UAR
    • 用户映射 UAR
  • ibv_create_qp … mlx5_create_qp … 内核 create_user_qp:
    • UAR ID 关联 QP
    • 返回可用 UAR 索引

1. ibv_open_device UAR 映射

  • ibv_open_device 用户态代码(1):

    ibv_open_device mlx5_alloc_context
            // 请求的寄存器数量,默认 16,可通过环境变量 MLX5_TOTAL_UUARS 配置。
            req.total_num_bfregs = context->tot_uuars;
            // 请求的低延迟寄存器数量,默认 4,可通过环境变量 MLX5_NUM_LOW_LAT_UUARS 配置。
            req.num_low_latency_bfregs = context->low_lat_uuars;
            req.lib_caps |= (MLX5_LIB_CAP_4K_UAR | MLX5_LIB_CAP_DYN_UAR);
    
            // 调用至内核 mlx5_ib_alloc_ucontext 返回设备属性
            mlx5_cmd_get_context(req, resp ... ) ...
            // 根据返回的设备属性,初始化设备上下文,以及 UAR 映射等。
            mlx5_set_context
    
  • alloc_ucontext 内核态代码(1):IB 设备通过 ib_device_ops.alloc_ucontext 分配内核态设备上下文,并将这些信息存储在结构体 ibucontext 或私有结构体 mlx5_ib_ucontext。

    mlx5_ib_ucontext 结构体成员 mlx5_bfreg_info 维护着 UAR 信息,
    struct mlx5_ib_ucontext {
            struct ib_ucontext ibucontext;
            struct mlx5_bfreg_info bfregi;
            ...
    };
    
    struct mlx5_bfreg_info {
            u32 *sys_pages; // UAR ID
            int num_low_latency_bfregs;
            unsigned int *count;
            u32 num_sys_pages;
            u32 num_static_sys_pages;
            u32 total_num_bfregs;
            u32 num_dyn_bfregs;
    };
    

    网卡硬件可能在不同的平台使用,平台支持的页大小可以根据内核编译选项CONFIG_PAGE_SHIFT配置,通常为4K。另外需要明白,每 UAR 包含 BFREG 的总数为 NUM_BFREGS_PER_UAR 4,其中 MLX5_NUM_NON_FP_BFREGS_PER_UAR 2 用于慢速路径 No Fast Path。内核实现上 mlx5_ib_alloc_ucontext calc_total_bfregs 根据请求和硬件配置信息,初始化 mlx5_bfreg_info,最后调用 mlx5_ib_alloc_ucontext allocate_uars 分配硬件 UAR。Mellanox 创建硬件 UAR 资源命令参数和返回值如下图。

    calc_total_bfregs
            // 一个 UAR 对应一个系统页(4K)。每 UAR 包含 NUM_BFREGS_PER_UAR(4) BFREG,其中两个用于非快速路径 MLX5_NUM_NON_FP_BFREGS_PER_UAR。
            // total_num_bfregs 16 静态寄存器(用户态申请的寄存器数目)
            // 而每 UAR 支持 MLX5_NUM_NON_FP_BFREGS_PER_UAR(2) 慢速路径寄存器,因此需要 8 UAR,也就是 8 系统页。
            bfregi->num_static_sys_pages = 8;
            bfregi->num_dyn_bfregs = 1024; // 动态寄存器,默认 1024
            bfregi->total_num_bfregs = req->total_num_bfregs + bfregi->num_dyn_bfregs; // 动态+静态寄存器 1040
            bfregi->num_sys_pages = req->total_num_bfregs / 2; // 需要总系统页数量 8
            bfregi->sys_pages = kcalloc(bfregi->num_sys_pages, ....);
    
    allocate_uars 分配 num_static_sys_pages 硬件UAR资源
            for (i = 0; i < bfregi->num_static_sys_pages; i++) {
                // sys_pages 存储 UAR ID,该信息由硬件返回。
                mlx5_cmd_alloc_uar(dev->mdev, &bfregi->sys_pages[i]);
            }
    

  • ibv_open_device 用户态代码(2):用户态程序根据内核返回的信息完成 UAR 资源映射:

    (gdb) p *resp
    $4 = {
            bf_reg_size = 512,		// BF 寄存器内存大小
            tot_bfregs = 16,		// BF 静态寄存器数
            num_dyn_bfregs = 1024,		// BF 动态寄存器数
            num_uars_per_page = 1,		// UAR 每系统页包含的数量
            log_uar_size = 12,		// UAR 内存等于 4K
            qp_tab_size = 262144,
            cache_line_size = 64,
            max_sq_desc_sz = 1024,
            max_rq_desc_sz = 512,
            max_send_wqebb = 32768,
            max_recv_wr = 32768,
            max_srq_recv_wr = 32768,
            num_ports = 1,
            flow_action_flags = 0,
            comp_mask = 3,
            response_length = 72,
            cqe_version = 1 '\001',
            cmds_supp_uhw = 3 '\003',
            eth_min_inline = 2 '\002',
            clock_info_versions = 1 '\001',
            hca_core_clock_offset = 0,
            dump_fill_mkey = 1792
    }
    
    ibv_open_device ... mlx5_alloc_context ... mlx5_set_context
            ...
            gross_uuars = context->tot_uuars / MLX5_NUM_NON_FP_BFREGS_PER_UAR * NUM_BFREGS_PER_UAR; 总共 32 BFREG 寄存器
            context->bfs = calloc(gross_uuars, sizeof(*context->bfs));
    
            num_sys_page_map = context->tot_uuars / (context->num_uars_per_page * MLX5_NUM_NON_FP_BFREGS_PER_UAR);
    
            // 8 UAR 内存映射
            for (i = 0; i < num_sys_page_map; ++i) {
                // context->uar 映射
                mlx5_mmap(&context->uar[i], i, cmd_fd, ... MLX5_UAR_TYPE_REGULAR);
            }
    
            // 将 context->uar[].reg 平铺赋值到数组 context->bfs[].reg
        ...
    
  • mlx5_mmap 内核态代码(2): mmap 将硬件资源(如寄存器、内存区域)暴露为用户空间可访问的地址,允许用户态程序绕过内核,直接操作这些硬件资源。这种方式避免上下文切换和数据复制,减少延迟,提高吞吐量。在 Mellanox 实现中 mmap 实现了 UAR 资源的映射。UAR 映射的过程:获取用户态传递过来的index索引,根据该索引获取PCIe 地址,最后 remap 设备地址和 vma 用户态地址。

    ib_device_ops.mmap = mlx5_ib_mmap
    
    int mlx5_ib_mmap(struct ib_ucontext *ibcontext, struct vm_area_struct *vma)
            // offset 包含了操作类型和UAR INDEX
            command = get_command(vma->vm_pgoff);
            case MLX5_IB_MMAP_REGULAR_PAGE:
            // 1. 根据 vm_pgoff 获取命令操作
            // 2. 根据 vm_pgoff 获取 UAR 索引,进而得到设备物理地址
            // 3. 将物理地址和vma 虚拟地址进行 remap 映射
            uar_mmap(dev, command, vma, context);
            ...
    
  • 最终映射关系图

2. ibv_create_qp UAR QP 关联

mlx5_create_qp 主要完成 QP 和特定的 UAR 关联。

用户态代码:

mlx5_create_qp
        ibv_cmd_create_qp_ex 创建 QP

        // 1. 创建的新 QP 并不是关联所有 UAR 资源而是根据返回的 bfreg_index 确定使用的 UAR。
        // 2. qp->bf = &ctx->bfs[uuar_index]; 将该寄存器赋值到 qp,其实 ibv_open_device 后支持16个寄存器。
        map_uuar(context, qp, resp_drv->bfreg_index, bf);

内核代码:

create_user_qp
        // 1. 从已经分配的 context->bfregi 选择可用的 bfreg
        bfregn = alloc_bfreg(dev, &context->bfregi);
        // 2. 根据 bfreg 获取 sys_page, 存储 UAR ID
        uar_index = bfregn_to_uar_index(dev, &context->bfregi, bfregn, false);
        // 3. QP 关联 UAR ID
        MLX5_SET(qpc, qpc, uar_page, uar_index);
        // 4. 返回关联的索引
        resp->bfreg_index = ...;

最后总图

思考

  • 在 ibv_open_device 执行过程 Mellanox 实际分配映射了 8 UAR, 16 寄存器, 如果在同一个 ibv_context 调用 ibv_create_qp 超出16次,则会出现复用 BFREG 的情况。

篇尾:

  • HUATUO 华佗是由滴滴开源并依托 CCF 孵化的操作系统深度观测项目。
  • 关注微信公众号,或扫码加微信,邀请你加入用户群(请备注姓名+单位):

微信