네트워크 드라이버의 동기화 및 알림

두 개의 실행 스레드가 유니프로세서 컴퓨터 또는 대칭 SMP(다중 프로세서) 컴퓨터에서 동시에 액세스할 수 있는 리소스를 공유할 때마다 동기화해야 합니다. 예를 들어 유니프로세서 컴퓨터에서 한 드라이버 함수가 공유 리소스에 액세스하고 ISR과 같은 더 높은 IRQL에서 실행되는 다른 함수에 의해 중단되는 경우 리소스를 확정되지 않은 상태로 두는 경합 상태를 방지하기 위해 공유 리소스를 보호해야 합니다. SMP 컴퓨터에서 두 스레드가 서로 다른 프로세서에서 동시에 실행되고 동일한 데이터를 수정하려고 시도할 수 있습니다. 이러한 액세스는 동기화되어야 합니다.

NDIS는 동일한 IRQL에서 실행되는 스레드 간에 공유 리소스에 대한 액세스를 동기화하는 데 사용할 수 있는 스핀 잠금을 제공합니다. 리소스를 공유하는 두 스레드가 서로 다른 IRQL에서 실행되는 경우 NDIS는 공유 리소스에 대한 액세스를 직렬화할 수 있도록 하위 IRQL 코드의 IRQL을 일시적으로 발생시키는 메커니즘을 제공합니다.

스레드가 스레드 외부의 이벤트 발생에 따라 달라지면 스레드는 알림을 사용합니다. 예를 들어 디바이스를 검사 수 있도록 일정 기간이 지난 경우 드라이버에 알림을 받아야 할 수 있습니다. 또는 NIC(네트워크 인터페이스 카드) 드라이버가 폴링과 같은 주기적인 작업을 수행해야 할 수 있습니다. 타이머는 이러한 메커니즘을 제공합니다.

이벤트는 두 개의 실행 스레드가 작업을 동기화하는 데 사용할 수 있는 메커니즘을 제공합니다. 예를 들어 미니포트 드라이버는 디바이스에 기록하여 NIC에서 인터럽트 테스트를 수행할 수 있습니다. 드라이버는 작업이 성공했음을 드라이버에 알리기 위해 인터럽트를 기다려야 합니다. 이벤트를 사용하여 인터럽트 완료를 기다리는 스레드와 인터럽트를 처리하는 스레드 간에 작업을 동기화할 수 있습니다.

이 항목의 다음 하위 섹션에서는 이러한 NDIS 메커니즘에 대해 설명합니다.

스핀 잠금

스핀 잠금은 유니프로세서 또는 다중 프로세서 컴퓨터의 IRQL > PASSIVE_LEVEL 실행되는 커널 모드 스레드에서 공유하는 리소스를 보호하기 위한 동기화 메커니즘을 제공합니다. 스핀 잠금은 SMP 컴퓨터에서 동시에 실행되는 다양한 실행 스레드 간의 동기화를 처리합니다. 스레드는 보호된 리소스에 액세스하기 전에 스핀 잠금을 획득합니다. 스핀 잠금은 스레드를 유지하지만 스핀 잠금을 보유하는 스레드는 리소스를 사용하지 않습니다. SMP 컴퓨터에서 스핀 잠금을 기다리는 스레드는 잠금을 보유하는 스레드에서 스핀 잠금을 해제할 때까지 스핀 잠금을 획득하려고 시도합니다.

스핀 잠금의 또 다른 특징은 연결된 IRQL입니다. 스핀 잠금을 획득하려고 시도하면 요청 스레드의 IRQL이 스핀 잠금과 연결된 IRQL로 일시적으로 발생합니다. 이렇게 하면 동일한 프로세서의 모든 하위 IRQL 스레드가 실행 중인 스레드를 선점하지 못하게 됩니다. 동일한 프로세서에서 더 높은 IRQL에서 실행되는 스레드는 실행 스레드를 선점할 수 있지만 이러한 스레드는 IRQL이 낮기 때문에 스핀 잠금을 획득할 수 없습니다. 따라서 스레드가 스핀 잠금을 획득한 후에는 다른 스레드가 해제될 때까지 스핀 잠금을 획득할 수 없습니다. 잘 작성된 네트워크 드라이버는 스핀 잠금이 유지되는 시간을 최소화합니다.

스핀 잠금의 일반적인 용도는 큐를 보호하는 것입니다. 예를 들어 미니포트 드라이버 송신 함수인 MiniportSendNetBufferLists는 프로토콜 드라이버가 전달한 패킷을 큐에 대기할 수 있습니다. 다른 드라이버 함수도 이 큐를 사용하므로 MiniportSendNetBufferLists 는 한 번에 하나의 스레드만 링크 또는 콘텐츠를 조작할 수 있도록 스핀 잠금으로 큐를 보호해야 합니다. MiniportSendNetBufferLists 는 스핀 잠금을 획득하고, 큐에 패킷을 추가한 다음, 스핀 잠금을 해제합니다. 스핀 잠금을 사용하면 패킷이 큐에 안전하게 추가되는 동안 잠금을 유지하는 스레드가 큐 링크를 수정하는 유일한 스레드가 됩니다. 미니포트 드라이버가 패킷을 큐에서 떼면 동일한 스핀 잠금으로 이러한 액세스가 보호됩니다. 큐의 헤드 또는 큐를 구성하는 링크 필드를 수정하는 지침을 실행하는 경우 드라이버는 스핀 잠금으로 큐를 보호해야 합니다.

드라이버는 큐를 과도하게 보호하지 않도록 주의해야 합니다. 예를 들어 드라이버는 패킷을 큐에 넣기 전에 패킷의 네트워크 드라이버 예약 필드에서 일부 작업(예: 길이가 포함된 필드 채우기)을 수행할 수 있습니다. 드라이버는 스핀 잠금으로 보호되는 코드 영역 외부에서 이 작업을 수행할 수 있지만 패킷을 큐에 대기하기 전에 수행해야 합니다. 패킷이 큐에 있고 실행 중인 스레드가 스핀 잠금을 해제한 후 드라이버는 다른 스레드가 패킷을 즉시 큐에서 제거할 수 있다고 가정해야 합니다.

스핀 잠금 문제 방지

교착 상태를 방지하려면 NDIS 드라이버가 NdisXxxSpinlock 함수 이외의 NDIS 함수를 호출하기 전에 모든 NDIS 스핀 잠금을 해제해야 합니다. NDIS 드라이버가 이 요구 사항을 준수하지 않으면 다음과 같이 교착 상태가 발생할 수 있습니다.

  1. NDIS 스핀 잠금 A를 보유하는 스레드 1은 NdisAcquireSpinLock 함수를 호출하여 NDIS 스핀 잠금 B를 획득하려고 시도하는 Ndis Xxx 함수를 호출합니다.

  2. NDIS 스핀 잠금 B를 보유하는 스레드 2는 NdisAcquireSpinLock 함수를 호출하여 NDIS 스핀 잠금 A를 획득하려고 시도하는 Ndis Xxx 함수를 호출합니다.

  3. 서로가 스핀 잠금을 해제하기를 기다리는 스레드 1과 스레드 2는 교착 상태가 됩니다.

Microsoft Windows 운영 체제는 네트워크 드라이버가 둘 이상의 스핀 잠금을 동시에 보유하는 것을 제한하지 않습니다. 그러나 드라이버의 한 섹션이 스핀 잠금 B를 보유하는 동안 스핀 잠금 A를 획득하려고 시도하고 다른 섹션에서 스핀 잠금 A를 보유하는 동안 스핀 잠금 B를 획득하려고 하면 교착 상태가 발생합니다. 둘 이상의 스핀 잠금을 획득하는 경우 드라이버는 획득 순서를 적용하여 교착 상태를 방지해야 합니다. 즉, 드라이버가 스핀 잠금 B 전에 스핀 잠금 A를 획득하는 경우 위에서 설명한 상황은 발생하지 않습니다.

