Linux的Posix aio API

Q:什么是异步/同步io,什么是阻塞/非阻塞io?
A:按照获取数据的方式可将io区分为同步和异步的:同步是指需要调用者主动访问I/O口来进行数据的读写;异步是指调用者只需要提出io请求,然后处理其他事情,由内核完成读写操作之后通知调用者。重点就是读写操作是不是调用者主动完成的。按照执行的时候进程是否被挂起可将io区分为阻塞和非阻塞:阻塞当然就是进程需要等待io完成而被挂起;非阻塞就是执行io时,无论结果如何都会即刻返回。

Q:为什么会出现posix aio这一套异步I/O接口?
A:开发这套接口是为了提高CPU和I/O设备效率,提高程序设计的并发性。早先的Linux只有signal driven I/O,而这不能算是真正意义上的异步I/O,因为内核只是通知我们I/O口准备就绪了,但是还是需要调用者主动去处理读写操作;相比之下,使用Posxi aio接口时,调用者向内核提交I/O申请之后就不用管了,等到系统通知自己的时候,I/O操作已经完成。这一套posix aio最早出现在real-time draft standard (APUE,不知道怎么翻译)中,后来被加入到SUS的基础标准(base)中,也即是说所有unix-like platform都应该实现它。不过据说,posix aio实际效果不佳,或是因为Linux kernel aio的实现还不够完善,对posix aio的支持还不够(存疑)。Posix aio对网络的支持也不够好,但是对文件I/O支持很好,网络I/O方面使用epoll更加,二者正好形成互补。

Q:Posix aio使用到的核心数据结构和意义?
A :

struct aiocb {
int  aio_fildes;                /* 用于aio的文件描述符 */
off_t  aio_offset;              /* 读写操作的偏移 */
volatile void *aio_buf;         /* aio缓冲区 */
size_t  aio_nbytes;             /* 缓冲区大小 */
int  aio_reqprio;               /* aio优先级的建议值 */
struct sigevent  aio_sigevent;  /* aio完成后出发的notifying类型 */
int  aio_lio_opcode;            /* 用于list IO的操作码 */
};

aio_offset:此值与操作系统所维护的文件偏移量相互独立,所以不要对一个文件同时使 用常规I/O和异步I/O。同时,如果文件状态标志指定了O_APPEND,则此offset不起作用。
aio_reqprio:此数值必须在0到系统限制值之间(),否则后续接口对aiocb的参数检测不能通过,报EINVAL。
aio_lio_opcode:用于指明此aiocb对应的list I/O操作是读(LIO_READ)、写(LIO_WRITE)还是忽略(LIO_NOP)

struct sigevent {
    int  sigev_notify;          /* 触发通知的类型 */
    int  sigev_signo;           /* I/O完成后内核向调用进程发出的信号值 */
    union  sigval sigev_value;      /* 通知中附带的数据 */
    void  (*sigev_notify_function)(union sigval); /* I/O完成后触发的detach线程函数 */
    pthread_attr_t  *sigev_notify_attributes;    /* 控制上面线程的属性 */
};

sigev_notify:I/O操作完成后对调用进程的通知类型

tpyeaction
SIGEV_NONE不进行通知
SIGEV_SIGNAL触发sigev_signo信号,进程自己在signal handler中处理相 关事务。如果在信号注册中使用了SA_SIGINFO标志,则信号会被排队,并且struct siginfo中的sig_value域被设置成sigev_value(参见sigaction函数)
SIGEV_THREAD默认生成一个独立的detached状态的线程(可使用sigev_notify_attributes改变线程属性),线程中运行sigev_notify_function函数,参数为sigev_value

sigev_value:为一个联合类型,定义如下

union sigev_value {
    int  sival_int;
    void  *sival_ptr;
};

Q:aio接口函数的原型和用法?
A:所有aio接口函数都在头文件aio.h中声明,而实现是打包在实时库librt.a/librt.so中,所以在编译的时候需要加入-lrt。(ps:标准库函数一般都放在libc.a/libc.so中,而gcc会默认连接此库,所以在使用的时候不需要加-lc,其他的库却需要显式说明)

