一、驱动层:SFUD(Serial Flash Universal Driver) 是一款开源的串行 SPI Flash 通用驱动库
二、中间层:FAL(FLASH ABSTRACTION LAYER))FLASH 抽象层
三、应用层:FlashDB(FlashDB 是一款超轻量级的嵌入式数据库)
后记1:FlashDB嵌入式数据库之TSDB数据存储解析
FlashDB之TSDB解析
- 一、初始化过程记录
- 二、Flash中的存储格式
- 三、实际使用率计算
- 四、查询流程
- 五、补充:性能测试
写在前面,好久才来写这边文章,读源码还是比较累的,先记录下来后期会议的时候方便一点。
一、初始化过程记录
先看一下帧头结构体
struct sector_hdr_data {uint8_t status[FDB_STORE_STATUS_TABLE_SIZE]; /**< sector store status @see fdb_sector_store_status_t */uint32_t magic; /**< magic word(`T`, `S`, `L`, `0`) */fdb_time_t start_time; /**< the first start node's timestamp */struct {fdb_time_t time; /**< the last end node's timestamp */uint32_t index; /**< the last end node's index */uint8_t status[TSL_STATUS_TABLE_SIZE]; /**< end node status, @see fdb_tsl_status_t */} end_info[2];uint32_t reserved;
};
根据计算sizeof(struct sector_hdr_data)= 40bytes
根据注释可以看出来,帧头中存储了扇区的状态、关键字、开始时间、结束时间、和预留了一个32位的数这样就可以实现对单个扇区的快速检索,
只需要读取头部的40个字节,就可以快速判断扇区是否已存满,扇区中的数据的开始时间和结束时间,如果需要检索数据本扇区中的数据是否符合条件,当然前提是时间戳是连续的,这个是默认条件。
二、Flash中的存储格式
源代码调用的层次很多,我们直接讲结果,有兴趣的自己看源码,都是开源的。
可以看到帧头后面紧跟着的是数据头信息,每条数据头信息由16个字节组成
- 数据头信息 = 数据状态 + 数据时间戳 + 数据长度 + 数据内容起始地址
- 这样的好处是数据头信息是固定长度和固定位置的,可以方便我们快速根据时间戳检索,数据的内容是可以不定长的。
- log内容用指针指向特定的地址,长度就不需要固定的
- 每个扇区单独成块,就是一个最小的数据库单元
扇区头 | log1_hdr | log2_hdr | 剩余空间 | 剩余空间 | log2_data | log1_data |
---|
可以看到log1的数据头指向的数据内容在扇区的最后面,log2指向的内容再次后面,二边向中间挤压
三、实际使用率计算
每条记录固定占用空间为16字节
如果你的数据内容是16字节
实际使用率 = 16/(16+16) = 50%
一个扇区可以储存数量 = (4096 - 40) / 16 + 16 = 126(条)
1M FLASH有扇区数量 = 1M / 4096 = 256 (个)
1M FLASH可以存储数据 = 126*256 = 32256(条)
如果你的flash是8M的自己算一下。
四、查询流程
原生的api接口只有一个按时间查询的命令
/*** The TSDB iterator for each TSL by timestamp.** @param db database object* @param from starting timestap* @param to ending timestap* @param cb callback* @param arg callback argument*/
void fdb_tsl_iter_by_time(fdb_tsdb_t db, fdb_time_t from, fdb_time_t to, fdb_tsl_cb cb, void *cb_arg)
{struct tsdb_sec_info sector;uint32_t sec_addr, oldest_addr = db->oldest_addr, traversed_len = 0;struct fdb_tsl tsl;bool found_start_tsl = false;if (!db_init_ok(db)) {FDB_INFO("Error: TSL (%s) isn't initialize OK.\n", db_name(db));}// FDB_INFO("from %s", ctime((const time_t * )&from));
// FDB_INFO("to %s", ctime((const time_t * )&to));if (cb == NULL) {return;}sec_addr = oldest_addr;/* search all sectors */do {if (read_sector_info(db, sec_addr, §or, false) != FDB_NO_ERR) {continue;}/* sector has TSL */if ((sector.status == FDB_SECTOR_STORE_USING || sector.status == FDB_SECTOR_STORE_FULL)) {if (sector.status == FDB_SECTOR_STORE_USING) {/* copy the current using sector status */sector = db->cur_sec;}if ((!found_start_tsl && ((from >= sector.start_time && from <= sector.end_time)|| (sec_addr == oldest_addr && from <= sector.start_time))) || (found_start_tsl)) {uint32_t start = sector.addr + SECTOR_HDR_DATA_SIZE, end = sector.end_idx;found_start_tsl = true;/* search start TSL address, using binary search algorithm */while (start <= end) {tsl.addr.index = start + ((end - start) / 2 + 1) / LOG_IDX_DATA_SIZE * LOG_IDX_DATA_SIZE;read_tsl(db, &tsl);if (tsl.time < from) {start = tsl.addr.index + LOG_IDX_DATA_SIZE;} else {end = tsl.addr.index - LOG_IDX_DATA_SIZE;}}tsl.addr.index = start;/* search all TSL */do {read_tsl(db, &tsl);if (tsl.time >= from && tsl.time <= to) {/* iterator is interrupted when callback return true */if (cb(&tsl, cb_arg)) {return;}} else {return;}} while ((tsl.addr.index = get_next_tsl_addr(§or, &tsl)) != FAILED_ADDR);}} else if (sector.status == FDB_SECTOR_STORE_EMPTY) {return;}traversed_len += db_sec_size(db);} while ((sec_addr = get_next_sector_addr(db, §or, traversed_len)) != FAILED_ADDR);
}
就是从起始时间查询到结束时间,提供了一个回调接口,在这个接口中把数据显示在LCD屏上。
五、补充:性能测试
单纯的速度测试我没有做,原文给出了大概的速度,而且也没有实际的使用价值。
原文给的速度
msh />tsl bench
Append 13421 TSL in 5 seconds, average: 2684.20 tsl/S, 0.37 ms/per
Query total spent 1475 (ms) for 13422 TSL, min 0, max 1, average: 0.11 ms/per
我的环境是 STM32F4+TSDB+FATFS+U盘(USB_Host)
导出了10W条速度用时1分35秒(实际工程中测试)
导出至USB ≈ 1052per/s,差距还是挺大的。