Linux AIO

· 1298 words · 3 minute read

对 Linux 的 AIO 一直是一些碎片化的知识,没有好好总结。我们知道,AIO 的推荐使用场景是访问块设备、结合 O_DIRECT 一起使用。比如,CephBlockDevice.h.

Jens Axboe 在 fio 的 issues 里写道:

fio-issue-521

如果去看 libaio 的测试用例,及其 man 手册中的示例代码,就会发现:

  • 示例中访问的只是普通文件(不是块设备);
  • 而且打开文件的模式并不是都加了 O_DIRECT

普通文件可以用 AIO 接口吗? 🔗

显然可以,比如 libaio 的示例代码,或者看 seastar 的源代码。

不启用 O_DIRECT 时的行为是什么? 🔗

仔细阅读上面 Jens Axboe 的回复,libaio is still only async for O_DIRECT. 这句话的意思是,打开 O_DIRECT 才能获得 async 语义,但不是说必须使用 O_DIRECT.

vfs 对 io_submit 的处理 🔗

vfs 对 AIO 请求的处理逻辑可以在fs/aio.c 中找到。以写请求为例,req 经过 io_submit() 入口一直到达文件操作指针 f_op 中注册的 write_iter() 方法。

/* file: fs/aio.c */
static int aio_write(struct kiocb *req, const struct iocb *iocb,
                     bool vectored, bool compat)
{
    /* ... omitted ... */
    req->ki_flags |= IOCB_WRITE;
    aio_rw_done(req, call_write_iter(file, req, &iter));
    /* ... omitted ... */
}

/* file: include/linux/fs.h */
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iopoll)(struct kiocb *kiocb, struct io_comp_batch *,
                    unsigned int flags);
    /* ... omitted ... */
};

static inline ssize_t call_write_iter(struct file *file, struct kiocb *kio,
                                     struct iov_iter *iter)
{
    return file->f_op->write_iter(kio, iter);
}

内核对 write_iter() 接口的定义

write_iter

possibly asynchronous write with iov_iter as source

这里有个关键字 possibly. 我们看看 XFS 的实现

/* file: fs/xfs/xfs_file.c */
STATIC ssize_t
xfs_file_write_iter(
	struct kiocb		*iocb,
	struct iov_iter		*from)
{
    /* ... omitted ... */
    if (iocb->ki_flags & IOCB_DIRECT) {
		/*
		 * Allow a directio write to fall back to a buffered
		 * write *only* in the case that we're doing a reflink
		 * CoW.  In all other directio scenarios we do not
		 * allow an operation to fall back to buffered mode.
		 */
		ret = xfs_file_dio_write(iocb, from);
		if (ret != -ENOTBLK)
			return ret;
	}

	return xfs_file_buffered_write(iocb, from);
}

进一步阅读 xfs_file_buffered_write() 得到的简单结论是:对于 buffered IO, XFS 会默默地进行同步写。Ext4 也有类似逻辑。1 其实,只要分别检查block/fops.c, net/socket.cread_iter()write_iter() 的实现,就能顺藤摸瓜找到块设备、套接字对 io_submit() 的处理。

同步的 io_submit() 有啥好处? 🔗

既然未打开 O_DIRECT 时,调用io_submit() 会同步阻塞,为啥不直接用read/write 接口?io_submit() 有个好处:不仅可以对单个 fd 进行批量请求(类似 readv/writev),它还可以批量提交针对多个 fd 的请求,从而进一步节省系统调用的次数。2

struct iocb cb[2] = {{.aio_fildes = fd1,
                      .aio_lio_opcode = IOCB_CMD_PWRITE,
                      .aio_buf = (uint64_t)&buf[0],
                      .aio_nbytes = 0},
                     {.aio_fildes = fd2,
                     .aio_lio_opcode = IOCB_CMD_PREAD,
                     .aio_buf = (uint64_t)&buf[0],
                     .aio_nbytes = BUF_SZ}};
struct iocb *list_of_iocb[2] = {&cb[0], &cb[1]};
io_submit(ctx, 2, list_of_iocb);

O_DIRECT 隐含的要求 🔗

man (2) openNOTES 小节里写了有整整一页关于 O_DIRECT 的相关要求。比如:

  • 地址和长度的对齐要求 - 必须按照逻辑块的大小(blockdev –getss)对齐。
  • 如果 DIO 请求的地址来自 mmap(2) MAP_PRIVATE、堆上分配的内存、静态分配的内存,则该请求不能和 fork(2) 同时并发运行。
  • 等等。

如果使用 O_DIRECT 方式操纵文件,则读写的粒度以逻辑块为大小。因此,不可能写出一个只包含 hello world 的 11 字节大小的文件。

简单总结 🔗

下面内容主要来自网络:3

AIO 的工作场景 🔗

  • 块设备上的 DIO;
  • 某些支持 DIO 的文件系统,如:ext4, jfs, xfs 等等。

需要注意的场景 🔗

采用 buffered IO 时,io_submit() 可能并不会报错(取决于文件系统的实现),而是会悄悄变成同步 IO – 也就是 IO 请求完成才会返回。

展望 🔗

自从 2019 年 5 月 io_uring 进入 Linux 5.1 内核,如今终于有了同时支持 DIO、Buffered IO 的异步 IO 接口。随着 3.10.x, 4.18.x 慢慢退役,也许采用 liburing 的项目会渐渐超过 libaio 吧。

彩蛋 🔗

GNOME indexer 也不是那么一无是处。

$ tracker3 search -f -t address_space_operations
Files:
  file:///home/live4thee/Docuements/io/Linux-VFS-and-Block.pdf

又因为terminal 会识别 URL, 鼠标一点就会直接通过 xdg-open 打开文档。


  1. 执行 ext4_dio_write_iter() 的过程中甚至可能回退到 ext4_buffered_write_iter(). ↩︎

  2. 参考 https://blog.cloudflare.com/io_submit-the-epoll-alternative-youve-never-heard-about/ ↩︎

  3. https://lse.sourceforge.net/io/aio.html ↩︎

comments powered by Disqus