io_uring最简单的实例io_uring-test.c分析
文章目录
- 1. 背景知识
- 1.1 io_uring的3个系统调用
- 1.2 io_uring封装库liburing
- 2. 应用io_uring-test.c示例分析
- 2.1 下载地址及编译方法
- 2.2 代码分析
- 2.2.1 io_uring_queue_init API
- 2.2.2 io_uring_get_sqe API
- 2.2.3 io_uring_prep_readv API
- 2.2.4 io_uring_submit API
- 2.2.5 io_uring_wait_cqe API
- 2.2.5 io_uring_queue_exit函数
- 3. io_uring性能提升关键
- 3.1 队列映射共享
- 3.2 队列无锁操作
- 3.3 支持缓存注册
- 4 参考

1. 背景知识
1.1 io_uring的3个系统调用
io_uring提供了3个系统调用函数:io_uring_setup,io_uring_enter,io_uring_register。
-
io_uring_setup:创建内核上下文(下图io_ring_ctx),分配一个关联内核上下文(private指针指向io_ring_ctx)的文件,返回其句柄。内核上下文的核心是创建(分配)了装载IO执行实体SQE、IO返回实体CQE及其环构成者指针sq、cq。
-
io_uring_enter:是一个通知信使。应用要提交了,可由它通知内核处理提交;应用要结果了,可由它问内核是否有足够的数量的数据;它还可以同时进行通知提交并索要一定数量的结果。在等待结果时可以指定一定超时时间。
-
io_uring_register:case比较多,典型的应用是注册buffer和文件句柄,以注册buffer为例,所谓注册就是将用户申请的用于IO处理的buffer在内存管理时加上pin标志,一是防止被交换,二是后续使用不需要再次映射到内核,减少操作,使用方法是直接使用READ_FIXED/WRITE_FIXED和buffer index指代注册的buffer。
1.2 io_uring封装库liburing
应用要使用以上3个系统调用完成IO还是有点复杂,为简化使用,提供了对其封装的liburing库:
-
io_uring_queue_init:调系统调用io_uring_setup和mmap完成io_uring在内核和用户的初始化。
-
io_uring_get_sqe:取一个可用的sqe,不涉及系统调用。
-
io_uring_prep_*系列函数:将IO参数直接赋值sqe的成员,prepare的意思即是准备好了sqe,如下具体接口只简单列了文件类和网络类:
- io_uring_prep_read、io_uring_prep_write:文件类。
- io_uring_prep_accept、io_uring_prep_send:网络类。
-
io_uring_submit:将准备好的sqe提交给内核,触发内核执行操作,内部依赖io_uring_enter系统调用。
-
io_uring_wait_cqe:阻塞等待至少一个操作完成,并返回完成的CQE,这一步是阻塞的;不阻塞的接口是io_uring_peek_batch_cqe,批量获取完成队列中的操作结果, 返回值表示已经完成的操作数量。
-
io_uring_cqe_seen:调的是io_uring_cq_advance,通知内核该cqe已被处理,它是空闲可用的。
2. 应用io_uring-test.c示例分析
2.1 下载地址及编译方法
- 下载地址
git clone https://github.com/axboe/liburing.git - 编译方法
cd liburing
./configure
make && sudo make install
编译应用时,需要指定库和定义_GUN_SOURCE: -luring -D_GUN_SOURCE
如:gcc -o io_uring_test io_uring_test.c -luring -D_GUN_SOURCE
2.2 代码分析
examle目录下的io_uring-test.c,功能用于读取测试,代码如下
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "liburing.h"#define QD 4int main(int argc, char *argv[])
{struct io_uring ring;int i, fd, ret, pending, done;struct io_uring_sqe *sqe;struct io_uring_cqe *cqe;struct iovec *iovecs;struct stat sb;ssize_t fsize;off_t offset;void *buf;if (argc < 2) {printf("%s: file\n", argv[0]);return 1;}ret = io_uring_queue_init(QD, &ring, 0);if (ret < 0) {fprintf(stderr, "queue_init: %s\n", strerror(-ret));return 1;}fd = open(argv[1], O_RDONLY | O_DIRECT);if (fd < 0) {perror("open");return 1;}if (fstat(fd, &sb) < 0) {perror("fstat");return 1;}fsize = 0;iovecs = calloc(QD, sizeof(struct iovec));for (i = 0; i < QD; i++) {if (posix_memalign(&buf, 4096, 4096))//分配一块4096字节对齐的4096大小内存,地址给bufreturn 1;iovecs[i].iov_base = buf;iovecs[i].iov_len = 4096;fsize += 4096;}offset = 0;i = 0;do {sqe = io_uring_get_sqe(&ring);if (!sqe)break;io_uring_prep_readv(sqe, fd, &iovecs[i], 1, offset);offset += iovecs[i].iov_len;i++;if (offset >= sb.st_size)break;} while (1);ret = io_uring_submit(&ring);if (ret < 0) {fprintf(stderr, "io_uring_submit: %s\n", strerror(-ret));return 1;} else if (ret != i) {fprintf(stderr, "io_uring_submit submitted less %d\n", ret);return 1;}done = 0;pending = ret;fsize = 0;for (i = 0; i < pending; i++) {ret = io_uring_wait_cqe(&ring, &cqe);if (ret < 0) {fprintf(stderr, "io_uring_wait_cqe: %s\n", strerror(-ret));return 1;}done++;ret = 0;if (cqe->res != 4096 && cqe->res + fsize != sb.st_size) {fprintf(stderr, "ret=%d, wanted 4096\n", cqe->res);ret = 1;}fsize += cqe->res;io_uring_cqe_seen(&ring, cqe);if (ret)break;}printf("Submitted=%d, completed=%d, bytes=%lu\n", pending, done,(unsigned long) fsize);close(fd);io_uring_queue_exit(&ring);for (i = 0; i < QD; i++)free(iovecs[i].iov_base);free(iovecs);return 0;
}
2.2.1 io_uring_queue_init API
io_uring_queue_init 》io_uring_queue_init_params 》io_uring_queue_init_try_nosqarr 》__io_uring_queue_init_params 》__sys_io_uring_setup //系统调用完成SQ/CQ的创建》io_uring_queue_mmap //完成给用户接口的接口映射,主要是些指针
代码使用API io_uring_queue_init申请了实体为4的环,系统调用__sys_io_uring_setup完成内核的SQ/CQ创建,使用io_uring_queue_mmap完成SQ/CQ到用户空间的映射,并将其信息填入用户空间的相关结构里。
2.2.2 io_uring_get_sqe API
io_uring_get_sqe》_io_uring_get_sqeIOURINGINLINE struct io_uring_sqe *_io_uring_get_sqe(struct io_uring *ring)LIBURING_NOEXCEPT
{struct io_uring_sq *sq = &ring->sq;unsigned head = io_uring_load_sq_head(ring), tail = sq->sqe_tail;struct io_uring_sqe *sqe;if (tail - head >= sq->ring_entries)return NULL;sqe = &sq->sqes[(tail & sq->ring_mask) << io_uring_sqe_shift(ring)];sq->sqe_tail = tail + 1;io_uring_initialize_sqe(sqe);return sqe;
}
没有系统调用,只是使用io_uring_queue_init获取到的信息里获取一个可用的sqe。
2.2.3 io_uring_prep_readv API
io_uring_prep_readv》io_uring_prep_rw(IORING_OP_READV, sqe, fd, iovecs, nr_vecs, offset);//op为IORING_OP_READVIOURINGINLINE void io_uring_prep_rw(int op, struct io_uring_sqe *sqe, int fd,const void *addr, unsigned len,__u64 offset)LIBURING_NOEXCEPT
{sqe->opcode = (__u8) op;sqe->fd = fd;sqe->off = offset;sqe->addr = (unsigned long) addr;sqe->len = len;
}
只是简单地使用本地信息填充IO任务信息到sqe。
2.2.4 io_uring_submit API
io_uring_submit》__io_uring_submit_and_waitIORING_OP_READV》__io_uring_submit //below ==>》__sys_io_uring_enter》__sys_io_uring_enter2》syscall(__NR_io_uring_enter, ...);static int __io_uring_submit(struct io_uring *ring, unsigned submitted,unsigned wait_nr, bool getevents)
{bool cq_needs_enter = getevents || wait_nr || cq_ring_needs_enter(ring);unsigned flags = ring_enter_flags(ring);int ret;liburing_sanitize_ring(ring);if (sq_ring_needs_enter(ring, submitted, &flags) || cq_needs_enter) {if (cq_needs_enter)flags |= IORING_ENTER_GETEVENTS;ret = __sys_io_uring_enter(ring->enter_ring_fd, submitted,wait_nr, flags, NULL);} elseret = submitted;return ret;
}
__io_uring_submit函数做了判断即sq_ring_needs_enter(进入函数是IORING_SETUP_SQPOLL或IORING_ENTER_SQ_WAKEUP)或cq_needs_enter,需要获取结果才调__sys_io_uring_enter
否则直接返回submitted值。
2.2.5 io_uring_wait_cqe API
io_uring_wait_cqe
》__io_uring_peek_cqe //直接返回的情况
》io_uring_wait_cqe_nr》__io_uring_get_cqe》_io_uring_get_cqe》__io_uring_peek_cqe //直接返回的情况》sq_ring_needs_enter//直接返回的情况》__sys_io_uring_enter2》syscall(__NR_io_uring_enter
满足条件就会直接返回,否则会调用io_uring_enter系统调用,完成结果收割。
2.2.5 io_uring_queue_exit函数
io_uring_queue_exit》__sys_munmap//unmap sqes》io_uring_unmap_rings //unmap sq cq》io_uring_unregister_ring_fd 》do_register》__sys_io_uring_register》syscall(__NR_io_uring_register,...)
完成sqes、sq、 cq的unmap,如果有fd,调io_uring_register完成IORING_UNREGISTER_RING_FDS。
3. io_uring性能提升关键
3.1 队列映射共享
减少用户空间到内核空间的内存拷贝和上下文切换开销
3.2 队列无锁操作
通过队列首尾的无锁(原子)操作,较少了锁带来的性能损耗
3.3 支持缓存注册
支持将buffer、文件句柄等注册到内核,减少频繁映射申请带来的损耗
4 参考
- 图文详解io_uring高性能异步IO架构(原理篇)