【问题标题】:Looking the cause of a race condition on a multicore corepack在多核核心包上寻找竞争条件的原因
【发布时间】:2015-10-08 20:45:26
【问题描述】:

我正在使用基于写入索引和读取索引的简单软件队列。

介绍详情;语言:C,编译器:GCC 优化:-O3 带额外参数,架构:Armv7a,CPU:多核,2 Cortex A-15,L2 缓存:共享和启用,L1 缓存:每个 CPU,启用,架构应该是缓存连贯。

CPU 1 负责写入,CPU 2 负责读取。下面是非常简化的示例代码。您可以假设索引的初始值为零。

常见:

#define QUE_LEN 4

unsigned int my_que_write_index = 0; //memory
unsigned int my_que_read_index = 0; //memory

struct my_que_struct{
  unsigned int param1;
  unsigned int param2;
};

struct my_que_struct my_que[QUE_LEN]; //memory

CPU 1 运行:

void que_writer
{
unsigned int write_index_local;

write_index_local = my_que_write_index; //my_que_write_index is in memory
my_que[write_index_local].param1 = 16; //my_que is my queue and stored in memory also
my_que[write_index_local].param2 = 32;
//similar writing stuff

++write_index_local;
if(write_index_local == QUE_LEN) write_index_local = 0;

my_que_write_index = write_index_local;
}

CPU 2 运行:

void que_reader()
{
unsigned int read_index_local, param1, param2;

read_index_local = my_que_read_index; //also in memory
while(read_index_local != my_que_write_index)
 {
   param1 = my_que[read_index_local].param1;

   if(param1 == 0) FATAL_ERROR;

   param2 = my_que[read_index_local].param2;
   //similar reading stuff

   my_que[read_index_local].param1 = 0;

   ++read_index_local;
   if(read_index_local == QUE_LEN) read_index_local = 0;
 }

my_que_read_index = read_index_local;
}

好的,在正常情况下,永远不会发生致命错误,因为队列的 param1 始终以常量值 16 存储。但不知何故,队列的 param1 发生了 0 并发生了致命错误。

很明显,这在某种程度上是一个竞争条件问题,但我不知道它是如何发生的。索引由 CPU 单独更新。

我不想在不了解问题核心的情况下用内存屏障填充我的代码。你知道这是怎么回事吗?

详细说明:这是一个baremetal系统,这些代码是禁止中断的,没有抢占和任务切换。

【问题讨论】:

  • 不要发布“简化版”,而是 minimal reproducible example。你甚至不显示声明。
  • 您知道编译器可能会随意重新排序代码吗?您将需要stdatomic.h。有很多文章,包括实现线程安全缓冲区的code-sn-ps;我强烈建议您先仔细阅读它们。例如:为什么读取代码实际上要重新读取写入索引,因为从它的角度来看它没有改变?
  • 为了确定,您已在两个内核上启用了 ACTLR SMP 位,是吗?它们是否都使用相同的页表,以及设置的内存类型和属性是什么?至于了解您需要障碍的位置和原因(假设您实际上并未使用未缓存的强排序内存),我建议您尝试理解this document
  • 你能从你的工具链中发布这段代码的反汇编汇编代码,然后你可能会知道编译器做了什么重新排序。

标签: c gcc embedded race-condition armv7


【解决方案1】:

允许编译器和 CPU 重新排列它们认为合适的存储和加载(即,只要单线程程序无法观察到差异)。当然,对于多线程程序,这些效果是可以很好观察到的。

比如这段代码

write_index_local = my_que_write_index;
my_que[write_index_local].param1 = 16;
my_que[write_index_local].param2 = 32;
++write_index_local;
if(write_index_local == QUE_LEN) write_index_local = 0;
my_que_write_index = write_index_local;

可以这样重新排序

a = my_que_write_index;
my_que_write_index = write_index_local == QUE_LEN - 1 ? 0 : a + 1;
my_que[a].param1 = 16;
my_que[a].param2 = 32;

正确处理这些事情需要避免此类重新排序的原子和障碍。查看 Preshing 的一系列优秀博文以了解原子,这可能是一个好的开始:http://preshing.com/20120612/an-introduction-to-lock-free-programming/,但也请查看以下博文。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-13
    • 2011-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-11
    • 2018-07-15
    相关资源
    最近更新 更多