fs - 文件系统额外操作
作者:王棚嶙
一、概述
fs
库是LuatOS的核心文件系统操作模块,提供对设备存储空间的底层访问能力。该库支持对嵌入式设备的Flash存储器,外部存储器(sd卡)进行空间查询,获取文件大小的操作功能。
PS:现fs库功能均可在io库内接口实现,强烈建议后续全部使用io库功能。
io.fsstat(path)替代fs.fsstat(path)
io.exists(path)替代fs.fsize(path)
1.1 存储系统概述
LuatOS的存储系统采用清晰的分层架构设计,其核心目标是为应用层提供统一、简洁的文件操作接口,从而屏蔽底层不同存储介质和文件系统的差异。开发者无需关心数据具体存储在何种硬件上,也无需关心底层文件系统的具体实现,只需调用统一的API即可完成文件操作。
该系统的层次结构与各组件间的协作关系如下图所示,其数据流与依赖关系自上而下贯穿各层:
1.1.1 物理硬件层
这是数据存储的物理载体,LuatOS支持多种常见的嵌入式存储硬件:
1、SPI/SDIO TF卡:通过高速SPI或SDIO接口连接的块存储设备,特点是容量大、可插拔,适用于存储大量数据(如音频、图片文件);
2、SPI NOR Flash:通过SPI接口访问的NOR型闪存。其特点是读写速度快,通常用于存放固件代码或需要快速读写的关键数据;
3、SPI NAND Flash:通过SPI接口访问的NAND型闪存。其特点是容量大、成本低,适用于存储日志等容量需求较大的数据;
1.1.2 文件系统实现层(fatfs&lf)
文件系统负责管理存储设备的空间,组织文件和目录结构。LuatOS根据硬件特性,适配了两种主流的嵌入式文件系统:
1、FATFS:一个兼容FAT12/FAT16/FAT32标准的通用文件系统。它通常与SD/TF卡配对使用,;
2、LFS(LittleFS):它通常与SPI NOR Flash和SPI NAND Flash配对使用,非常适合在裸Flash设备上运行;
1.1.3 统一API层 (io)
fs库不推荐新客户使用,该库仅对已经使用过的老客户进行保留,其功能已经完全移植到io库中,且更加完善,新客户建议都参考io核心库:
1、fs
库:提供文件系统的额外操作不建议使用,请使用io库进行替代;
2、io
库:提供标准的数据流操作接口,用于文件的打开、读写、关闭等,是处理文件内容的主要手段,包含fs库的所有功能,推荐使用;
1.1.4 应用层(luatOS应用脚本)
客户通过调用io库,fatfs库,lf库以及fs库中的api接口,来实现自己的业务逻辑。
1.1.5 推荐使用
你的需求 | 推荐使用的库和函数 | 理由 |
---|---|---|
快速检查一个文件的大小 | io.exists(path) | 最简洁、最直接,一行代码搞定。 |
快速查看文件系统的整体使用情况(如剩余空间) | io.fsstat(path) | 提供通用信息,代码兼容性好。 |
需要对FAT格式的U盘、SD卡进行深度管理 | fatfs 库 (如fatfs.getfree(), fatfs.mount()) | 只有它才能提供FAT文件系统的专业控制和详细信息。 |
要读取、写入文件内容,或进行复杂的文件操作 | io 库 (如 io.open, file:read, file:write) | 这是进行文件I/O操作的标准和核心方式,不可替代。 |
对于FAT文件系统需求,使用 fatfs
库。
对于文件内容操作需求,使用 io
库。
1.2 关于"块"(block )的概念解释
块(Block)是文件系统管理存储的基本单位,但需要注意:
1.2.1 块的数量是整数
fs.fsstat
返回的"总空间"和"已用空间"值表示的是块的数量这些都是整数。
1.2.2 块的大小也是整数
每个块的大小是固定的,通常是512字节或4096字节等2的幂次方。
1.2.3 为什么需要单独返回块大小
1、不同的文件系统可能使用不同的块大小
2、同一设备上的不同分区可能使用不同的块大小
3、知道块大小才能将块数量转换为实际字节数
1.2.4 计算实际空间
1、总空间(字节) = 总块数 × 块大小
2、已用空间(字节) = 已用块数 × 块大小
3、空闲空间(字节) = (总块数 - 已用块数) × 块大小
1.2.5 为什么会有这样的设计?
这种设计(返回块数和块大小而不是直接返回字节数)有以下几个原因:
1、效率:文件系统内部是以块为单位管理空间的,直接返回块数更高效
2、精确性:避免浮点数运算可能带来的精度问题
3、灵活性:适应不同块大小的文件系统
二、核心示例
1、核心示例是指:使用本库文件提供的核心 API,开发的基础业务逻辑的演示代码;
2、核心示例的作用是:帮助开发者快速理解如何使用本库,所以核心示例的逻辑都比较简单;
3、更加完整和详细的 demo,请参考 LuatOS 仓库 中各个产品目录下的 demo/fs_io;
local mount_ok, mount_err = fatfs.mount(fatfs.SPI, "/sd", spi_id, pin_cs, 24 * 1000 * 1000)
if mount_ok then
log.info("fatfs.mount", "挂载成功", mount_err)
else
log.error("fatfs.mount", "挂载失败", mount_err)
end
-- ########## 获取SD卡的可用空间信息并打印。 ##########
local data, err = fatfs.getfree("/sd")
if data then
-- 打印SD卡的可用空间信息
log.info("fatfs", "getfree", json.encode(data))
else
-- 执行tfcard文件操作演示
log.info("文件操作", "===== 开始文件操作 =====")
dir_path = "/sd/io_test"
-- 创建目录
if io.mkdir(dir_path) then
log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
-- 创建并写入文件
local file_path = dir_path .. "/boottime"
local file = io.open(file_path, "wb")
if file then
file:write("这是io库API文档示例的测试内容")
file:close()
-- 在LuatOS文件操作中,执行file:close()是必须且关键的操作,它用于关闭文件句柄,释放资源,并确保数据被正确写入磁盘。
-- 如果不执行file:close(),可能会导致数据丢失、文件损坏或其他不可预测的问题。
log.info("文件创建", "文件写入成功", "路径:" .. file_path)
else
log.error("文件创建", "文件创建失败", "路径:" .. file_path)
end
-- === fs函数测试开始 ===
log.info("=== fs函数测试开始 ===")
-- 测试fs.fsstat
-- 日志打印fs.fsstat成功: 总空间=244264960块 已用=2304块 块大小=512字节 类型=fatfs
log.info("测试fs.fsstat('/sd'):")
local ok, total, used, block_size, fs_type = fs.fsstat("/sd")
if ok then
log.info("fs.fsstat成功:",
"总空间=" .. total .. "块",
"已用=" .. used .. "块",
"块大小=" .. block_size.."字节",
"类型=" .. fs_type)
else
-- 错误信息在total中
log.info("fs.fsstat失败:", total)
end
-- 测试fs.fsize
local test_file = "/sd/io_test/test_fs.txt"
log.info("测试fs.fsize('" .. test_file .. "'):")
-- 先创建测试文件
local f = io.open(test_file, "w")
if f then
f:write("Hello LuatOS!")
f:close()
-- 测试fs.fsize
local file_size = fs.fsize(test_file)
if file_size and file_size > 0 then
-- 日志打印fs.fsize成功: 文件大小=13字节
log.info("fs.fsize成功:", "文件大小=" .. file_size .. "字节")
else
log.info("fs.fsize失败:", "返回值=" .. tostring(file_size))
end
-- 清理测试文件
os.remove(test_file)
else
log.info("无法创建测试文件")
end
-- === fs函数测试结束 ===
三、常量详解
核心库常量,顾名思义是由合宙 LuatOS 内核固件中定义的、不可重新赋值或修改的固定值,在脚本代码中不需要声明,可直接调用;
fs核心库无常量
四、函数详解
4.1 fs.fsstat(path)
功能
获取文件系统状态信息;
注意事项
1、路径参数可选,默认为根目录"/",表示模组内置Flash中的文件系统分区;
2、目前在传入错误路径的情况下,会自动调整为可查的文件系统分区根目录,强烈建议使用io.fsstat(path);
3、返回多个值表示文件系统状态信息;
参数
path
参数含义:文件系统路径;
数据类型:string;
取值范围:有效文件系统路径;
是否必选:否;
注意事项:默认值为根目录"/",表示模组内置Flash中的文件系统分区,若路径无效可能返回失败;
参数示例:"/"
返回值
local success, total_blocks, used_blocks, block_size, fs_type = fs.fsstat(path);
存在五个返回值:success, total_blocks, used_blocks, block_size, fs_type;
success
含义说明:操作是否成功;
数据类型:boolean;
取值范围:true 或 false;
注意事项:获取成功返回true,否则返回false;
返回示例:true
total_blocks
含义说明:总的block(块)数量;
数据类型:number;
取值范围:≥0的整数;
注意事项:文件系统总容量指标,失败时第一个返回值success为false0,每个块的大小都是一致的;
返回示例:244264960
used_blocks
含义说明:已使用的block(块)数量;
数据类型:number;
取值范围:≥0的整数;
注意事项:文件系统已使用容量指标,失败时第一个返回值success为false,每个块的大小都是一致的;
返回示例:2304
block_size
含义说明:block(内存块)的大小;
数据类型:number;
取值范围:≥0的整数;
注意事项:单位:字节;
返回示例:512
fs_type
含义说明:文件系统类型;
数据类型:string;
取值范围:文件系统标识字符串,如"lfs","fatfs";
注意事项:理论上失败时返回空字符串"",目前实际会自动识别能够识别到的文件系统类型;
返回示例:"fatfs"
示例
-- ########## 获取SD卡的可用空间信息并打印。 ##########
local data, err = fatfs.getfree("/sd")
if data then
-- 打印SD卡的可用空间信息
--fatfs getfree {"free_sectors":244263168,"total_kb":122132480,"free_kb":122131584,"total_sectors":244264960}
log.info("fatfs", "getfree", json.encode(data))
-- 创建目录
if io.mkdir(dir_path) then
log.info("io.mkdir", "目录创建成功", "路径:" .. dir_path)
-- 创建并写入文件
local file_path = dir_path .. "/boottime"
local file = io.open(file_path, "wb")
if file then
file:write("这是io库API文档示例的测试内容")
file:close()
-- 在LuatOS文件操作中,执行file:close()是必须且关键的操作,它用于关闭文件句柄,释放资源,并确保数据被正确写入磁盘。
-- 如果不执行file:close(),可能会导致数据丢失、文件损坏或其他不可预测的问题。
log.info("文件创建", "文件写入成功", "路径:" .. file_path)
else
-- === fs函数测试开始 ===
log.info("=== fs函数测试开始 ===")
-- 测试fs.fsstat
-- 日志打印fs.fsstat成功: 总空间=244264960块 已用=2304块 块大小=512字节 类型=fatfs
log.info("测试fs.fsstat('/sd'):")
local success, total_blocks, used_blocks, block_size, fs_type = fs.fsstat("/sd")
if ok then
log.info("fs.fsstat成功:",
"总空间=" .. total_blocks .. "块",
"已用=" .. used_blocks .. "块",
"块大小=" .. used_blocks.."字节",
"类型=" .. fs_type)
else
-- 错误信息在total中
log.info("fs.fsstat失败:", total)
end
--[[
我使用的是128GB的新的sd卡,我们验证下数据,fatfs.getfree 得到的空间信息是否正确:
total_sectors = 244,264,960 个扇区
每个扇区通常是 512 字节(这是FAT文件系统的标准)
总容量 = 244,264,960 × 512 = 125,063,659,520 字节
转换为KB = 125,063,659,520 / 1024 = 122,132,480 KB (与日志中的 total_kb 一致)
转换为GB = 122,132,480 / 1024 / 1024 ≈ 116.5 GB
128GB TF卡显示为116.5GB是正常的,因为:
1.厂商使用的十进制计算(1GB = 10⁹字节)vs 系统使用的二进制计算(1GB = 2³⁰字节)
2.文件系统本身会占用一些空间
3.可能有隐藏的系统分区
fs.fsstat('/sd') 给出的空间是否正确?
总块数 = 244,264,960
块大小 = 512 字节
总容量 = 244,264,960 × 512 = 125,063,659,520 字节 ≈ 116.47GB
已用块数 = 2,304
已用空间 = 2,304 × 512 = 1,179,648 字节 ≈ 1.15 MB
这与 fatfs.getfree 的结果一致
]]
4.2 fs.fsize(path)
功能
获取文件大小;
注意事项
1、必须提供有效的文件路径;
2、无法判断文件是否存在,如要判断请使用io.exists(path);
参数
path
参数含义:文件系统路径;
数据类型:string;
取值范围:有效文件系统路径;
是否必选:是;
注意事项:暂无;
参数示例:"/main.luac"
返回值
local size = fs.fsize(path)
有一个返回值 size
size
含义说明:文件大小;
数据类型:number;
取值范围:≥0的整数;
注意事项:获取一个文件的大小时:
1、首先使用io.exists(path)接口判断下文件是否存在,如果文件不存在,则不需 要继续判断,根据文件不存在的结论,执行自己的业务逻辑即可;如果文件存 在,继续执行第2步判断;
2、使用fs.fsize(path)获取返回值,返回值为0表示获取失败,返回值大于0表示 获取成功(此时的返回值就是文件大小);
返回示例:13
示例
local test_file = "/sd/io_test/test_fs.txt"
log.info("测试fs.fsize('" .. test_file .. "'):")
-- 先创建测试文件
local f = io.open(test_file, "w")
if f then
f:write("Hello LuatOS!")
f:close()
-- 测试fs.fsize
local size = fs.fsize(test_file)
if size and size > 0 then
-- 日志打印fs.fsize成功: 文件大小=13字节
log.info("fs.fsize成功:", "文件大小=" .. size .. "字节")
else
log.info("fs.fsize失败:", "返回值=" .. tostring(size))
end
其他情况示例
-- 测试1: 已存在的空文件
local test_file = "/test_fs.txt"
log.info("测试1 - fs.fsize('" .. test_file .. "') 已存在的空文件:")
local f = io.open(test_file, "w")
if f then
-- 创建空文件,不写入任何内容
f:close()
log.info("空文件创建成功")
local size = fs.fsize(test_file)
if size then
-- fopen test_fs.txt rb
-- fs.fsize结果: 文件大小=0字节
log.info("fs.fsize结果:", "文件大小=" .. tostring(size) .. "字节")
-- 测试2: 不存在的路径文件
local non_exist_file = "/non_exist_file.txt"
log.info("测试2 - fs.fsize('" .. non_exist_file .. "') 不存在的文件:")
local size = fs.fsize(non_exist_file)
if size then
-- fopen non_exist_file.txt rb
-- fopen /non_exist_file.txt rb not found
-- fs.fsize结果: 文件大小=0字节
log.info("fs.fsize结果:", "文件大小=" .. tostring(size) .. "字节")
-- 测试3: 空路径
log.info("测试3 - fs.fsize('') 空路径:")
local size = fs.fsize("")
if size then
-- fopen rb
-- fopen rb not found
-- fs.fsize结果: 文件大小=0字节
log.info("fs.fsize结果:", "文件大小=" .. tostring(size) .. "字节")
五、产品支持说明
支持 LuatOS 开发的所有产品都支持 fs 核心库,但是不建议使用,请使用io核心库接口实现业务逻辑。