Linux异步IO

Linux的异步IO有两种方式,一种是由glibc提供的aio_xxx函数组成的伪异步IO,另一种则是由Linux内核支持的io_xxx系统调用组成的异步IO。在命名的方式上,glibc相应的函数和数据结构均以aio开头,而Linux系统调用及相应的数据结构则以io或io_开头(aio_context_t是一个例外)。以下分别介绍这两种异步IO方式。


一、glibc异步IO

glibc采用多线程同步的方式来实现异步IO,确切地说这并不是真正意义上的异步IO。我们把调用aio_xxx的线程称为主线程,而把实际请求read/write/fsync的线程称为异步IO线程。

(1) int aio_read(struct aiocb *aiocbp);

主线程先将aiocbp->__error_code设为EINPROGRESS,然后把aiocbp挂到aiocbp->aio_fildes的请求队列。这个请求队列是一个优先队列,aio_fildes相同的aiocb按照优先级进行排序。如果不存在为aio_fildes处理的异步IO线程,那么主线程将会创建一个异步IO线程来处理aio_fildes队列中的请求。对每个aiocb,异步IO线程会先尝试使用pread来读取数据,如果pread返回ESPIPE的错误(例如对管道和套接字进行pread操作),它会再次用read系统调用来读取,在这种情况下,aio_offset将会被忽略。当操作成功完成后,异步IO线程将aiocbp->__error_code设成0,并用操作返回的值来设置aiocbp->__return_value。如果操作失败,则把它设成执行pread/read, pwrite/write,或者fsync/fdatasync出错时的errno。如果操作被撤销,则将__error_code设成ECANCELED。

(2)int aio_write(struct aiocb *aiocbp);

这个函数与aio_read类似,不同的是,异步IO线程会先尝试使用pwrite来写入数据,如果pwrite返回ESPIPE的错误(例如对管道和套接字进行pwrite操作),它会再次用write系统调用来写入,在这种情况下,aio_offset将会被忽略。

(3)int aio_fsync(int op, struct aiocb *aiocbp);

异步IO线程使用fsync或者fdatasync系统调用来完成主线程的请求:当op是O_SYNC时调用fsync,当op是O_DSYNC时调用fdatasync。

(4)int aio_cancel(int fd, struct aiocb *aiocbp);
主线程撤销异步IO请求。如果aiocbp为NULL,则撤销fd上所有的IO请求,否则只撤销aiocbp对应的请求(要求fd=aiocbp->aio_fildes)。

(5)int aio_error(const struct aiocb *aiocbp);

这个函数返回aiocbp->__error_code的值。

(6)int aio_return(const struct aiocb* aiocbp);

这个函数返回aiocbp->__return_value的值。

(7)int aio_suspend(const struct aiocb * const aiocb_list[], int nitems, const struct timespec *timeout);

这个函数有一点儿类似select,但也有所不同,select的fd_set是没用const修饰的,它们除了用作输入参数外也用作输出参数。而aio_suspend函数的第一个参数是用const修饰的,它只用作输入参数。

(8)获取异步IO的处理状态与结果

在aiocb结构体中有一个sigevent类型的成员aio_sigevent。sigevent的定义如下(sigevent(7)):

       union sigval {          /* Data passed with notification */
           int     sival_int;         /* Integer value */
           void   *sival_ptr;         /* Pointer value */
       };

       struct sigevent {
           int          sigev_notify; /* Notification method:SIGEV_NONE, SIGEV_SIGNAL, SIGEV_THREAD */
           int          sigev_signo;  /* Notification signal */
           union sigval sigev_value;  /* Data passed with
                                         notification */
           void       (*sigev_notify_function) (union sigval);
                            /* Function used for thread
                               notification (SIGEV_THREAD) */
           void        *sigev_notify_attributes;
                            /* Attributes for notification thread
                               (SIGEV_THREAD) */
           pid_t        sigev_notify_thread_id;
                            /* ID of thread to signal (SIGEV_THREAD_ID) */
       };

第一种方式SIGEV_NONE:异步IO线程不通知。在这种情况下,主线程可以采用aio_error循环测试,这种方式效率低下且浪费CPU的时间,另外可以采用aio_suspend,这样效率会更高一些。

第二种方式SIGEV_SIGNAL:采用信号的方式通知,这种方式效率比第一种方式要高,但是编写的信号处理函数必须保证是异步信号安全的。

第三种方式SIGEV_THREAD:创建一个线程来完成通知事件的处理,这里涉及到线程同步的问题。


二、Linux内核异步IO处理

(1)int io_setup(unsigned nr_events, aio_context_t *ctx_idp);

该函数会在内核中创建一个kioctx对象。确切地说,在一个进程描述符中的mm_struct结构中,有一个ioctx_list指针,它指向了一个kioctx链表。每次创建一个kioctx对象时,都将它插入到这个链表的头部。kioctx::user_id用来唯一的标识了这个kioctx。io_setup函数返回的ctx_idp指向的值就是这个user_id。

(2)int io_submit(aio_context_t ctx_id, long nr, struct iocb **iocbpp);

将iocb数组提交到ctx_id指定的kioctx对象。

(3)int io_cancel(aio_context_t ctx_id, struct iocb *iocb, struct io_event *result);

将iocb从ctx_id指定的kioctx对象中撤销掉。

(4)int io_getevents(aio_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

从ctx_id指定的kioctx对象的完成队列中读取事件。

(5)int io_destroy(aio_context_t ctx_id);

销毁ctx_id指定的kioctx对象。


io_setup返回的描述符(aio_context_t)与epoll_create返回的描述符类似。io_submit和io_cancel函数则对应epoll_ctl函数(EPOLL_CTL_ADD和EPOLL_CTL_DEL)。io_getevents则对应于epoll_wait。io_destroy对应于close。


有关Linux内核异步IO的实现后续会在这里加上~~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值