Търсене на причината за състояние на състезание на многоядрен corepack

Използвам проста софтуерна опашка, базирана на индекс за запис и индекс за четене.

Подробности за въведението; Език: C, Компилатор: GCC Оптимизация: -O3 с допълнителни параметри, Архитектура: Armv7a, CPU: Многоядрен, 2 Cortex A-15, L2 Cache: Споделен и активиран, L1 Cache: Всеки 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 и възниква фатална грешка.

Ясно е, че това по някакъв начин е проблем със състоянието на състезанието, но не мога да разбера как се случва. Индексите се актуализират отделно от процесорите.

Не искам да запълвам кода си с бариери на паметта, без да разбирам същината на проблема. Имате ли идея как става това?

Подробности: Това е baremetal система, тези кодове са забранени за прекъсване и няма превключване или превключване на задачи.


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