3

我有一个具有以下实现的功能:

void func (uint8 index, uint8 status)
{
  if (status == 1)
  {
    myArrayOfStructures[index].status = 1;
  }

  else if (status == 0)
  {
    myArrayOfStructures[index].status = 0;
  }

  else
  {
    /* Nothing */
  }
}

注意:myArrayOfStructures 是文件上的全局变量。

我认为这个函数是可重入的,只要它的参数的传递是通过堆栈完成的,因为以下分析:

在函数调用时,函数的 2 个参数被压入堆栈。如果函数本身被另一个 OS 任务中断,则第二次将参数压入堆栈。因此,函数的 2 个实例是“独立的”,因为每个实例在堆栈中都有自己的一组参数。

直到,我使用某个编译器选项优化了这个文件的速度。

经过优化,我发现那些参数的传递是通过寄存器来完成的(我知道编译器有权做这样的事情(寄存器分配))。但是这种优化使这个函数不可重入。

所以,我的问题是

  • 上述功能真的是可重入的吗?
  • 如果是(重入),编译器如何进行这样的优化以恢复其重入状态?

请参考您的答案。

非常感谢。

4

2 回答 2

2

无论编译器选择何种参数传递模式,您的函数都是可重入的。

尽管依赖全局状态来正确执行会破坏重入,但简单地访问全局状态并不一定会使您的函数不可重入。特别是,如果您从不读取全局变量,就像函数中的情况一样,您的代码是可重入的,因为它的执行不依赖于全局状态。

就通过寄存器传递参数而言,上下文切换也会保存所有寄存器的内容,因此无论参数传递模式如何,这些值都是中断安全的。

于 2017-01-03T10:03:31.477 回答
0

可能不是。我会指定它什么时候是可重入的,什么时候不是。但在此之前,您必须知道虽然每个处理器只有一组物理寄存器,但这些寄存器的状态是每个线程的。每个线程都保持自己的状态,不能摆弄其他线程的寄存器状态。操作系统确保了这一点。

一般来说,根据定义,编译器优化永远不会在正确的代码中引入错误。但是,优化可能会让现有的错误浮出水面,但无论是否使用优化,错误都存在于代码中。因此,无论您编写什么代码,无论优化如何,它都应该可以正常工作。这有例外,但它们与问题无关。

现在我来回答你的问题。假设该函数被调用了一些indexstatus1。考虑以下情况:

void func (uint8 index, uint8 status)
{
  if (status == 1)
  {
    // Interrupt occurs here.
    myArrayOfStructures[index].status = 1;
  }

  else if (status == 0)
  {
    myArrayOfStructures[index].status = 0;
  }

  else
  {
    /* Nothing */
  }
}

index当中断发生并且同一个线程以相同的and为0调用同一个函数时status,它将将该索引处的数组元素的值设置为0。当第一次调用恢复时,它将1 写入同一个数组元素。我假设您认为这是不正确的行为,因为新状态丢失了。这表明该函数是不可重入的。

如果对数组元素的访问不是原子的,那么即使在单词的单线程含义中,该函数也不是可重入的,因为如果更新或读取数组元素,该线程可能会在中间中断。

现在让我们考虑两个线程同时执行具有相同索引但具有不同状态的函数。在这种情况下,会发生数据竞争。这意味着两件事。首先,结果是不确定的。您不知道将存储哪种状态。如果这与您的正确性要求一致,那很好。但很可能,您想存储最新状态,因此这种不确定性使其不可重入。其次,更大的问题是,单个数据竞争会使您的整个程序根据 C 标准具有未定义的行为。这当然意味着该功能不可重入。

在没有缓存一致性的对称多处理器系统中或在分布式内存计算机系统中,相同的数组元素可以有多个值,因此您将处于相同的情况,其中最近的状态是未知的。

编译器优化可以通过降低检测到错误的概率使函数看起来是可重入的。例如,如果编译器可以确定仅status使用 0 或 1 调用该函数,则它可以将生成的汇编代码优化为以下内容:

  1. 比较 status 和 myArrayOfStructures[index].status 是否相等。

  2. 如果不相等,则有条件地将状态写入 myArrayOfStructures[index].status。

这是有效的,因为全局变量被初始化为零,因此数组中的每个元素都将为 0 或 1。此代码对应于 x86 上仅有的两条指令,其中使函数不可重入的情况较少。

事实上,编译器优化甚至可以使函数可重入。例如,如果编译器可以确定该函数仅使用status0 调用,则该函数中的所有代码都将变为死代码,使其有效地可重入。这就是为什么我一开始就说“可能不会”。

于 2017-01-03T19:30:53.693 回答