LINUX.ORG.RU

Вызов функции и барьеры памяти

 ,


0

1

Допустим, мы пишем код для микроконтроллера. У нас есть две функции - 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 из другого модуля, всё будет работать так же хорошо.

★★★★★

Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от anonymous

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

Соответственно, и проблемы те же самые. Если бы критическая секция была захватом mutex, а переменную дёргали два разных потока, то отсутствие барьера памяти привело бы к таким же проблемам.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Я не думаю, что меня кто-то серьёзный возьмёт на работу, если учесть что в моём резюме будет лишь пара проектов по фрилансу на Meteor, да самодельная РТОС для микроконтроллеров, которая пока ещё даже не готова и никем кроме меня не используется.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от KivApple

самодельная

Ну слава б-гу. Я уж плохого подумать про тебя успел.

Короче, у тебя каша в глове, успокойся и еще раз на свежую голову:

  • разберись как конпелирует код конпелятор (что он может оптимизировать/переупорядочивать, а что нет, почему и как это лечить)
  • разберись что и как переупорядочивает процессор и как с этим жить
  • разберись с атомарными операциями (у тебя сейчас, похоже, именно с этим косяк)
  • научись читать мануалы на процы, такие моменты часто разбираются с примерами в апендиксах или отдельных брошюрках типа «programmer's guide....»

А то так и будешь asm volatile("":::«memory») расставлять где надо и не надо в надежде на чудо.

anonymous
()
Ответ на: комментарий от anonymous

Это достаточно большой документ. Можно конкретно по моему вопросу? Судя по тому, что добавление volatile к переменным помогает, то проблема именно в генерации кода компилятором.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Алсо,

Мог ли компилятор посчитать

осиль objdump.

anonymous
()
Ответ на: комментарий от KivApple

Это достаточно большой документ.

лiл

Можно конкретно по моему вопросу?

Нет. Учись читать мануалы.

Судя по тому, что добавление volatile к переменным помогает, то проблема именно в генерации кода компилятором.

Нет.

anonymous
()
Ответ на: комментарий от KivApple

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

anonymous
()

У нас есть две функции - enterCriticalSection и leaveCriticalSection. Они запрещают и разрешают прерывания соответственно.

в твоем примере - обе запрещают

Вставил после запрета прерываний и перед их разрешением asm volatile("":::«memory») и убрал volatile в декларациях переменных. Результат тот же - всё отлично работает.

это барьер для компилятора так что компилятор скорей всего менял порядок доступа к памяти в твоем неработающем коде

anonymous
()
Ответ на: комментарий от anonymous

в твоем примере - обе запрещают

Опечатался. В реальном коде всё нормально.

Вопрос: вызов функции из другой единицы компиляции является барьером памяти для компилятора? То есть если я вызову эти функции в другом файле, то всё так же будет хорошо? Или он может таки закешировать переменные и всё сломать.

KivApple ★★★★★
() автор топика

а какой у тебя контроллер?

Harald ★★★★★
()
Ответ на: комментарий от KivApple

АБАНАМАТ!

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

Конпелятор ничего не знает про мемори барьеры.

Ты скажи луче, ты в принципе собираешься доку читать или так и будешь тупить на лоре? Чтоб силы на тебя больше не тратить.

anonymous
()

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

Harald ★★★★★
()
Ответ на: комментарий от Harald

Судя по всему я неправильно выразился. Просто когда гуглил свой вопрос встречал в некоторых местах, что это называли именно так.

Да, я имел ввиду именно кеширование переменной в регистрах. Является ли вызов функции из другого модуля поводом перечитать значение, если это не локальная переменная, а, например, член класса (но вызываемая функция не относится к нему никак)?

KivApple ★★★★★
() автор топика
Ответ на: АБАНАМАТ! от anonymous

Я уже читаю потихоньку. Можешь посоветовать ещё что-нибудь более общее, связанное больше с компиляторами, а не железками? Судя по всему (в первую очередь по ассемблерному листингу, где не появилось никаких команд синхронизации доступа после добавления этого asm) в данном случае проблема именно в непонимании данного момента.

