02 键值对存储(fskv)
作者:马亚丹
一、什么是 fskv
fskv 是合宙 LuatOS 系统专为嵌入式设备设计的键值对 (Key-Value) 存储库,全称为 "Flash Key-Value",主要功能是在模组的 flash 存储器中持久化存储数据,使设备断电后数据不会丢失,相当于给嵌入式设备提供了一个 "不会失忆的大脑"。
核心功能与特点:
- 持久化存储:数据存储在 flash 上,设备重启或断电后数据仍然保留
- 键值对形式:通过字符串 key 访问对应 value,使用简单直观
- 大容量支持:单个 value 最大可存储 4096 字节数据
- 稳定性能:读写速度恒定,不受脏数据影响
- 自动均衡擦除:最高 10 万次均衡擦写,延长 flash 使用寿命
- 支持多种数据类型:字符串、数值、布尔值和 table
fskv 占用独立的 64KB 分区, 是合宙 LuatOS 生态中重要的数据持久化解决方案,特别适合物联网设备存储不频繁变更但需要长期保存的数据。
注:10 万次均衡擦写是指 Flash 存储的单个单元最多能承受 10 万次 “擦除 - 写入” 循环,再通过均衡擦写技术将数据分散到所有单元,避免个别单元提前损坏,从而延长整体设备寿命,
从原理上来说,依靠均衡擦写技术,设备能持续的稳定使用。
二、功能演示概述
本文演示合宙 4G 模组使用 LuatOS 开发时, fskv 的应用功能.
使用 Air780EHV 核心板下载 Air780EHV 的 LuatOS 示例代码中 fskv 的例程进行验证,例程中实现的功能核心业务逻辑为:
1.初始化 fskv
2.获取 kv 数据库状态
3.设置不同类型的 kv 数据
4.设置 table 内的键值对数据
5.根据 key 获取对应的数据
6.使用 kv 迭代器遍历 kv 数据
7.删除 kv 数据,清空 KV 数据
三、准备硬件环境
3.1 参考:硬件环境清单第二章节内容,准备以及组装好硬件环境。
3.2 Air780EHV 核心板一块。
3.3 TYPE-C USB 数据线一根 ,Air780EHV 核心板和数据线的硬件接线方式为:
- Air780EHV 核心板通过 TYPE-C USB 口供电;(USB 的拨码开关 off/on,拨到 on);
- TYPE-C USB 数据线直接插到核心板的 TYPE-C USB 座子,另外一端连接电脑 USB 口。
四、准备软件环境
在开始实践本示例之前,先准备一下软件环境:
- Luatools 工具,如果是第一次使用 Luatools 工具,请仔细阅读此链接教程。
- 内核固件文件(底层 core 固件文件):LuatOS-SoC_V2018_Air780EHV_1.soc;参考项目使用的内核固件;如有更新可以使用最新固件。
- luatos 需要的脚本和资源文件:https://gitee.com/openLuat/LuatOS/tree/master/module/Air780EHV/demo/fskv
main.lua:主程序入口;
fskv_test.lua: 功能演示核心脚本,初始化 fskv、写入 kv 数据、读取删除数据等,在 main.lua 中加载运行。
- lib 脚本文件:使用 Luatools 烧录时,勾选 添加默认 lib 选项,使用默认 lib 脚本文件;
准备好软件环境之后,接下来查看如何烧录项目文件到 Air780EHV 核心板,将本篇文章中演示使用的项目文件烧录到 Air780EHV 核心板中。
五、API 接口说明
六、示例代码和功能展示
6.1 流程介绍
1、搭建好硬件环境
2、Luatools 烧录内核固件和 demo 脚本代码
3、烧录成功后,代码会自动运行,查看打印日志,如果正常运行,会打印初始化 fskv、写入 kv 数据、读取删除数据等信息,如下 log 显示
6.2 代码和 log
6.2.1 main.lua 代码示例
(点击查看 fskv 的完整 demo)
PROJECT = "fskv_demo"
VERSION = "001.000.000"
-- 在日志中打印项目名和项目版本号
log.info("main", PROJECT, VERSION)
-- 如果内核固件支持wdt看门狗功能,此处对看门狗进行初始化和定时喂狗处理
-- 如果脚本程序死循环卡死,就会无法及时喂狗,最终会自动重启
if wdt then
--配置喂狗超时时间为9秒钟
wdt.init(9000)
--启动一个循环定时器,每隔3秒钟喂一次狗
sys.timerLoopStart(wdt.feed, 3000)
end
-- 如果内核固件支持errDump功能,此处进行配置,【强烈建议打开此处的注释】
-- 因为此功能模块可以记录并且上传脚本在运行过程中出现的语法错误或者其他自定义的错误信息,可以初步分析一些设备运行异常的问题
-- 以下代码是最基本的用法,更复杂的用法可以详细阅读API说明文档
-- 启动errDump日志存储并且上传功能,600秒上传一次
-- if errDump then
-- errDump.config(true, 600)
-- end
-- 使用LuatOS开发的任何一个项目,都强烈建议使用远程升级FOTA功能
-- 可以使用合宙的iot.openluat.com平台进行远程升级
-- 也可以使用客户自己搭建的平台进行远程升级
-- 远程升级的详细用法,可以参考fota的demo进行使用
-- 启动一个循环定时器
-- 每隔3秒钟打印一次总内存,实时的已使用内存,历史最高的已使用内存情况
-- 方便分析内存使用是否有异常
-- sys.timerLoopStart(function()
-- log.info("mem.lua", rtos.meminfo())
-- log.info("mem.sys", rtos.meminfo("sys"))
-- end, 3000)
-- 加载fskv_test功能模块
require "fskv_test"
-- 用户代码已结束---------------------------------------------
-- 结尾总是这一句
sys.run()
-- sys.run()之后不要加任何语句!!!!!因为添加的任何语句都不会被执行
6.2.2 核心代码部分
(点击查看 fskv 的完整 demo)
--1.定义功能函数:置kv 数据
local function setKV()
--如下所示设置用户数据是字符串
local r1 = fskv.set("my_str", "goodgoodstudy")
log.info("fskv设置用户数据是字符串", r1)
--如下所示设置用户数据是布尔值
local r2 = fskv.set("my_bool", true)
log.info("fskv设置用户数据是布尔值", r2)
--如下所示设置用户数据是数值
local r3 = fskv.set("my_number", 1.23)
log.info("fskv设置用户数据是数值", r3)
--如下所示设置用户数据是整数
local r4 = fskv.set("my_int", 5)
log.info("fskv设置用户数据是整数", r4)
--如下所示设置用户数据是table
local r5 = fskv.set("my_table", { name = "wendal", age = 18 })
log.info("fskv用户数据是table类型", r5)
return true
end
--2.定义功能函数:设置 table 内的键值对数据
local function setttable()
--如下所示设置用户数据是字符串
local r1=fskv.sett("mytable", "wendal", "goodgoodstudy")
log.info("mytable设置用户数据是字符串", r1)
--如下所示设置用户数据是布尔值
local r2=fskv.sett("mytable", "upgrade", true)
log.info("mytable设置用户数据是布尔值", r2)
--如下所示设置用户数据是数值
local r3=fskv.sett("mytable", "timer", 1)
log.info("mytable设置用户数据是数值", r3)
--如下所示设置用户数据是table
local r4=fskv.sett("mytable", "bigd", { name = "wendal", age = 123 })
log.info("mytable设置用户数据是table", r4)
return true
end
--3.定义功能函数:获取kv数据
local function getKV()
local my_str = fskv.get("my_str")
log.info("获取my_str的类型和值", type(my_str), my_str)
local my_bool = fskv.get("my_bool")
log.info("获取upgrade的类型和值", type(my_bool), my_bool)
local my_number = fskv.get("my_number")
log.info("获取my_number的类型和值", type(my_number), my_number)
local my_int = fskv.get("my_int")
log.info("获取my_int的类型和值", type(my_int), my_int)
local my_table = fskv.get("my_table")
log.info("获取my_table的类型和值", my_table,json.encode(my_table))
local mytable = fskv.get("mytable")
log.info("获取mytable的类型和值", mytable, json.encode(mytable))
return true
end
--4.定义功能函数:kv数据库迭代器遍历KV数据
local function iterKV()
local iter = fskv.iter()
log.info("kv数据库迭代器", iter)
if iter then
while true do
local k = fskv.next(iter)
log.info("kv迭代器获取下一个key", k)
if not k then
log.info("kv数据库遍历完成")
break
end
log.info("fskv", k, "value", fskv.get(k))
end
end
return true
end
--====功能演示主函数====
local function fskv_test()
-- 初始化kv数据库
local r = fskv.init()
log.info("fskv", "init complete", r)
--获取 kv 数据库状态
local used, total, kv_count = fskv.status()
log.info("获取kv数据库状态", "fskv", "kv", used, total, kv_count)
--=======设置kv 数据=======
if not setKV() then
log.info("设置kv数据失败")
end
--=======设置 table 内的键值对数据=======
if not setttable() then
log.info("设置 table 内的键值对数据失败")
end
--======根据 key 获取对应的数据====
if not getKV() then
log.info("获取kv数据失败")
end
--======使用kv数据库迭代器遍历KV数据====
if not iterKV() then
log.info("遍历kv数据失败")
end
-- 删除测试
local d = fskv.del("my_bool")
log.info("fskv", "my_bool删除结果", d)
local t = fskv.get("my_bool")
log.info("fskv", "删除后查询my_bool", type(t), t)
-- 如果设置table的value为nil, 代表删除对应的skey
-- 如下写法是删除name
log.info("设置新的table,key是mytable2", fskv.set("mytable2", { age = 18, name = "wendal" }))
log.info("mytable2的值", fskv.get("mytable2"),json.encode(fskv.get("mytable2")))
log.info("mytable2删除name测试", fskv.sett("mytable2", "name", nil))
log.info("mytable2删除结果", fskv.get("mytable2"), json.encode(fskv.get("mytable2")))
--清空整个kv数据库
log.info("清空整个kv数据库", fskv.clear())
local used, total, kv_count = fskv.status()
log.info("获取kv数据库状态", "fskv", "kv", used, total, kv_count)
end
sys.taskInit(fskv_test)
6.2.3 例程 log 打印
[2025-11-21 17:18:37.104][000000000.372] I/user.main fskv_demo 001.000.000
[2025-11-21 17:18:37.111][000000000.379] I/lfs sfd_lfs mount ret -84, exec auto-format
[2025-11-21 17:18:37.120][000000000.551] D/lfs init ok
[2025-11-21 17:18:37.135][000000000.552] I/user.fskv init complete true
[2025-11-21 17:18:37.149][000000000.552] I/user.获取kv数据库状态 fskv kv 8192 65536 0
[2025-11-21 17:18:37.156][000000000.553] I/user.fskv设置用户数据是字符串 true
[2025-11-21 17:18:37.167][000000000.554] I/user.fskv设置用户数据是布尔值 true
[2025-11-21 17:18:37.177][000000000.555] I/user.fskv设置用户数据是数值 true
[2025-11-21 17:18:37.187][000000000.557] I/user.fskv设置用户数据是整数 true
[2025-11-21 17:18:37.196][000000000.558] I/user.fskv用户数据是table类型 true
[2025-11-21 17:18:37.210][000000000.560] I/user.mytable设置用户数据是字符串 true
[2025-11-21 17:18:37.218][000000000.563] I/user.mytable设置用户数据是布尔值 true
[2025-11-21 17:18:37.225][000000000.566] I/user.mytable设置用户数据是数值 true
[2025-11-21 17:18:37.234][000000000.569] I/user.mytable设置用户数据是table true
[2025-11-21 17:18:37.247][000000000.571] I/user.获取my_str的类型和值 string goodgoodstudy
[2025-11-21 17:18:37.263][000000000.573] I/user.获取upgrade的类型和值 boolean true
[2025-11-21 17:18:37.273][000000000.574] I/user.获取my_number的类型和值 number 1.230000
[2025-11-21 17:18:37.283][000000000.576] I/user.获取my_int的类型和值 number 5
[2025-11-21 17:18:37.290][000000000.578] I/user.获取my_table的类型和值 table: 0C7F59D0 {"name":"wendal","age":18}
[2025-11-21 17:18:37.303][000000000.580] I/user.获取mytable的类型和值 table: 0C7F5858 {"bigd":{"name":"wendal","age":123},"wendal":"goodgoodstudy","timer":1,"upgrade":true}
[2025-11-21 17:18:37.309][000000000.580] I/user.kv数据库迭代器 userdata: 0C7F5670
[2025-11-21 17:18:37.317][000000000.583] I/user.kv迭代器获取下一个key my_bool
[2025-11-21 17:18:37.322][000000000.585] I/user.fskv my_bool value true
[2025-11-21 17:18:37.333][000000000.587] I/user.kv迭代器获取下一个key my_int
[2025-11-21 17:18:37.352][000000000.588] I/user.fskv my_int value 5
[2025-11-21 17:18:37.366][000000000.591] I/user.kv迭代器获取下一个key my_number
[2025-11-21 17:18:37.375][000000000.593] I/user.fskv my_number value 1.230000
[2025-11-21 17:18:37.387][000000000.595] I/user.kv迭代器获取下一个key my_str
[2025-11-21 17:18:37.399][000000000.597] I/user.fskv my_str value goodgoodstudy
[2025-11-21 17:18:37.407][000000000.599] I/user.kv迭代器获取下一个key my_table
[2025-11-21 17:18:37.418][000000000.601] I/user.fskv my_table value table: 0C7F5538
[2025-11-21 17:18:37.437][000000000.603] I/user.kv迭代器获取下一个key mytable
[2025-11-21 17:18:37.460][000000000.606] I/user.fskv mytable value table: 0C7F5420
[2025-11-21 17:18:37.473][000000000.608] I/user.kv迭代器获取下一个key nil
[2025-11-21 17:18:37.484][000000000.608] I/user.kv数据库遍历完成
[2025-11-21 17:18:37.501][000000000.653] I/user.fskv my_bool删除结果 true
[2025-11-21 17:18:37.515][000000000.654] I/user.fskv 删除后查询my_bool nil nil
[2025-11-21 17:18:37.528][000000000.655] I/user.设置新的table,key是mytable2 true
[2025-11-21 17:18:37.539][000000000.659] I/user.mytable2的值 table: 0C7F51E0 {"age":18,"name":"wendal"}
[2025-11-21 17:18:37.558][000000000.662] I/user.mytable2删除name测试 true
[2025-11-21 17:18:37.567][000000000.664] I/user.mytable2删除结果 table: 0C7F4FF8 {"age":18}
[2025-11-21 17:18:37.580][000000000.738] I/user.清空整个kv数据库 true
[2025-11-21 17:18:37.592][000000000.739] I/user.获取kv数据库状态 fskv kv 8192 65536 0
6.2.4 luatools 页面显示


七、总结
本文档主要介绍 4G 通信中 fskv 通信的应用。
结合 demo 例程讲解了 fskv 基本原理,介绍了 fskv 主要 API,旨在最简单的快速上手 Air780EHV 的 LuatOS 的 fskv 开发.