9

让我解释一下我所说的数据一致性问题。以以下场景为例

uint16 x,y;
x=0x01FF;
y=x;

显然,这些变量是 16 位的,但如果使用 8 位 CPU 和这段代码,读或写操作将不是原子的。从而可能在两者之间发生中断并更改值。这是可能导致数据不一致的一种情况。

这是另一个例子,

if(x>7) //x is global variable
{
 switch(x)
        {
           case 8://do something
                   break;
           case 10://do something
                   break;
           default: //do default
        }
}

在上面的摘录代码中,如果一个中断在 if 语句之后但在 switch 语句之前将 x 的值从 8 更改为 5,我们会以默认情况结束,而不是情况 8。

请注意,我正在寻找检测此类情况的方法(但不是解决方案)

是否有任何工具可以检测嵌入式 C 中的此类问题?

4

2 回答 2

4

具有上下文(线程/中断)意识的静态分析工具可以确定共享数据的使用,并且此类工具可以识别保护此类数据(或缺少此类数据)的特定机制。

Polyspace Code Prover就是这样一种工具;它非常昂贵且非常复杂,并且除了上述之外还有更多功能。特别是从这里的白皮书中引用(省略):

通过抽象解释,以下程序元素以新的方式解释:

[...]

  • 在多任务程序中,任何全局共享数据都可能随时更改,除非已应用内存锁或临界区等保护机制

[...]

自从我使用它以来,它可能在很长一段时间内都得到了改进,但我遇到的一个问题是它适用于 lock-access-unlock 习惯用法,您可以在其中向工具指定 lock/unlock 调用或宏是什么。问题在于我从事的 C++ 项目使用了一种更智能的方法,其中锁定对象(例如互斥锁、调度程序锁定或中断禁用)锁定在实例化(在构造函数中)并在析构函数中解锁,以便它自动解锁当对象超出范围时(范围成语锁定)。这意味着解锁对 Polyspace 来说是隐式且不可见的。然而,它至少可以识别所有共享数据。

该工具的另一个问题是您必须为并发分析指定所有线程和中断入口点,在我的例子中,这些是任务和中断类中的私有虚拟函数,再次使它们对 Polyspace 不可见。这是通过有条件地公开入口点仅用于抽象分析来解决的,但这意味着正在测试的代码不具有要运行的代码的确切语义。

当然,这些对于 C 代码来说都不是问题,根据我的经验,Polyspace 在任何情况下都更成功地应用于 C;您不太可能以适合该工具的风格编写代码,而不是该工具与您现有的代码库一起工作。

于 2017-08-21T16:24:01.470 回答
3

据我所知,没有这样的工具。那可能是因为您无法检测到它们。

C 代码中的几乎每个操作都有可能在完成之前被中断。不如 16 位场景明显,还有这个:

uint8_t a, b;
...
a = b;

不能保证这是原子的!上述赋值也可以转换为多个汇编指令,例如 1) 加载a到寄存器中,2) 将寄存器存储在内存地址。除非您反汇编 C 代码并检查,否则您无法知道这一点。

这会产生非常微妙的错误。任何“只要我在我的 8 位 CPU 上使用 8 位变量,我就不会被中断”的假设都是幼稚的。即使这样的代码会导致给定 CPU 上的原子操作,该代码也是不可移植的。

唯一可靠、完全可移植的解决方案是使用某种信号量。在嵌入式系统上,这可以像bool 变量一样简单。另一种解决方案是使用内联汇编器,但不能跨平台移植。

为了解决这个问题,C11 为语言引入了限定符_Atomic。然而,嵌入式系统编译器对 C11 的支持仍然平平。

于 2017-08-21T10:01:43.273 回答