세마포어를 이용한 커널 모드 Condition Variable 구현

저작권 안내
  • 책 또는 웹사이트의 내용을 복제하여 다른 곳에 게시하는 것을 금지합니다.
  • 책 또는 웹사이트의 내용을 발췌, 요약하여 강의 자료, 발표 자료, 블로그 포스팅 등으로 만드는 것을 금지합니다.

안녕하세요. 이재홍입니다.

윈도우는 Windows Vista, Windows Server 2008 부터 유저 모드에서 사용할 수 있는 Condition Variable 함수들이 추가되었습니다. (Windows Vista에 새로 추가된 동기화 기본 형식)

세마포어를 이용하여 커널 모드에서 사용할 수 있는 Condition Variable을 구현해보도록 하겠습니다.

Condition Variable은 기존 동기화 객체와는 약간 다른 특징을 가지고 있습니다. 뮤텍스 혹은 스핀락의 경우 대기와 해제가 1:1 대응입니다. 즉 한번에 하나씩 대기하고 있는 객체를 깨울 수 있습니다. Condition Variable은 대기하고 있는 하나의 객체를 깨울 수도 있고, 대기하고 있는 여러개의 객체를 한번에 모두 깨울 수도 있습니다.

구현 원리는 세마포어의 특성을 이용하는 것입니다. 세마포어는 대기하고 있는 객체를 원하는 개수 만큼 깨울 수 있는 기능이 있습니다. 따라서 대기하는 객체의 개수를 저장하고 있고, 대기하는 객체를 한번에 깨울 때에는 저장하고 있던 객체의 개수를 이용하는 것입니다.

#define MAXIMUM_WAIT_CONDITION_VARIABLES 1024

typedef struct _KCONDITION_VARIABLE {
    KSEMAPHORE Semaphore;
    KSPIN_LOCK SpinLock;
    ULONG Waiters;
} KCONDITION_VARIABLE, *PKCONDITION_VARIABLE, *PRKCONDITION_VARIABLE;

VOID
KeInitializeConditionVariable (
    __out PKCONDITION_VARIABLE ConditionVariable
    )
{
    ConditionVariable->Waiters = 0;
    KeInitializeSpinLock(&ConditionVariable->SpinLock);
    KeInitializeSemaphore(&ConditionVariable->Semaphore, 0, 
        MAXIMUM_WAIT_CONDITION_VARIABLES);
}

먼저 KCONDITION_VARIABLE 구조체와 초기화 함수입니다. 대기하고 있는 객체의 수를 저장하는 변수를 초기화 하고, 스핀락과 세마포어를 초기화 합니다. 스핀락은 Waiters 변수를 보호하기 위해 사용합니다.

NTSTATUS
KeSleepConditionVariableMX (
    __inout PKCONDITION_VARIABLE ConditionVariable,
    __inout PKMUTEX Mutex,
    __in_opt PLARGE_INTEGER Timeout
    )
{
    NTSTATUS status;
    KIRQL irql = KeGetCurrentIrql();

    KeAcquireSpinLock(&ConditionVariable->SpinLock, &irql);
    ConditionVariable->Waiters++;
    KeReleaseSpinLock(&ConditionVariable->SpinLock, irql);

    KeReleaseMutex(Mutex, TRUE);
    
    status = KeWaitForSingleObject(&ConditionVariable->Semaphore, Executive, 
        KernelMode, FALSE, Timeout);

    KeWaitForSingleObject(Mutex, Executive, KernelMode, FALSE, NULL);

    return status;
}

뮤텍스와 함께 사용하는 Condition Variable 대기 함수입니다. 대기할 때 마다 Waiters 변수를 증가시킵니다. Windows Vista(Server 2008)에 있는 SleepConditionVariableCS라는 함수와 비슷한 용도입니다. 이 함수는 크리티컬 섹션을 사용하는 함수입니다.

