Net 링으로 네트워크 데이터 수신

NetAdapterCx 클라이언트 드라이버는 프레임워크가 수신 큐에 대한 EvtPacketQueueAdvance 콜백 함수를 호출할 때 네트워크 데이터를 받습니다. 이 콜백 중에 클라이언트 드라이버는 수신된 조각 및 패킷을 OS로 드레이닝하여 수신을 표시한 다음 새 버퍼를 하드웨어에 게시합니다.

수신(Rx) 사후 및 드레이닝 작업 개요

다음 애니메이션은 간단한 PCI NIC(네트워크 인터페이스 카드)의 클라이언트 드라이버가 수신(Rx) 큐에 대해 사후 및 드레이닝 작업을 수행하는 방법을 보여 줍니다. 이 예제 시나리오의 조각 버퍼는 OS에서 조각 링에 할당되고 연결됩니다. 이 예제에서는 하드웨어 수신 큐와 OS 수신 큐 간의 일대일 관계를 가정합니다.

PCI 네트워크 인터페이스 카드 수신에 대한 넷 링 포스트 및 드레이닝 작업을 보여 주는 애니메이션입니다.

이 애니메이션에서는 클라이언트 드라이버가 소유한 패킷이 연한 파란색과 진한 파란색으로 강조 표시되고 클라이언트 드라이버가 소유한 조각은 노란색과 주황색으로 강조 표시됩니다. 밝은 색은 드라이버가 소유한 요소의 드레이닝 하위 섹션을 나타내고 어두운 색은 드라이버가 소유한 요소의 사후 하위 섹션을 나타냅니다.

순서대로 데이터 수신

다음은 패킷당 하나의 조각을 사용하여 순서대로 데이터를 수신하는 드라이버에 대한 일반적인 시퀀스입니다.

  1. NetRxQueueGetRingCollection을 호출하여 수신 큐의 링 컬렉션 구조를 검색합니다. 이를 큐의 컨텍스트 공간에 저장하여 드라이버의 호출을 줄일 수 있습니다. 링 컬렉션을 사용하여 수신 큐의 조각 링 및 패킷 링에 대한 드레이닝 반복기를 검색합니다.
  2. 순 링을 드레이닝하여 OS에 수신된 데이터를 나타냅니다.
    1. 조각 링의 현재 인덱스 및 패킷 링의 현재 인덱스를 추적하기 위해 UINT32 변수를 할당합니다. 이러한 변수를 링의 드레이닝 하위 섹션의 시작 부분인 해당 넷 링의 BeginIndex 로 설정합니다. 조각 링의 NextIndex로 설정하여 조각 링의 드레이닝 섹션 끝에 UINT32 변수를 할당합니다.
    2. 루프에서 다음을 수행합니다.
      1. 조각이 하드웨어에서 수신되었는지 확인합니다. 그렇지 않은 경우 루프를 중단합니다.
      2. NetRingGetFragmentAtIndex를 호출하여 조각을 가져옵니다.
      3. 일치하는 하드웨어 설명자에 따라 ValidLength와 같은 조각의 정보를 입력합니다.
      4. NetRingGetPacketAtIndex를 호출하여 이 조각에 대한 패킷을 가져옵니다.
      5. 패킷의 FragmentIndex 를 조각 링의 조각 현재 인덱스로 설정하고 조각 수를 적절하게 설정하여 패킷에 조각을 바인딩합니다(이 예제에서는 1로 설정됨).
      6. 필요에 따라 체크섬 정보와 같은 다른 패킷 정보를 입력합니다.
      7. NetRingIncrementIndex를 호출하여 조각 인덱스를 진행합니다.
      8. NetRingIncrementIndex를 호출하여 패킷 인덱스를 진행합니다.
    3. 조각 링의 BeginIndex 를 현재 조각 인덱스 변수로 업데이트하고 패킷 링의 BeginIndex 를 현재 패킷 인덱스로 업데이트하여 수신된 패킷과 해당 조각을 OS로 표시하도록 마무리합니다.
  3. 다음 수신을 위해 조각 버퍼를 하드웨어에 게시합니다.
    1. 현재 조각 인덱스를 링의 포스트 하위 섹션의 시작 부분인 조각 링의 NextIndex로 설정합니다. 조각 끝 인덱스를 조각 링의 EndIndex로 설정합니다.
    2. 루프에서 다음을 수행합니다.
      1. 조각의 정보를 일치하는 하드웨어 설명자에 게시합니다.
      2. NetRingIncrementIndex를 호출하여 조각 인덱스를 진행합니다.
    3. 조각 링의 NextIndex 를 현재 조각 인덱스 변수로 업데이트하여 하드웨어에 조각 게시를 완료합니다.

