Простая реализация взаимного исключения C в двухъядерной системе AMP

Я работаю с Zynq-7000 SoC - разрабатываю двухъядерное приложение (CPU0, CPU1). Я хочу использовать общую встроенную память (OCM) с отключенным кешем для двунаправленного обмена данными между ядрами. Моя идея состоит в том, чтобы установить обмен данными следующим образом:

typedef struct
{
  uint8_t mstatus;
  uint8_t[10] mdata;
} mailbox;

mailbox core0mbox;
mailbox core1mbox;

Структура mailbox содержит буфер для хранения данных (mdata) и его состояния (mstatus). Статус может быть равен 0 или 1 (в общем случае - нулевое значение указывает на то, что данные были обработаны приемником и новые данные могут быть записаны в буфер; ненулевое значение указывает на то, что данные не были обработаны приемником. еще ресивер). Есть два почтовых ящика — core0mbox (хранит данные, полученные ядром 0 от ядра 1) и core1mbox (хранит данные, полученные ядром 1 от ядра 0), оба хранятся в OCM.

Когда ядро ​​0 хочет отправить данные, оно опрашивает флаг состояния core1mbox.mstatus

  • если он равен нулю, ядро ​​заполняет буфер, связанный с core1mbox, данными, а затем устанавливает флаг, связанный с core1mbox, на 1
  • если он имеет ненулевое значение, ядро ​​не может отправить данные

Когда ядро ​​1 хочет отправить данные, оно опрашивает флаг состояния core0mbox.mstatus.

  • если он равен нулю, ядро ​​заполняет буфер, связанный с core0mbox, данными, а затем устанавливает флаг, связанный с core0mbox, на 1
  • если он имеет ненулевое значение, ядро ​​не может отправить данные

Ядро 0 периодически опрашивает core0mbox.mstatus — если оно имеет ненулевое значение, то ядро ​​0 обрабатывает данные и после завершения устанавливает core0mbox.mstatus в 0.

Ядро 1 периодически опрашивает core1mbox.mstatus - если оно имеет ненулевое значение, то ядро ​​1 обрабатывает данные и после завершения устанавливает core1mbox.mstatus в 0.

Мой вопрос: может ли эта схема привести к неопределенному поведению системы (например, повреждению данных) из-за проблем, вызванных одновременным доступом? Я знаю, что эта схема не могла бы работать, если бы флаг состояния мог иметь больше значений (проблемы из-за неатомарной операции записи/чтения) или если бы в системе было бы большее количество ядер, но, похоже, она хорошо работает для описанная ситуация.


person Steven    schedule 16.11.2016    source источник
comment
Это зависит от того, являются ли операции над uint8_t атомарными. Обычно на int они есть, но я не знаю насчет uint8_t в вашей системе.   -  person yyny    schedule 16.11.2016
comment
Я считаю, что эта реализация будет работать, даже если операции над uint8_t не будут атомарными (поскольку нам важно только, равен ли статус нулю или нет).   -  person Steven    schedule 16.11.2016
comment
Имейте в виду, что современному оборудованию разрешено переупорядочивать доступ к памяти при определенных обстоятельствах... Как вы гарантируете, что другое ядро ​​​​не увидит mstatus == 1 до тех пор, пока все данные не будут записаны? Вам нужны хотя бы некоторые барьеры памяти, если не полный мьютексный подход.   -  person twalberg    schedule 16.11.2016
comment
@ Стивен Это не так просто. Некоторые процессоры могут записывать переменную по крупицам, очищать ее перед записью, временно хранить в ней мусор, использовать битовые сдвиги, логические операторы и т. д. Кроме того, ваш компилятор (а иногда и ваш процессор, как отметил @twalberg) может переупорядочивать доступ к памяти, чтобы увеличить производительность, однако этого можно избежать, используя простой volatile на mstatus. Однако, как я объяснил, это бесполезно, если операции над uint8_t не являются атомарными.   -  person yyny    schedule 18.11.2016
comment
@Steven Теперь на большинстве процессоров общие операции с uint8_t (по крайней мере, чтение и запись) являются атомарными, и ваш код будет работать должным образом (если вы определите mstatus volatile), так что все должно быть в порядке. В противном случае вам придется использовать C11 или, если это невозможно, что-то специфичное для платформы, например futex в Linux или __atomic в GCC.   -  person yyny    schedule 18.11.2016


Ответы (1)


Мьютексы предназначены не только для управления непосредственно затрагиваемыми данными, но и для гарантий того, что потоки взаимно видят другие данные последовательно. C до C11 не имеет определений и инструментов, чтобы даже говорить об этом, и вы будете полностью зависеть от поведения конкретной архитектуры.

Что может случиться здесь в вашей настройке, например, так это то, что из-за какой-то предвзятости архитектуры изменения в поле status могут быть видны другому потоку, где изменения в полях mdata могут быть еще не переданы (подумайте об иерархиях кеша, выравнивании слов). ..).

Так что нет, вам не следует этого делать, если вы точно не знаете, будет ли модель согласованности данных реализована для вашей платформы.

Минимальное требование для того, чтобы это работало на любой платформе, – это квалифицировать ваши переменные как volatile, поскольку в противном случае оптимизирующий компилятор может сделать вид, что никакие изменения каких-либо данных не могут произойти вне текущей функции.

person Jens Gustedt    schedule 23.07.2017