io_uring、liburing

1、概述

io_uring是linux内核中高效的异步I/O框架,Linux内核5.1引入,用于提高I/O的性能。liburing是基于io_uring封装的库,提供更简洁的api。
 

2、工作机制

1、应用程序将I/O请求提交到提交队列中,每个请求可以理解为提交队列中的一个节点(SQE)。

2、内核处理。

3、当I/O操作完成,内核将结果放到完成队列中,每个结果为完成队列中的一个节点(CQE)。

异步操作:
io_uring允许用户将I/O操作提交给内核,内核在后台异步处理这些操作。用户不需要等待操作完成,而是可以在稍后查询完成队列以获取操作结果。

举例:

把recv任务抛到到提交到队列中,由内核worker从请求队列中取任务执行任务,把recv结果放到完成队列中,应用程序从完成队列中取结果。
 

应用程序要做的就是往请求队列中抛任务,从完成队列中取结果。

原理图:

3、思考问题

3.1、io_uring如何避免频繁的拷贝?

问题描述:每个任务频繁添加到请求队列,这里会有频繁的拷贝过程?

用户空间和内核之间共享一块内存区域,用于传递提交的I/O请求和完成的I/O请求事件,使用了mmap映射内存来避免频繁拷贝,所以不需要拷贝。

3.2、提交队列和完成队列如何做到线程安全?

对于io_uring的队列来说,多线程操作不需要加锁,io_uring使用了无锁环形队列,支持多个线程可以安全且高效的并发处理I/O请求。

4、核心函数

io_uring提供了3个系统函数,io_uring_setup,io_uring_enter,io_uring_register。liburing对其封装了一层。liburing主要函数介绍:
io_uring_queue_init_params
初始化io_uring环境,包括初始化提交队列(SQ)和完成队列(CQ),内部会调用io_uring_setup

io_uring_prep_*系列函数

io_uring_prep_readio_uring_prep_writeio_uring_prep_acceptio_uring_prep_send 等,将I/O操作放到提交队列中

io_uring_submit

将提交队列中的操作提交给内核,触发内核执行操作,内部依赖io_uring_enter系统调用

io_uring_wait_cqe
阻塞等待至少一个操作完成,并返回完成的CQE,这一步是阻塞的

io_uring_peek_batch_cqe

批量获取完成队列中的操作结果, 返回值表示已经完成的操作数量

io_uring_cq_advance

这个函数通知io_uring,应用程序已经处理完这些事件,可以从完成队列中释放了

5、io_uring和epoll区别

  io_uring只是个异步I/O框架,可以处理网络通信(socket),也可以处理文件操作(读写文件)等      epoll专门用于处理网络通信(socket)

   io_uring从完成队列中获取结果,内核已经把操作完成了。比如read操作,从完成队列获取结果时,数据已经读取到buf中了。
   epoll发生事件时,具体的I/O操作仍需用户代码完成,比如当有EPOLLIN事件时,程序需要调用read函数,把数据读到buf中。

io_uring可以批量提交多个I/O操作,然后一次性等待他们的完成,大大的减少了系统调用的数量。

io_uring利用共享内存在用户空间和内核空间传递数据,减少了频繁的拷贝。
epoll使用时需要多次系统调用,例如:epoll_ctrl注册或修改文件描述符事件
epoll每次等待事件,都需要从用户空间切换到内核空间。

6、示例代码,使用io_uring实现tcpserver(代码详细注释)

1、首先安装liburing
sudo apt-get install liburing-dev

2、编译代码
gcc  -o io_uring_server io_uring.c

#include <stdio.h>
#include <liburing.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define QUEUE_LENGTH   1024
#define BUFFER_LENGTH  1024
#define LISTEN_BACKLOG 50

#define EVENT_ACCEPT 0
#define EVENT_RECV   1
#define EVENT_SEND   2

struct conn_info
{
    int fd;
    int event;
};

// 初始化服务器
int init_server(unsigned short port) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0
    servaddr.sin_port = htons(port); // 0-1023 系统默认端口

    // 绑定
    if(-1 == bind(sockfd, (struct  sockaddr*)&servaddr, sizeof(servaddr)))
    {
        printf("bind error\n");
        return -1;
    }

    printf("bind success\n");

    // 监听
    if(-1 == listen(sockfd, LISTEN_BACKLOG))
    {
        printf("listen error\n");
        return -1;
    }
    printf("listen success sockfd:%d\n", sockfd);
    return sockfd;
}

