在嵌入式开发中,数据存储一直是个刚需:设备参数配置、传感器历史数据、运行日志记录……传统方案要么用文件系统裸奔,解析麻烦;要么上 SQLite,但移植门槛高、踩坑多。
今天分享一个基于睿擎派 RC3506 的完整 SQLite 方案——从源码移植到 VFS 适配,从 DAO 层封装到 Shell 调试,手把手带你搞定嵌入式数据库。
SQLite 是全世界部署最广泛的 SQL 数据库引擎,没有之一:
● 零配置、无服务器:单文件数据库,无需独立进程,直接嵌入应用
● 体积极小:核心代码约 250KB(可裁剪),RAM 占用 250KB 起
● 全功能 SQL:支持事务、索引、触发器、视图,语法兼容标准 SQL
● 可靠性极高:原子写入、故障恢复机制完善,数据安全有保障
● 授权友好:公共域代码,商用免费,无需开源你的应用
从智能手机、汽车电子到工业网关,SQLite 几乎无处不在。对于 RT-Thread 嵌入式平台,它同样是大型数据持久化的常见方案。
整个 SQLite for RT-Thread 方案分五层:

核心移植工作集中在 VFS 适配层,而 dbhelper 中间层则大幅简化了应用开发难度。
首先需要告诉 SQLite:“我在 RT-Thread 上运行”。关键宏定义:
#define SQLITE_OS_OTHER 1// 不使用默认 OS 层
#define SQLITE_OS_RTTHREAD 1 // 启用 RT-Thread VFS
#define SQLITE_THREADSAFE 1 // 启用线程安全
#define SQLITE_OMIT_WAL 1 // 禁用 WAL(减少资源占用)
#define SQLITE_OMIT_LOAD_EXTENSION 1 // 禁用动态加载扩展
SQLITE_OMIT_WAL=1 是嵌入式场景的常见选择:WAL(Write-Ahead Logging)虽然提升并发性能,但会增加文件数量和内存占用。对于单线程写入场景,回滚日志模式足够。
SQLite 要求所有 VFS 实现 sqlite3_vfs 结构体:
static sqlite3_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_API intsqlite3_os_init(void)
{
sqlite3_vfs_register(&_rtthread_vfs,1);// 注册为默认 VFS
return SQLITE_OK;
}
核心的读写接口,直接调用 DFS 的 read()/write()/lseek():
staticint_rtthread_io_read(sqlite3_file *file_id,void*pbuf,int cnt, sqlite3_int64 offset)
{
RTTHREAD_SQLITE_FILE_T *file =(RTTHREAD_SQLITE_FILE_T*)file_id;
// 定位到指定偏移
if(lseek(file->fd, offset,SEEK_SET)!= offset){
return SQLITE_IOERR_READ;
}
// 循环读取直到完成(处理 EINTR 中断)
int r_cnt;
do{
r_cnt =read(file->fd, pbuf, cnt);
if(r_cnt == cnt)break;
if(r_cnt <0&& errno != EINTR)return SQLITE_IOERR_READ;
// 处理部分读取
if(r_cnt >0){
cnt -= r_cnt;
pbuf =(void*)(r_cnt +(char*)pbuf);
}
}while(r_cnt >0);
return SQLITE_OK;
}
staticint_rtthread_io_write(sqlite3_file *file_id,constvoid*pbuf,int cnt, sqlite3_int64 offset)
{
RTTHREAD_SQLITE_FILE_T *file =(RTTHREAD_SQLITE_FILE_T*)file_id;
if(lseek(file->fd, offset,SEEK_SET)!= offset){
return SQLITE_IOERR_WRITE;
}
int w_cnt;
do{
w_cnt =write(file->fd, pbuf, cnt);
if(w_cnt == cnt)break;
if(w_cnt <0&& errno != EINTR)return SQLITE_IOERR_WRITE;
if(w_cnt >0){
cnt -= w_cnt;
pbuf =(void*)(w_cnt +(char*)pbuf);
}
}while(w_cnt >0);
return SQLITE_OK;
}
特别注意 EINTR 处理:在实时系统中,系统调用可能被信号中断,必须重试。
SQLite 的并发控制依赖文件锁。RT-Thread 没有文件锁,我们用信号量模拟:
typedefstruct{
sqlite3_io_methods const*pMethod;
int fd;// 文件描述符
int eFileLock;// 锁状态:NO_LOCK/SHARED/EXCLUSIVE
structrt_semaphore sem;// 信号量
} RTTHREAD_SQLITE_FILE_T;
staticint_rtthread_io_lock(sqlite3_file *file_id,int eFileLock)
{
RTTHREAD_SQLITE_FILE_T *file =(RTTHREAD_SQLITE_FILE_T*)file_id;
rt_sem_t psem =&file->sem;
// 已持有锁,直接升级级别
if(file->eFileLock > NO_LOCK){
file->eFileLock = eFileLock;
return SQLITE_OK;
}
// 尝试获取信号量
if(rt_sem_trytake(psem)!= RT_EOK){
return SQLITE_BUSY;// 其他线程持有锁
}
file->eFileLock = eFileLock;
return SQLITE_OK;
}
staticint_rtthread_io_unlock(sqlite3_file *file_id,int eFileLock)
{
RTTHREAD_SQLITE_FILE_T *file =(RTTHREAD_SQLITE_FILE_T*)file_id;
if(eFileLock == NO_LOCK){
rt_sem_release(&file->sem);// 释放信号量
}
file->eFileLock = eFileLock;
return SQLITE_OK;
}
注意:这是进程内锁,只能防止同一进程内的多线程并发访问。如果多个进程同时访问同一数据库文件,仍需依赖文件系统的锁机制或外部协调。
SQLite 内部需要随机数(生成临时文件名)和当前时间戳:
staticint_rtthread_vfs_randomness(sqlite3_vfs *pvfs,int nByte,char*zOut)
{
char tick8 =(char)rt_tick_get();
char tick16 =(char)(rt_tick_get()>>8);
for(int i =0; i < nByte; i++){
zOut[i]=(char)(i ^ tick8 ^ tick16);
tick8 = zOut[i];
tick16 =~(tick8 ^ tick16);
}
return nByte;
}
staticint_rtthread_vfs_current_time_int64(sqlite3_vfs *pvfs, sqlite3_int64 *pnow)
{
time_t t;
time(&t);
*pnow =((sqlite3_int64)t)*1000+24405875*(sqlite3_int64)8640000;
return SQLITE_OK;
}
直接调用 SQLite 原生 API 门槛较高:需要手动管理 sqlite3_stmt、处理错误码、编写回滚逻辑。dbhelper 封装了常用操作,让应用层代码更简洁。
intdb_helper_init(void)
{
sqlite3_initialize();// 初始化 SQLite 内部状态
db_mutex_lock =rt_mutex_create("dbmtx", RT_IPC_FLAG_FIFO);
return RT_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,int index,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);
}
return SQLITE_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);
return SQLITE_OK;
}
student_t s;
db_query_by_varpara("SELECT * FROM student WHERE id=?;",
student_create,&s,"%d", student_id);
示例工程 12_data_parsers_sqlite 实现了一个完整的学生成绩管理 DAO 层。
CREATETABLE student(
id INTEGERPRIMARYKEY AUTOINCREMENT,
name VARCHAR(32)NOTNULL,
score INTEGERNOTNULL
);
// 增:批量插入学生
intstudent_add(rt_list_t*h);
// 删:按 ID 删除或删除全部
intstudent_del(int id);
intstudent_del_all(void);
// 改:更新学生信息
intstudent_update(student_t*s);
// 查:按 ID 查询单个,或查询全部
intstudent_get_by_id(student_t*s,int id);
intstudent_get_all(rt_list_t*q);
// 条件查询:按分数区间查询,支持升序/降序
intstudent_get_by_score(rt_list_t*h,int low,int high,enumorder_type order);
示例提供了完整的 Shell 命令,方便调试:
msh /> create_student_tbl # 创建数据库和表
Database path: /data/stu_info.db
msh /> stu add100# 批量插入 100 条记录
Insert 100 record(s): 125ms, speed: 1ms/record
msh /> stu # 查询全部
id:1 name:Student1234 score:87
id:2 name:Student5678 score:92
...
record(s): 100
msh /> stu score 60100 -d # 查询 60-100 分,降序排列
id:88 name:Student9999 score:99
id:42 name:Student1234 score:95
...
msh /> stu update 1 Alice 100# 更新记录
update record success!
msh /> stu del 1# 删除指定记录
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 命令
全部0条评论
快来发表一下你的评论吧 !