NTSTATUS
KeSleepConditionVariableRS (
    __inout PKCONDITION_VARIABLE ConditionVariable,
    __inout PERESOURCE Resource,
    __in_opt PLARGE_INTEGER Timeout,
    __in ULONG Flags
    )
{
    NTSTATUS status;
    KIRQL irql = KeGetCurrentIrql();

    KeAcquireSpinLock(&ConditionVariable->SpinLock, &irql);
    ConditionVariable->Waiters++;
    KeReleaseSpinLock(&ConditionVariable->SpinLock, irql);

    ExReleaseResourceLite(Resource);

    status = KeWaitForSingleObject(&ConditionVariable->Semaphore, Executive, 
        KernelMode, FALSE, Timeout);

    if (Flags == CONDITION_VARIABLE_LOCKMODE_SHARED)
        ExAcquireResourceSharedLite(Resource, TRUE);
    else
        ExAcquireResourceExclusiveLite(Resource, TRUE);

    return status;    
}

이번에는 리소스와 함께 사용하는 Condition Variable 대기 함수입니다. Windows Vista(Server 2008)에 있는 SleepConditionVariableSRW 함수에 대응합니다. 리소스를 Shared로 사용할 때에는 Flags에 CONDITION_VARIABLE_LOCKMODE_SHARED를 지정해줍니다.

VOID
KeWakeConditionVariable (
    __inout PKCONDITION_VARIABLE ConditionVariable
    )
{
    KIRQL irql = KeGetCurrentIrql();

    if (ConditionVariable->Waiters > 0)
    {
        KeAcquireSpinLock(&ConditionVariable->SpinLock, &irql);
        KeReleaseSemaphore(&ConditionVariable->Semaphore, IO_NO_INCREMENT, 1, FALSE);
        ConditionVariable->Waiters--;
        KeReleaseSpinLock(&ConditionVariable->SpinLock, irql);
    }
}

대기하고 있는 Condition Variable 객체 하나를 깨우는 함수입니다. 세마포어의 특성을 이용하는 것인데, KeReleaseSemaphore에 1을 지정하여 대기하고 있는 세마포어 객체 하나를 깨웁니다. 그리고 대기하고 있는 객체의 개수를 감소 시킵니다.

VOID
KeWakeAllConditionVariable (
    __inout PKCONDITION_VARIABLE ConditionVariable
    )
{
    KIRQL irql = KeGetCurrentIrql();

    if (ConditionVariable->Waiters > 0)
    {
        KeAcquireSpinLock(&ConditionVariable->SpinLock, &irql);
        KeReleaseSemaphore(&ConditionVariable->Semaphore, IO_NO_INCREMENT, 
            ConditionVariable->Waiters, FALSE);
        ConditionVariable->Waiters = 0;
        KeReleaseSpinLock(&ConditionVariable->SpinLock, irql);
    }
}

대기하고 있는 모든 Condition Variable 객체를 깨우는 함수입니다. KeReleaseSemaphore에는 대기하고 있는 객체의 개수를 지정합니다. 그래서 대기하고 있는 개수 만큼 세마포어 객체를 깨웁니다. 끝으로 대기하고 있는 객체의 개수를 0으로 초기화 합니다.

condvar_semaphore.zip : Condition Variable 구현과 사용 예제입니다.

예제에는 3개의 쓰레드를 생성하는데, 2번과 3번 쓰레드는 Condition Variable 객체를 대기하고 있고 1번 쓰레드가 KeWakeAllConditionVariable 함수를 이용하여 2, 3번 쓰레드를 깨우도록 되어 있습니다.

관련글


저작권 안내

이 웹사이트에 게시된 모든 글의 무단 복제 및 도용을 금지합니다.
  • 블로그, 게시판 등에 퍼가는 것을 금지합니다.
  • 비공개 포스트에 퍼가는 것을 금지합니다.
  • 글 내용, 그림을 발췌 및 요약하는 것을 금지합니다.
  • 링크 및 SNS 공유는 허용합니다.

Published

2009-06-20