Какую гарантию даёт read_volatile, которой не даёт обычное чтение raw pointer?
read_volatile сообщает компилятору, что чтение является наблюдаемым побочным эффектом и не должно быть удалено как якобы ненужное. Это важно для регистров устройств и другой памяти, состояние которой может изменяться независимо от потока Rust.
Однако read_volatile не делает доступ безопасным сам по себе: указатель всё ещё должен быть корректным, выровненным и указывать на допустимую для чтения память. Операция также не обеспечивает атомарность, синхронизацию между потоками или корректность гонок.
Обычные чтения и записи памяти компилятор может оптимизировать: удалить неиспользованный результат, объединить обращения или заменить несколько операций одной. Для памяти, взаимодействующей с устройством, это недопустимо: само чтение регистра может запускать или подтверждать аппаратное событие.
volatile появился как механизм выражения таких обращений без превращения их в обычные оптимизируемые загрузки и сохранения. В Rust он доступен через функции core::ptr::read_volatile и core::ptr::write_volatile.
Представим регистр состояния устройства по известному адресу. Программа читает его, чтобы получить актуальный статус, но не использует результат сразу или использует его в ветвлении, которое компилятор способен упростить. Обычный доступ не выражает требование, что физическое чтение должно произойти.
Использование read_volatile решает только вопрос наблюдаемости операции для компилятора. Если адрес неверен, не выровнен для типа или не разрешён аппаратурой, результатом всё равно может быть неопределённое поведение, аппаратная ошибка или зависание устройства.
Обычный raw-pointer load подчиняется модели обычной памяти Rust: компилятор вправе рассуждать о нём как о чтении значения, если сохраняется наблюдаемое поведение программы. read_volatile имеет специальную семантику: чтение считается внешне наблюдаемым и не может быть просто устранено или превращено в отсутствие доступа.
Минимальный пример:
Здесь &raw const создаёт raw pointer без промежуточного создания обычной ссылки на static mut, а read_volatile требует выполнить чтение. В реальном драйвере адрес обычно предоставляется платформой или картированием MMIO, поэтому дополнительно проверяются адресное пространство, выравнивание и допустимый размер доступа.
У volatile-доступа нет свойств атомарных операций. Если несколько потоков или устройств обращаются к памяти конкурентно, для требуемого протокола нужны атомики, барьеры памяти или механизмы, определённые платформой. Для MMIO порядок операций иногда требует специальных аппаратных барьеров; одним read_volatile это не гарантируется.
Volatile также не продлевает время жизни объекта и не исправляет алиасинг. Нельзя использовать его как способ безопасно читать освобождённую память, обходить правила доступа Rust или заменить синхронизацию.
В драйвере нужно прочитать регистр состояния устройства. Вариант с обычным чтением проще, но компилятор может оптимизировать обращение как обычную загрузку, что не соответствует контракту MMIO. Вариант с атомарным чтением лучше подходит для RAM, используемой несколькими потоками, но не является универсальной заменой volatile: аппаратный регистр может не поддерживать атомарную семантику требуемого типа.
Выбран read_volatile с отдельной безопасной обёрткой, которая проверяет допустимый адрес и тип доступа. Если документация платформы требует барьеров или определённого порядка операций, обёртка дополнительно использует платформенный механизм синхронизации. Такой дизайн отделяет гарантию выполнения самого обращения от гарантий многопоточности и корректности протокола устройства.
Делает ли read_volatile чтение атомарным?
Нет. Volatile описывает необходимость выполнить наблюдаемое обращение, но не запрещает другому потоку или устройству увидеть частичное состояние там, где аппаратно возможны неатомарные доступы. Для атомарности требуются подходящий атомарный тип и поддержка целевой платформы либо специальный протокол устройства.
Можно ли через read_volatile безопасно прочитать значение по любому числовому адресу?
Нет. Raw pointer должен указывать на память, доступную для чтения как выбранный тип, и иметь необходимое выравнивание, если конкретная операция его требует. Кроме того, адрес должен принадлежать допустимому адресному пространству; volatile не превращает произвольный адрес в действительный объект или корректный MMIO-регистр.
Заменяет ли volatile барьер памяти между операциями с устройством?
Нет. Volatile гарантирует специальную семантику самого обращения, но не является общим механизмом межпоточной синхронизации и не обещает нужный аппаратный порядок всех операций. Если протокол устройства требует, чтобы запись команды завершилась до чтения статуса, это должно быть обеспечено подходящими барьерами или API конкретной архитектуры.