APUE编程:26---高级I/O之(异步IO:struct  aiocb)

  • 使用gcc编译AIO程序时,gcc命令需要加上-lrt

一、前言

  • 信号机制提供了一种以异步形式通知某种事件已经发生的方法。由BSD和System V派生的所有系统都提供了某种形式的异步I/O,使用一个信号(在System V中是SIGPOLL,在BSD中是SIGIO)通知进程,对某个描述符所关心的某个事件已经发生
  • 但是这些形式的异步I/O是受限制的:它们并不能用在所有的文件类型上,而且只能使用一个信号。如果要对一个以上的描述符进行一步I/O,那么在进程接收到该信号时并不知道这一信号对应于哪一个描述符
  • SUSv4将通用的异步I/O机制从实时扩展部分调整到基本规范部分,这种机制解决了这些陈旧的异步I/O设施存在的局限性
  • 在我们了解异步I/O的不同方法之前,需要先讨论一下成本。在使用异步I/O的时候,要通过选择来灵活处理多个并发操作,这会使得应用程序的设计复杂化。更简单的做法可能是使用多线程,使用同步模型来编写程序,并让这些线程以异步的方式运行

、不同系统的异步I/O

System  V异步IO

  • 在System V中,异步I/O是STREAMS系统的一部分。它只对STREAMS设备和STREAMS管道起作用。System V的异步I/O信号是SIGPOLL
  • 为了对一个STREAMS设备启动异步I/O,需要调用ioctl。它的第二个参数(request)是I_SETSIG。第三个参数是由下图中的常量构成的整型值。这些常量在<stropts.h>中定义

  • 与STREAMS机制相关的接口在SUSv4中已被标记为启用,所以这里不讨论它们的任何细节
  • 除了调用ioctl说明产生SIGPOLL信号的条件以外,还应为该信号建立信号处理程序。参阅信号表(https://blog.csdn.net/qq_41453285/article/details/89156861),对于SIGPOLL的默认动作是终止该进程,所以应当在调用ioctl之前建立信号处理程序

BSD异步IO

  • 在BSB派生的系统中,异步I/O是SIGIO和SIGURG两个信号的组合。SIGIO是通用异步I/O信号,SIGURG则用来通知进程在网络连接上到达了带外的数据
  • 为了接收SIGIO信号,需执行下列三步:
    • (1)调用signal或sigaction为SIGIO信号建立信号处理程序
    • (2)以命令F_SETOWN调用fcntl来设置进程ID和进程组ID,它们将接收对于该描述符的信号
    • (3)以命令F_SETFL调用fcntl设置O_ASYNC文件状态标志,使在该描述符上可以进行异步I/O
  • 第(3)步仅能对指向终端或网络的描述符执行,这是BSD异步I/O设施的一个基本限制
  • 对于SIGURG信号,只需执行第(1)步和第(2)步。该信号仅对引用支持带外数据的网络连接描述符而产生
  • 在信号驱动I/O中有一个例子,可以参阅:https://blog.csdn.net/qq_41453285/article/details/89607010

POSIXS异步IO

  • 本文章介绍POSIXS异步I/O,POSIXS异步I/O接口为不同类型的文件进行了异步I/O提供了一套一致的方法。这些接口来自实时草案标准,该标准是Single UNIX Specification的可选项。在SUSv4中,这些接口被移到了基本部分中,所有现在所有平台都被要求支持这些接口
  • 使用POSIX异步I/O接口,会带来下列麻烦:
    • 每个异步操作有3处可能产生错误的地方:一处在操作提交的部分,一处在操作本身的结果,还有一处在用于决定异步操作状态的函数中
    • 与POSIX异步I/O接口的传统方法相比,它们本身设计大量的额外设置和处理规则
      • 事实上,并不能把非异步I/O函数称为“同步”的,因为尽管它们相对于程序来说是同步的,但相对于I/O来说并非如此。回顾前面对于同步写的讨论,当write函数的调用返回时,写的数据是持久的,我们称这个写操作为“同步”的。也不能依靠把传统的调用归类为“标准”的I/O调用来区别传统的I/O函数和异步I/O函数,因为这样会使它们和标准I/O库中的函数调用相混淆。为了避免产生这种混淆,本文我们把read和write函数归类为“传统”的I/O函数
    • 从错误中恢复可能比较困难。举例来说,如果提交了多个异步写操作,其中一个失败了,下一步我们应该怎么做?如果这些写操作是相关的,那么可能还需要撤销所有成功的写操作

三、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 to transfer */
    int aio_reqprio;              /* priority */
    struct sigevent aio_sigevent; /* signal information */
    int aio_lio_opcode;           /* operation for list I/O */
};
  •  功能:异步I/O接口使用AIO控制块来描述I/O操作,aiocb结构体定义了AIO控制块。AIO控制快只是用来初始化异步IO的,并不做任何操作

