【io_uring】liburing 用户库源码分析
文章目录
当前内容基于 liburing 2.1 版本
整体流程
之前有过总结,使用 io_uring 的一般流程如下:
- 使用
open、fstat等函数来打开文件以及元数据查看等操作- 因为 io_uring 替换的是读写接口,后续 io_uring 操作的对象是
fd(由open函数执行返回的)
- 因为 io_uring 替换的是读写接口,后续 io_uring 操作的对象是
- 使用
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来获取cqeio_uring_wait_cqe会阻塞当前线程直到有一个cqe返回io_uring_peek_cqe不会阻塞,如果当前没有cqe,就会返回错误io_uring_cqe_get_data可以从cqe中获取user_data
- 通过
io_uring_cqe_seen对当前cqe进行清除,避免被二次处理 - 所有 IO 完成后,通过
io_uring_queue_exit将ring销毁
io_uring_queue_init
-
函数调用逻辑
-
函数功能
该函数主要将队列深度以及额外的
flags参数传递到内核,让内核的io_uring_setup来初始化io_uring结构体,同时使用mmap将在内核中初始化的SQ、CQ以及SQEs映射到用户态初始化时传递的
flags将影响io_uring的运行方式:IORING_SETUP_IOPOLL:开启此选项必须保证后续只用O_DIRECT打开文件并且文件系统的file_operations中注册了iopoll函数,否则 IO 将下发失败。开启后内核将调用注册的iopoll函数来主动轮询设备驱动确认 IO 是否完成,iopoll的触发时机可以参看 io_uring 内核源码分析IORING_SETUP_SQPOLL:将启动一个单独的内核线程io_sq_thread,内核将主动轮询 SQ,然后将 IO 下发至驱动设备,能大大减少提交 IO 时的系统调用开销(内核线程工作时,提交 IO 将无需系统调用;但是该线程可能会休眠,休眠时需要系统调用来唤醒该线程)IORING_SETUP_SQ_AFF:当IORING_SETUP_SQPOLL已经配置后,启用sq_thread_cpu字段,用于配置内核线程io_sq_thread的跑在哪个 CPU 上
io_uring_get_sqe
由于 SQ 已经通过 mmap 映射到用户态,该函数只需在读取 sq->khead 时通过 io_uring_smp_load_acquire 保证一致性,而 sq->sqe_tail 只用于用户态,直接读取即可,根据 sq->khead 以及 sq->sqe_tail 判断 SQ 是否已满,未满则给出 sq->sqe_tail 处的 sqe 即可,然后更新 sq->sqe_tail
io_uring_prep_#OP
通过调用 io_uring_prep_rw 对 sqe 填充命令 OP、fd、buffer 指针以及 offset 信息等
io_uring_sqe_set_data
直接对 sqe->user_data 进行赋值
io_uring_submit
-
函数调用逻辑
-
函数功能
-
__io_uring_flush_sq根据
sq->sqe_tail、sq->sqe_head差值依次填充sq->array,然后一次性更新sq->ktail,并返回内核中仍未处理sqe数量(sq->ktail - sq->khead) -
sq_ring_needs_enter判断内核线程
io_sq_thread是否启用以及正常工作(没有休眠):- 首先要判断用户态
ring->flags是否配置了IORING_SETUP_SQPOLL标志位,判断是否启用了内核线程io_sq_thread - 然后再判断内核态
ring->sq.kflags是否配置了IORING_SQ_NEED_WAKEUP标志位,判断内核线程io_sq_thread是否需要唤醒
当内核线程
io_sq_thread启用并且正常工作时,则整个io_uring_submit到此结束,无需后续的__sys_io_uring_enter系统调用,减少了 IO 下发的系统调用的开销 - 首先要判断用户态
-
__sys_io_uring_enter系统调用陷入内核态,将参数传递给内核的
io_uring_setup函数,主要用于提交 IO 和获取 IO 完成情况,具体功能和初始化时配置的ring->flags相关,详细分析可以参看 io_uring 内核源码分析
-
io_uring_wait_cqe
在用户态轮询判断是否有一个新的 cqe,无需系统调用陷入内核,但是会阻塞当前线程直到有一个新的 cqe 或者出错
io_uring_peek_cqe
仅在用户态判断一次是否有新的 cqe,无需系统调用陷入内核,如果没有新的 cqe,会返回失败信息 -errno
io_uring_cqe_get_data
cqe->user_data 会在 IO 完成后,从 sqe 复制到对应的 cqe 中,该函数只用直接对 cqe->user_data 进行读取
io_uring_cqe_seen
更新 cq->khead,避免当前 cqe 被重复获取
io_uring_queue_exit
首先通过 munmap 将初始化时 mmap 的 SQ、CQ 以及 SQEs 解除映射,然后通过 close 关闭 io_uring 对应的 fd,close 会调用到该 fd 注册的 io_uring_release 来释放 io_uring
参考资料
本文作者: ywang_wnlo
本文链接: https://ywang-wnlo.github.io/posts/d7259d1d.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

浙公网安备 33010602011771号