IB程序设计流程

本文详细介绍了InfiniBand(IB)编程流程,从获取设备、初始化全局结构体到建立连接,涵盖QP状态转换、接收和发送队列的设置,以及事件通知和完成队列的轮询。主要涉及关键概念如保护域(PD)、内存注册(MR)、完成队列(CQ)、队列对(QP)和地址处理(AH)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IB程序设计流程

以ibv_ud_pingpong为例

1. 声明控制结构体

1.1全局结构体

struct pingpong_context {
	struct ibv_context	*context; //ibv设备上下文
	struct ibv_comp_channel *channel; //event事件通道
	struct ibv_pd		*pd; //保护域
	struct ibv_mr		*mr; //内存注册
	struct ibv_cq		*cq; //完成队列
	struct ibv_qp		*qp; //QP任务队列
	struct ibv_ah		*ah; //address handler
	void			*buf; //缓存地址
	int			 size;  // 数据大小 
	int			 rx_depth; //接收队列中未完成任务的最大数量
	int			 pending; //?
	struct ibv_port_attr     portinfo; //端口信息
};

1.2目标地址结构体

struct pingpong_dest {
	int lid; //目标lid
	int qpn; //目标qp序号
	int psn; //?
	union ibv_gid gid; //?
};

2. 主函数执行流程

2.1准备工作

声明变量
获取参数

2.2 正式工作

2.2.1 获取设备

获取设备列表 dev_list = ibv_get_deivce_list(NULL)

获取指定设备 ib_dev = dev_list[i]

  • 也可以通过ibv_get_device_name(dev_list[i])获取指定设备名称,与给定名称进行比较,获取指定名称设备
2.2.2 初始化全局结构体(核心)----pp_init_ctx
static struct pingpong_context *pp_init_ctx(struct ibv_device *ib_dev, int size,
					    int rx_depth, int port,
					    int use_event)

初始化结构体

	struct pingpong_context *ctx;

	ctx = malloc(sizeof *ctx);  //为结构体开辟空间

初始化用户指定参数

	ctx->size     = size;
	ctx->rx_depth = rx_depth;

初始化缓存

	ctx->buf = memalign(page_size, size + 40);  //分配指定大小内存作为缓冲区
	if (!ctx->buf) {
		fprintf(stderr, "Couldn't allocate work buf.\n");
		goto clean_ctx;
	}
	/* FIXME memset(ctx->buf, 0, size + 40); */
	memset(ctx->buf, 0x7b, size + 40); //将缓存区域size+40范围内都设置为0x7b

初始化设备

	ctx->context = ibv_open_device(ib_dev);
	if (!ctx->context) {
		fprintf(stderr, "Couldn't get context for %s\n",
			ibv_get_device_name(ib_dev));
		goto clean_buffer;
	}

初始化事件通道

	if (use_event) {
		ctx->channel = ibv_create_comp_channel(ctx->context);
		if (!ctx->channel) {
			fprintf(stderr, "Couldn't create completion channel\n");
			goto clean_device;
		}
	} else
		ctx->channel = NULL;

初始化保护域(protection domain)

/*
* QP,AH,MR和SRQ等组件通常只能和与自己同类的组件进行交互,而保护域就是负责进行不同类的组件之间的交互。
* PD可以将QP和MR以及MW进行绑定,以此使得网卡可以访问主机内存,并对这一过程进行控制
* PD还能再UD传输中将QP与AH进行绑定,以此控制对于UD传输目标的访问。
*/
	ctx->pd = ibv_alloc_pd(ctx->context);
	if (!ctx->pd) {
		fprintf(stderr, "Couldn't allocate PD\n");
		goto clean_comp_channel;
	}

向保护域中注册缓存区

	ctx->mr = ibv_reg_mr(ctx->pd, ctx->buf, size + 40, IBV_ACCESS_LOCAL_WRITE);
	if (!ctx->mr) {
		fprintf(stderr, "Couldn't register MR\n");
		goto clean_pd;
	}

创建完成队列

	ctx->cq = ibv_create_cq(ctx->context, rx_depth + 1, NULL,ctx->channel, 0);
	if (!ctx->cq) {
		fprintf(stderr, "Couldn't create CQ\n");
		goto clean_mr;
	}

