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

softroce-rxe源码解析

2023-10-07 01:33:47
574
0

一、基本简介

rxe是完全使用软件实现的rdma协议。基本架构如下图所示,特性如下:

  • 基于udp实现
  • 向用户呈现的是一套标准的IB verbs接口
  • 基于标准的IB spec实现
  • 所有的verbs操作都需要陷入内核。不同于硬件RDMA,数据路径by-pass kernel
  • 支持RDMA的操作:SEND/RECV、WRITE、READ、ATOMIC
  • 支持RC、UC、UD
  • 支持NAK重传、超时重传机制
  • 支持累计ACK,WQE是一个一个确定,CI依次+1
  • 支持CQ中断,但不支持EQ。
  • 不支持拥塞管理(没有PFC、ECN),依赖于内核协议栈的拥塞管理

代码路径:

用户态代码路径:rdma-core/providers/rxe/

内核代码路径:kernel/driver/infiniband/sw/rxe/

本文的分析基于内核4.19版本的代码。

二、设备初始化

1、rxe模块中使用到的两个机制pool和queue

(1)Pool机制-rxe用于管理qpc,cqc,mrc等context使用的方法,两个结构体如下所示:

struct rxe_pool_entry {
       struct rxe_pool             *pool;
       struct kref             ref_cnt;
       struct list_head     list;
       /* only used if indexed or keyed */
       struct rb_node             node;
       u32               index;
};

struct rxe_pool {
       struct rxe_dev       *rxe;
       spinlock_t              pool_lock; /* pool spinlock */
       size_t                    elem_size;      /*qp、cq、mr等结构体的大小*/
       struct kref             ref_cnt;
       void               (*cleanup)(struct rxe_pool_entry *obj);
       enum rxe_pool_state    state;
       enum rxe_pool_flags    flags;
       enum rxe_elem_type    type;   /*pool 的类型*/
       unsigned int         max_elem;   /*支持的pool的个数*/
       atomic_t        num_elem;   /*当前pool中的个数*/

       /* only used if indexed or keyed */
       struct rb_root        tree;   /*用于管理pool*/
       unsigned long             *table; /*index bitmap*/
       size_t                    table_size; /*根据max_index和min_index计算的table size*/
       u32               max_index;  /*和范围*/
       u32               min_index;
       u32               last;       /*记录上次分配的bit,便于查找index*/
       size_t                    key_offset;
       size_t                    key_size;
};
  • 所有context要包含struct rxe_pool_entry成员
  • 使用红黑树管理context
  • pool中记录了qp,cq,mr等元素的一些基本信息,在创建时候使用。用于申请内存、资源检测是否足够等等。

(2)Queue机制-rxe模块用于管理qp、cq队列使用的方法,如下所示:

struct rxe_queue结构体说明

struct rxe_queue {
       struct rxe_dev       *rxe;
       struct rxe_queue_buf    *buf;
       struct rxe_mmap_info   *ip;
       size_t                    buf_size;
       size_t                    elem_size;
       unsigned int         log2_elem_size;   
       unsigned int         index_mask;
};

          buf_size = sizeof(rxe_queue_buf) + queue depth * queue entry size

          elem_size = queue entry size

          index_mask = queue depth – 1

          buf = vmalloc_user(buf_size),用于给用户态mmap使用。

struct rxe_queue_buf结构体说明:

  struct rxe_queue_buf {
       __u32                   log2_elem_size;
       __u32                   index_mask;   /*队列深度的掩码*/
       __u32                   pad_1[30];
       __u32                   producer_index;   /*生产者index*/
       __u32                   pad_2[31];
       __u32                   consumer_index;  /*消费者index*/
       __u32                   pad_3[31];
       __u8                     data[0];                 /*队列的起始地址*/
};

       内核和用户态对queue的操作主要使用到index_mask、producer_index;和consumer_index。对于queue中的pi和ci的修改陈述:(1)mmap的内存,所以内核和用户态是操作的一个数据(2)CQ的PI内核修改,加锁;CI用户态修改,加锁;(3)SQ的PI用户态修改,加锁;CI内核修改,不加锁;(4)RQ的PI用户态修改,加锁;CI内核修改,不加锁

