当前位置:热点 > >正文
Linux驱动学习笔记:异步IO
2023-06-12 17:23:30    嵌入式Linux充电站

前言

前几篇介绍了几种IO模型,今天介绍另一种IO模型——异步IO。


(资料图)

相对于前面的几种IO模型,异步IO在提交完IO操作请求后就立即返回,程序不需要等到IO操作完成再去做别的事情,具有非阻塞的特性。

当底层把IO操作完成后,可以给提交者发送信号,或者调用注册的回调函数,告知请求提交者IO操作已完成。

在信号处理函数或者回调函数中,可以使用异步IO接口来获得IO的完成情况,比如获取读写操作返回的字节数或错误码、读取的数据等。

相关接口

struct aiocb结构体

struct aiocb {    int aio_fildes;               /* file descriptor */    off_t aio_offset;             /* file offset for I/O */    volatile void *aio_buf;       /* buffer for I/O */    size_t aio_nbytes;            /* number of bytes tdo transfer */    int aio_reqprio;              /* priority */    struct sigevent aio_sigevent; /* signal information */    int aio_lio_opcode;           /* operation for list I/O */};

aio_fildes:操作的文件描述符

aio_offset:偏移量,读写操作的起始位置

aio_buf:读写操作的数据缓冲区

aio_nbytes:传输数据的字节数

aio_reqprio:请求权限

aio_lio_opcode:操作码,表示读还是写,LIO_READ代表读,LIO_WRITE代表写。

struct sigevent结构体

struct sigevent {    int sigev_notify;          /* notify type */    int sigev_signo;           /* signal number */    union sigval sigev_value;  /* notify argument */    void (*sigev_notify_function)(union sigval); /* notify function */    pthread_attr_t *sigev_notify_attributes;    /* notify attrs */};

sigev_notify:通知类型。SIGEV_NONE表示不通知;SIGEV_SIGNAL表示IO操作完成后,收到sigev_signo指定的信号;SIGEV_THREAD表示IO操作完成,内核会创建一个新线程执行一个回调函数,函数由sigev_notify_function指定

sigev_signo:指定的信号

sigev_notify_function:回调函数

sigev_notify_attributes:使用默认属性,一般设置为NULL

应用层异步IO读写函数:

#include < aio.h >int aio_read(struct aiocb *aiocb);int aio_write(struct aiocb *aiocb); //返回值:成功返回0;失败返回-1

获取一个异步读、写或者同步操作的完成状态:

#include < aio.h >int aio_error(const struct aiocb *aiocb);

调用aio_return函数,可以用来判断异步IO的执行情况

#include < aio.h >ssize_t aio_return(const struct aiocb *aiocb);

下面以一个实际例子说明异步IO的用法

应用层

#include < stdio.h >#include < stdlib.h >#include < string.h >#include < sys/types.h >#include < sys/stat.h >#include < sys/ioctl.h >#include < fcntl.h >#include < errno.h >#include < poll.h >#include < linux/input.h >#include < aio,h >void aiow_completion_handler (sigval_t sigval){ int ret; struct aiocb *req; req = (struct aiocb *)sigval.sival_ptr; if (aio_error(req) == 0) {     ret = aio_return (req) ;     printf ("aio write %d bytes\\n", ret); } return;}void aior_completion_handler ( sigval_t sigval ){    int ret;    struct aiocb *req;        req = (struct aiocb * )sigval.sival_ptr;        if (aio_error (req > == 0 ) {     ret = aio_return (req);        if (ret)            printf ("aio read: %s\\n", (char * ) req- >aio_buf );    }        return;}        int main(int argc, char *argv []){        int ret;    int fd;    struct aiocb aiow,aior;        fd = open("/dev/vser0", O_RDWR);    if (fd == -1)        goto fail;        memset (&aiow, 0, sizeof (aiow));    memset (&aior, 0, sizeof (aior));        aiow.aio_fildes = fd;    aiow.aio_buf = malloc(32);    strcpy((char *)aiow.aio_buf,"aio test"); aiow.aio_nbytes = strlen ( (char * ) aiow.aio_buf ) + 1; aiow.a*io_of f set = 0; aiow.aio_sigevent.sigev_notify = SIGEV_THREAD; aiow.aio_sigevent.sigev_notify_function = aiow_completion_handler; aiow.aio_sigevent.sigev_notify_attributes = NULL; aiow.aio_sigevent.sigev_value.sival_ptr = &aiow;    aior.aio_fildes = fd; aior.aio_buf = malloc (32); aior.aio_nbytes = 32; aior.aio_offset = 0; aior.aio_sigevent •sigev_notify = SIGEV_THREAD;    aior.aio_sigevent.sigev_notify_function = aior_completion_handler; aior.aio_sigevent.sigev_notify_attributes = NULL; aior.aio_sigevent.sigev_value.sival_ptr = &aior;               while(1){        if(aio_write(&aiow) == -1)            goto fail;        if(aio_read(&aior) == -1)            goto fail;        sleep(1);    }           fail:           perror("aio test");           exit(EXIT_FAILURE);}

