在编写代码时,我经常检查是否发生错误。例如:
char *x = malloc( some_bytes );
if( x == NULL ){
fprintf( stderr, "Malloc failed.n" );
exit(EXIT_FAILURE);
}
我过去也用过strerror( errno )
。
我只写过小的桌面应用程序,在这种情况下,如果程序在错误的情况下出现exit()
并不重要。
现在,然而,我正在为嵌入式系统(Arduino)编写C代码,我不希望系统在出现错误时退出。我想让它进入一个特定的状态/功能,在那里它可以关闭系统,发送错误报告和安全闲置。
我可以简单地调用error_handler()
函数,但是我可能在堆栈深处并且内存非常低,使error_handler()
不可操作。
相反,我希望执行能够有效地折叠堆栈,释放一堆内存,并开始整理断电和错误报告。如果系统不能安全断电,就会有严重的火灾危险。
在低内存嵌入式系统中是否有安全错误处理的标准方法?
编辑1:我将限制在嵌入式系统中使用malloc()
。在这种特殊情况下,如果文件的格式不正确,则在读取文件时将发生错误。
也许你在等待神圣和神圣的setjmp
/longjmp
,那个来保存他们所有的内存饥渴的罪恶堆栈的人?
#include <setjmp.h>
jmp_buf jumpToMeOnAnError;
void someUpperFunctionOnTheStack() {
if(setjmp(jumpToMeOnAnError) != 0) {
// Error handling code goes here
// Return, abort(), while(1) {}, or whatever here...
}
// Do routinary stuff
}
void someLowerFunctionOnTheStack() {
if(theWorldIsOver)
longjmp(jumpToMeOnAnError, -1);
}
Edit:出于与您所说的相同的原因,不希望在嵌入式系统上执行malloc()
/free()
。简直无法处理。除非你使用很多返回代码/setjmp()
s来释放内存,直到堆栈…
如果您的系统有看门狗,您可以使用:
char *x = malloc( some_bytes );
assert(x != NULL);
assert()
的实现可以像这样:
#define assert (condition)
if (!(condition)) while(true)
如果看门狗发生故障,系统将复位。重启时系统检查复位原因,若复位原因为看门狗复位,则系统进入安全状态
在进入while
循环之前,assert
也会输出错误信息,打印堆栈跟踪或将部分数据保存在非易失性存储器中。
在低内存嵌入式系统中是否有安全错误处理的标准方法?
是的,有一个行业实际的处理方法。这很简单:
- 对于程序中的每个模块,您都需要有一个结果类型,例如自定义enum,它描述了该模块内的函数可能出错的所有可能的事情。
- 正确地记录每个函数,说明错误时返回的代码和成功时返回的代码。
- 您将所有错误处理留给调用者。
- 如果调用者是另一个模块,它也将错误传递给自己的调用者。可能会将错误重命名为更合适的名称,如果适用的话。
- 错误处理机制位于main()中,位于调用堆栈的底部。
这与经典状态机一起工作得很好。典型的main是:
void main (void)
{
for(;;)
{
serve_watchdog();
result = state_machine();
if(result != good)
{
error_handler(result);
}
}
}
你不应该在裸机或RTOS微控制器应用程序中使用malloc,不是因为安全原因,而是因为使用它没有任何意义。编程时运用常识
使用setjmp(3)
设置恢复点,并使用longjmp(3)
跳转到该恢复点,将堆栈恢复到setjmp点时的状态。它不会释放错置内存。
alloca()
也会稍微好一些。
最小化堆栈使用:
编写程序,使调用并行,而不是函数调用子函数,子函数调用子函数,子函数调用子函数....即顶级函数调用子函数,子函数立即返回状态信息。顶层函数然后调用下一个子函数…等
程序架构的嵌套方法:
top level function
second level function
third level function
forth level function
在嵌入式系统中应避免使用嵌入式系统程序架构的首选方法是:
top level function (the reset event handler)
(variations in the following depending on if 'warm' or 'cold' start)
initialize hardware
initialize peripherals
initialize communication I/O
initialize interrupts
initialize status info
enable interrupts
enter background processing
interrupt handler
re-enable the interrupt
using 'scheduler'
select a foreground function
trigger dispatch for selected foreground function
return from interrupt
background processing
(this can be, and often is implemented as a 'state' machine rather than a loop)
loop:
if status info indicates need to call second level function 1
second level function 1, which updates status info
if status info indicates need to call second level function 2
second level function 2, which updates status info
etc
end loop:
注意,尽可能不存在"第三层函数x"
请注意,前台功能必须在再次调度之前完成。
注意:上面我省略了很多其他细节,比如
kicking the watchdog,
the other interrupt events,
'critical' code sections and use of mutex(),
considerations between 'soft real-time' and 'hard real-time',
context switching
continuous BIT, commanded BIT, and error handling
etc