Допустим, мы пишем код для микроконтроллера. У нас есть две функции - enterCriticalSection и leaveCriticalSection. Они запрещают и разрешают прерывания соответственно. При этом делают это по-умному - используя счётчик таким образом, чтобы позволить рекурсивные блокировки. То есть если вызвать два раза enterCriticalSection, то прерывания будут разрешены только лишь после второго вызова leaveCriticalSection.
Теперь допустим, что у нас есть некоторая переменная, с которой мы работаем в двух местах - в обработчике прерываний и в этой «критической секции» (в обработчике прерываний во время работы с переменной мы тоже входим в критическую секцию, на всякий пожарный).
Мы уверены, что пока мы в обработчике прерывания, нового такого же прерывания не будет. Также мы уверены, что пока мы в критической секции, прерывание не выполнится. Поэтому мы не используем volatile при декларации переменной, чтобы зря не терять производительность.
Сегодня я столкнулся с тем, что судя по всему компилятор таки оптимизировал обращения к этой переменной сильнее, чем надо. Точнее, я могу сделать такой вывод по косвенным признакам - портилась структура связанного списка (добавление/удаление элементов туда как раз и осуществляется в критической секции). Как только я описал его поля как volatile - структура портится перестала.
Функции входа и выхода выглядят так:
void enterCriticalSection() {
__asm__ volatile("cpsid i");
counter++;
}
void leaveCriticalSection() {
counter--;
if (counter == 0) {
__asm__ volatile("cpsid i");
}
}
Функции вызывались из той же единицы трансляции, так что вполне могли заинлайнится.
Мог ли компилятор посчитать asm volatile(...) недостаточным условием для того, чтобы не использовать закешированные значения до его вызова?
Вставил после запрета прерываний и перед их разрешением asm volatile("":::«memory») и убрал volatile в декларациях переменных. Результат тот же - всё отлично работает.
Хорошо. А как насчёт вызова данных функций из других единиц трансляции, когда компилятор не будет знать, что внутри них есть asm("":::«memory»)? Он ведь догадается, что при вызове функции из другой единицы трансляции со значениями в памяти может случиться всё что угодно? Или, быть может, её надо как-то пометить для этого?
Я задаю эти вопросы в двумя целями:
1) Убедиться, что проблема скорее всего была именно в этом, а это не просто совпадение и на самом деле ошибка осталась.
2) Убедиться, что при вызове enterCriticalSection из другого модуля, всё будет работать так же хорошо.