#c #computer-science #volatile #computer-architecture #memorycache
#c #информатика #энергозависимые #архитектура процессора #memorycache
Вопрос:
Кэш управляется аппаратным обеспечением кэша прозрачно для процессора, поэтому, если мы используем изменяемые переменные в программе C, как гарантируется, что моя программа каждый раз считывает данные с указанного фактического адреса памяти, но не с кэша.
Я понимаю, что,
-
Ключевое слово Volatile сообщает компилятору, что ссылки на переменные не должны быть оптимизированы и должны считываться так, как запрограммировано в коде.
-
Кэш управляется аппаратным обеспечением кэша прозрачно, поэтому, когда процессор выдает адрес, он не знает, поступают ли данные из кэша или из памяти.
Итак, если у меня есть требование считывать адрес памяти каждый раз, когда требуется, как я могу убедиться, что на него ссылаются не из кэша, а с требуемого адреса?
Каким-то образом эти две концепции плохо сочетаются. Пожалуйста, поясните, как это делается.
(Представьте, что у нас есть политика обратной записи в кэше (если требуется для анализа проблемы))
Спасибо, микроядро 🙂
Ответ №1:
Разработчик прошивки здесь. Это стандартная проблема во встроенном программировании, которая сбивает с толку многих (даже очень опытных) разработчиков.
Я предполагаю, что вы пытаетесь получить доступ к аппаратному регистру, и значение этого регистра может меняться со временем (будь то состояние прерывания, таймер, индикация GPIO и т. Д.).
volatile
Ключевое слово является лишь частью решения и во многих случаях может быть необязательным. Это приводит к тому, что переменная перечитывается из памяти каждый раз, когда она используется (в отличие от оптимизации компилятором или сохранения в регистре процессора при многократном использовании), но является ли считываемая «память» фактическим аппаратным регистром или кэшированным местоположением, неизвестно вашему коду иключевое слово не влияет volatile
. Если ваша функция считывает регистр только один раз, вы, вероятно, можете остановиться volatile
, но, как правило, я предлагаю, чтобы большинство аппаратных регистров определялись как volatile
.
Более серьезной проблемой является кэширование и согласованность кэша. Самый простой подход здесь — убедиться, что ваш регистр находится в некэшированном адресном пространстве. Это означает, что каждый раз, когда вы обращаетесь к регистру, вы гарантированно считываете / записываете фактический аппаратный регистр, а не кэш-память. Более сложный, но потенциально более эффективный подход заключается в использовании кэшированного адресного пространства и в том, чтобы ваш код вручную принудительно обновлял кэш для конкретных ситуаций, подобных этой. Для обоих подходов то, как это достигается, зависит от архитектуры и выходит за рамки вопроса. Это может включать MTRR (для x86), MMU, модификации таблицы страниц и т. Д.
Надеюсь, это поможет. Если я что-то пропустил, дайте мне знать, и я расширю свой ответ.
Комментарии:
1. Целью
volatile
при использовании хорошего компилятора должно быть обеспечение того, чтобы сгенерированный код сообщал процессору обо всем, что необходимо записать до определенного момента, и не просил процессор считывать информацию до тех пор, пока после этого. Программисту также может потребоваться использовать встроенные или другие средства для принудительной очистки аппаратного кэша, но принудительная очистка аппаратного кэша была бы бесполезной, если бы компилятор кэшировал регистры способами, о которых аппаратное обеспечение ничего не знало.
Ответ №2:
Из вашего вопроса с вашей стороны возникает неправильное представление.
Volatile
ключевое слово не связано с кэшем, как вы описываете.
Когда ключевое volatile
слово указано для переменной, это дает компилятору подсказку не выполнять определенные оптимизации, поскольку эта переменная может неожиданно измениться в других частях программы.
Здесь имеется в виду, что компилятор не должен повторно использовать значение, уже загруженное в регистр, но снова обращаться к памяти, поскольку значение в регистре не гарантируется таким же, как значение, сохраненное в памяти.
Остальное, касающееся кэш-памяти, напрямую не связано с программистом.
Я имею в виду, что синхронизация любой кэш-памяти процессора с ОЗУ — это совершенно другая тема.
Комментарии:
1. Итак, если, как я уже говорил, переменная обновляется каким-либо другим потоком или драйвером, считываемым с устройства ввода, какова гарантия того, что я считываю правильное значение, а не что-то кэшированное? Как избежать такого сценария в коде?
2. При использовании
volatile
гарантируется, что вы всегда будете считывать последнее обновление, которое было выполнено в памяти из другого потока. Но у меня такое чувство, что ваша проблема больше связана с уровнем ОС, т.Е. синхронизацией кэша и памяти3. @Cratylus Если вы используете потоки, «последние», «прошлые» … четко не определены между потоками, работающими на разных ядрах.
Ответ №3:
Я предлагаю пометить страницу как не кэшированную менеджером виртуальной памяти.
В Windows это делается с помощью настройки PAGE_NOCACHE
при вызове VirtualProtect
.
Для несколько иной цели инструкции SSE 2 содержат инструкции _mm_stream_xyz
по предотвращению загрязнения кэша, хотя я не думаю, что они применимы к вашему случаю здесь.
В любом случае нет переносимого способа делать то, что вы хотите на C; вы должны использовать функциональность ОС.
Комментарии:
1. Итак, это зависит от платформы? Следовательно, кэш не контролируется аппаратным обеспечением кэша? (если бы аппаратное обеспечение полностью управляло кэшем, тогда оно не проверяло бы флаг PAGE_NOCACHE, верно?)
2. @Microkernel: оно управляется аппаратным обеспечением. Но операционная система сообщает аппаратному обеспечению, что делать (в конце концов, аппаратное обеспечение понятия не имеет, как ОС хочет управлять памятью), и вы запрашиваете ОС делать то, что вы хотите. И вся эта информация хранится в — угадайте, где? — сама память. Однако это пассивный процесс — ОС вмешивается только в том случае, если что-то выходит из строя (например, ошибка страницы). Кроме этого, аппаратное обеспечение просто продолжает делать то, что ОС попросила его сделать, без вмешательства ОС.
3. Хм, хорошо… Кажется, я где-то ошибаюсь, я всегда считал, что кэш процессора прозрачен для всех, кроме аппаратного обеспечения кэша! Любые ссылки, которые я должен прочитать, чтобы правильно понять мои концепции? ! Большое спасибо за разъяснения 🙂
4. @Microkernel: Конечно! 🙂 По сути, операционная система хранит всю информацию об управлении памятью внутри «таблиц страниц» в памяти и сообщает процессору, где искать информацию. Затем процессор управляет всем и запрашивает у операционной системы «помощь» всякий раз, когда он не может решить, что делать. Вы можете прочитать о подкачке здесь и о кэшировании здесь ; дайте мне знать, если у вас все еще есть какие-либо вопросы. (Вот почему они говорят, что операционная система находится между аппаратным и программным обеспечением — это действительно так!)
Ответ №4:
В Википедии есть довольно хорошая статья о MTRR (регистрах диапазона типов памяти), которые применяются к семейству процессоров x86.
Подводя итог, начиная с Pentium Pro, Intel (и скопированная AMD) имела эти регистры MTR, которые могли устанавливать атрибуты uncached, write-through, write-combining, write-protect или write-back для диапазонов памяти.
Начиная с Pentium III, но, насколько я знаю, они действительно полезны только с 64-разрядными процессорами, они поддерживают MTRR, но их можно переопределить таблицами атрибутов страницы, которые позволяют процессору устанавливать тип памяти для каждой страницы памяти.
Основное использование MTRR, о котором я знаю, — это графическая оперативная память. Гораздо эффективнее пометить это как объединение записи. Это позволяет кэш-памяти хранить записи и ослабляет все правила упорядочения записи в память, позволяя выполнять очень высокоскоростную пакетную запись на видеокарту.
Но для ваших целей вам потребуется либо MTRR, либо параметр PAT либо для некэширования, либо для сквозной записи.
Ответ №5:
Как вы говорите, кэш прозрачен для программиста. Система гарантирует, что вы всегда видите значение, в которое было записано последним, если вы обращаетесь к объекту через его адрес. «Единственное», что вы можете понести, если в вашем кэше находится устаревшее значение, — это штраф во время выполнения.
Комментарии:
1. Только если на компьютере установлен только один процессор.
2. @JeremyP, я думаю, что вопрос здесь был задан вне рамок одновременного доступа к общей памяти. Если у вас есть это в дополнение, да, все становится намного сложнее. Затем вам нужно будет применить соответствующие инструменты для обеспечения согласованности данных. Но тогда это более общая проблема, просмотр ее с точки зрения кэшей, вероятно, также не является правильным представлением.
3. Я не думаю, что это выходило за рамки одновременного доступа к памяти. Предпосылка вопроса заключается в том, что существует одновременный доступ к памяти, в противном случае, как вы указываете, кэш прозрачен.
4. На компьютере не должно быть более одного процессора. Регистры управления устройством, отображенные в память, могут иметь тот же эффект (для жестких микроконтроллеров разработчик может позаботиться о том, чтобы не кэшировать это адресное пространство, для программных ядер на FPGA / PLD, не обязательно ). См. Страницу 4 из altera.com/ja_JP/pdfs/literature/hb/nios2/n2sw_nii52007.pdf
5. @JeremyP » Только если на компьютере установлен только один процессор «, что не всегда неверно, но крайне вводит в заблуждение. Он должен гласить: только если на компьютере нет нескольких процессоров, которые не предназначены для поддержки потоков. Если процессор предназначен для поддержки потоков, то это гарантировано.
Ответ №6:
volatile
гарантирует, что данные считываются каждый раз, когда это необходимо, не беспокоясь о каком-либо кэше между процессором и памятью. Но если вам нужно прочитать фактические данные из памяти, а не кэшированные данные, у вас есть два варианта:
- Создайте плату, на которой указанные данные не кэшируются. Возможно, это уже имеет место, если вы обращаетесь к какому-либо устройству ввода-вывода,
- Используйте специальные инструкции процессора, которые обходят кэш. Это используется, когда вам нужно очистить память для активации возможных ошибок SEU.
Детали второго варианта зависят от ОС и / или процессора.
Комментарии:
1. Я должен не согласиться с этим сообщением.
volatile
Ключевое слово просто не позволяет компилятору C выполнять определенные оптимизации для переменных. Он ничего не делает с кэшем. Некоторые компиляторы могут дать вам возможность искажать значение этого ключевого слова (компилятор ARC является одним из них), но для большинства компиляторов это не так.
Ответ №7:
использование ключевого слова _Uncached может помочь во встроенных ОС, таких как MQX
#define MEM_READ(addr) (*((volatile _Uncached unsigned int *)(addr)))
#define MEM_WRITE(addr,data) (*((volatile _Uncached unsigned int *)(addr)) = data)
Комментарии:
1. Кнопка code существует не просто так. Пожалуйста, не злоупотребляйте форматированием.
2. Какой компилятор поддерживает
_Uncached
ключевое слово? Поиск в Google для «_Uncached» дает ваш ответ в качестве первого результата.