int set_event_accept(struct io_uring* ring, int sockfd, struct sockaddr* addr, socklen_t* addrlen, int flags)
{
    // 将accept操作细节填写到提交队列条目(SQE)中
    struct io_uring_sqe* sqe = io_uring_get_sqe(ring);
    io_uring_prep_accept(sqe, sockfd, addr, addrlen, flags);

    struct conn_info accept_info = {
        .fd = sockfd,
        .event = EVENT_ACCEPT,
    };

    memcpy(&sqe->user_data, &accept_info, sizeof(accept_info));
}

int set_event_recv(struct io_uring* ring, int fd, void* buf, size_t count, int flags)
{
    // 将read操作细节填写到提交队列条目(SQE)中
    struct io_uring_sqe* sqe = io_uring_get_sqe(ring);
    io_uring_prep_read(sqe, fd, buf, count, flags);

    struct conn_info recv_info = {
        .fd = fd,
        .event = EVENT_RECV,
    };
    memcpy(&sqe->user_data, &recv_info, sizeof(recv_info));
}

int set_event_send(struct io_uring* ring, int fd, void* buf, size_t count, int flags)
{
    // 将send操作细节填写到提交队列条目(SQE)中
    struct io_uring_sqe* sqe = io_uring_get_sqe(ring);
    io_uring_prep_send(sqe, fd, buf, count, flags);

    struct conn_info send_info = {
        .fd = fd,
        .event = EVENT_SEND,
    };
    memcpy(&sqe->user_data, &send_info, sizeof(send_info));
}

int main()
{
    // 初始化服务器
    unsigned short port = 2000;
    int listen_fd = init_server(port);
    if (listen_fd < 0)
    {
        printf("init server error\n");
        return -1;
    }

    struct io_uring ring;
    struct io_uring_params ring_params;
    io_uring_queue_init_params(QUEUE_LENGTH, &ring, &ring_params);
#if 0
    struct sockaddr_in clientaddr;
    socklen_t clientlen = sizeof(clientaddr);
    accept(listen_fd, (struct sockaddr*)&clientaddr, &clientlen);
#else
    // 设置事件
    struct sockaddr_in clientaddr;
    socklen_t clientlen = sizeof(clientaddr);
    set_event_accept(&ring, listen_fd, (struct sockaddr*)&clientaddr, &clientlen, 0);
#endif

    char buf[BUFFER_LENGTH] = {0};
    while(1)
    {
        // 将准备好的SQE提交给内核,触发内核执行操作,内部依赖io_uring_enter系统调用
        io_uring_submit(&ring);

        // 阻塞等待至少一个操作完成,并返回完成的CQE,这一步是阻塞的
        struct io_uring_cqe *cqe;
        io_uring_wait_cqe(&ring, &cqe);

        struct io_uring_cqe* cqes[128];
        // 批量获取已经完成的操作结果, nready表示完成的操作数量
        int nready = io_uring_peek_batch_cqe(&ring, cqes, 128);
        for (int i = 0; i < nready; i++)
        {
            struct io_uring_cqe *cqe = cqes[i];
            struct conn_info coninfo;
            memcpy(&coninfo, &cqe->user_data, sizeof(struct conn_info));
            if (coninfo.event == EVENT_ACCEPT)
            {
                // 走到这里时,说明有新的连接到来,需要处理
                set_event_accept(&ring, listen_fd, (struct sockaddr*)&clientaddr, &clientlen, 0);
                int clientfd = cqe->res;
                set_event_recv(&ring, clientfd, buf, BUFFER_LENGTH, 0);
            }
            else if (coninfo.event == EVENT_RECV)
            {
                // 走到这里时,说明有数据可读,需要处理
                int clientfd = coninfo.fd;
                int nread = cqe->res;
                if (nread <= 0)
                {
                    close(clientfd);
                }
                else if (nread > 0)
                {
                    set_event_send(&ring, clientfd, buf, BUFFER_LENGTH, 0);
                    printf("recv data: %s\n", buf);
                }
            }
            else if (coninfo.event == EVENT_SEND)
            {
                // 走到这里时,说明有数据可写,需要处理
                int clientfd = coninfo.fd;
                set_event_recv(&ring, clientfd, buf, BUFFER_LENGTH, 0);
            }               
        }

        // 这个函数通知io_uring,应用程序已经处理完这些事件,可以释放这些事件了
        io_uring_cq_advance(&ring, nready);
    }
    return 0;
}

学习链接:https://github.com/0voice

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值