1、首先定义两个用于读和写的异步IO控制块struct aiob

2、初始化异步IO控制块,包括文件描述符、用读写的缓冲区、读写的字节数和回调函数

3、发起一个异步操作, 调用aio_wirte或者aio_read,该函数会立即返回,具体的读写操作会在驱动中完成

4、读写完成后,对应的回调函数会被自动调用

在写操作回调函数中,通过aio_erroraio_return获取了IO操作的错误码及实际的写操作的返回值。

在读操作回调函数中,除了可以获取完成状态,还可以从aio_buf中获取读取的数据。

注意这里的关键点:1、 调用aio_wirte或者aio_read,该函数会立即返回,具体的读写操作会在驱动中完成。2、读写完成后,对应的 回调函数会被自动调用

驱动层

驱动中异步IO相关代码如下:

DEFINE_KFIFO(vsfifo, char, 32);static ssize_t my_read(struct file *flip, char __user *buf, size_t count, loff_t *pos){ int ret;    unsigned int copied = 0;        ret = kfifo_to_user(&vsfifo, buf, count, &copied);     return ret == 0 ? copied : ret;}static ssize_t my_write(struct file *flip, const char __user *buf, size_t count, loff_tt *pos){ int ret;    unsigned int copied = 0;        ret = kfifo_from_user(&vsfifo, buf, count, &copied);         return ret == 0 ? copied :ret;}static ssize_t my_aio_read (struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos){ size_t read = 0; unsigned long i;    ssize_t reg;        for(i = 0; i < nr_segs; i++) {  ret = my_read(iocb- >ki_filp, iov[i].iov_base, iov[i].iov_len, &pos);        if(ret < 0)            break;        read += ret; }        return read ? read : -EFAULT;}static ssize_t my_aio_write (struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segsf loff_t pos){ size_t written = 0; unsigned longi;    ssize t ret;        for (i = 0;i < nr_segs;i++) {     ret = my_write(iocb- >lci_filp, iov [i].iov_base, iov [i].iov_len, &pos);        if(ret < 0)            break;        written += ret;    }        return written ? written : -EFAULT;}static struct file_operations my_ops = {......    .aio_read = my_aio_read,    .aio_write = my_aio_write,}

从驱动中可以看到,我们分别实现了两种读写函数my_readmy_writemy_aio_readmy_aio_write

在一般的读写操作函数实现中,是这样的:

static struct file_operations my_ops = {......    .read = my_read,    .write = my_write,}

my_read()my_wirte()函数实际上实现的是struct file_operations.read.write接口。

但是在异步IO的实现中,``my_aio_read()my_aio_write()分别实现的是.aio_read.aio_write`接口:

static struct file_operations my_ops = {......    .aio_read = my_aio_read,    .aio_write = my_aio_write,}

my_aio_read()函数又会去调用my_read()函数,my_aio_write()函数会去调用my_write()函数。

所以实际上,异步IO还是要先实现一遍.read.write的接口函数,然后在.aio_read.aio_write的接口实现中,去调用之前实现的.read.write接口。 只不过这里的主要区别是,异步IO会多次调用my_read()my_write()函数

以异步读为例,在 my_aio_read函数中,最关键的还是调用了my_read函数,但是my_read函数被调用了nr_serg次,这和分散/聚集操作是类似的,即 一次读操作实际上是分多次进行的,每次读取一定的字节数(iov[i].iov_len),然后分别将读到的数据放入分散的内存区域中(iov[i].iov_base

从驱动中不难发现,异步IO可以在驱动中阻塞,但是上层的操作却是非阻塞的。

X 关闭

往期话题
最近更新

Copyright ©  2015-2022 起点产业园区网版权所有  备案号:皖ICP备2022009963号-12   联系邮箱: 39 60 29 14 2@qq.com