aio_fildes成员

  • 表示被打开用来读和写的文件描述符

aio_offset成员

  • 偏移量,表示读或写操作的起始位置
  • 注意,异步I/O操作必须显式地指定偏移量。异步I/O接口并不影响由操作系统维护的文件偏移量。只要不在同一个进程里把异步I/O函数和传统的I/O函数混在一起用在同一个文件上,就不会导致什么问题。同时值得注意的是,如果使用异步I/O接口向一个以追加模式(使用O_APPEND)打开的文件中写入数据,AIO控制块中的aio_offset字段会被系统忽略

aio_buf成员

  • 对于读写操作,数据会被复制到缓冲区中,aio_buf就作为此缓冲区

aio_nbytes成员

  • 指定要读或写的字节数

aio_reqpiro成员

  • 待续

aio_lio_opcode成员

  • 此字段指定了该操作是一个读操作还是一个写操作
  • 取值如下:
    • LIO_READ:该操作是一个读操作
    • LIO_WRITE:该操作是一个写操作
    • LIO_NOP:将被忽略的空操作
  • 读操作AIO控制块就传给aio_read函数处理,写操作AIO控制块就传给aio_write函数处理

aio_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 */
};
  • 在I/O时间完成后,会通过aio_sigevent通知应用程序
  • 其相关成员如下:
    • sigev_notify:控制通知的类型,取值可能是以下3个中的一个:
      • SIGEV_NONE:异步I/O请求完成后,不通知进程
      • SIGEV_SINGNAL:异步I/O请求完成后,产生由sigev_signo字段指定的信号。如果应用程序已选择捕捉信号,且在建立信号处理程序的时候指定了SA_SIGINFO标志,那么该信号将被入队(如果实现支持排队信号)。信号处理程序会传送给一个siginfo结构,该结构的si_value字段被设置为sigev_value(如果使用了SA_SIGINFO标志)
      • SIGEV_THREAD:
    • sigev_signo:
    • sigev_value:传递给回调函数的参数。因为是个枚举,所以初始化sigev_value成员的时候,只能选择其中一者进行初始化
    • sigev_notify_function:回调函数(注意事项:此回调函数可以做很多的事情,因为AIO是内核单独帮你开一个线程进行的。但是信号处理函数signal、sigaction就不能做太多的事情)。sigval联合包含下列字段:
      • void *sival_ptr;
      • int sival_int;
    • sigev_notify_attributes:一般使用默认属性,设置为NULL

四、 aio_read、aio_write函数

#include <aio.h>
int aio_read(struct aiocb *aiocb);
int aio_write(struct aiocb *aiocb);

//返回值:成功返回0;失败返回-1
  • 功能:struct  aiocb结构体只是用来初始化AIO控制板,并不执行实际IO操作。需要使用aio_read、aio_write来开启异步读或异步写操作
  • 注意事项:
    • aio_read、aio_write只是将异步IO请求放入操作系统的等待处理队列中,也不执行实际异步IO操作
    • IO操作在等待时,必须确保AIO控制板和数据库缓冲区稳定;保证对应的内存始终是合法的,否则不能被复用
  • 返回值:当两个函数成功返回后,异步IO就被成功的放入等待处理队列中了

五、aio_fsync函数

#include <aio.h>
int aio_fsync(int op, struct aiocb *aiocb);

//返回值:成功返回0;失败返回-1
  • 功能:如果想要所有等待的异步操作不等待而写入持久化的存储中,可以设立一个AIO控制板并调用该函数
  • aio_read、aio_write函数会进行数据的缓冲。使用了aio_fsync就不必再去使用aio_read和aio_write了
  • 与aio_read、aio_write一样,此函数成功返回后,在之后进行的异步IO操作中,数据不会被持久化

