searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

用户态rdma(urdma)分析

2023-10-20 07:56:51
119
0

一、基本架构:

urdma是使用DPDK实现的全软件用户态rdma功能。基本架构如上图所示:

1、urdmad:使用DPDK实现的primary进程,是RDMA网卡资源的管理进程。主要功能是:

  • 初始化DPDK,创建kni网卡
  • RDMA QP资源的管理
  • QP的建链
  • 下发匹配规则,将对应的报文送给对应的队列。
  • 从kni网口处理非RDMA报文交给内核协议栈。

2、urdma_prov,实现用户态verbs接口,同时APP作为DPDK的secondary进程运行。主要功能是:

  • 标准IB vebrs的实现
  • 实现RDMA报文的收发包处理
  • 和urdmad交互,完成RDMA的建链

3、urdma_kmod,运行在内核态用于支持RDMA CM建链操作。目前实现只有控制面,不支持数据面操作。这部分可以参考内核代码softiwarp: /infiniband/sw/siw。

二、urdma协议

urdma是基于UDP实现的RDMA协议。是一个非标准的iWARP或者Roce,和标准的iWARP协议相比较,如上图所示:

1、urdma实现iWARP DDP和RDMAP protocol

2、urdma实现TRP,用于可靠性传输字段

3、urdma选择UDP,避免粘包,还有TCP的一些其他特性。

对应的协议头部如下:

2.1协议头 rdmap

rdmap_head:
struct rdmap_packet {
	uint8_t ddp_flags; /* 0=Tagged 1=Last 7-6=DDP_Version */
	uint8_t rdmap_info; /* 1-0=RDMAP_Version 7-4=Opcode */
	uint32_t sink_stag;
} __attribute__((__packed__));

ddp_flags:
用来标识Buffer模型。值为1时表示Tagged Buffer Model,值为0时表示Untagged Buffer Model。接收端依据此flag来决定如何解析报文。

rdmap_info:
rdma的opcode字段send/read/write/atomic

sink_stag:
从代码上看是一个扩展字段:目前用于rdma read,write,atomic作为rkey。其余情况为0 

2.2 协议头:ddp

ddp tag hdr: 用于rdma write和rdma read resp
struct rdmap_tagged_packet {
	struct rdmap_packet head;
	uint64_t offset;
} __attribute__((__packed__));

offset 表示Payload在Tagged Buffer中从Buffer的起始地址到最终数据的存放地址的偏移

ddp untag hdr:用于rdma send、read req、atimoc
struct rdmap_untagged_packet {
	struct rdmap_packet head;
	uint32_t qn; /* Queue Number */
	uint32_t msn; /* Message Sequence Number */
	uint32_t mo; /* Message Offset */
} __attribute__((__packed__));

qn: 数据接收端的untagged队列的序号,个人感觉可以理解为opcode。 ddp_queue_send = 0, ddp_queue_read_request = 1, ddp_queue_terminate = 2(错误处理), ddp_queue_atomic_response = 3, ddp_queue_ack = 4,

msn: 消息序列号,必须从1开始,每次递增1。当到达0xFFFFFFFF之后,重新变为0。每个消息都只能对应一个Untagged packet,所以msn+qn能够唯一确定一个Untagged packet 。

mo:表示从QN和MSN确认的Untagged Buffer的起始位置到消息目的地址的偏移

2.3 协议头:trp

trp hdr
struct trp_hdr {
	uint32_t psn;  
	uint32_t ack_psn;
	uint16_t opcode;
} __attribute__((__packed__));

psn:(1)作为累计ack的时候是最小psn;  (2)作为普通ack是当前报文的psn

ack_psn:(1)作为累计ack的时候是最大psn(psn + 1); (2)为普通ack是已经收到的ack psn

opcode:trp的opcode当前只支持0,累计ack,   QP shutdown。
  ->累计ack:当接收端预期的recv_ack_psn小于接收的psn,认为丢失了ack时使用
  ->QP shutdown:在本端QP down的时候通知对端QP shutdown时使用

三、报文处理流程

1、urdmad为每个RDMA队列对分配一个硬件接收/发送以太网队列,便于APP直接通过IB verbs访问NIC,下发报文。

2、NIC硬件过滤器用于将数据包分离到RX队列中

  • 目前从代码实现来看只支持Flow direct过滤
  • urdmad将未过滤的包送给内核协议栈处理
  • 命中过滤规则的包直接送给对应的QP接收队列,APP通过IB vebrs处理

四、设备初始化

第一步:insmod urdma.ko, 结果如下图所示

1、创建字符设备/dev/urdma,用于RDMA CM建链使用

2、注册urdma bus

3、完成/sys/device/urdma的创建

4、注册netdev的notify,关注网卡的注册,UP、DOWN事件

5、创建cm thread

第二步:以primary方式运行urdmad ,完成RDMA驱动加载, 结果如下图所示。

