VxWorks入门小白菜鸟教程4 —— 异步I/O库(AIO)的使用
一、功能描述
本库根据 POSIX 1003.1b 标准实现异步 I/O(AIO)。AIO 的核心价值是让应用程序的计算任务与 I/O 操作并行执行,具体特性包括:
- 支持对单个文件同时发起多次 I/O 操作。
- 支持同时对多个文件发起 I/O 操作。
- 异步 I/O 发起后,会与应用程序逻辑并行执行,效果等同于由独立线程处理 I/O 任务。
二、头文件
使用 AIO 功能时,需包含以下头文件:
#include <aio.h>
三、核心函数(ROUTINES)
以下函数用于发起、管理和查询异步 I/O 操作,均符合 POSIX 标准:
- aio_read() - 发起异步读操作
- aio_write() - 发起异步写操作
- lio_listio() - 发起一组异步 I/O 请求
- aio_suspend() - 等待异步 I/O 请求完成
- aio_cancel() - 取消已提交的异步 I/O 请求
- aio_fsync() - 异步文件同步
- aio_error() - 获取异步 I/O 操作的错误状态
- aio_return() - 获取异步 I/O 操作的返回状态
四、AIO 环境变量
加载实时进程(RTP)时,可通过以下环境变量配置 AIO 库的运行参数。默认值由系统头文件定义。
环境变量 | 说明 | 默认设置(由头文件定义) |
MAX_LIO_CALLS | 最大未完成的 lio_listio() 调用数量 | AIO_CLUST_MAX |
MAX_AIO_SYS_TASKS | 支持 AIO 的系统任务(线程)数量 | AIO_IO_TASKS_DFLT |
AIO_TASK_PRIORITY | AIO 系统任务的优先级 | AIO_IO_PRIO_DFLT |
AIO_TASK_STACK_SIZE | AIO 系统任务的栈大小 | AIO_IO_STACK_DFLT |
五、AIO 操作流程
异步 I/O 的操作流程需遵循以下规则,核心是通过标准文件描述符管理 I/O,并通过专用函数查询操作结果:
- 文件打开:需通过标准 open() 函数打开文件,获取的文件描述符将用于后续所有 AIO 调用。
- 发起 I/O 请求:通过 aio_read()、aio_write() 或 lio_listio() 发起异步操作。这些函数的返回值仅表示 “请求是否成功提交到队列”,不代表 I/O 操作本身的成功或失败。
- 查询操作结果:
- aio_error():获取 I/O 操作的错误状态。操作未完成时,返回值固定为 EINPROGRESS。
- aio_return():操作完成后,通过此函数获取最终返回状态(如实际读写的字节数)。
- 其他管理操作:
- aio_cancel():取消已提交但未完成的 I/O 请求。
- aio_suspend():阻塞等待指定的 I/O 请求完成。
- aioShow():非 POSIX 标准函数,用于显示当前未完成的 AIO 请求(Wind River 扩展)。
六、异步 I/O 控制块
所有 AIO 函数均需传入 AIO 控制块(aiocb) 作为参数,用于描述单个异步 I/O 操作的详细信息。使用时需严格遵守以下规则:
aiocb 的使用约束:
- 内存分配:调用者需自行分配 aiocb 内存,且在 I/O 操作完成并调用 aio_return() 前,该内存不可释放。
- 禁止在任务栈上创建 aiocb(除非调用者会阻塞到 I/O 完成并执行 aio_return()),否则栈内存可能被覆盖。
- 不可重复使用:同一 aiocb 不能同时用于多个异步操作,否则会导致未定义行为。
- 提交后不可修改:aiocb 提交给系统后(如调用 aio_write() 后),需等待 aio_return() 执行完成,才能修改、释放或复用该 aiocb。
aiocb 结构体在 aio.h 中定义,包含以下成员(部分为 Wind River 扩展):
struct aiocb
{int aio_fildes; // 文件描述符off_t aio_offset; // 文件偏移量volatile void * aio_buf; // I/O 数据缓冲区地址size_t aio_nbytes; // 读写字节数int aio_reqprio; // 请求优先级调整值struct sigevent aio_sigevent; // 完成通知信号(可选)int aio_lio_opcode; // lio_listio() 操作类型AIO_SYS aio_sys; // 系统内部使用(用户不可修改)
};
结构体成员说明:
- aio_fildes:用于 I/O 操作的文件描述符(由 open() 函数返回)。
- aio_offset:文件读写的起始偏移量(从文件开头计算)。
- 注意:AIO 不会自动更新文件偏移量(与 read()/write() 不同),每次操作需手动设置正确的 aio_offset。
- aio_buf:指向 I/O 数据缓冲区的指针(读操作时为数据接收缓冲区,写操作时为数据发送缓冲区)。
- aio_nbytes:请求读写的字节数。
- aio_reqprio:降低 AIO 请求优先级的数值(仅能降低,不能提高)。
- 取值范围:0 到 AIO_PRIO_DELTA_MAX。
- 若调整后优先级低于系统最低值,则自动使用最低优先级。
- aio_sigevent:可选配置,用于定义 I/O 完成时发送的信号(如 SIGEV_SIGNAL 表示发送指定信号)。
- aio_lio_opcode:仅用于 lio_listio() 函数,指定操作类型,可选值为 LIO_READ(读)、LIO_WRITE(写)、LIO_NOP(无操作)。
- aio_sys:Wind River 扩展成员,系统内部使用,用户禁止修改。
七、参考示例
以下示例展示了基于 AIO 的异步写和异步读实现,包含信号通知机制的使用。
示例 1:异步写操作
struct aiocb *pAioWrite;
int fd; // 假设已通过 open() 获取文件描述符
char *buffer = malloc(BUF_SIZE); // 数据缓冲区
// 1. 分配并初始化 aiocb
if ((pAioWrite = calloc(1, sizeof(struct aiocb))) == NULL)
{printf("calloc 分配 aiocb 失败\n");return ERROR;
}
pAioWrite->aio_fildes = fd; // 绑定文件描述符
pAioWrite->aio_buf = buffer; // 绑定数据缓冲区
pAioWrite->aio_offset = 0; // 从文件开头写入
strcpy(pAioWrite->aio_buf, "test string"); // 写入数据
pAioWrite->aio_nbytes = strlen("test string"); // 写入字节数
pAioWrite->aio_sigevent.sigev_notify = SIGEV_NONE; // 不使用信号通知
// 2. 发起异步写请求
aio_write(pAioWrite);
// 3. 并行执行其他任务(此处省略)
// ... 其他业务逻辑 ...
// 4. 等待写操作完成
while (aio_error(pAioWrite) == EINPROGRESS)taskDelay(1); // 延迟 1 个时间片,避免忙等
// 5. 获取结果并释放资源
aio_return(pAioWrite);
free(pAioWrite);
free(buffer);
示例 2:带信号通知的异步读操作
struct aiocb *pAioRead;
int fd; // 假设已通过 open() 获取文件描述符
char *buffer = malloc(BUF_SIZE); // 数据缓冲区
struct sigaction action1;
// 1. 初始化信号处理函数(I/O 完成时触发)
action1.sa_sigaction = sigHandler; // 信号处理函数
action1.sa_flags = SA_SIGINFO; // 支持扩展信号信息
sigemptyset(&action1.sa_mask); // 清空信号掩码
sigaction(TEST_RT_SIG1, &action1, NULL); // 注册信号 TEST_RT_SIG1
// 2. 分配并初始化 aiocb
if ((pAioRead = calloc(1, sizeof(struct aiocb))) == NULL)
{printf("calloc 分配 aiocb 失败\n");return ERROR;
}
pAioRead->aio_fildes = fd; // 绑定文件描述符
pAioRead->aio_buf = buffer; // 绑定接收缓冲区
pAioRead->aio_nbytes = BUF_SIZE; // 读取字节数
pAioRead->aio_sigevent.sigev_signo = TEST_RT_SIG1; // 完成时发送 TEST_RT_SIG1
pAioRead->aio_sigevent.sigev_notify = SIGEV_SIGNAL; // 通知方式:信号
pAioRead->aio_sigevent.sigev_value.sival_ptr = (void *)pAioRead; // 传递 aiocb 指针
// 3. 发起异步读请求
aio_read(pAioRead);
// 4. 并行执行其他任务(此处省略)
// ... 其他业务逻辑 ...
示例 3:信号处理函数(I/O 完成回调)
void sigHandler(int sig, struct siginfo info, void *pContext)
{struct aiocb *pAioDone;// 从信号信息中获取完成的 aiocbpAioDone = (struct aiocb *)info.si_value.sival_ptr;// 获取 I/O 结果并释放资源aio_return(pAioDone);free(pAioDone);free(pAioDone->aio_buf); // 释放数据缓冲区
}
八、配置说明
AIO 库在使用动态链接(共享库)时需单独配置,静态链接时无需额外操作:
- 动态链接:在链接命令中添加 -lAioPx,或在构建环境的 LIBS 宏、Workbench 项目的 “库” 选项卡中添加该库。
- 静态链接:AIO 库已包含在默认链接中,无需额外配置。
九、实操演示
- 新建文件夹aio1,在VScode中打开该文件夹,新建3个文件:main.c、failtest.c、CMakeLists.txt。
- 在main.c中输入代码:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <aio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>#define BUFFER_SIZE 1024 // 读取缓冲区大小
#define FILENAME "test.txt" // 要读取的文件int main(int argc, char * argv[]) {/* Wait a little (5 seconds) for WRASP I/O to set up after initial boot *//*sleep(5);*/
#ifdef _WRS_KERNELprintf("\nHello world from kernel space!\n");printf("argc=%d\n", argc);printf("argv[%d]=%s\n", argc, argv==NULL ? "NULL" : (char*)argv);
#elseint i;printf("\nHello world from user space!\n");for(i=0; i<argc; i++) {printf("argv[%d]=%s\n", i, argv[i]);}
#endifint fd;struct aiocb aio; // AIO 控制块ssize_t bytes_read;int ret;// 1. 打开文件(只读模式)fd = open(FILENAME, O_RDONLY);if (fd == -1) {perror("open 失败"); // 打印错误信息(如文件不存在)exit(EXIT_FAILURE);}// 2. 分配读取缓冲区(需保证在AIO操作期间有效)char *buffer = malloc(BUFFER_SIZE);if (buffer == NULL) {perror("malloc 缓冲区失败");close(fd);exit(EXIT_FAILURE);}// 3. 初始化 AIO 控制块(aiocb)memset(&aio, 0, sizeof(struct aiocb)); // 清零结构体,避免未定义行为aio.aio_fildes = fd; // 绑定文件描述符aio.aio_buf = buffer; // 绑定读取缓冲区aio.aio_nbytes = BUFFER_SIZE; // 计划读取的字节数aio.aio_offset = 0; // 从文件起始位置开始读取// 不使用信号通知(通过轮询等待完成)aio.aio_sigevent.sigev_notify = SIGEV_NONE;// 4. 发起异步读操作ret = aio_read(&aio);if (ret == -1) {perror("aio_read 发起失败"); // 失败原因:如参数错误、文件不可读等free(buffer);close(fd);exit(EXIT_FAILURE);}printf("异步读请求已提交,等待完成...\n");// 5. 轮询等待异步操作完成(实际应用中可并行处理其他任务)while (aio_error(&aio) == EINPROGRESS) {// 这里可以执行其他业务逻辑,而非空等printf("等待中...(可执行其他操作)\n");sleep(1); // 休眠1秒,减少CPU占用}// 6. 获取异步操作结果bytes_read = aio_return(&aio);if (bytes_read == -1) {perror("异步读操作失败");free(buffer);close(fd);exit(EXIT_FAILURE);}// 7. 处理读取到的数据printf("\n读取完成,共读取 %zd 字节:\n", bytes_read);printf("----------------------------------------\n");fwrite(buffer, 1, bytes_read, stdout); // 输出读取到的内容printf("\n----------------------------------------\n");// 8. 清理资源free(buffer);close(fd);printf("\n资源已释放,程序退出\n");return 0;
}
3、在failtest.c中输入代码:
int main() {return 1; // 故意返回失败
}
4、在CMakeLists.txt中输入:
cmake_minimum_required(VERSION 2.8.7)#指定项目名称和支持的编程语言:C、C++、汇编
project(aio_test C CXX ASM)#创建一个名为 hello_cmake 的可执行程序,并指定其构建所需的源代码文件。
add_executable(aio_testmain.c
)# 可执行程序链接 VxWorks 的 AIO 库(库名:AioPx)
target_link_libraries(aio_test PRIVATE AioPx)
5、在VScode中打开终端,执行命令(新建build文件夹,并进入该文件夹):
mkdir build
cd build
6、在VScode的终端中执行命令(生成构建文件Makefile):
cmake .. -G "Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE="D:\!VxWorks\wrsdk-vxworks7-win-qemu-1.10\wrsdk-vxworks7-win-qemu\vxsdk\sysroot\mk\rtp.toolchain.cmake"7、在VScode的终端中执行命令(根据Makefile进行编译链接,生成可执行文件):
make
8、在build文件夹中新建test.txt,并输入(任意内容都行):
你好,我是aio9、在build文件夹中右键->在终端中打开,执行命令(启动ftp服务器,参考小白入门1):
python -m pyftpdlib -p 21 -u target -P vxTarget -i 127.0.0.110、启动VxWorks(参考小白入门1),然后执行aio_test:

完美!
