2. VxWorks I/O系统与文件描述符
VxWorks 的 I/O 系统本质上是一层“设备抽象层”:上层应用只看到文件描述符(fd)和一组标准调用(open/read/write/ioctl/close),下层则由具体驱动提供实际的数据搬运与控制逻辑。理解这一层的关键,是搞清楚 fd 如何映射到设备、调用如何穿过 I/O 系统落到驱动函数,以及错误如何沿调用链返回。
一、设备抽象:fd、设备节点与驱动函数表
在 VxWorks 中,设备通过 iosDevAdd() 注册到 I/O 系统,形成一个设备节点(DEV_HDR)。每个设备节点携带设备名、驱动号以及指向驱动函数表的指针。应用调用 open("/tyCo/0", O_RDWR, 0) 时,I/O 系统按路径查找设备节点,再调用该设备驱动函数表中的 open 例程,返回一个文件描述符。fd 并不是设备本身,而是 I/O 系统维护的一张描述符表中的索引,表中记录该 fd 对应的设备节点和驱动私有数据指针。
驱动函数表通常包含七个入口:open、close、read、write、ioctl、remove、create(部分驱动可省略后两者)。其中 open 负责初始化设备并返回私有数据指针,read/write 负责数据搬运,ioctl 负责参数配置与状态查询。这种“函数表 + 私有数据”的结构,使同一套 I/O 接口可以适配串口、块设备、网络设备等不同硬件。
二、调用链:从 open 到驱动例程
以串口设备 /tyCo/0 为例,一次完整的写操作调用链如下:应用调用 write(fd, buf, n),I/O 系统根据 fd 找到设备节点,取出驱动函数表中的 write 例程并调用,同时把 fd 对应的私有数据指针传入。驱动例程将数据写入 UART 发送寄存器或环形缓冲区,返回实际写入字节数。若设备未就绪,驱动返回 ERROR,I/O 系统将 errno 置为对应错误码,应用层通过 errno 判断失败原因。
这里有一个容易混淆的点:read/write 的返回值是“实际传输字节数”,可能小于请求长度,尤其在非阻塞模式下。应用不能假设一次 write 就写完所有数据,必须循环处理剩余部分。而 ioctl 的返回值通常是 0 表示成功、-1 表示失败,具体命令字(如 FIOBAUDRATE、FIOSETOPTIONS)决定参数含义。
三、错误处理:errno 与返回值约定
VxWorks I/O 系统遵循 POSIX 风格约定:成功返回非负值,失败返回 ERROR(-1),并设置全局 errno。常见错误码包括 S_ioLib_NO_DEVICE(设备不存在)、S_ioLib_DEVICE_BUSY(设备忙)、S_ioLib_IO_ERROR(底层 I/O 错误)、EINVAL(参数无效)。在驱动内部,应通过 errnoSet() 设置错误码,而不是直接返回错误码,否则会破坏调用链的约定。
errno 是任务私有的(在 VxWorks 中通常通过任务变量实现),因此多任务并发调用同一设备时,各任务看到的 errno 互不干扰。但驱动内部的共享状态(如环形缓冲区)仍需自行加锁保护。
四、实操示例:打开串口并配置波特率
下面示例演示打开 /tyCo/0、设置波特率 115200、写入一行数据并读取回显。示例参数为教学假设值,实际波特率需与硬件匹配。
#include <ioLib.h>
#include <tyLib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
void uartDemo(void)
{
int fd;
char txBuf[] = "HELLO_VXWORKS\r\n";
char rxBuf[64];
int n;
/* 1. 打开设备,O_RDWR 表示读写 */
fd = open("/tyCo/0", O_RDWR, 0);
if (fd == ERROR) {
printf("open failed, errno=%d\n", errno);
return;
}
/* 2. 通过 ioctl 设置波特率,参数为整数指针 */
{
int baud = 115200;
if (ioctl(fd, FIOBAUDRATE, (int)&baud) == ERROR) {
printf("set baudrate failed, errno=%d\n", errno);
close(fd);
return;
}
}
/* 3. 写入数据,检查实际写入长度 */
n = write(fd, txBuf, strlen(txBuf));
if (n != (int)strlen(txBuf)) {
printf("write short: %d/%d, errno=%d\n",
n, (int)strlen(txBuf), errno);
}
/* 4. 读取回显,非阻塞模式下可能返回 0 */
n = read(fd, rxBuf, sizeof(rxBuf) - 1);
if (n > 0) {
rxBuf[n] = '\0';
printf("recv: %s\n", rxBuf);
} else if (n == 0) {
printf("no data available\n");
} else {
printf("read failed, errno=%d\n", errno);
}
/* 5. 关闭设备,释放 fd */
close(fd);
}
运行结果判断依据:若 open 返回非负 fd,说明设备节点存在且驱动 open 成功;若 ioctl 返回 0,说明波特率命令被驱动接受;write 返回值等于请求长度,说明数据已全部交给驱动;read 返回大于 0 表示收到回显,返回 0 表示当前无数据(非阻塞),返回 -1 则需查看 errno 定位是设备错误还是参数错误。
五、常见错误与排查要点
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| open 返回 -1,errno=S_ioLib_NO_DEVICE | 设备名拼写错误或未调用 iosDevAdd 注册 | 用 iosDevShow() 列出已注册设备 |
| ioctl 返回 -1,errno=EINVAL | 命令字不被该驱动支持,或参数指针无效 | 查阅驱动文档确认支持的 ioctl 命令 |
| write 返回短写 | 发送缓冲区满或设备流控生效 | 循环写入剩余数据,或检查硬件流控引脚 |
| read 长期返回 0 | 非阻塞模式且无数据,或对端未发送 | 确认打开标志是否含 O_NONBLOCK,检查对端 |