上一篇文章提到io_uring是Linux中最新的原生异步I/O实现。事实上,io_uring也支持轮询,是一个很好的epoll替代品。API使用io_uring轮询一个fd非常简单。首先初始化io_uring对象(io_uring_queue_init),getsqe(io_uring_get_sqe)是所有io_uring操作所必需的,上一篇已经介绍过,这里不再赘述。得到sqe后,使用io_uring_prep_poll_add初始化sqe指针。staticinlinevoidio_uring_prep_poll_add(structio_uring_sqe*sqe,intfd,shortpoll_mask);第一个参数是前面得到的sqe指针;第二个参数是你要轮询的文件描述符;第三个是标志位,这里io_uring没有引入新的flags(宏),但是poll(2)定义的flags被带过来了,比如POLLIN,POLLOUT等。和其他I/O请求一样,每个sqe可以设置一个用户自身的价值在其中。使用io_uring_sqe_set_data可以看到一次只能添加一个轮询请求。如果有多个fd,则重复调用io_uring_get_sqe分别获取多个sqe指针和io_uring_prep_poll_add。io_uring_get_sqe不是系统调用,不会进入内核,而io_uring_prep_poll_add是简单的结构体参数赋值,所以不存在速度问题。添加需要的请求后,统一使用io_uring_submit提交,使用io_uring_peek_cqe获取完成状态等,与标准的异步I/O请求一致。使用io_uring做轮询和默认的epoll、poll方式有很大区别。io_uring的轮询始终工作在one-shot模式(相当于epoll的EPOLLONESHOT),即一旦一个轮询操作完成,用户必须重新提交轮询请求否则不会触发新的事件,从而保证每个轮询请求都有一个而且只有一个回应。由于是one-shot模式,epoll中没有区分LT和ET模式。使用io_uring_prep_poll_removestatic清除正在进行的轮询请求inlinevoidio_uring_prep_poll_remove(structio_uring_sqe*sqe,void*user_data);还需要sqe然后提交。可以看到这个函数很特别,直接需要user_data这个参数。内核正在将之前提交的user_data与您现在指定的user_data进行比较,并删除相等值的请求。例子在网络编程中最初的需求是异步监听客户端访问(O_NONBLOCKaccept),这也是epoll代码例子很多。使用io_uring如下:intsockfd=socket(...);bind(...);listen(...);structio_uringring;io_uring_queue_init(32,&ring,0);structio_uring_sqe*sqe=io_uring_get_sqe(&ring);io_uring_prep_poll_add(sqe,sockfd,POLLIN);io_uring_submit(&ring);structio_uring_cqe*cqe;io_uring_wait_cqe(&ring,&cqe);intclientfd=accept(sockfd,...);多么有利啊。使用io_uring作为轮询最有用的一点是统一监控和处理轮询和aio完成事件。想象一下,拿到clientfd后,可以立即使用io_uring_prep_readv读取请求体,同时可以使用io_uring_prep_poll_add接受其他客户端的访问。这才是真正的异步编程。
