VxWorks入门小白菜鸟教程4 —— 异步I/O库(AIO)的使用

一、功能描述

本库根据 POSIX 1003.1b 标准实现异步 I/O(AIO)。AIO 的核心价值是让应用程序的计算任务与 I/O 操作并行执行,具体特性包括:

  1. 支持对单个文件同时发起多次 I/O 操作。
  2. 支持同时对多个文件发起 I/O 操作。
  3. 异步 I/O 发起后,会与应用程序逻辑并行执行,效果等同于由独立线程处理 I/O 任务。

二、头文件

使用 AIO 功能时,需包含以下头文件:

#include <aio.h>

三、核心函数(ROUTINES)

以下函数用于发起、管理和查询异步 I/O 操作,均符合 POSIX 标准:

  1. aio_read() - 发起异步读操作
  2. aio_write() - 发起异步写操作
  3. lio_listio() - 发起一组异步 I/O 请求
  4. aio_suspend() - 等待异步 I/O 请求完成
  5. aio_cancel() - 取消已提交的异步 I/O 请求
  6. aio_fsync() - 异步文件同步
  7. aio_error() - 获取异步 I/O 操作的错误状态
  8. 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,并通过专用函数查询操作结果:

  1. 文件打开:需通过标准 open() 函数打开文件,获取的文件描述符将用于后续所有 AIO 调用。
  2. 发起 I/O 请求:通过 aio_read()、aio_write() 或 lio_listio() 发起异步操作。这些函数的返回值仅表示 "请求是否成功提交到队列",不代表 I/O 操作本身的成功或失败。
  3. 查询操作结果:
  1. aio_error():获取 I/O 操作的错误状态。操作未完成时,返回值固定为 EINPROGRESS。
  2. aio_return():操作完成后,通过此函数获取最终返回状态(如实际读写的字节数)。
  1. 其他管理操作:
  1. aio_cancel():取消已提交但未完成的 I/O 请求。
  2. aio_suspend():阻塞等待指定的 I/O 请求完成。
  3. aioShow():非 POSIX 标准函数,用于显示当前未完成的 AIO 请求(Wind River 扩展)。

六、异步 I/O 控制块

所有 AIO 函数均需传入 AIO 控制块(aiocb) 作为参数,用于描述单个异步 I/O 操作的详细信息。使用时需严格遵守以下规则:

aiocb 的使用约束:

  1. 内存分配:调用者需自行分配 aiocb 内存,且在 I/O 操作完成并调用 aio_return() 前,该内存不可释放。
  2. 禁止在任务栈上创建 aiocb(除非调用者会阻塞到 I/O 完成并执行 aio_return()),否则栈内存可能被覆盖。
  3. 不可重复使用:同一 aiocb 不能同时用于多个异步操作,否则会导致未定义行为。
  4. 提交后不可修改: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;         // 系统内部使用(用户不可修改)
};

结构体成员说明:

  1. aio_fildes:用于 I/O 操作的文件描述符(由 open() 函数返回)。
  2. aio_offset:文件读写的起始偏移量(从文件开头计算)。
  3. 注意:AIO 不会自动更新文件偏移量(与 read()/write() 不同),每次操作需手动设置正确的 aio_offset。
  4. aio_buf:指向 I/O 数据缓冲区的指针(读操作时为数据接收缓冲区,写操作时为数据发送缓冲区)。
  5. aio_nbytes:请求读写的字节数。
  6. aio_reqprio:降低 AIO 请求优先级的数值(仅能降低,不能提高)。
  1. 取值范围:0 到 AIO_PRIO_DELTA_MAX。
  2. 若调整后优先级低于系统最低值,则自动使用最低优先级。
  1. aio_sigevent:可选配置,用于定义 I/O 完成时发送的信号(如 SIGEV_SIGNAL 表示发送指定信号)。
  2. aio_lio_opcode:仅用于 lio_listio() 函数,指定操作类型,可选值为 LIO_READ(读)、LIO_WRITE(写)、LIO_NOP(无操作)。
  3. aio_sys:Wind River 扩展成员,系统内部使用,用户禁止修改。

七、参考示例

以下示例展示了基于 AIO 的异步写和异步读实现,包含信号通知机制的使用。

