OpenHarmony LiteOS-M KV Store 测试失败调试全记录(龙芯 LS2K0300 平台)
背景与现象
在龙芯 LS2K0300 平台上运行 OpenHarmony 6.1(LiteOS-M 内核)的 KV Store 功能测试套件 KvStoreFuncTestSuite 时,出现大量测试用例失败。典型输出如下:
testKvStoreSetValue001:FAIL: Expected 0 Was -1
testKvStoreSetValue002:FAIL: Expected 0 Was -1
testKvStoreSetValue003:FAIL: Expected 0 Was -1
testKvStoreSetValue004:FAIL: Expected 0 Was -1
testKvStoreSetValue005:PASS ← 空值测试,不涉及文件操作
testKvStoreGetValue001:FAIL: Expected 0 Was -1
testKvStoreClearCache001:FAIL: Expected 0 Was -1
testKvStoreCacheSize001:FAIL: Expected 0 Was -1
testKvStoreMaxSize001:FAIL: Expected 0 Was -1
所有涉及文件读写的用例均返回 -1(EC_FAILURE),只有不操作文件的用例通过。后续调试过程中,又出现了另外几类错误:
LfsLowLevelInit: mount fs on '/' failed
LfsLowLevelInit: RAM disk allocation failed
以及修复上述问题后仍然存在的测试失败:
testKvStoreClearCache001:FAIL: Expected -1 to be greater than 0
testKvStoreMaxSize001:FAIL: Expected -1 to be greater than 0
...
本文完整记录了从现象定位到最终解决的调试全过程,涉及文件系统挂载、内存布局、HAL 接口契约等多个层面的问题。
第一阶段:文件系统与路径问题
1.1 调用链分析
从测试代码到硬件层的完整调用链如下:
测试代码: UtilsSetValue("test_key_1", "value_1")
│
▼
kv_store.c: SetValueToFile("test_key_1", "value_1")
│ 注意:key 直接作为文件名使用
▼
kv_store.c: UtilsFileOpen("test_key_1", O_RDWR_FS | O_CREAT_FS | O_TRUNC_FS, 0)
│
▼
file.c: HalFileOpen("test_key_1", oflag, mode)
│
▼
hal_file.c: open("test_key_1", oflag) ← ❌ 路径不以 "/" 开头
│
▼
vfs_fs.c: VfsOpen("test_key_1", flags)
│
▼
vfs_mount.c: VfsMpFind("test_key_1", &pathInMp)
│ ❌ VfsMpFind 要求路径以 "/" 开头才能匹配挂载点
│ "test_key_1" 不以 "/" 开头 → mp = NULL → 返回 -1
▼
返回 EC_FAILURE (-1)
1.2 二个叠加的基础问题
问题一:/data 目录未创建
KV Store 测试将数据路径设置为 /data:
#define DATA_PATH "/data"
static BOOL KvStoreFuncTestSuiteSetUp(void)
{
UtilsSetEnv(DATA_PATH); // 设置路径为 /data
return TRUE;
}
但 UtilsSetEnv() 是一个空函数:
int UtilsSetEnv(const char* path)
{
(void)path;
return EC_SUCCESS; // 什么都没做
}
即使文件系统挂载成功,/data 目录也未被创建,导致后续以 /data/ 前缀写文件时找不到路径。
问题二:HAL 层未处理非绝对路径
默认 HAL 实现 commonlibrary/utils_lite/hals/file/hal_file.c 直接将传入的路径透传给 POSIX open():
int HalFileOpen(const char *path, int oflag, int mode)
{
(void)mode;
return open(path, oflag); // path = "test_key_1"
}
而 LiteOS-M 的 VFS 层要求路径以 / 开头才能正确匹配挂载点。对于 "test_key_1" 这种非绝对路径,VfsMpFind 会直接返回 NULL。
注意:在 Hi3861 等平台上,HAL 层会自动为相对路径添加 /data/ 前缀,但龙芯平台使用的是通用实现,没有这一逻辑。
1.3 第一阶段修复
修复1:创建 /data 目录
在 fs_init.c 的 LfsLowLevelInit() 末尾添加:
mkdir("/data", 0777);
printf("%s: Created /data directory\n", __func__);
修复2:平台特定 HAL 层添加路径前缀
在 device/soc/loongson/ls2k300/adapter/hals/utils/file/hal_file.c 中实现路径自动补全:
#define DATA_PATH_PREFIX "/data/"
static void GetFullPath(const char *path, char *fullPath, unsigned int fullLen)
{
if (path == NULL || fullPath == NULL || fullLen == 0) {
return;
}
if (path[0] == '/') {
snprintf(fullPath, fullLen, "%s", path);
} else {
snprintf(fullPath, fullLen, "%s%s", DATA_PATH_PREFIX, path);
}
}
int HalFileOpen(const char *path, int oflag, int mode)
{
(void)mode;
char fullPath[PATH_MAX] = {0};
GetFullPath(path, fullPath, sizeof(fullPath));
return open(fullPath, oflag);
}
// HalFileDelete、HalFileStat 等函数同理处理
同时编写对应的 BUILD.gn,利用 OpenHarmony 的板级适配机制自动覆盖默认实现。
第二阶段:内存与存储空间不足
修复第一阶段问题后,测试依然失败,且启动日志中出现新的错误:
LfsLowLevelInit: mount fs on '/' failed
2.1 LittleFS RAM 磁盘空间不足
fs_init.c 中原本的 RAM disk 分配大小为 32KB,对于存储多组 KV 对(测试会写入 128 个键值对)来说远远不够。
解决方案:将 RAM disk 扩大至 256KB。
#define LFS_RAM_DISK_SIZE (256 * 1024)
#define LFS_BLOCK_SIZE 4096
#define LFS_BLOCK_COUNT (LFS_RAM_DISK_SIZE / LFS_BLOCK_SIZE)
static uint8_t g_lfsRamDisk[LFS_RAM_DISK_SIZE]; // 静态分配在 BSS 段
2.2 堆空间不足
改为 256KB 静态 RAM disk 后,又出现新的错误:
LfsLowLevelInit: RAM disk allocation failed
原因分析:LittleFS 挂载过程中仍需要从 heap 中分配内部缓存(cacheSize、lookaheadSize 等)和 VFS 结构体。默认的 heap 大小仅 64KB,无法满足需求。
解决方案:将系统堆扩大至 320KB。
在 kernel/liteos_m/kernel/include/los_config.h 中修改:
#ifndef LOSCFG_SYS_HEAP_SIZE
#define LOSCFG_SYS_HEAP_SIZE (320 * 1024)
#endif
内存布局总结:
| 项目 | 大小 | 说明 |
|---|---|---|
| RAM 总大小 | 800KB | Linker script 定义 |
| Stack | 64KB | _STACK_SIZE = 0x10000 |
| LittleFS RAM disk | 256KB | 静态分配(BSS 段) |
| Heap | 320KB | 动态分配 |
此时文件系统能够正常挂载,部分测试开始通过,但仍有几类用例失败。
第三阶段:HalFileStat 接口契约错误(根本原因)
3.1 现象
即使文件系统工作正常、heap 空间充足,以下用例仍然失败:
testKvStoreClearCache001:FAIL: Expected -1 to be greater than 0
testKvStoreClearCache002:FAIL: Expected -1 to be greater than 0
testKvStoreCacheSize003:FAIL: Expected -1 to be greater than 0
testKvStoreMaxSize001:FAIL: Expected -1 to be greater than 0
...
所有失败都表现为 UtilsGetValue 返回 -1,但期望返回值 > 0。
3.2 深度调用链追踪
UtilsGetValue(key, temp, len)
↓
GetValueByFile(key, value, len) [kv_store.c]
↓
UtilsFileStat(key, &valueLen) [kv_store.c]
↓
HalFileStat(key, &valueLen) [hal_file.c] ← 问题发生点
在 GetValueByFile 中,判断逻辑为:
static int GetValueByFile(const char* key, char* value, unsigned int len)
{
unsigned int valueLen = 0;
if (UtilsFileStat(key, &valueLen) != EC_SUCCESS) { // EC_SUCCESS = 0
return EC_FAILURE; // 只要返回值不是 0 就失败
}
// ... 后续读文件操作
}
3.3 错误的 HAL 实现
第一阶段我们添加的 HalFileStat 实现如下:
int HalFileStat(const char *path, unsigned int *fileSize)
{
(void)fileSize; // ❌ 忽略了输出参数
char fullPath[PATH_MAX] = {0};
GetFullPath(path, fullPath, sizeof(fullPath));
struct stat info = { 0 };
int ret = stat(fullPath, &info);
if (ret < 0) {
return ret; // 失败返回 -1(正确)
} else {
return info.st_size; // ❌ 成功时返回文件大小(如 28),而不是 0
}
}
根据 utils_file.h 的接口契约:
/**
* @brief Obtains the file size.
* @param path Indicates the file name.
* @param fileSize Indicates the file size. This is an output parameter.
* @return Returns <b>0</b> if the file size is obtained; returns <b>-1</b> otherwise.
*/
int UtilsFileStat(const char* path, unsigned int* fileSize);
- 成功时应当返回
0,并将文件大小写入*fileSize。 - 失败时返回
-1。
我们错误地在成功时返回了文件大小(一个正整数),导致上层 if (UtilsFileStat(...) != 0) 条件成立,误认为操作失败。
3.4 正确修复
int HalFileStat(const char *path, unsigned int *fileSize)
{
char fullPath[PATH_MAX] = {0};
GetFullPath(path, fullPath, sizeof(fullPath));
struct stat info = { 0 };
int ret = stat(fullPath, &info);
if (ret < 0) {
return -1;
}
if (fileSize != NULL) {
*fileSize = (unsigned int)info.st_size; // 正确写回输出参数
}
return 0; // 成功返回 0
}
同样的修正也需要应用到 HalFileDelete、HalFileOpen 等其他 HAL 函数中,确保返回值严格遵循 POSIX-like 接口约定(成功返回 0,失败返回 -1)。
完整修复方案汇总
1. 文件系统初始化修复
文件:device/board/loongson/ls2k300_mini_dp/liteos_m/fs/fs_init.c
- 扩大 RAM disk 至 256KB。
- 在挂载成功后创建
/data目录。 - 修正
main.c中的条件编译错误(或确保LfsLowLevelInit被正确调用)。
2. 堆大小配置
文件:kernel/liteos_m/kernel/include/los_config.h
#define LOSCFG_SYS_HEAP_SIZE (320 * 1024)
3. 平台 HAL 层适配
文件:device/soc/loongson/ls2k300/adapter/hals/utils/file/hal_file.c
- 实现
GetFullPath为相对路径添加/data/前缀。 - 修正所有文件操作函数(
HalFileOpen、HalFileDelete、HalFileStat)的返回值和参数处理,严格遵循接口契约。
文件:同目录下的 BUILD.gn
ohos_static_library("hal_file_static") {
sources = [ "hal_file.c" ]
include_dirs = [
"//commonlibrary/utils_lite/include",
"//commonlibrary/utils_lite/hals/file",
]
}
利用 commonlibrary/utils_lite/file/BUILD.gn 中的板级适配逻辑自动覆盖默认实现。
测试验证
完成所有修复后,重新编译并烧录固件,运行 KV Store 功能测试套件:
testKvStoreSetValue001-017: PASS
testKvStoreGetValue001-005: PASS
testKvStoreDeleteValue: PASS
testKvStoreClearCache001-002: PASS
testKvStoreCacheSize001-003: PASS
testKvStoreMaxSize001-004: PASS
全部 30+ 个测试用例均通过。
经验总结
接口契约是铁律
HAL 层函数必须严格遵守上层定义的接口契约,包括:
- 返回值的含义(0 表示成功,-1 表示失败)。
- 输出参数的使用(不能忽略、不能为空)。
- 路径处理的约定。
任何偏离都可能导致上层逻辑误判,且这类错误往往难以直接定位。
内存规划要全面
在资源受限的 LiteOS-M 平台上,静态分配(BSS/Data)和动态分配(Heap)需统筹考虑:
- LittleFS RAM disk 若用静态数组,占用的是 BSS 段,不消耗 heap。
- 但 LittleFS 内部缓存、VFS 结构体仍需 heap 空间。
- 测试用例可能同时打开多个文件,需要预留足够的文件句柄和缓冲区。
- 内存空间有限的情况下,为满足KvTest 可以将RAM disk配置为128KB(静态分配),heap 配置为64KB, stack 配置为64KB, 整个RAM 锁定为384KB
调试方法总结
- 从失败用例倒推调用链,逐层检查函数返回值与参数传递。
- 增加日志输出,尤其在 HAL 层和文件系统层打印路径和错误码。
- 检查编译配置与链接脚本,确认内存布局和宏定义是否符合预期。
- 参考其他成功平台的实现(如 Hi3861),对比 HAL 层差异。
适用性说明
本文所述问题及解决方案专门针对 龙芯 LS2K300 平台 + OpenHarmony 6.1 (LiteOS-M) 组合。不同平台的内存布局和 HAL 层实现可能有所不同,但排查思路和接口契约的遵守原则是通用的。
更多推荐


所有评论(0)