Атомарность и порядок выполнения
От атомарных операций чтения перейдем к различиям между атомарностью и порядком выполнения. Как уже рассказывалось, операции чтения одного машинного слова всегда выполняются атомарно. Эти операции никогда не перекрываются операциями записи того же машинного слова. Иными словами, операция чтения данных всегда возвращает машинное слово в консистентном состоянии: иногда возвращается значение, которое было до записи, а иногда — то, которое стало после записи, но никогда не возвращается значение, которое было во время записи. Например, если целочисленное значение вначале было равно 42, а потом стало 365, то операция чтения всегда вернет значение 42 или 365, но никогда не смешанное значение. Это называется атомарностью.
Иногда бывает так, что вашему коду необходимо нечто большее, например операция чтения всегда выполняется перед ожидающей операцией записи. Это называется не атомарностью, а порядком выполнения ( ordering ). Атомарность гарантирует, что инструкции выполняются не прерываясь и что они либо выполняются полностью, либо не выполняются совсем. Порядок выполнения же гарантирует, что две или более инструкций, даже если они выполняются разными потоками или разными процессами, всегда выполняются в нужном порядке.
Атомарные операции, которые обсуждаются в этом разделе, гарантируют только атомарность. Порядок выполнения гарантируется с помощью операций барьеров ( barrier ), которые будут рассмотрены дальше в текущей главе.
В любом коде использование атомарных операций, где это возможно, более предпочтительно по сравнению со сложными механизмами блокировок. Для большинства аппаратных платформ одна или две атомарные операции приводят к меньшим накладным затратам и к более эффективному использованию процессорного кэша, чем в случае более сложных методов синхронизации. Как и в случае любого кода, который чувствителен к производительности, всегда разумным будет протестировать несколько вариантов.
Битовые атомарные операции
В дополнение к атомарным операциям с целыми числами, ядро также предоставляет семейство функций, которые позволяют работать на уровне отдельных битов. Не удивительно, что эти операции зависят от аппаратной платформы и определены в файле .
Тем не менее может вызвать удивление то, что функции, которые реализуют битовые операции, работают с обычными адресами памяти. Аргументами функций являются указатель и номер бита. Бит 0 — это наименее значащий бит числа, которое находится по указанному адресу. На 32-разрядных машинах бит 31 — это наиболее значащий бит, а бит 0 — наименее значащий бит машинного слова. Нет ограничений на значение номера бита, которое передается в функцию, хотя большинство пользователей работают с машинными словами и номерами битов от 0 до 31 (или до 63 для 64-битовых машин).
Так как функции работают с обычными указателями, то в этом случае нет аналога типу atomic_t
, который используется для операций с целыми числами. Вместо этого можно использовать указатель на любые данные. Рассмотрим следующий пример.
unsigned long word = 0;
set_bit(0, &word); /* атомарно устанавливается бит 0 */
set_bit(1, &word); /* атомарно устанавливается бит 1 */
printk("%ul\n", word); /* будет напечатано "3" */
clear_bit(1, &word); /* атомарно очищается бит 1 */
change_bit(0, &word); /* атомарно изменяется значение бита 1,
теперь он очищен */
/* атомарно устанавливается бит нуль и возвращается предыдущее
значение этого бита (нуль) */
if (test_and_set_bit(0, &word)) {
/* условие никогда не выполнится ... */
}
Список стандартных атомарных битовых операций приведен в табл. 9.2.
Таблица 9.2. Список стандартных атомарных битовых операций
Атомарная битовая операция |
Описание |
void set_bit(int nr, void *addr) |
Атомарно установить nr -й бит в области памяти, которая начинается с адреса addr |
void clear_bit(int nr, void *addr) |
Атомарно очистить nr -й бит в области памяти, которая начинается с адреса addr |
void change_bit(int nr, void *addr) |
Атомарно изменить значение nr -го бита в области памяти, которая начинается с адреса addr , на инвертированное |
int test_and_set_bit(int nr, void *addr) |
Атомарно установить значение nr -го бита в области памяти, которая начинается с адреса addr , и возвратить предыдущее значение этого бита |
int test_and_clear_bit(int nr, void *addr) |
Атомарно очистить значение nr -го бита в области памяти, которая начинается с адреса addr , и возвратить предыдущее значение этого бита |
int test_and_change_bit(int nr, void *addr) |
Атомарно изменить значение nr -го бита в области памяти, которая начинается с адреса addr , на инвертированное и возвратить предыдущее значение этого бита |
int test_bit(int nr, void *addr) |
Атомарно возвратить значение nr -го бита в области памяти, которая начинается с адреса addr |
Для удобства работы также предоставляются неатомарные версии всех битовых операций. Эти операции работают так же, как и их атомарные аналоги, но они не гарантируют атомарности выполнения операций, и имена этих функций начинаются с двух символов подчеркивания. Например, неатомарная форма функции test_bit()
будет иметь имя __test_bit()
. Если нет необходимости в том, чтобы операции были атомарными, например, когда данные уже защищены с помощью блокировки, неатомарные операции могут выполняться быстрее.
Читать дальше