Атомарность операций

Низкоуровневое программирование портов, микроконтроллеров и т.д.

Модератор: Andy

Ответить
sysel
Сообщения: 2
Зарегистрирован: 02 окт 2009, 10:33

Здравствуйте!
Пишу многопоточную программу под х86 процессор (защищенный режим) на С++.
Есть глобальная переменная (назовём её Count) для взаимодействия двух потоков. (в обоих потоках присутствуют операции чтение, запись и чтение-модификация-запись (Count=Count+A)).
По логике программы операции чтение и запись не могут привести к нарушению работы, а вот чтение-модификация-запись - может.

Возникла идея реализовать операцию Count=Count+A в виде ассемблерной вставки, где операция выполняется инструкцией Add:
__asm{
push eax
mov eax,A
add Count,eax
pop eax
};
, т.к. код, генерируемый компилятором по умолчанию сводился к:
mov eax,Count
mov ebx,A
add eax,ebx ; опасная строка 1
mov Count,eax ; опасная строка 2

Все эти заморочки вызваны тем, что в любом месте моя программа может быть прервана планировщиком ОС. Т.е. если планировщик вклиниться между опасными командами 1 и 2, запустит на выполнение второй поток, который выполнит опасные команды 1 и 2, и опять запустит на выполнение первый поток, который выполнит опасную команду 2, возникнет ситуация "пепец".

Теперь внимание вопрос: если операция Count=Count+A в ассемблерном коде представлена одной процессорной командой Add, гарантирует ли это отсутствие ситуации "пепец" ?

Иными словами, можно ли ассемблерную инструкцию считать атомарной (выполняемой как единое целое) ?

Иными словами: планировщик современных ОС вклинивается между инструкциями x86 или между инструкциями внутреннего RISC-ядра процессора?

На всякий случай добавлю: пользоваться объектами синхронизации ОС (критические секции, Interlocked...) не вышло - очень замедляет работу моего RealTime приложения. Приложение запущено в пользовательском режиме (кольцо 4), cli и sti не советовать.

На всякий случай доп. информация (скорее всего не существенно):
Среда: Visual Studio 2005
ОС(целевая): Windows CE 6.0 R2
Процессор(целевой): AMD Geode LX800 (x86(набор инструкций P5), FPU, MMX, 3DNow!)
Аватара пользователя
somewhere
Сообщения: 1858
Зарегистрирован: 31 авг 2006, 17:14
Откуда: 71 RUS
Контактная информация:

Вообще, во избежание использования бубна и танцев с ним вокруг кода, в случае совместного использования переменных двумя потоками - рекомендуется вводить флаги модификаций. Или иными словами биты в переменной, где каждый бит отвечает за признак модификации конкретного потока. В вашем случае назначив бит 0 - 1-ому потоку, а бит 1 - 2-ому потоку можно с легкостью в любой момент времени установить - разрешено ли модифицировать Count или нельзя. Разрешено если оба бита сброшены, запрещено если хотя бы один из них установлен. Если биты 2-7 заранее обнулить, то просто достаточно проверять на равенство нулю перед каждой модификацией Count в каждом потоке. Если переменная = 0, то установить свой бит и модифицировать, затем сбросить бит.
&quot писал(а):Иными словами, можно ли ассемблерную инструкцию считать атомарной (выполняемой как единое целое) ?
Можно, но с поправками. В силу конвеерной оптимизации на ядрах современных процессоров бывает, что за один такт выполняется более 1 инструкции. В масштабе одного ядра такая оптимизация вам только на руку и опасные строки 1-2 выполнятся за 1 такт. Кстати, модификация одного и того же участка памяти двумя и более ядрами невозможна и аппаратно защищена. В этом случае приоритет имеет ядро, которое имеет меньший индекс, а остальные ждут пока адресная шина данных освободится.
&quot писал(а): Приложение запущено в пользовательском режиме (кольцо 4)
Кольца принято нумеровать 0,1,2,3 в соответствии со спецификацией дескриптора сегмента таблиц GDT, LDT
It's a long way to the top if you wanna rock'n'roll
sysel
Сообщения: 2
Зарегистрирован: 02 окт 2009, 10:33

somewhere писал(а):Вообще, во избежание использования бубна и танцев с ним вокруг кода, в случае совместного использования переменных двумя потоками - рекомендуется вводить флаги модификаций. Или иными словами биты в переменной, где каждый бит отвечает за признак модификации конкретного потока. В вашем случае назначив бит 0 - 1-ому потоку, а бит 1 - 2-ому потоку можно с легкостью в любой момент времени установить - разрешено ли модифицировать Count или нельзя.
Описанная Вами работа с флагами модификаций - тоже операции чтение-модификация-запись.
somewhere писал(а):
Можно, но с поправками. В силу конвеерной оптимизации на ядрах современных процессоров бывает, что за один такт выполняется более 1 инструкции.
Ключевое слово - БЫВАЕТ. А мне нужны гарантии, что всё будет ОК. К тому же вопрос: как ведёт себя конвеер при поступлении запроса на прерывание?
somewhere писал(а): Кольца принято нумеровать 0,1,2,3 в соответствии со спецификацией дескриптора сегмента таблиц GDT, LDT
Простите, опечатка. Моё приложение работает в 3 кольце.
Аватара пользователя
somewhere
Сообщения: 1858
Зарегистрирован: 31 авг 2006, 17:14
Откуда: 71 RUS
Контактная информация:

&quot писал(а):как ведёт себя конвеер при поступлении запроса на прерывание?
Конвеер должен гарантировать выполнение инструкций до начала нового такта. В случае прерывания кеш данных выполнит сброс, а это гарантирует правильное содержимое ячеек памяти перед началом его работы. А прерывание начнет работу, как только начнется новый такт - прервать работу инструкции оно не может. Отсюда вывод - изменение Count должно выполнятся одной инструкцией.
It's a long way to the top if you wanna rock'n'roll
Ответить