我有一个具有以下实现的功能:
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个实例是“独立的”,因为每个实例在堆栈中都有自己的参数集。
直到我使用某个编译器选项优化了此文件的速度。
优化后,我发现这些参数的传递是通过寄存器完成的(我知道编译器有权做这样的事情(Register Allocation))。但是这种优化使得这个功能不可重入。
所以,我的问题是
请参考我的回答参考。
非常感谢。
答案 0 :(得分:2)
无论编译器选择何种参数传递模式,您的函数都是可重入的。
尽管依赖全局状态进行正确执行可以打破重新入侵,但只需访问全局状态并不一定会使您的函数不可重入。特别是,如果您从未读过全局变量(如函数中的情况),则代码是可重入的,因为它的执行不依赖于全局状态。
就通过寄存器传递参数而言,上下文切换也会保存所有寄存器的内容,因此无论参数传递模式如何,这些值都是中断安全的。
答案 1 :(得分:0)
可能不是。我指定它何时可以重入以及什么时候不可重入。但在此之前,您必须知道尽管每个处理器只有一组物理寄存器,但这些寄存器的状态是每个线程。每个线程都保持其状态,并且不能摆弄其他线程的寄存器状态。操作系统确保了这一点。
通常,根据定义,编译器优化永远不会在正确的代码中引入错误。但是,优化可能允许现有错误浮出水面,但无论是否使用优化,代码中都存在错误。因此,无论您编写什么代码,它都应该正常工作,无论优化如何。这有例外,但它们与问题无关。
现在我回答你的问题。假设已调用函数,其中index
和status
为{1}。请考虑以下情况:
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
和status
0的相同函数时,它会将该索引处的数组元素的值设置为0。调用重新开始,它会将1写入同一个数组元素。我认为你认为这是不正确的行为,因为新状态丢失了。这表明该功能不可重入。
如果对数组元素的访问不是原子的,那么即使在单词的单线程含义中,该函数也不是可重入的,因为如果更新或读取数组元素,该线程可能会在中间被中断。
现在让我们考虑两个线程同时执行具有相同索引但具有不同状态的函数。在这种情况下,发生数据竞争。这意味着两件事。首先,结果是不确定的。您不知道将存储哪个状态。如果这符合您的正确性要求,那就没问题了。但可能,您希望存储最新状态,因此这种不确定性使其不可重入。其次,这是一个更大的问题,就是单个数据竞争会使整个程序根据C标准具有未定义的行为。这当然意味着该功能不可重入。
在没有高速缓存一致性的对称多处理器系统中或在分布式内存计算机系统中,相同的数组元素可以有多个值,因此您将处于最新状态未知的相同情况。
编译器优化可以通过降低检测错误的概率使函数看起来是可重入的。例如,如果编译器可以确定仅使用0或1的status
调用该函数,那么它可以将生成的汇编代码优化为以下内容:
比较状态和myArrayOfStructures [index] .status是否相等。
有条件地将状态写入myArrayOfStructures [index] .status if 不等于。
这是有效的,因为全局变量初始化为零,因此数组中的每个元素都将为0或1.此代码对应于x86上的两条指令,其中使得函数不可重入的情况较少。
实际上,编译器优化甚至可以使函数重入。例如,如果编译器可以确定仅在status
为0的情况下调用该函数,则函数中的所有代码都将变为死代码,从而使其有效地重入。这就是我开头说的原因"可能不是。"