Очередь IRP

Тема в разделе "WASM.NT.KERNEL", создана пользователем Mika0x65, 5 мар 2012.

  1. Mika0x65

    Mika0x65 New Member

    Публикаций:
    0
    Регистрация:
    30 июл 2005
    Сообщения:
    1.384
    Мое почтение всем.

    Переделываю сейчас очередь IRP для драйвера USB устройства. Хочется добиться максимальной гибкости и полного контроля над входящими и исходящими IRP. В книге у Они есть описание очереди IRP, но оно больше подходит для драйверов, находящихся на дне стека, т.е. работающих с оборудованием напрямую, т.к. отправляет запросы на DISPATCH_LEVEL. Решил попробовать создать свой вариант очереди:

    Код (Text):
    1. NTSTATUS TriageIrp(PDEVQUEUE pDevQueue, PIRP pIrp)
    2. {
    3.     NTSTATUS status;
    4.     KIRQL    Irql;
    5.  
    6.     KeAcquireSpinLock(&pDevQueue ->LockDevQueue, &Irql);
    7.  
    8.     status = STATUS_SUCCESS
    9.  
    10.     if (pDevQueue ->FailStatus != STATUS_SUCCESS)
    11.     {
    12.         status = pDevQueue ->FailStatus;
    13.     }
    14.     else if (pDevQueue ->ulStallCount)
    15.     {
    16.         if (pIrp ->MajorFunction != IRP_MN_START && pIrp ->MajorFunction != IRP_MJ_POWER) //Simplified a bit
    17.         {
    18.             CsqInsertIrpEx(&pDevQueue ->CsqHoldIrps, pIrp, NULL, NULL); //IRP is
    19. hold
    20.  
    21.             status = STATUS_PENDING;
    22.         }
    23.     }
    24.  
    25.     if (status == STATUS_SUCCESS)
    26.     {
    27.         InsertQueueIrp(pDevQueue, pIrp); //IRP is accepted and queued to
    28. accepted IRPs queue
    29.     }
    30.  
    31.     KeReleaseSpinLock(&pDevQueue ->LockDevQueue, Irql);
    32.  
    33.     return status;
    34. }
    35.  
    36. VOID StallQueue(PDEVQUEUE pDevQueue)
    37. {
    38.     KIRQL Irql;
    39.  
    40.     KeAcquireSpinLock(&pDevQueue ->LockDevQueue, &Irql);
    41.  
    42.     pDevQueue ->ulStallCount++;
    43.  
    44.     KeReleaseSpinLock(&pDevQueue ->LockDevQueue, Irql);
    45. }
    46.  
    47. VOID UnStallQueue(PDEVQUEUE pDevQueue)
    48. {
    49.     KIRQL Irql;
    50.     PIRP  pIrp;
    51.  
    52.     KeAcquireSpinLock(&pDevQueue ->LockDevQueue, &Irql);
    53.  
    54.     pDevQueue ->ulStallCount--;
    55.  
    56.     if (pDevQueue ->FailStatus != STATUS_SUCCESS)
    57.     {
    58.         //Abort hold IRPs
    59.     }
    60.  
    61.     if (!pDevQueue ->ulStallCount)
    62.     {
    63.         //Resend hold IRPs
    64.     }
    65.  
    66.     KeReleaseSpinLock(&pDevQueue ->LockDevQueue, Irql);    
    67. }
    68.  
    69. BOOLEAN WaitSentIrps(PDEVQUEUE pDevQueue)
    70. {
    71.     BOOLEAN bRes, bWait;
    72.  
    73.     KeAcquireSpinLock(&pDevQueue ->LockDevQueue, &Irql);
    74.  
    75.     bRes = FALSE;
    76.  
    77.     bWait = TRUE;
    78.  
    79.     if (pDevQueue ->ulStallCount)
    80.     {
    81.         bRes = TRUE;
    82.  
    83.         if (IsEmptyList(&pDevQueue ->ListAcceptedIrps))
    84.         {
    85.             bWait = FALSE;
    86.         }
    87.     }
    88.  
    89.     KeReleaseSpinLock(&pDevQueue ->LockDevQueue, Irql);
    90.  
    91.     //Here we have small window for race condition.
    92.     // It is possible only if MN_START arrives immediately with power down.
    93.     // Don't think it will ever happen.
    94.     if (bWait)
    95.     {
    96.         KeWaitForSingleObject(&pDevQueue ->EventStop);
    97.     }
    98. }
    Поясню, как работает очередь.

    Каждый обработчик 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.

    К чему я это все -- хотелось бы получить комментарии по коду. Может кто-то видит еще состояния состязания? Или есть предложения по улучшению кода? Буду рад любым комментариям.

    Заранее благодарен.