op参数

  • 如果为O_DSYNC:那么操作执行起来就会像调用fdatasync函数一样
  • 如果为O_SYNC:那么操作执行起来就会像调用fsync函数一样

六、aio_error函数

#include <aio.h>
int aio_error(const struct aiocb *aiocb);
  • 功能:为了获取一个异步读、写或者同步操作的完成状态,需要调用aio_error函数

返回值

  • 0:异步操作成功返回,此时可以调用aio_return函数获取操作返回值
  • -1:只是对aio_error的调用失败,并不设置errno
  • EINPROGRESS:异步读、写或同步操作仍在等待
  • ECANCELLED:请求被应用程序取消
  • 其他情况:可以调用aio_return函数获取异步操作的返回值

七、aio_return函数

#include <aio.h>
ssize_t aio_return(const struct aiocb *aiocb);
  • 功能:调用此函数,可以用来判断异步IO的执行情况

注意事项

  • 知道异步操作完成之前,都需要小心不要调用aio_return函数,因为操作完成之前的结果是未定义的
  • 还需要小心对每个异步操作只调用一次aio_return
  • 一旦调用了此函数,操作系统就可以释放掉包含了IO操作返回值的记录

返回值

  • -1:aio_return函数本身失败,返回-1,并设置errno
  • 其他情况:将返回异步操作的结果,即会返回read、write或者fsync在被成功调用时可能返回的结果

八、aio_suspend函数

#include <aio.h>
int aio_suspend(const struct aiocb *const list[], int nent,const struct timespec *timeout);

//返回值:见下
  • 功能:异步IO正常执行之后就可以执行其他事务,但是当其他所有事务都完成之后,异步操作还没有完成,就可以调用aio_suspend函数来阻塞进程,直到异步操作完成

返回值

  • -1,erron=EINTR:如果程序被一个信号中断,返回-1并设置erron=EINTR
  • -1,errno=EAGAIN:如果在没有任何IO操作完成的情况下,并且阻塞的时间超过了timeout值,那么-1并设置erron=EAGAIN
  • 0:如果有任何IO操作完成,将返回0

直接返回:如果在调用aio_suspend操作时,所有的异步IO操作都已完成,那么函数将在不阻塞的情况下直接返回

参数

  • list:一个指向AIO控制块数组的指针,表名要阻塞等待的AIO控制块
  • nent:指定了参数1数组的条目数(数组中的空指针会被跳过)
  • timeout:指定aio_suspend函数的阻塞等待时间(如果为空指针,则永久阻塞等待)

九、aio_cancel函数

#include <aio.h>
int aio_cancel(int fd, struct aiocb *aiocb);
  • 功能:如果我们不想再完成等待中的异步IO操作,可以使用该函数来取消他们
  • 如果异步IO操作被成功取消,对应的AIO控制块调用aio_error函数将会返回错误ECANCELED。如果操作不能被取消,对应的AIO控制块不会因为对aio_cancel的调用而被修改

参数

  • fd:指定了那么未完成的异步IO操作的文件描述符
  • aiocb:如果此参数非NULL,则此函数尝试取消所有由此AIO控制块秒数的单个异步IO操作。如果该参数为NULL,系统将会尝试取消所有参数1指定文件上未完成的异步IO操作
  • 注意事项:上面只所以说系统“尝试”取消操作,是因为无法保证系统能够取消正在进程中的任何操作

返回值

  • AIO_ALLDONE:所有操作在尝试取消它们之前已经完成
  • AIO_CANCELED:所有要求的操作已被取消
  • AIO_NOTCANCELED:至少有一个要求的操作没有被取消
  • -1:对aio_cancel的调用失败,并设置errno错误码

十、lio_listio函数

#include <aio.h>
int lio_listio(int mode, struct aiocb *restrict const list[restrict],int nent, struct sigevent *restrict sigev);

//返回值:成功返回0;出错返回-1
  • 功能:此函数可以控制一个AIO控制块列表来描述IO请求

mode参数

  • 决定了IO是否真的要异步
LIO_WAIT

lio_listio函数将在参数2指定IO控制块列表中的所有IO操作完成之后才返回

