前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Linux RDMA RXE/SoftRoCE 软件RoCE-内核驱动源码

Linux RDMA RXE/SoftRoCE 软件RoCE-内核驱动源码

原创
作者头像
晓兵
发布于 2024-04-10 15:15:01
发布于 2024-04-10 15:15:01
2.3K00
代码可运行
举报
文章被收录于专栏:Linux内核Linux内核DPU
运行总次数:0
代码可运行

术语

RXE: Software RDMA over Ethernet, 软件RoCE

简介

rdma_rxe 内核模块提供 RoCEv2 协议的软件实现。 RoCEv2 协议是存在于 UDP/IPv4 或 UDP/IPv6 之上的 RDMA 传输协议。 InfiniBand (IB) 基本传输标头 (BTH) 封装在 UDP 数据包中。 创建 RXE 实例后,通过 RXE 进行通信与通过任何 OFED 兼容的 Infiniband HCA 进行通信相同,尽管在某些情况下会涉及寻址问题。 特别是,虽然 GRH 标头的使用在 IB 子网中是可选的,但对于 RoCE 来说是强制性的。 基于 IB 动词编写的动词应用程序应该可以无缝工作,但它们需要在创建地址向量时提供 GRH 信息。 修改库和驱动程序以提供硬件所需的从 GID 到 MAC 地址的映射

Soft RoCE 驱动程序 Soft RoCE (RXE) - 软件 RoCE 驱动程序 ib_rxe 实现 RDMA 传输并作为内核动词提供程序注册到 RDMA 核心设备。 它还实现了数据包IO层。 另一方面,ib_rxe 作为 udp 封装协议(在这种情况下为 RDMA)注册到 Linux netdev 堆栈,用于通过任何以太网设备发送和接收数据包。 这会在 UDP/以太网网络层上产生 RDMA 传输,形成 RoCEv2 兼容设备。 Soft RoCE 驱动程序的配置过程需要绑定到任何现有的以太网网络设备。 这是通过 /sys 接口完成的。 用户空间 Soft RoCE 库 (librxe) 为用户应用程序提供了与 Soft RoCE 设备一起运行的能力。 在用户空间中使用 rxe 动词需要包含 librxe 作为 libibverbs 的设备特定插件。 librxe是单独打包的

配置和使用

代码语言:javascript
代码运行次数:0
运行
复制
动态添加和删除rxe网卡
rdma link add rxe_eth0 type rxe netdev eth0
rdma link
root@u20:~/project/rdma/rdma-core/build/bin# ./ibv_rc_pingpong -d rxe_ens3 -g 0

架构

代码语言:javascript
代码运行次数:0
运行
复制
Architecture:+-----------------------------------------------------------+
     |                          Application                      |
     +-----------------------------------------------------------+
                            +-----------------------------------+
                            |             libibverbs            |
User                        +-----------------------------------+
                            +----------------+ +----------------+
                            | librxe         | | HW RoCE lib    |
                            +----------------+ +----------------+
+---------------------------------------------------------------+
     +--------------+                           +------------+
     | Sockets      |                           | RDMA ULP   |
     +--------------+                           +------------+
     +--------------+                  +---------------------+
     | TCP/IP       |                  | ib_core             |
     +--------------+                  +---------------------+
                             +------------+ +----------------+
Kernel                       | ib_rxe     | | HW RoCE driver |
                             +------------+ +----------------+
     +------------------------------------+
     | NIC driver                         |
     +------------------------------------+

IB Spec/RDMA相关参考

代码语言:javascript
代码运行次数:0
运行
复制
ib端口状态/物理状态/及位宽等:
enum ib_port_state {
    IB_PORT_NOP     = 0,
    IB_PORT_DOWN        = 1,
    IB_PORT_INIT        = 2,
    IB_PORT_ARMED       = 3,
    IB_PORT_ACTIVE      = 4,
    IB_PORT_ACTIVE_DEFER    = 5
};// IB端口物理状态
enum ib_port_phys_state {
    IB_PORT_PHYS_STATE_SLEEP = 1,
    IB_PORT_PHYS_STATE_POLLING = 2,
    IB_PORT_PHYS_STATE_DISABLED = 3,
    IB_PORT_PHYS_STATE_PORT_CONFIGURATION_TRAINING = 4,
    IB_PORT_PHYS_STATE_LINK_UP = 5,
    IB_PORT_PHYS_STATE_LINK_ERROR_RECOVERY = 6,
    IB_PORT_PHYS_STATE_PHY_TEST = 7,
};// IB端口位宽
enum ib_port_width {
    IB_WIDTH_1X = 1,
    IB_WIDTH_2X = 16,
    IB_WIDTH_4X = 2,
    IB_WIDTH_8X = 4,
    IB_WIDTH_12X    = 8
};

IB组织带宽演进计划:

用户态调用栈

