4. dosFs文件系统API:dosFsOpen、dosFsWrite、dosFsRead、dosFsClose等核心API使用

好,咱们直接进入正题。dosFs文件系统的API,说白了就是一套操作文件的工具。你想想看,不管你的设备多复杂,最终都要落到“打开文件、读写数据、关闭文件”这三板斧上。我这些年调试过的板子,十有八九的问题都出在这几个API的用法上。今天我就把dosFsOpen、dosFsWrite、dosFsRead、dosFsClose这几个核心API掰开揉碎了讲清楚。

4.1 dosFsOpen:打开文件的正确姿势

dosFsOpen是文件操作的入口。它的原型长这样:

int dosFsOpen
    (
    DOS_VOLUME_DESC_ID  volDesc,    /* 卷描述符 */
    const char *        name,       /* 文件名 */
    UINT32              flags,      /* 打开标志 */
    UINT32              mode        /* 创建模式(仅O_CREAT时有效) */
    )

这里我重点说说flags参数。很多人一上来就写O_RDWR,其实不一定对。我建议你根据实际需求选:

  • O_RDONLY:只读打开。日志分析、配置文件读取时常用。
  • O_WRONLY:只写打开。数据采集、日志写入时用。
  • O_RDWR:读写打开。需要同时读写时用。
  • O_CREAT:文件不存在则创建。配合mode参数使用。
  • O_TRUNC:如果文件存在,清空内容。小心使用!

重要提醒:dosFsOpen返回的是文件描述符(int类型),不是指针。返回-1表示打开失败。我见过有人拿返回值当指针用,结果系统直接崩溃。

我的经验:在项目中,我习惯先检查errno。比如打开失败时,用printErrno(errno)打印错误码,能快速定位问题。有一次客户说文件打不开,我一看错误码是S_dosFsLib_FILE_NOT_FOUND,原来是路径写错了。

4.2 dosFsWrite:写入数据,别踩这些坑

dosFsWrite用来向文件写入数据。原型如下:

int dosFsWrite
    (
    int     fd,         /* 文件描述符 */
    char *  buffer,     /* 数据缓冲区 */
    int     nBytes      /* 要写入的字节数 */
    )

返回值是实际写入的字节数。如果小于nBytes,说明磁盘空间不足或发生了错误。

嗯,这里要注意一个细节:dosFsWrite是同步写入。什么意思?就是数据写入后,不一定立即落盘。它可能还在缓存里。如果你突然断电,数据就丢了。

避坑指南:我曾经在一个数据采集项目中,每10ms写一次日志。结果系统运行几天后,发现日志文件里缺了最后几秒的数据。排查下来,是缓存没刷。后来我在关键位置加了dosFsSync(),问题解决。

如果你需要确保数据立即写入物理介质,可以这样做:

/* 写入数据 */
int written = dosFsWrite(fd, buffer, size);
if (written != size) {
    /* 处理错误 */
}

/* 强制刷缓存 */
dosFsSync(fd);

4.3 dosFsRead:读取数据,注意边界

dosFsRead的用法和dosFsWrite对称:

int dosFsRead
    (
    int     fd,         /* 文件描述符 */
    char *  buffer,     /* 数据缓冲区 */
    int     nBytes      /* 要读取的字节数 */
    )

返回值是实际读取的字节数。如果返回0,表示已经读到文件末尾。如果返回-1,说明出错了。

我个人习惯在读取时,总是检查返回值。你想想看,如果文件只有100字节,你非要读200字节,那第二次调用就会返回0。不检查的话,程序可能用脏数据继续运行。

核心要点:dosFsRead不会自动追加'\0'。如果你读的是文本文件,记得手动加字符串结束符。我见过有人读配置文件,忘记加'\0',结果解析时乱码。

char buffer[256];
int nRead = dosFsRead(fd, buffer, sizeof(buffer) - 1);
if (nRead > 0) {
    buffer[nRead] = '\0';  /* 手动加结束符 */
    printf("Read: %s\n", buffer);
}

4.4 dosFsClose:善始善终

dosFsClose的用法最简单,但也最容易忽略:

int dosFsClose
    (
    int fd   /* 文件描述符 */
    )

返回值:0表示成功,-1表示失败。

为什么我要单独拿出来讲?因为很多人觉得“关闭文件无所谓”。其实不然:

  • 释放资源:每个打开的文件都会占用系统资源。不关闭的话,文件描述符会耗尽。
  • 刷缓存:关闭文件时,dosFs会自动刷缓存。但如果你不关,缓存可能一直留在内存里。
  • 保证数据完整性:关闭文件后,其他进程才能安全访问。

我的习惯:我写代码时,dosFsOpendosFsClose总是成对出现。打开后立刻写关闭代码,中间再插业务逻辑。这样就不会漏掉。

4.5 完整示例:一个日志写入任务

说了这么多,咱们看个完整的例子。这是一个典型的日志写入任务:

void logTask(void)
{
    DOS_VOLUME_DESC_ID volDesc;
    int fd;
    char buffer[128];
    int count = 0;

    /* 获取卷描述符 */
    volDesc = dosFsVolDescGet("/ata0:");
    if (volDesc == NULL) {
        printf("Failed to get volume descriptor\n");
        return;
    }

    /* 打开日志文件 */
    fd = dosFsOpen(volDesc, "system.log",
                   O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd == -1) {
        printf("Failed to open log file, errno = 0x%x\n", errno);
        return;
    }

    /* 写入日志 */
    while (count < 100) {
        snprintf(buffer, sizeof(buffer),
                 "Log entry #%d: system running\n", count++);
        int written = dosFsWrite(fd, buffer, strlen(buffer));
        if (written != strlen(buffer)) {
            printf("Write failed at entry %d\n", count);
            break;
        }
        taskDelay(100);  /* 模拟间隔 */
    }

    /* 关闭文件 */
    if (dosFsClose(fd) != 0) {
        printf("Failed to close file\n");
    }
}

注意:这个例子中,我用了O_TRUNC。每次启动都会清空旧日志。如果你需要追加,应该用O_APPEND标志。我曾经在一个项目中忘了改这个,结果历史日志全丢了,被客户骂了一顿。

4.6 常见错误与调试技巧

最后,我总结几个常见错误,你写代码时可以对照检查:

错误现象 可能原因 解决方法
dosFsOpen返回-1 卷未挂载、路径错误、权限不足 检查errno,确认卷已挂载
dosFsWrite写入字节数不对 磁盘空间满、缓冲区太小 检查返回值,预留空间
dosFsRead读到乱码 未加'\0'、文件编码问题 手动加结束符,确认编码
文件描述符耗尽 忘记调用dosFsClose 成对使用Open/Close

调试时,我建议你多用dosFsShow()命令。在VxWorks shell里输入dosFsShow "/ata0:",可以查看卷的状态、已打开的文件列表等信息。这招帮我解决过不少疑难杂症。

好了,dosFs的核心API就讲到这里。记住:打开要检查、读写要判断、关闭不能忘。下一节咱们聊聊更高级的话题——文件系统的性能优化。