保护您的嵌入式软件免受内存损坏
本文的目的是提供一种软件方法,解释如何处理存储在非易失性设备(如小型 EEPROM 或闪存)中的内存数据集损坏。在微型嵌入式系统中看到这些数据集是很常见的,这些系统存储了配置参数、关键系统日志等持久数据。这些数据集可能会在系统崩溃、电源故障或 ESD 后损坏。
本文提出了一种简单但有效的机制,可以以较低的损坏可能性保存此类数据。此外,该方法包括一个众所周知的机制来检测变量损坏,因为它们可能被各种原因损坏,例如环境因素(例如,EMI、热量、辐射)、硬件故障(例如,电源波动、电源故障、存储器单元故障、地址线短路)或软件故障(其他软件错误地修改存储器)。尽管本文使用 C 语言来实现所提出的方法,但它也可以很容易地用其他编程语言(如 C++)来实现。
假设一个嵌入式系统可以在运行时通过一组存储在非易失性存储器中的参数进行配置。这些参数排列在一个 C 结构中:
typedef struct ConfigData ConfigData ;
结构体配置数据
{
int optionA;
多头选项B;
};
如果在更新这些数据时发生电源故障或系统重置会发生什么?它们可能已损坏。为了解决这个问题,在配置数据上计算了一个称为CRC值的固定长度的二进制代码,以检测它们是否已被损坏。除了数据值外,此代码还存储在非易失性存储器中:
typedef struct Config配置;
结构体配置
{
配置数据数据;
crc32 crc;
};
假设实现此方法的软件模块称为Config,它提供初始化、设置和获取配置数据的功能,同时通过调用 CRC 计算器来保护它们。这些函数在名为Config.h的头文件中定义,并在名为Config.c的源文件中实现。以下代码片段显示了文件Config.h 的一个片段。
typedef enum ConfigErrorCode ConfigErrorCode;
枚举配置错误代码
{
NO_ERRORS,
INIT_DATA,
CORRUPT_DATA
};
…
typedef void (*ConfigErrorHandler)(ConfigErrorCode errorCode);
…
ConfigErrorCode Config_init ( void );
void Config_setErrorHandler (ConfigErrorHandler errorHandler);
bool Config_getOptionA ( int *value);
bool Config_getOptionB ( long *value);
bool Config_setOptionA(整数值);
bool Config_setOptionB(长值);
系统启动时,首先检查存储在非易失性存储器中的数据,如果它们没有损坏,则将其复制到变量中。否则,它们将恢复为在文件ConfigDft.h中定义的默认值。尽管此过程在系统首次启动时很有用,但稍后将探索更复杂的替代方法。函数Config_init()涵盖了此功能。
静态常量配置 configDefault =
{
{
CONFIG_OPTA_DFT,
CONFIG_OPTB_DFT
}, 0
};
…
配置错误代码
配置初始化(无效)
{
ConfigErrorCode res = NO_ERRORS;
crc32_init();
if (checkDataFromNVMem(&config) == false )
{
res = INIT_DATA;
if (errorHandler != (ConfigErrorHandler) 0 )
{
错误处理程序(res);
}
配置 = configDefault;
NVMem_storeData(CONFIG_ADDR_BEGIN, sizeof (Config),
( const uint8_t *)&config);
}
返回资源;
}
CRC 值在配置更新时设置,读取配置时检查。更新意味着将新配置与其 CRC 一起存储在私有变量和非易失性存储器中。
例如,函数Config_setOptionA()显示了如何设置配置选项,在本例中为optionA。
bool Config_setOptionA(整数值)
{
bool res = false ;
if (checkData(( const Config *)&config) == false )
{
if (errorHandler != (ConfigErrorHandler) 0 )
{
错误处理程序(CORRUPT_DATA);
}
}
别的
{
config.data.optionA = 值;
config.crc = Crc32_calc(( const uint8_t *)&config, sizeof (Config),
0xffffffff );
NVMem_storeData(CONFIG_ADDR_BEGIN, sizeof (Config),
( const uint8_t *)&config);
资源=真;
}
返回资源;
}
更新非易失性存储器后,函数Config_setOptionA()可以添加另一个验证,以确保最近存储的数据已正确写入。
本文中提出的方法建议从变量而不是直接从非易失性存储器读取配置,因为该变量是存储在非易失性存储器中的配置的更新副本。读取配置时,将重新计算 CRC 并与存储的 CRC 进行比较。如果它们不同,则调用errorHandler()。否则,检索到的数据将返回给客户端。下面显示的函数Config_getOptionA()演示了如何从系统配置中检索配置选项。
bool Config_getOptionA ( int *value)
{
bool res = false ;
if (checkData(( const Config )&config) == false )
{
if (errorHandler != (ConfigErrorHandler) 0 )
{
错误处理程序(CORRUPT_DATA);
}
}
别的
{
如果(值!=(整数)0 )
{
*值= config.data.optionA;
资源=真;
}
}
返回资源;
}
该Config_init()函数表明如何处理数据损坏在启动时,它建议还原整个配置为默认值。但是,可以使用更高级的替代方案来代替使用非易失性存储器的两个块来存储配置为Config建议的结构。一个块称为主块,另一个块称为备份块,稍后将证明其合理性。当系统启动时,检查存储在两个非易失性存储块中的配置,重新计算 CRC 并将其与存储的 CRC 进行比较;如果只有一个块损坏,则整个健康块将被复制到另一个块。如果两者都已损坏,则它们将恢复为默认值。当两个块都健康时,就会出现最后一个条件。在这种情况下,每个块存储的 CRC 会相互比较。如果它们不同,主块将被复制到备份块。
使用闪存等非易失性设备时,应为每个块分配该内存的一个专有物理扇区。下图表示此机制的行为:
所述Config_init()函数被修改为执行机构上面所解释的:
…
static const RecProc recovery[] =
{
proc_in_error、proc_recovery、proc_backup、proc_cmp
};
…
静态ConfigErrorCode
proc_in_error ( void )
{
块 = configDefault;
block.crc = crc32_calc(( const uint8_t *)&block.data,
sizeof (ConfigData), 0xffffffff );
NVMem_storeData(CONFIG_MAIN_ADDR, sizeof (Config),
( const uint8_t *)&block);
NVMem_storeData(CONFIG_BACKUP_ADDR, sizeof (Config),
( const uint8_t *)&block);
返回CORRUPT_DATA;
}
静态ConfigErrorCode
proc_recovery(空隙)
{
块 = 备份块;
NVMem_storeData(CONFIG_MAIN_ADDR, sizeof (Config),
( const uint8_t *)&block);
返回RECOVER_DATA;
}
静态ConfigErrorCode
proc_backup ( void )
{
NVMem_storeData(CONFIG_BACKUP_ADDR, sizeof (Config),
( const uint8_t *)&block);
返回BACKUP_DATA;
}
静态ConfigErrorCode
proc_cmp(空隙)
{
ConfigErrorCode res = NO_ERRORS;
if (main.readCRC != backup.readCRC)
{
res = proc_backup();
}
返回资源;
}
…
配置错误代码
配置初始化(无效)
{
内部状态;
crc32_init();
NVMem_readData(CONFIG_MAIN_ADDR, sizeof (Config),
( uint8_t *)&block);
main.readCRC = Crc32_calc(( const uint8_t *)&block.data,
sizeof (ConfigData), 0xffffffff );
main.result = (main.readCRC == block.crc) ?1 :0 ;
NVMem_readData(CONFIG_BACKUP_ADDR, sizeof (Config),
( uint8_t *)&backupBlock);
backup.readCRC = Crc32_calc(( const uint8_t *)&backupBlock.data,
sizeof (ConfigData), 0xffffffff );
backup.result = (backup.readCRC == backupBlock.crc) ?1 :0 ;
状态 = 0 ;
状态 = (main.result << 1 ) | 备份结果;
返回(*恢复[状态])();
}
如果系统不需要在每次通过 set 和 get 函数访问配置选项时检查存储在 RAM 中的数据集,那么替代版本将如下所示:
bool Config_getOptionA ( int *value)
{
bool res = false ;
如果(值!=(整数*) 0 )
{
*值 = block.data.optionA;
资源=真;
}
返回资源;
}
bool
Config_setOptionA ( int值)
{
block.data.optionA = 值;
block.crc = crc32_calc(( const uint8_t *)&block.data,
sizeof (ConfigData), 0xffffffff );
NVMem_storeData(CONFIG_MAIN_ADDR, sizeof (Config),
( const uint8_t *)&block.data);
NVMem_storeData(CONFIG_BACKUP_ADDR, sizeof (Config),
( const uint8_t *)&block.data);
返回 真;
}
显示的用 C 语言编写的源代码及其单元测试用例可在安全内存模式存储库中找到。它包含三个目录Config.alt1/、 Config.alt2/和Config.recovery/,它们对应于根据建议的方法实现模块 Config 的不同替代方案。Config.alt1和Config.alt2类似,但最后一个不会在每次通过 set 和 get 函数访问配置选项时检查存储在 RAM 中的数据集。而替代的Config.recovery派生自Config.alt2 但包括恢复机制。
尽管引入的方法是保护嵌入式软件免受非易失性存储器损坏的有效方法,但强烈建议添加额外的电子电路,例如早期电源故障检测和替代电源(例如电池或超级电容器)来处理以更可靠的方式断电。在发生电源故障时,该电路不仅允许软件系统成功完成对非易失性存储器中的数据集的更新,而且还避免开始新的更新。反过来,电源故障的内存损坏产品将不太可能发生。
相关实战:https://www.99qibang.cn/information/06c829b2d59445b5944cecb94c02e263.html
https://www.99qibang.cn/information/e9357fa31de6437eb058cf9cba2b1a2a.html
https://www.99qibang.cn/information/fa17a9e0cd764adfaf2dc2d7b53095db.html
https://www.99qibang.cn/information/1cc14f8520fb446d910b259075f5f242.html
https://www.99qibang.cn/information/ba23a48112be4081bfc709df119b7889.html