Перейти к содержанию
    

'volatile' или, все-таки, нет?

Приветствую!

 

Вот интересно... Допустим, у нас есть переменная, к которой осуществляется доступ как из фоновой задачи, так и из прерывания

volatile u32 GVar;

TmrISR()
{
  TmrOff();
  GVar = 1;
}

int main(void)
{
  GVar = 1;
  
  while(1)
  {
    if(GVar)
      GVar = 0, TmrOn();
  }
}

 

Все понятно, все логично - volatile нужен, так как сами обращения к переменной GVar есть в двух местах. И одно из них - код ISR. Код чисто как пример, это формальность.

 

А если, допустим, есть функция, работающая с глобальной переменной, и я использую эту функцию и в фоновом процессе, и в прерывании, должен ли я ставить volatile?
С точки зрения логики, ИМХО, нет. Так как вызов функции должен выполнять всегда одно и то же, а значит все операции с переменной будут одинаковы и в ISR, и в фоновом процессе.

 

Так ли это? Или, все-таки, оптимизация рано или поздно выстрелит в ногу?

 

Писал драйвер, и FSM этого драйвера находится в одной глобальной статической структуре.

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

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

Вот и думаю, оставлять volatile или нет.

 

ИМХО, если доступ в функции однократен, то volatile можно убрать.

Если в функции присутствует > 1 обращения к разделяемой переменной, она должна быть volatile.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

volatile нужно когда двойная запись в ячейку памяти даёт принципиально другой результат отличный от просто последней записи.
Т.е. если мы ногодрыгом делаем клок записывая 1 0 1 0 и т.д. то volatile обязательно. Если это просто ячейка памяти хранящяя последнюю температуру то не надо. 

Т.е. volatile это про то что все операции записи будут выполнены как бы бессмысленно с точки зрения оптимизатора они выглядели. И совершенно не имеет отношения к тому со скольки мест вы будете править переменную.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

5 минут назад, MegaVolt сказал:

Т.е. volatile это про то что все операции записи будут выполнены как бы бессмысленно с точки зрения оптимизатора они выглядели. И совершенно не имеет отношения к тому со скольки мест вы будете править переменную.

Это я понимаю. Но смотрите, допустим, есть вот такие функции

f1()
{
  GVar |= 1 << 0;
}

f2()
{
  GVar |= 1 << 1;
}

f3()
{
  GVar |= 1 << 2;
}

 

Разве обязан я тут объявлять GVar с volatile? ИМХО, нет.

Каждая из функций должна выполнить операцию доступа к памяти (чтение + запись).

 

А если я теперь, например, слинкую f1() как обработчик прерывания, разве что-то поменяется? ИМХО нет.

Компилятор по-прежнему должен осуществлять доступ на чтение + запись в каждой из функций.

А вот корректность результата такого кода на плечах программиста - это другой вопрос.

Т.е. по сути, формально, GVar стала изменчивой извне (в прерывании), но volatile тут, вроде бы, не нужен.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 минуты назад, Arlleex сказал:

Разве обязан я тут объявлять GVar с volatile? ИМХО, нет.

По моему тоже. Если нету реально многопоточности то проблем быть не должно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для синхронизации между потоками исполнения и прерываниями больше подходят барьеры. В исходном примере достаточно добавить memory fence в бесконечный цикл. Только данном случае важен не порядок обращения к памяти а то, что компилятор после барьера должен прочитать значения глобальных переменных снова, не помню как это правильно называется.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

6 минут назад, MegaVolt сказал:

По моему тоже. Если нету реально многопоточности то проблем быть не должно.

Даже если многопоточность будет.

Ну, например, f1(), f2() и f3() вызываются каждая из своего потока.

Каждая из них выполнит чтение + запись. И все.

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

Если же процесс читает переменную в каком-нибудь цикле внутри потока и эта переменная может измениться другим потоком (в том числе ISR), то да, volatile нужен.

Иначе, в целях оптимизации, чтение произойдет один раз в регистр, и цикл застрянет навечно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

33 минуты назад, Arlleex сказал:

А если, допустим, есть функция, работающая с глобальной переменной, и я использую эту функцию и в фоновом процессе, и в прерывании, должен ли я ставить volatile?
С точки зрения логики, ИМХО, нет. Так как вызов функции должен выполнять всегда одно и то же, а значит все операции с переменной будут одинаковы и в ISR, и в фоновом процессе.

А какая разница, как происходит обращение - через функцию или без? Скажем, компилятор в начале функции прочитает переменную в регистр, и будет работать с ним. А в середине функции - бац, прерывание, вызов функции, записали новое значение. Потом возврат, и функция записывает значение из регистра. И мы потеряли изменения, сделанные в прерывании.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 минуту назад, AHTOXA сказал:

А какая разница, как происходит обращение - через функцию или без? Скажем, компилятор в начале функции прочитает переменную в регистр, и будет работать с ним. А в середине функции - бац, прерывание, вызов функции, записали новое значение. Потом возврат, и функция записывает значение из регистра. И мы потеряли изменения, сделанные в прерывании.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 минуту назад, Arlleex сказал:

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

Да, просто volatile здесь не поможет. Здесь нужен atomic (который обычно подразумевает volatile + атомарность изменений).

volatile - это для регистров периферии.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну да, поэтому мне и стало интересно. Функции такие есть, а вот от лишнего синтаксического мусора свой код хотел бы освободить.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Добавлю, что в случае флага варианты типа

while (flag) {...};

и

bool getFlag() { return globalFlag; }

while (getFlag()) {...};

одинаковы - и там и там без volatile можно пролететь.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

36 минут назад, AHTOXA сказал:

Добавлю, что в случае флага варианты типа

...

одинаковы - и там и там без volatile можно пролететь.

В случае while(flag) {...}; (и любых вырожденных форм этой конструкции) понятно - в пределах одной функции будет множественный доступ. Тут volatile нужен.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

критическую секцию на край можно поставить (не в прерывании, а в фоне кода).

да, конечно это - костыль, но для подобного построения кода это вполне допустимо, особенно в крохотных и примитивных проектиках

тем более, если переменная имеет размер больше, чем слово проца, т. е. атомарно за раз его не прочитать или не записать

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

14 минут назад, Forger сказал:

критическую секцию на край можно поставить (не в прерывании, а в фоне кода).

да, конечно это - костыль, но для подобного построения кода это вполне допустимо, особенно в крохотных и примитивных проектиках

тем более, если переменная имеет размер больше, чем слово проца, т. е. атомарно за раз его не прочитать или не записать

Так речь не об атомарности - для ее обеспечения соответствующие методы есть, в том числе критические секции.

Речь шла насчет корректности применения volatile к изменяемому объекту, когда изменяется переменная не напрямую, а через вызов функции.

И в этой функции осуществляется однократный доступ к этой переменной.

 

Я сейчас в одном проекте реализую менеджер доступа к шине I2C. На ней висит пока что FRAM, но в будущем, возможно, что-то навесить еще придется.

Так вот чтобы осуществить транзакцию (из любого места, в том числе из прерывания), пользовательский код запрашивает свободный 'transfer slot'.

Эти слоты организованы в кольцевую очередь. По завершению транзакции автоматом происходит вычитка из этой очереди следующей транзакции (если есть).

 

Соответственно, у меня глобальная структура есть с текущими параметрами транзакции, указателем на статус выполняемой операции и т.д.

static struct
{
#define MNGACT_STOP    0
#define MNGACT_WORK    1
  volatile u8 act;
  volatile u8 mode;
  u8          serv;
  volatile u8 rw;
  u8          addr;
#define MNGFSM_SB      0
#define MNGFSM_RSB     1
#define MNGFSM_ADDRACK 2
#define MNGFSM_DATAACK 3
  volatile u8       fsm;
  volatile sI2CSOp *sop;
  
  struct
  {
    sI2CSDFRAM  tinf;
    volatile u8 addrw;
  }fram;
}BusMng;

 

Когда код немного разросся из обычных прямых обращений к полям этой структуры в выполнения законченных действий (функций), захотел обрезать volatile там, где это неуместно.

 

Собственно, вопрос считаю решенным, всем спасибо.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, Arlleex said:

Разве обязан я тут объявлять GVar с volatile? ИМХО, нет.

Каждая из функций должна выполнить операцию доступа к памяти (чтение + запись).

А если я теперь, например, слинкую f1() как обработчик прерывания, разве что-то поменяется? ИМХО нет.

 

если оптимизатор никогда не заинлайнит f1, а вдруг так бывает?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...