当前位置: 首页 > 科技观察

嵌入式软件错误的前五个原因

时间:2023-03-14 22:34:37 科技观察

查找和消除嵌入式软件中的潜在错误是困难的。追踪观察到的崩溃、挂起或其他计划外的运行时行为的根本原因通常需要大量的工作和昂贵的工具。嵌入式开发工程师经常放弃寻找罕见异常的原因——这些异常在实验室中不容易重现——并将其视为“用户错误”或“故障”,然而,机器中的这些潜在危害仍然存在时间。因此,这里有一份关于难以重现固件错误的最常见根本原因的指南。1.堆碎片动态内存分配并没有被嵌入式软件开发人员广泛使用——有充分的理由,其中之一就是堆碎片问题。C的malloc()标准库例程或C++的new关键字创建的所有数据结构都在堆上。堆是RAM中具有预定最大大小的特定区域。最初,堆中的每个分配都会将剩余的“空闲”空间减少相同的字节数。不再需要的数据结构的存储可以通过调用free()或使用delete关键字返回到堆中。理论上,这使得该存储空间在后续分配期间可重复使用。但是分配和删除的顺序通常至少是伪随机的——导致堆变成一堆更小的碎片。2.堆栈溢出每个程序员都知道堆栈溢出是一件非常糟糕的事情?。但是,每次堆栈溢出的影响各不相同。损害的性质和不当行为发生的时间完全取决于哪些数据或指令被泄露以及它们的使用方式。重要的是,堆栈溢出与其对系统的负面影响之间的时间长度取决于使用损坏位之前的时间长度。不幸的是,在嵌入式开发中,堆栈溢出对嵌入式系统的影响远远超过台式机。这有几个原因,包括:嵌入式系统通常只能依赖少量的RAM;通常没有虚拟内存可以依赖(因为没有磁盘);基于RTOS任务的固件设计利用多个堆栈(每个任务一个),每个堆栈大小都必须足够大,以确保不存在唯一的最坏情况堆栈深度;中断处理程序可能会尝试使用这些相同的堆栈。3.缺少“volatile”关键字未能使用C“volatile”关键字标记某些类型的变量会导致系统出现许多症状,这些症状仅在编译器的优化器设置为低级别或禁用正常工作时才会出现。volatile限定符在变量声明期间使用,以防止优化该变量的读写。请注意,除了确保对给定变量的所有读取和写入都发生之外,使用volatile还通过添加额外的“序列点”来限制编译器。对多个volatiles的访问必须按照它们在代码中写入的顺序执行。4.竞争条件竞争条件是指两个或多个执行线程(可能是RTOS任务或main()加上ISR)的组合结果根据每条指令交错的精确顺序而变化的任何情况。例如,假设嵌入式开发人员有两个执行线程,其中一个定期递增全局变量(g_counter+=1;),另一个偶尔重置它(g_counter=0;)。如果递增不能总是原子地执行(即在单个指令周期内),那么这里就存在竞争条件。计数器变量的两次更新之间的冲突可能永远不会或很少发生。但是当它这样做时,计数器实际上并没有在内存中重置。这种影响可能会对系统造成严重后果,尽管它可能要到实际碰撞很久之后才会发生。最佳实践:可以通过包围代码的“关键部分”来防止竞争条件,这些代码必须以适当的抢占绑定行为原子地执行。为防止涉及ISR的竞争条件,在其他代码的关键部分必须至少禁用一个中断信号。在RTOS任务之间存在竞争的情况下,最佳做法是创建一个特定于该共享对象的互斥量,每个任务在进入临界区之前都必须获取该互斥量。请注意,依靠特定CPU的功能来确保原子性并不是一个好主意,因为这只会在编译器或CPU更改之前防止竞争条件。5.不可重入函数从技术上讲,不可重入函数问题是竞争条件问题的一个特例。出于这个原因,由不可重入函数引起的运行时错误是相似的,并且不会以可重现的方式发生——这使得它们同样难以调试。不幸的是,与其他类型的竞争条件相比,不可重入函数在代码审查中也更难发现。使函数可重入的关键是暂停对外围寄存器、全局变量(包括静态局部变量)、持久堆对象和共享内存区域的所有访问的抢占。嵌入式开发人员可以通过禁用一个或多个中断或通过获取和释放互斥锁来实现这一点,共享数据类型的细节通常决定最佳解决方案。