Linux系统编程(十)--高级IO-异步IO

文章目录

    • 1 同步IO与异步IO
    • 2 POSIX异步IO(aiocb)
    • 3 异步操作状态
      • 3.1 aio_error
      • 3.2 aio_return
    • 4 等待异步IO操作
    • 5 异步IO取消操作
    • 6 批量请求
    • 7 异步通知
      • 7.1 两种通知方式
      • 7.2 aiocb的成员aio_sigevent
      • 7.3 sigevent 的成员

1 同步IO与异步IO

塞和非阻塞

从简单的开始,我们以经典的读取文件的模型举例。(对操作系统而言,所有的输入输出设备都被抽象成文件。)

在发起读取文件的请求时,应用层会调用系统内核的I/O接口。

如果应用层调用的是阻塞型I/O,那么在调用之后,应用层即刻被挂起,一直出于等待数据返回的状态,直到系统内核从磁盘读取完数据并返回给应用层,应用层才用获得的数据进行接下来的其他操作。

如果应用层调用的是非阻塞I/O,那么调用后,系统内核会立即返回(虽然还没有文件内容的数据),应用层并不会被挂起,它可以做其他任意它想做的操作。(至于文件内容数据如何返回给应用层,这已经超出了阻塞和非阻塞的辨别范畴。)

这便是(脱离同步和异步来说之后)阻塞和非阻塞的区别。总结来说,是否是阻塞还是非阻塞,关注的是接口调用(发出请求)后等待数据返回时的状态。被挂起无法执行其他操作的则是阻塞型的,可以被立即「抽离」去完成其他「任务」的则是非阻塞型的。

同步和异步

阻塞和非阻塞解决了应用层等待数据返回时的状态问题,那系统内核获取到的数据到底如何返回给应用层呢?这里不同类型的操作便体现的是同步和异步的区别。

对于同步型的调用,应用层需要自己去向系统内核问询,如果数据还未读取完毕,那此时读取文件的任务还未完成,应用层根据其阻塞和非阻塞的划分,或挂起或去做其他事情(所以同步和异步并不决定其等待数据返回时的状态);如果数据已经读取完毕,那此时系统内核将数据返回给应用层,应用层即可以用取得的数据做其他相关的事情。

而对于异步型的调用,应用层无需主动向系统内核问询,在系统内核读取完文件数据之后,会主动通知应用层数据已经读取完毕,此时应用层即可以接收系统内核返回过来的数据,再做其他事情。

这便是(脱离阻塞和非阻塞来说之后)同步和异步的区别。也就是说,是否是同步还是异步,关注的是任务完成时消息通知的方式。由调用方盲目主动问询的方式是同步调用,由被调用方主动通知调用方任务已完成的方式是异步调用。

Linux系统编程(十)--高级IO-异步IO_第1张图片

举例说明

老张爱喝茶,废话不说,煮开水。 出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。 1 老张把水壶放到火上,立等水开。(同步阻塞) 老张觉得自己有点傻 2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。 3 老张把响水壶放到火上,立等水开。(异步阻塞) 老张觉得这样傻等意义不大 4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞) 老张觉得自己聪明了。

所谓同步异步,只是对于水壶而言。 普通水壶,同步;响水壶,异步。 虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。 同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。

所谓阻塞非阻塞,仅仅对于老张而言。 立等的老张,阻塞;看电视的老张,非阻塞。 情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

2 POSIX异步IO(aiocb)

除了 POSIX 异步 IO 以外,还有 System V 异步 IO、BSD 异步 IO。这里只讲 POSIX 异步IO,对应aio开头的一系列函数:

函数 语义
aio_read 请求异步读操作
aio_write 请求异步写操作
aio_error 检查异步请求的状态
aio_return 获得完成的异步请求的返回状态
aio_suspend 阻塞调用进程,直到一个或多个异步请求已经完成(或失败)
aio_cancel 取消异步 I/O 请求
lio_listio 发起一系列 I/O 操作

