主动端(active)
被动端(passive)
RDMA CM 是一种通信管理器,用于设置可靠、连接和不可靠的数据报数据传输。 它提供用于建立连接的 RDMA 传输中立接口。 API 概念基于套接字,但适用于基于队列对 (QP) 的语义:通信必须通过特定的 RDMA 设备进行,并且数据传输基于消息。 RDMA CM 可以控制 RDMA API 的 QP 和通信管理(连接建立/拆除)部分,或者仅控制通信管理部分。 它与 libibverbs 库定义的 verbs API 结合使用。 libibverbs 库提供了发送和接收数据所需的底层接口。 RDMA CM 可以异步或同步操作。 用户通过在特定调用中使用 rdma_cm 事件通道参数来控制操作模式。 如果提供了事件通道,rdma_cm 标识符将报告该通道上的事件数据(例如连接结果)。 如果未提供通道,则所选 rdma_cm 标识符的所有 rdma_cm 操作将被阻止,直到完成。 RDMA CM 为不同的 libibverbs 提供商提供了一个选项来宣传和使用特定于该提供商的各种 QP 配置选项。 此功能称为 ECE(增强连接建立)
rdma_cm 支持通过 libibverbs 库和接口提供的所有verbs api。 但是,它还为一些更常用的verbs功能提供了包装函数。 完整的抽象verbs调用集是:
rdma_reg_msgs - 注册用于发送和的缓冲区数组接收
rdma_reg_read - 为 RDMA 读取操作注册缓冲区
rdma_reg_write - 为 RDMA 写操作注册缓冲区
rdma_dereg_mr - 取消注册内存区域
rdma_post_recv - 发布缓冲区以接收消息
rdma_post_send - 发布缓冲区以发送消息
rdma_post_read - 发布 RDMA 以将数据读入缓冲区
rdma_post_write - 发布 RDMA 以从缓冲区发送数据
rdma_post_recvv - 发布缓冲区向量以接收消息
rdma_post_sendv - 发布缓冲区向量来发送消息
rdma_post_readv - 发布缓冲区向量以接收 RDMA读
rdma_post_writev - 发布缓冲区向量以发送 RDMA 写入
rdma_post_ud_send - 发布缓冲区以在 UD QP 上发送消息
rdma_get_send_comp - 获取发送或 RDMA 的完成状态操作
rdma_get_recv_comp - 获取有关已完成接收的信息
本节提供通信主动方(active)或客户端的基本操作的总体概述。 此流程假定异步操作,并显示低级调用详细信息。 对于同步操作,将消除对 rdma_create_event_channel、rdma_get_cm_event、rdma_ack_cm_event 和 rdma_destroy_event_channel 的调用。 抽象调用(例如 rdma_create_ep)将其中多个调用封装在单个 API 下。 用户还可以参考示例应用程序来获取代码示例。 一般的连接流程是:
rdma_getaddrinfo 检索目的地的地址信息
rdma_create_event_channel 创建接收事件的通道
rdma_create_id 分配一个 rdma_cm_id,这在概念上类似于套接字
rdma_resolve_addr 获取本地RDMA设备以到达远程地址
rdma_get_cm_event 等待 RDMA_CM_EVENT_ADDR_RESOLVED 事件
rdma_ack_cm_event 确认事件
rdma_create_qp 为通信分配一个QP
rdma_resolve_route 确定到远程地址的路由
rdma_get_cm_event 等待 RDMA_CM_EVENT_ROUTE_RESOLVED 事件
rdma_ack_cm_event 确认事件
rdma_connect 连接远程服务器
rdma_get_cm_event 等待 RDMA_CM_EVENT_ESTABLISHED 事件
rdma_ack_cm_event 确认事件
通过连接执行数据传输
rdma_disconnect 断开连接
rdma_get_cm_event 等待 RDMA_CM_EVENT_DISCONNECTED 事件
rdma_ack_cm_event 确认事件
rdma_destroy_qp 销毁 QP
rdma_destroy_id 释放rdma_cm_id
rdma_destroy_event_channel 释放事件通道
rdma_set_local_ece 设置所需的 ECE 选项
在服务端/被动方也是以上类似的操作, 用于在节点之间设置不可靠的数据报(UD)通信。 然而,QP 之间没有形成实际连接,因此不需要断开连接。 尽管此示例显示客户端发起/断开连接,但连接的任何一方都可以发起断开连接
本节概述了通信的被动端或服务器端的基本操作。 一般的连接流程是
rdma_create_event_channel 创建接收事件的通道
rdma_create_id 分配一个rdma_cm_id,这在概念上类似于套接字
rdma_bind_addr 设置本地监听的端口号
rdma_listen 开始监听连接请求
rdma_get_cm_event 等待具有新 rdma_cm_id 的 RDMA_CM_EVENT_CONNECT_REQUEST 事件
rdma_create_qp 为新 rdma_cm_id 上的通信分配 QP
rdma_accept 接受连接请求
rdma_ack_cm_event 确认事件
rdma_get_cm_event 等待 RDMA_CM_EVENT_ESTABLISHED 事件
rdma_ack_cm_event 确认事件
通过连接执行数据传输
rdma_get_cm_event 等待 RDMA_CM_EVENT_DISCONNECTED 事件
rdma_ack_cm_event 确认事件
rdma_disconnect 断开连接
rdma_destroy_qp 销毁 QP
rdma_destroy_id 释放连接的rdma_cm_id
rdma_destroy_id 释放监听的rdma_cm_id
rdma_destroy_event_channel 释放事件通道
rdma_get_remote_ece 获取客户端发送的ECe选项
rdma_set_local_ece 设置所需的 ECE 选项
rdma_accept(3), rdma_ack_cm_event(3), rdma_bind_addr(3),
rdma_connect(3), rdma_create_ep(3), rdma_create_event_channel(3),
rdma_create_id(3), rdma_create_qp(3), rdma_dereg_mr(3),
rdma_destroy_ep(3), rdma_destroy_event_channel(3),
rdma_destroy_id(3), rdma_destroy_qp(3), rdma_disconnect(3),
rdma_event_str(3), rdma_free_devices(3), rdma_getaddrinfo(3),
rdma_get_cm_event(3), rdma_get_devices(3), rdma_get_dst_port(3),
rdma_get_local_addr(3), rdma_get_peer_addr(3),
rdma_get_recv_comp(3), rdma_get_remote_ece(3),
rdma_get_request(3), rdma_get_send_comp(3), rdma_get_src_port(3),
rdma_join_multicast(3), rdma_leave_multicast(3), rdma_listen(3),
rdma_migrate_id(3), rdma_notify(3), rdma_post_read(3)
rdma_post_readv(3), rdma_post_recv(3), rdma_post_recvv(3),
rdma_post_send(3), rdma_post_sendv(3), rdma_post_ud_send(3),
rdma_post_write(3), rdma_post_writev(3), rdma_reg_msgs(3),
rdma_reg_read(3), rdma_reg_write(3), rdma_reject(3),
rdma_resolve_addr(3), rdma_resolve_route(3),
rdma_get_remote_ece(3), rdma_set_option(3), mckey(1),
rdma_client(1), rdma_server(1), rping(1), ucmatose(1), udaddy(1)
server: 服务端创建事件通道,创建通信标识ID, 启动RDMA监听
rdma_create_event_channel <- vrb_eq_open <- fi_eq_open
rdma_create_id <- vrb_create_ep <- vrb_open_ep <- fi_endpoint, librdmacm/cma.c -> rdma_create_event_channel HG创建端点 na_ofi_basic_ep_open
rdma_listen -> fi_listen -> .listen = vrb_pep_listen -> vrb_pep_listen -> rdma_listen, na_ofi_basic_ep_open -> fi_enable -> rxm_ep_ctrl -> rxm_start_listen -> fi_listen
if (ofi_epoll_add(_eq->epollfd, _eq->channel->fd, OFI_EPOLL_IN, NULL)) -> 将rdma事件通道的fd关联到eq的epollfd
client:客户端创建事件通道,创建通信标识ID, 解析服务端地址, 发送数据时, 获取连接, 解析路由
rdma_create_event_channel
rdma_create_id
rdma_resolve_addr -> RDMA_CM_EVENT_ADDRESS_RESOLVED -> rxm_open_conn -> fi_endpoint (vrb_open_ep) -> rdma_resolve_addr HG -> HG_Trigger -> hg_op_id->callback(&hg_cb_info) 查询地址设置的回调 lookup_callback -> HG_Forward -> NA_Msg_send_unexpected -> fi_senddata -> rxm_get_conn -> fi_endpoint -> vrb_open_ep -> vrb_create_ep -> rdma_resolve_addr
rdma_resolve_route -> RDMA_CM_EVENT_ROUTE_RESOLVED -> rxm_send_connect -> fi_connect -> rdma_resolve_route 也是HG发送的时候建立连接
------------------
分配RDMA结构(服务端和客户端对等, 均要执行), 查询网卡, 分配保护域, 创建完成通道,完成队列, 通知完成队列准备好接收完成事件, 创建队列对, 注册内存
ibv_query_device <- fi_getinfo -> vrb_getinfo -> ibv_query_device
ibv_alloc_pd <- fi_domain -> rxm_domain_open -> ibv_alloc_pd
ibv_create_comp_channel -> na_ofi_eq_open -> fi_cq_open -> vrb_cq_open -> ibv_create_comp_channel
ibv_create_cq -> na_ofi_eq_open -> fi_cq_open -> vrb_cq_open -> ibv_create_cq
ibv_req_notify_cq -> na_ofi_poll_try_wait -> fi_trywait -> vrb_trywait -> vrb_cq_trywait
rdma_create_qp -> na_ofi_context_create -> fi_enable -> rdma_create_qp
ibv_reg_mr -> NA_Mem_register -> na_ofi_mem_register -> fi_mr_regv -> ibv_reg_mr
------------------
轮询完成队列
ibv_poll_cq -> na_ofi_msg_send_unexpected -> fi_senddata -> fi_send -> vrb_flush_cq -> ibv_poll_cq
接收端提前往接收队列放置工作请求WR
ibv_post_recv -> rxm_open_conn -> ibv_post_recv | na_ofi_tag_recv, na_ofi_msg_multi_recv -> fi_trecv -> ibv_post_recv
客户端与服务端建立连接
rdma_connect -> server -> RDMA_CM_EVENT_CONNECT_REQUEST, -> fi_senddata -> rxm_get_conn -> rdma_connect
server:
case RDMA_CM_EVENT_CONNECT_REQUEST
------------------
分配RDMA结构
ibv_query_device
ibv_alloc_pd
ibv_create_comp_channel
ibv_create_cq
ibv_req_notify_cq
rdma_create_qp
ibv_reg_mr
------------------
ibv_post_recv
rdma_accept
RDMA_CM_EVENT_ESTABLISHED
ibv_post_send
clinet: 客户端发送非预期消息
RDMA_CM_EVENT_ESTABLISHED
ibv_post_send -> na_ofi_msg_send_unexpected -> ibv_post_send
销毁资源
server:
rdma_disconnect
ibv_dereg_mr
ibv_destroy_cq
ibv_destroy_comp_channel
rdma_destroy_qp
rdma_destroy_id
rdma_destroy_event_channel
client:
rdma_disconnect
ibv_dereg_mr
ibv_destroy_cq
ibv_destroy_comp_channel
rdma_destroy_qp
rdma_destroy_id
rdma_destroy_event_channel
librdmacm/examples/rdma_server.c -> main
examples/rdma_server.c -> main -> run
static const char *server = "0.0.0.0";
static const char *port = "7471";
hints.ai_flags = RAI_PASSIVE;
hints.ai_port_space = RDMA_PS_TCP
rdma_getaddrinfo
ucma_init
sync_devices_list
ibv_get_device_list
insert_cma_dev
cma_dev->guid = ibv_get_device_guid(dev)
list_add_after(&cma_dev_list, &p->entry, &cma_dev->entry)
ucma_set_af_ib_support
ucma_getaddrinfo
ucma_convert_to_rai
rdma_create_ep
ucma_passive_ep -> librdmacm:在 rdma_create_ep 中添加对被动端的支持,允许调用 rdma_create_ep 来监听 rdma_cm_id
rdma_bind_addr2
rdma_listen -> CMA_INIT_CMD(&cmd, sizeof cmd, LISTEN) -> ucma_listen
ucma_query_addr
rdma_get_request
rdma_get_cm_event
retry:
CMA_INIT_CMD_RESP(&cmd, sizeof cmd, GET_EVENT, &resp, sizeof resp) -> ucma_get_event
ibv_query_qp
rdma_reg_msgs
rdma_post_recv
rdma_accept(id, NULL) -> librdmacm/rdma_server:添加新的示例服务器应用程序,提供一个简单的服务器应用程序来演示接受来自客户端的连接请求并交换消息所需的最少编码
ucma_valid_param(id_priv, conn_param)
ucma_modify_qp_rtr -> librdmacm:允许用户指定最大 RDMA 资源,允许用户指示库应选择建立连接时应使用的最大可用 RDMA 读取值。 库根据本地硬件限制和连接请求数据选择最大值
qp_attr.qp_state = IBV_QPS_INIT
rdma_init_qp_attr(id, &qp_attr, &qp_attr_mask)
CMA_INIT_CMD_RESP(&cmd, sizeof cmd, INIT_QP_ATTR, &resp, sizeof resp) -> ucma_init_qp_attr
ibv_modify_qp(id->qp, &qp_attr, qp_attr_mask
qp_attr.qp_state = IBV_QPS_RTR
rdma_init_qp_attr(id, &qp_attr, &qp_attr_mask)
rdma_seterrno(ibv_modify_qp(id->qp, &qp_attr, qp_attr_mask))
ucma_modify_qp_rts
CMA_INIT_CMD(&cmd, sizeof cmd, ACCEPT) -> ucma_accept
ucma_complete(id)
rdma_get_cm_event(id_priv->id.channel, &id_priv->id.event)
rdma_get_recv_comp
rdma_post_send
rdma_get_send_comp
以下是部分接口详解:
创建事件通道:
rdma_create_event_channel - 打开用于报告通信事件的通道。 描述:异步事件通过事件通道上报给用户。 每个事件通道映射到一个文件描述符。 注意:所有创建的事件通道必须通过调用 rdma_destroy_event_channel 销毁。 用户应调用 rdma_get_cm_event 来检索事件通道上的事件。 另请参见:rdma_get_cm_event、rdma_destroy_event_channel, 流程: 查询获取所有IB设备,存放在cma_dev_array全局数组中;检测是否支持AF_IB协议, 打开CM的fd, 返回事件
struct rdma_event_channel *rdma_create_event_channel(void)
ucma_init()
channel->fd = open_cdev(dev_name, dev_cdev) -> 打开fd /dev/infiniband/rdma_cm
返回通道
用户态完整调用栈:
struct rdma_event_channel *rdma_create_event_channel(void)
ucma_init
if (cma_dev_cnt)
check_abi_version
ibv_get_device_list
cma_dev_array = calloc(dev_cnt, sizeof(*cma_dev_array))
cma_dev_array[i].guid = ibv_get_device_guid(dev_list[i])
ucma_set_af_ib_support()
rdma_create_id(NULL, &id, NULL, RDMA_PS_IB)
rdma_create_id2
ucma_init
ucma_alloc_id
rdma_create_event_channel -> to kernel -> ucma_open -> bpftrace -e 'kprobe:ucma_open{ printf("bt:%s\n", kstack); }'
CMA_INIT_CMD_RESP(&cmd, sizeof cmd, CREATE_ID, &resp, sizeof resp) -> UCMA_CMD_CREATE_ID -> ucma_create_id
ret = write(id_priv->id.channel->fd, &cmd, sizeof cmd)
VALGRIND_MAKE_MEM_DEFINED(&resp, sizeof resp)
ucma_insert_id(id_priv)
idm_set(&ucma_idm, id_priv->handle, id_priv)
rdma_bind_addr(id, (struct sockaddr *) &sib)
rdma_bind_addr2
CMA_INIT_CMD(&cmd, sizeof cmd, BIND)
ucma_query_addr
ucma_query_gid
channel->fd = open("/dev/infiniband/rdma_cm", O_RDWR | O_CLOEXEC)
return channel
分配通信标识
int rdma_create_id(struct rdma_event_channel *channel, struct rdma_cm_id **id, void *context, enum rdma_port_space ps)
cmd = UCMA_CMD_CREATE_ID
ret = write(id_priv->id.channel->fd, &cmd, sizeof cmd) -> 通知内核
ucma_insert_id(id_priv)
idm_set -> librdmacm:定义通过 RDMA 接口 (rsockets) 的流式传输,引入了一组新的 API,支持 RDMA 设备上的字节流接口。 新接口与套接字匹配,只是所有函数调用都以“r”为前缀。 定义了以下函数: rsocket rbind、rlisten、raccept、rconnect rshutdown、rclose rrecv、rrecvfrom、rrecvmsg、rread、rreadv rsend、rsendto、rsendmsg、rwrite、rwritev rpoll、rselect rgetpeername、rgetsockname rsetsockopt、rgetsockopt、rfcntl 函数采用相同的方法 参数与用于套接字的参数相同。 目前支持以下功能和标志: PF_INET、PF_INET6、SOCK_STREAM、IPPROTO_TCP MSG_DONTWAIT、MSG_PEEK SO_REUSEADDR、TCP_NODELAY、SO_ERROR、SO_SNDBUF、SO_RCVBUF O_NONBLOCK rpoll 调用支持轮询 rsockets 和普通 fd,
index_map(二级指针): 索引映射 - 将结构与索引关联起来。 同步必须由调用者提供。 调用者必须通过将索引映射设置为 0 来初始化它
提供一组索引操作接口, 设置,插入(idx_insert),增长(idx_grow),替换,移除,清理等
rsocket是附在rdma_cm库中的一个子模块,提供了完全类似于socket接口的rdma调用
对于rdma编程,目前主流实现是利用rdma_cm来建立连接,然后利用verbs来传输数据。 rdma_cm和ibverbs分别会创建一个fd,这两个fd的分工不同。rdma_cm fd主要用于通知建连相关的事件,verbs fd则主要通知有新的cqe发生。当直接对rdma_cm fd进行poll/epoll监听时,此时只能监听到POLLIN事件,这意味着有rdma_cm事件发生。当直接对verbs fd进行poll/epoll监听时,同样只能监听到POLLIN事件,这意味着有新的cqe 作者:异客z 链接:https://www.jianshu.com/p/4d71f1c8e77c
内核态调用栈:
rdma_create_id
static ssize_t ucma_write
ret = ucma_cmd_table[hdr.cmd](file, buf + sizeof(hdr), hdr.in, hdr.out) -> ucma_create_id
ctx = ucma_alloc_ctx(file)
cm_id = rdma_create_user_id(ucma_event_handler, ctx, cmd.ps, qp_type)
__rdma_create_id
id_priv->state = RDMA_CM_IDLE
id_priv->id.event_handler = event_handler <- cma_listen_handler
id_priv->gid_type = IB_GID_TYPE_IB
id_priv->seq_num &= 0x00ffffff
rdma_restrack_new(&id_priv->res, RDMA_RESTRACK_CM_ID)
if (parent)
rdma_restrack_parent_name
rdma_restrack_attach_task
rdma_restrack_set_name
rdma_restrack_attach_task
ucma_set_ctx_cm_id(ctx, cm_id)
ucma_finish_ctx(ctx)
list_add_tail(&ctx->list, &ctx->file->ctx_list)
xa_store(&ctx_table, ctx->id, ctx, GFP_KERNEL)
内核态:
ucma_bind
ucma_get_ctx
rdma_bind_addr
state = RDMA_CM_ADDR_BOUND,
cma_check_linklocal
memcpy(cma_src_addr(id_priv), addr, rdma_addr_size(addr))
id_priv->afonly = 1
daddr = cma_dst_addr(id_priv) -> return (struct sockaddr *) &id_priv->id.route.addr.dst_addr
ret = cma_get_port(id_priv)
cma_select_ib_ps
cma_use_port
bind_list = cma_ps_find(id_priv->id.route.addr.dev_addr.net, ps, snum)
cma_alloc_port
or cma_check_port
cma_bind_port
用户态:
rdma_listen -> ucma_listen
atomic_set(&ctx->backlog, cmd.backlog)
rdma_listen
if (!cma_comp_exch(id_priv, RDMA_CM_ADDR_BOUND, RDMA_CM_LISTEN))
rdma_bind_addr
rdma_bind_addr_dst cma_dst_addr
cma_check_port
rdma_cap_ib_cm
cma_ib_listen
ib_cm_insert_listen(id_priv->id.device, cma_ib_req_handler, svc_id)
cma_listen_on_all
内核调用栈:
#0 cma_listen_on_dev (id_priv=0xffff888108b66800, cma_dev=0xffff88810980e480, to_destroy=0xffffc9000382fd08) at drivers/infiniband/core/cma.c:2540
#1 0xffffffffc05427a8 in cma_listen_on_all (id_priv=0xffff888108b66800) at drivers/infiniband/core/cma.c:2592
#2 rdma_listen (id=0xffff888108b66800, backlog=1024) at drivers/infiniband/core/cma.c:3867
#3 0xffffffffc0483d70 in ucma_listen (file=<optimized out>, inbuf=<optimized out>, in_len=<optimized out>, out_len=<optimized out>) at drivers/infiniband/core/ucma.c:1105
cma_listen_on_dev
__rdma_create_id(net, cma_listen_handler, id_priv,
dev_id_priv->state = RDMA_CM_ADDR_BOUND
_cma_attach_to_dev
rdma_restrack_add
cma_id_get
rdma_listen
if (!cma_comp_exch(id_priv, RDMA_CM_ADDR_BOUND, RDMA_CM_LISTEN))
cma_ib_listen
addr = cma_src_addr(id_priv)
svc_id = rdma_get_service_id(&id_priv->id, addr)
id = ib_cm_insert_listen(id_priv->id.device, cma_ib_req_handler, svc_id)
cm_id_priv = cm_alloc_id_priv
cm_init_listen
listen_id_priv = cm_insert_listen(cm_id_priv, cm_handler)
cur_cm_id_priv = rb_entry(parent, struct cm_id_private,
rb_link_node(&cm_id_priv->service_node, parent, link)
rb_insert_color(&cm_id_priv->service_node, &cm.listen_service_table) -> 该函数是红黑树相对于普通二叉排序树最大的差别。这个函数中平衡二叉树的方式主要有3种:a)改变颜色,0表示red,1表示black。对于对齐的节点,低4位总是0,所以默认的情况下是红色的节点。那么对于父节点是black的情况,直接插入即可,无需改变颜色。如果父节点是红色则需要改变颜色。b)左旋转,将一个节点的右孩子变为父节点,节点本身变为左孩子。c)右旋转,将一个节点的做孩子变为父节点,节点本身变为右孩子
cm_id_priv->id.state = IB_CM_LISTEN
list_add_tail(&dev_id_priv->listen_list, &id_priv->listen_list)
内核态:
ucma_init_qp_attr
rdma_init_qp_attr(ctx->cm_id, &qp_attr, &resp.qp_attr_mask)
cma_ib_init_qp_attr
ib_addr_get_pkey
ib_find_cached_pkey
or ib_cm_init_qp_attr > 每一个QP状态所设置的QP属性不一样
cm_init_qp_init_attr
qp_attr->qp_access_flags = IB_ACCESS_REMOTE_WRITE
qp_attr->pkey_index = cm_id_priv->av.pkey_index
qp_attr->port_num = cm_id_priv->av.port->port_num
cm_init_qp_rtr_attr
qp_attr->ah_attr = cm_id_priv->av.ah_attr
qp_attr->ah_attr.ib.dlid = cm_id_priv->av.dlid_datapath
qp_attr->path_mtu = cm_id_priv->path_mtu
qp_attr->dest_qp_num = be32_to_cpu(cm_id_priv->remote_qpn)
qp_attr->rq_psn = be32_to_cpu(cm_id_priv->rq_psn)
qp_attr->max_dest_rd_atomic
qp_attr->min_rnr_timer = 0
rdma_ah_get_dlid(&cm_id_priv->alt_av.ah_attr)
qp_attr->alt_port_num = cm_id_priv->alt_av.port->port_num
...
cm_init_qp_rts_attr
qp_attr->sq_psn = be32_to_cpu(cm_id_priv->sq_psn)
qp_attr->retry_cnt = cm_id_priv->retry_count
qp_attr->rnr_retry = cm_id_priv->rnr_retry_count
qp_attr->max_rd_atomic = cm_id_priv->initiator_depth
qp_attr->timeout = cm_id_priv->av.timeout
qp_attr->path_mig_state = IB_MIG_REARM
...
or iw_cm_init_qp_attr
iwcm_init_qp_init_attr
iwcm_init_qp_rts_attr
qp_attr->timeout = id_priv->timeout
qp_attr->min_rnr_timer = id_priv->min_rnr_timer
内核态:
UCMA_CMD_CONNECT -> static ssize_t (*ucma_cmd_table[]) -> static ssize_t ucma_connect
copy_from_user
ucma_get_ctx_dev
ucma_copy_conn_param -> RDMA/cma:为AF_IB设置qkey,允许用户在使用AF_IB时指定qkey。 qkey 被添加到 struct rdma_ucm_conn_param 中代替保留字段,但为了向后兼容,仅当关联的 rdma_cm_id 使用 AF_IB 时才可访问
...
dst->qkey = (id->route.addr.src_addr.ss_family == AF_IB) ? src->qkey : 0;
rdma_connect_ece -> RDMA/ucma:扩展ucma_connect以接收ECE参数,CMID的主动方通过librdmacm的rdma_connect()和内核的ucma_connect()发起连接。 扩展 UCMA 接口来处理这些新参数
rdma_connect(id, conn_param) -> rdma_connect_locked
cma_comp_exch(id_priv, RDMA_CM_ROUTE_RESOLVED, RDMA_CM_CONNECT)
rdma_cap_ib_cm(id->device, id->port_num) -> rdma_cap_ib_cm - 检查设备端口是否具有 Infiniband Communication Manager 功能。 @device:要检查的设备 @port_num:要检查的端口号 InfiniBand 通信管理器是通过通用服务接口 (GSI) 访问的许多预定义通用服务代理 (GSA) 之一。 它的作用是促进节点之间连接的建立以及已建立的连接的其他管理相关任务。 返回:如果端口支持 IB CM,则返回 true(但这并不能保证 CM 实际正在运行)
RDMA_CORE_CAP_IB_CM
if (id->qp_type == IB_QPT_UD) -> cma_resolve_ib_udp -> RDMA/cma:添加对 RDMA_PS_UDP 的支持,允许通过 rdma_cm 使用 UD QP,以便为使用 SIDR 解析数据报消息的 IB 地址提供地址转换服务
or cma_connect_ib
check_add_overflow(offset, conn_param->private_data_len, &req.private_data_len) -> 为了简单性和代码卫生,下面的后备代码坚持 a、b 和 *d 具有相同的类型(类似于 min() 和 max() 宏),而 gcc 的类型通用溢出检查器接受不同的类型。 因此,我们不只是将 check_add_overflow 设置为 __builtin_add_overflow 的别名,而是添加类似于下面的类型检查
ib_create_cm_id(id_priv->id.device, cma_ib_handler, id_priv)
cm_alloc_id_priv -> RDMA/cm:简化建立监听cm_id
cm_id_priv->id.cm_handler = cm_handler
RB_CLEAR_NODE(&cm_id_priv->service_node) -> rb_tree
init_completion(&cm_id_priv->comp)
xa_alloc_cyclic -> 在 XArray 中找到存储此条目的位置 -> 在 xa 中查找 limit.min 和 limit.max 之间的空条目,将索引存储到 id 指针中,然后将条目存储在该索引处。 并发查找不会看到未初始化的 id。 对空条目的搜索将从下一个开始,并在必要时回绕, https://docs.kernel.org/core-api/xarray.html#c.xa_alloc_cyclic
cm_id_priv->id.local_id = (__force __be32)id ^ cm.random_id_operand
cm_finalize_id(cm_id_priv)
xa_store(&cm.local_id_table,
trace_cm_send_req
ib_send_cm_req(id_priv->cm_id.ib, &req)
ib_mad_send_buf -> ib_mad_send_buf - MAD 数据缓冲区和发送的工作请求。@next:用于将 MAD 链接在一起以进行发布的指针。 @mad:为没有激活 RMPP 的 MAD 引用分配的 MAD 数据缓冲区。 对于使用 RMPP 的 MAD,引用公共和管理类特定标头。 @mad_agent:分配缓冲区的 MAD 代理。 @ah:发送 MAD 时使用的地址句柄。 @context:用户控制的上下文字段。 @hdr_len:表示MAD的数据头的大小。 此长度包括常见的 MAD、RMPP 和特定于类的标头。 @data_len:表示用户传输的数据的总大小。 @seg_count:为此发送分配的 RMPP 段数。 @seg_size:每个 RMPP 段中数据的大小。 这不包括特定于类的标头。 @seg_rmpp_size:每个 RMPP 段的大小,包括类特定标头。 @timeout_ms:等待响应的时间。 @retries:重试响应请求的次数。 对于使用 RMPP 的 MAD,这适用于每个窗口。 完成后,返回完成传输所需的重试次数。 用户负责初始化 MAD 缓冲区本身,任何 RMPP 标头除外。 超出 data_len 分配的额外段缓冲区空间是填充
cm_validate_req_param(param)
cm_id_priv->timewait_info = cm_create_timewait_info(cm_id_priv->
INIT_DELAYED_WORK(&timewait_info->work.work, cm_work_handler)
cm_init_av_by_path(param->primary_path, param->ppath_sgid_attr, &av) -> IB/cm:将 sa_path_rec 的成员替换为“struct sgid_attr *”,在处理 CM 消息中的路径记录条目时,现在还提供关联的 GID 属性。 目前,对于 RoCE,网络设备的网络命名空间指针和 ifindex 存储在路径记录条目中。 在处理 CM 消息时,netdev 的这两个字段都可以随时更改。 另外,存储网络命名空间而不保留引用将导致释放后使用崩溃。 因此将其删除。 RoCE 的网络设备信息是通过 ib_cm 请求中引用的 gid 属性提供的。 这样的设计会导致当网络指针无效时内核可能崩溃的情况。 然而今天它总是被初始化为init_net,它不会变得无效。 为了支持处理接收到的数据包的任意命名空间中的数据包,有必要避免这种情况。 该补丁消除了对网络指针和 ifindex 的依赖; 相反,它将依赖于包含指向 netdev 的指针的 SGID 属性
port = get_cm_port_from_path(path, sgid_attr)
rdma_find_gid
index = find_gid(table, gid, &gid_attr_val, false, mask, NULL)
ib_find_cached_pkey(cm_dev->ib_device, port->port_num,
cache = device->port_data[port_num].cache.pkey
if ((cache->table[i] & 0x7fff) == (pkey & 0x7fff))
if (cache->table[i] & 0x8000)
cm_set_av_port(av, port)
ib_init_ah_attr_from_path(cm_dev->ib_device, port->port_num, path, &new_ah_attr, sgid_attr) -> av->ah_attr 可能会根据 wc 或请求处理时间进行初始化,这可能会引用 sgid_attr。 因此在堆栈上初始化一个新的ah_attr。 如果初始化失败,则使用旧的 ah_attr 来发送任何响应。 如果初始化成功,则使用新的 ah_attr 覆盖旧的。 这样就可以使用 ah_attr 来返回错误响应 -> ib_init_ah_attr_from_path - 根据 SA 路径记录初始化地址句柄属性。 @device:设备关联啊属性初始化。 @port_num:指定设备上的端口。 @rec:用于 ah 属性初始化的路径记录条目。 @ah_attr:从路径记录初始化地址句柄属性。 @gid_attr:初始化期间要考虑的SGID属性。 当 ib_init_ah_attr_from_path() 返回成功时,(a) 对于 IB 链路层,当 IB 链路层存在 GRH 时,它可选地包含对 SGID 属性的引用。 (b) 对于 RoCE 链路层,它包含对 SGID 属性的引用。 用户必须调用 rdma_destroy_ah_attr() 来释放对使用 ib_init_ah_attr_from_path() 初始化的 SGID 属性的引用
rdma_ah_find_type
rdma_ah_set_sl
rdma_ah_set_port_num(ah_attr, port_num)
rdma_ah_set_static_rate(ah_attr, rec->rate)
roce_resolve_route_from_path(rec, gid_attr)
might_sleep -> RDMA/addr:将 addr_resolve 标记为 might_sleep(),在通过 ib_nl_fetch_ha() 的一条路径下,这会调用 nlmsg_new(GFP_KERNEL),这是一个睡眠调用。 这是一条非常罕见的路径,因此将 fetch_ha() 和有条件调用 fetch_ha() 的模块外部入口点标记为 might_sleep()
rdma_gid2ip((struct sockaddr *)&sgid, &rec->sgid)
if (ipv6_addr_v4mapped((struct in6_addr *)gid))
memcpy(&out_in->sin_addr.s_addr, gid->raw + 12, 4)
else
memcpy(&out_in->sin6_addr.s6_addr, gid->raw, 16)
rdma_gid2ip((struct sockaddr *)&dgid, &rec->dgid)
addr_resolve((struct sockaddr *)&sgid, (struct sockaddr *)&dgid, &dev_addr, false, true, 0)
if (resolve_by_gid_attr) -> RDMA/core:RoCE考虑gid属性的net ns,解析目的地址或路由时,当netnamespace不可用时,参考SGID属性的netdevice的netnamespace。 这通常是从网络到达 RoCE 端口的请求的情况
set_addr_netns_by_gid_rcu(addr)
ret = addr4_resolve(src_in, dst_in, addr, &rt) -> https://hustcat.github.io/queue-pair-in-rdma/
ip_route_output_key(addr->net, &fl4) -> ip_route_output_flow(net, flp, NULL) -> 查找路由表:为套接字缓冲区设置路由出口信息。如果已有TCP连接,则套接字缓冲区保存了路由信息
addr->hoplimit = ip4_dst_hoplimit(&rt->dst) -> 访问到dst metric的RTAX_HOPLIMIT字段,此字段并未做初始化,其值为零,ip4_dst_hoplimit会采用系统默认的hoplimit
or ret = addr6_resolve(src_in, dst_in, addr, &dst)
ipv6_dst_lookup_flow
ip6_dst_hoplimit
rdma_set_src_addr_rcu
rdma_ah_set_grh(attr, dgid, flow_label, sgid_attr->index, hop_limit, traffic_class)
grh->flow_label = flow_label
grh->sgid_index = sgid_index
grh->hop_limit = hop_limit
grh->traffic_class = traffic_class
addr_resolve_neigh -> arp
memcpy(addr->dst_dev_addr, addr->src_dev_addr, MAX_ADDR_LEN)
or ret = fetch_ha(dst, addr, dst_in, seq)
rdma_addr_set_net_defaults
rec->roce.route_resolved = true
else rdma_ah_set_dlid(ah_attr, be32_to_cpu(sa_path_get_dlid(rec))) -> attr->ib.dlid = (u16)dlid
init_ah_attr_grh_fields(device, port_num,
sa_conv_pathrec_to_gid_type(rec) -> IB_GID_TYPE_ROCE
gid_attr = rdma_find_gid_by_port(device, &rec->sgid, type,
local_index = find_gid(table, gid, &val, false, mask, NULL)
or rdma_hold_gid_attr
rdma_move_grh_sgid_attr
rdma_move_ah_attr(&av->ah_attr, &new_ah_attr)
cm_move_av_from_path(&cm_id_priv->av, &av)
msg = cm_alloc_priv_msg(cm_id_priv) -> cm_alloc_msg
mad_agent = cm_id_priv->av.port->mad_agent
ah = rdma_create_ah(mad_agent->qp->pd, &cm_id_priv->av.ah_attr, 0)
rdma_fill_sgid_attr
rdma_lag_get_ah_roce_slave
_rdma_create_ah -> IB/核心:引入和使用 rdma_create_user_ah,引入 rdma_create_user_ah API,该 API 允许将 udata 传递给提供程序驱动程序,并另外解析 RoCE 的 DMAC。 ib_resolve_eth_dmac() 解析单播、多播、链接本地 ipv4 映射的 ipv6 和 ipv6 目标 gid 条目的目标 mac 地址。 这允许所有 RoCE 提供程序驱动程序避免重复此类代码。 这种更改带来了一致性,其中 IB 核心始终解析 dmac 并将其传递给用户和内核使用者的 RoCE 提供程序驱动程序,并且 ah_attr->roce.dmac 始终是提供程序驱动程序的输入字段。 这种一致性避免了将 ib_resolve_eth_dmac 符号导出到提供程序或其他模块。 因此,它会在补丁系列的后面部分作为导出符号被删除。 现在 uverbs 和 umad 都使用 rdma_create_user_ah API,它修复了 umad 地址的 DMAC 无效的问题
rdma_zalloc_drv_obj_gfp -> kzalloc_node(size, gfp, dev->ops.get_numa_node(dev)
device->ops.create_user_ah(ah, &init_attr, udata) -> irdma_create_user_ah
irdma_setup_ah
irdma_create_hw_ah -> IRDMA_OP_AH_CREATE
hash_add(iwdev->ah_hash_tbl, &parent_ah->list, key)
or device->ops.create_ah(ah, &init_attr, NULL)
rdma_lag_put_ah_roce_slave
rdma_unfill_sgid_attr
m = ib_create_send_mad(mad_agent, cm_id_priv->id.remote_cm_qpn, cm_id_priv->av.pkey_index, 0, IB_MGMT_MAD_HDR, IB_MGMT_MAD_DATA, GFP_ATOMIC, IB_MGMT_BASE_VERSION)
opa = rdma_cap_opa_mad(mad_agent->device, mad_agent->port_num) -> IB/mad:添加部分英特尔 OPA MAD 支持,此补丁是 3 个补丁中的第一个,添加了 OPA MAD 处理 1) 添加英特尔 Omni-Path 架构定义 2) 增加最大管理版本以适应 OPA 3) 更新 ib_create_send_mad 如果设备支持 OPA MAD 和发送的 MAD 是 OPA 基本版本,根据需要更改 MAD 大小和 sg 长度
if (ib_mad_kernel_rmpp_agent(mad_agent))
INIT_LIST_HEAD(&mad_send_wr->rmpp_list)
mad_send_wr->mad_list.cqe.done = ib_mad_send_done;
mad_send_wr->send_wr.wr.num_sge = 2
mad_send_wr->send_wr.wr.opcode = IB_WR_SEND
ret = alloc_send_rmpp_list(mad_send_wr, mad_size, gfp_mask)
m->context[0] = cm_id_priv
msg->context[1] = (void *)(unsigned long)IB_CM_REQ_SENT
ib_post_send_mad -> [IB] 修复 MAD 层 DMA 映射,以避免在映射后触及数据缓冲区。MAD 层在 DMA 映射完成后触及用于发送的数据缓冲区,从而违反了 DMA API。 这会导致非缓存一致性架构出现问题,因为执行 DMA 的设备不会看到仅存在于 CPU 缓存中的有效负载缓冲区的更新。 通过让所有 MAD 使用者使用 ib_create_send_mad() 分配其发送缓冲区,并将 DMA 映射移动到 MAD 层,以便可以在调用 send 之前(以及 MAD 层对发送缓冲区进行任何修改之后)完成此操作,可以解决此问题。 在非缓存一致性 PowerPC 440SPe 系统上进行测试
ib_mad_enforce_security -> IB/核心:在管理数据报上强制执行安全性,在创建和销毁 MAD 代理时分配和释放安全上下文。 该上下文用于控制对 PKey 的访问以及发送和接收 SMP。 发送或接收 MAD 时,检查代理是否有权访问端口子网前缀的 PKey。 在 SMI QP 的 MAD 和监听代理注册期间,检查调用进程是否有权访问管理子网并向 LSM 注册回调以获取策略更改通知。 当发生策略更改通知时,重新检查权限并设置一个标志,指示允许发送和接收 SMP。 发送和接收 MAD 时,如果代理位于 SMI QP 上,请检查代理是否有权访问 SMI。 由于安全策略可以更改,因此在创建代理时可能允许许可,但不再允许
rdma_protocol_ib
ib_security_pkey_access
ib_is_mad_class_rmpp
handle_outgoing_dr_smp
ib_mad_kernel_rmpp_agent
ib_send_rmpp_mad
ib_send_mad
ib_dma_map_single
ib_uses_virt_dma
dma_map_single
ib_post_send
.post_send = mlx5_ib_post_send_nodrain,
or cma_connect_iw
ucma_put_ctx
static ssize_t ucma_accept
else ret = rdma_accept_ece(ctx->cm_id, NULL, &ece)
rdma_accept -> rdma_accept - 调用以接受连接请求或响应。@id:与请求关联的连接标识符。 @conn_param:建立连接所需的信息。 如果接受连接请求,则必须提供此信息。 如果接受连接响应,则该参数必须为 NULL。 通常,此例程仅由侦听器调用以接受连接请求。 如果用户正在执行自己的 QP 转换,则还必须在连接的活动端调用它。 如果出现错误,则会向远程端发送拒绝消息,并将与 id 关联的 qp 的状态修改为错误,以便刷新任何先前发布的接收缓冲区。 该函数供内核 ULP 使用,并且必须从处理程序回调下调用
lockdep_assert_held(&id_priv->handler_mutex)
ret = cma_rep_recv(id_priv)
cma_modify_qp_rtr -> RDMA/cma:使用用户值覆盖默认的 responder_resources,默认情况下,responder_resources 参数设置为连接请求中收到的参数。 被动方在接受连接时可以覆盖该值。 将 QP 转换为 RTR 状态时使用被动方提供的值,而不是连接请求中给出的值。 如果没有此更改,如果被动方支持的responder_resources 少于请求中的资源,RTR 转换可能会失败。 为了代码一致性并防止 QP 破坏,请重构覆盖 initiator_depth 以匹配 responder_resources 的设置方式
qp_attr.qp_state = IB_QPS_INIT
rdma_init_qp_attr
ib_modify_qp
qp_attr.qp_state = IB_QPS_RTR
ib_modify_qp
cma_modify_qp_rts
trace_cm_send_rtu
ib_send_cm_rtu -> IB:基于IP地址的RDMA连接管理器,基于InfiniBand的基于IP地址连接的内核连接管理代理。 该代理定义了通用 RDMA 连接抽象,以支持想要通过不同 RDMA 设备进行连接的客户端。 该代理还代表客户端处理 RDMA 设备热插拔事件
data = cm_copy_private_data(private_data, private_data_len)
msg = cm_alloc_msg(cm_id_priv)
cm_format_rtu
ib_post_send_mad(msg, NULL)
cm_id->state = IB_CM_ESTABLISHED
cm_set_private_data(cm_id_priv, data, private_data_len)
drivers/infiniband/core/ucma.c
static ssize_t (*ucma_cmd_table[])(struct ucma_file *file,
const char __user *inbuf,
int in_len, int out_len) = {
[RDMA_USER_CM_CMD_CREATE_ID] = ucma_create_id,
[RDMA_USER_CM_CMD_DESTROY_ID] = ucma_destroy_id,
[RDMA_USER_CM_CMD_BIND_IP] = ucma_bind_ip,
[RDMA_USER_CM_CMD_RESOLVE_IP] = ucma_resolve_ip,
[RDMA_USER_CM_CMD_RESOLVE_ROUTE] = ucma_resolve_route,
[RDMA_USER_CM_CMD_QUERY_ROUTE] = ucma_query_route,
[RDMA_USER_CM_CMD_CONNECT] = ucma_connect,
[RDMA_USER_CM_CMD_LISTEN] = ucma_listen,
[RDMA_USER_CM_CMD_ACCEPT] = ucma_accept,
[RDMA_USER_CM_CMD_REJECT] = ucma_reject,
[RDMA_USER_CM_CMD_DISCONNECT] = ucma_disconnect,
[RDMA_USER_CM_CMD_INIT_QP_ATTR] = ucma_init_qp_attr,
[RDMA_USER_CM_CMD_GET_EVENT] = ucma_get_event,
[RDMA_USER_CM_CMD_GET_OPTION] = NULL,
[RDMA_USER_CM_CMD_SET_OPTION] = ucma_set_option,
[RDMA_USER_CM_CMD_NOTIFY] = ucma_notify,
[RDMA_USER_CM_CMD_JOIN_IP_MCAST] = ucma_join_ip_multicast,
[RDMA_USER_CM_CMD_LEAVE_MCAST] = ucma_leave_multicast,
[RDMA_USER_CM_CMD_MIGRATE_ID] = ucma_migrate_id,
[RDMA_USER_CM_CMD_QUERY] = ucma_query,
[RDMA_USER_CM_CMD_BIND] = ucma_bind,
[RDMA_USER_CM_CMD_RESOLVE_ADDR] = ucma_resolve_addr,
[RDMA_USER_CM_CMD_JOIN_MCAST] = ucma_join_multicast
};
RDMA CM用户手册: https://man7.org/linux/man-pages/man7/rdma_cm.7.html
RDMA CM用户态仓库(笔记): https://github.com/ssbandjl/rdma-core/blob/master/readme
博客: https://cloud.tencent.com/developer/user/5060293/articles | https://logread.cn | https://blog.csdn.net/ssbandjl | https://www.zhihu.com/people/ssbandjl/posts
https://cloud.tencent.com/developer/column/101987
技术会友: 欢迎对DPU/智能网卡/卸载/网络,存储加速/安全隔离等技术感兴趣的朋友加入DPU技术交流群
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。