2、rxe模块的加载

通过insmod rdma_rxe.ko触发rxe模块的加载,主要完成下面两件事情

  • 初始化udp tunnel,使用udp框架完成收发包
  • 注册netdev事件,关心网卡的UP、DOWN、MTU变化等事

3、rdma设备的加载

通过sysfs或者netlink发起rxe设备add请求,与内核rxe模块通信完成rxe设备的加载(具体使用方法见后文)。

rxe模块响应add请求,通过rxe_add()函数完成结构体struct rxe_dev{}的初始化

  • 初始化rxe_dev->attr,配置rdma设备cap(max qp、max_cq、max sge、max mr等等)
  • 初始化rxe_dev->port,配置prot的属性(guid、MTU等等)
  • 初始化rxe_dev->rxe_poll指向的结构体。pool使用rbtree管理创建的qp、mr等资源。
  • 完成IB device的注册

三、MR

1MR注册

上图是MR注册的基本过程。最终完成struct rxe_mem{}的初始化。

struct rxe_mem {
	struct rxe_pool_entry	pelem;
	union {
		struct ib_mr		ibmr;
		struct ib_mw		ibmw;
	};
	struct rxe_pd		*pd;
	struct ib_umem		*umem;
	u32			lkey;
	u32			rkey;
	enum rxe_mem_state	state;
	enum rxe_mem_type	type;
	u64			va;			/*用户传入的地址*/
	u64			iova;			/*用户传入的地址*/
	size_t			length;	/*用户传入的长度*/
	u32			offset;		/*第一个entry的偏移*/
	int			access;		/*访问权限*/
	int			page_shift;
	int			page_mask;
	int			map_shift;
	int			map_mask;
	u32			num_buf;			/* entry的个数*/
	u32			nbuf;
	u32			max_buf;	
	u32			num_map;		/*一级表的个数*/
	struct rxe_map		**map;	/*二级页表*/
};

说明:

(1)4K页表存放256个地址entry(struct rxe_phys_buf)。enrty包含了地址和长度地址和长度都是通过ib_umem_get()整理后得来的。size不一定全部是4K。可以使连续地址的大小

(2)MR的组织形式以二级页表的方式组织,

(3)MR中记录页表存放的地址,地址存放的是内核的虚拟地址

(4)rxe_mem->offset是第一个entry中的可使用的偏移。

2、lookup mr过程

(1)根据sge的key获取index,从rb tree中找到对应的mr context。 需要循环遍历。

(2)根据sge中的addr也mr context中保存的地址进行比较,将数据memcpy到payload对应的地址中。这块是sge对应的buf和skb 对应的buf互相copy(TX和RX)

四、CQ

1CQ创建

上图是创建CQ的过程。(1)CQ有创建个数的最大限制。(2)CQ不同于MR、QP不需要用rb_tree管理,通过QP找到CQ即可。结构体如下:

struct rxe_cq {
	struct rxe_pool_entry	pelem;
	struct ib_cq		ibcq;
	struct rxe_queue	*queue;
	spinlock_t		cq_lock;
	u8			notify;
	bool			is_dying;
	int			is_user;
	struct tasklet_struct	comp_task;	/*中断函数*/
};

2、POLL CQ

(1)内核在RX流程中完成CQE结构体的赋值,根据CQ的PI,填充CQE,PI++,填充CQE过程需要加锁

(2)用户态根据CQ addr、CI和PI,获取CQE, CI++, 返回给APP.Poll cq 需要加锁的

 

3、CQ的中断模式;

cq->complete  task用于中断模式。调用创建CQ时复制的ib_uverbs_comp_handle()处理函数。ib_uverbs_comp_handler()函数调用wake_up_interruptible()唤醒对应阻塞的线程,通知用户态处理。

五、QP

1QP创建

上图是创建QP的基本过程,完成struct rxe_qp{}的初始化。