示例 1 :异步写操作

cpp 复制代码
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 :带信号通知的异步读操作

cpp 复制代码
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 完成回调)

cpp 复制代码
void sigHandler(int sig, struct siginfo info, void *pContext)
{
    struct aiocb *pAioDone;
    // 从信号信息中获取完成的 aiocb
    pAioDone = (struct aiocb *)info.si_value.sival_ptr;
    // 获取 I/O 结果并释放资源
    aio_return(pAioDone);
    free(pAioDone);
    free(pAioDone->aio_buf); // 释放数据缓冲区
}

八、配置说明

AIO 库在使用动态链接(共享库)时需单独配置,静态链接时无需额外操作:

  1. 动态链接:在链接命令中添加 -lAioPx,或在构建环境的 LIBS 宏、Workbench 项目的 "库" 选项卡中添加该库。
  2. 静态链接:AIO 库已包含在默认链接中,无需额外配置。

九、实操演示

  1. 新建文件夹aio1,在VScode中打开该文件夹,新建3个文件:main.c、failtest.c、CMakeLists.txt。
  2. 在main.c中输入代码:
cpp 复制代码
#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_KERNEL
	printf("\nHello world from kernel space!\n");
	printf("argc=%d\n", argc);
	printf("argv[%d]=%s\n", argc, argv==NULL ? "NULL" : (char*)argv);
#else
	int i;
	printf("\nHello world from user space!\n");
	for(i=0; i<argc; i++) {
		printf("argv[%d]=%s\n", i, argv[i]);
	}
#endif
	
    int 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中输入代码:

cpp 复制代码
int main() {
    return 1;  // 故意返回失败
}

4、在CMakeLists.txt中输入:

cpp 复制代码
cmake_minimum_required(VERSION 2.8.7)

#指定项目名称和支持的编程语言:C、C++、汇编
project(aio_test C CXX ASM)

#创建一个名为 hello_cmake 的可执行程序,并指定其构建所需的源代码文件。
add_executable(aio_test
	main.c
)

# 可执行程序链接 VxWorks 的 AIO 库(库名:AioPx)
target_link_libraries(aio_test PRIVATE AioPx)

5、在VScode中打开终端,执行命令(新建build文件夹,并进入该文件夹):

cpp 复制代码
mkdir build
cd build

6、在VScode的终端中执行命令(生成构建文件Makefile):

cpp 复制代码
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进行编译链接,生成可执行文件):

cpp 复制代码
make

8、在build文件夹中新建test.txt,并输入(任意内容都行):

cpp 复制代码
你好,我是aio

9、在build文件夹中右键->在终端中打开,执行命令(启动ftp服务器,参考小白入门1):

cpp 复制代码
python -m pyftpdlib -p 21 -u target -P vxTarget -i 127.0.0.1

10、启动VxWorks(参考小白入门1),然后执行aio_test:

完美!

相关推荐
kaikaile19959 分钟前
基于DSP28335与AD7606的采样程序实现
单片机·嵌入式硬件
Joshua-a14 分钟前
STM32嵌入式开发核心:volatile与寄存器操作详解
单片机·嵌入式硬件
九鼎创展科技3 小时前
九鼎创展发布X3588SCV4核心板,集成LPDDR5内存,提升RK3588S平台性能边界
android·人工智能·嵌入式硬件·硬件工程
智者知已应修善业5 小时前
【51单片机LED贪吃蛇】2023-3-27
c语言·c++·经验分享·笔记·嵌入式硬件·51单片机
国科安芯5 小时前
MCU芯片AS32A601与INA226芯片精确测量实现与应用
网络·单片机·嵌入式硬件·架构·安全性测试
一支闲人6 小时前
STM32 CAN外设1
stm32·单片机·嵌入式硬件·基础知识·cna协议
HanLop6 小时前
51单片机入门
单片机·嵌入式硬件·51单片机
猪八戒1.016 小时前
onenet接口
开发语言·前端·javascript·嵌入式硬件
d111111111d16 小时前
SPI通信协议--在STM32中介绍(学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习
电子科技圈17 小时前
IAR与Quintauris携手推进RISC-V汽车实时应用的功能安全软件开发
嵌入式硬件·安全·设计模式·编辑器·汽车·risc-v