创建QP

	{
		struct ibv_qp_init_attr attr = {
			.send_cq = ctx->cq, //指定发送任务的完成队列
			.recv_cq = ctx->cq, //指定接收任务的完成队列
			.cap     = { //QP属性设置
				.max_send_wr  = 1, // 发送队列中未完成任务的最大数量
				.max_recv_wr  = rx_depth, //接收队列中未完成任务的最大数量
				.max_send_sge = 1, //发送队列中,一个WR中包含的散列表SGE的最大数量
				.max_recv_sge = 1  //接收队列中,一个WR中包含的散列表SGE的最大数量
			},
			.qp_type = IBV_QPT_RC,//QP类型
		}; //创建QP所需的参数

		ctx->qp = ibv_create_qp(ctx->pd, &attr);
		if (!ctx->qp)  {
			fprintf(stderr, "Couldn't create QP\n");
			goto clean_cq;
		}
	}

修改qp状态从REST到INIT(端口与QP绑定)

  • 一旦QP转换为INIT状态,用户就可以开始将接收缓冲区发布到接收队列
	{
		struct ibv_qp_attr attr = {
			.qp_state        = IBV_QPS_INIT,
			.pkey_index      = 0, //pkey 索引
			.port_num        = port, //指定QP端口号
			.qkey            = 0x11111111 //QP的编号?  //非连接传输状态专用
		}; //qp属性修改参数

		if (ibv_modify_qp(ctx->qp, &attr,
				  IBV_QP_STATE              |
				  IBV_QP_PKEY_INDEX         |
				  IBV_QP_PORT               |
				  IBV_QP_QKEY)) {
			fprintf(stderr, "Failed to modify QP to INIT\n");
			goto clean_qp;
		}
	}
2.2.3 接收队列登记任务WR------pp_post_recv
static int pp_post_recv(struct pingpong_context *ctx, int n)//n = ctx->rx_depth接收队列深度

向接收队列登记WR,用于指定接收任务的缓存区域

//散列表	
	struct ibv_sge list = {
		.addr	= (uintptr_t) ctx->buf, //申请的缓存区域
		.length = ctx->size + 40, //缓存大小
		.lkey	= ctx->mr->lkey //ibv_reg_mr中获取的mr
	};
//任务请求wr	
	struct ibv_recv_wr wr = {
		.wr_id	    = PINGPONG_RECV_WRID, //用户分配的work request ID
		.sg_list    = &list, //WR的散列表
		.num_sge    = 1, //sg_list的条目数
	};
//ibv_post_recv()动词
	struct ibv_recv_wr *bad_wr; //用于存储出错任务请求
	int i;

	for (i = 0; i < n; ++i)
		if (ibv_post_recv(ctx->qp, &wr, &bad_wr))
			break;

	return i;
2.2.4 事件—ibv_req_notify_cq
//ibv_req_notify_cq 为指定完成队列(CQ)配置通知机制
if (use_event)
		if (ibv_req_notify_cq(ctx->cq, 0)) {
			fprintf(stderr, "Couldn't request CQ notification\n");
			return 1;
		}
//函数原型
int ibv_req_notify_cq(struct ibv_cq *cq, int solicited_only)
//cq ibv_create_cq 创建的完成队列
//solicited_only  只有当WR被标记为solicited时才进行通知
//用户使用ibv_get_cq_event来接收提醒
//通知机制将只对一个通知进行防护。一旦发出通知,该机制必须通过ibv_req_notify_cq对的新调用重新配置。

当完成队列条目(CQE)被放置在CQ上时,完成事件将被发送到与CQ相关联的完成信道(CC)。

2.2.5 获取端口信息----pp_get_port_info
//获取端口信息
//ib_port用户指定ib设备端口,默认为1
//&ctx->portinfo 存储端口信息
	if (pp_get_port_info(ctx->context, ib_port, &ctx->portinfo)) {
		fprintf(stderr, "Couldn't get port info\n");
		return 1;
	}
//函数原型 ibv_query_port检索与端口关联的各种属性
//context:struct ibv_context from ibv_open_device
int pp_get_port_info(struct ibv_context *context, int port,
		     struct ibv_port_attr *attr)
{
	return ibv_query_port(context, port, attr);
}
struct ibv_port_attr
{
enum ibv_port_state state;//端口状态
enum ibv_mtu max_mtu; //端口支持的最大传输单元MTU
enum ibv_mtu active_mtu; //当前的MTU
int gid_tbl_len; //GID表格的长度(source global id)
uint32_t port_cap_flags; //端口支持的容量大小
uint32_t max_msg_sz; //最大信息长度
uint32_t bad_pkey_cntr; //Bad P_key counter
uint32_t qkey_viol_cntr; //Q_Key violation counter
uint16_t pkey_tbl_len; //分区表长度
uint16_t lid; //该端口被分配的第一个LID
uint16_t sm_lid; //subet manager子网管理的LID
uint8_t lmc; //LID mask control 当多个LID被分配给当前端口时使用
uint8_t max_vl_num; //最大虚拟线路
uint8_t sm_sl; //SM服务等级SL
uint8_t subnet_timeout; //子网传播延时
uint8_t init_type_reply; //SM的初始化方式
uint8_t active_width; //当前带宽
uint8_t active_speed; //当前速度
uint8_t phys_state; //物理端口状态
};
2.2.6 获取GID属性

GID:用于标识网络适配器上的端口、路由器上的端口或多播组的128位标识符。
GID是一个有效的128位IPv6地址(根据RFC 2373),具有IBA中定义的附加属性限制,以促进高效的发现、通信和路由。

	if (gidx >= 0) {
		if (ibv_query_gid(ctx->context, ib_port, gidx, &my_dest.gid)) {
			fprintf(stderr, "Could not get local gid for gid index "
								"%d\n", gidx);
			return 1;
		}
	} else
		memset(&my_dest.gid, 0, sizeof my_dest.gid);

//从设备的GID表格中获取指定序号的GID
//context 设备
//port_num 物理端口号(1是第一个端口)
//index 返回GID表格中的第几个条目(0为第一个)
int ibv_query_gid(struct ibv_context *context, uint8_t port_num, int index, union ibv_gid *gid)
//gid数据结构
//GID是由GUID(全局唯一标识符)和子网分配的前缀组成
union ibv_gid
{
	uint8_t raw[16];
	struct
	{
		uint64_t subnet_prefix;
		uint64_t interface_id;
	} global;
}
2.2.7 建立连接(核心)----pp_connetct_ctx
/*建立双方QP之间的连接
* ctx: 设备
* port: ib_port 设备端口号
* my_psn: packet sequence number 接收队列起始包序号
* sl: service level 服务等级
* dest:
* sgid_idx: 
*/
static int pp_connect_ctx(struct pingpong_context *ctx, int port, int my_psn,
			  int sl, struct pingpong_dest *dest, int sgid_idx)

QP从INIT状态到RTR状态

当QP绑定了相应的缓存,它就能从INIT状态转换为准备接受(RTR-Ready to receive)状态。

	struct ibv_qp_attr attr = {
		.qp_state		= IBV_QPS_RTR //状态转换标志
	};

	if (ibv_modify_qp(ctx->qp, &attr, IBV_QP_STATE)) {
		fprintf(stderr, "Failed to modify QP to RTR\n");
		return 1;
	}

从这一阶段开始QP就能开始接收工作。

QP从RTR状态到RTS状态

当QP计入RTR状态后,就能够转换为RTS(Ready to send)状态

	attr.qp_state	    = IBV_QPS_RTS;//状态转换标志
	attr.sq_psn	    = my_psn; //send queue starting packet sequence number发送队列起始包序号,应该与远端QP的接收队列起始包序号rq_psn相同

	if (ibv_modify_qp(ctx->qp, &attr,
			  IBV_QP_STATE              |
			  IBV_QP_SQ_PSN)) {
		fprintf(stderr, "Failed to modify QP to RTS\n");
		return 1;
	}

从这一段开始,QP就能够开始发送操作并且已经启用全部功能。用户可以通过ibv_post_send向其发送传输请求了。

创建AH(Address handler)

AH包含了数据到达远端目标所需的全部信息。在面向连接的传输方式(RC,UC)中,AH与QP相绑定。在面向数据报传输(UD)方式中,AH与工作请求(WR)绑定

//设置AH属性
	struct ibv_ah_attr ah_attr = {
		.is_global     = 0,
		.dlid          = dest->lid,
		.sl            = sl,
		.src_path_bits = 0,
		.port_num      = port
	};
	if (dest->gid.global.interface_id) {
		ah_attr.is_global = 1;
		ah_attr.grh.hop_limit = 1;
		ah_attr.grh.dgid = dest->gid;
		ah_attr.grh.sgid_index = sgid_idx;
	}
//创建AH
	ctx->ah = ibv_create_ah(ctx->pd, &ah_attr);
	if (!ctx->ah) {
		fprintf(stderr, "Failed to create AH\n");
		return 1;
	}

	return 0;
}
//函数原型
// pd: 保护域,ibv_pd,由ibv_alloc_pd生成
// attr: AH参数
struct ibv_ah *ibv_create_ah(struct ibv_pd *pd, struct ibv_ah_attr *attr)
//AH参数数据结构
struct ibv_ah_attr
{
	struct ibv_global_route grh; //全局路由,详情见下
	uint16_t dlid; //目标lid
	uint8_t sl; //服务等级
	uint8_t src_path_bits; //source path bits 源路径位?
	uint8_t static_rate; //静态速度
	uint8_t is_global; //这是一个全局地址,使用grh
	uint8_t port_num; //为了到达当前地址所使用的物理端口号
};
struct ibv_global_route
{
	union ibv_gid dgid; //目标gid
	uint32_t flow_label;//流表格
	uint8_t sgid_index; //源gid的序号
	uint8_t hop_limit; //hop limit
	uint8_t traffic_class; //traffic class
};
2.2.8 发送队列登记任务-pp_post_send
static int pp_post_send(struct pingpong_context *ctx, uint32_t qpn)

