Linux下原生异步IO接口libaio介绍

    xiaoxiao2021-03-25  135

    http://backend.blog.163.com/blog/static/20229412620135257159731/ 调研   fio 的实现时,接触了libaio的使用方式。由于fio 的io engine发送及接受数据的流程是按照liaio库的方式进行的。所以初步使用了libaio。 现总结如下。 几点说明     本文的重点在于libaio的使用方式。所以  1.对什么是同步、异步及阻塞、非阻塞IO,请参考相应资料。比较权威的资料是 Richard Stevens的“ UNIXNetwork Programming Volume 1, Third Edition: The Sockets Networking  ”,6.2节“ I/O Models  ”。其他网络资料比如   同步,异步,阻塞,非阻塞 、 阻塞,非阻塞IO和同步,异步IO 2.对POSIX AIO及libaio的区别,请参看POSIX AIO and libaio on Linux。 libaio是原生的 linux aio,行为更为低级; POSXI AIO是在用户空间模拟异步IO的功能,不需要内核的支持。 3.linux网络编程中的异步IO接口epoll、kqueue等在此不做介绍,这方面资料也相对较多。 4.学习libaio是为了调研fio的实现,并没有深入使用,目前网上介绍这方面的资料相对较少,权当抛砖引玉。 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 <stdio.h>       /* for perror() */ #include <unistd.h>     /* for syscall() */ #include <sys/syscall.h>     /* for __NR_* definitions */ #include <linux/aio_abi.h>   /* 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 <stdio.h> /* for perror() */ #include <unistd.h> /* for syscall() */ #include <sys/syscall.h> /* for __NR_* definitions */ #include <linux/aio_abi.h> /* for AIO types and constants */ #include <fcntl.h> /* O_RDWR */ #include <string.h> /* memset() */ #include <inttypes.h> /* 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)到最后一个参数,

    如果timeoutNULL,表示永久等待。

    如果timeout0io_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);

    转载请注明原文地址: https://ju.6miu.com/read-5972.html

    最新回复(0)