嵌入式备份还原与磨损均衡机制

目录

设计思路

单条数据结构

单扇区数据块分类管理

关键技术实现

双区数据读写接口

备份还原核心设计

磨损均衡算法实现


       在嵌入式系统中,flash存储经常用于保存配置数据或关键信息,但flash操作容易出问题,比如掉电或干扰导致数据损坏。flash的每个扇区有擦写次数限制,频繁擦写会缩短寿命,下面介绍嵌入式领域安全存储的一种方案,在实际项目中可以借鉴使用,供大家学习参考。

设计思路

  • 双区备份设计:
    •  主区与备份区:将Flash划分64KB做为主区,文件系统创建一个4KB的文件用来做备份区,数据存储时需同时写入两个区域,并通过CRC32校验保证一致性。
    • 读写流程:
      • 写入:先擦除主区,写入数据并计算CRC,再同步至备份区。
      • 读取:优先读取主区数据并校验CRC,校验失败则数据无效;若主区数据不存在读取备份区并校验CRC,校验失败则数据无效。
  • 磨损均衡设计
    • 扇区间均衡:主区64KB空间共16个扇区,扇区间轮换写入,避免单扇区过载
    • 扇区分块:每个扇区分成16块,每个块可以保存一条用户数据。

单条数据结构

typedef struct
{
    uint8_t index;
    uint8_t version;
    uint16_t data_len;
    uint32_t crc;
    uint8_t data[0];    
}St_SecData;
  • index,标识数据块的逻辑或物理位置,常用于磨损均衡算法中的地址映射
  • version,实现多版本数据管理,支持AB分区备份与回滚机制
  • data_len,动态数据长度标识,避免存储冗余。结合柔性数组data[0]实现变长数据高效存储 
  • crc,数据完整性校验,防止因Flash写入异常或读取干扰导致的数据错误。校验失败时触发备份区恢复流程
  • data[0],柔性数组,动态分配实际数据空间,适用于非固定长度的数据

单扇区数据块分类管理

typedef struct {
    uint8_t index;     // 数据块索引
    uint16_t max_len;  // 最大允许长度
} St_MaxDataLen;