이러한 단계는 코드에서 다음과 같을 수 있습니다.

void
MyEvtRxQueueAdvance(
    NETPACKETQUEUE RxQueue
)
{
    //
    // Retrieve the receive queue's ring collection and net rings. 
    // This example stores the Rx queue's ring collection in its queue context space.
    //
    PMY_RX_QUEUE_CONTEXT rxQueueContext = MyGetRxQueueContext(RxQueue);
    NET_RING_COLLECTION const * ringCollection = rxQueueContext->RingCollection;
    NET_RING * packetRing = ringCollection->Rings[NET_RING_TYPE_PACKET];
    NET_RING * fragmentRing = ringCollection->Rings[NET_RING_TYPE_FRAGMENT];
    UINT32 currentPacketIndex = 0;
    UINT32 currentFragmentIndex = 0;
    UINT32 fragmentEndIndex = 0;

    //
    // Indicate receives by draining the rings
    //
    currentPacketIndex = packetRing->BeginIndex;
    currentFragmentIndex = fragmentRing->BeginIndex;
    fragmentEndIndex = fragmentRing->NextIndex;
    while(currentFragmentIndex != fragmentEndIndex)
    {
        // Test for fragment reception. Break if fragment has not been received.
        ...
        //

        NET_FRAGMENT * fragment = NetRingGetFragmentAtIndex(fragmentRing, currentFragmentIndex);
        fragment->ValidLength = ... ;
        NET_PACKET * packet = NetRingGetPacketAtIndex(packetRing, currentPacketIndex);
        packet->FragmentIndex = currentFragmentIndex;
        packet->FragmentCount = 1;

        if(rxQueueContext->IsChecksumExtensionEnabled)
        {
            // Fill in checksum info
            ...
            //
        }        

        currentFragmentIndex = NetRingIncrementIndex(fragmentRing, currentFragmentIndex);
        currentPacketIndex = NetRingIncrementIndex(packetRing, currentPacketIndex);
    }
    fragmentRing->BeginIndex = currentFragmentIndex;
    packetRing->BeginIndex = currentPacketIndex;

    //
    // Post fragment buffers to hardware
    //
    currentFragmentIndex = fragmentRing->NextIndex;
    fragmentEndIndex = fragmentRing->EndIndex;
    while(currentFragmentIndex != fragmentEndIndex)
    {
        // Post fragment information to hardware descriptor
        ...
        //

        currentFragmentIndex = NetRingIncrementIndex(fragmentRing, currentFragmentIndex);
    }
    fragmentRing->NextIndex = currentFragmentIndex;
}

순서가 잘못된 데이터 수신

Tx 큐와 달리 클라이언트 드라이버는 하드웨어 수신 큐당 하나의 OS 수신 큐가 있는 경우 일반적으로 데이터를 순서대로 수신하지 않습니다. 이는 클라이언트 드라이버의 NIC 유형에 관계없이 수행됩니다. 디바이스가 PCI 기반이고 OS가 수신 버퍼를 할당하고 소유하는지 또는 디바이스가 USB 기반이고 USB 스택이 수신 버퍼를 소유하는지 여부에 관계없이 클라이언트 드라이버는 수신된 각 조각에 대한 패킷을 초기화하고 OS에 표시합니다. 이 경우 순서는 중요하지 않습니다.

하드웨어가 하드웨어 수신 큐당 둘 이상의 OS 수신 큐를 지원하는 경우 수신 버퍼에 대한 액세스를 동기화해야 합니다. 이렇게 하는 scope 이 항목의 외부에 있으며 하드웨어 디자인에 따라 달라집니다.