Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Сетевые драйверы Windows используют запросы OID для отправки контрольных сообщений в стек привязки NDIS. Драйверы протокола, такие как TCPIP или vSwitch, используют десятки OID для настройки каждой функции базового драйвера сетевого адаптера. До Windows 10 версии 1709 запросы OID отправляются двумя способами: регулярными и прямыми.
В этом разделе представлен третий стиль вызова OID: синхронный. Асинхронный вызов должен быть с низкой задержкой, неблокирующим, масштабируемым и надежным. Синхронный интерфейс запроса OID доступен начиная с NDIS 6.80, который входит в Windows 10 версии 1709 и более поздних версий.
Сравнение с обычными и прямыми запросами OID
При синхронных запросах OID, полезная нагрузка запроса (OID как такового) в точности совпадает с обычными и прямыми запросами OID. Единственное различие заключается в самом вызове. Таким образом, то, что одинаково для всех трех типов OID; только то, как отличается.
В следующей таблице описываются различия между регулярными OID, Direct OIDs и синхронными OID.
| Свойство | Стандартный OID | Прямой OID | Синхронный OID |
|---|---|---|---|
| Полезная нагрузка | NDIS_OID_REQUEST | NDIS_OID_REQUEST | NDIS_OID_REQUEST |
| Типы OID | Статистика, Запрос, Задать, Метод | Статистика, Запрос, Установить, Метод | Статистика, Запрос, Набор, Метод |
| Может быть выдано | Протоколы, фильтры | Протоколы, фильтры | Протоколы, фильтры |
| Может быть завершено кем-либо | Минипорты, фильтры | Минипорты, фильтры | Минипорты, фильтры |
| Фильтры могут изменяться | Да | Да | Да |
| NDIS выделяет память | Для каждого фильтра (клон OID) | Для каждого фильтра (клон OID) | Только если необычно большое количество фильтров (контекст вызова) |
| Может перо | Да | Да | нет |
| Может блокировать | Да | нет | нет |
| IRQL | == ПАССИВНЫЙ | <= DISPATCH | <= ДИСПЕТЧЕРИЗАЦИЯ |
| Сериализация выполнена NDIS | Да | нет | нет |
| Вызываются фильтры | Рекурсивно | Рекурсивно | Итеративно |
| Фильтры клонируют OID | Да | Да | нет |
Фильтрация
Как и другие два типа вызовов OID, драйверы фильтров имеют полный контроль над запросом OID в синхронном вызове. Драйверы фильтров могут наблюдать, перехватывать, изменять и выдавать синхронные OID-запросы. ** Однако ради повышения эффективности механизм синхронного OID несколько отличается.
Сквозное, перехват и инициация
Концептуально все запросы OID выдаются из более высокого драйвера и выполняются более низким драйвером. По пути запрос OID может пройти через любое количество драйверов фильтров.
В большинстве случаев драйвер протокола выдает запрос OID, и все фильтры просто передают запрос OID вниз без изменения. На следующем рисунке показан распространенный сценарий.
Однако любой модуль фильтра может перехватывать запрос OID и завершить его. В этом случае запрос не передается в более низкие драйверы, как показано на следующей схеме.
В некоторых случаях модуль фильтра может решить создать собственный запрос OID. Этот запрос начинается на уровне модуля фильтра и проходит только более низкие драйверы, как показано на следующей схеме.
Все запросы OID имеют этот базовый поток: более высокий драйвер (протокол или драйвер фильтра) выдает запрос и более низкий драйвер (минипорт или драйвер фильтра) завершает его.
Как работают обычные и прямые запросы OID
Регулярные или прямые запросы OID отправляются рекурсивно. На следующей схеме показана последовательность вызовов функции. Обратите внимание, что сама последовательность похожа на последовательность, описанную на схемах из предыдущего раздела, но упорядочена для отображения рекурсивного характера запросов.
Если установлено достаточное количество фильтров, NDIS будет вынужден выделить новый стек потоков для более глубокого рекурсивного вызова.
NDIS считает, что структура NDIS_OID_REQUEST допустима только для одного прыжка вдоль стека. Если драйвер фильтра хочет передать запрос до следующего нижнего драйвера (что является случаем для подавляющего большинства OID), драйвер фильтра должен вставить несколько десятков строк стандартного кода, чтобы клонировать запрос OID. Этот шаблон имеет несколько проблем:
- Она вынуждает выделить память для клонирования OID. При обращении к пулу памяти это происходит медленно и делает невозможным гарантировать движение вперед выполнения запроса OID.
- Структура OID должна оставаться одинаковой с течением времени, так как все драйверы фильтров жестко кодируют механизм копирования содержимого одного NDIS_OID_REQUEST в другой.
- Требование большого количества шаблонного кода скрывает, что фильтр на самом деле делает.
Модель фильтрации для синхронных запросов OID
Модель фильтрации для синхронных запросов OID использует синхронный характер вызова, чтобы решить проблемы, описанные в предыдущем разделе.
Обработчики задач и завершения
В отличие от обычных и прямых запросов OID, существует два перехватчика фильтра для синхронных запросов OID: обработчик проблем и полный обработчик. Драйвер фильтра может зарегистрировать ни один, один или оба перехватчика.
Вызовы проблем вызываются для каждого драйвера фильтра, начиная с верхней части стека вниз до нижней части стека. Вызов проблемы любого фильтра может остановить OID от продолжения вниз и завершить OID с помощью кода состояния. Если ни один фильтр не принимает решение перехватить OID, то OID достигает драйвера сетевого адаптера, который должен обработать OID синхронно.
После завершения OID вызываются функции завершения для каждого драйвера фильтра, начиная с места завершения в стеке и до самого верха стека. Функция полного вызова может осматривать или модифицировать запрос OID, а также осматривать или модифицировать код состояния завершения OID.
На следующей схеме показан типичный случай, когда протокол выдает синхронный запрос OID, а фильтры не перехватывают запрос.
Обратите внимание, что модель вызова для синхронных OID является итеративной. Благодаря этому использование стека ограничивается константой, устраняя необходимость когда-либо расширять стек.
Если драйвер фильтра перехватывает синхронный OID в обработчике запросов, OID не предоставляется более низким фильтрам или драйверу сетевого интерфейсного контроллера (NIC). Однако полные обработчики для более высоких фильтров по-прежнему вызываются, как показано на следующей схеме:
Минимальное выделение памяти
Регулярные и прямые запросы OID требуют, чтобы драйвер фильтра клонировал NDIS_OID_REQUEST. В отличие от этого, синхронные запросы OID не разрешены клонировать. Преимуществом этой структуры является то, что синхронные OID имеют меньшую задержку — запрос OID не клонируется многократно при прохождении по стеку фильтров, и возникает меньше возможностей для сбоев.
Однако это вызывает новую проблему. Если OID не может быть клонирован, где драйвер фильтра хранит свое состояние для каждого запроса? Например, предположим, что драйвер фильтра преобразует один OID в другой. На пути вниз по стеку фильтру необходимо сохранить старый OID. На этапе возврата по стеку фильтр должен восстановить старый OID.
Чтобы решить эту проблему, NDIS выделяет слот размером с указатель для каждого фильтра-драйвера, для каждого обрабатываемого синхронного OID-запроса. NDIS сохраняет этот слот в процессе вызова от обработчика инициирования фильтра к его обработчику завершения. Это позволяет обработчику событий сохранять состояние, которое затем используется обработчиком завершения. В следующем фрагменте кода показан пример.
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;
}
Аналогичным образом драйвер фильтра может выдавать новый запрос синхронного идентификатора OID, использующий только одну строку кода:
status = NdisFSynchronousOidRequest(binding->NdisBindingHandle, &oid);
В противоположность этому, драйвер фильтра, который должен выдать обычный или прямой OID, должен настроить асинхронный обработчик завершения и реализовать код, чтобы отличать завершения OID, относящиеся к самому драйверу, от завершений OID, которые он только что клонировал. Пример этого шаблона показан на примере стандартного запроса OID.
Совместимость
Хотя стили регулярных, прямых и синхронных вызовов используют одни и те же структуры данных, конвейеры не переходят к одному обработчику в мини-порте. Кроме того, некоторые OID нельзя использовать в некоторых конвейерах. Например, OID_PNP_SET_POWER требует тщательной синхронизации и часто заставляет минипорт выполнять блокирующие вызовы. Это затрудняет обработку в прямом обратном вызове OID и исключает его использование в синхронном обратном вызове OID.
Таким образом, как и в случае с запросами Direct OID, синхронные вызовы OID могут быть использованы только с подмножеством OID. В Windows 10 версии 1709 поддерживается только OID OID_GEN_RSS_SET_INDIRECTION_TABLE_ENTRIES, используемый в версии 2 технологии распределения нагрузки на стороне получения (RSSv2) в синхронном пути OID.
Реализация синхронных запросов OID
Дополнительные сведения о реализации синхронного интерфейса запроса OID в драйверах см. в следующих разделах: