开发可靠的嵌入式软件归结为计划最坏的情况,并确保嵌入式开发人员有适当的防护措施和陷阱来处理这些情况。嵌入式软件中一个被忽视的领域通常是堆栈。堆栈是微控制器用来存储局部变量、函数调用返回地址、中断上下文和函数参数等信息的临时存储器。
为给定的应用程序设置堆栈大小完全取决于开发人员。许多编译器会提供默认值0x400字节,但这对所有应用程序来说真的足够了吗?对于任何给定的应用程序,确定堆栈的大小都很困难。开发人员需要确定最坏情况的使用,这包括了解最大函数调用深度、将在堆栈上定义多少局部变量,甚至可能需要存储多少并发中断上下文。即使有今天的技术和工具,这也不是一件容易的事。
那么开发者做什么呢?他只需确定当天的风向,从稀薄的空气中抽出一个数字,并把这个数字作为堆栈的大小。底线是,如果堆栈溢出了分配给它的内存区域,最坏的情况会是什么?图1显示了典型的内存映射以及堆、堆栈和全局/静态区域的位置。嵌入式开发随着堆栈的增长,它将朝着内存映射的全局/静态变量区域增长。如果堆栈溢出,它将开始覆盖全局和静态变量定义!导致存储在内存中的值被破坏,并有机会对系统的行为造成严重破坏。
图1–内存映射
一个可靠的系统不能允许堆栈溢出。那么,当这种情况出现时,有什么技术可以检测到呢?最常见的技术之一是在堆栈和全局/静态变量区域之间创建一个保护区域。应该选择足够大的保护区域,这样如果堆栈溢出,它就不能穿透下面的内存区域。一些常见的值是16和32字节,但这些需要基于一次可以放入堆栈的最大对象。存储器映射设置示例如图2所示。
图2–堆栈保护存储器映射
微控制器可以通过两种方式监控保护区。第一种是使用板载存储器保护单元(MPU )(如果微控制器上有)。在这种情况下,如果对存储器的堆栈保护区有任何写访问,MPU将被设置为触发中断。中断触发表明堆栈溢出,可以采取保护措施并记录错误。
如果MPU不可用,嵌入式开发人员可以在系统初始化期间用已知的位模式填充存储器的保护区。然后可以创建一个任务或函数,定期检查保护区,并将其与已知的位模式进行比较。如果模式已经改变,那么应用程序可以强制中断,然后记录错误并开始纠正措施。
有两种不同的方法可以用来创建大模式。第一种是使用链接器文件在堆栈保护中创建一个填充区域。该区域将在启动时的C复制期间自动初始化。第二种方法是手动写入位模式,作为系统初始化的一部分,方法是创建一个指向堆栈保护开始的指针,然后将位模式放入内存。
可以说,在适当的位置设置堆栈保护会降低嵌入式软件的效率。让应用程序定期检查防护是否仍然完好,可能需要一个函数调用、一个循环和一个取消引用的指针。这种性能影响在现代微控制器上几乎不明显,应该是一个静音点。在创建一个可靠的系统时,嵌入式开发人员需要进行这些类型的检查,以确保系统按预期运行。如果发生灾难性故障,如堆栈溢出,系统将能够检测到溢出,并在系统发生可怕的事情或甚至更糟的系统用户发生之前采取纠正措施。