Поделиться через


Синхронный интерфейс запроса OID в NDIS 6.80

Сетевые драйверы Windows используют запросы OID для отправки управляющих сообщений в стек привязки NDIS. Драйверы протоколов, такие как TCPIP или vSwitch, используют десятки идентификаторов OID для настройки каждой функции базового драйвера сетевого адаптера. До Windows 10 версии 1709 запросы OID отправлялись двумя способами: обычными и прямыми.

В этом разделе представлен третий стиль вызова OID: Синхронный. Синхронный вызов должен быть низкой задержкой, неблокирующим, масштабируемым и надежным. Интерфейс запросов Synchronous OID доступен начиная с версии NDIS 6.80, которая входит в состав Windows 10 версии 1709 и более поздних версий.

Сравнение с обычными и прямыми запросами OID

При синхронных запросах OID полезные данные вызова (сам OID) точно такие же, как и в обычных и прямых запросах OID. Единственное отличие заключается в самом вызове. Таким образом, то, что одинаково для всех трех типов OID; только чем отличается.

В следующей таблице описаны различия между обычными идентификаторами OID, прямыми ИД и синхронными идентификаторами OID.

attribute Обычный идентификатор объекта OID Прямой OID Синхронный OID
Payload NDIS_OID_REQUEST NDIS_OID_REQUEST NDIS_OID_REQUEST
Типы OID Stats, Query, Set, Method Stats, Query, Set, Method Stats, Query, Set, Method
Может быть выдан с помощью Протоколы, фильтры Протоколы, фильтры Протоколы, фильтры
Может быть завершено с помощью Минипорты, фильтры Минипорты, фильтры Минипорты, фильтры
Фильтры могут изменяться Да Да Да
NDIS выделяет память Для каждого фильтра (клон OID) Для каждого фильтра (клон OID) Только при необычно большом количестве фильтров (контекст вызова)
Может выполняться Да Да Нет
Может блокировать Да Нет Нет
IRQL == ПАССИВНЫЙ <= DISPATCH <= DISPATCH
Сериализованный с помощью NDIS Да Нет Нет
Вызываются фильтры Рекурсивно Рекурсивно Итеративно
Клонирование идентификатора объекта OID фильтрами Да Да Нет

Фильтрация

Как и другие два типа вызовов OID, драйверы фильтров имеют полный контроль над запросом OID в синхронном вызове. Драйверы фильтров могут отслеживать, перехватывать, изменять и выдавать синхронные идентификаторы OID. Однако для эффективности механизм синхронного OID несколько отличается.

Сквозная передача, перехват и источник

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

В большинстве случаев драйвер протокола выдает запрос OID, и все фильтры просто передают запрос OID без изменений. На следующем рисунке показан этот распространенный сценарий.

Типичный путь OID происходит из протокола.

Однако любой модуль фильтра может перехватывать запрос OID и завершать его. В этом случае запрос не передается в более низкие драйверы, как показано на следующей схеме.

Типичный путь OID исходит из протокола и перехватывается фильтром.

В некоторых случаях модуль фильтра может решить создать собственный запрос OID. Этот запрос начинается на уровне модуля фильтра и проходит только по более низким драйверам, как показано на следующей схеме.

Типичный путь OID получен из фильтра.

Все запросы OID имеют такой базовый поток: более высокий драйвер (драйвер протокола или фильтр) выдает запрос, а более низкий драйвер (драйвер мини-порта или драйвер фильтра) завершает его.

Как работают обычные и прямые запросы OID

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

Последовательность вызовов функции для обычных и прямых запросов OID.

Если установлено достаточное количество фильтров, NDIS будет вынужден выделить новый стек потоков для более глубокой рекурсии.

