Программисты на C часто используют volatile для обозначения того, что переменная может быть изменена вне текущего потока выполнения; в результате у них иногда возникает соблазн использовать его в коде ядра, когда используются общие структуры данных. Другими словами, они, как известно, рассматривали изменчивые типы как своего рода легкую атомарную переменную, которой они не являются. Использование volatile в коде ядра почти никогда не является правильным; этот документ описывает почему.
Ключевым моментом, который необходимо понять в отношении изменчивости, является то, что ее целью является подавление оптимизации, что почти никогда не является тем, чем кто-то действительно хочет заниматься. В ядре необходимо защищать разделяемые структуры данных от нежелательного одновременного доступа, что является совсем другой задачей. Процесс защиты от нежелательного параллелизма также позволит более эффективно избежать почти всех проблем, связанных с оптимизацией.
Как и volatile, примитивы ядра, обеспечивающие безопасный одновременный доступ к данным (спин-блокировки, мьютексы, барьеры памяти и т. Д.), Предназначены для предотвращения нежелательной оптимизации. Если они используются должным образом, не будет необходимости использовать также и volatile. Если volatile по-прежнему необходимо, в коде почти наверняка есть ошибка. В правильно написанном коде ядра volatile может только замедлять работу.
Рассмотрим типичный блок кода ядра:
spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);
Если весь код следует правилам блокировки, значение shared_data не может неожиданно измениться, пока удерживается the_lock. Любой другой код, который может захотеть поиграть с этими данными, будет ожидать блокировки. Примитивы спин-блокировки действуют как барьеры памяти - они явно написаны для этого - это означает, что доступ к данным не будет оптимизирован для них. Таким образом, компилятор может подумать, что он знает, что будет в shared_data, но вызов spin_lock (), поскольку он действует как барьер памяти, заставит его забыть все, что он знает. Не будет проблем с оптимизацией при доступе к этим данным.
Если бы shared_data были объявлены как volatile, блокировка все равно была бы необходима. Но компилятору также будет запрещено оптимизировать доступ к shared_data в критической секции, когда мы знаем, что никто другой не может с ним работать. Пока блокировка удерживается, shared_data не является энергозависимым. При работе с общими данными правильная блокировка делает энергозависимые ненужными - и потенциально опасными.
Класс энергозависимого хранилища изначально предназначался для регистров ввода-вывода с отображением в памяти. Внутри ядра доступ к регистрам также должен быть защищен блокировками, но также не требуется, чтобы компилятор «оптимизировал» доступ к регистрам в критической секции. Но в ядре доступ к памяти ввода / вывода всегда осуществляется через функции доступа; доступ к памяти ввода-вывода напрямую через указатели не одобряется и не работает на всех архитектурах. Эти средства доступа написаны для предотвращения нежелательной оптимизации, поэтому, опять же, volatile не нужна.
Другая ситуация, когда можно испытать желание использовать volatile, - это когда процессор занят ожиданием значения переменной. Правильный способ ожидания ожидания:
while (my_variable != what_i_want)
cpu_relax();
Вызов cpu_relax () может снизить энергопотребление процессора или уступить многопоточному двойному процессору; оно также служит барьером памяти, поэтому, опять же, энергозависимость не нужна. Конечно, ожидание в основном это антисоциальный акт с самого начала.
Есть еще несколько редких ситуаций, когда volatile имеет смысл в ядре:
Вышеупомянутые функции доступа могут использовать volatile на архитектурах, где прямой доступ к памяти ввода-вывода работает. По сути, каждый вызов средства доступа становится небольшим критическим разделом сам по себе и гарантирует, что доступ происходит так, как этого ожидает программист.
Встроенный код сборки, который изменяет память, но не имеет других видимых побочных эффектов, рискует быть удаленным GCC. Добавление ключевого слова volatile в операторы asm предотвратит это удаление.
Переменная jiffies является особенной в том смысле, что она может иметь различное значение при каждой ссылке на нее, но она может быть прочитана без какой-либо специальной блокировки. Таким образом, jiffies может быть изменчивым, но добавление других переменных этого типа решительно осуждается. Jiffies считается проблемой «глупого наследия» (слова Линуса) в этом отношении; исправить это будет больше проблем, чем стоит.
Указатели на структуры данных в когерентной памяти, которые могут быть изменены устройствами ввода-вывода, иногда могут быть законно изменчивыми. Кольцевой буфер, используемый сетевым адаптером, где этот адаптер меняет указатели, чтобы указать, какие дескрипторы были обработаны, является примером ситуации такого типа.
Для большей части кода ни одно из приведенных выше обоснований для volatile не применимо. В результате, использование volatile, вероятно, будет рассматриваться как ошибка и приведет к дополнительной проверке кода. Разработчики, которые испытывают желание использовать volatile, должны сделать шаг назад и подумать о том, чего они действительно пытаются достичь.