代码语言:javascript
代码运行次数:0
运行
复制
rdma link add rxe_eth0 type rxe netdev eth0
SOURCES\mlnx-iproute2-6.0.0\rdma\rdma.c -> main
rd_cmd
int cmd_link(struct rd *rd)
    const struct rd_cmd cmds[] = {
        { NULL,     link_show },
        { "add",    link_add },
        { "delete", link_del },
        { "show",   link_show },
        { "list",   link_show },
        { "help",   link_help },
        { 0 }
    };static int link_add(struct rd *rd)
    link_add_type
        link_add_netdev
            rd_prepare_msg(rd, RDMA_NLDEV_CMD_NEWLINK, &seq,   -> to kernel 转到内核 rxe_newlink
            mnl_attr_put_strz(rd->nlh, RDMA_NLDEV_ATTR_DEV_NAME, rd->link_name)
            mnl_attr_put_strz(rd->nlh, RDMA_NLDEV_ATTR_LINK_TYPE, rd->link_type)
            mnl_attr_put_strz(rd->nlh, RDMA_NLDEV_ATTR_NDEV_NAME, link_netdev)
            rd_sendrecv_msg(rd, seq)

驱动实现

模块初始化:

代码语言:javascript
代码运行次数:0
运行
复制
late_initcall(rxe_module_init); -> rxe_module_init -> rdma_rxe:确保rdma_rxe init在正确的时间发生,当CONFIG_RDMA_RXE=y且CONFIG_IPV6=y时出现问题。 这会导致 rdma_rxe 初始化在 IPv6 服务准备就绪之前发生。 该补丁将 rdma_rxe 的初始化延迟到 IPv6 服务准备就绪之后。 此修复基于 Logan Gunthorpe 在更旧的代码库上提出的修复
    rxe_alloc_wq
        rxe_wq = alloc_workqueue("rxe_wq", WQ_UNBOUND, WQ_MAX_ACTIVE)
    rxe_net_init
        rxe_net_ipv4_init
            rxe_setup_udp_tunnel
                udp_sock_create
                    udp_sock_create4
                        sock_create_kern
                        kernel_bind
                    or udp_sock_create6
                tnl_cfg.encap_rcv = rxe_udp_encap_recv
                    rxe_get_dev_from_net
                    if (skb_linearize(skb))
                    udph = udp_hdr(skb)
                    pkt->paylen = be16_to_cpu(udph->len) - sizeof(*udph)
                    skb_pull(skb, sizeof(struct udphdr))
                    rxe_rcv(skb)
                        rxe_chk_dgid
                        pkt->opcode = bth_opcode(pkt)
                        pkt->psn = bth_psn(pkt)
                        pkt->mask |= rxe_opcode[pkt->opcode].mask -> struct rxe_opcode_info rxe_opcode[RXE_NUM_OPCODE]
                        hdr_check
                        rxe_icrc_check
                            rxe_icrc_hdr
                            rxe_crc32
                                SHASH_DESC_ON_STACK
                                crypto_shash_update
                                shash_desc_ctx
                                barrier_data
                            icrc = ~icrc
                        rxe_counter_inc
                            atomic64_inc(&rxe->stats_counters[index])
                        rxe_rcv_pkt(pkt, skb)
                            rxe_resp_queue_pkt
                            or rxe_comp_queue_pkt
                                skb_queue_tail(&qp->resp_pkts, skb)
                                rxe_sched_task(&qp->comp.task)
                                or rxe_run_task(&qp->comp.task)
                setup_udp_tunnel_sock
        rxe_net_ipv6_init
        register_netdevice_notifier(&rxe_net_notifier)
    rdma_link_register(&rxe_link_ops) -> RDMA/core:添加 RDMA_NLDEV_CMD_NEWLINK/DELLINK 支持,添加对新 LINK 消息的支持以允许添加和删除 rdma 接口。 这最初将用于软 rdma 驱动程序,该驱动程序由管理员指定要使用的 netdev 设备动态实例化设备实例。 rdma_rxe 模块将是这些消息的第一个用户。 该设计是根据 RTNL_NEWLINK/DELLINK 建模的:如果 rdma 驱动程序提供链接添加/删除功能,则它们会向 rdma 内核注册。 每个驱动程序都注册一个唯一的“类型”字符串,用于调度来自用户空间的消息。 为“type”字符串定义了新的 RDMA_NLDEV_ATTR。 用户模式将在 NEWLINK 消息中传递 3 个属性:RDMA_NLDEV_ATTR_DEV_NAME 表示要创建的所需 rdma 设备名称,RDMA_NLDEV_ATTR_LINK_TYPE 表示要添加的链接“类型”,RDMA_NLDEV_ATTR_NDEV_NAME 表示用于此链接的 net_device 接口。 DELLINK 消息将包含要删除的设备的 RDMA_NLDEV_ATTR_DEV_INDEX
        down_write(&link_ops_rwsem)
        list_add(&ops->list, &link_ops)
        up_write(&link_ops_rwsem)
    pr_info("loaded\n")

Netlink实现

代码语言:javascript
代码运行次数:0
运行
复制
static struct rdma_link_ops rxe_link_ops = {
    .type = "rxe",
    .newlink = rxe_newlink,
};
rxe_newlink -> 添加对 RDMA_NLDEV_CMD_NEWLINK/DELLINK 消息的支持,允许动态添加新的 RXE 链接。 暂时弃用旧的模块选项
    is_vlan_dev -> RDMA/rxe:防止在 vlan 接口之上创建 rxe,在 vlan 接口之上创建 rxe 设备将创建一个无功能的设备,该设备具有空的 gids 表,并且不能用于 rdma cm 通信。 这是由 enum_all_gids_of_dev_cb()/is_eth_port_of_netdev() 中的逻辑引起的,该逻辑仅考虑连接到已配置网络设备的“上层设备”的网络,导致 vlan 接口的 gid 集为空,并尝试通过此 rdma 连接 由于无法解析 gid,设备在 cm_init_av_for_response 中失败。 显然,实现此行为是为了适应为每个端口创建 RoCE 设备的 HW-RoCE 设备,因此 RXE 的行为必须与 HW-RoCE 设备相同,并且仅为每个真实设备创建 rxe 设备。 为了通过 vlan 接口进行通信,用户必须使用 vlan 地址的 gid 索引,而不是通过 vlan 创建 rxe
    rxe_get_dev_from_net
    rxe_net_add
        ib_alloc_device
        rxe_add
            rxe_init -> RDMA/rxe:用xarray替换红黑树,当前rxe驱动程序使用红黑树向rxe对象池添加索引。 Linux xarrays 提供了一种更好的方法来实现索引的相同功能。 此补丁将池对象的红黑树替换为 xarray。 由于 xarray 已经有一个自旋锁,请使用它来代替池 rwlock。 确保 xarray(index)kref(ref count) 中的所有更改均以原子方式发生
                rxe_init_device_param
                    rxe->attr.vendor_id         = RXE_VENDOR_ID
                    addrconf_addr_eui48((unsigned char *)&rxe->attr.sys_image_guid -> RDMA/rxe:将 sys_image_guid 设置为与 HW IB 设备对齐,RXE 驱动程序不设置 sys_image_guid,并且用户空间应用程序看到零。 这会导致 pyverbs 测试失败并出现以下回溯,因为 IBTA 规范要求具有有效的 sys_image_guid。 回溯(最近一次调用最后一次):文件“./tests/test_device.py”,第 51 行,在 test_query_device self.verify_device_attr(attr) 文件“./tests/test_device.py”,第 74 行,在 verify_device_attr 中断言 attr.sys_image_guid != 0 为了修复它,请将 sys_image_guid 设置为等于 node_guid
                rxe_init_ports -> RDMA/rxe:删除 pkey 表,RoCE 规范要求 RoCE 设备仅支持默认 pkey。 然而,rxe 驱动程序维护一个 64 个实体的 pkey 表,并且仅使用第一个条目。 删除 pkey 表并使用默认 pkey 硬连接长度为 1 的表进行硬编码。 将 pkey_table 的所有检查替换为与 default_pkey 的比较
                    rxe_init_port_param
                        port->attr.state        = IB_PORT_DOWN
                        ...
                rxe_init_pools
                    rxe_pool_init(rxe, &rxe->uc_pool, RXE_TYPE_UC)
                        pool->rxe       = rxe
                        pool->elem_size     = ALIGN(info->size, RXE_POOL_ALIGN)
                        xa_init_flags(&pool->xa, XA_FLAGS_ALLOC)
                rxe->mcg_tree = RB_ROOT
            rxe_set_mtu
                eth_mtu_int_to_enum
                mtu = mtu ? min_t(enum ib_mtu, mtu, IB_MTU_4096) : IB_MTU_256
            rxe_register_device(rxe, ibdev_name)
                dev->node_type = RDMA_NODE_IB_CA
                ib_set_device_ops(dev, &rxe_dev_ops)
                ib_device_set_netdev(&rxe->ib_dev, rxe->ndev, 1)
                    alloc_port_data
                    add_ndev_hash
                        hash_add_rcu(ndev_hash, &pdata->ndev_hash_link,
                rxe_icrc_init
                    crypto_alloc_shash
                ib_register_device
                
​
rxe设备操作表
static const struct ib_device_ops rxe_dev_ops = {
    .owner = THIS_MODULE,
    .driver_id = RDMA_DRIVER_RXE,
    .uverbs_abi_ver = RXE_UVERBS_ABI_VERSION,.alloc_hw_port_stats = rxe_ib_alloc_hw_port_stats,
    .alloc_mr = rxe_alloc_mr,
    .alloc_mw = rxe_alloc_mw,
    .alloc_pd = rxe_alloc_pd,
    .alloc_ucontext = rxe_alloc_ucontext,
    .attach_mcast = rxe_attach_mcast,
    .create_ah = rxe_create_ah,
    .create_cq = rxe_create_cq,
    .create_qp = rxe_create_qp,
    .create_srq = rxe_create_srq,
    .create_user_ah = rxe_create_ah,
    .dealloc_driver = rxe_dealloc,
    .dealloc_mw = rxe_dealloc_mw,
    .dealloc_pd = rxe_dealloc_pd,
    .dealloc_ucontext = rxe_dealloc_ucontext,
    .dereg_mr = rxe_dereg_mr,
    .destroy_ah = rxe_destroy_ah,
    .destroy_cq = rxe_destroy_cq,
    .destroy_qp = rxe_destroy_qp,
    .destroy_srq = rxe_destroy_srq,
    .detach_mcast = rxe_detach_mcast,
    .device_group = &rxe_attr_group,
    .enable_driver = rxe_enable_driver,
    .get_dma_mr = rxe_get_dma_mr,
    .get_hw_stats = rxe_ib_get_hw_stats,
    .get_link_layer = rxe_get_link_layer,
    .get_port_immutable = rxe_port_immutable,
    .map_mr_sg = rxe_map_mr_sg,
    .mmap = rxe_mmap,
    .modify_ah = rxe_modify_ah,
    .modify_device = rxe_modify_device,
    .modify_port = rxe_modify_port,
    .modify_qp = rxe_modify_qp,
    .modify_srq = rxe_modify_srq,
    .peek_cq = rxe_peek_cq,
    .poll_cq = rxe_poll_cq,
    .post_recv = rxe_post_recv,
    .post_send = rxe_post_send,
    .post_srq_recv = rxe_post_srq_recv,
    .query_ah = rxe_query_ah,
    .query_device = rxe_query_device,
    .query_pkey = rxe_query_pkey,
    .query_port = rxe_query_port,
    .query_qp = rxe_query_qp,
    .query_srq = rxe_query_srq,
    .reg_user_mr = rxe_reg_user_mr,
    .req_notify_cq = rxe_req_notify_cq,
    .rereg_user_mr = rxe_rereg_user_mr,
    .resize_cq = rxe_resize_cq,INIT_RDMA_OBJ_SIZE(ib_ah, rxe_ah, ibah),
    INIT_RDMA_OBJ_SIZE(ib_cq, rxe_cq, ibcq),
    INIT_RDMA_OBJ_SIZE(ib_pd, rxe_pd, ibpd),
    INIT_RDMA_OBJ_SIZE(ib_qp, rxe_qp, ibqp),
    INIT_RDMA_OBJ_SIZE(ib_srq, rxe_srq, ibsrq),
    INIT_RDMA_OBJ_SIZE(ib_ucontext, rxe_ucontext, ibuc),
    INIT_RDMA_OBJ_SIZE(ib_mw, rxe_mw, ibmw),
};

注册IB设备(ib_register_device)

代码语言:javascript
代码运行次数:0
运行
复制
probe, add(dev)
ib_register_device -> roce和IB注册的flow: https://blog.csdn.net/tiantao2012/article/details/77746141
    assign_name
    setup_device(device) -> ib_register_device() 执行多个分配和初始化步骤。 将其拆分为更小、更易读的函数,以便于审查和维护 -> setup_device() 分配内存并设置需要调用设备操作的数据,这是在 ib_alloc_device 期间未完成这些操作的唯一原因。 它由 ib_dealloc_device() 撤消
        ib_device_check_mandatory
            IB_MANDATORY_FUNC
            mandatory_table
            IB_MANDATORY_FUNC(query_device),
            ...
        setup_port_data -> RDMA/device:将 ib_device per_port 数据合并到一个位置,没有理由对每个端口数据进行 3 次分配。 将它们组合在一起并使所有每端口数据的生命周期与 struct ib_device 匹配。 后续补丁将需要更多特定于端口的数据,现在有一个好地方可以放置它
            alloc_port_data
                rdma_end_port
                rdma_for_each_port
                    INIT_LIST_HEAD(&pdata->pkey_list)
                    INIT_HLIST_NODE(&pdata->ndev_hash_link)
            rdma_for_each_port
                get_port_immutable -> .get_port_immutable = irdma_roce_port_immutable
                    ib_query_port
                    immutable->max_mad_size = IB_MGMT_MAD_SIZE
                verify_immutable
                    rdma_cap_ib_mad -> rdma_cap_ib_mad - 检查设备的端口是否支持 Infiniband 管理数据报。 @device:要检查的设备 @port_num:要检查的端口号 管理数据报 (MAD) 是 InfiniBand 规范的必需部分,并且受所有 InfiniBand 设备支持。 OPA 接口还支持稍微扩展的版本。 返回:如果端口支持发送/接收MAD数据包,则返回true
                        device->port_data[port_num].immutable.core_cap_flags & RDMA_CORE_CAP_IB_MAD
                    rdma_max_mad_size -> rdma_max_mad_size - 返回此 RDMA 端口所需的最大 MAD 大小。 @device:设备 @port_num:端口号 该 MAD 大小包括 MAD 标头和 MAD 负载。 不包含其他标头。 返回端口所需的最大 MAD 大小。 如果端口不支持 MAD,则返回 0
        device->ops.query_device
    ib_cache_setup_one(device) -> IB/核心:添加 RoCE GID 表管理,RoCE GID 基于与 RDMA (RoCE) 设备端口相关的以太网网络设备上配置的 IP 地址。 目前,每个支持 RoCE(ocrdma、mlx4)的低级驱动程序都管理自己的 RoCE 端口 GID 表。 由于本质上没有任何特定于供应商的内容,因此我们对其进行概括,并增强 RDMA 核心 GID 缓存来完成这项工作。 为了填充 GID 表,我们监听事件: (a) netdev up/down/change_addr 事件 - 如果 netdev 构建在我们的 RoCE 设备上,我们需要添加/删除其 IP。 这涉及添加与此 ndev 相关的所有 GID、添加默认 GID 等。 (b) inet 事件 - 将新 GID(根据 IP 地址)添加到表中。 为了对端口 RoCE GID 表进行编程,提供商必须实现 add_gid 和 del_gid 回调。 RoCE GID 管理要求我们在 GID 旁边声明关联的 net_device。 为了管理 GID 表,此信息是必需的。 例如,当删除 net_device 时,其关联的 GID 也需要删除。 RoCE 要求根据相关网络设备的 IPv6 本地链路为每个端口生成默认 GID。 与基于常规 IPv6 链路本地的 GID(因为我们为每个 IP 地址生成 GID)相反,当网络设备关闭时,默认 GID 也可用(为了支持环回)。 锁定的完成方式如下:该补丁修改了 GID 表代码,适用于实现 add_gid/del_gid 回调的新 RoCE 驱动程序以及未实现 add_gid/del_gid 回调的当前 RoCE 和 IB 驱动程序。 更新表的流程不同,因此锁定要求也不同。 更新 RoCE GID 表时,通过 mutex_lock(&table->lock) 实现针对多个写入者的保护。 由于写入表需要我们在表中查找一个条目(可能是空闲条目)然后修改它,因此该互斥锁保护 find_gid 和 write_gid 确保操作的原子性。 GID 缓存中的每个条目均受 rwlock 保护。 在 RoCE 中,写入(通常来自 netdev 通知程序的结果)涉及调用供应商的 add_gid 和 del_gid 回调,这些回调可能会休眠。 因此,为每个条目添加无效标志。 RoCE 的更新是通过工作队列完成的,因此允许休眠。 在IB中,更新是在write_lock_irq(&device->cache.lock)中完成的,因此write_gid不允许休眠并且add_gid/del_gid不会被调用。 当将网络设备传入/传出 GID 缓存时,该设备始终被传递为保持 (dev_hold)。 该代码使用单个工作项来更新所有 RDMA 设备,遵循 netdev 或 inet 通知程序。 该补丁将缓存从客户端(这是不正确的,因为缓存是 IB 基础设施的一部分)转变为在设备注册/删除时显式初始化/释放
        gid_table_setup_one
            _gid_table_setup_one
            rdma_roce_rescan_device -> {net, IB}/mlx5:管理多端口 RoCE 的端口关联,调用 mlx5_ib_add 时确定要添加的 mlx5 核心设备是否能够进行双端口 RoCE 操作。 如果是,请使用 num_vhca_ports 和affiliate_nic_vport_criteria 功能确定它是主设备还是从设备。 如果该设备是从属设备,请尝试找到与其关联的主设备。 可以关联的设备将共享系统映像 GUID。 如果没有找到,请将其放入非关联端口列表中。 如果找到主设备,则通过在 NIC vport 上下文中配置端口从属关系将端口绑定到它。 同样,当调用 mlx5_ib_remove 时确定端口类型。 如果它是从端口,则将其与主设备取消关联,否则只需将其从非关联端口列表中删除即可。 即使第二个端口不可用于关联,IB 设备也会注册为多端口设备。 当第二个端口稍后附属时,必须刷新 GID 缓存才能获取缓存中第二个端口的默认 GID。 导出roce_rescan_device以提供在绑定新端口后刷新缓存的机制。 在多端口配置中,所有 IB 对象(QPMRPD 等)相关命令应流经主站 mlx5_core_dev,其他命令必须发送到从端口 mlx5_core_mdev,提供一个接口来获取非 IB 对象命令的正确 mdev
                ib_enum_roce_netdev pass_all_filter enum_all_gids_of_dev_cb
        rdma_for_each_port
            ib_cache_update -> IB/核心:仅在相应事件上更新 PKEYGID 缓存,HCA 中的 PKEYGID 表都可以保存数百个条目。 阅读它们是昂贵的。 部分原因是用于检索它们的 API 一次仅返回一个条目。 此外,在某些实现上,例如 CX-3VF 在这方面是半虚拟化的,并且必须依赖 PF 驱动程序来执行读取。 这再次需要 VFPF 的通信。 IB Core 的缓存会根据所有事件进行刷新。 因此,根据收到的事件分别为 IB_EVENT_PKEY_CHANGEIB_EVENT_GID_CHANGE 来过滤 PKEYGID 缓存的刷新
                rdma_is_port_valid
                ib_query_port
                rdma_protocol_roce
                config_non_roce_gid_cache
                ib_query_pkey
                ib_security_cache_change
    ib_setup_device_attrs
    ib_device_register_rdmacg
    rdma_counter_init
    dev_set_uevent_suppress
    ib_setup_port_attrs
    enable_device_and_get
        add_client_context
            client->add(device) -> .add    = ib_uverbs_add_one,
    dev_set_uevent_suppress
    kobject_uevent(&device->dev.kobj, KOBJ_ADD)
    ib_device_put
​
​
static struct ib_client uverbs_client = {
    .name   = "uverbs",
    .no_kverbs_req = true,
    .add    = ib_uverbs_add_one,
    .remove = ib_uverbs_remove_one,
    .get_nl_info = ib_uverbs_get_nl_info,
};
​
ib_uverbs_add_one  -> RDMA:允许 ib_client 在调用 add() 时失败,添加客户端时不允许失败,但所有客户端在其添加例程中都有各种失败路径。 这会产生一种非常边缘的情况:添加客户端后,在添加过程中失败并且未设置 client_data。 然后,核心代码仍然会使用 NULL client_data 调用其他以 client_data 为中心的操作,例如 remove()、rename()、get_nl_info()get_net_dev_by_params() - 这是令人困惑和意外的。 如果 add() 回调失败,则不要再为设备调用任何客户端操作,甚至删除。 删除操作回调中现在对 NULL client_data 的所有冗余检查。 更新所有 add() 回调以正确返回错误代码。 EOPNOTSUPP 用于 ULP 不支持 ib_device 的情况 - 例如,因为它仅适用于 IB
参考: https://www.cnblogs.com/vlhn/p/8301427.html
    ib_uverbs_create_uapi
        uverbs_alloc_api
            uapi_merge_def(uapi, ibdev, uverbs_core_api, false)
                uapi_merge_obj_tree
                    uapi_merge_method
                case UAPI_DEF_WRITE:
                    rc = uapi_create_write(uapi, ibdev, def, cur_obj_key, &cur_method_key);
                        method_elm->handler = def->func_write
    dev_set_name
    cdev_init
    cdev_device_add
    ib_set_client_data
​
// 定义用户态verbs核心API
static const struct uapi_definition uverbs_core_api[] = {
    UAPI_DEF_CHAIN(uverbs_def_obj_counters),
    UAPI_DEF_CHAIN(uverbs_def_obj_cq),
    UAPI_DEF_CHAIN(uverbs_def_obj_device),
    UAPI_DEF_CHAIN(uverbs_def_obj_dm),
    UAPI_DEF_CHAIN(uverbs_def_obj_flow_action),
    UAPI_DEF_CHAIN(uverbs_def_obj_intf),
    UAPI_DEF_CHAIN(uverbs_def_obj_mr),
    UAPI_DEF_CHAIN(uverbs_def_write_intf),
    {},
};
​
rdma user/kernel api/abi:
const struct uapi_definition uverbs_def_write_intf[] = {
    ...
    DECLARE_UVERBS_OBJECT(
    UVERBS_OBJECT_PD,
    DECLARE_UVERBS_WRITE(
        IB_USER_VERBS_CMD_ALLOC_PD,
        ib_uverbs_alloc_pd,
        UAPI_DEF_WRITE_UDATA_IO(struct ib_uverbs_alloc_pd,
                    struct ib_uverbs_alloc_pd_resp),
        UAPI_DEF_METHOD_NEEDS_FN(alloc_pd)),
    DECLARE_UVERBS_WRITE(
        IB_USER_VERBS_CMD_DEALLOC_PD,
        ib_uverbs_dealloc_pd,
        UAPI_DEF_WRITE_I(struct ib_uverbs_dealloc_pd),
        UAPI_DEF_METHOD_NEEDS_FN(dealloc_pd))),
    ...
}
====>
{
    .kind = UAPI_DEF_OBJECT_START, 
    .object_start = { .object_id = UVERBS_OBJECT_PD }, 
},
{ 
    .kind = UAPI_DEF_WRITE, 
    .scope = UAPI_SCOPE_OBJECT, 
    .write = { 
        .is_ex = 0, 
        .command_num = IB_USER_VERBS_CMD_ALLOC_PD 
        }, 
        .func_write = ib_uverbs_alloc_pd,   <- method_elm->handler = def->func_write
        .write.has_resp = 1 + (sizeof(struct { int:(-!!(offsetof(struct ib_uverbs_alloc_pd, response) != 0)); })) + (sizeof(struct { int:(-!!(sizeof(((struct ib_uverbs_alloc_pd *)0)->response) != sizeof(u64))); })), 
        .write.req_size = sizeof(struct ib_uverbs_alloc_pd), .write.resp_size = sizeof(struct ib_uverbs_alloc_pd_resp), 
        .write.has_udata = 1 + (sizeof(struct { int:(-!!(offsetof(struct ib_uverbs_alloc_pd, driver_data) != sizeof(struct ib_uverbs_alloc_pd))); })) + (sizeof(struct { int:(-!!(offsetof(struct ib_uverbs_alloc_pd_resp, driver_data) != sizeof(struct ib_uverbs_alloc_pd_resp))); })), 
},
{ 
    .kind = UAPI_DEF_IS_SUPPORTED_DEV_FN, 
    .scope = UAPI_SCOPE_METHOD, 
    .needs_fn_offset = offsetof(struct ib_device_ops, alloc_pd) + (sizeof(struct { int:(-!!(sizeof(((struct ib_device_ops *)0)->alloc_pd) != sizeof(void *))); })), }, 
{ 
    .kind = UAPI_DEF_WRITE, 
    .scope = UAPI_SCOPE_OBJECT, 
    .write = { 
        .is_ex = 0, 
        .command_num = IB_USER_VERBS_CMD_DEALLOC_PD 
    }, 
    .func_write = ib_uverbs_dealloc_pd, 
    .write.req_size = sizeof(struct ib_uverbs_dealloc_pd), 
},
{ 
    .kind = UAPI_DEF_IS_SUPPORTED_DEV_FN, 
    .scope = UAPI_SCOPE_METHOD, 
    .needs_fn_offset = offsetof(struct ib_device_ops, dealloc_pd) + (sizeof(struct { int:(-!!(sizeof(((struct ib_device_ops *)0)->dealloc_pd) != sizeof(void *))); })), 
}

以分配PD(ibv_alloc_pd)为例的单步调试Linux内核模块调用栈

代码语言:javascript
代码运行次数:0
运行
复制
ibv_alloc_pd
root@u20:~/project/rdma/rdma-core/build/bin# ./ibv_rc_pingpong -d rxe_ens3 -g 0
Thread 3 hit Breakpoint 2, ib_uverbs_alloc_pd (attrs=0xffffc900045f3c88) at drivers/infiniband/core/uverbs_cmd.c:406
406     {
(gdb) bt
#0  ib_uverbs_alloc_pd (attrs=0xffffc900045f3c88) at drivers/infiniband/core/uverbs_cmd.c:406
#1  0xffffffffa065c825 in ib_uverbs_handler_UVERBS_METHOD_INVOKE_WRITE (attrs=0xffffc900045f3c88) at drivers/infiniband/core/uverbs_std_types_device.c:41
#2  0xffffffffa0659a95 in ib_uverbs_run_method (num_attrs=<optimized out>, pbundle=<optimized out>) at drivers/infiniband/core/uverbs_ioctl.c:471
#3  ib_uverbs_cmd_verbs (ufile=<optimized out>, hdr=<optimized out>, user_attrs=<optimized out>) at drivers/infiniband/core/uverbs_ioctl.c:612
#4  0xffffffffa0659cc8 in ib_uverbs_ioctl (filp=<optimized out>, cmd=<optimized out>, arg=140726932025184) at drivers/infiniband/core/uverbs_ioctl.c:644
#5  0xffffffff81371f87 in vfs_ioctl (arg=<optimized out>, cmd=<optimized out>, filp=<optimized out>) at fs/ioctl.c:47
#6  file_ioctl (arg=<optimized out>, cmd=<optimized out>, filp=<optimized out>) at fs/ioctl.c:510
#7  do_vfs_ioctl (filp=0xffff8880aa0dff00, fd=<optimized out>, cmd=<optimized out>, arg=140726932025184) at fs/ioctl.c:697
#8  0xffffffff81372257 in ksys_ioctl (fd=3, cmd=3222805249, arg=140726932025184) at fs/ioctl.c:714
#9  0xffffffff8137229a in __do_sys_ioctl (arg=<optimized out>, cmd=<optimized out>, fd=<optimized out>) at fs/ioctl.c:721
#10 __se_sys_ioctl (arg=<optimized out>, cmd=<optimized out>, fd=<optimized out>) at fs/ioctl.c:719
#11 __x64_sys_ioctl (regs=<optimized out>) at fs/ioctl.c:719
#12 0xffffffff81005497 in do_syscall_64 (nr=<optimized out>, regs=0xffffc900045f3f58) at arch/x86/entry/common.c:290
#13 0xffffffff81e0008c in entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:175
#14 0x0000000000000004 in fixed_percpu_data ()
#15 0x00005596cd698260 in ?? () at drivers/infiniband/core/uverbs_cmd.c:3306
#16 0x0000000000000004 in fixed_percpu_data ()
#17 0x00007ffd8acb30ec in ?? () at drivers/infiniband/core/uverbs_cmd.c:3306
#18 0x00007ffd8acb2f78 in ?? () at drivers/infiniband/core/uverbs_cmd.c:3306
#19 0x00007ffd8acb2f40 in ?? () at drivers/infiniband/core/uverbs_cmd.c:3306
#20 0x0000000000000246 in ib_umem_odp_unmap_dma_pages (umem_odp=0x0 <fixed_percpu_data>, virt=<optimized out>, bound=<optimized out>)

(gdb) info threads
  Id   Target Id                    Frame 
  1    Thread 1.1 (CPU#0 [running]) timerqueue_add (head=0xffff88813ba1dee0, node=0xffff88813ba1e3a0) at lib/timerqueue.c:37
  2    Thread 1.2 (CPU#1 [running]) vring_map_single (vq=0xffff88813a868a80, cpu_addr=0xffff888138762e00, size=432, direction=<optimized out>) at drivers/virtio/virtio_ring.c:342
* 3    Thread 1.3 (CPU#2 [running]) timerqueue_add (head=0xffff88813ba9dee0, node=0xffff88813ba9e3a0) at lib/timerqueue.c:47
  4    Thread 1.4 (CPU#3 [running]) 0xffffffff811e5125 in seccomp_run_filters (match=<optimized out>, sd=<optimized out>) at kernel/seccomp.c:272
  5    Thread 1.5 (CPU#4 [halted ]) 0xffffffff81c46cce in native_safe_halt () at ./arch/x86/include/asm/irqflags.h:60
  6    Thread 1.6 (CPU#5 [halted ]) 0xffffffff81c46cce in native_safe_halt () at ./arch/x86/include/asm/irqflags.h:60
  7    Thread 1.7 (CPU#6 [halted ]) 0xffffffff81c46cce in native_safe_halt () at ./arch/x86/include/asm/irqflags.h:60
  8    Thread 1.8 (CPU#7 [running]) 0x00007f99178f6142 in ?? () at drivers/infiniband/core/uverbs_cmd.c:3306

分配保护域PD

代码语言:javascript
代码运行次数:0
运行
复制
...
ib_uverbs_alloc_pd 分配保护域
    uverbs_request
    uobj_alloc(UVERBS_OBJECT_PD, attrs, &ib_dev) -> handle SIGTRAP nostop noprint ignore
    rdma_zalloc_drv_obj(ib_dev, ib_pd)
    pd->res.type = RDMA_RESTRACK_PD
    ret = ib_dev->ops.alloc_pd(pd, &attrs->driver_udata) -> rxe_alloc_pd
        rxe_add_to_pool
            might_sleep_if -> _cond_resched
                should_resched -> unlikely(raw_cpu_read_4(__preempt_count) == preempt_offset)
                preempt_schedule_common
                do
                    preempt_latency_start
                    __schedule(true)
                    preempt_latency_stop(1)
                    preempt_enable_no_resched_notrace()
                rcu_all_qs
            kref_get
            ib_device_try_get
            elem->pool = pool
            kref_init
    uobj->object = pd
    rdma_restrack_uadd(&pd->res)
    uverbs_response
    uobj_alloc_commit

RDMA 用户态下发CMD及ioctl实现机制

代码语言:javascript
代码运行次数:0
运行
复制
static const struct file_operations uverbs_fops = {
    .owner   = THIS_MODULE,
    .write   = ib_uverbs_write,
    .open    = ib_uverbs_open,
    .release = ib_uverbs_close,
    .llseek  = no_llseek,
    .unlocked_ioctl = ib_uverbs_ioctl,
    .compat_ioctl = ib_uverbs_ioctl,
};...
ib_uverbs_add_one
    cdev_init(&uverbs_dev->cdev,device->ops.mmap ? &uverbs_mmap_fops : &uverbs_fops);ioctl:
...
ENTRY(entry_SYSCALL_64)
    movq    %rsp, %rsi
    call    do_syscall_64 /* returns with IRQs disabled */ <- entry_SYSCALL_64 () at arch/x86/entry/entry_64.S:175 -> __visible void do_syscall_64
        enter_from_user_mode
        ti = current_thread_info()
        regs->ax = sys_call_table[nr](regs) -> SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)-> ksys_ioctl(fd, cmd, arg)
            security_file_ioctl
            error = do_vfs_ioctl(f.file, fd, cmd, arg)
                switch (cmd)
                default:
                    error = file_ioctl(filp, cmd, arg)
                        switch (cmd)
                        vfs_ioctl(filp, cmd, arg) -> error = filp->f_op->unlocked_ioctl(filp, cmd, arg) <- .unlocked_ioctl = ib_uverbs_ioctl
                            copy_from_user
                            srcu_read_lock
                            ib_uverbs_cmd_verbs
                                radix_tree_iter_lookup uapi_key_ioctl_method(hdr->method_id)
                                ib_uverbs_run_method -> ret = handler(&pbundle->bundle) -> static int UVERBS_HANDLER(UVERBS_METHOD_INVOKE_WRITE)
                                    return method_elm->handler(attrs)
                                        ib_uverbs_alloc_pd
​
                            srcu_read_unlock
        syscall_return_slowpath(regs)
​
​
​
#define DECLARE_UVERBS_NAMED_METHOD(_method_id, ...)                           \
    static const struct uverbs_attr_def *const UVERBS_METHOD_ATTRS(        \
        _method_id)[] = { __VA_ARGS__ };                               \
    static const struct uverbs_method_def UVERBS_METHOD(_method_id) = {    \
        .id = _method_id,                                              \
        .handler = UVERBS_HANDLER(_method_id),                         \
        .num_attrs = ARRAY_SIZE(UVERBS_METHOD_ATTRS(_method_id)),      \
        .attrs = &UVERBS_METHOD_ATTRS(_method_id),                     \
    }    

RXE创建QP

代码语言:javascript
代码运行次数:0
运行
复制
...
.create_qp = rxe_create_qp, RXE创建QP
rxe_create_qp
    rxe_qp_from_init
        rxe_qp_init_req
            rxe_init_task(rxe, &qp->comp.task, qp, rxe_completer, "comp")
            timer_setup(&qp->rnr_nak_timer, rnr_nak_timer, 0)
            timer_setup(&qp->retrans_timer, retransmit_timer, 0) <- mod_timer(&qp->retrans_timer
        rxe_qp_init_resp
            rxe_init_task(rxe, &qp->resp.task, qp, rxe_responder, "resp");
                check_resource
                    if (pkt->mask & RXE_RWR_MASK)
                        get_srq_wqe
                            wqe = queue_head(q)
                            memcpy(&qp->resp.srq_wqe, wqe, sizeof(qp->resp.srq_wqe))
                            qp->resp.wqe = &qp->resp.srq_wqe.wqe
                            advance_consumer(q)
                            srq->ibsrq.event_handler(&ev, srq->ibsrq.srq_context)
                        qp->resp.wqe = queue_head(qp->rq.queue);
rxe_completer
    case COMPST_GET_WQE
        state = get_wqe(qp, pkt, &wqe)

参考

Linux提交记录: https://github.com/ssbandjl/linux/commit/8700e3e7c4857d28ebaa824509934556da0b3e76

RDMA笔记: https://github.com/ssbandjl/linux/blob/master/rdma

Linux内核笔记: https://github.com/ssbandjl/linux/blob/master/readme_xb

Nvidia配置RXE: https://enterprise-support.nvidia.com/s/article/howto-configure-soft-roce

RXE指南: https://man7.org/linux/man-pages/man7/rxe.7.html

RXE文档: https://github.com/linux-rdma/rdma-core/blob/master/Documentation/rxe.md

迈络思OFED/MLNX_OFED驱动: https://github.com/ssbandjl/MLNX_OFED_SRC-5.9-0.5.6.0.git

IB带宽路线图: https://www.infinibandta.org/infiniband-roadmap/

晓兵(ssbandjl)

博客: https://cloud.tencent.com/developer/user/5060293/articles | https://logread.cn | https://blog.csdn.net/ssbandjl | https://www.zhihu.com/people/ssbandjl/posts

DPU专栏

https://cloud.tencent.com/developer/column/101987

技术会友: 欢迎对DPU/智能网卡/卸载/网络,存储加速/安全隔离等技术感兴趣的朋友加入DPU技术交流群

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
14.MySQL(二) 数据之表操作表内容操作Mysql 连接事务外键
数据之表操作 1.创建表 语法:CREATE TABLE table_name (column_name column_type); create table student( -> id INT NOT NULL AUTO_INCREMENT, -> name CHAR(32) NOT NULL, -> age INT NOT NULL, -> regiiter_date DATE, -> PRIMARY KEY(id) -> ); a
zhang_derek
2018/04/11
3.3K0
14.MySQL(二)
		数据之表操作表内容操作Mysql 连接事务外键
MYSQL库,表,记录的基本操作
  mysql – 用户权限相关数据   test – 用于用户测试数据   information_schema – MySQL本身架构相关数据
全栈程序员站长
2022/07/21
1.7K0
MYSQL库,表,记录的基本操作
MySQL单表&约束&事务
需求: 1 查询员工的总数 2 查看员工总薪水、最高薪水、最小薪水、薪水的平均值 3 查询薪水大于4000员工的个数 4 查询部门为’教学部’的所有员工的个数 5 查询部门为’市场部’所有员工的平均薪水
用户5513909
2023/04/25
1.3K0
MySQL单表&约束&事务
MySQL常用基础 - 小白必看
2、create database if not exists 数据库名 (判断数据库是否存在,不存在则创建)
EXI-小洲
2023/01/09
1.3K0
MySQL常用基础 - 小白必看
MySQL学习笔记2
DQL:查询语句 1. 排序查询 * 语法:order by 子句 * order by 排序字段1 排序方式1 , 排序字段2 排序方式2... * 排序方式: * ASC:升序,默认的。 * DESC:降序。 * 注意: * 如果有多个排序条件,则当前边的条件值一样时,才会判断第二条件。 2. 聚合函数:将一列数据作为一个整体,进行纵向的计算。 1. count:计算个数 1. 一般选
Eternity
2022/08/24
6850
【数据库_02】MySQL-约束
一、MySQL查询 1. 聚合函数 ① 统计 * 语法 count(需要统计的字段) * 注意 所有聚合函数都会自动跳过 null,解决方案 count(ifnull(字段,0));或count(*) * 示例 select count(*) from student; ② 最大值 * 语法 max(字段) * 示例 select max(math) from student; ③ 最小
用户8250147
2021/02/04
7470
MySQL常用语句
DDL操作数据库、表 1. 操作数据库:CRUD 创建(Create) 创建数据库:
一点博客
2019/07/24
7960
MySQL约束课堂笔记
张哥编程
2024/12/19
1610
MySQL的库表详细操作
  关于库的内容,咱们就说这些吧,哈哈,有点少是吧,不是咱们的重点,来看下面的表操作~~~
changxin7
2022/05/06
1.1K0
MySQL的库表详细操作
Mysql增删改查sql语句练习
Mysql增删改查sql语句练习 关于数据库的一些操作: 进入mysql 命令行: mysql -uroot –p 查看所有数据库: show databases; 创建数据库: create database wg charset utf8; 删除数据库: drop database wg; 选择数据库: use databases; 查看所有表: show tables; 查看创建数据库的语句:show create database databasename; 查看创建表的语句:show create table tablename; 查看表结构:desc tablename; 增: mysql> use wg; mysql> create table students( id int auto_increment primary key,name varchar(10) not null,sex varchar(12),address varchar(50),phone int not null unique); #自增长 auto_increment #非空 not null #默认值 default ‘xx’ #唯一 unique #指定字符集 charset #主键 primary key mysql> create table scores(id int auto_increment primary key,s_id int not null,grade float not null); 数据: mysql> insert into student (id,name,sex,phone) values(122,’wg’,’男’,’110’); mysql> insert into students values(111,’wg’,’121’,’dd’) ; 删: mysql> drop table tablename; mysql> truncate tablename; 快速删除表数据,自增长id从头在来,快速,从磁盘直接删除,不可恢复 mysql> delete from student; 删除整个表的数据,自增长继续 改: mysql> alter table oldtable rename newtable; 改表名 mysql> alter table scores modify s_id varchar(20);
全栈程序员站长
2022/07/25
2.2K0
Mysql增删改查sql语句练习
【MySQL】008-表的约束
说明链接:https://blog.csdn.net/u014743697/article/details/54136092
訾博ZiBo
2025/01/06
990
MySQL高级面试篇之索引详解大全
  索引是表的目录,在查找内容之前可以先在目录中查找索引位置,以此快速定位查询数据。对于索引,会保存在额外的文件中。
用户5224393
2019/08/13
6430
MySQL进阶之索引
索引是对数据库表中一个或多个列(例如,employee 表的姓名 (name) 列)的值进行排序的结构。如果想按特定职员的姓来查找他或她,则与在表中搜索所有的行相比,索引有助于更快地获取信息。
测试小兵
2019/11/19
4570
【MySQL】SQL语句查询、约束、备份与恢复
SELECT * FROM product ORDER BY price DESC;
陶然同学
2023/02/24
2K0
【MySQL】SQL语句查询、约束、备份与恢复
MySQL基础笔记
MySQL基础 一、数据库的基本概念 1.为什么要学数据库? 之前我们如果想将一些数据实现永久化存储,可以怎么做呢?没错。使用IO流的技术将数据保存到本地文件中 但是接下来我有这样一个需求:将下面的user.txt文件中的王五年龄修改为35 张三 23 男 李四 24 男 王五 25 女 赵六 26 女 周七 27 男 我们要如何实现呢? 可以采用字符缓冲流,将每一行数据读取出来,封装为User对象。将多个User对象保存到集合中 然后遍历集合,将王五对象的年龄修改为35,再重新将集合中的对象信息写
楠羽
2022/11/18
2.8K0
MySQL基础笔记
盘点MySQL慢查询的12个原因
日常开发中,我们经常会遇到数据库慢查询。那么导致数据慢查询都有哪些常见的原因呢?今天田螺哥就跟大家聊聊导致MySQL慢查询的12个常见原因,以及对应的解决方法。
捡田螺的小男孩
2023/02/24
1.5K0
盘点MySQL慢查询的12个原因
MySQL总结
2.alter table t1 modify name char(3); 修改类型
changxin7
2019/09/12
2K0
MySQL总结
My SQL常用操作汇总
写这篇随笔的目的是我发现了在上一篇关于My SQL的随笔中存在一些不严谨的代码问题,在这里再次简单的总结一下并加以改进,以代码为主。 # !每行命令必须以分号(;)结尾 先通过命令行进入数据库客户端 mysql -h服务端ip地址 -P(大写)服务端使用的端口,一般为3306 -p(小写) 回车之后输入密码,进入 显示所有数据库 show databases; 创建数据库并设置编码   - 数据库创建时可以设置字符集以及排序规则   - 字符集一般使用utf8的,排序规则一般使用忽略大
汪凡
2018/05/29
9550
MySQL[一]
1·什么是MySQL丶Oracle丶SQLite丶Access丶MS SQL Server等?
Wyc
2018/09/11
8830
12个MySQL慢查询的原因分析「建议收藏」
很多时候,我们的慢查询,都是因为没有加索引。如果没有加索引的话,会导致全表扫描的。因此,应考虑在 where 的条件列,建立索引,尽量避免全表扫描。
全栈程序员站长
2022/11/04
2K0
相关推荐
14.MySQL(二) 数据之表操作表内容操作Mysql 连接事务外键
更多 >
目录
  • 术语
  • 简介
    • 配置和使用
    • 架构
  • IB Spec/RDMA相关参考
  • 驱动实现
    • 注册IB设备(ib_register_device)
    • 以分配PD(ibv_alloc_pd)为例的单步调试Linux内核模块调用栈
    • 分配保护域PD
    • RDMA 用户态下发CMD及ioctl实现机制
    • RXE创建QP
  • 参考
  • 晓兵(ssbandjl)
    • DPU专栏
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档