스핀 잠금을 획득하면 IRQL이 DISPATCH_LEVEL 발생하며 이전 IRQL을 스핀 잠금에 저장합니다. 스핀 잠금을 해제하면 IRQL이 스핀 잠금에 저장된 값으로 설정됩니다. NDIS는 때때로 PASSIVE_LEVEL 드라이버를 입력하기 때문에 다음 코드 시퀀스에서 문제가 발생할 수 있습니다.

NdisAcquireSpinLock(A);
NdisAcquireSpinLock(B);
NdisReleaseSpinLock(A);
NdisReleaseSpinLock(B);

드라이버는 다음과 같은 이유로 이 시퀀스의 스핀 잠금에 액세스해서는 안 됩니다.

  • 스핀 잠금 A를 해제하고 스핀 잠금 B를 해제하는 사이에 코드는 DISPATCH_LEVEL 대신 PASSIVE_LEVEL 실행되며 부적절한 중단이 발생할 수 있습니다.

  • 스핀 잠금 B를 해제한 후 코드가 DISPATCH_LEVEL 실행되어 IRQL_NOT_LESS_OR_EQUAL 중지 오류로 인해 호출자가 훨씬 나중에 오류가 발생할 수 있습니다.

스핀 잠금을 사용하면 성능에 영향을 줍니다. 일반적으로 드라이버는 많은 스핀 잠금을 사용하면 안 됩니다. 일반적으로 고유한 함수(예: 송신 및 수신 함수)에는 두 개의 스핀 잠금을 사용할 수 있는 사소한 겹침이 있는 경우도 있습니다. 두 함수가 별도의 프로세서에서 독립적으로 작동할 수 있도록 두 개 이상의 스핀 잠금을 사용하는 것이 가치 있는 절충점일 수 있습니다.

타이머

타이머는 폴링 또는 시간 초과 작업에 사용됩니다. 드라이버는 타이머를 만들고 함수를 타이머와 연결합니다. 연결된 함수는 타이머에 지정된 기간이 만료될 때 호출됩니다. 타이머는 원샷 또는 주기적일 수 있습니다. 주기적 타이머가 설정되면 명시적으로 지워질 때까지 모든 기간이 만료될 때 계속 발생합니다. 원샷 타이머는 실행될 때마다 다시 설정해야 합니다.

타이머는 NdisAllocateTimerObject 를 호출하여 만들어지고 초기화되며 NdisSetTimerObject를 호출하여 설정합니다. 비속어 타이머를 사용하는 경우 NdisSetTimerObject를 호출하여 다시 설정해야 합니다. 타이머는 NdisCancelTimerObject를 호출하여 지워집니다.

이벤트

이벤트는 실행의 두 스레드 간에 작업을 동기화하는 데 사용됩니다. 이벤트는 드라이버에 의해 할당되고 NdisInitializeEvent를 호출하여 초기화됩니다. IRQL = PASSIVE_LEVEL 실행되는 스레드는 NdisWaitEvent 를 호출하여 대기 상태로 전환합니다. 드라이버 스레드는 이벤트를 대기할 때 대기할 최대 시간과 대기할 이벤트를 지정합니다. 스레드의 대기는 NdisSetEvent 가 호출되어 이벤트가 신호를 받을 때 또는 지정된 최대 대기 시간 간격이 만료되는 경우(중 먼저 발생하는 경우) 충족됩니다.

일반적으로 이벤트는 NdisSetEvent를 호출하는 협력 스레드에 의해 설정됩니다. 이벤트는 생성될 때 서명되지 않으며 대기 중인 스레드에 신호를 표시하기 위해 설정해야 합니다. 이벤트는 NdisResetEvent가 호출될 때까지 신호를 유지합니다.

네트워크 드라이버의 다중 프로세서 지원