struct rxe_qp {
	struct rxe_pool_entry	pelem;
	struct ib_qp		ibqp;
	struct ib_qp_attr	attr;
	unsigned int		valid;
	unsigned int		mtu;
	int			is_user;
	struct rxe_pd		*pd;
	struct rxe_srq		*srq;
	struct rxe_cq		*scq;
	struct rxe_cq		*rcq;
	enum ib_sig_type	sq_sig_type;
	struct rxe_sq		sq;
	struct rxe_rq		rq;
	struct socket		*sk;
	u32			dst_cookie;
	struct rxe_av		pri_av;
	struct rxe_av		alt_av;
	struct list_head	grp_list;
	spinlock_t		grp_lock; /* guard grp_list */
	struct sk_buff_head	req_pkts;   /*内核协议栈的req pkt*/
	struct sk_buff_head	resp_pkts;
	struct sk_buff_head	send_pkts;

	struct rxe_req_info	req;        /*tx处理*/
	struct rxe_comp_info	comp;   /*ACK报文*/
	struct rxe_resp_info	resp;   /*respond报文*/

	atomic_t		ssn;
	atomic_t		skb_out;
	int			need_req_skb;
	struct timer_list retrans_timer;    /*超时重传*/
	u64 qp_timeout_jiffies;
	/* Timer for handling RNR NAKS. */
	struct timer_list rnr_nak_timer;    /*rnr nak 重传*/
	spinlock_t		state_lock; /* guard requester and completer */
	struct execute_work	cleanup_work;
};

说明:

(1)不同于其他的硬件RDMA厂商,没有db_record

(2)RXE是内核申请queue buf,用户态映射。硬件厂商是用户态申请

(3)收发包的回调函数参数是QP。不需要lookup QO

(4)wqe size是固定的,内核和用户态用一个结构体。SQ和RQ的组织如下:

2TX-POST SEND

上图是Post send的基本过程。Send wqe的结构体如下:

struct rxe_send_wqe {
	struct rxe_send_wr	wr;		/*wqe hdr信息*/
	struct rxe_av		av;
	__u32			status;
	__u32			state;		/*wqe的状态 pending、process*/
	__aligned_u64		iova;
	__u32			mask;
	__u32			first_psn;		/*wqe对应报文的第一个psn*/
	__u32			last_psn;		/*wqe对应报文的最后一个psn*/
	__u32			ack_length;
	__u32			ssn;
	__u32			has_rd_atomic;
	struct rxe_dma_info	dma;	/*sge信息*/
};

说明:

(1)post send敲doorbel是向内核发送了POST_SEND的命令,代码如下;

cmd.hdr.command	= IB_USER_VERBS_CMD_POST_SEND;
cmd.hdr.in_words = sizeof(cmd) / 4;
cmd.hdr.out_words = sizeof(resp) / 4;
cmd.response	= (uintptr_t)&resp;
cmd.qp_handle	= ibqp->handle;
cmd.wr_count	= 0;
cmd.sge_count	= 0;
cmd.wqe_size	= sizeof(struct ibv_send_wr);
write(ibqp->context->cmd_fd, &cmd, sizeof(cmd));

(2)报文发送是在当前post send的上下文完成

(3)分片的报文要记录buf信息,一次只获取mtu大小的报文(lookup mr),填充获取addr和payload len

(4)对于需要ACK的wqe,需要pending,还需要放在SQ中

(5)QP的ci在ack后,更新

(6)post send过程加锁了(分为用户态PI加锁和内核态QP处理报文过程加锁)所以同时只有一个线程能操作队列。多个线程操作同一个qp时,第一个获取到QP资源的线程会继续处理到没有wqe,后面的线程直接返回。

(7)最终发包是调用rxe_send()函数完成。

3RX-POST RECV

上图是post recv的过程。(1)post recv过程需要加锁,所以同时只有一个线程下发rqe(2)操作不会下限到内核。结构体如下:

