NDIS 폴링 모드 개요
NDIS 폴링 모드는 네트워크 인터페이스 데이터 경로를 구동하는 OS 제어 폴링 실행 모델입니다.
이전에는 NDIS에서 데이터 경로 실행 컨텍스트에 대한 공식적인 정의가 없었습니다. NDIS 드라이버는 일반적으로 DPC(지연 프로시저 호출)에 의존하여 실행 모델을 구현했습니다. 그러나 DPC를 사용하면 긴 지시 체인이 만들어질 때 시스템에 과부하가 발생할 수 있으며, 이 문제를 피하기 위해서는 정확히 처리하기 어려운 많은 코드가 필요합니다. NDIS 폴링 모드는 DPC 및 유사한 실행 도구에 대한 대안 역할을 합니다.
NDIS 폴링 모드는 일정 결정의 복잡성을 NIC 드라이버에서 NDIS로 이동하며, 여기서 NDIS는 반복당 작업 제한을 설정합니다. 이 폴 모드는 다음을 제공합니다.
OS가 NIC에 다시 압력을 가하는 메커니즘입니다.
OS가 인터럽트를 세밀하게 제어하는 메커니즘입니다.
NDIS 폴링 모드는 NDIS 6.85 이상 미니포트 드라이버에서 사용할 수 있습니다.
DPC 모델의 문제
다음 시퀀스 다이어그램은 NDIS 미니포트 드라이버가 DPC를 사용하여 Rx 패킷의 버스트를 처리하는 방법의 일반적인 예를 보여 줍니다. 이 예제에서 하드웨어는 PCIe NIC 측면에서 표준입니다. 수신 하드웨어 큐와 해당 큐에 대한 인터럽트 마스크가 있습니다.
Rx 패킷 및 수신 하드웨어 큐가 있는 NDIS DPC 모델을 보여 주는
네트워크 활동이 없으면 하드웨어에 Rx 인터럽트를 사용하도록 설정됩니다. Rx 패킷이 도착하는 경우:
하드웨어는 인터럽트를 생성하고 NDIS는 드라이버의 ISR(MiniportInterrupt 함수)을 호출합니다.
드라이버는 매우 높은 IRQL에서 실행되기 때문에 ISR에서 거의 작업을 수행하지 않는다. 드라이버는 ISR에서 인터럽트를 사용하지 않도록 설정하고 하드웨어 처리를 MiniportInterruptDPC 함수(DPC)로 연기합니다.
NDIS는 결국 드라이버의 DPC를 호출하고, 드라이버는 하드웨어 큐에서 모든 완료를 처리하여 OS에 전달합니다.
드라이버가 DPC에 대한 I/O 작업을 연기할 때 네트워크 스택에 영향을 줄 수 있는 두 가지 문제는 다음과 같습니다.
드라이버는 시스템이 지정된 모든 데이터를 처리할 수 있는지 알지 못하므로, 드라이버는 하드웨어 큐에서 가능한 한 많은 데이터를 추출하여 상위 스택으로 올릴 수밖에 없습니다.
드라이버가 DPC를 사용하여 ISR에서 작업을 연기하기 때문에, 따라서 모든 표시는 DISPATCH_LEVEL로 이루어집니다. 이렇게 하면 긴 인디케이션 체인이 만들어질 때 시스템에 과부하가 걸리고 버그 검사 0x133 DPC_WATCHDOG_VIOLATION이 발생할 수 있습니다.
이러한 문제를 방지하려면 드라이버에서 많은 까다로운 코드가 필요합니다. DPC 워치독이 한계에 가까운지 KeQueryDpcWatchdogInformation 함수를 사용하여 확인할 수 있지만, 드라이버에 이를 바탕으로 인프라를 구축해야 합니다. 이를 위해서는 잠시 멈춘 후 패킷을 계속 표시하는 방법이 필요하며, 동시에 이러한 모든 작업을 데이터 경로의 수명과 동기화해야 합니다.
Poll 객체 소개
NDIS 폴링 모드는 DPC와 관련된 문제를 해결하기 위해 "Poll" 개체를 도입합니다. Poll 개체는 실행 컨텍스트 구문입니다. 미니포트 드라이버는 데이터 경로 작업을 처리할 때 DPC 대신 Poll 개체를 사용할 수 있습니다.
Poll 개체는 다음을 제공합니다.
NDIS가 반복당 작업 제한을 설정하는 방법을 제공합니다.
알림 메커니즘에 밀접하게 연결되어 있습니다. 이렇게 하면 작업을 처리해야 하는 경우와 관련하여 OS 및 NIC가 동기화됩니다.
반복 및 인터럽트 개념이 기본 제공되었습니다. DPC를 사용하는 경우 드라이버는 DPC를 완료할 때마다 인터럽트를 다시 사용하도록 설정해야 합니다. 폴 객체를 사용하는 경우, 드라이버는 각 폴링 반복 시마다 인터럽트를 다시 활성화할 필요가 없습니다. 폴 모드가 폴링이 완료되었음을 드라이버에게 알리고, 그때 인터럽트를 다시 활성화해야 하기 때문입니다.
예약 결정을 내릴 때 시스템은 DISPATCH_LEVEL 또는 PASSIVE_LEVEL 실행할지 여부를 스마트하게 결정할 수 있습니다. 이렇게 하면 서로 다른 NIC의 트래픽 우선 순위를 미세 조정하여 컴퓨터에서 더 공정한 워크로드 배포를 수행할 수 있습니다.
직렬화 보장이 있습니다. Poll 개체의 실행 컨텍스트 내에서 코드를 실행하면 동일한 실행 컨텍스트와 관련된 다른 코드가 실행되지 않습니다. 이렇게 하면 NIC 드라이버가 해당 데이터 경로의 잠금 없는 구현을 가질 수 있습니다.
NDIS 폴링 모드 모델
다음 시퀀스 다이어그램에서는 동일한 가상 PCIe NIC 드라이버가 DPC 대신 Poll 개체를 사용하여 Rx 패킷의 버스트를 처리하는 방법을 보여 줍니다.
Rx 패킷 및 수신 하드웨어 큐가 있는 NDIS 폴 모드를 보여주는
DPC 모델과 마찬가지로 Rx 패킷이 도착하면 하드웨어에서 인터럽트를 생성하고 NDIS는 드라이버의 ISR을 호출하고 드라이버는 ISR에서 인터럽트를 사용하지 않도록 설정합니다. 이 시점에서 폴링 모드 모델은 다음과 같이 다릅니다.
드라이버는 DPC를 큐에 대기하는 대신 ISR에서 Poll 개체 (이전에 만든 )을 큐에 대기하여 새 작업을 처리할 준비가 되었음을 NDIS에 알립니다.
향후 어느 시점에 NDIS가 드라이버의 폴링 반복 처리기을 호출하고 작업을 처리합니다. DPC와 달리, 드라이버는 하드웨어 큐에 준비된 요소 수만큼의 Rx NBL을 표시할 수 없습니다. 드라이버는 대신 처리기의 폴링 데이터 매개 변수를 확인하여 표시할 수 있는 최대 NBL 수를 가져와야 합니다.
드라이버가 최대 Rx 패킷 수를 다운로드하면, NBL을 초기화하고, 이를 폴링 처리기에서 제공하는 NBL 큐에 추가한 다음, 콜백에서 빠져나와야 합니다. 종료하기 전에 드라이버에서 인터럽트 기능을 사용하도록 설정하면 안 됩니다.
NDIS는 드라이버가 더 이상 전진하지 않는다고 평가될 때까지 드라이버를 계속 폴링합니다. 이 시점에서 NDIS는 폴링을 중지하고 드라이버에 인터럽트 다시 사용하도록요청합니다.
NDIS 폴 모드에 대한 표준화된 INF 키워드
NDIS 폴링 모드에 대한 지원을 사용하거나 사용하지 않도록 설정하려면 다음 키워드를 사용해야 합니다.
*NdisPoll 열거형 표준 INF 키워드는 다음과 같은 특성으로 표준화됩니다.
SubkeyName
INF 파일에서 지정해야 하며 레지스트리에 표시되는 키워드의 이름입니다.
ParamDesc
SubkeyName과 연결된 표시 텍스트입니다.
가치
목록의 각 옵션과 연결된 열거형 정수 값입니다. 이 값은 NDI\params\ SubkeyName\Value저장됩니다.
EnumDesc
메뉴에 표시되는 각 값과 연결된 표시 텍스트입니다.
기본값
메뉴의 기본값입니다.
| SubkeyName | ParamDesc | 가치 | EnumDesc |
|---|---|---|---|
| *NdisPoll | Ndis 폴링 모드 | 0 | 비활성화 |
| 1(기본값) | 활성화됨 |
열거형 키워드 사용에 대한 자세한 내용은 열거형 키워드참조하세요.
Poll 개체 만들기
Poll 개체를 만들기 위해 미니포트 드라이버는 MiniportInitializeEx 콜백 함수에서 다음을 수행합니다.
- 프라이빗 미니포트 컨텍스트를 할당합니다.
- NDIS_POLL_CHARACTERISTICS 구조를 할당하여 NdisPoll 및 NdisSetPollNotification 콜백 함수의 진입점을 지정합니다.
- NdisRegisterPoll호출하여 Poll 개체를 만들고 미니포트 컨텍스트에 저장합니다.
다음 예제에서는 미니포트 드라이버가 수신 큐 흐름에 대한 Poll 개체를 만드는 방법을 보여줍니다. 간단히 하기 위해 오류 처리는 생략됩니다.
NDIS_SET_POLL_NOTIFICATION NdisSetPollNotification;
NDIS_POLL NdisPoll;
NDIS_STATUS
MiniportInitialize(
_In_ NDIS_HANDLE NdisAdapterHandle,
_In_ NDIS_HANDLE MiniportDriverContext,
_In_ NDIS_MINIPORT_INIT_PARAMETERS * MiniportInitParameters
)
{
// Allocate a private miniport context
MINIPORT_CONTEXT * miniportContext = ...;
NDIS_POLL_CHARACTERISTICS pollCharacteristics;
pollCharacteristics.Header.Type = NDIS_OBJECT_TYPE_DEFAULT;
pollCharacteristics.Header.Revision = NDIS_POLL_CHARACTERISTICS_REVISION_1;
pollCharacteristics.Header.Size = NDIS_SIZEOF_NDIS_POLL_CHARACTERISTICS_REVISION_1;
pollCharacteristics.SetPollNotificationHandler = NdisSetPollNotification;
pollCharacteristics.PollHandler = NdisPoll;
// Create a Poll object and store it in the miniport context
NdisRegisterPoll(
NdisAdapterHandle,
miniportContext,
&pollCharacteristics,
&miniportContext->RxPoll);
return NDIS_STATUS_SUCCESS;
}
실행을 위해 Poll 개체를 큐에 추가하기
ISR에서 미니포트 드라이버는 NdisRequestPoll 호출하여 실행을 위해 Poll 개체를 큐에 추가합니다. 다음 예제에서는 수신 처리를 보여 주지만 간단히 하기 위해 인터럽트 줄의 공유를 무시합니다.
BOOLEAN
MiniportIsr(
KINTERRUPT * Interrupt,
void * Context
)
{
auto miniportContext = static_cast<MINIPORT_CONTEXT *>(Context);
auto hardwareContext = miniportContext->HardwareContext;
// Check if this interrupt is due to a received packet
if (hardwareContext->ISR & RX_OK)
{
// Disable the receive interrupt and queue the Poll
hardwareContext->IMR &= ~RX_OK;
NdisRequestPoll(miniportContext->RxPoll, nullptr);
}
return TRUE;
}
폴링 반복 처리기 구현
NDIS는 미니포트 드라이버의 NdisPoll 콜백을 호출하여 수신 표시를 확인하고 전송 완료를 점검합니다. NDIS는 먼저 드라이버가 NdisRequestPoll 호출하여 Poll 개체를 큐에 추가할 때 NdisPoll 호출합니다. NDIS는 드라이버가 수신 표시 또는 전송 완료에 대한 작업을 진행하는 동안 NdisPoll을 계속 호출합니다.
수신 지시를 위해 드라이버는 NdisPoll에서 다음을 수행해야 합니다.
- NDIS_POLL_DATA 구조체에서 수신 매개 변수를 확인하여 나타낼 수 있는 최대 NBL 수를 확인합니다.
- 최대 Rx 패킷 수를 가져옵니다.
- NBL을 초기화하십시오.
- NDIS_POLL_RECEIVE_DATA 구조에서 제공하는 NBL 큐에 추가합니다(NdisPollPollData 매개 변수의 NDIS_POLL_DATA 구조에 있음).
- 콜백을 종료합니다.
전송 완료의 경우 드라이버는 NdisPoll다음을 수행해야 합니다.
- NDIS_POLL_DATA 구조체의 전송 매개 변수를 확인하여 완료할 수 있는 최대 NBL 수를 가져옵니다.
- 최대 Tx 패킷 수를 가져옵니다.
- NBL을 완료합니다.
- NDIS_POLL_TRANSMIT_DATA 구조에서 제공하는 NBL 큐에 추가합니다(NdisPollPollData 매개 변수의 NDIS_POLL_DATA 구조에 있음).
- 콜백을 종료합니다.
드라이버는 NdisPoll 함수를 종료하기 전에 Poll 개체의 인터럽트 기능을 사용하도록 설정해서는 안 됩니다. NDIS는 진행이 없다고 판단될 때까지 드라이버를 지속적으로 검사합니다. 이 시점에서 NDIS는 폴링을 중지하고 드라이버에 인터럽트 다시 사용하도록요청합니다.
다음은 드라이버가 수신 큐 흐름에 NdisPoll 구현하는 방법입니다.
_Use_decl_annotations_
void
NdisPoll(
void * Context,
NDIS_POLL_DATA * PollData
)
{
auto miniportContext = static_cast<MINIPORT_CONTEXT *>(Context);
auto hardwareContext = miniportContext->HardwareContext;
// Drain received frames
auto & receive = PollData->Receive;
receive.NumberOfRemainingNbls = NDIS_ANY_NUMBER_OF_NBLS;
receive.Flags = NDIS_RECEIVE_FLAGS_SHARED_MEMORY_VALID;
while (receive.NumberOfIndicatedNbls < receive.MaxNblsToIndicate)
{
auto rxDescriptor = HardwareQueueGetNextDescriptorToCheck(hardwareContext->RxQueue);
// If this descriptor is still owned by hardware stop draining packets
if ((rxDescriptor->Status & HW_OWN) != 0)
break;
auto nbl = MakeNblFromRxDescriptor(miniportContext->NblPool, rxDescriptor);
AppendNbl(&receive.IndicatedNblChain, nbl);
receive.NumberOfIndicatedNbls++;
// Move to next descriptor
HardwareQueueAdvanceNextDescriptorToCheck(hardwareContext->RxQueue);
}
}
인터럽트 관리
미니포트 드라이버는 NdisSetPollNotification 콜백을 구현하여 Poll 개체와 연결된 인터럽트를 사용하거나 사용하지 않도록 설정합니다. NDIS는 일반적으로 미니포트 드라이버가 NdisPoll에서 진전하지 않는 것을 감지하면 NdisSetPollNotification 콜백을 호출한다. NDIS는 NdisSetPollNotification 사용하여 드라이버에 NdisPoll호출을 중지하도록 지시합니다. 드라이버는 새 작업을 처리할 준비가 되면 NdisRequestPoll 호출해야 합니다.
다음은 드라이버가 수신 큐 흐름에 NdisSetPollNotification 구현하는 방법입니다.
_Use_decl_annotations_
void
NdisSetPollNotification(
void * Context,
NDIS_POLL_NOTIFICATION * Notification
)
{
auto miniportContext = static_cast<MINIPORT_CONTEXT *>(Context);
auto hardwareContext = miniportContext->HardwareContext;
if (Notification->Enabled)
{
hardwareContext->IMR |= RX_OK;
}
else
{
hardwareContext->IMR &= ~RX_OK;
}
}