Судя по активному возмущению, я назвал барьерами памяти не совсем то, что ими является.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

Вот тут читай как правильно это делается: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/ch01s03s...

Судя по активному возмущению, я назвал барьерами памяти не совсем то, что ими является.

И правильно возмущаются, потому что это базовые знания

связанное больше с компиляторами, а не железками

А это тебе вообще не поможет, потому что барьеры это спец инструкции в железе (dsb, dmb) и компилятор их сам никогда не генерирует. Ты должен или сам их написать в ассемблере или вызывать через intrinsic тогда когда это действительно нужно.
Ну и как тебе тут сказали уже, запретить прерывания недостаточно для синхронизации уже 20 лет как, ни на интеле ни на арме.

pftBest ★★★★
()

Напиши эту часть кода полностью на ассемблере, заодно поймешь как оно работает и 90% вопросов отпадут сами собой.

anonymous
()
Ответ на: комментарий от anonymous

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

anonymous
()

Поэтому мы не используем volatile при декларации переменной, чтобы зря не терять производительность.

Instant fail.

Как только я описал его поля как volatile - структура портится перестала.

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

Мог ли компилятор посчитать asm volatile(...) недостаточным условием для того, чтобы не использовать закешированные значения до его вызова?

Обычно компиляторы для микроконтроллеров генерируют код в «однопоточном» режиме, то есть пока ты не напишешь волшебное слово volatile, компилятор генерирует код для работы с переменной так, как будто у тебя нет других потоков и обработчиков прерываний.

Вставил после запрета прерываний и перед их разрешением asm volatile("":::«memory») и убрал volatile в декларациях переменных. Результат тот же - всё отлично работает.

Ну да, барьеры памяти помогают в подобных ситуациях, но не всегда.

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

Но ничто не помешает компилятору перетасовать инструкции и перекинуть обращение к какой-нибудь переменной через барьер.

>>подробности

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

shkolnick-kun ★★★★★
()

мы не используем volatile при декларации переменной, чтобы зря не терять производительность

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

anonymous
()
Ответ на: комментарий от shkolnick-kun

У меня есть такой вопрос... как можно сделать нормальную критическую секцию при использовании pthreads? Ну то есть гарантировать, что код внутри неё не будет вызван конкурентно и кеширование данных используемых внутри не случится снаружи (ну если только мы явно не создадим локальную переменную, которой присвоим значение до вхождения в критическую секцию).

Сейчас сделано так:

void CriticalSection::enter() {
	int r = pthread_mutex_lock(&m_mutex);
	assert(r == 0);
	asm volatile("":::"memory");
	__sync_synchronize();
}

void CriticalSection::leave() {
	__sync_synchronize();
	asm volatile("":::"memory");
	pthread_mutex_unlock(&m_mutex);
}

Меня не очень беспокоит производительность, потому что код в критической секции небольшой и его не планируется вызывать миллионы раз в секунду. На микроконтроллере я просто запрещаю прерывания + вставлю asm volatile("":::«memory»), а вот под Linux вышеприведённого кода недостаточно. Ибо односвязные списки всё равно иногда портятся, несмотря на то что любая модификация (добавление или удаление) происходит только между enter и leave.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

как можно сделать нормальную критическую секцию при использовании pthreads?

Внезапно

pthread_mutex_lock(...);
...
pthread_mutex_unlock(...);

Сейчас сделано так:

Как я и ванговал, бегаешь и расставляешь asm volatile() везде где ни попадя. Молодец, чо.

Ибо односвязные списки всё равно иногда портятся

Значит ошибка где-то еще.

anonymous
()
Ответ на: комментарий от KivApple

Обычно достаточно mutex_lock/_unlock и выключенной оптимизации (зачем она там, где есть Linux?).

Если в таких условиях что-то портится, значит надо искать ошибку, не связанную с критическими секциями.

Можно попробовать статический анализатор кода, или тесты какие написать...

Я вот сейчас собираюсь проанализировать BuguRTOS при помощи adlint (на самом деле уже, но у меня он старый, варнинги на отключаются, много мусора в отчете).

shkolnick-kun ★★★★★
()
Последнее исправление: shkolnick-kun (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.