1、rte_eal_init()初始化DPDK运行环境

2、初始化core资源,用于后续APP使用RDMA功能时一个进程占用一个core。

3、do_init_driver()作为最主要的初始化函数,主要做以下事情:

  • 读取/etc/rdma/urdma.json配置,配置文件会设置预设的队列深度、预设的QP个数等信息。(需要看实际网卡的能力)
  • 创建控制通路:socket用于处理APP进程的QP申请、销毁消息。以及将主进程的数据同步到APP进程
  • open /dev/urdma设备,用于RDMA CM建链,同时在建链的时候,设置过滤规则到硬件。
  • kni网卡的初始化加载,这个动作会创建kni网卡(ifconfig可以看到),同时内核会响应通知notify。urdma.ko响应完成rdma设备的创建和注册。

五、APP--init ibv context

主要说明下ibv_get_device_list()接口在urdma中的实现。这个接口在ibvebs中,最终会调用到ops->alloc_device:urdma_device_alloc()接口。主要做以下事情:

  • 确认存在urdma_0设备
  • 创建线程(our_eal_master_thread()),和urdmad进程通信,确保两边的部分数据一致(max_qp等)。
  • 在our_eal_master_thread线程上下文遍历本进程创建所有QP的收发包处理
  • 初始化Ibv设备

六、MR

6.1 reg mr

由于DPDK的特性,APP作为second进程,申请的内存是DPDK已经pin主的内存,在注册MR的时候,不需要pin内存,更不需要陷入内核。所以MR的注册很简单。

usiw_reg_mr()

    ->usiw_hash_mr()根据address和len得到rkey

    ->urdma_reg_mr_with_rkey() 填充并返回ibv_mr,使用hash表(头插法)管理usiw_mr

6.2 lookup mr

usiw_mr_lookup() 传入usiw_mr_table 和 rkey

    -> 根据rkey % capacity 找到table->entry

    -> for() 遍历usiw_mr->next

    -> rkey相同,则找到mr,否则返回NULL

七、QP

7.1 创建QP

usiw_create_qp()完成QP的创建,QP和SQ、RQ的结构体如上。过程如下:

1、port_get_next_qp() 与urdmad进程通信,urdmad将QP地址给到APP进程。QP的内存这块理解是DPDK做了特殊处理,主从进程拿到的都是物理地址(地址打印分析)才能两边同时操作一个QP。

2、ibv_cmd_create_qp() 陷入内核,内核主要是接收并存储传递下来的QP数据,用于RDMA CM建链,内核返回qpn。

3、send_cq、recv_cq字段的初始化

4、创建SQ和RQ,结构体如右所示。使用两个ring环完成post send和post recv。 初始状态ring为空,storage都存入free_ring

5、QP创建成功,将qp加入到ctx->qp_active链表上,用于our_eal_master_thread线程收发包遍历链表

6、置QP状态处于usiw_qp_unbound状态(未和远端建链)。

7.2 建链

建链分为两部分:

(1)APP进程

APP通过RDMA CM接口陷入内核。rdma_connect/accept

rdma_accept陷入内核

  -> siw_accept()完成QP的建链

        ->siw_modify_qp()修改qp状态

   -> notify_established()将QP加入到链表上。唤醒/dev/urdma的poll,通知urdma进程当前有事件到来,要处理。

(2)urdmad进程

do_poll()做epoll_wait,等待fd事件

   ->监听到fd事件执行fd->data_ready

        ->chardev_data_ready() read fd,陷入内核

             -> urdma_chardev_read() 从链表上获取建链数据,返回用户态。数据格式如上图struct urdma_qp_connected_event{}所示

             -> handle_qp_connected_event()处理qp建链事件

     ->do_setup_qp()本端QP记录对端的QP信息,下发过滤规则到硬件网卡,最后置QP为usiw_qp_connected(连接状态)

7.3 post send

usiw_post_send()当QP状态是connect和running才能post send,send wqe的结构体如上

   ->for() 遍历wr

       ->qp_get_next_send_wqe() 加锁从free_ring中取wqe,如果没有直接返回

       ->根据wr完成填充wqe

       ->rte_ring_enqueue(qp->sq.ring, wqe)将wqe放入ring中。

7.4 post recv

usiw_post_recv()当QP状态是error时直接返回,recv wqe的结构体如上

   ->for() 遍历wr

       ->qp_get_next_recv_wqe () 加锁从free_ring中取wqe,如果没有直接返回

       ->根据wr完成填充wqe

       ->rte_ring_enqueue(qp->rq0.ring, wqe)将wqe放入ring中。

八、CQ

8.1 创建CQ

usiw_create_cq完成CQ的创建,结构体如上。过程如下:

1、计算CQ的大小,申请CQ

2、ibv_cmd_create_cq(),陷入内核,内核管理CQ,返回CQN

3、初始化cq->cqe_ring和cq->free_ring

4、将storage填充到free_ring中

5、其他字段初始化,返回ibv_cq

8.2 poll cq

usiw_poll_cq() 完成cq的poll。cqe_ring的填充在收发包的过程中完成

 ->do_poll_cq() 入参:cq、num、出参:cqe[]

      ->RING_DEQUEUE_BURST() 从cqe_ring中一次批量获取num个cqe

      ->for() 根据取出来的cqe个数遍历cqe,memcpy到出参中

      ->RING_ENQUEUE_BURST() 将出来的cqe一次批量放到free_ring中

 ->convert_cqes() 将cqe 转换为ibv_wc

九、TX/RX流程分析

APP处理报文都在our_eal_master_thread() 上下文处理,过程如下:
kni_loop()函数循环处理报文
  -> list_for_each_safe(&ctx->qp_active, qp, qp_next, ctx_entry)  遍历要处理的QP
       -> if (qp->conn_state == connected) 则执行start_qp()
            ->start_qp() : qp->remote_ep字段的初始化,初始化tx发送队列,置QP为running状态, 继续执行process_qp()
       -> if (qp->conn_state == running) 则执行progress_qp(),参数qp
            ->process_receive_queue() 处理收包,
	          ->rte_eth_rx_burst() 函数将报文从硬件队列中获取到本地
            ->for() 循环遍历获取的报文, 过程中有prefetch,预取内存的数据包
                  ->process_data_packet() 处理RX报文
                     ->首先解析报文头部,如果非建链QP的报文,直接丢弃报文
                     ->解析trp头部:
                          -> if (trp_opcode == trp_sack) 收到片段psn的确认ack, process_trp_sack()根据数据包中的min_psn和max_psn, while() 遍历pending wqe,置ACK标记
                          -> if (trp_opcode == trp_fin) 执行qp_shutdown(), 置QP error状态,消耗SQ、RQ的所有wqe,上送error cqe:IBV_WC_WR_FLUSH_ERR
                     -> 解析rdma->opcode
                              -> send:消耗rqe,上送cqe, IBV_WC_SUCCESS
                              -> read request:根据rkey找mr,判断是否在范围内,请求的addr和length,组装一个readresp的请求头,待后面respond_next_read_atomic()处理
                              -> write:代码中没有处理
                              -> atomic_request:根据rkey找mr,判断是否在范围内,组装一个atomic resp请求头,待后面respond_next_read_atomic()处理
                              -> read resp:每收到一个报文,根据包中的rkey找到mr,根据包中的地址和数据长度,将收到的报文memcoy到地址中,等到收到最后一个read resp报文后,从pending的wqe中找到rdma req wqe,再消耗cqe,将pending的wqe 放入到free_ring中
                              -> atomic resp:从pending的wqe中找到atomic req wqe,再消耗cqe,将pending的wqe 放入到free_ring中
                     ->sweep_unacked_packets() 处理还没有被ack的报文
                          ->for() 遍历被pending的wqe,if (pending-psn < send_last_ack_psn),执行do_process_ack()
                              ->do_process_ack() 处理ack的wqe,此处只处理SEND和WRITE,上送cqe,将wqe放入free_ring中
                          ->for() 遍历被pending的wqe,重传未ACK的报文,如果重传失败,则上送error cqe: IBV_WC_FATAL_ERR/ IBV_WC_RETRY_EXC_ERR,置QP 为ERROR状态
                     ->rte_ring_dequeue(qp->sq.ring, (void **)&send_wqe) 一次处理一个wqe,调用progress_send_wqe()将报文发送去,同时将报文pending
                     ->respond_next_read_atomic() 处理rdma read resp 和rdma atomic resp
                     ->send_ack() 回复ack报文
                     ->flush_tx_queue() 调用驱动接口,将在发送队列中的素有数据包发送出去
        -> if (qp->conn_state == shutdown) 则执行qp_shutdown()发送trp_fin报文,然后将qp置为ERR,flush sq、rq. 上送cqe err(IBV_WC_WR_FLUSH_ERR)
        -> if (qp->conn_state == error) 从ctx->qp_active上将qp删除,如果cq的引用计数为1后, 释放cq,然后发送消息destroy qp的消息到urdmad进程,本端释放qp.

十、urdma分析总结

  • 基于UDP实现的rdma功能。
  • 支持RC和UD
  • 支持RDMA SEND/RECV、READ、atomic
  • 支持可靠传输
  • 需要占用大量的CPU资源
  • 收发包上下文对于内存使用预取功能、使用无锁环,有一定的效率提升
  • 支持累计ACK。
  • 不同于rxe和siw,urdma是在用户态实现,数据路径不需要陷入内核
  • QP资源有限制,取决于硬件网卡的收发包队列个数:min(tx_queue_num,rx_queue_num)
  • 网卡需要支持Flow direct,否则需要修改代码使用软件进行过滤报文
  • 一个进程只有只有一个主线程在进行收发包,遍历当前进程创建的QP,然后进行收发包。
  • 收发包过程需要进行一次数据COPY,此操作会增加时延和降低带宽,是个优化点。
  • 如果urdma进程出现问题,则无法继续使用rdma功能
  • urdma是私有协议,只能两端都是urdma进行测试,否则无法使用

十一、使用方法

前提条件:

1、dpdk版本 17.05以上,20版本以下。因为urdma使用的dpdk接口,在最新版本都发生的了很大变化。尽可能选择17.05、17.11版本的DPDK,这样改的少。

2、内核版本选择4.xx的版本,urdma需要编译urdma.ko。最新内核的IB头文件发生了变化,需要进行大量才能编译通过。

3、选择的网卡尽可能支持Flow direct功能,使用硬件过滤,否则需要修改代码,在kni处理报文的代码处(do_xchg_packets())过滤RDMA报文交给对应的APP处理。

4、rdma core的版本使用17-18,建议选择17.11。

5、DPDK的编译(17.05为例,编译过程可能需要解决编译问题)

(1)vi config/common_base
#修改
CONFIG_RTE_BUILD_SHARED_LIB=y
CONFIG_RTE_EAL_IGB_UIO=y
CONFIG_RTE_KNI_KMOD=y

(2)编译
make config T=x86_64-native-linuxapp-gcc
export EXTRA_CFLAGS="-w -Wno-address-of-packed-member“
make

6、编译urdma,过程可能解决编译问题

  export RTE_SDK=/root/dpdk-17.05
  export RTE_TARGET=build
  export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$RTE_SDK/$RTE_TARGET/lib:/root/urdma-master/src/liburdma/.libs
  autoreconf –I
  ./configure --sysconfdir /etc
  make
  make install

7、两端都必须是urdma,才能测试

运行和测试

1、运行urdmad,初始化RDMA设备,过程如下:

modprobe uio
modprobe ib_core
modprobe ib_uverbs
modprobe ib_cm
modprobe ib_umad
modprobe iw_cm
modprobe rdma_cm
modprobe rdma_ucm

rmmod urdma.ko

insmod /home/matrix/workspace/rdma/dpdk/dpdk-src/build/kmod/igb_uio.ko 
insmod /home/matrix/workspace/rdma/dpdk/dpdk-src/build/kmod/rte_kni.ko 
insmod /home/matrix/workspace/rdma/urdma/urdma-src/src/kmod/urdma.ko 

#将liburdma-rdmav17.so拷贝到libibverbs下
cp /usr/local/lib/liburdma-rdmav17.so /usr/lib/x86_64-linux-gnu/libibverbs/liburdma-rdmav17.so
#dpdk绑定网卡
/home/matrix/workspace/rdma/dpdk/dpdk-src/usertools/dpdk-devbind.py -b igb_uio 03:00.0
#设置大页
mount -t hugetlbfs nodev /mnt/huge
sh -c 'echo 128 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages'

#设置库
export RTE_SDK=/home/matrix/workspace/rdma/dpdk/dpdk-src
export RTE_TARGET=build
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$RTE_SDK/$RTE_TARGET/lib:/home/matrix/workspace/rdma/urdma/urdma-src/src/liburdma/.libs

#运行urdma。
/home/matrix/workspace/rdma/urdma/urdma-src/src/urdmad/urdmad  -l 0-1 -d librte_pmd_vmxnet3_uio.so -d librte_mempool_ring.so --proc-type=primary --file-prefix=ubuntu
参数
        -l:表示运行的核;
        -d: 表示要加载的动态库(mempool是dpdk的特性需要加载,vmxnet3是选择的网卡(根据网卡选择)); 
        --proc-type: 运行的优先级;
        --file-prefix: 表示hostname

2、看到kni网卡,ibv_device能看到IB设备,此时可以进行RDMA测试了