const St_MaxDataLen SecDataInfo[MAX_SECUREDATA_COUNT + 1] = {
    {0, 0},
    {1, 52}, {2, 52}, ..., {8, 52},    // 索引1-8:最大52字节
    {9, 100}, ..., {12, 100},          // 索引9-12:最大100字节
    {13, 500}, {14, 500},              // 索引13-14:最大500字节
    {15, 1000}, {16, 1000}             // 索引15-16:最大1000字节
};

        将不同索引的数据块按最大长度分级管理(52/100/500/1000字节),通过预定义最大长度,确保数据写入时不会溢出。

    //注意4字节对齐
    typedef struct
    {
        St_SecData datainfo1;
        uint8_t data1[52];
        St_SecData datainfo2;
        uint8_t data2[52];
        St_SecData datainfo3;
        uint8_t data3[52];
        St_SecData datainfo4;
        uint8_t data4[52];
        St_SecData datainfo5;
        uint8_t data5[52];
        St_SecData datainfo6;
        uint8_t data6[52];
        St_SecData datainfo7;
        uint8_t data7[52];
        St_SecData datainfo8;
        uint8_t data8[52];
        St_SecData datainfo9;
        uint8_t data9[100];
        St_SecData datainfo10;
        uint8_t data10[100];
        St_SecData datainfo11;
        uint8_t data11[100];
        St_SecData datainfo12;
        uint8_t data12[100];
        St_SecData datainfo13;
        uint8_t data13[500];
        St_SecData datainfo14;
        uint8_t data14[500];
        St_SecData datainfo15;
        uint8_t data15[1000];
        St_SecData datainfo16;
        uint8_t data16[1000];
    	
    }St_4K_MAP;
    • St_SecData,单条数据块的页表管理结构,存储每条数据CRC、有效标志等
    • data,用来存储单条有效数据,地址紧跟在页表管理后面

    关键技术实现

    双区数据读写接口

    写入接口

    int32_t ql_securedata_store(uint8_t index, uint8_t *pdata, uint32_t len)
    {
        if(0 == SECUREDATA_AREA_START_ADDR)
        {
            return SECURE_DATA_RET_ERR_FATAL;
        }
        
        if((index < 1) || (index > MAX_SECUREDATA_COUNT) || (0 == len) || (NULL == pdata) || (false == CheckIFDatalenOK(index, len)))
        {
            return SECURE_DATA_RET_ERR_PARAM;
        }
    
        {
            St_SecData *SecureData = NULL;
            SecureData = malloc(sizeof(St_SecData) + SecDataInfo[index].max_datalen);
            if(NULL == SecureData)
            {
                return SECURE_DATA_RET_ERR_FULL;
            }
    
            //清FF,避免无效数据损伤FLASH
            memset(SecureData, 0xFF, sizeof(St_SecData) + SecDataInfo[index].max_datalen);
            SecureData->index = index;
            SecureData->data_len = len;
            SecureData->version = SECUREDATA_VALID;
            
            memcpy(SecureData->data, pdata, len);
            SecureData->crc = crc32_calc(SecureData, sizeof(St_SecData) - sizeof(SecureData->crc));
            SecureData->crc = crc32_update(SecureData->crc, SecureData->data, len);
            ql_securedata_flashwrite(index, (uint8_t *)SecureData, sizeof(St_SecData) + len);
            free(SecureData);
        }
    
        return SECURE_DATA_RET_OK;
    }
    

    读取接口

    int32_t ql_securedata_read(uint8_t index, uint8_t* pBuffer, uint32_t len)
    {
        if(0 == SECUREDATA_AREA_START_ADDR)
        {
            return SECURE_DATA_RET_ERR_FATAL;
        }
    	uint8_t i = 0;
        int32_t ret = len;
        if((index < 1) || (index > MAX_SECUREDATA_COUNT) || (0 == len) || (NULL == pBuffer))
        {
            SECDAT_TRACE(g_sw_SECDAT, "[SECDAT] para invalid index:%d, pBuffer:%p, len:%d", index, pBuffer, len);
            return SECURE_DATA_RET_ERR_PARAM;
        }
        uint32_t addr = ql_securedata_flashaddr_calc(index, READ);
        SECDAT_TRACE(g_sw_SECDAT, "[SECDAT] %08x", addr);
        if(addr != 0)
        {
        	St_SecData * datainfo = malloc(sizeof(St_SecData) + SecDataInfo[sizeof(SecDataInfo)/sizeof(SecDataInfo[0]) - 1].max_datalen);
        	hal_SpiFlashRead((uint32_t)addr, datainfo, sizeof(St_SecData) + SecDataInfo[index].max_datalen);
            if(ql_securedata_crcCheck(index, (St_SecData *)datainfo))
            {
                if(datainfo->data_len >= len)
                {
                    memcpy(pBuffer, datainfo->data, len);
                }
                else
                {
                    memcpy(pBuffer, datainfo->data, datainfo->data_len);
                    ret = datainfo->data_len;
                }
                free(datainfo);
            }
            else
            {
                SECDAT_TRACE(g_sw_SECDAT, "[SECDAT] crc unmatch");
                free(datainfo);
                return SECURE_DATA_RET_ERR_ERROR;
            }
        }
        //Flash中数据丢失,则看文件系统数据是否存在
        else
        {
    		FILE_ID hdl = FDI_fopen(SECDAT_FILENAME, "rb");
            if(hdl == 0)
            {
                return SECURE_DATA_RET_ERR_ERROR;
            }
            //文件系统文件存在,判断对应index的数据是否完整,crc是否正确
            else
            {
                uint8_t *Max_data_buffer = NULL;
                Max_data_buffer = malloc(sizeof(St_SecData) + SecDataInfo[MAX_SECUREDATA_COUNT].max_datalen);
                if(NULL == Max_data_buffer)
                {
                    return SECURE_DATA_RET_ERR_FULL;
                }
                memset(Max_data_buffer, 0, sizeof(St_SecData) + SecDataInfo[MAX_SECUREDATA_COUNT].max_datalen);
                St_SecData *secdat_tmp = (St_SecData *)Max_data_buffer;
                for(i = 1; i <= MAX_SECUREDATA_COUNT; ++i)
                {
                    //ssize_t readlen = vfs_read(hdl, secdat_tmp, sizeof(St_SecData) + SecDataInfo[i].max_datalen);
    				size_t readlen = FDI_fread((void *)secdat_tmp, 1, sizeof(St_SecData) + SecDataInfo[i].max_datalen, hdl);
    				//找到对应index的数据
                    if(i == index)
                    {
                        //若文件系统的数据正常则恢复,否则不做操作
                        if(readlen == (sizeof(St_SecData) + SecDataInfo[i].max_datalen) && ql_securedata_crcCheck(i, secdat_tmp))
                        {
                            if(secdat_tmp->data_len >= len)
                            {
                                memcpy(pBuffer, secdat_tmp->data, len);
                            }
                            else
                            {
                                memcpy(pBuffer, secdat_tmp->data, secdat_tmp->data_len);
                                ret = secdat_tmp->data_len;
                            }
                            ql_securedata_flashwrite(i, (uint8_t *)secdat_tmp, sizeof(St_SecData) + secdat_tmp->data_len);
                        }
                        else
                        {
                            ret = SECURE_DATA_RET_ERR_ERROR;
                        }
                        break;
                    }
                    memset(secdat_tmp, 0, sizeof(St_SecData) + SecDataInfo[i].max_datalen);
                }
                free(Max_data_buffer);
                FDI_fclose(hdl);
            }
        }
        return ret;
    }

    备份还原核心设计

    • 检查备份文件是否存在
    • 存在
      • 检查主区有效数据CRC校验,不正确则检查备份区CRC校验,如果正确将备份文件对应数据还原到主区,若备份区数据CRC校验失败,则数据丢失。
    •  不存在   
      • 创建新的备份文件
      • 检查主区有效数据CRC校验,CRC校验通过,将对应有效数据拷贝到备份文件,否则不操作
    void ql_securedata_init(void)
    {
        if(0 == SECUREDATA_AREA_START_ADDR){
            return;
        }   
        uint8_t i = 0;
        uint32_t flashaddr = 0;
        St_4K_MAP *Flash_4K = NULL;
        
        Flash_4K = malloc(sizeof(St_4K_MAP));
        if(NULL == Flash_4K)
        {
            return;
        }
        memset(Flash_4K, 0, sizeof(St_4K_MAP));
        //打开文件系统中备份的文件
    	FILE_ID hdl = FDI_fopen(SECDAT_FILENAME, "rb");
        //不存在则创建一个
        if(hdl == 0)
        {
            uint32_t offset = 0;
    
    		hdl = FDI_fopen(SECDAT_FILENAME, (char *)"wb");
    
            memset(Flash_4K, 0xFF, sizeof(St_4K_MAP));
            
            for(i = 1; i <= MAX_SECUREDATA_COUNT; ++i)
            {
                flashaddr = ql_securedata_flashaddr_calc(i, READ);
                if(flashaddr != 0)
                {
                	St_SecData datainfo = {0};
        			hal_SpiFlashRead((uint32_t)flashaddr, &datainfo, sizeof(datainfo));
                	hal_SpiFlashRead(flashaddr, ((uint8_t *)Flash_4K) + offset, sizeof(St_SecData) + datainfo.data_len);
                }
                offset = offset + sizeof(St_SecData) + SecDataInfo[i].max_datalen;
            }
    		FDI_fwrite((const void *)Flash_4K, sizeof(St_4K_MAP), 1, hdl);
        }
        //若已存在该文件,判断FLASH中的数据是否有损坏
        else
        {
            St_SecData *secdat_tmp = (St_SecData *)Flash_4K;
            for(i = 1; i <= MAX_SECUREDATA_COUNT; ++i)
            {
    			size_t readlen = FDI_fread((void *)secdat_tmp, 1, sizeof(St_SecData) + SecDataInfo[i].max_datalen, hdl);
                flashaddr = ql_securedata_flashaddr_calc(i, READ);
                //FLASH中数据损坏则使用文件系统中的数据重新填写
                if(0 == flashaddr)
                {
                    //若文件系统的数据正常则恢复,否则不做操作
                    if(readlen == (sizeof(St_SecData) + SecDataInfo[i].max_datalen) && ql_securedata_crcCheck(i, secdat_tmp))
                    {
                        ql_securedata_flashwrite(i, (uint8_t *)secdat_tmp, sizeof(St_SecData) + secdat_tmp->data_len);
                    }
                    else
                    {
                    	//do nothing
                    }
                }
                memset(secdat_tmp, 0, sizeof(St_4K_MAP));
            }
        }
        FDI_fclose(hdl);
        if(Flash_4K)
        {
            free(Flash_4K);
        }
        return;
    }
    

      磨损均衡算法实现

              主区大小划分了64KB,有效数据只有4KB,这样设计主要是为了实现flash磨损均衡。

              当向主区写数据时,例如写index为1的数据,主区16个扇区中的datainfo1初始化为一个环形链表,首先会轮训链表中的datainfo1有效标志是否置位,datainfo1有效标志均未被置位,那就向环形链表的头结点也就是扇区1的datainfo1中记录数据。若下次想更改datainfo1里面的内容时,就向这个扇区的下一个扇区的datainfo1中写入当前数据,写完后把上一个扇区中的有效标志清零。

              当在写完datainfo1后清除前一个扇区的有效标志位时模块被意外掉电,有效标志清除失败,下次上电时主区datainfo1中就存在两个有效标志被置位。这里需要判断这两个扇区是否是连续的,如果是连续的两个扇区,则认为后一个扇区中的数据是有效的数据,则向这两个扇区中的后一个的下一个扇区写数据,然后把这两个有效标志清零。

              如果这两个扇区不连续,则向第一个查到的扇区的下一个扇区写数据,写完把这两个不连续的扇区有效标志清零。

                      

              当向主区读数据时,正常情况是只有一个扇区中datainfo的数据有效标志被置位,CRC校验通过,把数据返回。若主区中存在两个datainfo有效标志被置位,若两个扇区是连续的,则认为后一个为最新的数据,检查数据CRC校验值,正确则把数据返回。

      如果扇区不连续,则认为前一个扇区中的数据为有效数据。

              这种设计每次向环形链表的下一个结点记录数据,实现了flash磨损均衡防止一个结点被反复擦写,延长flash寿命。

              文章中只是部分源码,希望对你有帮助。想获取完整源码,请到个人主页资源中付费下载,博主码字不易,感谢!

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值