Спін-блокування - найпростіший механізм синхронізації. Спін-блокування може бути захоплена. і звільнена. Якщо спін-блокування була захоплена, подальша спроба захопити спін-блокування будь-яким потоком призведе до нескінченного циклу зі спробою захоплення спін-блокування (стан потоку 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).
Вирішення цієї проблеми дуже просте. Всі блокування, які можуть захоплюватися одночасно, поміщаються в список в порядку убування частоти використання. При необхідності захоплення блокувань вони повинні бути захоплені в тому порядку, в якому вони вказані в списку. Таким чином, ми створили ієрархію блокувань.