百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

Rust实现RDMA异步编程(一):基于epoll实现RDMA 异步操作

sinye56 2025-01-24 13:52 26 浏览 0 评论

RDMA是一套高性能网络协议栈,多用于高性能计算、高性能存储领域。RDMA的library是用C实现的,但是没有很好用的Rust的binding,不方便Rust开发者使用。于是我们正在封装一层符合Rust风格、便于Rust开发者使用的RDMA Rust binding。特别的,异步编程是近几年很受关注的编程方式,用Rust异步编程来实现IO操作,可以避免操作系统的进程上下文切换,提高性能,而且Rust的异步编程框架也在逐步成熟和完善。本系列文章探讨下如何用异步的方式实现RDMA的操作。本文先讨论下如何基于Linux的epoll机制实现RDMA异步操作,后续的文章再探讨如何用Rust异步编程来实现RDMA异步操作。

RDMA操作简介

RDMA的编程模型是基于消息的方式来实现网络传输,并且用队列来管理待发送的消息和接收到的消息。RDMA的网络传输相关操作基本上都是跟队列相关的操作:比如把要发送的消息放入发送队列,消息发送完成后在完成队列里放一个发送完成消息,以供用户程序查询消息发送状态;再比如接收队列里收到消息,也要在完成队列里放个接收完成消息,以供用户程序查询有新消息要处理。

由上面的描述可以看出RDMA的队列分为几种:发送队列Send Queue (SQ),接收队列Receive Queue(RQ),和完成队列Completion Queue (CQ)。其中SQ和RQ统称工作队列Work Queue (WQ),也称为Queue Pair (QP)。此外,RDMA提供了两个接口,ibv_post_sendibv_post_recv,由用户程序调用,分别用于发送消息和接收消息:

  • 用户程序调用ibv_post_send把发送请求Send Request (SR)插入SQ,成为发送队列的一个新的元素Send Queue Element (SQE);
  • 用户程序调用ibv_post_recv把接收请求Receive Request (RR)插入RQ,成为接收队列的一个新元素Receive Queue Element (RQE)。

SQE和RQE也统称工作队列元素Work Queue Element (WQE)。

当SQ里有消息发送完成,或RQ有接收到新消息,RDMA会在CQ里放入一个新的完成队列元素Completion Queue Element (CQE),用以通知用户程序。用户程序有两种同步的方式来查询CQ:

  • 用户程序调用ibv_cq_poll来轮询CQ,一旦有新的CQE就可以及时得到通知,但是这种轮询方式很消耗CPU资源;
  • 用户程序在创建CQ的时候,指定一个完成事件通道ibv_comp_channel,然后调用ibv_get_cq_event接口等待该完成事件通道来通知有新的CQE,如果没有新的CQE,则调用ibv_get_cq_event时发生阻塞,这种方法比轮询要节省CPU资源,但是阻塞会降低程序性能。

关于RDMA的CQE,有个需要注意的地方:对于RDMA的Send和Receive这种双边操作,发送端在发送完成后能收到CQE,接收端在接收完成后也能收到CQE;对于RDMA的Read和Write这种单边操作,比如节点A从节点B读数据,或节点A往节点B写数据,只有发起Read和Write操作的一端,即节点A在操作结束后能收到CQE,另一端节点B完全不会感知到节点A发起的Read或Write操作,节点B也不会收到CQE。

Linuxepoll异步机制简介

Linux的epoll机制是Linux提供的异步编程机制。epoll专门用于处理有大量IO操作请求的场景,检查哪些IO操作就绪,使得用户程序不必阻塞在未就绪IO操作上,而只处理就绪IO操作。epoll比Linux之前的selectpoll这两种异步机制要强大,epoll特别适合有大量IO操作的场景,比如RDMA的场景,每个RDMA节点同时有很多队列,用于大量传输数据,那么就可以用epoll来查询每个CQ,及时获得RDMA消息的发送和接收情况,同时避免同步方式查询CQ的缺点,要么用户程序消耗大量CPU资源,要么用户程序被阻塞。

Linux的epoll机制提供了三个API接口:

  • epoll_create用于创建epoll实例,返回epoll实例的句柄;
  • epoll_ctl用于给epoll实例增加、修改、删除待检查的IO操作事件;
  • epoll_wait用于检查每个通过epoll_ctl注册到epoll实例的IO操作,看每个IO操作是否就绪/有期望的事件发生。

具体的epoll这三个接口的使用,后面结合代码示例来讲解。这里先解释下epoll的IO事件检查规则。如下图所示,epoll有两种检查规则:边沿触发Edge Trigger (ET),和电平触发Level Trigger (LT)。边沿触发和电平触发源自信号处理领域。边沿触发指信号一发生变化就触发事件,比如从0变到1就触发事件、或者从1到0就触发事件;电平触发指只要信号的状态处于特定状态就触发事件,比如高电平就一直触发事件,而低电平不触发事件。

对应到epoll,电平触发指的是,只要IO操作处于特定的状态,就会一直通知用户程序。比如当socket有数据可读时,用户程序调用epoll_wait查询到该socket有收到数据,只要用户程序没有把该socket上次收到的数据读完,每次调用epoll_wait都会通知用户程序该socket有数据可读;即当socket处于有数据可读的状态,就会一直通知用户程序。而epoll的边沿触发指的是epoll只会在IO操作的特定事件发生后通知一次。比如socket有收到数据,用户程序epoll_wait查询到该socket有数据可读,不管用户程序有没有读完该socket这次收到的数据,用户程序下次调用epoll_wait都不会再通知该socket有数据可读,除非这个socket再次收到了新的数据;即仅当socket每次收到新数据才通知用户程序,并不关心socket当前是否有数据可读。

RDMA完成队列CQ读取CQE的同步和异步方法

本节用RDMA读取CQ的操作为例展示如何基于epoll实现异步操作。先介绍下RDMA用轮询和阻塞的方式读取CQ,再介绍基于epoll的异步读取CQ的方法。下文的代码仅作为示例,并不能编译通过。

RDMA轮询方式读取CQE

RDMA轮询方式读取CQ非常简单,就是不停调用ibv_poll_cq来读取CQ里的CQE。这种方式能够最快获得新的CQE,直接用户程序轮询CQ,而且也不需要内核参与,但是缺点也很明显,用户程序轮询消耗大量CPU资源。

loop {
    // 尝试读取一个CQE
    poll_result = ibv_poll_cq(cq, 1, &mut cqe);
    if poll_result != 0 {
        // 处理CQE
    }
}

RDMA完成事件通道方式读取CQE

RDMA用完成事件通道读取CQE的方式如下:

  • 用户程序通过调用ibv_create_comp_channel创建完成事件通道;
  • 接着在调用ibv_create_cq创建CQ时关联该完成事件通道;
  • 再通过调用ibv_req_notify_cq来告诉CQ当有新的CQE产生时从完成事件通道来通知用户程序;
  • 然后通过调用ibv_get_cq_event查询该完成事件通道,没有新的CQE时阻塞,有新的CQE时返回;
  • 接下来用户程序从ibv_get_cq_event返回之后,还要再调用ibv_poll_cq从CQ里读取新的CQE,此时调用ibv_poll_cq一次就好,不需要轮询。

RDMA用完成事件通道读取CQE的代码示例如下:

// 创建完成事件通道
let completion_event_channel = ibv_create_comp_channel(...);
// 创建完成队列,并关联完成事件通道
let cq = ibv_create_cq(completion_event_channel, ...);

loop {
    // 设置CQ从完成事件通道来通知下一个新CQE的产生
    ibv_req_notify_cq(cq, ...);
    // 通过完成事件通道查询CQ,有新的CQE就返回,没有新的CQE则阻塞
    ibv_get_cq_event(completion_event_channel, &mut cq, ...);
    // 读取一个CQE
    poll_result = ibv_poll_cq(cq, 1, &mut cqe);
    if poll_result != 0 {
        // 处理CQE
    }
    // 确认一个CQE
    ibv_ack_cq_events(cq, 1);
}

用RDMA完成事件通道的方式来读取CQE,本质是RDMA通过内核来通知用户程序CQ里有新的CQE。事件队列是通过一个设备文件,/dev/infiniband/uverbs0(如果有多个RDMA网卡,则每个网卡对应一个设备文件,序号从0开始递增),来让内核通过该设备文件通知用户程序有事件发生。用户程序调用ibv_create_comp_channel创建完成事件通道,其实就是打开上述设备文件;用户程序调用ibv_get_cq_event查询该完成事件通道,其实就是读取打开的设备文件。但是这个设备文件只用于做事件通知,通知用户程序有新的CQE可读,但并不能通过该设备文件读取CQE,用户程序还要是调用ibv_poll_cq来从CQ读取CQE。

用完成事件通道的方式来读取CQE,比轮询的方法要节省CPU资源,但是调用ibv_get_cq_event读取完成事件通道会发生阻塞,进而影响用户程序性能。

基于epoll异步读取CQE

上面提到用RDMA完成事件通道的方式来读取CQE,本质是用户程序通过事件队列打开/dev/infiniband/uverbs0设备文件,并读取内核产生的关于新CQE的事件通知。从完成事件通道ibv_comp_channel的定义可以看出,里面包含了一个Linux文件描述符,指向打开的设备文件:

pub struct ibv_comp_channel {
    ...
    pub fd: RawFd,
    ...
}

于是可以借助epoll机制来检查该设备文件是否有新的事件产生,避免用户程序调用ibv_get_cq_event读取完成事件通道时(即读取该设备文件时)发生阻塞。

首先,用fcntl来修改完成事件通道里设备文件描述符的IO方式为非阻塞:

// 创建完成事件通道
let completion_event_channel = ibv_create_comp_channel(...);
// 创建完成队列,并关联完成事件通道
let cq = ibv_create_cq(completion_event_channel, ...);
// 获取设备文件描述符当前打开方式
let flags =
    libc::fcntl((*completion_event_channel).fd, libc::F_GETFL);
// 为设备文件描述符增加非阻塞IO方式
libc::fcntl(
    (*completion_event_channel).fd,
    libc::F_SETFL,
    flags | libc::O_NONBLOCK
);

接着,创建epoll实例,并把要检查的事件注册给epoll实例:

use nix::sys::epoll;

// 创建epoll实例
let epoll_fd = epoll::epoll_create()?;
// 完成事件通道里的设备文件描述符
let channel_dev_fd = (*completion_event_channel).fd;
// 创建epoll事件实例,并关联设备文件描述符,
// 当该设备文件有新数据可读时,用边沿触发的方式通知用户程序
let mut epoll_ev = epoll::EpollEvent::new(
    epoll::EpollFlags::EPOLLIN | epoll::EpollFlags::EPOLLET,
    channel_dev_fd
);
// 把创建好的epoll事件实例,注册到之前创建的epoll实例
epoll::epoll_ctl(
    epoll_fd,
    epoll::EpollOp::EpollCtlAdd,
    channel_dev_fd,
    &mut epoll_ev,
)

上面代码有两个注意的地方:

  • EPOLLIN指的是要检查设备文件是否有新数据/事件可读;
  • EPOLLET指的是epoll用边沿触发的方式来通知。

然后,循环调用epoll_wait检查设备文件是否有新数据可读,有新数据可读说明有新的CQE产生,再调用ibv_poll_cq来读取CQE:

let timeout_ms = 10;
// 创建用于epoll_wait检查的事件列表
let mut event_list = [epoll_ev];
loop {
    // 设置CQ从完成事件通道来通知下一个新CQE的产生
    ibv_req_notify_cq(cq, ...);
    // 调用epoll_wait检查是否有期望的事件发生
    let nfds = epoll::epoll_wait(epoll_fd, &mut event_list, timeout_ms)?;
    // 有期望的事件发生
    if nfds > 0 {
        // 通过完成事件通道查询CQ,有新的CQE就返回,没有新的CQE则阻塞
        ibv_get_cq_event(completion_event_channel, &mut cq, ...);
        // 循环读取CQE,直到CQ读空
        loop {
            // 读取一个CQE
            poll_result = ibv_poll_cq(cq, 1, &mut cqe);
            if poll_result != 0 {
                // 处理CQE
                ...
                // 确认一个CQE
                ibv_ack_cq_events(cq, 1);
            } else {
                break;
            }
        }
    }
}

上面代码有个要注意的地方,因为epoll是用边沿触发,所以每次有新CQE产生时,都要调用ibv_poll_cq把CQ队列读空。考虑如下场景,同时有多个新的CQE产生,但是epoll边沿触发只通知一次,如果用户程序收到通知后没有读空CQ,那epoll也不会再产生新的通知,除非再有新的CQE产生,epoll才会再次通知用户程序。

总之,本文用epoll机制实现RDMA异步读取CQE的例子,展示了如何实现RDMA的异步操作。RDMA还有类似的操作,都可以基于epoll机制实现异步操作。

对Rust和RDMA感兴趣的朋友,可以关注我们的开源项目
https://github.com/datenlord/async-rdma/


作者 | 王璞

后期编辑 | 张汉东

转自《Rust Magazine中文精选》

相关推荐

6个接私活的网站,你有技术就有钱

如果觉得有帮助,还请大家帮忙多多转发,点个关注作者:发哥链接:GitHubDaily本篇文章会向大家推荐国内外几个接外包比较靠谱的平台,主旨是贵精不贵多。因此,像「猪x戒」这种会让程序员自贬身价,扰乱...

