Поиск причины состояния гонки на многоядерном базовом пакете

Я использую простую программную очередь, основанную на индексе записи и индексе чтения.

Детали введения; Язык: C, Компилятор: GCC Оптимизация: -O3 с дополнительными параметрами, Архитектура: Armv7a, ЦП: Многоядерный, 2 ядра Cortex A-15, Кэш L2: Общий и включенный, Кэш L1: Каждый ЦП включен, Архитектура должна быть кешем последовательный.

ЦП 1 выполняет запись, а ЦП 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

ЦП 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;
}

ЦП 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, и возникает фатальная ошибка.

Понятно, что это какая-то проблема состояния гонки, но я не могу понять, как это происходит. Индексы обновляются центральными процессорами отдельно.

Я не хочу заполнять свой код барьерами памяти, не понимая суть проблемы. Вы хоть представляете, как это происходит?

Детали: Это система без операционной системы, эти коды отключены от прерываний, и нет вытеснения или переключения задач.


person tozak    schedule 08.10.2015    source источник
comment
Публикуйте не упрощенную версию, а минимально воспроизводимый пример. Вы даже декларации не показываете.   -  person too honest for this site    schedule 08.10.2015
comment
Вы знаете, что компилятор, скорее всего, изменит порядок кода по своему желанию? Вам понадобится stdatomic.h. Есть довольно много статей, в том числе фрагменты кода для реализации потокобезопасных буферов; Я настоятельно рекомендую вам сначала внимательно их прочитать. Например: почему код чтения на самом деле должен перечитывать индекс записи, если он не меняется с его точки зрения?   -  person too honest for this site    schedule 09.10.2015
comment
Просто чтобы быть уверенным, вы включили бит ACTLR SMP на обоих ядрах, да? Используют ли они одну и ту же таблицу страниц, и для какого типа памяти и атрибутов она настроена? Что касается понимания того, где и почему вам нужны барьеры (при условии, что вы на самом деле не используете некэшированную строго упорядоченную память), я бы посоветовал попытаться разобраться в этот документ.   -  person Notlikethat    schedule 09.10.2015
comment
Можете ли вы опубликовать деассемблированный код сборки этого кода из вашей цепочки инструментов, тогда, вероятно, вы узнаете, какое переупорядочение выполняется компилятором.   -  person AnshuMan Gupta    schedule 24.10.2015


Ответы (1)


Компилятору и ЦП разрешено переупорядочивать хранилища и загрузки по своему усмотрению (т.е. до тех пор, пока однопоточная программа не сможет заметить разницу). Конечно, для многопоточных программ эти эффекты хорошо заметны.

Например, этот код

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/, но также ознакомьтесь со следующими.

person Florian    schedule 08.10.2015