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

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

Здравствуйте!

 

Прошу прощения за оффтоп(хотя ОС Windows CE), но проблема такова:

 

Пишу многопоточную программу под х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 приложения. Приложение запущено в пользовательском режиме (кольцо 3), cli и sti не советовать.

 

На всякий случай доп. информация (скорее всего не существенно):

Среда: Visual Studio 2005

ОС(целевая): Windows CE 6.0 R2

Процессор(целевой): AMD Geode LX800 (x86(набор инструкций P5), FPU, MMX, 3DNow!)

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


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

Команды процессора с точки зрения прерываний - атомарны. Т.е начатая команда обязательно будет выполнена до конца. Правда с учетом конвеерной обработки, не все так гладко. Возможно, завершение выполнения команды будет происходить тогда, когда процессор начнет вход в прерывание. Но, я думаю, это не должно волновать, так как завершение будет занимать доли/единицы тактов.

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


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

Если ваш код работает в защищенном режиме, то отчего бы не использовать команды запрета и разрешения прерываний? Вот так:

 

__asm{

push eax

cli

mov eax,A

add Count,eax

sti

pop eax

};

 

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

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


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

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

Копать нужно в сторону примитивов взаимоисключений.

Описанные проблемы с разделением ресурса несколькими потоками не новы. Вон у классика Дейтела описано несколько алгоритмов.

Под win я бы использовал объекты диспетчеризации ядра (семафоры, мьютексы и проч.). А для защищенного режима попробуйте команду BTS - Bit Test and Set (386+), вот она и является атомарной.

 

Правильному планировщику должно быть по боку, когда он забирает процессор у потока. Другими словами: отработал квант времени - вон с пляжа.

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


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

Команды процессора с точки зрения прерываний - атомарны. Т.е начатая команда обязательно будет выполнена до конца. Правда с учетом конвеерной обработки, не все так гладко. Возможно, завершение выполнения команды будет происходить тогда, когда процессор начнет вход в прерывание. Но, я думаю, это не должно волновать, так как завершение будет занимать доли/единицы тактов.

 

В свое время на BlackFin 537 столкнулся с следующей проблемой:

 

* * * *
volatile int* piReg_;
* * * *
*piReg_ = /* доступ к внешней памяти */

* * *

 

Так вот: если во время доступа к внешней памяти происходило прерывание, то вместо одного чтения из памяти, иногда получалось 2. К сожалению в причинах не разобрался - просто на время доступа стал отключать прерывания.

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


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

В свое время на BlackFin 537 столкнулся с следующей проблемой:

....

Так вот: если во время доступа к внешней памяти происходило прерывание, то вместо одного чтения из памяти, иногда получалось 2. К сожалению в причинах не разобрался - просто на время доступа стал отключать прерывания.

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

 

2 xenia: Никаких cli / sci в коде прикладных программ под ОС высокого уровня Windows/Linux быть не может, и это правильно - времена ДОСов прошли.

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


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

2 xenia: Никаких cli / sci в коде прикладных программ под ОС высокого уровня Windows/Linux быть не может, и это правильно - времена ДОСов прошли.

 

Не скажите :). Вы лично пробовали CLI/STI под WinXP или Vista? Да, это привилегированные команды, которые процессор выполняет только в ядре (0-ом кольце защиты), а в приложении они вызывают "исключение", т.е. прерывание по случаю попытки нарушения защиты. Абортируют ли эти команды приложение? А вот и нет! Исключение и в самом деле происходит, однако супервизор решает, что наружение можно простить :). Т.е. проверив инструкцию, вызвавшую прерывание (это делается легко, т.к. когда происходит прерывание или исключение, ее адрес лежит на стеке), адрес возврата будет увеличен на единичку, благодаря чему управление вернется на инструкцию, следующую за CLI/STI. В результате выполнение приложения продолжится, как будто этих инструкций не было.

 

Однако то, что я описала - это только механизм "прощения" такого нарушения, а в реальности супервизор может перед "прощением" повысить приоритет потоку, вызвавшему такое исключение, если нарушившая порядок инструкция была CLI. Или вернуть потоку нормальный приоритет, если это была инструкция STI.

 

Как видите, под управлением современных ОС высокого уровня, примитивы CLI/STI тоже могут работать, только не напрямую, а опосредованно. И это, между прочим, решает массу проблем! Например, аппаратные прерывания от таймера, мыши, DMA и прочего будет продолжать работать, а процесс вытеснения потоков и задач может быть временно приостановлен. Хотя он будет все равно запущен, если STI долго не придет.

 

Другое дело, что опираться на подобные механизмы можно только тогда, когда в точности знаешь реакцию данной ОС на CLI/STI.

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


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

Другое дело, что опираться на подобные механизмы можно только тогда, когда в точности знаешь реакцию данной ОС на CLI/STI.
И работать, мне кажется, они будут медленнее Interlocked*, уже "забаненных" в первом сообщении за свою за неторопливость.

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


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

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

 

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

А то факт, что вместо одного чтения из внешней памяти происходило два - могу объяснить только прерыванием текущей операции чтения из внешней памяти, выполнением ISR, и повторным выполнением операции чтения

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


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

Пишу многопоточную программу под х86 процессор (защищенный режим) на С++.

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

 

Под Виндами правильно пользоваться Interlocked функциями Win32 API, такими как InterlockedIncrement. Код будет гарантированно переносимыми на другие платформы.

 

Ассемблерная команда инкремента переменной в памяти будет атомарной с точки зрения ядра процессора, но не с точки зрения доступа по шине. Поэтому предлагаемое решение не будет работать на многоядерных процессорах. Правильное решение на x86 ассемблере - использовать команду с префиксом lock.

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


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

Правильное решение на x86 ассемблере - использовать команду с префиксом lock.

 

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

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


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

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

 

Инструкция, разумеется, должна быть одна, а именно, инкремента ячейки памяти командами lock add или lock xadd. Более сложные атомарные конструкции всегда можно сконструировать при помощи lock cmpxchg и цикла.

 

См. IA32 Instruction Set Reference

 

Кстати, борющимся за скорость будет полезно ознакомиться вот с этой статьей из MSDN

 

http://msdn.microsoft.com/en-us/library/2ddez55b(VS.71).aspx

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


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

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

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

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

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

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

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

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

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

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