异步 IO 控制块(asynchronous I/O control block, aiocb)

#include 

struct aiocb {
    /* 下面所有字段依赖于具体实现 */

    int             aio_fildes;     /* 文件描述符 */
    off_t           aio_offset;     /* 文件偏移 */
    volatile void* aio_buf;        /* 缓冲区地址 */
    size_t          aio_nbytes;     /* 传输的数据长度 */
    int             aio_reqprio;    /* 请求优先级 */
    struct sigevent aio_sigevent;   /* 通知方法 */
    int             aio_lio_opcode; /* 仅被 lio_listio() 函数使用 */

    /* Various implementation-internal fields not shown */
};

aio_fildes: 文件描述符,相当于read 或 write 函数的第一个fd参数,表示想操作哪个文件描述符上的IO。

aio_offset: 文件偏移指针,表示想从文件的哪个位置开始操作。比如从文件的第10个字节开始读,就设成10。

aio_buf: 缓冲区的地址。

aio_nbytes: 要传输多少字节的数据。

实验:异步读操作的例子

// my_aio_read.c
#include 
#include 
#include 
#include 
#include 
#include 
// 需要包含 aio.h 文件
#include 
#include 
#include 

#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)

int main() {
    int fd, ret;
    char buf[64];
    // 定义一个异步控制块结构体,不懂没关系,不用管
    struct aiocb my_aiocb;

    // 初始化
    bzero((char*)&my_aiocb, sizeof(struct aiocb));

    my_aiocb.aio_buf = buf; // 告诉内核,有数据了就放这儿
    my_aiocb.aio_fildes = STDIN_FILENO; // 告诉内核,想从标准输入读数据
    my_aiocb.aio_nbytes = 64; // 告诉内核,缓冲区大小只有 64
    my_aiocb.aio_offset = 0; // 告诉内核,从偏移为 0 的地方开始读

    // 发起异步读操作,立即返回。你并不知道何时 buf 中会有数据
    ret = aio_read(&my_aiocb);
    if (ret < 0) ERR_EXIT("aio_read");

    // 不断的检查异步读的状态,如果返回 EINPROGRESS,说明异步读还没完成
    // 轮询检查状态是一种很笨的方式,其实可以让操作系统用信号的方式来通知,或者让操作系统完成读后主动创建一个线程执行。
    while (aio_error(&my_aiocb) == EINPROGRESS) {
        write(STDOUT_FILENO, ".", 1);
        sleep(1);
    }

    // 打印缓冲区内容,你并不知道内核是什么时候将缓冲区中的 hello 复制到你的 buf 中的。
    printf("content: %s\n", buf);

    return 0;
}
  • 编译和运行

注意编译的时候需要链接 rt 运行库。

$ gcc my_aio_read.c -o my_aio_read -lrt1

运行后,如果你什么也不操作,程序会在屏幕上打点。这里,我输入了 hello 后回车。

$ ./my_aio_read 
...he.llo..
content: hello

3 异步操作状态

使用 aio_read 或 aio_write 等函数发起了异步读或写时,内核就自己去干活了,轮询查看结果。还可以异步通知,后面讲。

// 不断的检查异步读的状态,如果返回 EINPROGRESS,说明异步读还没完成
// 轮询检查状态是一种很笨的方式,其实可以让操作系统用信号的方式来通知,或者让操作系统完成读后主动创建一个线程执行。在后面我们会继续学习这两种通知方式。
while (aio_error(&my_aiocb) == EINPROGRESS) {
    write(STDOUT_FILENO, ".", 1);
    sleep(1);
}

3.1 aio_error

作用:获取异步请求的状态。

int aio_error(const struct aiocb *aiocb);

返回值:

  • EINPROGRESS,异步请求未完成。

  • ECANCELED,异步请求被取消。

  • 0,请求成功完成。

  • > 0 的错误码,表明异步操作失败,该值相当于同步IO函数 read、write 出错时,设置的errno变量。