int aio_read(struct aiocb *aiocb);
int aio_write(struct aiocb *aiocb);

功能:向内核提出异步I/O申请,调用这两个函数并不会正真执行I/O操作,而只是将aiocb加入到aio的等待队列中,返回结果指明该申请是否成功提交。
注意:在异步I/O没有执行完或者撤销之前必须保证aiocb和I/O缓冲的内存不被占用。

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

向内核提出与磁盘同步的请求,与前两个函数类似,返回的结果只是表示请求是否被受理。只有在内核实际执行同步操作后,数据才会被写入磁盘中。(这里涉及到linux“延迟写”的概念,用户缓冲区数据通过系统调用(如:read/write)被复制到内核缓冲区中,默认条件下只有等到内核缓冲满或需要被复用,才会被放入I/O队列,执行实际的I/O操作,而fsync族的函数可以让内核缓冲区直接被放入I/O队列中,但这离实际写入磁盘还是有延迟。)

opmeaning
O_DSYNC效果相当于调用fdatasync函数,只对文件数据部分进行同步
O_SYNC效果相当于调用fsync,对文件的数据和属性都进行同步操作

int aio_error(const struct aiocb *aiocb);

对aiocb对应的异步I/O进行错误检测,返回值有以下几种情况:

ret valmeaning
0I/O操作成功
-1对aio_error自身调用失败,检查errno
EINPROGRESSI/O操作正在进行中
anything elseI/O操作出现错误,返回值为错误码

ssize_t aio_return(const struct aiocb *aiocb);

功能:当I/O操作完成后,调用此函数获得aiocb对应操作的返回值(read、wirte、fsync等操作的返回值)。
注意:此函数在一次异步I/O操作中只能调用一次,调用之后存储返回值的内存可能被复用;在I/O完成之前调用,返回值是undefined的。

int aio_suspend(const struct aiocb *const list[], int nent, 
const struct timespec *timeout);

阻塞进程以等待list参数中的I/O操作完成,跳出阻塞的情况如下:
1. 被信号中断而返回,返回值为-1,且errno = EINTR;
2. 超过阻塞时间限制,返回值为-1,且errno = EAGAIN;
3. 阻塞时限内有至少一个I/O操作完成,返回值为0;

int aio_cancel(int fd, struct aiocb *aiocb);

为fd对应文件上的一个没有完成的I/O提出撤销申请,发出请求不一定能被成功撤销。如果aiocb = NULL,则会试图撤销fd上所有的异步I/O请求。返回值说明撤销情况:

ret valmeaning
-1aio_cancel自身调用出现错误,检查errno
AIO_ALLDONE撤销前所有I/O操作都已经完成
AIO_CANCELED所有I/O操作都被撤销
AIO_NOTCANCELED至少有一个I/O操作没有被撤销

int lio_listio(int mode, struct aiocb *restrict const list[restrict],
int nent, struct sigevent *restrict sigev);

List I/O的调用接口。mode = LIO_WAIT同步调用,阻塞直到所有list中的I/O操作都完成才返回,此时sigev参数无效;mode = LIO_NOWAIT则是费阻塞调用,提交请求之后就返回,等到所有I/O操作完成之后,通过sigev中描述的方式通知调用进程。

实践

一个主进程fork三个子进程,父子进程之间通过FIFO通信,兄弟进程之间互不干扰。父进程使用非阻塞异步读FIFO端口,并使用detached线程处理读取到的结果;子进程使用阻塞异步写FIFO端口。兄弟进程之间会产生竞争关系,但是异步机制能够自己协调,当本进程确实获得了写FIFO的权利时,才会从aio_suspend返回,跳出阻塞来进行写操作。另外,主进程中FIFO要在fork子进程之后才能打开,否则打开操作会阻塞,因为FIFO写端没有打开;而主进程阻塞之后无法fork子進程,就没法作为FIFO写端,形成死锁

host.c:

