Взятие семафора должно быть атомарным. Это?

Этот фрагмент кода взят из источника Pintos: https://www.cs.usfca.edu/~benson/cs326/pintos/pintos/src/threads/synch.c

void
sema_down (struct semaphore *sema) 
{
  enum intr_level old_level;

  ASSERT (sema != NULL);
  ASSERT (!intr_context ());

  old_level = intr_disable ();
  while (sema->value == 0) 
    {
      list_push_back (&sema->waiters, &thread_current ()->elem);
      thread_block ();
    }
  sema->value--;
  intr_set_level (old_level);
}

Факт взятия семафора sema->value--;, Если это работает, это должна быть атомарная операция. Как мы можем знать, что это атомная операция на самом деле? Я знаю, что современный процессор гарантирует, что выровненная операция памяти (для слова / двойного слова / четырехзначного слова - это зависит от) является атомарной. Но здесь я не уверен, почему это атомно.

1 ответ

Решение

TL:DR: что-то атомарное, если вы делаете это с отключенными прерываниями в системе UP, если вы не учитываете системные устройства, наблюдающие память с DMA.

Обратите внимание intr_disable (); / intr_set_level (old_level); вокруг операции.


современный процессор гарантирует, что согласованная работа памяти является атомарной

Для многопоточных наблюдателей это относится только к отдельным загрузкам или хранилищам, а не к операциям чтения-изменения-записи.


Чтобы что-то было атомным, мы должны рассмотреть, каких потенциальных наблюдателей нам не хватает. Важно то, что ничто не может наблюдать за операцией как за частично произошедшей. Самый простой способ добиться этого - сделать так, чтобы операция была физически / электрически мгновенной и влияла на все биты одновременно (например, нагрузка или накопитель на параллельной шине переходят от незапущенного к завершенному на границе тактового цикла, поэтому это атомно "бесплатно" до ширины параллельной шины). Это невозможно для чтения-изменения-записи, где лучшее, что мы можем сделать, - это остановить наблюдателей от просмотра между загрузкой и хранилищем.

Мой ответ об атомарности на x86 объяснил то же самое по-другому, о том, что значит быть атомным.


В однопроцессорной (UP) системе единственными асинхронными наблюдателями являются другие системные устройства (например, DMA) и обработчики прерываний. Если мы можем исключить наблюдателей без CPU из записи в наш семафор, то это просто атомарность в отношении прерываний, о которых мы заботимся.

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

Прерывание обрабатывается между двумя инструкциями, а не в середине инструкции. Архитектурное состояние машины либо включает декремент памяти, либо нет, потому что dec [mem] либо бежал, либо нет. Нам на самом деле не нужно lock dec [mem] за это.

Кстати, это вариант использования для cmpxchg без lock префикс. Я всегда удивлялся, почему они не просто делают lock неявный в cmpxchg и причина в том, что системы UP часто не нужны lock префиксы.


Нет надежного способа убедиться, что компилятор испускает dec [value] вместо чего-то вроде этого:

mov   eax, [value]
                           ;; interrupt here = bad
dec   eax
                           ;; interrupt here = bad
mov   [value], eax

Я не думаю, что C11 / C++11 предоставляют способ запрашивать атомарность запросов атомарности в отношении обработчиков / прерываний сигналов, но не других потоков. Они обеспечивают atomic_signal_fence как барьер компилятора, но я не помню тип, который на x86 избежал бы lock префиксы при сохранении другой семантики атомарных типов.

С11 / С ++ 11 volatile sig_atomic_t Имеется в виду эта идея, но она обеспечивает атомарность только для отдельных загрузок / хранилищ, а не для RMW. Это typedef для int на x86 Linux. Смотрите этот вопрос для некоторых цитат из стандарта.

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