在这种情况下,sigev参数将被忽略

LIO_NOWAITlio_listio函数将IO请求放入队列中之后就返回了,并不等待实际IO操作

list参数

  • 指向一个AIO控制块数组,指定了lio_listio要操作的IO

nent参数

  • 指定了参数2数组中的元素个数。如果参数2数组中包含NULL指针,空指针将被忽略

sigev参数

  • 此参数指向一个函数,当参数2数组中的所有的AIO完成之后,就会调用此参数的函数通知调用lio_listio的进程。如果不想被通知,可以将此参数设置为NULL
  • 注意:此参数对应的函数与每个AIO本身自己的异步通知函数不同,这个函数是lio_listio指定的AIO控制块数组所有的AIO都操作完成之后,返回给调用进程的
  • 如果mode参数为LIO_WAIT,sigev参数将被忽略

十一、IO操作数设置

  • 调用sysconf函数把name参数设置为_SC_IO_LISTIO_MAX来设定AIO_LISTIO_MAX的值
  • 调用sysconf函数把name参数设置为_SC_AIO_MAX来设定AIO_MAX的值
  • 调用sysconf函数把name参数设置为_SC_AIO_PRIO_DELTA_MAX来设定AIO_PRIO_DELTA_MAX的值

十二、异步读写案例

  • 异步读
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>
#define BUFFER_SIZE 1024

int main(int argc,char **argv)
{
    struct aiocb wr;//定义aio控制块结构体
    int ret,fd;
    char str[20] = {"hello,world"};//写的内容

    bzero(&wr,sizeof(wr));//置零wr结构体

    fd = open("test.txt",O_WRONLY | O_APPEND);
    if(fd < 0){
        perror("test.txt");
    }

    wr.aio_buf = str;

    //填充aiocb结构
    wr.aio_fildes = fd;
    wr.aio_nbytes = sizeof(str);

    //异步写操作
    ret = aio_write(&wr);
    if(ret < 0){
        perror("aio_write");
    }

    //等待异步写完成
    while(aio_error(&wr) == EINPROGRESS)//如果等于EINPROGRESS,说明异步IO还在进行
    {
        printf("hello,world\n");
    }

    //获得异步写的返回值
    ret = aio_return(&wr);
    printf("\n\n\n返回值为:%d\n",ret);

    return 0;
}
  • 异步写
#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>
#define BUFFER_SIZE 1024
int MAX_LIST = 2;

int main(int argc,char **argv)
{
    //aio操作所需结构体
    struct aiocb rd;
    int fd,ret,couter;

    fd = open("test.txt",O_RDONLY); //读取这个文件
    if(fd < 0){
        perror("test.txt");
    }

    bzero(&rd,sizeof(rd));//将rd结构体清空

    //为rd.aio_buf分配空间
    rd.aio_buf = malloc(BUFFER_SIZE + 1);

    //填充rd结构体
    rd.aio_fildes = fd;
    rd.aio_nbytes = BUFFER_SIZE; //需要读取的最大字节数(不一定要读取这么多,读完了就不读了)
    rd.aio_offset = 0; //文件偏移量

    //进行异步读操作
    ret = aio_read(&rd);
    if(ret < 0){
        perror("aio_read");
        exit(1);
    }
	//do other things
	
    couter = 0;
	//循环等待异步读操作结束
    while(aio_error(&rd) == EINPROGRESS)//如果等于EINPROGRESS,说明异步IO还在进行
    {
       //printf("第%d次\n",++couter);
    }
	
    //获取异步读返回值
    ret = aio_return(&rd);
	
    printf("\n\n返回值为:%d\n",ret);
	printf("%s\n",rd.aio_buf);
    
    free(rd.aio_buf);
    close(fd);
    return 0;
}

十三、aio_suspend演示案例

#include<stdio.h>
#include<stdlib.h>
#include<strings.h>
#include<aio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define BUFFER_SIZE 1024
#define MAX_LIST 2

