io_uring 是 Linux 在 5.1 版本引入的一套新的异步 IO 实现。相比 Linux 在 2.6 版本引入的 AIO,io_uring 性能强很多,接近 SPDK[1],同时支持 buffer IO
io_uring 的作者 Jens Axboe 是 Linux 内核块层和其他块设备的维护者,同时也是 CFQ、Noop、Deadline 调度器、blktrace 以及 FIO 的作者,对内核块层非常熟悉
io_uring 只增加了三个 Linux 系统调用分别是 io_uring_setup
,io_uring_enter
和 io_uring_register
他们的入口都在 Linux 内核源码的 fs/io_uring.c
文件中
用户程序可以直接利用 syscall(__NR_xxx, ……)
的方式直接调用,使用起来很麻烦
由于直接使用系统调用较为复杂,Jens Axboe 还提供了封装好的用户态库 liburing
,简化了 io_uring 的使用,代码位置在 github 上
liburing 仓库的 examples/
目录下提供了几个简单的样例程序:
文件 | 功能 | 其他 |
---|---|---|
io_uring-test.c | 读取一个文件的全部内容 | - |
io_uring-cp.c | 复制一个文件的内容到另一个文件 | 利用 user_data 手动处理读写 IO 之间的依赖,读 IO 返回之后才下发写 IO |
link-cp.c | 复制一个文件的内容到另一个文件 | 同时下发读写,利用 IOSQE_IO_LINK 保证读写之间的依赖[2] |
ucontext-cp.c | 复制 n 个文件的内容到另 n 个文件 | 利用 ucontext 进行上下文切换,模拟协程 |
仔细阅读前三个用例,可以看出利用 io_uring 的一般流程如下:
open
、fstat
等函数来打开文件以及元数据查看等操作
fd
(由 open
函数执行返回的)io_uring_queue_init
初始化 struct io_uring ring
结构体struct iovec *iovecs
结构体用于存放用户态 buffer 指针和长度io_uring_get_sqe
获取 sqe
io_uring_prep_#OP
对 sqe
填充命令,buffer 以及 offset 信息
io_uring_sqe_set_data
对 sqe
附加 user_data
信息(该信息会在 cqe
中进行返回)io_uring_submit
对整个 ring
的所有 sqe
进行下发io_uring_wait_cqe
或者 io_uring_peek_cqe
来获取 cqe
io_uring_wait_cqe
会阻塞当前线程直到有一个 cqe
返回io_uring_peek_cqe
不会阻塞,如果当前没有 cqe
,就会返回错误io_uring_cqe_get_data
可以从 cqe
中获取 user_data
io_uring_cqe_seen
对当前 cqe
进行清除,避免被二次处理io_uring_queue_exit
将 ring
销毁io_uring_setup
// fs/io_uring.c /* * Sets up an aio uring context, and returns the fd. Applications asks for a * ring size, we return the actual sq/cq ring sizes (among other things) in the * params structure passed in. */ static long io_uring_setup(u32 entries, struct io_uring_params __user *params) { struct io_uring_params p; long ret; int i; if (copy_from_user(&p, params, sizeof(p))) return -EFAULT; for (i = 0; i < ARRAY_SIZE(p.resv); i++) { if (p.resv[i]) return -EINVAL; } if (p.flags & ~(IORING_SETUP_IOPOLL | IORING_SETUP_SQPOLL | IORING_SETUP_SQ_AFF)) return -EINVAL; ret = io_uring_create(entries, &p); if (ret < 0) return ret; if (copy_to_user(params, &p, sizeof(p))) return -EFAULT; return ret; } SYSCALL_DEFINE2(io_uring_setup, u32, entries, struct io_uring_params __user *, params) { return io_uring_setup(entries, params); }
io_uring_enter
// fs/io_uring.c SYSCALL_DEFINE6(io_uring_enter, unsigned int, fd, u32, to_submit, u32, min_complete, u32, flags, const sigset_t __user *, sig, size_t, sigsz) { struct io_ring_ctx *ctx; long ret = -EBADF; int submitted = 0; struct fd f; if (flags & ~(IORING_ENTER_GETEVENTS | IORING_ENTER_SQ_WAKEUP)) return -EINVAL; f = fdget(fd); if (!f.file) return -EBADF; ret = -EOPNOTSUPP; if (f.file->f_op != &io_uring_fops) goto out_fput; ret = -ENXIO; ctx = f.file->private_data; if (!percpu_ref_tryget(&ctx->refs)) goto out_fput; /* * For SQ polling, the thread will do all submissions and completions. * Just return the requested submit count, and wake the thread if * we were asked to. */ ret = 0; if (ctx->flags & IORING_SETUP_SQPOLL) { if (flags & IORING_ENTER_SQ_WAKEUP) wake_up(&ctx->sqo_wait); submitted = to_submit; } else if (to_submit) { to_submit = min(to_submit, ctx->sq_entries); mutex_lock(&ctx->uring_lock); submitted = io_ring_submit(ctx, to_submit); mutex_unlock(&ctx->uring_lock); if (submitted != to_submit) goto out; } if (flags & IORING_ENTER_GETEVENTS) { unsigned nr_events = 0; min_complete = min(min_complete, ctx->cq_entries); if (ctx->flags & IORING_SETUP_IOPOLL) { ret = io_iopoll_check(ctx, &nr_events, min_complete); } else { ret = io_cqring_wait(ctx, min_complete, sig, sigsz); } } out: percpu_ref_put(&ctx->refs); out_fput: fdput(f); return submitted ? submitted : ret; }
io_uring_register
// fs/io_uring.c SYSCALL_DEFINE4(io_uring_register, unsigned int, fd, unsigned int, opcode, void __user *, arg, unsigned int, nr_args) { struct io_ring_ctx *ctx; long ret = -EBADF; struct fd f; f = fdget(fd); if (!f.file) return -EBADF; ret = -EOPNOTSUPP; if (f.file->f_op != &io_uring_fops) goto out_fput; ctx = f.file->private_data; mutex_lock(&ctx->uring_lock); ret = __io_uring_register(ctx, opcode, arg, nr_args); mutex_unlock(&ctx->uring_lock); out_fput: fdput(f); return ret; }