struct rxe_recv_wqe {
	__aligned_u64		wr_id;
	__u32			num_sge;	/*sge个数*/
	__u32			padding;
	struct rxe_dma_info	dma;	/*sge*/
};

4RX

上图是RX的流程,rxe收到的包只有IB头+payload两部分,UDP之前的包头已经被剥离。收到的包分为两种包:(1)处理requester;(2)处理ACK报文;代码如下:

static inline void rxe_rcv_pkt(struct rxe_dev *rxe, struct rxe_pkt_info *pkt,struct sk_buff *skb)
{
	if (pkt->mask & RXE_REQ_MASK)
		rxe_resp_queue_pkt(rxe, pkt->qp, skb);
	else
		rxe_comp_queue_pkt(rxe, pkt->qp, skb);
}

5RX-请求报文

上图是处理request报文的过程。简单说明:

(1)RDMA read request在softirq上下文处理,获取buf,通过rxe_send发送

(2)Requester 队列中的报文大于1个,在softirq中处理

(3)只有一个报文,在UDP tunnel收包上下文处理

6RX-ACK报文

上图是处理ACK报文的过程。简单说明:

(1)当respond 队列中的报文数量大于1,在softirp上下文处理

(2)一个报文,在UDP tunnel收包上下文处理

(3)没有处理cq full的情况,应该是认为报文是成功发送的,用户不用关心。

(4)SQ ci ++

7RX-ACK报文之RDMA_READ_RESPOND

解析ACK报文,判断opcode是rdma read resp,处理过程如下:

(1)首先从SQ中获取根据CI和SQ_addr获取wqe

(2)解析wqe,获取要存储报文的sge

(3)将rdma read resp的报文写到对应的buf中

  • 如果是多个rdma read resp报文,wqe还继续维持pending,同时记录写sge的偏移(写struct rxe_send_wqe->dma)。
  • 当多个rdma read resp 报文全部收齐后,ci ++,释放wqe。

8RX-累计ACK功能

(1)首先从SQ中获取根据CI和SQ_addr获取wqe。

(2)解析wqe,发现wqe->last_psn(这个wqe对应的最后一个分片报文的psn) < ack 报文中的psn时,认为是累计ack

  • Send/write操作,会处理到相同psn的wqe即可,一个wqe上送一个cqe。如果不是全部上送cqe或者不要求上送cqe,则不上送cqe
  • 如果pending的wqe是Rdma read resp 或者atomic报文就任务丢包了,需要重传

使用方法:

1、前提条件:

(1)确定设备上是否有rxe的用户态驱动,不存在需要更新/安装rdma-core

  • 查看/etc/libibverbs.d/目录下是否有driver
  • 查看设备上是否有librxe-rdma.so

(2)确定设备上是否有IB、rxe的内核驱动,不存在就找对应的内核源码,单独编译driver/infiniband/生成驱动

  • 查看是否有ko,ib_core.ko,rdma_rxe.ko

2、添加设备

(1)两种方法:

  • 通过rdma link add命令,(只在高版本内核中支持)
    • rdma link add xxx type rxe netdev xxx 命令通过netlink与内核IB模块交互,与c中的注册模块完成rxe设备的添加

  • 通过sysfs添加
    • echo xxx(网卡) > /sys/module/rdma_rxe/parameters/add。如果没有parameters目录,就是加载ko不正确。

3、性能测试:

使用perftest测试,使用标卡和cx6对打测试,中间一跳交换机

  • 8个进程占用8个核,带宽:22Gb
  • 时延:26us
  • PPS:0.5M

分析

  • 数据面需要陷入内核操作
  • 数据包需要经过一次完整的协议栈
  • rxe发包过程中,需要将内存通过CPU将数据写入发包缓存中,网卡还需要一次dma才能将数据发送出去
  • rxe的收包是瓶颈,写内存是CPU处理 softirq

4、测试网卡的选择

  • 尽可能的使用不带有rdma功能的网卡
  • 使用Mlnx网卡测试rxe的时候,网卡会截获rdma报文,不会上送rxe。

 

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

softroce-rxe源码解析

2023-10-07 01:33:47
574
0

一、基本简介

rxe是完全使用软件实现的rdma协议。基本架构如下图所示,特性如下:

  • 基于udp实现
  • 向用户呈现的是一套标准的IB verbs接口
  • 基于标准的IB spec实现
  • 所有的verbs操作都需要陷入内核。不同于硬件RDMA,数据路径by-pass kernel
  • 支持RDMA的操作:SEND/RECV、WRITE、READ、ATOMIC
  • 支持RC、UC、UD
  • 支持NAK重传、超时重传机制
  • 支持累计ACK,WQE是一个一个确定,CI依次+1
  • 支持CQ中断,但不支持EQ。
  • 不支持拥塞管理(没有PFC、ECN),依赖于内核协议栈的拥塞管理

代码路径:

用户态代码路径:rdma-core/providers/rxe/

内核代码路径:kernel/driver/infiniband/sw/rxe/

本文的分析基于内核4.19版本的代码。

二、设备初始化

1、rxe模块中使用到的两个机制pool和queue

(1)Pool机制-rxe用于管理qpc,cqc,mrc等context使用的方法,两个结构体如下所示:

struct rxe_pool_entry {
       struct rxe_pool             *pool;
       struct kref             ref_cnt;
       struct list_head     list;
       /* only used if indexed or keyed */
       struct rb_node             node;
       u32               index;
};

struct rxe_pool {
       struct rxe_dev       *rxe;
       spinlock_t              pool_lock; /* pool spinlock */
       size_t                    elem_size;      /*qp、cq、mr等结构体的大小*/
       struct kref             ref_cnt;
       void               (*cleanup)(struct rxe_pool_entry *obj);
       enum rxe_pool_state    state;
       enum rxe_pool_flags    flags;
       enum rxe_elem_type    type;   /*pool 的类型*/
       unsigned int         max_elem;   /*支持的pool的个数*/
       atomic_t        num_elem;   /*当前pool中的个数*/

       /* only used if indexed or keyed */
       struct rb_root        tree;   /*用于管理pool*/
       unsigned long             *table; /*index bitmap*/
       size_t                    table_size; /*根据max_index和min_index计算的table size*/
       u32               max_index;  /*和范围*/
       u32               min_index;
       u32               last;       /*记录上次分配的bit,便于查找index*/
       size_t                    key_offset;
       size_t                    key_size;
};
  • 所有context要包含struct rxe_pool_entry成员
  • 使用红黑树管理context
  • pool中记录了qp,cq,mr等元素的一些基本信息,在创建时候使用。用于申请内存、资源检测是否足够等等。

(2)Queue机制-rxe模块用于管理qp、cq队列使用的方法,如下所示:

struct rxe_queue结构体说明

struct rxe_queue {
       struct rxe_dev       *rxe;
       struct rxe_queue_buf    *buf;
       struct rxe_mmap_info   *ip;
       size_t                    buf_size;
       size_t                    elem_size;
       unsigned int         log2_elem_size;   
       unsigned int         index_mask;
};

          buf_size = sizeof(rxe_queue_buf) + queue depth * queue entry size

          elem_size = queue entry size

          index_mask = queue depth – 1

          buf = vmalloc_user(buf_size),用于给用户态mmap使用。

struct rxe_queue_buf结构体说明:

  struct rxe_queue_buf {
       __u32                   log2_elem_size;
       __u32                   index_mask;   /*队列深度的掩码*/
       __u32                   pad_1[30];
       __u32                   producer_index;   /*生产者index*/
       __u32                   pad_2[31];
       __u32                   consumer_index;  /*消费者index*/
       __u32                   pad_3[31];
       __u8                     data[0];                 /*队列的起始地址*/
};

       内核和用户态对queue的操作主要使用到index_mask、producer_index;和consumer_index。对于queue中的pi和ci的修改陈述:(1)mmap的内存,所以内核和用户态是操作的一个数据(2)CQ的PI内核修改,加锁;CI用户态修改,加锁;(3)SQ的PI用户态修改,加锁;CI内核修改,不加锁;(4)RQ的PI用户态修改,加锁;CI内核修改,不加锁