aio_error 是线程安全的。

3.2 aio_return

作用:获取异步请求的状态。

ssize_t aio_return(struct aiocb *aiocbp);

注意:对于每个请求只能使用一次,而且要在 aio_error 返回值不是 EINPROGRESS 的情况下使用。

返回值:

  1. 如果异步操作完成了,该函数返回值就相当于同步IO类函数 read, write, fsync 或 fdatasync 等的返回值。

  2. 如果异步操作未完成的情况下你使用了它,结果是未定义的!

实验:程序 my_aio_return.c 实际上只是将前面的代码稍稍做了修改。

// my_aio_return.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)

int main() {
    int fd, ret;
    char buf[64] = { 0 };
    struct aiocb my_aiocb;

    bzero((char*)&my_aiocb, sizeof(struct aiocb));

    my_aiocb.aio_buf = buf;
    my_aiocb.aio_fildes = STDIN_FILENO;
    my_aiocb.aio_nbytes = 64;
    my_aiocb.aio_offset = 0;

    ret = aio_read(&my_aiocb);
    if (ret < 0) ERR_EXIT("aio_read");

    while (aio_error(&my_aiocb) == EINPROGRESS) {
        sleep(1);
    }

    // 获取最终的异步操作状态
    ret = aio_return(&my_aiocb);
    if (ret < 0) ERR_EXIT("aio_return");

    // 打印内容和返回值。
    printf("content: %s, return: %d\n", buf, ret);

    return 0;
}

编译运行

$ gcc my_aio_return.c -o my_aio_return -lrt
$ ./my_aio_return 
hello
content: hello
, return: 6

程序启动后,在终端输入了字符串 hello 后回车。然后在程序的界面打印出 hello 的内容,注意后面换行符也被送到缓冲区了,aio_return 返回的是读到的字节数。

4 等待异步IO操作

类似线程中的 pthread_join 函数,在异步 IO 中,aio_suspend等待指定的异步 IO 操作完成才返回。

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

参数

  • aiocb_list:数组,存储的元素类型是 const struct aiocb* 类型。如果某个元素为 NULL,aio_suspend 会忽略它。

  • nitems:aiocb_list 数组大小。

  • timeout:超时时间,设置成 NULL 表示永远等待,直到异步 IO 操作完成。

函数语义

aio_suspend 函数会阻塞调用线程,直到发生下面的事情:

  1. aiocb_list 中的一个或多个请求已完成。
  2. 收到信号,被信号打断。
  3. timeout 不空,超时时间已过。

实验

程序 my_aio_suspend 同样是对前面程序的修改。

// my_aio_suspend.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)

int main() {
    int fd, ret;
    char buf[64] = { 0 };
    struct aiocb my_aiocb;

    bzero((char*)&my_aiocb, sizeof(struct aiocb));

    my_aiocb.aio_buf = buf;
    my_aiocb.aio_fildes = STDIN_FILENO;
    my_aiocb.aio_nbytes = 64;
    my_aiocb.aio_offset = 0;

    ret = aio_read(&my_aiocb);
    if (ret < 0) ERR_EXIT("aio_read");

    // 为了传递给 aio_suspend 用,创建一个大小为 5 的数组
    const struct aiocb* aio_list[5] = { NULL };

    // 将其一中元素赋值,不一定是第 0 个,随便啊。
    aio_list[0] = &my_aiocb;

    // 只要 my_aiocb 这个异步读还没完成,aio_suspend 就会阻塞
    ret = aio_suspend(aio_list, 5, NULL);
    if (ret < 0) ERR_EXIT("aio_suspend");
    puts("aio_suspend return");

    // 实际上,这个 while 循环我们不可能看到它执行的。写在这里只是为了演示。
    while (aio_error(&my_aiocb) == EINPROGRESS) {
        puts("EINPROGRESS");
    }

    // 获取返回值。
    ret = aio_return(&my_aiocb);
    if (ret < 0) ERR_EXIT("aio_return");

    printf("content: %s, return: %d\n", buf, ret);

    return 0;
}