NDIS считает структуру NDIS_OID_REQUEST допустимой только для одного прыжка вдоль стека. Если драйвер фильтра хочет передать запрос следующему более низкому драйверу (что относится к подавляющему большинству OID), драйвер фильтра должен вставить несколько десятков строк стандартного кода для клонирования запроса OID. Этот шаблон имеет несколько проблем:

  1. Он принудительно клонировать OID при выделении памяти. Попадание в пул памяти выполняется медленно и делает невозможным гарантировать выполнение запроса OID вперед.
  2. Структура OID должна оставаться неизменной с течением времени, так как все драйверы фильтров жестко кодируют механизм копирования содержимого одного NDIS_OID_REQUEST в другой.
  3. Требование так много шаблонов скрывает, что фильтр на самом деле делает.

Модель фильтрации для синхронных запросов OID

Модель фильтрации для синхронных запросов OID использует синхронный характер вызова для решения проблем, рассмотренных в предыдущем разделе.

Обработчики проблем и завершения

В отличие от обычных и прямых запросов OID существует два обработчика фильтров для синхронных запросов OID: обработчик проблем и обработчик завершения. Драйвер фильтра не может регистрировать ни один, ни один, ни оба перехватчика.

Вызовы проблем вызываются для каждого драйвера фильтра, начиная с верхней части стека и до нижней части стека. Любой вызов проблемы фильтра может остановить OID от продолжения вниз и завершить OID с помощью некоторого кода состояния. Если ни какой фильтр не решит перехватить OID, он достигает драйвера сетевого адаптера, который должен завершить его синхронно.

После завершения OID вызовы Complete вызываются для каждого драйвера фильтра, начиная с того места, где в стеке был завершен OID, вплоть до верхней части стека. Вызов Complete может проверять или изменять запрос OID, а также проверять или изменять код состояния завершения OID.

На следующей схеме показан типичный случай, когда протокол выдает синхронный запрос OID, а фильтры не перехватывают запрос.

Последовательность вызовов функции для синхронных запросов OID.

Обратите внимание, что модель вызова для синхронных идентификаторов OID является итеративной. Благодаря этому использование стека ограничивается константой, избавляя от необходимости когда-либо расширять стек.

Если драйвер фильтра перехватывает синхронный OID в обработчике проблем, OID не предоставляется более низким фильтрам или драйверу сетевого адаптера. Однако по-прежнему вызываются обработчики complete для более высоких фильтров, как показано на следующей схеме:

Последовательность вызовов функции для синхронных запросов OID с перехватом фильтром.

Минимальное выделение памяти

Для обычных и прямых запросов OID требуется драйвер фильтра для клонирования NDIS_OID_REQUEST. В отличие от этого, клонировать синхронные запросы OID не разрешено. Преимущество такой структуры заключается в том, что синхронные OID имеют меньшую задержку — запрос OID не клонируется повторно по мере перемещения по стеку фильтров, а также меньше возможностей для сбоя.

Тем не менее, это вызывает новую проблему. Если OID не может быть клонирован, где драйвер фильтра сохраняет его состояние для каждого запроса? Например, предположим, что драйвер фильтра преобразует один OID в другой. На пути вниз по стеку фильтр должен сохранить старый OID. При резервном копировании стека фильтру необходимо восстановить старый OID.

Чтобы решить эту проблему, NDIS выделяет слот размера указателя для каждого драйвера фильтра для каждого текущего синхронного запроса OID. NDIS сохраняет этот слот во время вызова от обработчика проблемы фильтра к его обработчику Complete. Это позволяет обработчику проблем сохранять состояние, которое позже используется обработчиком Complete. Фрагмент кода приведен ниже.

NDIS_STATUS
MyFilterSynchronousOidRequest(
  _In_ NDIS_HANDLE FilterModuleContext,
  _Inout_ NDIS_OID_REQUEST *OidRequest,
  _Outptr_result_maybenull_ PVOID *CallContext)
{
  if ( . . . should intercept this OID . . . )
  {
    // preserve the original buffer in the CallContext
    *CallContext = OidRequest->DATA.SET_INFORMATION.InformationBuffer;

    // replace the buffer with a new one
    OidRequest->DATA.SET_INFORMATION.InformationBuffer = . . . something . . .;
  }

  return NDIS_STATUS_SUCCESS;
}

