다음을 통해 공유


NDIS 폴링 모드

NDIS 폴링 모드 개요

NDIS 폴링 모드는 네트워크 인터페이스 데이터 경로를 구동하는 OS 제어 폴링 실행 모델입니다.

이전에는 NDIS에서 데이터 경로 실행 컨텍스트에 대한 공식적인 정의가 없었습니다. NDIS 드라이버는 일반적으로 DPC(지연 프로시저 호출)에 의존하여 실행 모델을 구현했습니다. 그러나 긴 표시 체인이 만들어지고 이 문제를 방지하려면 제대로 하기 어려운 많은 코드가 필요할 때 DPC를 사용하면 시스템에 과부하가 발생할 수 있습니다. NDIS 폴링 모드는 DPC 및 유사한 실행 도구에 대한 대안을 제공합니다.

NDIS 폴링 모드는 일정 결정의 복잡성을 NIC 드라이버에서 NDIS로 이동하며, 여기서 NDIS는 반복당 작업 제한을 설정합니다. 이 폴링 모드를 달성하려면 다음을 제공합니다.

  1. OS가 NIC에 다시 압력을 가하는 메커니즘입니다.

  2. OS가 인터럽트를 세밀하게 제어하는 메커니즘입니다.

NDIS 폴링 모드는 NDIS 6.85 이상 미니포트 드라이버에서 사용할 수 있습니다.

DPC 모델의 문제

다음 시퀀스 다이어그램은 NDIS 미니포트 드라이버가 DPC를 사용하여 Rx 패킷의 버스트를 처리하는 방법의 일반적인 예를 보여 줍니다. 이 예제에서 하드웨어는 PCIe NIC 측면에서 표준입니다. 수신 하드웨어 큐와 해당 큐에 대한 인터럽트 마스크가 있습니다.

Rx 패킷 및 수신 하드웨어 큐가 있는 NDIS DPC 모델을 보여 주는 다이어그램

네트워크 활동이 없으면 하드웨어에 Rx 인터럽트를 사용하도록 설정됩니다. Rx 패킷이 도착하는 경우:

  1. 하드웨어는 인터럽트를 생성하고 NDIS는 드라이버의 ISR(MiniportInterrupt 함수)을 호출합니다.

  2. 드라이버는 매우 높은 IRQL에서 실행되기 때문에 ISR에서 거의 작동하지 않습니다. 드라이버는 ISR에서 인터럽트를 사용하지 않도록 설정하고 하드웨어 처리를 MiniportInterruptDPC 함수(DPC)로 연기합니다.

  3. NDIS는 결국 드라이버의 DPC를 호출하고 드라이버는 하드웨어 큐에서 모든 완료를 드레이닝하고 OS에 나타냅니다.

드라이버가 DPC에 대한 I/O 작업을 연기할 때 네트워크 스택에 영향을 줄 수 있는 두 가지 문제는 다음과 같습니다.

  1. 드라이버는 시스템이 표시되는 모든 데이터를 처리할 수 있는지 알지 못하므로 드라이버는 하드웨어 큐에서 가능한 한 많은 요소를 드레이닝하고 스택을 표시할 수밖에 없습니다.

  2. 드라이버가 DPC를 사용하여 ISR에서 작업을 연기하기 때문에 모든 표시는 DISPATCH_LEVEL. 이로 인해 긴 표시 체인이 만들어질 때 시스템에 과부하가 걸리고 버그 검사가 0x133 DPC_WATCHDOG_VIOLATION 발생할 수 있습니다.

이러한 문제를 방지하려면 드라이버에서 많은 까다로운 코드가 필요합니다. DPC Watchdog가 KeQueryDpcWatchdogInformation 함수를 사용하여 한도에 근접하고 DPC를 중단했는지 확인할 수 있지만 드라이버에서 이 문제를 중심으로 인프라를 빌드해야 합니다. 잠시 일시 중지한 다음 패킷을 계속 나타내야 하며, 동시에 이 모든 것을 데이터 경로의 수명과 동기화해야 합니다.

폴링 개체 소개

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에서 인터럽트를 사용하지 않도록 설정합니다. 이 시점에서 폴링 모드 모델은 다음과 같이 다릅니다.

  1. 드라이버는 DPC를 큐에 대기하는 대신 ISR에서 Poll 개체큐에 대기하여 새 작업을 처리할 준비가 되었음을 NDIS에 알립니다.

  2. 향후 NDIS에서 드라이버의 폴링 반복 처리기를 호출하여 작업을 처리할 수 있습니다. DPC와 달리 드라이버는 하드웨어 큐에 준비된 요소가 있는 만큼의 Rx NCL을 나타낼 수 없습니다. 드라이버는 대신 처리기의 폴링 데이터 매개 변수를 확인하여 표시할 수 있는 최대 NCL 수를 가져와야 합니다.

    드라이버가 최대 Rx 패킷 수를 가져오면 NBL을 초기화하고 폴링 처리기에서 제공하는 NBL 큐에 추가하고 콜백을 종료합니다. 종료하기 전에 드라이버에서 인터럽트 기능을 사용하도록 설정하면 안 됩니다.

  3. NDIS는 드라이버가 더 이상 진행되지 않는다고 평가할 때까지 드라이버를 계속 폴링합니다. 이 시점에서 NDIS는 폴링을 중지하고 드라이버에 인터럽트 다시 사용하도록 요청합니다.

NDIS 폴링 모드에 대한 표준화된 INF 키워드

NDIS 폴링 모드에 대한 지원을 사용하거나 사용하지 않도록 설정하려면 다음 키워드를 사용해야 합니다.

*NdisPoll 열거형 표준화된 INF 키워드에는 다음과 같은 특성이 있습니다.

SubkeyName
INF 파일에서 지정해야 하며 레지스트리에 표시되는 키워드의 이름입니다.

ParamDesc
SubkeyName과 연결된 표시 텍스트입니다.


목록의 각 옵션과 연결된 열거형 정수 값입니다. 이 값은 NDI\params\ SubkeyName 값에\저장됩니다.

EnumDesc
메뉴에 표시되는 각 값과 연결된 표시 텍스트입니다.

기본값
메뉴의 기본값입니다.

SubkeyName ParamDesc EnumDesc
*NdisPoll Ndis 폴링 모드 0 사용 안 함
1(기본값) 사용

열거형 키워드 사용에 대한 자세한 내용은 열거형 키워드를 참조 하세요.

Poll 개체 만들기

Poll 개체를 만들기 위해 미니포트 드라이버는 MiniportInitializeEx 콜백 함수에서 다음을 수행합니다.

  1. 프라이빗 미니포트 컨텍스트를 할당합니다.
  2. NdisPoll NdisSetPollNotification 콜백 함수의 진입점을 지정하는 NDIS_POLL_CHARACTERISTICS 구조를 할당합니다.
  3. 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에서 다음을 수행해야 합니다.

  1. NDIS_POLL_DATA 구조체의 수신 매개 변수를 확인하여 나타낼 수 있는 최대 NCL 수를 가져옵니다.
  2. 최대 Rx 패킷 수를 가져옵니다.
  3. NCL을 초기화합니다.
  4. NDIS_POLL_RECEIVE_DATA 구조에서 제공하는 NBL 큐에 추가합니다(NdisPoll PollData 매개 변수의 NDIS_POLL_DATA 구조에 있음).
  5. 콜백을 종료합니다.

전송 완료를 위해 드라이버는 NdisPoll에서 다음을 수행해야 합니다.

  1. NDIS_POLL_DATA 구조체의 전송 매개 변수를 확인하여 완료할 수 있는 최대 NCL 수를 가져옵니다.
  2. 최대 Tx 패킷 수를 가져옵니다.
  3. NCL을 완료합니다.
  4. NDIS_POLL_TRANSMIT_DATA 구조에서 제공하는 NBL 큐에 추가합니다(NdisPoll PollData 매개 변수의 NDIS_POLL_DATA 구조에 있음).
  5. 콜백을 종료합니다.

드라이버는 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; 
    } 
}