none
Нюанс работы функции ObReferenceObjectByHandle? RRS feed

  • Общие обсуждения

  • С помощью этой функции хочу организовать обработку прерываний. Связать хендлер в драйвере с объектом в приложении.
    По запросу приложения, драйвер ожидает определённое прерывание и как оно срабатывает, так драйвер переводит объект приложения (event) в сигнальное состояние.
    Механизм без проблем работает с одним прерыванием.
    Но если во время ожидания прерывания 1, приходит прерывание 2, объект приложения всё равно переводится в сигнальное состояние.

    Приложение:
    HANDLE Event= CreateEvent(NULL, TRUE, FALSE, name);
    while (1){
      //передача переменных в драйвер
      Register_Interrupt(INT_QDATx, Event);
      //Ожидание
      WaitForSingleObject(Event, INFINITE);
      cout<<"Got Interrupt"<<endl;
      ResetEvent(Event);
    }
    Драйвер:
    typedef struct _DEVICE_EXTENSION{
      ...
      struct {
        handle handler1;
        handle handler2;
        ....
        } int_evt;
    } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
    //Для каждого прерывания создан свой хендлер, чтобы разные приложения могли одновременно ожидать разные прерывания.
    
    //IOCTL функция по запросу ожидания прерывания
    NTSTATUS DevCtrl_Register_Interrupt(...){
      ...
      switch(int){
        case INT_1:
          hEvent = &pDE->IntEvt.handle1;
        break;
        case INT_2:
          hEvent = &pDE->IntEvt.handle2;
        break;
      }
      ...
      Status= ObReferenceObjectByHandle(pInputBuf->m_hEvt, SYNCHRONIZE | EVENT_MODIFY_STATE, *ExEventObjectType, WdfRequestGetRequestorMode(Request), hEvent, NULL);
    //pInputBuf->m_hEvt и есть полученный от приложения Event
      ...
    }
    
    //обработка прерываний:
    MyDeviceEvtInterruptDpc(IN WDFINTERRUPT Interrupt,                        IN WDFDEVICE    Device){
      ...
      if(/*по битам регистра сработало первое прерывание*/){
        if (!KeSetEvent(pDE->IntEvt.handle1, 0, FALSE))
          ObDereferenceObject(pDE->IntEvt.handle1);
      }
      if(/*по битам регистра сработало второе прерывание*/){
        if (!KeSetEvent(pDE->IntEvt.handle2, 0, FALSE))
          ObDereferenceObject(pDE->IntEvt.handle2);
      }
      ....
    }
    В итоге допустим приложение прислало в драйвер Event и ожидает второе прерывание.
    Драйвер связал полученный Event (pInputBuf->m_hEvt) с помощью ObReferenceObjectByHandle с хендлером handle2, предназначенным для этого прерывания.
    Во время ожидания второго прерывания срабатывает первое прерывание, проверка по битам проходит и handle1 переводится в сигнальное состояние,
    а вместе с ним и Event приложения. И в итоге я на экране вижу сообщение "Got Interrupt", хотя ожидал другое прерывание.

    Адреса у присылаемых Event разные, адреса хендлеров драйвера разные. Хендлеры не равны между собой.
    Проблема в нюансах работы функции ObReferenceObjectByHandle?

    4 июня 2015 г. 14:18

Все ответы

  • Давно не занимался драйверами Windows, но могу сказать, что события для сигнала о прерывании на уровень пользователя не годятся. Используйте семафоры, они, по крайней мере, помнят, сколько раз их "взводили".

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    4 июня 2015 г. 17:04
  • Что-то не понятно у Вас с типами. Что это за "handle" в данной структуре?

    typedef struct _DEVICE_EXTENSION{
      ...
      struct {
        handle handler1;
        handle handler2;
        ....
        } int_evt;
    } DEVICE_EXTENSION, *PDEVICE_EXTENSION;

    Судя по контексту использования - это должно быть KEVENT? Тогда почему Вы отдаете функции KeSetEvent значение, а не указатель, как она того требует. Если же это указатель (PKEVENT), то почему функция ObReferenceObjectByHandle принимает ее адрес? Какой тогда тип у hEvent?

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    5 июня 2015 г. 11:20
  • Касательно событий:

    функция CreateEvent возвращает событие типа HANDLE. Поэтому я создал структуру с переменными этого типа. Просто маленькими буквами написал, пока вырезал ненужные куски кода из примера.

    KeSetEvent я отправляю указатель поле структуры pDE, а она как раз и есть указатель.

    MyDeviceEvtInterruptDpc(IN WDFINTERRUPT Interrupt,
                            IN WDFDEVICE    Device){
      ....
      PDEVICE_EXTENSION       pDE;
      pDE  = DeviceGetExtension(WdfInterruptGetDevice(Interrupt));
      ...
      if(/*по битам регистра сработало первое прерывание*/){
        if (!KeSetEvent(pDE->IntEvt.handle1, 0, FALSE))
          ObDereferenceObject(pDE->IntEvt.handle1);
      }
      .... 
    }

    Косяк в ObReferenceObjectByHandle признаю. hEvent был также просто HANDLE. Переделал вот так:

    NTSTATUS DevCtrl_Register_Interrupt(...){
      PKEVENT  *hEvent;
      ...
      switch(int){
        case INT_1:
          hEvent = pDE->IntEvt.handle1;
        break;
        case INT_2:
          hEvent = pDE->IntEvt.handle2;
        break;
      }
      ...
      Status= ObReferenceObjectByHandle(pInputBuf->m_hEvt, SYNCHRONIZE | EVENT_MODIFY_STATE, *ExEventObjectType, WdfRequestGetRequestorMode(Request), hEvent, NULL);
    //pInputBuf->m_hEvt и есть полученный от приложения Event
      ...
    }

    Касательно семафоров:
    Я так понимаю, что функция CreateEvent меняется на CreateSemaphore(NULL, 1, 1, NULL),
    KeSetEvent меняется на ReleaseSemaphore, а вот аналога для ResetEvent не могу найти.
    WaitForSingleObject остаётся и главное семафор между приложением и драйвером связывается всё той же ObReferenceObjectByHandle.
    Если путаются события, то почему не будут путаться семафоры?

    В любом случае как только будет доступ к железу, опробую оба варианта.
    Спасибо.
    5 июня 2015 г. 13:38
  • Давайте по порядку. Приложение уровня пользователя взаимодействует с объектами ядра посредством переменной типа HANDLE. На уровне ядра те же объекты доступны через указатели на структуры, причем для каждого объекта существует своя структура. Для событий - это KEVENT. Следовательно, Ваша DEVICE_EXTENSION должна хранить не HANDLE, а PKEVENT. Соответственно, значение, передаваемое ObReferenceObjectByHandle в выходном предпоследнем параметре должно быть указателем на PKEVENT, а не HANDLE.

    Что касается семафоров, для них Reset-ом выступает Wait-функция, также, как для событий с автосбросом. Мое предложение об использовании семафоров касается больше не Вашей проблемы, а корректного подсчета количества прерываний.


    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!


    • Изменено kosuke904 7 июня 2015 г. 11:52
    5 июня 2015 г. 19:23
  • Кстати, в вызове CreateSemaphore(NULL, 1, 1, NULL) нет никакого смысла, т.к. он превращает семафор в событие с автосбросом. Третий параметр задает максимальное количество установок семафора и передавать в нем 1 неразумно.

    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    7 июня 2015 г. 11:51
  • Спасибо большое за разъяснения на счёт взаимодействия приложения и драйвера.

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

    подправил драйвер и код теперь выглядит вот так:

    typedef struct _DEVICE_EXTENSION{
      ...
      struct {
        PKEVENT handler1;
        PKEVENT handler2;
        ....
        } int_evt;
    } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
    
    NTSTATUS DevCtrl_Register_Interrupt(...){
      ...
      PKEVENT  *hEvent;
      ...
      switch(int){
        case INT_1:
          hEvent = &pDE->IntEvt.handle1;
        break;
        case INT_2:
          hEvent = &pDE->IntEvt.handle2;
        break;
      }
      ...
      Status= ObReferenceObjectByHandle(pInputBuf->m_hEvt, SYNCHRONIZE | EVENT_MODIFY_STATE, *ExEventObjectType, WdfRequestGetRequestorMode(Request), hEvent, NULL);
      ...
    }
    MyDeviceEvtInterruptDpc(IN WDFINTERRUPT Interrupt,
                            IN WDFDEVICE    Device){
      ....
      PDEVICE_EXTENSION       pDE;
      pDE  = DeviceGetExtension(WdfInterruptGetDevice(Interrupt));
      ...
      if(/*по битам регистра сработало первое прерывание*/){
        KeSetEvent(pDE->IntEvt.handle1, 0, FALSE);
      }
      ....
    }

    в функции DPC пришлось убрать ObDereferenceObject, т.к. на третий раз срабатывания неожидаемого приложением прерывания я получал BSOD.
    если я правильно понимаю, то эта функция сначала уменьшает значение евента до нуля, а далее вообще его удаляет. И на третий раз KeSetEvent обращается уже к несуществующей переменной.

    Дальше начал проверку. Я уже обрадовался, что ошибка исправлена, но нет.
    1. Запросил прерывание 1 и приложение успешно его получила.
    2. Запросил прерывание 2.
    3. Во время ожидания прерывания 2, сработало прерывание 1, но приложение на это никак не отреагировало.
    4. Сработало прерывание 2, приложение получило об этом информацию и снова начало ждать прерывание 2 (бесконечный цикл).
    5. Во время ожидания прерывания 2, сработало прерывание 1, и приложение получило об этом информацию!!!

    т.е. прерывания 1 и 2 не путаются пока оба ни разу не переводились в сигнальное состояние. После этого начинают опять путаться.
    Пробовал возвращать ObDereferenceObject во второе прерывание и менял его на KeResetEvent не помогает.

    И тут заметил очень интересный момент, который не знаю как решить.
    в ObReferenceObjectByHandle я посылаю переменную полученную от приложения pInputBuf->m_hEvt. Эта структура находится как в драйвере, так и в приложении.
    И если я правильно понимаю, то поле m_hEvt в приложении должно быть HANDLE, а в драйвере PKEVENT. Без структуры обойтись не могу, т.к. в структуре есть и другие поля.
    Важен ли этот момент и как его можно решить?

    10 июня 2015 г. 12:22
  • в функции DPC пришлось убрать ObDereferenceObject, т.к. на третий раз срабатывания неожидаемого приложением прерывания я получал BSOD.

    Объект ядра (событие) принадлежит ядру, а не приложению или драйверу. Появление у объекта ядра нового клиента увеличивает в нем счетчик ссылок, когда все клиенты откажутся от объекта. счетчик ссылок будет = 0 и объект будет ядром уничтожен. Функция ObReferenceObjectByHandle увеличивает счетчик ссылок события, а функция ObDereferenceObject - уменьшает.

    Поэтому, пока событие Вам нужно, ни в коем случае ObDereferenceObject вызывать нельзя. BSOD Вы получили обратившись к уже уничтоженному объекту.

    т.е. прерывания 1 и 2 не путаются пока оба ни разу не переводились в сигнальное состояние. После этого начинают опять путаться.

    Покажите код создания событий в приложении, а также код передачи дескрипторов событий в драйвер.

    И если я правильно понимаю, то поле m_hEvt в приложении должно быть HANDLE, а в драйвере PKEVENT.

    Нет. Функция ObReferenceObjectByHandle делает из HANDLE, полученного от приложения, PKEVENT, чтобы событием можно было пользоваться и в драйвере.


    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    10 июня 2015 г. 14:18
  • Поэтому, пока событие Вам нужно, ни в коем случае ObDereferenceObject вызывать нельзя.

    Может вы тогда сможете подсказать как корректнее его использовать?
    Вот беру кусок кода из функции DPC:

    if(/*по битам регистра сработало первое прерывание*/){
       KeSetEvent(pDE->IntEvt.handle1, 0, FALSE);
      ObDereferenceObject(pDE->IntEvt.handle1);
    }
    Если данная проверка проходит в тот момент, когда приложение ожидает прерывание, то до ObDereferenceObject выполнялась ObReferenceObjectByHandle и счётчик сначала увеличивается, а потом уменьшается.
     А что делать когда данная проверка проходит в момент, когда приложение не ожидает прерывание? ObReferenceObjectByHandle не выполнялась, значит и ObDereferenceObject не должно выполняться.
     пробовал вводить такую проверку:
    if(/*по битам регистра сработало первое прерывание*/){
      if (!KeSetEvent(pDE->IntEvt.handle1, 0, FALSE))
        ObDereferenceObject(pDE->IntEvt.handle1);
    }
    В теории если keSetEvent переводит евент из несигнального состояния в сигнальное, то приложение ожидает прерывание и ObReferenceObjectByHandle выполнялась.
    Если евент и до KeSetEvent был в сигнальном состоянии, то приложение не ожидает прерывания и ObDereferenceObject выполнять не надо.
    На практике этот механизм корректно не работал.

    Покажите код создания событий в приложении, а также код передачи дескрипторов событий в драйвер.
    UINT32 Register_Interrupt(UINT32 InterruptName, HANDLE hEvent){
    
      MyIOCTLinterruptStruct InputBuf;
      InputBuf.m_nInt = InterruptName;
      InputBuf.m_hEvt = hEvent;
    
      if (InterruptName<0 &&InterruptName>7)
    	  return 3;
    
      DWORD nActualOutputSize = 0;
      BOOL bSuccess;
      bSuccess = ::DeviceIoControl(
    	  m_hDevice, 
    	  IOCTL_REGISTER_INTERRUPT,
    	  &InputBuf, sizeof(InputBuf),
    	  NULL, NULL,
    	  &nActualOutputSize,
    	  NULL);
    
      if(!bSuccess)
          return 2;
    
      return 1;
    }
    
    mainfunc(){
      ...
      HANDLE Event= CreateEvent(NULL, TRUE, FALSE, name);
      ...
      while (1){
        //передача переменных в драйвер
        Register_Interrupt(INT_QDATx, Event);
        //Ожидание
        WaitForSingleObject(Event, INFINITE);
        cout<<"Got Interrupt"<<endl;
        ResetEvent(Event);
      }
    }
    Функция ObReferenceObjectByHandle делает из HANDLE, полученного от приложения, PKEVENT

    понял, спасибо. Значит тип поля в структуре оставляю HANDLE.

    10 июня 2015 г. 15:10
  • Какая-то странная у Вас архитектура взаимодействия приложения и драйвера. Зачем в бесконечном цикле много раз отдавать дескриптор события драйверу? Это достаточно сделать один раз. А уж потом "погрузиться" в ожидание прерываний.

    Мне кажется, что логика должна быть такой: приложение, заинтересованное в получении событий о прерывании (прерываниях), регистрирует у драйвера нужное количество событий. Затем (если приложению больше нечего делать) засыпает в функции WaitForMultipleObjects (за точное название не ручаюсь, уточните в MSDN) до момента срабатывания какого-либо события и обрабатывает его. Если же приложение должно выполнять свои задачи, ожидание можно вынести в отдельный поток.

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


    Если сообщение помогло Вам, пожалуйста, не забудьте отметить его как ответ данной темы. Удачи в программировании!

    10 июня 2015 г. 21:00