编译运行

$ gcc my_aio_suspend.c -o my_aio_suspend -lrt
$ ./my_aio_suspend 
hello
aio_suspend return
content: hello
, return: 6

启动程序后,程序首先会在 aio_suspend 处阻塞,在终端输入 hello 后,aio_suspend 就返回了。

5 异步IO取消操作

int aio_cancel(int fd, struct aiocb *aiocbp)

参数

  • fd:想取消哪个描述符上的异步 IO 请求。

  • aiocbp:空,表示取消该描述符上所有的异步 IO 请求,不空,取消由 aiocbp 指定的异步 IO 请求。

返回值

  • AIO_CANCELED:所有请求被成功取消。

  • AIO_NOTCANCELED:至少有一个请求未被取消,因为它处于异步 IO 的处理过程中,也就是 aio_error 返回值为 EINPROGRESS 的时候。

  • AIO_ALLDONE:所有请求都已经完成了。

  • -1:有错误发生,同时设置 errno 变量。

实验

程序 my_aio_cancel.c 演示了 aio_cancel 的使用。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)

int main() {
  int fd, ret;
  char buf[64] = { 0 };
  struct aiocb my_aiocb;

  bzero((char*)&my_aiocb, sizeof(struct aiocb));

  my_aiocb.aio_buf = buf;
  my_aiocb.aio_fildes = STDIN_FILENO;
  my_aiocb.aio_nbytes = 64; 
  my_aiocb.aio_offset = 0;

  // 发起异步读请求
  ret = aio_read(&my_aiocb);
  if (ret < 0) ERR_EXIT("aio_read");

  // 取消异步读请求
  ret = aio_cancel(STDIN_FILENO, &my_aiocb);

  if (ret == AIO_CANCELED)
    puts("AIO_CANCELED");
  else if (ret == AIO_NOTCANCELED)
    puts("AIO_NOTCANCELED");
  else if (ret == AIO_ALLDONE)
    puts("AIO_ALLDONE");
  else if (ret == -1) 
    ERR_EXIT("aio_cancel");

  while((ret = aio_error(&my_aiocb)) == EINPROGRESS) {
    sleep(1);
  }

  if (ret == ECANCELED)
    puts("ECANCELED");
  else if (ret == 0)
    puts("Request completed");
  else if (ret > 0) {
    errno = ret;
    ERR_EXIT("aio_error");
  }

  ret = aio_return(&my_aiocb);
  if (ret < 0) ERR_EXIT("aio_return");

  printf("content: %s, return: %d\n", buf, ret);

  return 0;
}

编译运行

$ gcc my_aio_cancel.c -o my_aio_cancel -lrt
$ ./my_aio_cancel 
AIO_NOTCANCELED
hello
Request completed
content: hello
, return: 6

从结果里可以看到,无法取消前面发起的异步读操作。

6 批量请求

POSIX 提供了函数 lio_listio 可以一次性发起多个异步 IO 请求。

int lio_listio(int mode, struct aiocb *const aiocb_list[], int nitems, struct sigevent *sevp);

函数参数

  • mode 有两个可选值:LIO_WAIT 和 LIO_NOWAIT。
含义
LIO_WAIT lio_listio阻塞,直到所有的异步IO请求完成。此时参数 sevp 被忽略掉
LIO_NOWAIT lio_listio立即返回,当所有异步IO请求完成后,进行异步通知,通知方式由参数 sevp 指定,为 NULL表示不需要异步通知。
  • aiocb_list:数组数组元素为aiocb 结构体。

  • nitems:数组大小。

在使用 lio_listio 函数时,需要将 aiocb 中的 aio_lio_opcode 成员赋值,说明内核发起何种异步IO操作。可以取如下值:

含义
LIO_READ 发起异步读操作
LIO_WRITE 发起异步写操作
LIO_NOP 忽略掉aiocb

不管sevp参数,设为NULL。

实验

程序 my_aio_lio 修改了前面的代码,将 aio_read 函数替换成了 lio_listio 函数发起异步读请求。程序中并没有演示同时发起多个请求,只是发起了一个。实际上,一个你会了,多个也没什么问题,无非就是给数组赋几个值而已

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)

int main() {
    int fd, ret;
    char buf[64];
    struct aiocb my_aiocb;

    bzero((char*)&my_aiocb, sizeof(struct aiocb));

    my_aiocb.aio_buf = buf;
    my_aiocb.aio_fildes = STDIN_FILENO;
    my_aiocb.aio_nbytes = 64;
    my_aiocb.aio_offset = 0;

    // 注意这里多了一个成员的赋值,aio_lio_opcode 成员是专门给 lio_listio 函数用的。
    my_aiocb.aio_lio_opcode = LIO_READ;

    // 定义一个大小为 5 的数组,多大无所谓了,看你需求了。
    struct aiocb* aio_list[5] = { NULL };
    // 将你想发起的请求的控制块放到数组里,放哪个位置都行。这里我只发起了一个读操作。
    aio_list[3] = &my_aiocb;

    // 调用 lio_listio 发起读请求
    ret = lio_listio(LIO_NOWAIT, aio_list, 5, NULL);


    while (aio_error(&my_aiocb) == EINPROGRESS) {
        write(STDOUT_FILENO, ".", 1);
        sleep(1);
    }


    printf("content: %s\n", buf);

    return 0;
}

编译运行

$ gcc my_aio_lio.c -o my_aio_lio -lrt
$ ./my_aio_lio 
....hell.o
content: hello

7 异步通知

7.1 两种通知方式

1、 使用 while 循环和 aio_error 函数轮询。

while(aio_error(&my_aiocb) == EINPROGRESS) {
    write(STDOUT_FILENO, ".", 1); 
    sleep(1);
}

2、采用异步的方式通知程序异步操作已完成。

7.2 aiocb的成员aio_sigevent

struct aiocb {
    // ...
    struct sigevent aio_sigevent;   /* 通知方法 */
    // ...
};

aio_sigevent是一个结构体,组成如下。

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

struct sigevent {
    int          sigev_notify; /* 通知方式 */
    int          sigev_signo;  /* 通知所用的信号,可以自己指定,比如 SIGUSR1 */
    union sigval sigev_value;  /* 通知附带的数据 */

    // 下面两个成员仅仅用于 SIGEV_THREAD 通知方式
    void         (*sigev_notify_function) (union sigval); /* 线程通知函数 */
    void* sigev_notify_attributes; /* 通知线程的属性,一般指定为 pthread_attr_t 结构的地址 */


    // 通知线程 id. 这个成员仅仅用于 SIGEV_THREAD_ID 通知方式,这种通知方式我们不学它。
    pid_t        sigev_notify_thread_id;
};

7.3 sigevent 的成员

(1) sigev_notify

异步 IO 完成后,用什么样的方式通知用户程序。四个可选值:

  • SIGEV_NONE:表示不通知。

  • SIGEV_SIGNAL:异步 IO 操作完成后,程序收到指定的信号(成员 sigev_signo)。

  • SIGEV_THREAD:当异步 IO 操作完成后,内核会创建一个新线程执行一个函数,函数由成员 sigev_notify_function 指定。函数参数是sigev_value。

  • SIGEV_THREAD_ID:Linux 操作系统独有的。仅用于 POSIX 定时器中。不管。

(2) sigev_signo

如果使用SIGEV_SIGNAL方式通知,异步IO操作完成后,进程会收到这个成员指定的信号。

(3) sigev_value

产生通知时,所附加的数据。通知产生时,通过带参数的信号处理函数或线程函数的参数传递过来。