int main(int argc,char *argv[])
{
    if(argc!=2){
        perror("please enter [file name]");
        exit(EXIT_FAILURE);
    }

    int fd;
    int ret;
    struct aiocb rd;
    struct aiocb* aiocblist[MAX_LIST];
   
    fd=open(argv[1],O_RDONLY);
    if(fd<0){
        perror("open");
        exit(EXIT_FAILURE);
    }

    bzero(&rd,sizeof(rd));
    rd.aio_buf=malloc(BUFFER_SIZE+1);
    rd.aio_fildes=fd;
    rd.aio_nbytes=BUFFER_SIZE+1;
    rd.aio_offset=0;

    aiocblist[0]=&rd;

    ret=aio_read(&rd);
    if(ret<0){
        perror("aio_read");
        exit(EXIT_FAILURE);
    }

    printf("开始异步IO等待\n");
    aio_suspend(aiocblist,MAX_LIST,NULL);

    ret=aio_return(&rd);
    if(ret!=-1)
        printf("AIO return:%d\n",ret);
   
    close(fd);
    exit(0);
}

十四、lio_listio演示案例

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<aio.h>
#include<strings.h>

#define LIST_MAX 2
#define BUFFER_SIZE 1024

int main(int argc,char *argv[])
{
    if(argc!=3){
        perror("please enter [read filename] [write filename]");
        exit(EXIT_FAILURE);
    }

    int rd,wd;
    int ret;
    struct aiocb rdAio,wdAio;
    struct aiocb* aiolist[LIST_MAX];

    rd=open(argv[1],O_RDONLY);
    if(rd<0){
        perror("open");
        exit(EXIT_FAILURE);
    }
    bzero(&rdAio,sizeof(rdAio));
    rdAio.aio_fildes=rd;
    rdAio.aio_offset=0;
    rdAio.aio_nbytes=BUFFER_SIZE;
    rdAio.aio_lio_opcode=LIO_READ;
    rdAio.aio_buf=malloc(BUFFER_SIZE);
    aiolist[0]=&rdAio;

    wd=open(argv[2],O_WRONLY|O_APPEND);
    if(wd<0){
        perror("open");
        exit(EXIT_FAILURE);
    }
    bzero(&wdAio,sizeof(wdAio));
    wdAio.aio_fildes=wd;
    wdAio.aio_buf="HelloWorld";
    wdAio.aio_offset=0;
    wdAio.aio_lio_opcode=LIO_WRITE;
    wdAio.aio_nbytes=10;
    aiolist[1]=&wdAio;

    lio_listio(LIO_WAIT,aiolist,LIST_MAX,NULL);

    ret=aio_return(&rdAio);
    if(ret!=-1)
        printf("read aio return:%d\n",ret);

    ret=aio_return(&wdAio);
    if(ret!=-1)
        printf("write aio return:%d\n",ret);

    exit(0);
}

十五、异步IO回调函数的使用

#include<stdio.h>
#include<stdlib.h>
#include<aio.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#define BUFFER_SIZE 1024

void aio_completion_handler(sigval_t sigval)
{
    struct aiocb* rdAio;

    rdAio=(struct aiocb*)sigval.sival_ptr;

    if(aio_error(rdAio)==0)
    {
        int ret;
        ret=aio_return(rdAio);
        if(ret>0)
            printf("aio read value:%d\n",ret);
    }
}
int main(int argc,char *argv[])
{
    if(argc!=2){
        perror("please enter [read filename]");
        exit(EXIT_FAILURE);
    }

    int rd;
    int ret;
    struct aiocb rdAio;

    rd=open(argv[1],O_RDONLY);
    if(rd<0){
        perror("open");
        exit(EXIT_FAILURE);
    }

    bzero(&rdAio,sizeof(rdAio));
    rdAio.aio_fildes=rd;
    rdAio.aio_nbytes=BUFFER_SIZE;
    rdAio.aio_buf=malloc(BUFFER_SIZE);
    rdAio.aio_offset=0;
    rdAio.aio_sigevent.sigev_notify=SIGEV_THREAD;
    rdAio.aio_sigevent.sigev_notify_function=aio_completion_handler;
    rdAio.aio_sigevent.sigev_notify_attributes=NULL;
    rdAio.aio_sigevent.sigev_value.sival_ptr=&rdAio;

    ret=aio_read(&rdAio);
    if(ret<0){
        perror("aio_read");
        exit(EXIT_FAILURE);
    }
    
    printf("start aio read\n");
    sleep(1);
    printf("end aio read\n");
    exit(0);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董哥的黑板报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值