root@ubuntu:/sys/devices/urdma# ifconfig
ens32: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.92.160  netmask 255.255.255.0  broadcast 192.168.92.255
        inet6 fe80::250:56ff:fe25:e76c  prefixlen 64  scopeid 0x20<link>
        ether 00:50:56:25:e7:6c  txqueuelen 1000  (Ethernet)
        RX packets 62854  bytes 4448101 (4.4 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 167011  bytes 131558018 (131.5 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

kni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.186.130  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::20c:29ff:fe92:d2e6  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:92:d2:e6  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 12  bytes 1338 (1.3 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

root@ubuntu:/sys/devices/urdma# ibv_devices 
device                 node GUID
    ------              ----------------
    rxe0                025056fffe25e76c
    urdma_0             000c2992d2e60000
root@ubuntu:/sys/devices/urdma# 

 

0条评论
0 / 1000
z****n
8文章数
1粉丝数
z****n
8 文章 | 1 粉丝
原创

用户态rdma(urdma)分析

2023-10-20 07:56:51
119
0

一、基本架构:

urdma是使用DPDK实现的全软件用户态rdma功能。基本架构如上图所示:

1、urdmad:使用DPDK实现的primary进程,是RDMA网卡资源的管理进程。主要功能是:

  • 初始化DPDK,创建kni网卡
  • RDMA QP资源的管理
  • QP的建链
  • 下发匹配规则,将对应的报文送给对应的队列。
  • 从kni网口处理非RDMA报文交给内核协议栈。

2、urdma_prov,实现用户态verbs接口,同时APP作为DPDK的secondary进程运行。主要功能是:

  • 标准IB vebrs的实现
  • 实现RDMA报文的收发包处理
  • 和urdmad交互,完成RDMA的建链

3、urdma_kmod,运行在内核态用于支持RDMA CM建链操作。目前实现只有控制面,不支持数据面操作。这部分可以参考内核代码softiwarp: /infiniband/sw/siw。

二、urdma协议

urdma是基于UDP实现的RDMA协议。是一个非标准的iWARP或者Roce,和标准的iWARP协议相比较,如上图所示:

1、urdma实现iWARP DDP和RDMAP protocol

2、urdma实现TRP,用于可靠性传输字段

3、urdma选择UDP,避免粘包,还有TCP的一些其他特性。

对应的协议头部如下:

2.1协议头 rdmap

rdmap_head:
struct rdmap_packet {
	uint8_t ddp_flags; /* 0=Tagged 1=Last 7-6=DDP_Version */
	uint8_t rdmap_info; /* 1-0=RDMAP_Version 7-4=Opcode */
	uint32_t sink_stag;
} __attribute__((__packed__));

ddp_flags:
用来标识Buffer模型。值为1时表示Tagged Buffer Model,值为0时表示Untagged Buffer Model。接收端依据此flag来决定如何解析报文。

rdmap_info:
rdma的opcode字段send/read/write/atomic

sink_stag:
从代码上看是一个扩展字段:目前用于rdma read,write,atomic作为rkey。其余情况为0 

2.2 协议头:ddp

ddp tag hdr: 用于rdma write和rdma read resp
struct rdmap_tagged_packet {
	struct rdmap_packet head;
	uint64_t offset;
} __attribute__((__packed__));

offset 表示Payload在Tagged Buffer中从Buffer的起始地址到最终数据的存放地址的偏移

ddp untag hdr:用于rdma send、read req、atimoc
struct rdmap_untagged_packet {
	struct rdmap_packet head;
	uint32_t qn; /* Queue Number */
	uint32_t msn; /* Message Sequence Number */
	uint32_t mo; /* Message Offset */
} __attribute__((__packed__));

qn: 数据接收端的untagged队列的序号,个人感觉可以理解为opcode。 ddp_queue_send = 0, ddp_queue_read_request = 1, ddp_queue_terminate = 2(错误处理), ddp_queue_atomic_response = 3, ddp_queue_ack = 4,

msn: 消息序列号,必须从1开始,每次递增1。当到达0xFFFFFFFF之后,重新变为0。每个消息都只能对应一个Untagged packet,所以msn+qn能够唯一确定一个Untagged packet 。

mo:表示从QN和MSN确认的Untagged Buffer的起始位置到消息目的地址的偏移

2.3 协议头:trp

trp hdr
struct trp_hdr {
	uint32_t psn;  
	uint32_t ack_psn;
	uint16_t opcode;
} __attribute__((__packed__));

psn:(1)作为累计ack的时候是最小psn;  (2)作为普通ack是当前报文的psn

ack_psn:(1)作为累计ack的时候是最大psn(psn + 1); (2)为普通ack是已经收到的ack psn

opcode:trp的opcode当前只支持0,累计ack,   QP shutdown。
  ->累计ack:当接收端预期的recv_ack_psn小于接收的psn,认为丢失了ack时使用
  ->QP shutdown:在本端QP down的时候通知对端QP shutdown时使用

三、报文处理流程

1、urdmad为每个RDMA队列对分配一个硬件接收/发送以太网队列,便于APP直接通过IB verbs访问NIC,下发报文。

2、NIC硬件过滤器用于将数据包分离到RX队列中

  • 目前从代码实现来看只支持Flow direct过滤
  • urdmad将未过滤的包送给内核协议栈处理
  • 命中过滤规则的包直接送给对应的QP接收队列,APP通过IB vebrs处理

四、设备初始化

第一步:insmod urdma.ko, 结果如下图所示

1、创建字符设备/dev/urdma,用于RDMA CM建链使用

2、注册urdma bus

3、完成/sys/device/urdma的创建

4、注册netdev的notify,关注网卡的注册,UP、DOWN事件

5、创建cm thread

第二步:以primary方式运行urdmad ,完成RDMA驱动加载, 结果如下图所示。

1、rte_eal_init()初始化DPDK运行环境

2、初始化core资源,用于后续APP使用RDMA功能时一个进程占用一个core。

3、do_init_driver()作为最主要的初始化函数,主要做以下事情:

  • 读取/etc/rdma/urdma.json配置,配置文件会设置预设的队列深度、预设的QP个数等信息。(需要看实际网卡的能力)
  • 创建控制通路:socket用于处理APP进程的QP申请、销毁消息。以及将主进程的数据同步到APP进程
  • open /dev/urdma设备,用于RDMA CM建链,同时在建链的时候,设置过滤规则到硬件。
  • kni网卡的初始化加载,这个动作会创建kni网卡(ifconfig可以看到),同时内核会响应通知notify。urdma.ko响应完成rdma设备的创建和注册。

五、APP--init ibv context

主要说明下ibv_get_device_list()接口在urdma中的实现。这个接口在ibvebs中,最终会调用到ops->alloc_device:urdma_device_alloc()接口。主要做以下事情:

  • 确认存在urdma_0设备
  • 创建线程(our_eal_master_thread()),和urdmad进程通信,确保两边的部分数据一致(max_qp等)。
  • 在our_eal_master_thread线程上下文遍历本进程创建所有QP的收发包处理
  • 初始化Ibv设备

六、MR

6.1 reg mr

由于DPDK的特性,APP作为second进程,申请的内存是DPDK已经pin主的内存,在注册MR的时候,不需要pin内存,更不需要陷入内核。所以MR的注册很简单。

usiw_reg_mr()

    ->usiw_hash_mr()根据address和len得到rkey

    ->urdma_reg_mr_with_rkey() 填充并返回ibv_mr,使用hash表(头插法)管理usiw_mr

6.2 lookup mr

usiw_mr_lookup() 传入usiw_mr_table 和 rkey

    -> 根据rkey % capacity 找到table->entry

    -> for() 遍历usiw_mr->next

    -> rkey相同,则找到mr,否则返回NULL

七、QP

7.1 创建QP

usiw_create_qp()完成QP的创建,QP和SQ、RQ的结构体如上。过程如下:

1、port_get_next_qp() 与urdmad进程通信,urdmad将QP地址给到APP进程。QP的内存这块理解是DPDK做了特殊处理,主从进程拿到的都是物理地址(地址打印分析)才能两边同时操作一个QP。

2、ibv_cmd_create_qp() 陷入内核,内核主要是接收并存储传递下来的QP数据,用于RDMA CM建链,内核返回qpn。

3、send_cq、recv_cq字段的初始化

4、创建SQ和RQ,结构体如右所示。使用两个ring环完成post send和post recv。 初始状态ring为空,storage都存入free_ring

5、QP创建成功,将qp加入到ctx->qp_active链表上,用于our_eal_master_thread线程收发包遍历链表

6、置QP状态处于usiw_qp_unbound状态(未和远端建链)。

7.2 建链

建链分为两部分:

(1)APP进程

APP通过RDMA CM接口陷入内核。rdma_connect/accept

rdma_accept陷入内核

  -> siw_accept()完成QP的建链

        ->siw_modify_qp()修改qp状态

   -> notify_established()将QP加入到链表上。唤醒/dev/urdma的poll,通知urdma进程当前有事件到来,要处理。

(2)urdmad进程

do_poll()做epoll_wait,等待fd事件

   ->监听到fd事件执行fd->data_ready

        ->chardev_data_ready() read fd,陷入内核

             -> urdma_chardev_read() 从链表上获取建链数据,返回用户态。数据格式如上图struct urdma_qp_connected_event{}所示

             -> handle_qp_connected_event()处理qp建链事件

     ->do_setup_qp()本端QP记录对端的QP信息,下发过滤规则到硬件网卡,最后置QP为usiw_qp_connected(连接状态)

7.3 post send

usiw_post_send()当QP状态是connect和running才能post send,send wqe的结构体如上

   ->for() 遍历wr

       ->qp_get_next_send_wqe() 加锁从free_ring中取wqe,如果没有直接返回

       ->根据wr完成填充wqe

       ->rte_ring_enqueue(qp->sq.ring, wqe)将wqe放入ring中。

7.4 post recv

usiw_post_recv()当QP状态是error时直接返回,recv wqe的结构体如上

   ->for() 遍历wr

       ->qp_get_next_recv_wqe () 加锁从free_ring中取wqe,如果没有直接返回

       ->根据wr完成填充wqe

       ->rte_ring_enqueue(qp->rq0.ring, wqe)将wqe放入ring中。

八、CQ

8.1 创建CQ

usiw_create_cq完成CQ的创建,结构体如上。过程如下:

1、计算CQ的大小,申请CQ

2、ibv_cmd_create_cq(),陷入内核,内核管理CQ,返回CQN

3、初始化cq->cqe_ring和cq->free_ring

4、将storage填充到free_ring中

5、其他字段初始化,返回ibv_cq

8.2 poll cq

usiw_poll_cq() 完成cq的poll。cqe_ring的填充在收发包的过程中完成

 ->do_poll_cq() 入参:cq、num、出参:cqe[]

      ->RING_DEQUEUE_BURST() 从cqe_ring中一次批量获取num个cqe

      ->for() 根据取出来的cqe个数遍历cqe,memcpy到出参中

      ->RING_ENQUEUE_BURST() 将出来的cqe一次批量放到free_ring中

 ->convert_cqes() 将cqe 转换为ibv_wc

九、TX/RX流程分析

APP处理报文都在our_eal_master_thread() 上下文处理,过程如下:
kni_loop()函数循环处理报文
  -> list_for_each_safe(&ctx->qp_active, qp, qp_next, ctx_entry)  遍历要处理的QP
       -> if (qp->conn_state == connected) 则执行start_qp()
            ->start_qp() : qp->remote_ep字段的初始化,初始化tx发送队列,置QP为running状态, 继续执行process_qp()
       -> if (qp->conn_state == running) 则执行progress_qp(),参数qp
            ->process_receive_queue() 处理收包,
	          ->rte_eth_rx_burst() 函数将报文从硬件队列中获取到本地
            ->for() 循环遍历获取的报文, 过程中有prefetch,预取内存的数据包
                  ->process_data_packet() 处理RX报文
                     ->首先解析报文头部,如果非建链QP的报文,直接丢弃报文
                     ->解析trp头部:
                          -> if (trp_opcode == trp_sack) 收到片段psn的确认ack, process_trp_sack()根据数据包中的min_psn和max_psn, while() 遍历pending wqe,置ACK标记
                          -> if (trp_opcode == trp_fin) 执行qp_shutdown(), 置QP error状态,消耗SQ、RQ的所有wqe,上送error cqe:IBV_WC_WR_FLUSH_ERR
                     -> 解析rdma->opcode
                              -> send:消耗rqe,上送cqe, IBV_WC_SUCCESS
                              -> read request:根据rkey找mr,判断是否在范围内,请求的addr和length,组装一个readresp的请求头,待后面respond_next_read_atomic()处理
                              -> write:代码中没有处理
                              -> atomic_request:根据rkey找mr,判断是否在范围内,组装一个atomic resp请求头,待后面respond_next_read_atomic()处理
                              -> read resp:每收到一个报文,根据包中的rkey找到mr,根据包中的地址和数据长度,将收到的报文memcoy到地址中,等到收到最后一个read resp报文后,从pending的wqe中找到rdma req wqe,再消耗cqe,将pending的wqe 放入到free_ring中
                              -> atomic resp:从pending的wqe中找到atomic req wqe,再消耗cqe,将pending的wqe 放入到free_ring中
                     ->sweep_unacked_packets() 处理还没有被ack的报文
                          ->for() 遍历被pending的wqe,if (pending-psn < send_last_ack_psn),执行do_process_ack()
                              ->do_process_ack() 处理ack的wqe,此处只处理SEND和WRITE,上送cqe,将wqe放入free_ring中
                          ->for() 遍历被pending的wqe,重传未ACK的报文,如果重传失败,则上送error cqe: IBV_WC_FATAL_ERR/ IBV_WC_RETRY_EXC_ERR,置QP 为ERROR状态
                     ->rte_ring_dequeue(qp->sq.ring, (void **)&send_wqe) 一次处理一个wqe,调用progress_send_wqe()将报文发送去,同时将报文pending
                     ->respond_next_read_atomic() 处理rdma read resp 和rdma atomic resp
                     ->send_ack() 回复ack报文
                     ->flush_tx_queue() 调用驱动接口,将在发送队列中的素有数据包发送出去
        -> if (qp->conn_state == shutdown) 则执行qp_shutdown()发送trp_fin报文,然后将qp置为ERR,flush sq、rq. 上送cqe err(IBV_WC_WR_FLUSH_ERR)
        -> if (qp->conn_state == error) 从ctx->qp_active上将qp删除,如果cq的引用计数为1后, 释放cq,然后发送消息destroy qp的消息到urdmad进程,本端释放qp.

十、urdma分析总结

  • 基于UDP实现的rdma功能。
  • 支持RC和UD
  • 支持RDMA SEND/RECV、READ、atomic
  • 支持可靠传输
  • 需要占用大量的CPU资源
  • 收发包上下文对于内存使用预取功能、使用无锁环,有一定的效率提升
  • 支持累计ACK。
  • 不同于rxe和siw,urdma是在用户态实现,数据路径不需要陷入内核
  • QP资源有限制,取决于硬件网卡的收发包队列个数:min(tx_queue_num,rx_queue_num)
  • 网卡需要支持Flow direct,否则需要修改代码使用软件进行过滤报文
  • 一个进程只有只有一个主线程在进行收发包,遍历当前进程创建的QP,然后进行收发包。
  • 收发包过程需要进行一次数据COPY,此操作会增加时延和降低带宽,是个优化点。
  • 如果urdma进程出现问题,则无法继续使用rdma功能
  • urdma是私有协议,只能两端都是urdma进行测试,否则无法使用

十一、使用方法

前提条件:

1、dpdk版本 17.05以上,20版本以下。因为urdma使用的dpdk接口,在最新版本都发生的了很大变化。尽可能选择17.05、17.11版本的DPDK,这样改的少。

2、内核版本选择4.xx的版本,urdma需要编译urdma.ko。最新内核的IB头文件发生了变化,需要进行大量才能编译通过。

3、选择的网卡尽可能支持Flow direct功能,使用硬件过滤,否则需要修改代码,在kni处理报文的代码处(do_xchg_packets())过滤RDMA报文交给对应的APP处理。

4、rdma core的版本使用17-18,建议选择17.11。

5、DPDK的编译(17.05为例,编译过程可能需要解决编译问题)

(1)vi config/common_base
#修改
CONFIG_RTE_BUILD_SHARED_LIB=y
CONFIG_RTE_EAL_IGB_UIO=y
CONFIG_RTE_KNI_KMOD=y

(2)编译
make config T=x86_64-native-linuxapp-gcc
export EXTRA_CFLAGS="-w -Wno-address-of-packed-member“
make

6、编译urdma,过程可能解决编译问题

  export RTE_SDK=/root/dpdk-17.05
  export RTE_TARGET=build
  export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$RTE_SDK/$RTE_TARGET/lib:/root/urdma-master/src/liburdma/.libs
  autoreconf –I
  ./configure --sysconfdir /etc
  make
  make install

7、两端都必须是urdma,才能测试

运行和测试

1、运行urdmad,初始化RDMA设备,过程如下:

modprobe uio
modprobe ib_core
modprobe ib_uverbs
modprobe ib_cm
modprobe ib_umad
modprobe iw_cm
modprobe rdma_cm
modprobe rdma_ucm

rmmod urdma.ko

insmod /home/matrix/workspace/rdma/dpdk/dpdk-src/build/kmod/igb_uio.ko 
insmod /home/matrix/workspace/rdma/dpdk/dpdk-src/build/kmod/rte_kni.ko 
insmod /home/matrix/workspace/rdma/urdma/urdma-src/src/kmod/urdma.ko 

#将liburdma-rdmav17.so拷贝到libibverbs下
cp /usr/local/lib/liburdma-rdmav17.so /usr/lib/x86_64-linux-gnu/libibverbs/liburdma-rdmav17.so
#dpdk绑定网卡
/home/matrix/workspace/rdma/dpdk/dpdk-src/usertools/dpdk-devbind.py -b igb_uio 03:00.0
#设置大页
mount -t hugetlbfs nodev /mnt/huge
sh -c 'echo 128 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages'

#设置库
export RTE_SDK=/home/matrix/workspace/rdma/dpdk/dpdk-src
export RTE_TARGET=build
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$RTE_SDK/$RTE_TARGET/lib:/home/matrix/workspace/rdma/urdma/urdma-src/src/liburdma/.libs

#运行urdma。
/home/matrix/workspace/rdma/urdma/urdma-src/src/urdmad/urdmad  -l 0-1 -d librte_pmd_vmxnet3_uio.so -d librte_mempool_ring.so --proc-type=primary --file-prefix=ubuntu
参数
        -l:表示运行的核;
        -d: 表示要加载的动态库(mempool是dpdk的特性需要加载,vmxnet3是选择的网卡(根据网卡选择)); 
        --proc-type: 运行的优先级;
        --file-prefix: 表示hostname

2、看到kni网卡,ibv_device能看到IB设备,此时可以进行RDMA测试了

root@ubuntu:/sys/devices/urdma# ifconfig
ens32: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.92.160  netmask 255.255.255.0  broadcast 192.168.92.255
        inet6 fe80::250:56ff:fe25:e76c  prefixlen 64  scopeid 0x20<link>
        ether 00:50:56:25:e7:6c  txqueuelen 1000  (Ethernet)
        RX packets 62854  bytes 4448101 (4.4 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 167011  bytes 131558018 (131.5 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

kni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.186.130  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::20c:29ff:fe92:d2e6  prefixlen 64  scopeid 0x20<link>
        ether 00:0c:29:92:d2:e6  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 12  bytes 1338 (1.3 KB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

root@ubuntu:/sys/devices/urdma# ibv_devices 
device                 node GUID
    ------              ----------------
    rxe0                025056fffe25e76c
    urdma_0             000c2992d2e60000
root@ubuntu:/sys/devices/urdma# 

 

文章来自个人专栏
RDMA杂谈
8 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0