liaio介绍
linux kernel 提供了5个系统调用来实现异步IO。文中最后介绍的是包装了这些系统调用的用户空间的函数。
libaio系统调用
AIO
系统调用总共五个,后面会一一介绍。
*
int
io_setup
(
unsigned
nr_events
,
aio_context_t
*
ctxp
);
*
int
io_destroy
(
aio_context_t
ctx
);
*
int
io_submit
(
aio_context_t
ctx
,
long
nr
,
struct
iocb
*
cbp
[]);
*
int
io_cancel
(
aio_context_t
ctx
,
struct
iocb
*,
struct
io_event
*
result
);
*
int
io_getevents
(
aio_context_t
ctx
,
long
min_nr
,
long
nr
,
struct io_event *events, struct timespec *timeout);
1.异步IO上下文 aio_context_t
>> aio_context_t.c >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define
_GNU_SOURCE
/* syscall() is not POSIX */
#include
/* for perror() */
#include
/* for syscall() */
#include
/* for __NR_* definitions */
#include
/* for AIO types and constants */
inline
int
io_setup
(
unsigned
nr
,
aio_context_t
*
ctxp
)
{
return
syscall
(
__NR_io_setup
,
nr
,
ctxp
);
}
inline
int
io_destroy
(
aio_context_t
ctx
)
{
return
syscall
(
__NR_io_destroy
,
ctx
);
}
int
main
()
{
aio_context_t
ctx
;
int
ret
;
ctx
=
0
;
ret
=
io_setup
(
128
,
&
ctx
);
if
(
ret
<
0
)
{
perror
(
"io_setup error"
);
return
-
1
;
}
printf
(
"after io_setup ctx:%Ld\n"
,
ctx
);
ret
=
io_destroy
(
ctx
);
if
(
ret
<
0
)
{
perror
(
"io_destroy error"
);
return
-
1
;
}
printf
(
"after io_destroy ctx:%Ld\n"
,
ctx
);
return
0
;
}
系统调用io_setup会创建一个所谓的"AIO上下文"(即aio_context,后文也叫‘
AIO context’等)
结构体到在内核中。
aio_context是用以内核实现异步AIO的数据结构。它其实是一个无符号整形,位于头文件
/usr/include/linux/aio_abi.h
。
typedef unsigned long aio_context_t;
每个进程都可以有多个
aio_context_t。传入io_setup的第一个参数在这里是128,表示同时驻留在上下文中的IO请求的个数;第二个参数是一个指针,内核会填充这个值。
io_destroy的作用是销毁这个上下文
aio_context_t
上面的例子很简单,创建一个
aio_context_t并销毁。
编译运行,编译时需要连接库libaio(-laio):
$ gcc
-
Wall
aio_context_t
.
c
-
o
aio_context_t
-
laio
$
./
aio_context_t
after io_setup ctx
:
139730712117248
after
io_destroy ctx
:
139730712117248
2.提交并查询IO
#define _GNU_SOURCE /* syscall() is not POSIX */
#include
/* for perror() */
#include
/* for syscall() */
#include
/* for __NR_* definitions */
#include
/* for AIO types and constants */
#include
/* O_RDWR */
#include
/* memset() */
#include
/* uint64_t */
inline
int
io_setup
(
unsigned
nr
,
aio_context_t
*
ctxp
)
{
return
syscall
(
__NR_io_setup
,
nr
,
ctxp
);
}
inline
int
io_destroy
(
aio_context_t
ctx
)
{
return
syscall
(
__NR_io_destroy
,
ctx
);
}
inline
int
io_submit
(
aio_context_t
ctx
,
long
nr
,
struct
iocb
**
iocbpp
)
{
return
syscall
(
__NR_io_submit
,
ctx
,
nr
,
iocbpp
);
}
inline
int
io_getevents
(
aio_context_t
ctx
,
long
min_nr
,
long
max_nr
,
struct
io_event
*
events
,
struct
timespec
*
timeout
)
{
return
syscall
(
__NR_io_getevents
,
ctx
,
min_nr
,
max_nr
,
events
,
timeout
);
}
int
main
()
{
aio_context_t
ctx
;
struct
iocb cb
;
struct
iocb
*
cbs
[
1
];
char
data
[
4096
];
struct
io_event events
[
1
];
int
ret
;
int
fd
;
int
i
;
for
(
i
=
0
;
i
<
4096
;
i
++)
{
data
[
i
]=
i
%
50
+
60
;
}
fd
=
open
(
"./testfile"
,
O_RDWR
|
O_CREAT
,
S_IRWXU
);
if
(
fd
<
0
)
{
perror
(
"open error"
);
return
-
1
;
}
ctx
=
0
;
ret
=
io_setup
(
128
,
&
ctx
);
printf
(
"after io_setup ctx:%ld"
,
ctx
);
if
(
ret
<
0
)
{
perror
(
"io_setup error"
);
return
-
1
;
}
/* setup I/O control block */
memset
(&
cb
,
0
,
sizeof
(
cb
));
cb
.
aio_fildes
=
fd
;
cb
.
aio_lio_opcode
=
IOCB_CMD_PWRITE
;
/* command-specific options */
cb
.
aio_buf
=
(
uint64_t
)
data
;
cb
.
aio_offset
=
0
;
cb
.
aio_nbytes
=
4096
;
cbs
[
0
]
=
&
cb
;
ret
=
io_submit
(
ctx
,
1
,
cbs
);
if
(
ret
!=
1
)
{
if
(
ret
<
0
)
perror
(
"io_submit error"
);
else
fprintf
(
stderr
,
"could not sumbit IOs"
);
return
-
1
;
}
/* get the reply */
ret
=
io_getevents
(
ctx
,
1
,
1
,
events
,
NULL
);
printf
(
"%d\n"
,
ret
);
struct
iocb
*
result
=
(
struct
iocb
*)
events
[
0
].
obj
;
printf
(
"reusult:%Ld"
,
result
->
aio_buf
);
ret
=
io_destroy
(
ctx
);
if
(
ret
<
0
)
{
perror
(
"io_destroy error"
);
return
-
1
;
}
return
0
;
}
1) 每一个提交的IO请求用结构体struct iocb来表示。
首先初始化这个结构体为全零: memset(&cb, 0, sizeof(cb));
然后初始化文件描述符(cb.aio_fildes = fd)和AIO 命令(cb.aio_lio_opcode = IOCB_CMD_PWRITE)
文件描述符对应上文所打开的文件。本例中是./testfile.
内核当前支持的AIO 命令有
IOCB_CMD_PREAD
读; 对应系统调用pread().
IOCB_CMD_PWRITE
写,对应系统调用pwrite().
IOCB_CMD_FSYNC
同步文件数据到磁盘,对应系统调用fsync()
IOCB_CMD_FDSYNC
同步文件数据到磁盘,对应系统调用fdatasync()
IOCB_CMD_PREADV
读,对应系统调用readv()
IOCB_CMD_PWRITEV
写,对应系统调用writev()
IOCB_CMD_NOOP
只是内核使用
cb.aio_buf = (uint64_t)data;其中的data对应要读或要写入的数据的内存地址。
cb.aio_offset=0 表示文件的绝对偏移量
2) 调用io_submit
函数原型int io_submit(aio_context_t ctx, long nr, struct iocb *cbp[]);
当一个IO控制块(struct iocb cb)初始化完毕,把这个指针放入一个数组中( cbs[0] = &cb),因为io_submit系统调用需要接受一个二维指针。在io_submit(ctx, 1, cbs)中, 参数分别为IO上下文(aio_context_t)、数组(struct iocb)大小、数组地址(cbs).
io_submit的返回值,可以是如下值:
A) ret = (提交的iocb的数目)
表示所有的iocb都被接受并处理
B) 0 < ret < (提交的iocb的数目)
io_submit() 系统调用会从传入的cbs中一个一个处理iocb,如果提交的某个iocb失败,将停止并且返回iocb的索引号。没办法知晓错 误的具体原因,但是如果第一个iocb提交失败,参看C条。
C) ret < 0
有两种原因:
1) 在io_submit()开始之前发生了某种错误(e.g.比如AIO context非法).
2) 提交第一个iocb(cbx[0])失败
3) 调用io_getevents()
当提交了iocb之后,可以不用等待IO完成去做其他的操作。对于每一个已经完成的IO请求(成功或失败),内核都会创建一个io_event结构。io_getevent()系统调用可以用来获取这一结构。这需要做以下操作。
原型 int io_getevents(aio_context_t ctx, long min_nr, long nr, struct io_event *events, struct timespec *timeout)
a) 使用哪一个AIO上下文(变量ctx)
b) 内核把这个变量放入哪个内存位置 (变量events)
c) events的最小个数(变量min_nr,)
如果完成的iocb的个数比这个值要小io_getevents会阻塞,直到达到这个值,
参看第e条查看阻塞时间。
d) 想要获取的events的最大个数(变量nr)。
e) 如果获取不到足够的events,而又不想永久等待。可以指定相对时间(timeout)到最后一个参数,
如果timeout为NULL,表示永久等待。
如果timeout为0,io_getevents()不阻塞 。
编译并运行:
$ gcc
-
Wall
submit_reslut
.
c
-
o submit_reslut
-
laio
$
./
submit_reslut
after io_setup ctx
:
1404534983884801
result
:
140735403362480
$ cat testfile
会发现文中中有相应的内容。
libaio用户空间函数
从上文可以看出,直接使用系统调用执行一个完整的IO输入输出,流程比较麻烦。在用户空间包装了几个函数用以简化这一操作。详细请参考
Linux libaio,Linux下原生异步IO接口Libaio的用法
static inline void io_prep_pread(struct iocb *iocb, int fd, void *buf, size_t count, long long offset) static inline void io_prep_pwrite(struct iocb *iocb, int fd, void *buf, size_t count, long long offset) static inline void io_prep_preadv(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset) static inline void io_prep_pwritev(struct iocb *iocb, int fd, const struct iovec *iov, int iovcnt, long long offset) static inline void io_prep_poll(struct iocb *iocb, int fd, int events) static inline void io_prep_fsync(struct iocb *iocb, int fd) static inline void io_prep_fdsync(struct iocb *iocb, int fd) static inline int io_poll(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd, int events) static inline int io_fsync(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd) static inline int io_fdsync(io_context_t ctx, struct iocb *iocb, io_callback_t cb, int fd) static inline void io_set_eventfd(struct iocb *iocb, int eventfd);