/*****************************************************
 * the host use aio to monitor what the client wanna
 * say through FIFO
 * *************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <aio.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <signal.h>

#define BSZ 64
sig_atomic_t is_reading = 0;
sig_atomic_t is_quitting = 0;
int pid[3];

void err_exit(const char *str)
{
    perror(str);
    exit(1);
}

void read_thread(union sigval val)
{
    printf("%s", (char *)val.sival_ptr);
    is_reading = 0;
}

void main_work(int fd)
{
    static int cnt = 0;
    struct aiocb aio_ctl;
    char buf[BSZ];
    int fl, i;

    fl = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, fl | O_NONBLOCK);

    //aio control block init
    aio_ctl.aio_fildes = fd;
    aio_ctl.aio_offset = 0;
    aio_ctl.aio_buf = buf;
    aio_ctl.aio_nbytes = BSZ;
    //signal setting
    aio_ctl.aio_sigevent.sigev_notify = SIGEV_THREAD;
    aio_ctl.aio_sigevent.sigev_value.sival_ptr = (void *)buf;
    aio_ctl.aio_sigevent.sigev_notify_function = read_thread;
    aio_ctl.aio_reqprio = 0;

    memset(buf, 0, BSZ);

    while (!is_quitting)
    {
        if (read(STDIN_FILENO, buf, BSZ) > 0)
        {
            if (strncmp(buf, "quit", 4) == 0)
            {
                is_reading = 1;
                is_quitting = 1;
            }
            else
                printf("wrong code\n");
        }

        if (!is_reading)
        {
            aio_read(&aio_ctl);
            is_reading = 1;
        }

        sleep(1);
        printf("host work: %d\n", ++cnt);
    }

    for (i = 0; i < 3; i++)
    {
        kill(pid[i], SIGKILL);
        waitpid(pid[i], NULL, 0);
    }

    fcntl(STDIN_FILENO, F_SETFL, fl);
    return;
}

int main(void)
{
    int i;
    int fd;

    //fork
    for (i = 0; i < 3; i++)
    {
        if ((pid[i] = fork()) < 0)
            err_exit("fork error");
        //child
        else if (pid[i] == 0)
        {
            printf("child %d has been born\n", i);
            execl("client1", "client1", NULL);
            err_exit("execl error");
        }
        else if (pid[i] > 0 && i == 2)
        {
            //open fifo, cannot open before child been born
            //otherwise dead lock occur
            if ((fd = open("fifo", O_RDONLY)) < 0)
                err_exit("open error");

            main_work(fd);
        }
    }
    close(fd);
    return 0;
}

client.c:

/*****************************************************
*   send a string to standard output at a random time pause
****************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#include <aio.h>

#define BSZ 64

void err_exit(const char *str)
{
    perror(str);
    exit(1);
}

int main(void)
{
    char buf[BSZ] = {0};
    int fd = -1;
    int sleep_time;
    int err;
    static int call_id = 0;
    struct aiocb aio_blk;
    const struct aiocb *aio_list[1];

    //random seed
    srand((int)time(NULL));

    //open fifo
    fd = open("fifo", O_WRONLY);

    //aio block init
    aio_blk.aio_fildes = fd;
    aio_blk.aio_buf = buf;
    aio_blk.aio_nbytes = BSZ;
    aio_blk.aio_offset = 0;
    aio_blk.aio_reqprio = 0;
    aio_blk.aio_sigevent.sigev_notify = SIGEV_NONE;
    aio_list[0] = &aio_blk;

    while (1)
    {
        sleep_time = rand() % 10 + 1;
        sleep(sleep_time);
        sprintf(buf, "This is the %d call from child %d\n", ++call_id, getpid());
        if ((err = aio_write(&aio_blk)) < 0)
            err_exit("aio_write error");
        if((err = aio_suspend(aio_list, 1, NULL)) < 0)
            err_exit("aio_suspend error");
        if ((err = aio_error(&aio_blk)) == -1)
            err_exit("aio_error error");
        else if (err != 0)
            err_exit("aio_write error");
    }
    close(fd);
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值