Привет!
Почитал разные материалы про атомарные операции, никак не могу прийти к выводу в чем опасность чтения? При модификации все понятно - произошло прерывание или переключение задач в ОСРВ, которое вклинилось между инструкциями, данные испортятся.
А вот при чтении?
Возьмем функцию на Cortex-M3:
long long a;
void store(void)
{
a=0xDDCCFFEE12345678;
}
Ассемблерный код после компиляции такой:
0x00001314 493A LDR r1,[pc,#232] ; @0x00001400
0x00001316 483B LDR r0,[pc,#236] ; @0x00001404
0x00001318 4A3B LDR r2,[pc,#236] ; @0x00001408
0x0000131A E9C21000 STRD r1,r0,[r2,#0]
Если я располагаю этот код в одной задаче ОСРВ, а читаю допустим в другой, то ведь никаких проблем не будет т.к. переменная a меняется по последней инструкции? В чем подвох?
Надо смотреть, гарантируется ли непрерываемое выполнение STRD. STM точно может быть прервана.
Гарантируется. Рвутся только STM/LDM.
Понятно, спасибо.
А каким образом избегать ошибок неатомарного доступа при работе с ОСРВ на архитектуре Cortex-M3, какие механизмы ОСРВ использовать?
Допустим, у меня есть задача ОСРВ, которая вычитывает данные из АЦП. Далее эти данные как-то обрабатываются (по неким формулам) и затем доступ к этим данным (чтение) должны получить другие задачи ОСРВ. Как это лучше организовать? При этом, программируя на Си, получается неэффективно проверять все генерируемые компилятором ассемблерные инструкции, есть какой-то универсальный способ, чтобы не лазить в ассемблерный код?
andrewlekar
Aug 9 2013, 04:44
Не хотите лазить в ассемблерный код - защищайте всё межпоточное взаимодействие. Защищать можно семафорами/мютекасми, запретом прерываний, переписыванием кода на использование мэйлбоксов.
Rev0.0
Aug 12 2013, 07:07
Хорошо.
Мне просто интересен опыт как делаете вы в своих проектах, если в проекте используется RTOS. Вот у меня проект средней сложности (не знаю как описать) - как думаю будет несколько задач RTOS (7-8), принимаемые данные от АЦП (всего их 3) имеет смысл разделить и обрабатывать в разных задачах, часть из них независимые часть будет пересекаться. Как бы вы сделали?
1. С маилбоксами все хорошо, можно не плодить глобальные переменные, проект красиво будет выглядеть.
2. С глобальными переменными вроде контроллеру посвободнее будет.
andrewlekar
Aug 12 2013, 08:28
Я бы, для работы с АЦП, выделил отдельную задачу. Результаты бы складывал в локальный буфер:
static adc_measure_t adc_results[ADC_CHANNELS]. Для обращения к полученным значениям ADC сделал бы функцию геттер:
adc_task_get_adc_value(int channel). Внутри этой функции я бы делал блокировку, копирование результата в локальную переменную (если много, то memcpy) и разблокировку:
Код
lock(mutex)
int result = adc_results[channel];
unlock(mutex)
return result;
Да, задача ADC Task при складировании результатов от ацп тоже должна выполнять блокировку-разблокировку.
Rev0.0
Aug 13 2013, 12:25
andrewlekar, не совсем понял вашу идею. Результат АЦП обычно копируется в буфер прямо в прерывании, какой тогда смысл в вышеприведенной блокировке внутри задачи?
static int adc_buf[2];/* статический массив в модуле */
void ADC_isr(void)
{
...
adc_buf[0] = ADC_REG0;
adc_buf[1] = ADC_REG1;
...
}
а далее уже, наверное, можно сделать эту функцию-геттер (глобальную), которая будет возвращать результат в задаче:
int ADC_get_result(unsigned char channel)
{
int result;
result = adc_buf[channel];
return result;
}
Это я к тому что в общем случае ни ваш вариант ни мой не защищают изменение массива adc_buf в прерывании, когда какая-либо задача уже начала его читать, а атомарность обеспечивается лишь 32-битной архитектурой и соответствующими ассеблерными инструкциями.
В общем случае:
int ADC_get_result(unsigned char channel)
{
int result;
disable_interrupt();
result = adc_buf[channel];
enable_interrupt();
return result;
}
А если делать через мьютексы, то это нас не защитит т.к. на прерывания они не распространяются. Или я ошибаюсь?
amaora
Aug 13 2013, 14:27
Есть lock-free способы. Например как-то так, для одного писателя и одного читателя,
Код
struct data d[3], *w, *r;
writer ()
{
struct data *p;
for (i = 0; i < 3; ++i) {
p = d + i;
if (p != r && p != w)
break;
}
p->adc1 = ADC1;
p->adc2 = ADC2;
...
w = p;
}
read ()
{
r = w;
return r;
}
r = read ();
foo(r->adc1);
bar(r->adc2);
...
Но,
- Нужно время на отладку своей реализации, надо не забывать о соблюдении порядка доступа к памяти, как на уровне генерации кода так и при работе конвеера вычислителя
- Надежность блокировок может быть важнее производительности
- Неизвестно ещё, что будет быстрее
andrewlekar
Aug 13 2013, 17:29
У вас может и складируются в прерывании. У меня лично складируются в самой задаче.

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