Программисты на C часто использовали volatile для обозначения того, что переменная может быть изменена вне текущего потока выполнения; в результате у них иногда возникает соблазн использовать его в коде ядра, когда используются общие структуры данных. Другими словами, они, как известно, рассматривают изменчивые типы как своего рода простую атомарную переменную, которой они не являются. Использование 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 была объявлена изменчивой, блокировка все равно была бы необходима. Но компилятор также не сможет оптимизировать доступ к shared_data внутри критического раздела, когда мы знаем, что никто другой не может с ним работать. Пока блокировка удерживается, shared_data не является изменчивым. При работе с общими данными правильная блокировка делает изменчивые данные ненужными - и потенциально опасными.
Класс энергозависимой памяти изначально предназначался для регистров ввода-вывода с отображением в память. Внутри ядра доступ к регистрам также должен быть защищен блокировками, но также не следует, чтобы компилятор «оптимизировал» доступ к регистрам в критическом разделе. Но внутри ядра доступ к памяти ввода / вывода всегда осуществляется через функции доступа; доступ к памяти ввода-вывода напрямую через указатели не одобряется и работает не на всех архитектурах. Эти аксессоры написаны для предотвращения нежелательной оптимизации, поэтому, опять же, в volatile нет необходимости.
Другая ситуация, в которой может возникнуть соблазн использовать volatile, - это когда процессор занят ожиданием значения переменной. Правильный способ выполнения активного ожидания:
while (my_variable != what_i_want)
cpu_relax();
Вызов cpu_relax () может снизить энергопотребление ЦП или уступить место двухпоточному процессору; он также служит барьером для памяти, поэтому, опять же, volatile не нужен. Конечно, ожидание в ожидании - это вообще антисоциальный акт с самого начала.
Есть еще несколько редких ситуаций, когда volatile имеет смысл в ядре:
Вышеупомянутые функции доступа могут использовать volatile на архитектурах, где прямой доступ к памяти ввода-вывода действительно работает. По сути, каждый вызов метода доступа сам по себе становится небольшим критическим разделом и гарантирует, что доступ происходит так, как ожидал программист.
Встроенный ассемблерный код, который изменяет память, но не имеет других видимых побочных эффектов, рискует быть удаленным GCC. Добавление ключевого слова volatile в операторы asm предотвратит это удаление.
Переменная jiffies отличается тем, что каждый раз при обращении к ней может иметь другое значение, но ее можно прочитать без какой-либо специальной блокировки. Таким образом, jiffies могут быть непостоянными, но добавление других переменных этого типа категорически не одобряется. В этом отношении Джиффис считается «глупым наследием» (слова Линуса); починить это было бы труднее, чем оно того стоит.
Указатели на структуры данных в когерентной памяти, которые могут быть изменены устройствами ввода-вывода, иногда вполне законно могут быть энергозависимыми. Кольцевой буфер, используемый сетевым адаптером, где этот адаптер изменяет указатели, чтобы указать, какие дескрипторы были обработаны, является примером такого типа ситуации.
Для большей части кода ни одно из приведенных выше обоснований для volatile не применимо. В результате использование volatile, вероятно, будет рассматриваться как ошибка и потребует дополнительной проверки кода. Разработчики, которые склонны использовать volatile, должны сделать шаг назад и подумать о том, чего они на самом деле пытаются достичь.
volatile
создает барьер памяти при чтении, поэтому его можно использовать в качестве поточно-ориентированного флага завершения метода, поскольку он обеспечивает связь «происходит раньше» с кодом до установки флага. В C. - person Monstieur   schedule 14.04.2015