2、rxe模块的加载

通过insmod rdma_rxe.ko触发rxe模块的加载,主要完成下面两件事情

  • 初始化udp tunnel,使用udp框架完成收发包
  • 注册netdev事件,关心网卡的UP、DOWN、MTU变化等事

3、rdma设备的加载

通过sysfs或者netlink发起rxe设备add请求,与内核rxe模块通信完成rxe设备的加载(具体使用方法见后文)。

rxe模块响应add请求,通过rxe_add()函数完成结构体struct rxe_dev{}的初始化

  • 初始化rxe_dev->attr,配置rdma设备cap(max qp、max_cq、max sge、max mr等等)
  • 初始化rxe_dev->port,配置prot的属性(guid、MTU等等)
  • 初始化rxe_dev->rxe_poll指向的结构体。pool使用rbtree管理创建的qp、mr等资源。
  • 完成IB device的注册

三、MR

1MR注册

上图是MR注册的基本过程。最终完成struct rxe_mem{}的初始化。

struct rxe_mem {
	struct rxe_pool_entry	pelem;
	union {
		struct ib_mr		ibmr;
		struct ib_mw		ibmw;
	};
	struct rxe_pd		*pd;
	struct ib_umem		*umem;
	u32			lkey;
	u32			rkey;
	enum rxe_mem_state	state;
	enum rxe_mem_type	type;
	u64			va;			/*用户传入的地址*/
	u64			iova;			/*用户传入的地址*/
	size_t			length;	/*用户传入的长度*/
	u32			offset;		/*第一个entry的偏移*/
	int			access;		/*访问权限*/
	int			page_shift;
	int			page_mask;
	int			map_shift;
	int			map_mask;
	u32			num_buf;			/* entry的个数*/
	u32			nbuf;
	u32			max_buf;	
	u32			num_map;		/*一级表的个数*/
	struct rxe_map		**map;	/*二级页表*/
};

说明:

(1)4K页表存放256个地址entry(struct rxe_phys_buf)。enrty包含了地址和长度地址和长度都是通过ib_umem_get()整理后得来的。size不一定全部是4K。可以使连续地址的大小

(2)MR的组织形式以二级页表的方式组织,

(3)MR中记录页表存放的地址,地址存放的是内核的虚拟地址

(4)rxe_mem->offset是第一个entry中的可使用的偏移。

2、lookup mr过程

(1)根据sge的key获取index,从rb tree中找到对应的mr context。 需要循环遍历。

(2)根据sge中的addr也mr context中保存的地址进行比较,将数据memcpy到payload对应的地址中。这块是sge对应的buf和skb 对应的buf互相copy(TX和RX)

四、CQ

1CQ创建

上图是创建CQ的过程。(1)CQ有创建个数的最大限制。(2)CQ不同于MR、QP不需要用rb_tree管理,通过QP找到CQ即可。结构体如下:

struct rxe_cq {
	struct rxe_pool_entry	pelem;
	struct ib_cq		ibcq;
	struct rxe_queue	*queue;
	spinlock_t		cq_lock;
	u8			notify;
	bool			is_dying;
	int			is_user;
	struct tasklet_struct	comp_task;	/*中断函数*/
};

2、POLL CQ

(1)内核在RX流程中完成CQE结构体的赋值,根据CQ的PI,填充CQE,PI++,填充CQE过程需要加锁

(2)用户态根据CQ addr、CI和PI,获取CQE, CI++, 返回给APP.Poll cq 需要加锁的

 

3、CQ的中断模式;

cq->complete  task用于中断模式。调用创建CQ时复制的ib_uverbs_comp_handle()处理函数。ib_uverbs_comp_handler()函数调用wake_up_interruptible()唤醒对应阻塞的线程,通知用户态处理。

五、QP

1QP创建

上图是创建QP的基本过程,完成struct rxe_qp{}的初始化。