VOID
MyFilterSynchronousOidRequestComplete(
  _In_ NDIS_HANDLE FilterModuleContext,
  _Inout_ NDIS_OID_REQUEST *OidRequest,
  _Inout_ NDIS_STATUS *Status,
  _In_ PVOID CallContext)
{
  // if the context is not null, we must have replaced the buffer.
  if (CallContext != null)
  {
    // Copy the data from the miniport back into the protocol’s original buffer.
    RtlCopyMemory(CallContext, OidRequest->DATA.SET_INFORMATION.InformationBuffer,...);
     
    // restore the original buffer into the OID request
    OidRequest->DATA.SET_INFORMATION.InformationBuffer = CallContext;
  }
}

NDIS сохраняет один PVOID для каждого фильтра за вызов. NDIS эвристически выделяет разумное количество слотов в стеке, чтобы в обычном случае не было выделений пулов. Обычно это не более семи фильтров. Если пользователь настраивает патологическое дело, NDIS возвращается к выделению пула.

Уменьшенный шаблон

Рассмотрим шаблон в примере шаблон для обработки обычных или прямых запросов OID. Этот код является стоимостью записи только для регистрации обработчика OID. Если вы хотите издать собственные OID, вам придется добавить еще дюжину строк шаблонов. При использовании синхронных OID нет необходимости в дополнительной сложности обработки асинхронного завершения. Таким образом, вы можете вырезать большую часть этого шаблона.

Ниже приведен минимальный обработчик проблем с синхронными идентификаторами OID:

NDIS_STATUS
MyFilterSynchronousOidRequest(
  NDIS_HANDLE FilterModuleContext,
  NDIS_OID_REQUEST *OidRequest,
  PVOID *CallContext)
{
  return NDIS_STATUS_SUCCESS;
}

Если вы хотите перехватить или изменить определенный OID, это можно сделать, добавив всего несколько строк кода. Минимальный обработчик Complete еще проще:

VOID
MyFilterSynchronousOidRequestComplete(
  NDIS_HANDLE FilterModuleContext,
  NDIS_OID_REQUEST *OidRequest,
  NDIS_STATUS *Status,
  PVOID CallContext)
{
  return;
}

Аналогичным образом драйвер фильтра может выдавать новый собственный запрос синхронного идентификатора идентификатора, используя только одну строку кода:

status = NdisFSynchronousOidRequest(binding->NdisBindingHandle, &oid);

В отличие от этого, драйвер фильтра, который должен выдавать обычный или прямой идентификатор OID, должен настроить асинхронный обработчик завершения и реализовать некоторый код, чтобы отличать собственные завершения OID от завершений OID, которые он только что клонировал. Пример этого шаблона приведен в разделе Пример шаблона для выдачи обычного запроса OID.

Совместимость

Хотя стили регулярных, прямых и синхронных вызовов используют одни и те же структуры данных, конвейеры не переходят к одному обработчику в мини-порте. Кроме того, некоторые идентификаторы OID нельзя использовать в некоторых конвейерах. Например, OID_PNP_SET_POWER требует тщательной синхронизации и часто заставляет мини-порт выполнять блокирующие вызовы. Это затрудняет обработку при прямом обратном вызове OID и предотвращает его использование в обратном вызове синхронного объекта OID.

Поэтому, как и в случае с прямыми запросами OID, синхронные вызовы OID можно использовать только с подмножеством идентификаторов OID. В Windows 10 версии 1709 в синхронном пути OID поддерживается только OID_GEN_RSS_SET_INDIRECTION_TABLE_ENTRIES OID, используемый в масштабировании на стороне получения версии 2 (RSSv2).

Реализация синхронных запросов OID

Дополнительные сведения о реализации синхронного интерфейса запроса OID в драйверах см. в следующих разделах: