Вот мой контрольный список для «застрявшего» спинлока:
- Убедитесь, что спин-блокировка была инициализирована с помощью KeInitializeSpinLock. Если KSPIN_LOCK содержит неинициализированный мусор, то первая попытка его получения будет вращаться вечно.
- Убедитесь, что вы не получаете его рекурсивно / вложенно. KSPIN_LOCK не поддерживает рекурсию, и если вы попробуете ее, она будет вращаться вечно.
- Обычные спин-блокировки должны быть получены в IRQL <= DISPATCH_LEVEL. Если вам нужно что-то, что работает в DIRQL, посмотрите <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/wdf/synchronizing-interrupt-code" rel="nofollow noreferrer"> [1] и [2] .
- Проверка на утечки. Если один процессор получает спин-блокировку, но забывает освободить ее, то следующий процессор будет вращаться вечно при попытке получить блокировку.
- Убедитесь, что нет проблем с безопасностью памяти. Если код случайным образом записывает ненулевое значение поверх спин-блокировки, это приведет к тому, что оно будет получено, а следующее получение будет вращаться вечно.
Некоторые из этих проблем могут быть легко и автоматически обнаружены с помощью Driver Verifier; используйте его, если вы его еще не используете. Другие проблемы могут быть обнаружены, если вы включите спин-блокировку в маленький помощник, который добавляет ваши собственные утверждения. Например:
typedef struct _MY_LOCK {
KSPIN_LOCK Lock;
ULONG OwningProcessor;
KIRQL OldIrql;
} MY_LOCK;
void MyInitialize(MY_LOCK *lock) {
KeInitializeSpinLock(&lock->Lock);
lock->OwningProcessor = (ULONG)-1;
}
void MyAcquire(MY_LOCK *lock) {
ULONG current = KeGetCurrentProcessorIndex();
NT_ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
NT_ASSERT(current != lock->OwningProcessor); // check for recursion
KeAcquireSpinLock(&lock->Lock, &lock->OldIrql);
NT_ASSERT(lock->OwningProcessor == (ULONG)-1); // check lock was inited
lock->OwningProcessor = current;
}
void MyRelease(MY_LOCK *lock) {
NT_ASSERT(KeGetCurrentProcessorIndex() == lock->OwningProcessor);
lock->OwningProcessor = (ULONG)-1;
KeReleaseSpinLock(&lock->Lock, lock->OldIrql);
}
Обертки вокруг KSPIN_LOCK распространены. KSPIN_LOCK походит на гоночный автомобиль, у которого удалены все дополнительные функции, чтобы максимизировать сырую скорость. Если вы не учитываете микросекунды, вы можете разумно добавить обратно подогрев сидений и FM-радио, завернув низкоуровневый KSPIN_LOCK во что-то подобное вышеописанному. (И с помощью магии #ifdefs вы всегда можете убрать подушки безопасности из ваших розничных сборок, если вам нужно.)