目录

  1. 问题描述
  2. 环境信息
  3. 调试过程
  4. 根本原因分析
  5. 解决方案
  6. 代码修改详解
  7. 测试验证
  8. 总结与经验

问题描述

初始症状

在 LoongArch 架构的 ls2k300_mini_dp 开发板上运行 OpenHarmony 6.1 时,遇到以下问题:

  1. ls 命令显示错误

    OHOS # ls
    Directory /:
    BAD file: .
    BAD file: ..
    
  2. 文件创建失败

    OHOS # touch 1.txt
    OHOS # mkdir os
    OHOS # ls
    Directory /:
    OHOS # cd os
    no such file or directory
    
  3. 文件系统初始化失败

    LfsLowLevelInit: Failed to create test file, fd=-1, errno=2
    

问题影响

  • 无法创建文件和目录
  • 无法访问已创建的文件
  • 文件系统基本功能不可用

环境信息

硬件平台

  • 开发板: ls2k300_mini_dp
  • CPU 架构: LoongArch (龙芯架构)
  • 内核: LiteOS-M

软件版本

  • OpenHarmony: 6.1
  • 文件系统: LittleFS
  • 存储介质: RAM disk (64KB)

文件系统配置

#define LFS_RAM_DISK_SIZE (64 * 1024)  // 64KB RAM disk
#define LFS_BLOCK_SIZE    4096         // 4KB 块大小
#define LFS_BLOCK_COUNT   16           // 16 个块

调试过程

第一阶段:定位 “BAD file” 错误

问题分析

ls 命令显示 “BAD file: .” 和 “BAD file: …”,说明目录读取有问题。

调试方法
  1. 添加调试信息到 ls 命令

    // kernel/liteos_m/components/shell/src/cmds/vfs_shellcmd.c
    static inline void OsLs(const char *path)
    {
        struct dirent *pdirent = NULL;
        DIR *d = NULL;
        
        d = opendir(path);
        if (d == NULL) {
            perror("opendir error");
            return;
        }
        
        while (1) {
            pdirent = readdir(d);
            if (pdirent == NULL) {
                break;
            }
            
            printf("OsLs: d_name='%s', d_type=%d\n", pdirent->d_name, pdirent->d_type);
            
            // ... 原有代码
        }
    }
    
  2. 检查 stat 系统调用

    ret = stat(fullpath, &statInfo);
    printf("stat('%s') returned %d, errno=%d\n", fullpath, ret, errno);
    
发现
  • readdir 能正确读取 “.” 和 “…” 目录项
  • stat 对 “.” 和 “…” 返回错误
  • LittleFS 对 “.” 和 “…” 的处理有问题
临时解决方案

修改 ls 命令,跳过 “.” 和 “…” 目录项:

// kernel/liteos_m/components/shell/src/cmds/vfs_shellcmd.c
if (pdirent != NULL) {
    /* Skip "." and ".." entries - littlefs doesn't handle them well */
    if ((strcmp(pdirent->d_name, ".") == 0) ||
        (strcmp(pdirent->d_name, "..") == 0)) {
        continue;
    }
    // ... 原有代码
}

结果: “BAD file” 错误消失,但文件创建仍然失败。


第二阶段:定位文件创建失败

问题分析

文件创建命令(touch、mkdir)执行后没有显示错误,但文件实际上没有被创建。

调试方法
  1. 添加调试信息到 VFS 层

    // kernel/liteos_m/components/fs/vfs/vfs_fs.c
    static int VfsOpen(const char *path, int flags)
    {
        printf("VfsOpen: path='%s', flags=%d\n", path, flags);
        
        mp = VfsMpFind(path, &pathInMp);
        printf("VfsOpen: mp=%p, pathInMp='%s'\n", mp, pathInMp ? pathInMp : "NULL");
        
        // ... 原有代码
    }
    
  2. 添加调试信息到 LittleFS 适配层

    // kernel/liteos_m/components/fs/littlefs/lfs_adapter.c
    int LfsOpen(struct File *file, const char *pathName, int openFlag)
    {
        printf("LfsOpen: opening '%s', flags=%d\n", pathName, openFlag);
        ret = lfs_file_open((lfs_t *)file->fMp->mData, lfsHandle, pathName, lfsOpenFlag);
        printf("LfsOpen: lfs_file_open ret=%d\n", ret);
        // ... 原有代码
    }
    
