Атомное тестирование и установка для микроконтроллера LPC1788

Я работаю с микроконтроллером NXP LPC1788 и разрабатываю многопоточное приложение на C. В части моего приложения я определяю пользовательскую структуру данных связанного списка. Ранее у меня были проблемы с моей программой из-за одновременного доступа к определенному списку, который, как мне кажется, я решил, реализовав метод "захвата блокировки" и метод "освобождения блокировки" для списков, которые потоки могут вызывать перед доступом к самому списку.

Я сделал это, добавив элемент данных 'sema' в структуру списка:

typedef struct linked_list
{
  list_node_t *head;
  list_node_t *tail;
  uint32_t len;
  NODE_ITEM_TYPE_T itemType;
  uint32_t itemSize;
  uint8_t sema;
} linked_list_t;

Мой метод "получения блокировки" приведен ниже:

void LIST_AcquireLock(linked_list_t *list)
{
  while(list->sema);
  list->sema = 1;
}

Мой метод разблокировки приведен ниже:

void LIST_ReleaseLock(linked_list_t *list)
{
  list->sema = 0;
}

Обычно это работает нормально, так как мое приложение включает в себя добавление и удаление элементов в списке, например тысячи раз в секунду, и с тех пор я не заметил никаких ошибок, связанных с одновременным доступом.

Однако, чтобы быть более уверенным в том, что это работает, мне было интересно, есть ли какой-нибудь способ реализации подхода "тестируй и устанавливай". LPC1788 использует версию набора команд Thumb, специфичную для микроконтроллеров Cortex-M3, которую можно найти здесь или в руководстве пользователя на стр. 918+.

Просматривая его, я не могу найти ничего похожего на инструкцию по тестированию и настройке. Я мог бы просто пропустить это.

В идеале я хотел бы иметь что-то вроде этого:

void LIST_AcquireLock(linked_list_t *list)
{
  do{
    while(list->sema);
  } while(TestAndSet(list->sema));
}

РЕДАКТИРОВАТЬ

Основываясь на ответе Немо, я попытался сделать следующее:

void LIST_AcquireLock(linked_list_t *list)
{
  // Wait until lock seems free.
  while(list->sema);

  // Make sure lock is actually free.
  do {

    // If the semaphore is locked, we continue.
    // OTHERWISE we try to lock it ourselves.
    if(__LDREXB(&(list->sema))) continue;

    // If __STREXB returns 1, then another thread might have accessed that
    // memory location and we can't be sure the lock operation is atomic,
    // so try the locking procedure again.
  } while(__STREXB(1, &(list->sema)));
}

Если это полезно, это соответствующий код сборки:

LIST_AcquireLock:
??LIST_AcquireLock_0:
       0x56de: 0x7d01         LDRB      R1, [R0, #0x14]
       0x56e0: 0x2900         CMP       R1, #0
       0x56e2: 0xd1fc         BNE.N     ??LIST_AcquireLock_0    ; 0x56de
??LIST_AcquireLock_1:
       0x56e4: 0xf110 0x0114  ADDS.W    R1, R0, #20             ; 0x14
       0x56e8: 0xe8d1 0x1f4f  LDREXB    R1, [R1]
       0x56ec: 0xb2c9         UXTB      R1, R1
       0x56ee: 0x2900         CMP       R1, #0
??LIST_AcquireLock_2:
       0x56f0: 0xf110 0x0114  ADDS.W    R1, R0, #20             ; 0x14
       0x56f4: 0x2201         MOVS      R2, #1
       0x56f6: 0xe8c1 0x2f43  STREXB    R3, R2, [R1]
       0x56fa: 0x2b00         CMP       R3, #0
       0x56fc: 0xd1f2         BNE.N     ??LIST_AcquireLock_1    ; 0x56e4
       0x56fe: 0x4770         BX        LR

У меня проблемы с воспроизведением проблем одновременного доступа (при условии, что это были проблемы с параллелизмом, которые у меня были), поэтому я не знаю наверняка, что это работает.

1 ответ

Решение

ARM использует парадигму "связанный с нагрузкой / исключающий магазин" для атомарных операций. См. Этот вопрос и раздел 39.2.4.8 руководства пользователя, которое вы связали, для получения подробной информации.

[Обновить]

Исходя из кода, приведенного в ссылке @HansPassant, я бы предложил несколько небольших изменений в вашей программе:

void LIST_AcquireLock(linked_list_t *list)
{
  // Wait until lock seems free.
  //while(list->sema); // unnecessary

  // Make sure lock is actually free.
  do {

    // If the semaphore is locked, we continue.
    // OTHERWISE we try to lock it ourselves.
    if(__LDREXB(&(list->sema))) continue;

    // If __STREXB returns 1, then another thread might have accessed that
    // memory location and we can't be sure the lock operation is atomic,
    // so try the locking procedure again.
  } while(__STREXB(1, &(list->sema)));

  // Ensure CPU does not reorder any memory accesses across lock acquisition.
  __DMB();
}

__DMB() вероятно, не имеет отношения к очень простым ядрам ARM, но это определенно необходимо для более сложных. Современные процессоры имеют сложные модели памяти.

Другие вопросы по тегам