0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

实战 | 睿擎平台SQLite:嵌入式设备上的数据持久化方案,从移植到应用一文打通

RT-Thread官方账号 2026-04-29 19:22 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

嵌入式开发中,数据存储一直是个刚需:设备参数配置、传感器历史数据、运行日志记录……传统方案要么用文件系统裸奔,解析麻烦;要么上 SQLite,但移植门槛高、踩坑多。

今天分享一个基于睿擎派 RC3506的完整 SQLite 方案——从源码移植到 VFS 适配,从 DAO 层封装到 Shell 调试,手把手带你搞定嵌入式数据库。


为什么选 SQLite?

SQLite 是全世界部署最广泛的 SQL 数据库引擎,没有之一:

零配置、无服务器:单文件数据库,无需独立进程,直接嵌入应用

体积极小:核心代码约 250KB(可裁剪),RAM 占用 250KB 起

全功能 SQL:支持事务、索引、触发器、视图,语法兼容标准 SQL

可靠性极高:原子写入、故障恢复机制完善,数据安全有保障

授权友好:公共域代码,商用免费,无需开源你的应用

智能手机汽车电子到工业网关,SQLite 几乎无处不在。对于 RT-Thread 嵌入式平台,它同样是大型数据持久化的常见方案。


架构总览

整个 SQLite for RT-Thread 方案分五层:

a35cf6ce-43bd-11f1-ab55-92fbcf53809c.png

核心移植工作集中在 VFS 适配层,而 dbhelper 中间层则大幅简化了应用开发难度。


移植实战:VFS 适配层详解

1. 编译配置(sqlite_config_rtthread.h)

首先需要告诉 SQLite:“我在 RT-Thread 上运行”。关键宏定义:

#defineSQLITE_OS_OTHER 1// 不使用默认 OS 层
#defineSQLITE_OS_RTTHREAD 1 // 启用 RT-Thread VFS
#defineSQLITE_THREADSAFE 1 // 启用线程安全
#defineSQLITE_OMIT_WAL 1 // 禁用 WAL(减少资源占用)
#defineSQLITE_OMIT_LOAD_EXTENSION 1 // 禁用动态加载扩展

SQLITE_OMIT_WAL=1 是嵌入式场景的常见选择:WAL(Write-Ahead Logging)虽然提升并发性能,但会增加文件数量和内存占用。对于单线程写入场景,回滚日志模式足够。


2. VFS 注册(rtthread_vfs.c)

SQLite 要求所有 VFS 实现 sqlite3_vfs 结构体:

staticsqlite3_vfs _rtthread_vfs={
3,// iVersion:VFS 版本号
sizeof(RTTHREAD_SQLITE_FILE_T),// szOsFile:文件句柄大小
RTTHREAD_MAX_PATHNAME,// mxPathname:最大路径长度(256)
0,// pNext:链表指针(内部使用)
"rt-thread",// zName:VFS 名称
0,// pAppData:应用数据指针
_rtthread_vfs_open,// xOpen:打开文件
_rtthread_vfs_delete,// xDelete:删除文件
_rtthread_vfs_access,// xAccess:检查文件权限
_rtthread_vfs_fullpathname,// xFullPathname:获取绝对路径
// ... 其他接口
};

SQLITE_APIintsqlite3_os_init(void)
{
sqlite3_vfs_register(&_rtthread_vfs,1);// 注册为默认 VFS
returnSQLITE_OK;
}


3. 文件 IO 实现(rtthread_io_methods.c)

核心的读写接口,直接调用 DFS 的 read()/write()/lseek():

staticint_rtthread_io_read(sqlite3_file*file_id,void*pbuf,intcnt,sqlite3_int64 offset)
{
RTTHREAD_SQLITE_FILE_T*file=(RTTHREAD_SQLITE_FILE_T*)file_id;

// 定位到指定偏移
if(lseek(file->fd,offset,SEEK_SET)!=offset){
returnSQLITE_IOERR_READ;
}

// 循环读取直到完成(处理 EINTR 中断)
intr_cnt;
do{
r_cnt=read(file->fd,pbuf,cnt);
if(r_cnt==cnt)break;
if(r_cnt<0&&errno!=EINTR)returnSQLITE_IOERR_READ;
// 处理部分读取
if(r_cnt>0){
cnt-=r_cnt;
pbuf=(void*)(r_cnt+(char*)pbuf);
}
}while(r_cnt>0);

returnSQLITE_OK;
}

staticint_rtthread_io_write(sqlite3_file*file_id,constvoid*pbuf,intcnt,sqlite3_int64 offset)
{
RTTHREAD_SQLITE_FILE_T*file=(RTTHREAD_SQLITE_FILE_T*)file_id;

if(lseek(file->fd,offset,SEEK_SET)!=offset){
returnSQLITE_IOERR_WRITE;
}

intw_cnt;
do{
w_cnt=write(file->fd,pbuf,cnt);
if(w_cnt==cnt)break;
if(w_cnt<0&&errno!=EINTR)returnSQLITE_IOERR_WRITE;
if(w_cnt>0){
cnt-=w_cnt;
pbuf=(void*)(w_cnt+(char*)pbuf);
}
}while(w_cnt>0);

returnSQLITE_OK;
}

特别注意 EINTR 处理:在实时系统中,系统调用可能被信号中断,必须重试。


4. 文件锁实现(rtthread_io_methods.c)

SQLite 的并发控制依赖文件锁。RT-Thread 没有文件锁,我们用信号量模拟

typedefstruct{
sqlite3_io_methodsconst*pMethod;
intfd;// 文件描述符
inteFileLock;// 锁状态:NO_LOCK/SHARED/EXCLUSIVE
structrt_semaphoresem;// 信号量
}RTTHREAD_SQLITE_FILE_T;

staticint_rtthread_io_lock(sqlite3_file*file_id,inteFileLock)
{
RTTHREAD_SQLITE_FILE_T*file=(RTTHREAD_SQLITE_FILE_T*)file_id;
rt_sem_tpsem=&file->sem;

// 已持有锁,直接升级级别
if(file->eFileLock>NO_LOCK){
file->eFileLock=eFileLock;
returnSQLITE_OK;
}

// 尝试获取信号量
if(rt_sem_trytake(psem)!=RT_EOK){
returnSQLITE_BUSY;// 其他线程持有锁
}

file->eFileLock=eFileLock;
returnSQLITE_OK;
}

staticint_rtthread_io_unlock(sqlite3_file*file_id,inteFileLock)
{
RTTHREAD_SQLITE_FILE_T*file=(RTTHREAD_SQLITE_FILE_T*)file_id;

if(eFileLock==NO_LOCK){
rt_sem_release(&file->sem);// 释放信号量
}
file->eFileLock=eFileLock;
returnSQLITE_OK;
}

注意:这是进程内锁,只能防止同一进程内的多线程并发访问。如果多个进程同时访问同一数据库文件,仍需依赖文件系统的锁机制或外部协调。


5. 随机数与时间戳

SQLite 内部需要随机数(生成临时文件名)和当前时间戳:

staticint_rtthread_vfs_randomness(sqlite3_vfs*pvfs,intnByte,char*zOut)
{
chartick8=(char)rt_tick_get();
chartick16=(char)(rt_tick_get()>>8);

for(inti=0;i<nByte;i++){
zOut[i]=(char)(i^tick8^tick16);
tick8=zOut[i];
tick16=~(tick8^tick16);
}
returnnByte;
}

staticint_rtthread_vfs_current_time_int64(sqlite3_vfs*pvfs,sqlite3_int64*pnow)
{
time_tt;
time(&t);
*pnow=((sqlite3_int64)t)*1000+24405875*(sqlite3_int64)8640000;
returnSQLITE_OK;
}


中间层封装:dbhelper 设计

直接调用 SQLite 原生 API 门槛较高:需要手动管理 sqlite3_stmt、处理错误码、编写回滚逻辑。dbhelper 封装了常用操作,让应用层代码更简洁。

初始化

intdb_helper_init(void)
{
sqlite3_initialize();// 初始化 SQLite 内部状态
db_mutex_lock=rt_mutex_create("dbmtx",RT_IPC_FLAG_FIFO);
returnRT_EOK;
}
INIT_APP_EXPORT(db_helper_init);// 自动初始化

数据库创建

intdb_create_database(constchar*sqlstr)
{
returndb_nonquery_operator(sqlstr,0,0);
}

// 使用示例
constchar*sql="CREATE TABLE student("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"name VARCHAR(32) NOT NULL,"
"score INT NOT NULL);";
db_create_database(sql);

带数据绑定的批量插入

intdb_nonquery_operator(constchar*sqlstr,
int(*bind)(sqlite3_stmt*,int,void*),
void*param);

关键优化:一条 SQL 语句只编译一次,然后循环绑定数据执行,大幅提升批量操作性能。

// 插入学生数据
staticintstudent_insert_bind(sqlite3_stmt*stmt,intindex,void*arg)
{
rt_list_t*h=arg;
student_t*s;
rt_list_for_each_entry(s,h,list){
sqlite3_reset(stmt);
sqlite3_bind_text(stmt,1,s->name,strlen(s->name),NULL);
sqlite3_bind_int(stmt,2,s->score);
sqlite3_step(stmt);
}
returnSQLITE_OK;
}

db_nonquery_operator("INSERT INTO student(name,score) VALUES(?,?);",
student_insert_bind,&student_list);

内部已开启事务,失败自动回滚。

注意:db_nonquery_by_varpara() 是单条 SQL 执行,不支持事务。如需事务,请使用 db_nonquery_operator() 或 db_nonquery_transaction()。

带变参的查询

intdb_query_by_varpara(constchar*sql,
int(*create)(sqlite3_stmt*,void*),
void*arg,
constchar*fmt,...);

// 查询指定 ID 的学生
staticintstudent_create(sqlite3_stmt*stmt,void*arg)
{
student_t*s=arg;
if(sqlite3_step(stmt)!=SQLITE_ROW)return0;
s->id=db_stmt_get_int(stmt,0);
db_stmt_get_text(stmt,1,s->name);
s->score=db_stmt_get_int(stmt,2);
returnSQLITE_OK;
}

student_ts;
db_query_by_varpara("SELECT * FROM student WHERE id=?;",
student_create,&s,"%d",student_id);

实战示例:学生成绩管理系统

示例工程 12_data_parsers_sqlite 实现了一个完整的学生成绩管理 DAO 层。

表结构设计

CREATETABLEstudent(
idINTEGERPRIMARYKEYAUTOINCREMENT,
nameVARCHAR(32)NOTNULL,
scoreINTEGERNOTNULL
);

DAO 层核心接口

// 增:批量插入学生
intstudent_add(rt_list_t*h);

// 删:按 ID 删除或删除全部
intstudent_del(intid);
intstudent_del_all(void);

// 改:更新学生信息
intstudent_update(student_t*s);

// 查:按 ID 查询单个,或查询全部
intstudent_get_by_id(student_t*s,intid);
intstudent_get_all(rt_list_t*q);

// 条件查询:按分数区间查询,支持升序/降序
intstudent_get_by_score(rt_list_t*h,intlow,inthigh,enumorder_typeorder);

Shell 命令调试

示例提供了完整的 Shell 命令,方便调试:

msh />create_student_tbl # 创建数据库和表
Database path: /data/stu_info.db

msh />stuadd100# 批量插入 100 条记录
Insert100record(s): 125ms, speed: 1ms/record

msh />stu # 查询全部
id:1 name:Student1234 score:87
id:2 name:Student5678 score:92
...
record(s):100

msh />stu score60100-d # 查询 60-100 分,降序排列
id:88 name:Student9999 score:99
id:42 name:Student1234 score:95
...

msh />stu update1Alice100# 更新记录
update record success!

msh />stu del1# 删除指定记录
Del record success with id:1

msh />stu del # 删除全部
Del all record success!

资源占用

资源

最小配置

典型配置

RAM

250KB

500KB+

ROM

310KB

500KB+

栈空间

4KB

8KB

睿擎派 RC3506(RK3506,512MB DDR)完全满足需求。如果资源紧张,可通过 SQLITE_OMIT_* 宏裁剪不需要的功能。


总结

SQLite 为嵌入式设备提供了企业级的数据管理能力,而 RT-Thread 的 VFS 适配层让移植工作变得简单。通过 dbhelper 中间层封装,应用开发者无需深入了解 SQLite API,就能快速实现数据持久化功能。

关键要点回顾:

VFS 适配层是移植核心,实现文件 IO、锁、随机数、时间戳

dbhelper 封装了事务、数据绑定、错误处理,大幅简化应用代码

Prepared Statement + 事务是批量操作的性能关键

单写者模型:多线程读,单线程写,避免锁竞争

完整示例代码已集成到睿擎 SDK V2604,欢迎体验!

配套资料包

想在自己的项目里复现 SQLite 移植?我们整理了完整资料包,助你快速上手:

SQLite 移植源码包(含 VFS 适配层、dbhelper 封装)

交叉编译脚本与集成示例(RuiChing Studio 可直接导入)

学生成绩管理 DAO 层完整代码

本篇文章涉及的全部 SQL 脚本与 Shell 命令

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据存储
    +关注

    关注

    5

    文章

    1038

    浏览量

    53015
  • 数据库
    +关注

    关注

    7

    文章

    4085

    浏览量

    68560
  • 嵌入式设备
    +关注

    关注

    0

    文章

    126

    浏览量

    17750
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    DeepSeekQwen,AI大模型的移植与交互实战指南-飞凌嵌入式

    在不久前发布的《技术实战|OK3588-C开发板上部署DeepSeek-R1大模型的完整指南》中,小编为大家介绍了DeepSeek-R1在飞凌嵌入式OK3588-C开发板
    的头像 发表于 03-28 08:06 3050次阅读
    <b class='flag-5'>从</b>DeepSeek<b class='flag-5'>到</b>Qwen,AI大模型的<b class='flag-5'>移植</b>与交互<b class='flag-5'>实战</b>指南-飞凌<b class='flag-5'>嵌入式</b>

    派文件系统指南:开发到发布全流程实践 | 技术解析

    嵌入式系统开发中,文件系统扮演着至关重要的角色,它负责数据持久存储、配置文件管理和资源访问等核心功能。
    的头像 发表于 11-05 18:13 8310次阅读
    <b class='flag-5'>睿</b><b class='flag-5'>擎</b>派文件系统指南:<b class='flag-5'>从</b>开发到发布全流程实践 | 技术解析

    嵌入式系统到底该选哪款数据库,SQLite真的是最优解吗?

    数据库选型的最优解吗?在边缘计算、物联网网关、嵌入式系统中,数据往往是各种传感器或设备采集的时序数据,这些
    发表于 02-11 11:02

    嵌入式数据sqlite移植及使用的资料分享

    嵌入式数据sqlite移植及使用、实验目的二.实验内容三.预备知识四.实验设备及工具(包括软
    发表于 10-28 09:48

    sqlite3是如何移植嵌入式Linux

    sqlite可以说是目前使用最广泛的文件型数据库,嵌入式设备的首选。据说iphone手机和微信的数据
    发表于 11-04 08:58

    如何将sqlite3移植嵌入式Linux开发板M6708

    原文链接:添加链接描述最近,因为项目的需要,我们购买了广州致远电子有限公司的M6708-T工控板(预装Linux系统),准备将sqlite3移植嵌入式开发板
    发表于 12-27 07:26

    嵌入式数据Sqlite移植教程

    嵌入式数据Sqlite移植教程 sqlite-3.3.6编译安装与交叉编译全过程详细记录
    发表于 03-11 09:57 4084次阅读

    SQLite嵌入式Wince中的应用

    应该尽量小,SQLite[1]在Linux中的应用很广泛,本设计介绍了SQLite作为款小巧的嵌入式数据库在Wince[2]中的应用实例。
    发表于 11-30 08:55 839次阅读
     <b class='flag-5'>SQLite</b>在<b class='flag-5'>嵌入式</b>Wince中的应用

    如何吧SQLite移植嵌入式Linux系统的详细资料说明

    本文档的主要内容详细介绍的是如何吧SQLite移植嵌入式Linux系统的详细资料说明。
    发表于 01-18 08:00 8次下载
    如何吧<b class='flag-5'>SQLite</b><b class='flag-5'>移植</b><b class='flag-5'>到</b><b class='flag-5'>嵌入式</b>Linux系统的详细资料说明

    嵌入式实验】《嵌入式数据sqlite 移植及使用》

    嵌入式数据sqlite 移植及使用、实验目的二.实验内容三.预备知识四.实验设备及工具(包
    发表于 10-21 10:51 6次下载
    【<b class='flag-5'>嵌入式</b>实验】《<b class='flag-5'>嵌入式</b><b class='flag-5'>数据</b>库 <b class='flag-5'>sqlite</b> <b class='flag-5'>移植</b>及使用》

    sqlite3移植嵌入式Linux

    sqlite可以说是目前使用最广泛的文件型数据库,嵌入式设备的首选。据说iphone手机和微信的数据
    发表于 11-01 17:21 13次下载
    <b class='flag-5'>sqlite</b>3<b class='flag-5'>移植</b><b class='flag-5'>到</b><b class='flag-5'>嵌入式</b>Linux

    明晚8点|文件系统实战开发到发布全流程解析

    文件操作到镜像发布,次直播掌握完整开发流程!在嵌入式系统开发中,文件系统是数据存储、配置管理和资源访问的核心基础。然而在实际开发中,文件操作效率低下、镜像打包流程复杂、系统发布困难
    的头像 发表于 11-11 11:53 794次阅读
    明晚8点|<b class='flag-5'>睿</b><b class='flag-5'>擎</b>文件系统<b class='flag-5'>实战</b>:<b class='flag-5'>从</b>开发到发布全流程解析

    【直播预告】下周三晚8点|物联网实战传感器采集MQTT云全流程解析

    传感器采集云端通信,次直播打通物联网全链路开发!在物联网应用开发中,传感器数据采集不稳定、外设配置复杂、云端通信不可靠等问题常常困扰着
    的头像 发表于 11-21 17:07 2202次阅读
    【直播预告】下周三晚8点|<b class='flag-5'>睿</b><b class='flag-5'>擎</b>物联网<b class='flag-5'>实战</b>:<b class='flag-5'>从</b>传感器采集<b class='flag-5'>到</b>MQTT<b class='flag-5'>上</b>云全流程解析

    明晚:物联网实战传感器采集MQTT云全流程解析|问学直播

    传感器采集云端通信,次直播打通物联网全链路开发!在物联网应用开发中,传感器数据采集不稳定、外设配置复杂、云端通信不可靠等问题常常困扰着
    的头像 发表于 11-25 18:31 558次阅读
    明晚:<b class='flag-5'>睿</b><b class='flag-5'>擎</b>物联网<b class='flag-5'>实战</b>:<b class='flag-5'>从</b>传感器采集<b class='flag-5'>到</b>MQTT<b class='flag-5'>上</b>云全流程解析|问学直播

    直播预告 | 开源软件包移植适配专题(第1期):SQLite 入门精通

    嵌入式数据库在资源受限设备跑不动?交叉编译SQLite总是缺依赖、配置难?想在AMP双系统下统一数据
    的头像 发表于 04-24 18:05 399次阅读
    直播预告 | 开源软件包<b class='flag-5'>移植</b>适配专题(第1期):<b class='flag-5'>SQLite</b> <b class='flag-5'>从</b>入门<b class='flag-5'>到</b>精通