Взятие семафора должно быть атомарным. Это?
Этот фрагмент кода взят из источника 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. Смотрите этот вопрос для некоторых цитат из стандарта.