(4) sigev_notify_function

线程函数,仅仅用于 SIGEV_THREAD 通知方式。

(5) sigev_notify_attributes

创建线程的属性,pthread_attr_t 类型的指针。仅用于 SIGEV_THREAD 通知方式。

(6) sigev_notify_thread_id

仅仅用于 SIGEV_THREAD_ID 通知方式,不学。

实验

这部分有两个实验,一个用信号的方式通知,另一个是用线程的方式。

  • 信号通知
// my_aio_sig.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)
#define IO_SIGNAL SIGUSR1

// 信号处理函数
void handler(int sig, siginfo_t* info, void* ucontext) {
    int ret;
    printf("receive signal: %d\n", sig);
    struct aiocb* my_aiocb = info->si_value.sival_ptr;
    while (aio_error(my_aiocb) == EINPROGRESS) {
        write(STDOUT_FILENO, ".", 1);
    }

    ret = aio_return(my_aiocb);
    if (ret < 0) ERR_EXIT("aio_return");

    printf("content: %s\n", (char*)(my_aiocb->aio_buf));
    exit(0);
}

int main() {
    int fd, ret;
    struct aiocb my_aiocb;
    char buf[64];

    bzero((char*)&my_aiocb, sizeof(struct aiocb));
    // 注册信号处理函数
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = handler;
    sigaction(IO_SIGNAL, &sa, NULL);

    my_aiocb.aio_buf = buf;
    my_aiocb.aio_fildes = STDIN_FILENO;
    my_aiocb.aio_nbytes = 64;
    my_aiocb.aio_offset = 0;
    // 设置通知方式为信号通知
    my_aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
    // 设置通知信号
    my_aiocb.aio_sigevent.sigev_signo = IO_SIGNAL;
    // 通知附加数据。这个成员将来会传递到信号处理函数中。
    my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;

    ret = aio_read(&my_aiocb);
    if (ret < 0) ERR_EXIT("aio_read");

    while (1) {
        pause();
    }

    return 0;
}

编译运行

$ gcc my_aio_sig.c -o my_aio_sig -lrt
$ ./my_aio_sig 
hello
receive signal: 10
content: hello
  • 线程通知
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ERR_EXIT(msg) do { perror(msg); exit(1); } while(0)

// 线程函数
void handler(union sigval val) {
    int ret;
    struct aiocb* my_aiocb = val.sival_ptr;
    while (aio_error(my_aiocb) == EINPROGRESS) {
        write(STDOUT_FILENO, ".", 1);
    }

    ret = aio_return(my_aiocb);
    if (ret < 0) ERR_EXIT("aio_return");

    printf("content: %s\n", (char*)(my_aiocb->aio_buf));
    exit(0);
}

int main() {
    int fd, ret;
    struct aiocb my_aiocb;
    const struct aiocb* aio_list[1] = { &my_aiocb };
    char buf[64];

    bzero((char*)&my_aiocb, sizeof(struct aiocb));

    my_aiocb.aio_buf = buf;
    my_aiocb.aio_fildes = STDIN_FILENO;
    my_aiocb.aio_nbytes = 64;
    my_aiocb.aio_offset = 0;
    // 通知方式设置为线程通知
    my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
    // 通知附加数据
    my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb;
    // 线程函数
    my_aiocb.aio_sigevent.sigev_notify_function = handler;


    ret = aio_read(&my_aiocb);
    if (ret < 0) ERR_EXIT("aio_read");

    ret = aio_suspend(aio_list, 1, NULL);
    if (ret < 0) ERR_EXIT("aio_suspend");

    while (1) pause();

    puts("main exited");

    return 0;
}

编译运行

$ gcc my_aio_thread.c -o my_aio_thread -lrt
$ ./my_aio_thread 
hello
content: hello

Linux系统编程(十)--高级IO-异步IO_第2张图片

你可能感兴趣的:(Linux系统编程,linux)