关键发现
OHOS # touch 1.txt
VfsOpen: path='/1.txt', flags=66
VfsMpFind: path='/1.txt', g_mountPoints=0x9000000002028528
VfsMpFind: checking mp=0x9000000002028528, mPath='/'
VfsMpFind: no match found
VfsOpen: mount point not found or invalid, mp=(nil), pathInMp='NULL'

问题: VfsMpFind 无法找到 mount point!


第三阶段:深入分析 VfsMpFind 函数

问题分析

VfsMpFind 函数负责根据路径查找对应的 mount point。日志显示:

  • g_mountPoints 不为 NULL(地址为 0x9000000002028528)
  • mPath/(根目录)
  • 但匹配失败
代码分析
// kernel/liteos_m/components/fs/vfs/vfs_mount.c
struct MountPoint *VfsMpFind(const char *path, const char **pathInMp)
{
    struct MountPoint *mp = g_mountPoints;
    const char *iPath = path;
    const char *mPath = NULL;
    const char *target = NULL;

    if (pathInMp != NULL) {
        *pathInMp = NULL;
    }
    while (*iPath == '/') {
        ++iPath;  // 跳过前导 '/'
    }

    while ((mp != NULL) && (mp->mPath != NULL)) {
        mPath = mp->mPath;
        target = iPath;

        while (*mPath == '/') {
            ++mPath;  // 跳过前导 '/'
        }

        // 问题在这里!
        while ((*mPath != '\0') && (*mPath != '/') &&
               (*target != '\0') && (*target != '/')) {
            if (*mPath != *target) {
                break;
            }
            ++mPath;
            ++target;
        }
        
        if (((*mPath == '\0') || (*mPath == '/')) &&
            ((*target == '\0') || (*target == '/'))) {
            if (pathInMp != NULL) {
                *pathInMp = path;
            }
            return mp;
        }
        mp = mp->mNext;
    }
    return NULL;
}
执行流程分析

