Подтвердить что ты не робот

Чтение и запись реализации атомной операции в ядре Linux

Недавно я заглянул в реализацию ядра ядра Atom и прочитал несколько вопросов.

Сначала соответствующий код из архитектуры ia64:

typedef struct {
    int counter;
} atomic_t;

#define atomic_read(v)      (*(volatile int *)&(v)->counter)
#define atomic64_read(v)    (*(volatile long *)&(v)->counter)

#define atomic_set(v,i)     (((v)->counter) = (i))
#define atomic64_set(v,i)   (((v)->counter) = (i))
  • Как для операций чтения, так и для записи, кажется, что прямой подход был сделан для чтения или записи в переменную. Если в другом месте нет другого трюка, я не понимаю, какие гарантии существуют, что эта операция будет атомарной в домене сборки. Я думаю, что очевидным ответом будет то, что такая операция переводится в один код операции сборки, но даже в этом случае, как это гарантируется при учете различных уровней кэша памяти (или других оптимизаций)?

  • В считанных макросах летучий тип используется в трюке кастинга. Кто-нибудь знает, как это влияет на атомарность здесь? (Обратите внимание, что он не используется в операции записи)

4b9b3361

Ответ 1

Я думаю, что вы неправильно понимаете (очень неопределенное) использование слова "атомный" и "неустойчивый" здесь. Atomic только на самом деле означает, что слова будут прочитаны или записаны атомарно (в один шаг и гарантируя, что содержимое этой позиции памяти будет всегда писать или другое, а не что-то среднее). И ключевое слово volatile указывает компилятору никогда не принимать данные в этом месте из-за более раннего чтения/записи (в основном, никогда не оптимизировать чтение).

То, что слова "атомный" и "неустойчивый" не означает здесь, - это любая форма синхронизации памяти. Ни один из них не подразумевает ЛЮБОЙ барьер или ограждения для чтения/записи. Ничто не гарантируется в отношении согласованности памяти и кеша. Эти функции в основном атомные только на уровне программного обеспечения, и аппаратное обеспечение может оптимизировать/лежать, но оно сочтет нужным.

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

Что касается volatile, в то время как вообще нет необходимости в нем, если вы не обращаетесь к IO с отображением памяти, все зависит от того, когда/где/почему вызывают макросы atomic_read и atomic_write, Многие компиляторы (хотя и не установлены в спецификации C) генерируют барьеры/ограждения памяти для изменчивых переменных (GCC, с верхней части моей головы, является одним из них. MSVC делает это точно). Хотя это обычно означает, что все чтения/записи этой переменной теперь официально освобождаются от каких-либо оптимизаций компилятора, в этом случае, создавая "виртуальную" изменчивую переменную, только этот конкретный экземпляр чтения/записи отключается для оптимизации и переупорядочение.

Ответ 2

Чтения являются атомарными на большинстве основных архитектур, при условии, что они выровнены с кратным их размеру (и не превышают размер чтения типа ввода), см. руководства по архитектуре Intel. С другой стороны, записи различны, Intel заявляет, что в x86 однобайтовые записи и выровненные записи могут быть атомарными, в соответствии с IPF (IA64) все используют семантику получения и выпуска, что делает ее гарантированной атомарной, см. this.

volatile запрещает компилятору кэшировать значение локально, заставляя его извлекать, когда есть доступ к нему.

Ответ 3

Если вы пишете для определенной архитектуры, вы можете сделать определенные для нее предположения.
Я предполагаю, что IA-64 компилирует эти вещи в одну инструкцию.

Кэш не должен быть проблемой, если счетчик не пересекает границу строки кэша. Но если требуется выравнивание 4/8 байт, этого не может быть.

"Настоящая" атомная инструкция требуется, когда машинная инструкция переводится в два обращения к памяти. Это относится к инкрементам (чтение, приращение, запись) или сравнение и замена.

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