'Энергозависим' требуемый для общей памяти, к которой получают доступ через функцию доступа?

[редактирование] Для дополнительного чтения, и быть ясным, это - то, о чем я говорю: Введение в энергозависимое ключевое слово

При рассмотрении кода встроенных систем одна из наиболее распространенных ошибок, которые я вижу, является пропуском энергозависимых для совместно используемых данных потока/прерывания. Однако мой вопрос состоит в том, 'безопасно' ли не использовать volatile когда к переменной получают доступ через функцию доступа или функцию членства?

Простой пример; в следующем коде...

volatile bool flag = false ;
void ThreadA()
{
    ...
    while (!flag)
    {
        // Wait
    }
    ...
}

interrupt void InterruptB()
{
    flag = true ;
} 

... переменная flag должно быть энергозависимо, чтобы гарантировать, что чтение в ThreadA не оптимизировано, однако если флаг был считан через функцию таким образом...

volatile bool flag = false ;
bool ReadFlag() { return flag }
void ThreadA()
{
    ...
    while ( !ReadFlag() )
    {
        // Wait
    }
    ...
}

... делает flag все еще потребность быть энергозависимым? Я понимаю, что нет никакого вреда в нем являющийся энергозависимым, но мое беспокойство для того, когда это опущено, и пропуск не определяется; это будет безопасно?

Вышеупомянутый пример тривиален; в реальном случае (и причина моего выяснения), у меня есть библиотека классов, которая переносит RTOS, таким образом, что существует абстрактный класс cTask, что объекты задачи получены из. Такие "активные" объекты обычно имеют функции членства, что данные доступа, чем могут быть изменены в контексте задачи объекта, но получены доступ от других контекстов; действительно ли очень важно затем, что такие данные объявляются энергозависимые?

Я действительно интересуюсь тем, что гарантируется о таких данных, а не что мог бы сделать практический компилятор. Я могу протестировать много компиляторов и найти, что они никогда не оптимизируют чтение через средство доступа, но затем однажды находят компилятор или параметр компилятора, который делает это предположение неверным. Я мог предположить, например, что, если бы функция была встроена, такая оптимизация была бы тривиальна для компилятора, потому что это не отличалось бы, чем прямое чтение.

18
задан Clifford 30 June 2010 в 15:26
поделиться

5 ответов

Я понимаю C99, что, если вы не укажете volatile , то, как и когда фактически осуществляется доступ к переменной, определяется реализацией. Если вы укажете квалификатор volatile , тогда код должен работать в соответствии с правилами абстрактной машины .

Соответствующие части в стандарте: 6.7.3 Квалификаторы типа (изменчивое описание) и 5.1.2.3 Выполнение программы (определение абстрактной машины).

С некоторого времени я знаю, что многие компиляторы на самом деле имеют эвристику для обнаружения случаев, когда переменная должна быть повторно прочитана и когда можно использовать кэшированную копию. Volatile дает понять компилятору, что каждый доступ к переменной должен фактически быть доступом к памяти. Без volatile компилятор может никогда не перечитывать переменную.

И, кстати, упаковка доступа в функцию не меняет этого, поскольку функция даже без inline может все еще быть встроена компилятором в текущую единицу компиляции.

P.S. Для C ++, вероятно, стоит проверить C89, на котором он основан. У меня под рукой нет C89.

12
ответ дан 30 November 2019 в 09:03
поделиться

Конечно, во втором примере запись / изменение переменной 'flag' опущено. Если он никогда не записывается, нет необходимости в том, чтобы он был изменчивым.

Относительно основного вопроса

Переменная все еще должна быть помечена как изменчивая, даже если каждый поток обращается / модифицирует ее с помощью одной и той же функции.

Функция может быть «активной» одновременно в нескольких потоках. Представьте, что код функции - это просто план, который поток берет и выполняет. Если поток B прерывает выполнение ReadFlag в потоке A, он просто выполняет другую копию ReadFlag (с другим контекстом, например, с другим стеком, другим содержимым регистров). И тем самым он может испортить выполнение ReadFlag в потоке A.

1
ответ дан 30 November 2019 в 09:03
поделиться

Да это критично.
Как вы сказали, volatile предотвращает оптимизацию нарушения кода в разделяемой памяти [C ++ 98 7.1.5p8] .
Поскольку вы никогда не знаете, какую оптимизацию может выполнять данный компилятор сейчас или в будущем, вы должны явно указать, что ваша переменная является изменчивой.

5
ответ дан 30 November 2019 в 09:03
поделиться

В языке C ключевое слово volatile здесь не требуется (в общем смысле).

Из спецификации ANSI C (C89), раздел A8.2 «Спецификаторы типа»:

Нет не зависящая от реализации семантика для летучих объектов.

Керниган и Ричи комментируют этот раздел (ссылаясь на спецификаторы const и volatile ):

За исключением того, что он должен диагностировать явные попытки изменить const объекты, компилятор может игнорировать эти квалификаторы.

Учитывая эти детали, вы не можете гарантировать, как конкретный компилятор интерпретирует ключевое слово volatile или игнорирует его вообще. Ключевое слово, которое полностью зависит от реализации, не должно считаться «обязательным» ни в какой ситуации.

При этом K&R также заявляет, что:

Цель volatile - заставить реализация для подавления оптимизация, которая иначе могла бы происходить.

На практике именно так практически каждый компилятор, который я видел, интерпретирует volatile . Объявите переменную как volatile , и компилятор не будет пытаться каким-либо образом оптимизировать доступ к ней.

В большинстве случаев современные компиляторы довольно хорошо оценивают, можно ли безопасно кэшировать переменную или нет. Если вы обнаружите, что ваш конкретный компилятор оптимизирует что-то, чего он не должен, тогда может быть уместным добавление ключевого слова volatile . Однако имейте в виду, что это может ограничить объем оптимизации, которую компилятор может выполнить для остальной части кода в функции, которая использует переменную volatile .Некоторые компиляторы справляются с этим лучше, чем другие; один встроенный компилятор C, который я использовал, отключил бы все оптимизации для функции, которая обращается к volatile , но другие, такие как gcc, похоже, все еще могут выполнять некоторые ограниченные оптимизации.

Доступ к переменной через функцию доступа должен предотвратить кэширование значения функцией. Даже если функция автоматически встроена, каждый вызов функции должен повторно вызывать функцию и повторно получать новое значение. Я никогда не видел компилятора, который автоматически вставлял бы функцию доступа, а затем оптимизировал бы повторную выборку данных. Я не говорю, что этого не может быть (поскольку это поведение зависит от реализации), но я бы не стал писать код, который ожидал бы, что это произойдет. Ваш второй пример, по сути, помещает API-оболочку вокруг переменной, и библиотеки делают это без постоянного использования volatile .

В целом, обработка энергозависимых объектов в C зависит от реализации. Согласно спецификации ANSI C89, в них нет ничего "гарантированного".

Ваш код разделяет объект volatile между потоком и обработкой прерывания. Никакая реализация компилятора (которую я когда-либо видел) не дает volatile достаточной мощности для обработки параллельного доступа. Вы должны использовать какой-то блокирующий механизм , чтобы гарантировать, что два потока (в вашем первом примере) не наступят друг другу на ноги (даже если один из них является обработчиком прерывания, вы все равно можете иметь параллельный доступ к многопроцессорная или многоядерная система).

1
ответ дан 30 November 2019 в 09:03
поделиться

Изменить: я не очень внимательно читал код, поэтому я подумал, что это вопрос о синхронизации потоков, для которой никогда не следует использовать volatile , однако такое использование выглядит так, как будто это может быть нормально ( В зависимости от того, как еще используется рассматриваемая переменная, и если прерывание всегда выполняется так, что его представление о памяти (кеш-) согласовано с тем, которое видит поток. В случае 'можете ли вы удалить volatile квалификатор, если вы заключите его в вызов функции? »принятый ответ правильный, вы не можете. Я собираюсь оставить свой исходный ответ, потому что людям, читающим этот вопрос, важно знать, что volatile почти бесполезен за пределами некоторых очень особых случаев.

Подробнее Правка: ваш вариант использования RTOS может потребовать дополнительной защиты помимо энергозависимости, вам может потребоваться использовать барьеры памяти в некоторых случаях или сделать их атомарными ... Я могу ' на самом деле сказать вам наверняка, это просто то, о чем вам нужно быть осторожным (я бы посоветовал Однако если посмотреть на ссылку на документацию ядра Linux, которую я привел ниже, Linux не использует volatile для подобных вещей, и, скорее всего, по уважительной причине). Отчасти то, когда вам нужен volatile , а когда он не нужен, очень сильно зависит от модели памяти процессора, на котором вы работаете, и часто volatile недостаточно.

volatile - НЕПРАВИЛЬНЫЙ способ сделать это, он НЕ гарантирует, что этот код будет работать, он не предназначен для такого использования.

volatile был предназначен для чтения / записи в регистры устройства с отображением в память, и как таковой его достаточно для этой цели, однако он НЕ помогает, когда вы говорите о вещах, перемещающихся между потоками. (В частности, компилятор все еще громко переупорядочивает некоторые операции чтения и записи, как и ЦП во время выполнения (это ДЕЙСТВИТЕЛЬНО важно, поскольку volatile не сообщает ЦП делать что-то особенное (иногда он означает обход кеша, но это зависит от компилятора / процессора))

см. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html , Статья разработчика Intel , CERT , Документация по ядру Linux

Краткая версия этих статей, volatile , использованная так, как вы хотите, является ПЛОХОЙ и НЕПРАВИЛЬНО. Плохо, потому что это сделает ваш код медленнее, неправильно, потому что на самом деле он не выполняет то, что вы хотите.

На практике на x86 ваш код будет правильно работать с или без volatile , однако он будет непереносимым.

РЕДАКТИРОВАТЬ: Заметка, чтобы самому прочитать код ... это что-то вроде изменчивого предназначен для выполнения.

0
ответ дан 30 November 2019 в 09:03
поделиться