Реализация атомарной операции чтения и записи в ядре Linux - PullRequest
15 голосов
/ 15 февраля 2012

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

Сначала соответствующий код из архитектуры 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))
  1. Похоже, что для операций чтения и записи был выбран прямой подход к чтению или записи в переменную.Если где-то нет другого трюка, я не понимаю, какие существуют гарантии, что эта операция будет атомарной в области сборки.Я предполагаю, что очевидным ответом будет то, что такая операция преобразуется в один код операции сборки, но даже в этом случае, как это гарантируется, если принять во внимание различные уровни кэша памяти (или другие оптимизации)?

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

Ответы [ 3 ]

14 голосов
/ 15 февраля 2012

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

Что такое слова «атомарный» и «изменчивый»НЕ означает, что существует какая-либо форма синхронизации памяти.Ни один из них не подразумевает НИКАКИХ барьеров или ограждений для чтения / записи.Ничего не гарантируется в отношении согласованности памяти и кэша.Эти функции в основном являются атомарными только на уровне программного обеспечения, и аппаратное обеспечение может оптимизировать / использовать все, что сочтет нужным.

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

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

3 голосов
/ 15 февраля 2012

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

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

2 голосов
/ 15 февраля 2012

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

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

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

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

...