스핀 잠금 사용: 예제
드라이버가 스핀 잠금을 보유하는 시간을 최소화하면 드라이버의 성능과 시스템 전체의 성능이 크게 향상될 수 있습니다. 예를 들어 인터럽트 스핀 잠금이 SMP 컴퓨터에서 ISR과 StartIo 및 DpcForIsr 루틴 간에 공유되어야 하는 디바이스별 데이터를 보호하는 방법을 보여 주는 다음 그림을 고려해 보세요.
드라이버의 ISR은 한 프로세서의 DIRQL에서 실행되지만 StartIo 루틴은 두 번째 프로세서의 DISPATCH_LEVEL 실행됩니다. 커널 인터럽트 처리기는 드라이버의 ISR에 대한 InterruptSpinLock을 보유하며, 드라이버의 디바이스 확장에서 상태 또는 디바이스 레지스터에 대한 포인터(SynchronizeContext)와 같은 보호된 디바이스별 데이터에 액세스합니다. SynchronizeContext에 액세스할 준비가 된 StartIo 루틴은 KeSynchronizeExecution을 호출하여 연결된 인터럽트 개체, 공유 SynchronizeContext 및 드라이버의 SynchCritSection 루틴(이전 그림의 AccessDevice)에 대한 포인터를 전달합니다.
ISR이 반환될 때까지 드라이버의 InterruptSpinLock을 해제하면 KeSynchronizeExecution이 두 번째 프로세서에서 회전하므로 AccessDevice가 SynchronizeContext를 건드리지 않습니다. 그러나 KeSynchronizeExecution 은 또한 두 번째 프로세서의 IRQL을 인터럽트 개체의 SynchronizeIrql로 발생시켜 ISR이 반환되는 즉시 DIRQL에서 AccessDevice를 실행할 수 있도록 해당 프로세서에서 다른 디바이스 인터럽트 발생을 방지합니다. 그러나 다른 디바이스에 대한 DIRQL 인터럽트, 클록 인터럽트 및 전원 장애 인터럽트는 두 프로세서에서 계속 발생할 수 있습니다.
ISR이 드라이버의 DpcForIsr 를 큐에 대기하고 반환하면 AccessDevice는 연결된 인터럽트 개체의 SynchronizeIrql에 있는 두 번째 프로세서에서 실행되고 SynchronizeContext에 액세스합니다. 한편 DpcForIsr 는 DISPATCH_LEVEL IRQL의 다른 프로세서에서 실행됩니다. DpcForIsr도 SynchronizeContext에 액세스할 준비가 되었기 때문에 1단계에서 StartIo 루틴과 동일한 매개 변수를 사용하여 KeSynchronizeExecution을 호출합니다.
KeSynchronizeExecution이 스핀 잠금을 획득하고 StartIo 루틴을 대신하여 AccessDevice를 실행하면 드라이버 제공 동기화 루틴 AccessDevice에 SynchronizeContext에 대한 단독 액세스 권한이 부여됩니다. AccessDevice는 SynchronizeIrql에서 실행되므로 드라이버의 ISR은 스핀 잠금을 획득하고 AccessDevice가 실행되는 동안 디바이스가 다른 프로세서에서 중단되더라도 스핀 잠금이 해제될 때까지 동일한 영역에 액세스할 수 없습니다.
AccessDevice가 반환되어 스핀 잠금이 해제됩니다. StartIo 루틴은 두 번째 프로세서의 DISPATCH_LEVEL 실행을 다시 시작합니다. KeSynchronizeExecution 은 이제 세 번째 프로세서에서 AccessDevice를 실행하므로 DpcForIsr를 대신하여 SynchronizeContext에 액세스할 수 있습니다. 그러나 2단계에서 KeSynchronizeExecution이라는 DpcForIsr 이전에 디바이스 인터럽트 발생이 발생한 경우 KeSynchronizeExecution이 스핀 잠금을 획득하고 세 번째 프로세서에서 AccessDevice를 실행하기 전에 ISR이 다른 프로세서에서 실행될 수 있습니다.
이전 그림에서 알 수 있듯이 한 프로세서에서 실행되는 루틴은 스핀 잠금을 보유하지만, 해당 스핀 잠금을 획득하려는 다른 모든 루틴은 작업을 수행하지 않습니다. 이미 고정된 스핀 잠금을 획득하려는 각 루틴은 홀더가 스핀 잠금을 해제할 때까지 단순히 현재 프로세서에서 회전합니다. 스핀 잠금이 해제되면 하나의 루틴만 획득할 수 있습니다. 현재 동일한 스핀 잠금을 획득하려는 다른 모든 루틴은 계속 회전합니다.
스핀 잠금의 홀더는 발생된 IRQL에서 실행됩니다( 임원 스핀 잠금의 경우 DISPATCH_LEVEL 또는 인터럽트 스핀 잠금의 경우 DIRQL). KeAcquireSpinLock 및 KeAcquireInStackQueuedSpinLock의 호출자는 keReleaseSpinLock 또는 KeReleaseInStackQueuedSpinLock을 호출하여 잠금을 해제할 때까지 DISPATCH_LEVEL 실행됩니다. KeSynchronizeExecution의 호출자는 호출자가 제공한 SynchCritSection 루틴이 종료되고 KeSynchronizeExecution이 컨트롤을 반환할 때까지 현재 프로세서의 IRQL을 인터럽트 개체의 SynchronizeIrql로 자동으로 발생시킵니다. 자세한 내용은 스핀 잠금을 사용하는 통화 지원 루틴을 참조하세요.
스핀 잠금 사용에 대한 다음 사실을 염두에 두세요.
낮은 IRQL에서 실행되는 모든 코드는 스핀 잠금 홀더가 차지하는 프로세서 집합과 동일한 스핀 잠금을 획득하려는 다른 루틴에서 작업을 수행할 수 없습니다.
따라서 드라이버가 스핀 잠금을 보유하는 시간을 최소화하면 드라이버 성능이 훨씬 향상되고 전반적인 시스템 성능 향상에 크게 기여합니다.
이전 그림과 같이 커널 인터럽트 처리기는 선착순으로 다중 프로세서 컴퓨터의 동일한 IRQL에서 실행되는 루틴을 실행합니다. 커널은 다음도 수행합니다.
드라이버 루틴이 KeSynchronizeExecution을 호출하면 커널을 사용하면 KeSynchronizeExecution 호출이 발생한 동일한 프로세서에서 드라이버의 SynchCritSection 루틴이 실행됩니다(1단계 및 3단계 참조).
드라이버의 ISR이 DpcForIsr를 큐에 대기할 때 커널은 IRQL이 DISPATCH_LEVEL 아래로 떨어지는 사용 가능한 첫 번째 프로세서에서 DPC가 실행되도록 합니다. 반드시 IoRequestDpc 호출이 발생한 프로세서와 동일한 것은 아닙니다(2단계 참조).
드라이버의 인터럽트 기반 I/O 작업은 유니프로세서 컴퓨터에서 직렬화되는 경향이 있지만 동일한 작업은 SMP 컴퓨터에서 실제로 비동기적일 수 있습니다. 이전 그림에서 알 수 있듯이 드라이버의 ISR은 DpcForIsr 가 이미 CPU1에서 디바이스 인터럽트를 처리한 IRP 처리를 시작하기 전에 SMP 컴퓨터의 CPU4에서 실행될 수 있습니다.
즉, 인터럽트 스핀 잠금이 DpcForIsr 또는 CustomDpc 루틴이 실행되기 전에 다른 프로세서에서 디바이스 인터럽트 발생 시 ISR에서 실행될 때 ISR이 저장한 작업별 데이터를 ISR에서 덮어쓰지 않도록 보호할 수 있다고 가정해서는 안 됩니다.
드라이버는 ISR에서 수집한 데이터를 보존하기 위해 모든 인터럽트 기반 I/O 작업을 직렬화하려고 시도할 수 있지만, 해당 드라이버는 유니프로세서 컴퓨터보다 SMP 컴퓨터에서 훨씬 빠르게 실행되지 않습니다. 유니프로세서 및 다중 프로세서 플랫폼에서 이식 가능한 상태로 유지하면서 최상의 드라이버 성능을 얻으려면 드라이버는 다른 기술을 사용하여 DpcForIsr의 후속 처리를 위해 ISR에서 얻은 작업별 데이터를 저장해야 합니다.
예를 들어 ISR은 DpcForIsr에 전달하는 IRP에 작업별 데이터를 저장할 수 있습니다. 이 기술의 구체화는 ISR 보강 횟수를 참조하고, ISR 제공 데이터를 사용하여 개수의 IRP 수를 처리하고, 반환하기 전에 개수를 0으로 다시 설정하는 DpcForIsr 을 구현하는 것입니다. 물론 ISR과 SynchCritSection 은 동적으로 값을 유지하므로 드라이버의 인터럽트 스핀 잠금으로 카운트를 보호해야 합니다.