Java开源可商用的CMS建站系统_java建站源码

Java研发的CMS内容管理系统具有许多优势和特点,包括以下几个方面:跨平台性:Java是一种跨平台的编程语言,可以在不同的操作系统上运行,包括Windows、Linux、Mac等。这意味着Java...

SEO新手建站必看"干货"优质空间和功能选择技巧!

一.空间的分类服务器:远程的高级大型计算机。vps:虚拟服务器。虚拟空间:也称虚拟主机云主机:是在一组集群主机上虚拟出多个类似独立主机的部分,集群中每个主机上都有云主机的一个镜像,从而大大提高了虚拟主...

千字长文教你使用 宝塔面板 快速搭建网站

本文将教大家使用宝塔面板快速搭建网站,云服务器购买以及域名注册部分请自行上网搜索了解,亦可留言联系小编进行咨询。如果是和下方一样本地搭建演示的话,则不需要付费购买域名和主机。宝塔面板的是...

BlueHost香港虚拟主机建站的5个优点

应该是从2006年左右开始,如果我们建站选择国内的主机需要备案手续,而且比较繁琐,且根据各地的不同政策还需要到接入点拍照登记个人信息等,一来比较繁琐,二来我们担心万一网站可能存在的信息问题导致不必要的...

10款好用的Linux服务器网站管理面板推荐

如今在建站时,很多人都会使用管理面板来辅助建站,因为相对于手动安装软件,面板更加简单而且高效,即使新手也能很快学会搭建网站,在本文中我们来推荐几款好用的网站管理面板宝塔面板宝塔面板是一款简单好用的网站...

小白拥有一台云服务器到底能干些什么?成就感爆棚的简单方案!

?云服务器是什么?云服务器(比如阿里云、腾讯云等)是提供给用户的一种虚拟服务器资源,你可以把它看作一台“rent的电脑”,只需要支付少量费用就可以拥有一个功能强大的网络设备。对于小白来说,拥有一...

苹果CMS,苹果CMS采集插件,苹果CMS快速建站(图文教程)

苹果CMS,有着强大的管理功能,管理后台界面大方、操作简单、功能齐全、模块众多、双端管理。苹果CMS加上丰富的系统标签,系统内置了丰富的cms标签并支持thinkphp框架标签完美融合,可以调取系统内...

新手搭建网站、小程序、APP等系统,如何选择服务器?

今天和小蔡和大家说说,新手搭建网站,如何选择服务器?废话不多说,直接来干货。服务器是存放网站源代码的容器,也是运行网站程序的工具,所以是不可或缺的。新手刚接触搭建网站,若不知道怎么去选择一台适合自己...

在海外VPS服务器(Hostinger)上配置宝塔面板的操作步骤

不得不说,宝塔面板是真的好用啊~用上就放不下了,一些海外的免费开源的服务器集成面板(比如CloudPanel)我也用了,不喜欢,真的不如宝塔面板方便耐用。今天聊一下在海外服务器(也包括国内服务器,没有...

干货盘点:每个wordpress站长都推荐完成的60个任务清单

构建和运营wordpress网站包含了很多重要任务,遗漏哪一方面都可能造成或大或小的不良后果,因此我们特别整理了这个任务清单,为你查漏补缺,希望能对您现在运营或者即将开始构建的wordpress网站有...

为什么站长喜欢选择BlueHost主机建站

BlueHost正式成立于2003年,从事主机(虚拟主机)业务至今已经将近十余年,无论从口碑还是用户的评价,我们基本很少看到关于Bluehost主机产品和商家负面的评论信息。从2014年开始,Blue...

自助建站时代来临 半小时成建站达人

“H5”意为第五代HTML,即第五代网页编写语言。自从1991年第一代HTML开始研发以来,网页编写、网站建设一直都属于高端技术行业,网站建设人员都是一些专业型人才,这也意味着网站的建设和维护都需要不...

现代化、开源的 Linux 服务器运维管理面板

1Panel是一个现代化、开源的Linux服务器运维管理面板。1Panel的功能和优势包括:快速建站:深度集成Wordpress和Halo,域名绑定、SSL证书配置等一键搞定;高效管理...

[1Panel]开源,现代化,新一代的 Linux 服务器运维管理面板

测评介绍本期测评试用一下1Panel这款面板。1Panel是国内飞致云旗下开源产品。整个界面简洁清爽,后端使用GO开发,前端使用VUE的Element-Plus作为UI框架,整个面板的管理都是基于do...

取消回复欢迎 发表评论: