当前位置: 首页 > 工具软件 > FlashDB > 使用案例 >

FlashDB数据库动态回滚机制

解晟睿
2023-12-01

针对数据库存储存满的情况,FlashDB具有良好的处理机制。一般flash存满之后都需要整块擦除才能进行下一次存储,这样会造成擦除时间过长占用时间片的问题。FlashDB数据通过动态擦除、动态存储和状态位切换的方式完美的解决了数据库存满之后需要整块擦除的弊端。


1.sector(区块)定义

(1)区块大小

FlashDB的区块大小为4K字节,刚好是一个外部flash的最小区块单元,这样划分便于擦除和写入。

(2)区块参数

区块主要有校验标志、状态、区块首地址、起始node时间、结束node时间、结束node的id、结束node的状态、剩余的长度等参数,以下会针对具体参数进行详细阐述。

/* TSDB section information */
struct tsdb_sec_info {
    bool check_ok;                               /**< sector header check is OK */
    fdb_sector_store_status_t status;            /**< sector store status @see fdb_sector_store_status_t */
    uint32_t addr;                               /**< sector start address */
    uint32_t magic;                              /**< magic word(`T`, `S`, `L`, `0`) */
    fdb_time_t start_time;                       /**< the first start node's timestamp, 0xFFFFFFFF: unused */
    fdb_time_t end_time;                         /**< the last end node's timestamp, 0xFFFFFFFF: unused */
    uint32_t end_idx;                            /**< the last end node's index, 0xFFFFFFFF: unused */
    fdb_tsl_status_t end_info_stat[2];           /**< the last end node's info status */
    size_t remain;                               /**< remain size */
    uint32_t empty_idx;                          /**< the next empty node index address */
    uint32_t empty_data;                         /**< the next empty node's data end address */
};

(3)区块状态

FlashDB的区块状态用于标记该区块当前的状态,主要分为未使用、空、使用中、满四个状态

/* the flash sector store status */
enum fdb_sector_store_status {
    FDB_SECTOR_STORE_UNUSED,
    FDB_SECTOR_STORE_EMPTY,
    FDB_SECTOR_STORE_USING,
    FDB_SECTOR_STORE_FULL,
};

(3)区块首地址

FlashDB的区块首地址是用于标记该区块在flash中的具体位置,同时也是通过这个首地址进行区块回滚。

2.回滚处理流程

针对Sector不同状态会进行不同的处理,因此会出现以下三种情况。

(1)初次存储

当FlashDB数据库都为空的时候所有的区块都是EMPTY状态,此时直接将该区块标记为USING状态,接着直接将本次数据和时间戳写入数据库。

(2)过程中存储

当该区块已经标记为USING状态,同时下次写入的数量也不会超过该区块,直接将本次数据和时间戳写入数据库。

(3)满状态存储

当该区块已经标记为USING状态,同时下次写入的数量超过该区块范围。这时就会将区块暂时标记为FULL状态,直接将首地址偏移到下一个区块,同时擦除下一个区块,这是当前区块就是一个空的状态(将当前区块标记为空)。

通过循环往复执行(1)(2)(3)方式的存储就可以实现分区块存储,如果检测到区块为flash的最后一块区域直接跳转到第一个区块。这样就可以实现回滚存储的功能。以下代码是区块状态切换的流程处理。

