Linux Native AIO
来看看 Linux 提供的 AIO 系统调用(自行封装的头文件 native_aio.h):
#ifndef __NATIVE_AIO_H__
#define __NATIVE_AIO_H__
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <linux/aio_abi.h>
static inline int io_setup(unsigned nr_events, aio_context_t* ctx_idp)
{
return syscall(__NR_io_setup, nr_events, ctx_idp);
}
static inline int io_destroy(aio_context_t ctx)
{
return syscall(__NR_io_destroy, ctx);
}
static inline int io_submit(aio_context_t ctx, long nr, struct iocb** iocbpp)
{
return syscall(__NR_io_submit, ctx, nr, iocbpp);
}
static inline int io_getevents(aio_context_t ctx, long min_nr, long nr,
struct io_event* events, struct timespec* timeout)
{
return syscall(__NR_io_getevents, ctx, min_nr, nr, events, timeout);
}
static inline int io_cancel(aio_context_t ctx, struct iocb* iocb,
struct io_event* result)
{
return syscall(__NR_io_cancel, ctx, iocb, result);
}
#endif
通过 man 能够查到函数的描述,但是这些函数不是跨平台的,因此 libc 没有对这些函数进行封装,这里手动封装了一下。下面是一个使用示例:
#include "native_aio.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#define NR_EVENT 1024
#define BUFSIZE 4096
int main(void)
{
int fd, ret = -1;
char* buf;
aio_context_t ctx;
struct io_event event;
struct iocb cb;
struct iocb* cblist[] = {&cb};
fd = open(__FILE__, O_RDONLY | O_DIRECT);
if (fd == -1) {
fprintf(stderr, "open(%s) failed: %s.\n", __FILE__, strerror(errno));
return -1;
}
buf = aligned_alloc(512, BUFSIZE);
if (!buf) {
fprintf(stderr, "aligned_alloc(%u) failed: %s.\n", BUFSIZE,
strerror(errno));
goto err1;
}
memset(&ctx, 0, sizeof(ctx));
ret = io_setup(NR_EVENT, &ctx);
if (ret != 0) {
fprintf(stderr, "io_setup failed: %s.\n", strerror(errno));
goto err2;
}
memset(&cb, 0, sizeof(cb));
cb.aio_data = (__u64)buf;
cb.aio_fildes = fd;
cb.aio_lio_opcode = IOCB_CMD_PREAD;
cb.aio_buf = (__u64)buf;
cb.aio_offset = 0;
cb.aio_nbytes = BUFSIZE;
ret = io_submit(ctx, 1, cblist);
if (ret != 1) {
fprintf(stderr, "io_submit failed: %s.\n", strerror(errno));
goto err3;
}
ret = io_getevents(ctx, 1, 1, &event, NULL);
if (ret != 1) {
fprintf(stderr, "io_getevents failed: %s.\n", strerror(errno));
goto err3;
}
if (event.res <= 0) {
fprintf(stderr, "io error: %s.\n", strerror(-(event.res)));
} else {
printf("read %lld byte(s):\n", event.res);
write(1, (const void*)(event.data), event.res);
}
err3:
io_destroy(ctx);
err2:
free(buf);
err1:
close(fd);
return ret;
}
程序首先打开一个只读文件,注意这里加了选项“O_DIRECT”,直接从磁盘读取文件。因为绕过了 page cache,所以一般程序都会自己来实现 cache;接着使用 aligned_alloc() 申请存放读取内容的 buffer,起始地址需要和磁盘逻辑块大小对齐(一般是 512 字节)。
准备工作完成后,调用 io_setup() 来初始化一个 aio_context_t 标识符,用于后续的 aio 操作。描述 aio 信息的主要是结构体 struct iocb(在 /usr/include/linux/aio_abi.h 中定义):
struct iocb {
/* these are internal to the kernel/libc. */
__u64 aio_data; /* data to be returned in event's data */
__u32 PADDED(aio_key, aio_reserved1);
/* the kernel sets aio_key to the req # */
/* common fields */
__u16 aio_lio_opcode; /* see IOCB_CMD_ above */
__s16 aio_reqprio;
__u32 aio_fildes;
__u64 aio_buf;
__u64 aio_nbytes;
__s64 aio_offset;
/* extra parameters */
__u64 aio_reserved2; /* TODO: use this for a (struct sigevent *) */
/* flags for the "struct iocb" */
__u32 aio_flags;
/*
* if the IOCB_FLAG_RESFD flag of "aio_flags" is set, this is an
* eventfd to signal AIO readiness to
*/
__u32 aio_resfd;
}; /* 64 bytes */
需要填充的字段包括:
- aio_data:事件完成后随着 struct io_event 返回的内容,后面详细介绍;
- aio_lio_opcode:IO 类型,可能包含以下取值:
- IOCB_CMD_PREAD:对应系统调用 pread(),读取从指定位置开始的指定长度的内容,但是不改变文件偏移;
- IOCB_CMD_PWRITE:对应系统调用 pwrite(),从指定位置写入指定长度的内容,但是不改变文件偏移;
- IOCB_CMD_FSYNC:对应系统调用 fsync(),将元数据和文件内容写到磁盘;
- IOCB_CMD_FDSYNC:对应 fdatasync(),将文件内容和必需的元数据写到磁盘;
- IOCB_CMD_NOOP:不确定,据说还没被使用;
- IOCB_CMD_PREADV:对应 preadv(),从当前位置读取指定数量和长度的 buffer,但不改变文件偏移;
- IOCB_CMD_PWRITEV:对应 pwritev(),从当前位置写入指定数量和长度的 buffer,但不改变文件偏移。
- aio_fildes:要操作的文件描述符;
- aio_buf:要操作的 buffer 起始地址;
- aio_nbytes:要读写的长度;
- aio_offset:要读写的起始位置。
其它字段一定要置 0。
填充完成后就可以调用 io_submit() 提交请求了,请求提交后 io_submit() 会立即返回。接着可以调用 io_getevents() 获取在 aio_context_t 上提交的事件,函数的第二个参数是最少需要获取多少个事件,第三个参数是最多获取多少个事件,第四个参数是超时设置,如果超过指定事件还没等到最少个数的事件就返回,返回值就是实际返回的事件个数。
从 io_getevents() 返回后从 struct io_event 获取事件执行的结果:
struct io_event {
__u64 data; /* the data field from the iocb */
__u64 obj; /* what iocb this event came from */
__s64 res; /* result code for this event */
__s64 res2; /* secondary result */
};
其中的 data 字段就是 struct iocb 里的 aio_data;obj 就是 iocb 本身;res 小于等于 0 表示出错,-res 的值就是 errno,res 大于 0 表示读写成功的字节数;res2 不知道干啥用的。
最后用 io_destroy() 释放资源。