Linux驱动IO篇——异步IO

简介: Linux驱动IO篇——异步IO

前言

前几篇介绍了几种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可以在驱动中阻塞,但是上层的操作却是非阻塞的。

end

猜你喜欢

Linux驱动IO篇——IO多路复用

Linux驱动IO篇——阻塞/非阻塞IO

Linux驱动IO篇——ioctl设备操作

一个Linux驱动工程师必知的内核模块知识

Linux内核中常用的数据结构和算法

Linux内核中常用的C语言技巧

Linux内核基础篇——常用调试技巧汇总

Linux内核基础篇——动态输出调试

Linux内核基础篇——printk调试

Linux内核基础篇——initcall

相关文章
|
30天前
|
存储 JSON Linux
|
1月前
|
Oracle 关系型数据库 Linux
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
通过这一连串的步骤,可以专业且有效地在Linux下为Qt编译Oracle驱动库 `libqsqloci.so`,使得Qt应用能够通过OCI与Oracle数据库进行交互。这些步骤适用于具备一定Linux和Qt经验的开发者,并且能够为需要使用Qt开发数据库应用的专业人士提供指导。
50 1
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
|
19天前
|
Linux 开发者
深入理解Linux I/O模型:同步、异步、阻塞与非阻塞
【8月更文挑战第1天】在探索操作系统的奥秘中,I/O模型作为影响性能的关键因素之一,常常让开发者们感到困惑。本文将通过浅显易懂的语言和实际代码示例,揭示Linux下同步与异步、阻塞与非阻塞的概念及其区别,并指导如何在实际应用中选择合适的I/O模型以优化程序性能。
17 1
|
1月前
|
并行计算 数据处理 Python
Python并发编程迷雾:IO密集型为何偏爱异步?CPU密集型又该如何应对?
【7月更文挑战第17天】Python并发编程中,异步编程(如`asyncio`)在IO密集型任务中提高效率,利用等待时间执行其他任务。但对CPU密集型任务,由于GIL限制,多线程效率不高,此时应选用`multiprocessing`进行多进程并行计算以突破限制。选择合适的并发策略是关键:异步适合IO,多进程适合CPU。理解这些能帮助构建高效并发程序。
32 6
|
1月前
|
开发框架 并行计算 .NET
从菜鸟到大神:Python并发编程深度剖析,IO与CPU的异步战争!
【7月更文挑战第18天】Python并发涉及多线程、多进程和异步IO(asyncio)。异步IO适合IO密集型任务,如并发HTTP请求,能避免等待提高效率。多进程在CPU密集型任务中更优,因可绕过GIL限制实现并行计算。通过正确选择并发策略,开发者能提升应用性能和响应速度。
39 3
|
1月前
|
算法 Java 程序员
解锁Python高效之道:并发与异步在IO与CPU密集型任务中的精准打击策略!
【7月更文挑战第17天】在数据驱动时代,Python凭借其优雅语法和强大库支持成为并发处理大规模数据的首选。并发与异步编程是关键,包括多线程、多进程和异步IO。对于IO密集型任务,如网络请求,可使用`concurrent.futures`和`asyncio`;CPU密集型任务则推荐多进程,如`multiprocessing`;`asyncio`适用于混合任务,实现等待IO时执行CPU任务。通过这些工具,开发者能有效优化资源,提升系统性能。
51 4
|
1月前
|
开发框架 并行计算 算法
揭秘Python并发神器:IO密集型与CPU密集型任务的异步革命,你竟还傻傻分不清?
【7月更文挑战第18天】Python并发编程中,异步IO适合IO密集型任务,如异步HTTP请求,利用`asyncio`和`aiohttp`实现并发抓取,避免等待延迟。而对于CPU密集型任务,如并行计算斐波那契数列,多进程通过`multiprocessing`库能绕过GIL限制实现并行计算。选择正确的并发模型能显著提升性能。
45 2
|
1月前
|
开发框架 数据挖掘 .NET
显微镜下的Python并发:细说IO与CPU密集型任务的异步差异,助你精准施策!
【7月更文挑战第16天】在Python并发编程中,理解和区分IO密集型与CPU密集型任务至关重要。IO密集型任务(如网络请求)适合使用异步编程(如`asyncio`),以利用等待时间执行其他任务,提高效率。CPU密集型任务(如计算)则推荐使用多进程(如`multiprocessing`),绕过GIL限制,利用多核CPU。正确选择并发策略能优化应用性能。
31 2
|
1月前
|
SQL 关系型数据库 MySQL
实时计算 Flink版产品使用问题之在Flink算子内部使用异步IO可以通过什么办法实现
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
1月前
|
Linux 数据处理 C语言
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
【Linux】基础IO----系统文件IO & 文件描述符fd & 重定向(下)
37 0

热门文章

最新文章