Мое почтение всем. Переделываю сейчас очередь IRP для драйвера USB устройства. Хочется добиться максимальной гибкости и полного контроля над входящими и исходящими IRP. В книге у Они есть описание очереди IRP, но оно больше подходит для драйверов, находящихся на дне стека, т.е. работающих с оборудованием напрямую, т.к. отправляет запросы на DISPATCH_LEVEL. Решил попробовать создать свой вариант очереди: Код (Text): NTSTATUS TriageIrp(PDEVQUEUE pDevQueue, PIRP pIrp) { NTSTATUS status; KIRQL Irql; KeAcquireSpinLock(&pDevQueue ->LockDevQueue, &Irql); status = STATUS_SUCCESS if (pDevQueue ->FailStatus != STATUS_SUCCESS) { status = pDevQueue ->FailStatus; } else if (pDevQueue ->ulStallCount) { if (pIrp ->MajorFunction != IRP_MN_START && pIrp ->MajorFunction != IRP_MJ_POWER) //Simplified a bit { CsqInsertIrpEx(&pDevQueue ->CsqHoldIrps, pIrp, NULL, NULL); //IRP is hold status = STATUS_PENDING; } } if (status == STATUS_SUCCESS) { InsertQueueIrp(pDevQueue, pIrp); //IRP is accepted and queued to accepted IRPs queue } KeReleaseSpinLock(&pDevQueue ->LockDevQueue, Irql); return status; } VOID StallQueue(PDEVQUEUE pDevQueue) { KIRQL Irql; KeAcquireSpinLock(&pDevQueue ->LockDevQueue, &Irql); pDevQueue ->ulStallCount++; KeReleaseSpinLock(&pDevQueue ->LockDevQueue, Irql); } VOID UnStallQueue(PDEVQUEUE pDevQueue) { KIRQL Irql; PIRP pIrp; KeAcquireSpinLock(&pDevQueue ->LockDevQueue, &Irql); pDevQueue ->ulStallCount--; if (pDevQueue ->FailStatus != STATUS_SUCCESS) { //Abort hold IRPs } if (!pDevQueue ->ulStallCount) { //Resend hold IRPs } KeReleaseSpinLock(&pDevQueue ->LockDevQueue, Irql); } BOOLEAN WaitSentIrps(PDEVQUEUE pDevQueue) { BOOLEAN bRes, bWait; KeAcquireSpinLock(&pDevQueue ->LockDevQueue, &Irql); bRes = FALSE; bWait = TRUE; if (pDevQueue ->ulStallCount) { bRes = TRUE; if (IsEmptyList(&pDevQueue ->ListAcceptedIrps)) { bWait = FALSE; } } KeReleaseSpinLock(&pDevQueue ->LockDevQueue, Irql); //Here we have small window for race condition. // It is possible only if MN_START arrives immediately with power down. // Don't think it will ever happen. if (bWait) { KeWaitForSingleObject(&pDevQueue ->EventStop); } } Поясню, как работает очередь. Каждый обработчик IRP вызывает ф-ию TriageIrp, которая либо решает отклонить IRP, в случае, если очередь остановлена (исключение делается только для IRP_MN_START & IRP_MJ_POWER), или необходимо отклонять все IRP с заданным статусом, либо принимает IRP и помещает его в очередь входящих. Все это делается атомарно. Для остановки очереди вызывается ф-ия StallQueue, которая просто увеличивает счетчик остановок. Для продолжения работы очереди вызывается ф-ия UnStallQueue, которая пересылает все остановленные до этого IRP обработчикам. После остановки очереди, вероятно, потребуется ф-ия WaitSentIrps, которая ждет, пока все IRP, находящиеся в обработке, не будут завершены. Остановка очереди потребуется, в принципе, только в двух случаях: 1. Получение MN_QUERY_STOP или MN_STOP. 2. При понижении электропитания. В случае остановки устройства, вызывается последовательность ф-ий StallQueue/WaitSentIrps. В случае понижения питания потребуется ф-ия StallQueue, ожидать завершения IRP, находящихся в обработке придется либо неблокирующим способом, либо отклонять запрос на понижение питания при наличии отосланных IRP. Продолжение работы очереди потребуется в двух случаях: 1. Получение MN_START. 2. Повышение электропитания. Для работы очереди необходима установка completion routine, которая извлечет IRP из очереди принятых и просигнализирует событие, если очередь пуста. Самый сложный момент в создании очереди -- race conditions. Если операции не будут атомарны, то могут возникнуть проблемы. Одну проблему я вижу: если после MJ_POWER с понижением питания будет передан MN_START, то может возникнуть такая ситуация: MJ_POWER: StallQueue() MN_START: UnStallQueue(); В этом случае вызов WaitSentIrps может увидеть, что очередь пуста (KeWaitForSingleObject выполняется после выхода из спинлока, я отметил это место комментарием) и вернуть TRUE. А очередь уже будет запущена. Но, насколько я понимаю, вероятность такого события практически нулевая -- запускать устройство во время понижения электропитания ОС не станет. Есть мысль сделать отдельное состояние очереди, обозначающее понижение питания, но не совсем понятно, что тогда делать при получении MN_START после понижающего MJ_POWER. К чему я это все -- хотелось бы получить комментарии по коду. Может кто-то видит еще состояния состязания? Или есть предложения по улучшению кода? Буду рад любым комментариям. Заранее благодарен.