Синхронизация — Архив WASM.RU
Если вы работаете с тредами или процессами и используете какой-нибудь вид IPC, вам может потребоваться синхронизация этих тредов или процессов, чтобы они работали друг с другом согласованно. Для синхронизации мы можем использовать следующее:
- критические секции
- объекты ядра, такие как
- события
- мутексы
- семафоры
- ожидающие таймеры
Критические секции не являются объектами ядра, как вы можете видеть. Их нельзя использовать для синхронизации процессов, их можно использовать тольк для синхронизации тредов внутри одного процесса.
Немного теории
Хорошо, немного теории... хмм... я не хочу писать тяжелый для восприятия теоретический материал, но думаю, что будет неплохо сначала сказать несколько слов.
Мы синхронизируем большей частью из-за доступов к ресурсам и некоторым другим вещам, таким как доступ к нереентерабельному коду. Если один тред собирается синхронизироваться с другим, он говорит операционной системе (OS), что хочет получить доступ к ресурсам, код или чему бы то ни было еще. OS помещает этот тред в спящее состояние, пока другой тред, у которого есть затребованный ресурс, не скажет OS, что этот ресурс свободен для других тредов. В этот момент ожидающий тред находится в критической секции (CS). Конечно, если ресурс свободен в момент запроса, тред получит доступ немедленно.
Обратите внимание: Я использовал слова: 'OS помещает этот тред в спящее состояние', это значит, что тред не будет тратить системное время, пока он ожидает ресурс. Это обратно так называемому 'busy waiting', когда тред ждет, но на это потребляется системное время. Смотрите код:
Код (Text):
_CSflag dd 012345678h ... @nextCheck: mov eax,012345678h ; загружаем ненулевое значение xchg eax,dword ptr [_CSflag] ; меняем со значением флага or eax,eax ; проверяем, сброшен ли CS jnz @nextCheck ; нет, тратим еще время OS ; начало критической секции xor eax,eax ; оставляя флаг CS равным нулю mov dword ptr [_CSflag],eax ; конец критической секции ... Один тред ждет в цикле, пока друго (только один) в критической секции. Взаимоисключение подтвержденно инструкцией xchg.CS может быть частью программы, которая работает с уникальными ресурсами, и если более, чем один тред работает с ней в одно и то же время, он может потерперь неудачу. Поэтому эта часть код 'критична', а треды, работающие с ней, должны 'взаимно исключать' друг друга при работе с этой критической секцией. При использовании IPC в вирусах, требуется синхронизировать каждый зараженный процесс.
Представьте, что у нас есть два треда в каждой зараженной системе. Первый ищет на HDD файлы, которые можно заразит, а второй - заражает их. У нас есть разделяемая область памяти, где 'ищущий' тред сохраняет найденные имена файлов, а 'заражающий' тред загружает их из этой области. В момент времени у нас может быть любое количество этих тредов. Мы должны убедиться, что только один 'ищущий' тред будет писать в защищенную память и только один 'заражающий' тред будет ждать, пока новые имена не будут загружены в память.
Windows - это преимущественная мультизадачная система, что означает то, что запущенные процессы переключаются тогда, когда им это говорит делать система. Зная это, мы никогда не можем знать, насколько быстры наши процессы. Создавая синхронизированный код, мы не можем предполагать скорость выполнения тредов.
Я хочу показать здесь возможную проблему 'писателей и читателей'. Вы можете посмотреть примеры (такие как этот), поискать умные книги или вирусы, использующие IPC, их много. Я покажу вам, что предлагают Windows для осуществления синхронизации.
Критические секции
Как я сказал, критические секции не являются объектами ядра и не могут быть использованны для синхронизации процессов. Посмотрите выше на 'busy-waiting' код. Я покажу тот же код, который использует критические секции.
Код (Text):
Перед использованием критической секции мы должны создать ее, используя: push указатель на структуру CRITICAL_SECTION call InitializeCriticalSection Функция ничего не возваращает Если у вас мультипроцессорная система, вы можете использовать: push spin count push указатель на структуру CRITICAL_SECTION call InitializeCriticalSectionAndSpinCount Функция ничего не возвращает CRITICAL_SECCTION struc DebugInfo dd ? LockCount dd ? RecursionCount dd ? OwningThread dd ? LockSemaphore dd ? SpinCount dd ? CRITICAL_SECCTION endsИтак, наша критическая секция создана, и мы можем использовать ее.
Код (Text):
разделяемый код ... push указатель на структуру CRITICAL_SECTION call EnterCriticalSection ; начало критической секции ... ; здесь может быть только один тред ;.. push указатель на структуру CRITICAL_SECTION call LeaveCriticalSection ; конец критической секции ...Код отличается от того, что бы приведен выше, только методо ожидания. Если в критической секции находится один тред, а другой хочет в нее попасть, OS помещает его в сон...
Если критическая секция нам больше не нужна, мы удаляем ее:
Код (Text):
push указатель на структуру CRITICAL_SECTION call DeleteCriticalSection Функция ничего не возвращаетТакже существует путь, чтобы узнать, можно ли войти в критическую секцию, так, чтобы OS не поместила тред в сон:
Функция возвращает ненулевое значение, если удалось войти в критическую секцию.Код (Text):
push указатель на структуру CRITICAL_SECTION call TryEnterCriticalSectionПоследняя функция, которая работает с CS - это SetCriticalSectionSpinCount. Если у вас есть мультипроцессорная система - идите и прочитайте ее описания. У меня такой системы нет, поэтому этот вопрос меня не волнует .
Я думаю, что для использования критических секций вышеприведенной информации более чем достаточно.
Синхронизация через объекты ядра
Предполагается, что все объекты ядра могут использоваться как синхронизационные. Это значит, что они могут находиться в сигнализирующем или несигнализирующем состоянии. Если объект не используется, то он находится в несигнализирующем состоянии. Любой тред, который хочет получить доступ к таком объекту, должен подождать, пока объект не будет просигнализирован (например, процессы и треды сигнализируются, когда оканчивают свою работу). Доступ к объектам ядра можно получить через хэндл, который вы получаете как результат работы какой-нибудь функции с примерным названием CreateXOBJECTX.
Код (Text):
Чтобы поместить тред в сон и подождать, пока объект будет просигнализирован, используйте следующий код: push время ожидания push хэндл наблюдаемого объекта call WaitForSingleObject push время ожидания push булевое значение - ждать всех push указатель на массив объектов, которые нужно ждать call WaitForMultipleObjects булевое значение - установка в TRUE(1) укажет функции ждать все хэндлы, в противном случае(0) функция возвратится после сигнализации одного объекта время ожидания - время в ms, которое функция будет ожидать сигнализацию возможный результат работы этих функций: WAIT_OBJECT_0 = 0 - мы все ждали этого... объект был просигнализирован - при использовании функции XMultipleX с установкой ждать один объект, мы получим индекс этого хэндла в переданном массиве WAIT_ABANDONED = 080h - смотри в главе о мутексах WAIT_TIMEOUT = 0102h - время ожидания вышло... сигнализации объекта не произошло WAIT_FAILED = -1 - вызов функции не удался... используйте GetLastError Теперь вы знаете, как ждать объект. Давайте взглянем на сами синхронизационные объекты.События
События - это простые объекты ядра, у которых нет специальных условий, при которых они переключаются в сигнализирующее состояние. Представьте, что один из процессов ждет события. Другой процесс может переключить событие с помощью функции SetEvent. Итак, рассмотрим это шаг за шагом...
Код (Text):
Мы можем создать событие следующим образом: push указатель на имя события push булевое значение - начальное состояние push булевое значение - режим события push указатель на аттрибуты безопасности call CreateEvent(A/W) начальное состояние - событие будет создано в сигнализирующем состоянии(1) или несигнализирующем(0) режим события - событие может переводиться в несигнализирующее состояние автоматически (после того, как будет выполнена какая-нибудь из функций WaitX) или вручную (мы должны десигнализировать объект самостоятельно) auto reset = 0 manual reset = 1 аттрибуты безопасности - я объясню это в другой статье...Если один процесс создает событие, другой может получить доступ к этому событию, используя его имя:
Код (Text):
push указатель на имя события push булевое значение - наследуется или нет push доступ call OpenEvent(A/W) булевое значение - если установлено в TRUE(1), хэндл события может быть унаследован другими созданными процессами доступ : EVENT_ALL_ACCESS = 01f0003h - у вас есть полный доступ к событию EVENT_MODIFY_STATE = 2 - вы можете использовать только функции SetEvent и ResetEvent SYNCHRONIZE = 0100000h - Windows NT: вы можете использовать только функции WaitX CreateEvent и OpenEvent возвращают хэндл событияЕсть три функции для работы с состоянием событий.
Код (Text):
Чтобы установить событие в сигнализирующее состояние: push хэндл события call SetEvent Чтобы установить событие в несигнализирующее состояние: push хэндл события call ResetEvent push хэндл события call PulseEvent Эта функция устанавливает событие, пробуждает ждущий тред, а затем сбрасывает событие. Если функция используется на событии, сбрасываемом вручную, все ждущие треды будут пробуждены, а если событие сбрасывается автоматически, то будет пробуждена только одна ветвь. Все эти функции возвращают TRUE(1) в случае успеха.Мутексы
Слово мутекс означает 'mutual exclusion' (взаимное исключение). Это один из легких в использовании и очень полезных синхронизационных объектов. Он похож на критические секции, но его можно использовать во взаимодействии между процессами.
Мы можем создать мутекс следующим образом:
Код (Text):
push указатель на имя мутекса push булевое значение - инциализация push указатель на SECURITY_ATTRIBUTES call CreateMutex(A/W) initialization - if TRUE(1) then mutex is created unsignalizated else mutex is avaiable for anyone инициализация - если TRUE(1), тогда мутекс создается несигнализированным, в противном случае он доступен кому угодно.Существующий мутекс мы можем открыть так:
Код (Text):
push указатель на имя мутекса push булевое значение - наследование наследование - если установлено в TRUE(1), хэндл события может быть унаследован другим созданными процессоми доступ : MUTEX_ALL_ACCESS = 01f0001h полный доступ к мутексу SYNCHRONIZE = 0100000h Windows NT: вы можете использовать только функции WaitX и функцию ReleaseMutex (о ней позже)Обе эти функции возвращают хэндл мутекса, если вызов функции прошел успешно.
Хорошо, предположим, что ваш тред создал мутекс и владеет им. Другие треды ждут мутекса одной из функций WaitX. Если мутекс вам больше не нужен - освободите его с помощью функции ReleaseMutex - один ждущий тред проснется и мутекс снова будет в несигнализированном состоянии. Вы можете освободить только тот мутекс, которым владеете.
Код (Text):
push хэндл мутекса call ReleasMutex В случае успеха функция возвращает ненулевое значениеМожет случиться, что какой-то тред не освободит мутекс и закончит работу. Такой мутекс считается заброшенным, и через некоторое время система проверит его и освободит.
Все о мутексах.
Семафоры
Семафоры - это очень мощные синхронизационные объекты. Сказано, что вы можете синхронизировать с их помощью все, что угодно. Как и мутексы, семафоры наблюдают за входом в критическую секцию, но разница заключается в том, что в одной критической секции может быть больше одного треда. Семафоры могут использоваться для ресурсов с ограниченным количеством.
Мы можем создать семафор следующим образом:
Код (Text):
push указатель на имя семафора push максимальное количество push начальное количество push указатель на SECURITY_ATTRIBUTES call CreateSemaphore(A/W) максимальное количество - максимальное количество тредов, которое может быть внутри критической секции начальное количество - начальное количество тредов внутри критической секцииКаждый раз, когда вход в критическую секцию осуществляется через семафоры, Windows уменьшает количество 'свободных ресурсов' - понижает количество доступов в критическую секцию. Если счетчик семафора равен нулю, вход в критическую секцию закрывается и входящий тред помещается в сон.
Код (Text):
Конечно, есть функция API, чтобы открыть существующий семафор: push указатель на имя семафора push булевое занчение - наследование push доступ call OpenSemaphore(A/W) наследование - если установлено в TRUE(1), хэндл событие может быть унаследовано другими созданными процессами доступ : SEMAPHORE_ALL_ACCESS = 01f0003h - полный доступ к семафору SEMAPHORE_MODIFY_STATE = 2 - позволяет использование ReleaseSemaphore SYNCHRONIZE = 0100000h - позволяет использовать функции WaitX Обе эти функции возвращают хэндл семафора в случае успехаЕсли нет надобности в использовании ресурса, за которым наблюдает семафор, используйте функцию ReleaseSemaphore, чтобы увеличить количество возможных доступов к ресурсу.
Код (Text):
push указатель на двойное слово - получаем предыдущее значение счетчика семафора push насколько увеличить значение семафора push хэндл семафора call ReleaseSemaphoreСемафоры находятся в сигнализирующем состоянии, если значение его счетчика не равно нулю, в противном случае он устанавливается в несигнализирующее состоянии и входящий тред помещается в сон, как я говорил раньше...
Все о семафорах.
Ждущие таймеры
Ждущие таймеры появились в Windows начиная с NT 4. До сих пор у нас были объекты, которые мы должны были вручную (как правило ) переключить в сигнализирующее состояние. Ждущие таймеры - это объекты, которые сигнализируют себя сами после некоторого периода времени. Давайте начнем...
Код (Text):
Ждущий таймер создается следующим образом: push указатель на имя push булевое значение - авто/ручной сброс push указатель на SECURITY_ATTRIBUTES call CreateWaitableTime(A/W) авто/ручной сброс - также, как в событиях Мы можем открыть существующий ждущий таймер так: push указатель на имя push булевое значение - наследование push доступ call OpenWaitableTimer(A/W) наследование - если установлено в TRUE(1), хэндл события может быть унаследован другими созданными процессами доступ : TIMER_ALL_ACCESS = 01f0002h - полный доступ SYNCHRONIZE = 0100000h - позволяет использование функций WaitX TIMER_MODIFY_STATE = 2 - доступ к SetWaitableTime и CancelWaitableTimer Обе функции возвращают хэндл ждущего таймера в случае успехаИтак, мы создали или открыли ждущий таймер, а теперь настоло время установить его.
Код (Text):
push булевое значение - продолжение push указатель на аргумент завершающей процедуры push указатель на завершающую процедуру push период push указатель на начальное время push хэндл ждущего таймера call SetWaitableTimer указатель на начальное время - вам потребуется указать, когда таймер сработает в первый раз. Дата должна быть в формате LARGE_INTEGER. период - устанавливает период срабатываний в наносекундах. указатель на завершающую процедуру - процедура, выполняющаяся при срабатывании таймера. void APCRoutine(LPVOID argToCompletionRoutine,DWORD dwTimerLowValue, DWORD dwTimerHighValue); продолжение - если вы установите этот параметр в TRUE(1), компьютер выйдет из спящего режима, если он в нем находился... <img src="styles/smiles_s/smile3.gif" class="mceSmilie" alt=":smile3:" title="Smile3 :smile3:"> Последний API... мы можем сбросить настройки любого ждущего таймера: push хэндл ждущего таймера call CancelWaitableTimerВсе о ждущих таймерах.
Напоследок
Я думаю, что большинство новых вирусов будут использовать какой-нибудь вид IPC, а значит и один из видов синхронизации. Эта статья дает только обзор того, что Windows предлагает для синхронизации, эта тема довольно сложна и не может быть полностью объяснена в одной или двух статьях... идите и синхронизируйте... © mort[MATRiX], пер. Aquila
Синхронизация
Дата публикации 27 июн 2002