Иллюстрированный самоучитель по программированию систем защиты

Сериализация

Механизмы синхронизации

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

Спин-блокировки

Спин-блокировка – простейший механизм синхронизации. Спин-блокировка может быть захвачена, и освобождена. Если спин-блокировка была захвачена, последующая попытка захватить спин-блокировку любым потоком приведет к бесконечному циклу с попыткой захвата спин-блокировки (состояние потока busy-waiting). Цикл закончится только тогда, когда прежний владелец спин-блокировки освободит ее. Использование спин-блокировок безопасно на мультипроцессорных платформах, то есть гарантируется, что, даже если ее запрашивают одновременно два потока на двух процессорах, захватит ее только один из потоков.

Спин-блокировки предназначены для защиты данных, доступ к которым производится на различных, в том числе повышенных уровнях IRQL. Теперь представим такую ситуацию: код, работающий на уровне IRQL PASSIVE_ LEVEL захватил спин-блокировку для последующего безопасного изменения некоторых данных. После этого код был прерван кодом с более высоким уровнем IRQL DISPATCH_LEVEL, который попытался захватить ту же спин-блокировку, и, как следует из описания спин-блокировки, вошел в бесконечный цикл ожидания освобождения блокировки. Этот цикл никогда не закончится, так как код, который захватил спин-блокировку и должен ее освободить, имеет более низкий уровень IRQL и никогда не получит шанса выполниться!

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

В NT имеется два вида спин-блокировок:

  • Обычные спин-блокировки, особым случаем которых являются спин-блокировки отмены запроса ввода/вывода, используемые при организации очередей запросов ввода/вывода (см. раздел "Отмена запросов ввода/вывода").
  • Спин-блокировки синхронизации прерываний.

С обычными спин-блокировками связан IRQL DISPATCH_LEVEL, то есть:

  • все попытки их захвата должны производиться на уровне IRQL, меньшим или равным DISPATCH_LEVEL;
  • в случае захвата спин-блокировки текущий уровень IRQL поднимается до уровня DISPATCH_LEVEL.

Со спин-блокировками синхронизации прерываний связан один из уровней DIRQL. Использование обычных спин-блокировок будет описано ниже (за исключением спин-блокировок отмены запросов ввода/вывода, которые были описаны в предыдущем разделе). Использование спин-блокировок синхронизации прерываний будет описано в разделе, посвященном обработке прерываний.

Использование обычных спин-блокировок:

  1. VOID KeInitializeSpinLock(IN PKSPIN_LOCK SpinLock);
    Эта функция инициализирует объект ядра KSPIN_LOCK. Память под спин-блокировку уже должна быть выделена в невыгружаемой памяти.
  2. VOID KeAcquireSpinLock(IN PKSPIN_LOGK SpinLock, OUT PKIRQL Oldlrql);
    Эта функция захватывает спин-блокировку. Функция не вернет управление до успеха захвата блокировки. При завершении функции уровень IRQL повышается до уровня DISPATCH_LEVEL. Во втором параметре возвращается уровень IRQL, который был до захвата блокировки (он должен быть <= DISPATCH_LEVEL).
  3. VOID KeReleaseSpinLock(IN PKSPINJLOCK SpinLock, OUT PKIRQL Newlrql);
    Эта функция освобождает спин-блокировку и устанавливает уровень IRQL в значение параметра Newlrql. Это должно быть то значение, которое вернула функция KeAcquireSpinLock() в параметре Oldlrql.
  4. VOID KeAcquireLockAtDpcLevel(IN PKSPIN_LOCK SpinLock);
    Эта оптимизированная функция захватывает спин-блокировку кодом, уже работающем на уровне IRQL DISPATCH_LEVEL. В этом случае изменение уровня IRQL не требуется. На однопроцессорной платформе эта функция вообще ничего не делает, так как синхронизация обеспечивается самой архитектурой IRQL.
  5. VOID KeReleaseLockFromDpcLevel(IN PKSPIN_LOCK SpinLock);
    Эта функция освобождает спин-блокировку кодом, захватившим блокировку с помощью функции KeAcquireLockAtDpcLevel(). На однопроцессорной платформе эта функция ничего не делает.

Пример использования обычных спин-блокировок:

typedef struct _DEVICE_EXTENSION
KSPIN_LOCK spinlock }DEVICE_EXTENSION, *PDEVICE_EXTENSION;
*
NTSTATUS DriverEntry (….)
KelnitializeSpinLock(&extension › spinlock); }
NTSTATUS DispatchReadWrite(…)
{
KIRQL Oldlrql;
KeAcquireSpinLock(&extension › spinlock, &01dlrql);
// произвести обработку данных,
// защищенных спин-блокировкой
KeReleaseSpinLock(&extension › spinlock, Oldlrql); }

Проблема взаимоблокировок (deadlocks)

Если поток попробует захватить спин-блокировку повторно, он войдет в бесконечный цикл ожидания – "повиснет". Такая же ситуация возникнет, если два потока используют две спин-блокировки. Поток 1 захватывает блокировку 1, одновременно с этим поток 2 захватывает блокировку 2. Затем поток 1 пробует захватить блокировку 2, а поток 2 – блокировку 1. Оба потока "виснут". Эту ситуацию можно распространить на произвольное число потоков, она широко известна и носит название взаимоблокировки (deadlocks).

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

Если Вы заметили ошибку, выделите, пожалуйста, необходимый текст и нажмите CTRL + Enter, чтобы сообщить об этом редактору.