本次介绍一个极致轻量化的嵌入式存储方案 —— UTFS(μTFS,micro TAR File System)。
UTFS 简介
UTFS 项目地址:
https://github.com/clisystems/utfs
MIT 开源协议

UTFS 是一款类 TAR 归档、面向扁平字节寻址存储的极简存储层,并非完整带目录、日志、磨损均衡的通用文件系统。
它的定位是手写固化结构体的标准化替代方案。为什么这么说?往下看它的特点就明白了。
UTFS 的特点:
- 体积极小:仅
utfs.c + utfs.h 两个源码文件,纯 C99 实现;
- 移植门槛极低:只需实现
sys_read、sys_write 两个底层读写函数;
- 零动态内存:全程无
malloc/free,全部静态内存,不占用堆;
- 向前兼容:后续新增参数文件,不会破坏原有存储布局,升级不丢旧数据;
- 极低存储开销:每个文件仅 24 字节固定头部,数据无填充、无对齐浪费。
UTFS 不是 FatFS / LittleFS。它不做目录树、不做 journaling、也不做磨损均衡:

持久化的坑
嵌入式开发中,参数持久化常见的坑有哪些?
- 直接写固定偏移 EEPROM/Flash,新增配置就要全量改存储布局,旧固件数据直接报废;
- 手写结构体固化,多组参数分开存储,地址管理混乱,断电校验全靠自己造轮子;
- 引入 FatFS/LittleFS,ROM 占用大、必须堆内存,AVR、STM8 这类小 RAM 芯片直接跑不动。
很多产品的真实需求其实很朴素:
- 上电把几块配置读进 RAM
- 运行中改结构体字段
- 合适的时候整表写回 EEPROM / Flash
这时候如果硬上完整文件系统,目录、权限、长文件名这些给人看的能力往往用不上,代码和 RAM 却先涨一截。反过来,继续手搓一个巨型 struct 钉在 0x0000,短期最快,长期最容易在加字段那天翻车。

手搓固定偏移 vs UTFS
左边那种布局,每个字段的地址都是产品契约的一部分;右边 UTFS 的思路是:每个文件自带 24 字节头,按名字匹配,数据背靠背排布。以后加一个 calib,旧文件还是按名字找,而不是全员挪偏移。
demo:Linux PC 快速验证
想快速上手验证,直接在 PC 上跑一遍 Linux 示例即可:
cd Examples/gcc_linux
make
./main.bin -v


整体使用逻辑可以概括为三步:
utfs_set + utfs_register:把本地 RAM 结构体和文件名绑定注册;
utfs_load:上电从 EEPROM/Flash 读取所有注册数据到 RAM;
utfs_save:参数修改后,一次性全量写入持久化介质。

UTFS 到底长什么样
名字可以记成 micro TAR File System(μTFS / uTFS)。灵感来自 TAR:Header + Data 重复拼接。
桌面 TAR 头是 512 字节一块,嵌入式扛不住,也用不上用户/用户组/权限那一套。UTFS 把头压到 24 字节,后面紧跟数据,文件之间没有 padding。

从上面这张布局图可以看到,每个文件都由一个 24 字节的 Header 和紧随其后的数据区组成。Header 中封装了 Identifier、Signature、Size、Filename 这几个关键字段,分别用来认格式、判定首启/升级、确认数据长度以及按名匹配注册表。这种设计与 TAR 的归档思路一脉相承,但把头部压缩到了极限。
最小闭环:register → load → save
UTFS 的使用节奏和传统 open/read/write/close 不太一样。它更像:
一次性登记关心的文件,整表加载,整表保存。

UTFS 生命周期
典型写法可以这样组织:
utfs_init(false);
utfs_set(&sysfile, "system", &sysdata, sizeof(sysdata));
utfs_register(&sysfile, UTFS_NOFLAGS, UTFS_NOOPT);
utfs_set(&appfile, "appdata", &appdata, sizeof(appdata));
utfs_register(&appfile, UTFS_NOFLAGS, UTFS_NOOPT);
utfs_load();
if (utfs_file_signature(&appfile) != 0xABCD) {
memset(&appdata, 0, sizeof(appdata));
utfs_file_signature_set(&appfile, 0xABCD);
appdata.test_value = 1234; // 首启默认值
}
/* 业务里照常改 RAM 里的结构体……需要落盘时: */
utfs_save();
登记要持久化的东西,整表装进 RAM,改完再整表交回去。 中间业务代码不用围着文件系统 API 转,继续写结构体字段就行。
移植缝也刻意留得很窄:库自己不做 I/O,实现两个函数就行:
uint32_t sys_write(uint32_t address, void *ptr, uint32_t length);
uint32_t sys_read(uint32_t address, void *ptr, uint32_t length);
EEPROM、片内 Flash、外部 SPI/I²C Flash,只要能按地址读写字节,就能接。
总结
UTFS 适合的场景:
- 仅存储少量配置结构体(≤5 组参数),不需要日志、大文件存储;
- 小容量存储:1KB~16KB EEPROM、片内 Flash 参数分区。
如果你对这种轻量级存储方案有更多实践心得或疑问,欢迎到 云栈社区 与开发者们深入交流。