ibv_post_send将WR列表登记到发送队列上。这一操作将初始化所有数据传输,包括RDMA操作。当出现第一个错误时,对于WR列表的处理将会停止,指向发生错误的WR的指针将会在参数bad_wr中返回

struct ibv_sge list = {
	.addr	= (uintptr_t) ctx->buf + 40,
	.length = ctx->size,
	.lkey	= ctx->mr->lkey
};
struct ibv_send_wr wr = {
	.wr_id	    = PINGPONG_SEND_WRID,
	.sg_list    = &list, //散列表
	.num_sge    = 1, //散列表的条目数
	.opcode     = IBV_WR_SEND, //send操作
	.send_flags = IBV_SEND_SIGNALED, //发送此WR的完成事件。仅对QP设置sq_sig_all为0的QP有用
	.wr         = {
		.ud = {
			 .ah          = ctx->ah,
			 .remote_qpn  = qpn,
			 .remote_qkey = 0x11111111
		 }
	}
};
struct ibv_send_wr *bad_wr;

return ibv_post_send(ctx->qp, &wr, &bad_wr);
//ibv_send_wr数据结构
struct ibv_send_wr
{
	uint64_t wr_id; //用户分配的工作请求wr ID
	struct ibv_send_wr *next; //指向下一个WR的指针,最后一个为NULL
	struct ibv_sge *sg_list; //当前WR的散列表
	int num_sge; //sg_list散列表的条目数
	enum ibv_wr_opcode opcode;//操作编号 如IBV_WR_RDMA_WRITE,IBV_WR_SEND 等
	enum ibv_send_flags send_flags;//(可选)-这是标志的按位或。如IBV_SEND_FENCE等
	uint32_t imm_data;/* network byte order */ //以网络字节顺序发送的即时数据
	union
	{
		struct
		{
			uint64_t remote_addr;//rdma/atomic操作时使用的远端虚拟地址
			uint32_t rkey; //remote key(远端使用ibv_reg_mr获取),rdma/atomic操作使用
		} rdma;
		struct
		{
			uint64_t remote_addr;//同上
			uint64_t compare_add;//用于比较和交换操作的比较数据
			uint64_t swap;//交换数据
			uint32_t rkey;//同上
		} atomic;
		struct
		{
			struct ibv_ah *ah;//数据包操作使用的ah
			uint32_t remote_qpn;//远端qp编号
			uint32_t remote_qkey;//qkey
		} ud;
	} wr;
	uint32_t xrc_remote_srq_num; //shared receive queue (SRQ) number 仅用于XRC
};
2.2.9 获取完成事件
// channel: ibv_comp-channel 由ibv_create_comp_channel创建
// cq : 指向与事件关联的完成队列的指针
// cq_context :用户提供的上下文,由ibv_create_cq设置
if (ibv_get_cq_event(ctx->channel, &ev_cq, &ev_ctx)) {
	fprintf(stderr, "Failed to get cq_event\n");
	return 1;
}

ibv_get_cq_event会等待特定完成通道(CC completion channel)的消息提醒。这是一个阻塞操作,用户应当分配一个ibv_cq和一个void给这个函数。他们会被返回适当的值,用户应当负责释放这些指针。

每一个消息提醒都必须通过ibv_ack_cq-event进行确认。由于ibv_destory_cq操作会等待所有事件被确认才执行。

当一个特定完成队列CQ的消息提醒被发送到了CC中,那么这个完成队列CQ就解除了和CC的绑定关系,知道再次通过ibv_req_notify_cq绑定,CQ都不会再次发送消息提醒。

这个操作仅仅是通知一个CQ有一个完成队列条目(CQE)要处理,它并不会自己去处理这些CQE。用户需要通过ibv_poll_cq来进行相关处理。

2.2.10 轮询完成队列
/*
* 从完成队列CQE中检索CQE
* @param cq: 结构ibv_cq,由ibv_create_cq创建 
* @param num_entries: 返回的完成队列最大条目
* @return wc: 完成条目数组
* @return int:返回条目数量
*/
int ibv_poll_cq(struct ibv_cq *cq, int num_entries, struct ibv_wc *wc)

必须定期对CQ进行轮询,以防止超支。如果发生超支,则CQ将会被关闭并发送异步事件IBV_EVENT_CQ_ERR。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值