struct rxe_qp {
	struct rxe_pool_entry	pelem;
	struct ib_qp		ibqp;
	struct ib_qp_attr	attr;
	unsigned int		valid;
	unsigned int		mtu;
	int			is_user;
	struct rxe_pd		*pd;
	struct rxe_srq		*srq;
	struct rxe_cq		*scq;
	struct rxe_cq		*rcq;
	enum ib_sig_type	sq_sig_type;
	struct rxe_sq		sq;
	struct rxe_rq		rq;
	struct socket		*sk;
	u32			dst_cookie;
	struct rxe_av		pri_av;
	struct rxe_av		alt_av;
	struct list_head	grp_list;
	spinlock_t		grp_lock; /* guard grp_list */
	struct sk_buff_head	req_pkts;   /*内核协议栈的req pkt*/
	struct sk_buff_head	resp_pkts;
	struct sk_buff_head	send_pkts;

	struct rxe_req_info	req;        /*tx处理*/
	struct rxe_comp_info	comp;   /*ACK报文*/
	struct rxe_resp_info	resp;   /*respond报文*/

	atomic_t		ssn;
	atomic_t		skb_out;
	int			need_req_skb;
	struct timer_list retrans_timer;    /*超时重传*/
	u64 qp_timeout_jiffies;
	/* Timer for handling RNR NAKS. */
	struct timer_list rnr_nak_timer;    /*rnr nak 重传*/
	spinlock_t		state_lock; /* guard requester and completer */
	struct execute_work	cleanup_work;
};

说明:

(1)不同于其他的硬件RDMA厂商,没有db_record

(2)RXE是内核申请queue buf,用户态映射。硬件厂商是用户态申请

(3)收发包的回调函数参数是QP。不需要lookup QO

(4)wqe size是固定的,内核和用户态用一个结构体。SQ和RQ的组织如下:

2TX-POST SEND

上图是Post send的基本过程。Send wqe的结构体如下:

struct rxe_send_wqe {
	struct rxe_send_wr	wr;		/*wqe hdr信息*/
	struct rxe_av		av;
	__u32			status;
	__u32			state;		/*wqe的状态 pending、process*/
	__aligned_u64		iova;
	__u32			mask;
	__u32			first_psn;		/*wqe对应报文的第一个psn*/
	__u32			last_psn;		/*wqe对应报文的最后一个psn*/
	__u32			ack_length;
	__u32			ssn;
	__u32			has_rd_atomic;
	struct rxe_dma_info	dma;	/*sge信息*/
};

说明:

(1)post send敲doorbel是向内核发送了POST_SEND的命令,代码如下;

cmd.hdr.command	= IB_USER_VERBS_CMD_POST_SEND;
cmd.hdr.in_words = sizeof(cmd) / 4;
cmd.hdr.out_words = sizeof(resp) / 4;
cmd.response	= (uintptr_t)&resp;
cmd.qp_handle	= ibqp->handle;
cmd.wr_count	= 0;
cmd.sge_count	= 0;
cmd.wqe_size	= sizeof(struct ibv_send_wr);
write(ibqp->context->cmd_fd, &cmd, sizeof(cmd));

(2)报文发送是在当前post send的上下文完成

(3)分片的报文要记录buf信息,一次只获取mtu大小的报文(lookup mr),填充获取addr和payload len

(4)对于需要ACK的wqe,需要pending,还需要放在SQ中

(5)QP的ci在ack后,更新

(6)post send过程加锁了(分为用户态PI加锁和内核态QP处理报文过程加锁)所以同时只有一个线程能操作队列。多个线程操作同一个qp时,第一个获取到QP资源的线程会继续处理到没有wqe,后面的线程直接返回。

(7)最终发包是调用rxe_send()函数完成。

3RX-POST RECV

上图是post recv的过程。(1)post recv过程需要加锁,所以同时只有一个线程下发rqe(2)操作不会下限到内核。结构体如下:

struct rxe_recv_wqe {
	__aligned_u64		wr_id;
	__u32			num_sge;	/*sge个数*/
	__u32			padding;
	struct rxe_dma_info	dma;	/*sge*/
};

4RX

上图是RX的流程,rxe收到的包只有IB头+payload两部分,UDP之前的包头已经被剥离。收到的包分为两种包:(1)处理requester;(2)处理ACK报文;代码如下:

static inline void rxe_rcv_pkt(struct rxe_dev *rxe, struct rxe_pkt_info *pkt,struct sk_buff *skb)
{
	if (pkt->mask & RXE_REQ_MASK)
		rxe_resp_queue_pkt(rxe, pkt->qp, skb);
	else
		rxe_comp_queue_pkt(rxe, pkt->qp, skb);
}

5RX-请求报文

上图是处理request报文的过程。简单说明:

(1)RDMA read request在softirq上下文处理,获取buf,通过rxe_send发送

(2)Requester 队列中的报文大于1个,在softirq中处理

(3)只有一个报文,在UDP tunnel收包上下文处理

6RX-ACK报文

上图是处理ACK报文的过程。简单说明:

(1)当respond 队列中的报文数量大于1,在softirp上下文处理

(2)一个报文,在UDP tunnel收包上下文处理

(3)没有处理cq full的情况,应该是认为报文是成功发送的,用户不用关心。

(4)SQ ci ++

7RX-ACK报文之RDMA_READ_RESPOND

解析ACK报文,判断opcode是rdma read resp,处理过程如下:

(1)首先从SQ中获取根据CI和SQ_addr获取wqe

(2)解析wqe,获取要存储报文的sge

(3)将rdma read resp的报文写到对应的buf中

  • 如果是多个rdma read resp报文,wqe还继续维持pending,同时记录写sge的偏移(写struct rxe_send_wqe->dma)。
  • 当多个rdma read resp 报文全部收齐后,ci ++,释放wqe。

8RX-累计ACK功能

(1)首先从SQ中获取根据CI和SQ_addr获取wqe。

(2)解析wqe,发现wqe->last_psn(这个wqe对应的最后一个分片报文的psn) < ack 报文中的psn时,认为是累计ack

  • Send/write操作,会处理到相同psn的wqe即可,一个wqe上送一个cqe。如果不是全部上送cqe或者不要求上送cqe,则不上送cqe
  • 如果pending的wqe是Rdma read resp 或者atomic报文就任务丢包了,需要重传

使用方法:

1、前提条件:

(1)确定设备上是否有rxe的用户态驱动,不存在需要更新/安装rdma-core

  • 查看/etc/libibverbs.d/目录下是否有driver
  • 查看设备上是否有librxe-rdma.so

(2)确定设备上是否有IB、rxe的内核驱动,不存在就找对应的内核源码,单独编译driver/infiniband/生成驱动

  • 查看是否有ko,ib_core.ko,rdma_rxe.ko

2、添加设备

(1)两种方法:

  • 通过rdma link add命令,(只在高版本内核中支持)
    • rdma link add xxx type rxe netdev xxx 命令通过netlink与内核IB模块交互,与c中的注册模块完成rxe设备的添加

  • 通过sysfs添加
    • echo xxx(网卡) > /sys/module/rdma_rxe/parameters/add。如果没有parameters目录,就是加载ko不正确。

3、性能测试:

使用perftest测试,使用标卡和cx6对打测试,中间一跳交换机

  • 8个进程占用8个核,带宽:22Gb
  • 时延:26us
  • PPS:0.5M

分析

  • 数据面需要陷入内核操作
  • 数据包需要经过一次完整的协议栈
  • rxe发包过程中,需要将内存通过CPU将数据写入发包缓存中,网卡还需要一次dma才能将数据发送出去
  • rxe的收包是瓶颈,写内存是CPU处理 softirq

4、测试网卡的选择

  • 尽可能的使用不带有rdma功能的网卡
  • 使用Mlnx网卡测试rxe的时候,网卡会截获rdma报文,不会上送rxe。

 

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