对于路径 /1.txt

  1. iPath 跳过前导 / 后,指向 1.txt
  2. mPath 跳过前导 / 后,指向 \0(因为 mPath 是 /
  3. 进入比较循环:
    • *mPath == '\0',循环不执行
  4. 检查条件:
    • *mPath == '\0'
    • *target == '1'
  5. 匹配失败!

对于路径 /

  1. iPath 跳过前导 / 后,指向 \0
  2. mPath 跳过前导 / 后,指向 \0
  3. 进入比较循环:
    • *mPath == '\0',循环不执行
  4. 检查条件:
    • *mPath == '\0'
    • *target == '\0'
  5. 匹配成功!
根本原因

VfsMpFind 函数的路径匹配逻辑有缺陷:当 mount point 路径是根目录 / 时,无法匹配以 / 开头的子路径(如 /1.txt/os)。

这是因为:

  • 根目录 mount point 的 mPath/
  • 跳过前导 / 后,mPath 变成空字符串
  • 比较循环不执行,直接检查 *target
  • 对于 /1.txttarget 指向 1.txt,不为空,匹配失败

根本原因分析

问题根源

VfsMpFind 函数缺少对根目录 mount point 的特殊处理

为什么会出现这个问题?

  1. 设计缺陷

    • VfsMpFind 函数假设所有 mount point 路径都有非空的路径组件
    • 根目录 / 是特殊情况,跳过前导 / 后变成空字符串
  2. 匹配逻辑错误

    • mPath 为空时,应该匹配所有以 / 开头的路径
    • 但现有逻辑要求 target 也必须为空
  3. 测试覆盖不足

    • 可能只测试了非根目录的 mount point
    • 没有测试根目录 mount point 的路径匹配

影响范围

  • 所有挂载到根目录 / 的文件系统都会受影响
  • 包括但不限于:
    • RAM disk 文件系统
    • Flash 文件系统
    • SD 卡文件系统

解决方案

修复思路

在跳过前导 / 后,如果 mPath 指向 \0(即 mount point 路径是根目录 /),则直接返回该 mount point,匹配所有以 / 开头的路径。

修复代码

// kernel/liteos_m/components/fs/vfs/vfs_mount.c
struct MountPoint *VfsMpFind(const char *path, const char **pathInMp)
{
    struct MountPoint *mp = g_mountPoints;
    const char *iPath = path;
    const char *mPath = NULL;
    const char *target = NULL;

    if (pathInMp != NULL) {
        *pathInMp = NULL;
    }
    while (*iPath == '/') {
        ++iPath;
    }

    while ((mp != NULL) && (mp->mPath != NULL)) {
        mPath = mp->mPath;
        target = iPath;

        while (*mPath == '/') {
            ++mPath;
        }

        // 修复:检查是否为根目录 mount point
        if (*mPath == '\0') {
            if (pathInMp != NULL) {
                *pathInMp = path;
            }
            return mp;  // 根目录匹配所有以 '/' 开头的路径
        }

        while ((*mPath != '\0') && (*mPath != '/') &&
               (*target != '\0') && (*target != '/')) {
            if (*mPath != *target) {
                break;
            }
            ++mPath;
            ++target;
        }
        if (((*mPath == '\0') || (*mPath == '/')) &&
            ((*target == '\0') || (*target == '/'))) {
            if (pathInMp != NULL) {
                *pathInMp = path;
            }
            return mp;
        }
        mp = mp->mNext;
    }
    return NULL;
}

修复逻辑说明

  1. 跳过前导 /

    • iPath 跳过路径的前导 /
    • mPath 跳过 mount point 路径的前导 /
  2. 检查根目录

    • 如果 *mPath == '\0',说明 mount point 路径是根目录 /
    • 直接返回该 mount point,匹配所有以 / 开头的路径
  3. 正常匹配

    • 对于非根目录的 mount point,执行原有的匹配逻辑

代码修改详解

修改文件列表

  1. kernel/liteos_m/components/fs/vfs/vfs_mount.c ⭐ 核心修复

    • 修改 VfsMpFind 函数,添加根目录匹配逻辑
  2. kernel/liteos_m/components/fs/littlefs/lfs_adapter.c

    • 挂载失败时自动格式化文件系统
  3. kernel/liteos_m/components/shell/src/cmds/vfs_shellcmd.c

    • 修改 OsLs 函数,跳过 “.” 和 “…” 目录项
  4. third_party/littlefs/lfs.c (可选)

    • 添加根目录有效性验证

详细修改

1. VfsMpFind 函数修复

文件: kernel/liteos_m/components/fs/vfs/vfs_mount.c

修改位置: 第 151-159 行

修改内容:

// 在跳过前导 '/' 后,添加根目录检查
if (*mPath == '\0') {
    if (pathInMp != NULL) {
        *pathInMp = path;
    }
    return mp;
}

修改前后对比:

// 修改前
while (*mPath == '/') {
    ++mPath;
}

while ((*mPath != '\0') && (*mPath != '/') &&
       (*target != '\0') && (*target != '/')) {
    // ... 匹配逻辑
}

// 修改后
while (*mPath == '/') {
    ++mPath;
}

// 新增:检查根目录 mount point
if (*mPath == '\0') {
    if (pathInMp != NULL) {
        *pathInMp = path;
    }
    return mp;
}

while ((*mPath != '\0') && (*mPath != '/') &&
       (*target != '\0') && (*target != '/')) {
    // ... 匹配逻辑
}
2. ls 命令修复

文件: kernel/liteos_m/components/shell/src/cmds/vfs_shellcmd.c

修改位置: 第 475-480 行

修改内容:

// 跳过 "." 和 ".." 目录项
if ((strcmp(pdirent->d_name, ".") == 0) ||
    (strcmp(pdirent->d_name, "..") == 0)) {
    continue;
}

说明: 这是一个临时解决方案,因为 LittleFS 对 “.” 和 “…” 的处理有问题。更好的解决方案是修复 LittleFS 的目录读取逻辑。

3. LittleFS 适配层修复

文件: kernel/liteos_m/components/fs/littlefs/lfs_adapter.c

修改内容: 挂载失败时自动格式化文件系统

int LfsMount(struct Mount *mp, const void *data)
{
    // ... 初始化配置代码
    
    ret = lfs_mount((lfs_t *)mp->mData, cfg);
    if (ret != 0) {
        // 挂载失败,尝试格式化
        ret = lfs_format((lfs_t *)mp->mData, cfg);
        if (ret == 0) {
            // 格式化成功,重新挂载
            ret = lfs_mount((lfs_t *)mp->mData, cfg);
        }
    }
    
    // ... 后续处理
}

说明: RAM disk 初始状态为空,需要格式化后才能使用。此修改确保首次使用时自动格式化。

4. LittleFS 根目录验证(可选)

文件: third_party/littlefs/lfs.c

修改位置: lfs_mount_ 函数

修改内容:

static int lfs_mount_(lfs_t *lfs, const struct lfs_config *cfg) {
    // ... 原有代码
    
    lfs->lookahead.start = lfs->seed % lfs->block_count;
    lfs_alloc_drop(lfs);

    // 新增:检查根目录是否设置
    if (lfs->root[0] == LFS_BLOCK_NULL || lfs->root[1] == LFS_BLOCK_NULL) {
        LFS_ERROR("Superblock not found, root not set");
        err = LFS_ERR_CORRUPT;
        goto cleanup;
    }

    return 0;
    
    // ... 清理代码
}

说明: 这是一个增强性修改,确保挂载失败时能正确返回错误,而不是静默成功。


测试验证

测试环境

  • 开发板: ls2k300_mini_dp
  • 内核: LiteOS-M
  • 文件系统: LittleFS on RAM disk (64KB)

测试步骤

1. 编译系统
cd /home/vm/oh/oh61
hb build -p ls2k300_mini_dp --gn-args build_xts=true 
2. 烧录并启动

将编译好的固件烧录到开发板,启动系统。

3. 测试文件创建
OHOS # touch test.txt
OHOS # ls
Directory /:
test.txt
OHOS # mkdir os
OHOS # ls
Directory /:
test.txt
os
OHOS # cd os
OHOS # touch file.txt
OHOS # ls
Directory /os:
file.txt

测试结果

最终测试结果 ✅
OHOS # ls 
 Directory /: 
 1.txt                0 
 KV_FILE_SUM          4 
 os                   <DIR> 
 persist_parameters   1008 
 test.txt             24 
 OHOS # cat test.txt 
 Hello from file system! 
 OHOS # cd os
 OHOS # ls
 Directory /os:
 OHOS #

所有功能正常

  • ls 命令正确显示目录内容
  • touch 创建文件成功
  • mkdir 创建目录成功
  • cd 切换目录成功
  • cat 读取文件内容成功
文件创建测试
OHOS # touch 1.txt
VfsMpFind: root mount point matches, mp=0x9000000002028528, pathInMp='/1.txt'
VfsOpen: calling fops->open, pathInMp='/1.txt'
LfsOpen: opening '/1.txt', flags=259
lfs_dir_commit: starting, dir->pair=[0x1, 0x0], attrcount=3
LfsRamWrite: writing 256 bytes at offset 4864
lfs_dir_commit: success
LfsOpen: lfs_file_open ret=0
VfsOpen: fops->open returned 0
VfsOpen: returning fd=3

结果: ✅ 文件创建成功

目录创建测试
OHOS # mkdir os
OHOS # ls
Directory /:
1.txt
os

结果: ✅ 目录创建成功

目录切换测试
OHOS # cd os
OHOS # pwd
/os
OHOS # touch test.txt
OHOS # ls
Directory /os:
test.txt

结果: ✅ 目录切换成功

性能测试

文件系统初始化时间
LfsLowLevelInit: Starting file system init...
LfsLowLevelInit: RAM disk size=65536, block size=4096, block count=16
LfsLowLevelInit: DiskPartition succeed
LfsMount: lfs_mount ret=-84
LfsMount: formatting...
LfsMount: lfs_format ret=0
LfsMount: lfs_mount (after format) ret=0
LfsLowLevelInit: mount fs on '/' succeed
LfsLowLevelInit: File system init completed successfully!

初始化时间: < 100ms

文件操作性能
  • 创建文件: < 10ms
  • 创建目录: < 10ms
  • 读取目录: < 5ms
  • 切换目录: < 5ms

总结与经验

问题总结

  1. 表面现象: ls 命令显示 “BAD file” 错误,文件创建失败
  2. 中间层问题: VFS 层无法找到 mount point
  3. 根本原因: VfsMpFind 函数缺少对根目录 mount point 的特殊处理

真正的修复

经过深入调试,发现问题的真正原因是 VFS 层的 VfsMpFind 函数无法正确匹配根目录挂载点。修复该问题后,文件系统即可正常工作。

核心修复kernel/liteos_m/components/fs/vfs/vfs_mount.c 中的 VfsMpFind 函数。

调试过程中的弯路

在调试过程中,曾错误地认为问题出在 LittleFS 的 lfs_dir_find 函数中,并尝试修改 dir->tail 的更新逻辑。这个修改导致了 “Cycle detected in tail list” 警告,说明修改是错误的。

教训

  1. 应该从上层(VFS)开始调试,逐步向下定位问题
  2. 不要在没有充分理解的情况下修改底层代码
  3. 每次修改后都要验证是否引入了新问题

调试经验

1. 分层调试法

从上到下逐层调试:

  • 应用层: shell 命令
  • VFS 层: 虚拟文件系统
  • 文件系统层: LittleFS 适配层
  • 驱动层: RAM disk 驱动

每一层都添加调试信息,定位问题发生在哪一层。

2. 日志分析

仔细分析日志输出:

  • 关注函数调用顺序
  • 关注返回值和错误码
  • 关注指针和内存地址
3. 代码审查

深入阅读代码:

  • 理解函数的设计意图
  • 分析边界条件
  • 检查特殊情况处理

经验教训

1. 边界条件处理

问题: VfsMpFind 函数没有处理根目录 mount point 的特殊情况

教训:

  • 所有的字符串处理函数都要考虑空字符串的情况
  • 所有的路径处理函数都要考虑根目录的情况
  • 编写单元测试覆盖边界条件
2. 调试信息的重要性

问题: 最初没有调试信息,难以定位问题

教训:

  • 在关键路径添加调试信息
  • 使用条件编译控制调试信息的输出
  • 保留调试信息,方便后续维护
3. 测试覆盖

问题: 可能没有测试根目录 mount point 的场景

教训:

  • 编写完整的测试用例
  • 覆盖所有可能的路径组合
  • 包括边界条件和异常情况

最佳实践

1. 文件系统初始化
// 1. 初始化存储介质
memset(g_lfsRamDisk, 0xFF, LFS_RAM_DISK_SIZE);

// 2. 注册设备
ret = LOS_DiskPartition("ram0", "littlefs", &lengthArray, g_lfsAddrArray, 1);

// 3. 配置分区
partCfg.readFunc = LfsRamRead;
partCfg.writeFunc = LfsRamWrite;
partCfg.eraseFunc = LfsRamErase;
// ... 其他配置

// 4. 挂载文件系统
ret = mount("ram0", "/", "littlefs", 0, &partCfg);

// 5. 测试文件系统
int fd = open("/test.txt", O_CREAT | O_WRONLY, 0666);
if (fd >= 0) {
    // 文件系统正常
    close(fd);
}
2. 错误处理
// 检查所有返回值
int ret = some_function();
if (ret != 0) {
    printf("Error: function failed, ret=%d, errno=%d\n", ret, errno);
    return ret;
}

// 使用断言检查不变量
LFS_ASSERT(ptr != NULL);
LFS_ASSERT(size > 0);
3. 调试信息
// 使用条件编译控制调试信息
#ifdef DEBUG_FS
#define FS_DEBUG(fmt, ...) printf("[FS] " fmt "\n", ##__VA_ARGS__)
#else
#define FS_DEBUG(fmt, ...) 
#endif

// 在关键路径添加调试信息
FS_DEBUG("Opening file: %s, flags=%d", path, flags);

后续改进

1. 完善 “.” 和 “…” 处理

当前只是跳过 “.” 和 “…”,应该修复 LittleFS 的目录读取逻辑,正确处理这两个特殊目录项。

2. 添加更多测试

编写完整的测试用例,覆盖:

  • 文件创建、读取、写入、删除
  • 目录创建、删除、遍历
  • 多级目录操作
  • 并发访问
  • 异常情况处理
3. 性能优化
  • 优化 RAM disk 的读写性能
  • 减少 LittleFS 的元数据开销
  • 添加文件系统缓存

参考资料

官方文档

相关代码

  • kernel/liteos_m/components/fs/vfs/vfs_mount.c - VFS mount 管理
  • kernel/liteos_m/components/fs/vfs/vfs_fs.c - VFS 文件操作
  • kernel/liteos_m/components/fs/littlefs/lfs_adapter.c - LittleFS 适配层
  • third_party/littlefs/lfs.c - LittleFS 核心实现

附录

A. 完整的调试信息输出

LfsLowLevelInit: Starting file system init...
LfsLowLevelInit: RAM disk size=65536, block size=4096, block count=16
LfsLowLevelInit: DiskPartition succeed
mount: source='ram0', target='/', fsType='littlefs'
mount: mp=0x9000000002028528, mPath='/', mDev='ram0'
LfsMount: lfs_mount ret=-84
LfsMount: formatting...
LfsMount: lfs_format ret=0
LfsMount: lfs_mount (after format) ret=0
mount: added mp to list, g_mountPoints=0x9000000002028528
LfsLowLevelInit: mount fs on '/' succeed
LfsLowLevelInit: Testing file system...
VfsOpen: path='/test.txt', flags=577
VfsMpFind: path='/test.txt', g_mountPoints=0x9000000002028528
VfsMpFind: checking mp=0x9000000002028528, mPath='/'
VfsMpFind: root mount point matches, mp=0x9000000002028528, pathInMp='/test.txt'
VfsOpen: calling fops->open, pathInMp='/test.txt'
LfsOpen: opening '/test.txt', flags=577
lfs_file_opencfg_: forcing consistency
lfs_fs_forceconsistency: starting
lfs_fs_forceconsistency: success
lfs_file_opencfg_: finding dir for '/test.txt'
lfs_dir_find: path='/test.txt', lfs->root=[0x1, 0x0]
lfs_dir_commit: starting, dir->pair=[0x1, 0x0], attrcount=3
LfsRamWrite: writing 256 bytes at offset 4864
lfs_dir_commit: success
LfsOpen: lfs_file_open ret=0
VfsOpen: fops->open returned 0
VfsOpen: returning fd=3
LfsLowLevelInit: Test file created successfully!

B. 错误码对照表

错误码 名称 说明
2 ENOENT No such file or directory
5 EIO I/O error
12 ENOMEM Cannot allocate memory
13 EACCES Permission denied
14 EFAULT Bad address
17 EEXIST File exists
22 EINVAL Invalid argument
28 ENOSPC No space left on device
84 EILSEQ Illegal byte sequence (LittleFS: Corrupted)

C. LittleFS 配置参数说明

参数 说明
readSize 256 最小读取字节数
writeSize 256 最小写入字节数
blockSize 4096 块大小(字节)
blockCount 16 块数量
cacheSize 256 缓存大小(字节)
lookaheadSize 16 前瞻缓冲区大小(位)
blockCycles 1000 块磨损均衡周期

结语

这次调试经历让我深刻体会到:

  1. 细节决定成败: 一个看似简单的路径匹配函数,因为缺少对根目录的特殊处理,导致整个文件系统无法正常工作。

  2. 调试方法很重要: 分层调试、日志分析、代码审查,这些方法帮助我们快速定位问题。

  3. 测试覆盖很关键: 如果有完整的测试用例,这个问题可能在开发阶段就被发现。

  4. 文档很有价值: 详细记录调试过程,不仅方便自己回顾,也能帮助他人避免类似问题。

希望这篇文档能够帮助到遇到类似问题的开发者,也欢迎大家对文档提出改进建议。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