static fdb_err_t update_sec_status(fdb_tsdb_t db, tsdb_sec_info_t sector, fdb_blob_t blob, fdb_time_t cur_time)
{
    fdb_err_t result = FDB_NO_ERR;
    uint8_t status[FDB_STORE_STATUS_TABLE_SIZE];

    if (sector->status == FDB_SECTOR_STORE_USING && sector->remain < LOG_IDX_DATA_SIZE + FDB_WG_ALIGN(blob->size)) {
        uint8_t end_status[TSL_STATUS_TABLE_SIZE];
        uint32_t end_index = sector->empty_idx - LOG_IDX_DATA_SIZE, new_sec_addr, cur_sec_addr = sector->addr;
        /* save the end node index and timestamp */
        if (sector->end_info_stat[0] == FDB_TSL_UNUSED) {
            _FDB_WRITE_STATUS(db, cur_sec_addr + SECTOR_END0_STATUS_OFFSET, end_status, FDB_TSL_STATUS_NUM, FDB_TSL_PRE_WRITE, false);
            FLASH_WRITE(db, cur_sec_addr + SECTOR_END0_TIME_OFFSET, (uint32_t * )&db->last_time, sizeof(fdb_time_t), false);
            FLASH_WRITE(db, cur_sec_addr + SECTOR_END0_IDX_OFFSET, &end_index, sizeof(end_index), false);
            _FDB_WRITE_STATUS(db, cur_sec_addr + SECTOR_END0_STATUS_OFFSET, end_status, FDB_TSL_STATUS_NUM, FDB_TSL_WRITE, true);
        } else if (sector->end_info_stat[1] == FDB_TSL_UNUSED) {
            _FDB_WRITE_STATUS(db, cur_sec_addr + SECTOR_END1_STATUS_OFFSET, end_status, FDB_TSL_STATUS_NUM, FDB_TSL_PRE_WRITE, false);
            FLASH_WRITE(db, cur_sec_addr + SECTOR_END1_TIME_OFFSET, (uint32_t * )&db->last_time, sizeof(fdb_time_t), false);
            FLASH_WRITE(db, cur_sec_addr + SECTOR_END1_IDX_OFFSET, &end_index, sizeof(end_index), false);
            _FDB_WRITE_STATUS(db, cur_sec_addr + SECTOR_END1_STATUS_OFFSET, end_status, FDB_TSL_STATUS_NUM, FDB_TSL_WRITE, true);
        }
        /* change current sector to full */
        _FDB_WRITE_STATUS(db, cur_sec_addr, status, FDB_SECTOR_STORE_STATUS_NUM, FDB_SECTOR_STORE_FULL, true);
        sector->status = FDB_SECTOR_STORE_FULL;
        /* calculate next sector address */
        if (sector->addr + db_sec_size(db) < db_max_size(db)) {
            new_sec_addr = sector->addr + db_sec_size(db);
        }
        else if (db->rollover) {
            new_sec_addr = 0;
        } else {
            /* not rollover */
            return FDB_SAVED_FULL;
        }
        read_sector_info(db, new_sec_addr, &db->cur_sec, false);
        if (sector->status != FDB_SECTOR_STORE_EMPTY) {
            /* calculate the oldest sector address */
            if (new_sec_addr + db_sec_size(db) < db_max_size(db)) {
                db->oldest_addr = new_sec_addr + db_sec_size(db);
            } else {
                db->oldest_addr = 0;
            }
            format_sector(db, new_sec_addr);
            read_sector_info(db, new_sec_addr, &db->cur_sec, false);
        }
    } else if (sector->status == FDB_SECTOR_STORE_FULL) {
        /* database full */
        return FDB_SAVED_FULL;
    }

    if (sector->status == FDB_SECTOR_STORE_EMPTY) {
        /* change the sector to using */
        sector->status = FDB_SECTOR_STORE_USING;
        sector->start_time = cur_time;
        _FDB_WRITE_STATUS(db, sector->addr, status, FDB_SECTOR_STORE_STATUS_NUM, FDB_SECTOR_STORE_USING, true);
        /* save the start timestamp */
        FLASH_WRITE(db, sector->addr + SECTOR_START_TIME_OFFSET, (uint32_t *)&cur_time, sizeof(fdb_time_t), true);
    }

    return result;
}

3.总结

以上仅仅是通过数据写入时的状态切换简单的描述FlashDB回滚功能,与此同时还会针对数据、地址、当前时间进行操作,在这里就不去具体分析了。

 类似资料: