Home | Info | Research | Blog | Repos | Messages | Contact Me

 


윈도우에서 드라이버를 로드하려면 일단 CreateService 함수로 서비스를 생성해야 합니다. 드라이버를 잘 사용하고 난 뒤 드라이버를 언로드하고 드라이버의 서비스를 삭제할 때 DeleteService 함수를 사용합니다.

이때 응용프로그램과 통신을 위해 생성한 디바이스 오브젝트 등을 CreateFile 등의 함수로 열었을 때 꼭 핸들을 닫아줘야 합니다.

핸들을 닫지 않은채로 DeleteService 함수로 드라이버 서비스를 삭제하게 되면 완전히 삭제되지 않고 흔적이 남아있게 됩니다.

즉 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\<드라이버 이름> 키 아래에 DeleteFlag라는 DWORD 값이 생기게 됩니다. 데이터는 1이 들어가있습니다.



서비스가 완전히 삭제되지 않고 삭제 대기중인 상태에서 다시 OpenService로 해당 서비스의 핸들을 얻고 DeleteService 함수를 호출하게 되면 ERROR_SERVICE_MARKED_FOR_DELETE(0x00000430, 1072, 지정된 서비스가 지워진 것으로 표시되었습니다.) 에러가 발생하게 됩니다.

DeleteFlag가 설정된 서비스는 일정 시간이 지나면 자동적으로 삭제됩니다. 하지만 디바이스의 핸들을 닫지 않은 상태에서 CreateService(OpenService)와 DeleteService를 연속적으로 호출하게 되면, DeleteFlag가 남아있는 채로 서비스를 삭제하려고 하는 상황이 생길 수 있으니 주의해야 합니다.




요즘 나오는 컴퓨터들은 기본적으로 CPU의 개수가 2개 이상입니다. 예전에는 물리적인 CPU를 2개 이상 꽂아서 사용했지만, 최근들어서는 물리적인 CPU 하나에 여러개의 코어를 탑재하여 나오고 있습니다.(인텔 코어2 듀오, 코어2 쿼드 등) 펜티엄 4때에는 하이퍼쓰레딩이라는 기술이 사용되기도 했습니다.

윈도우는 실제로 CPU가 2개 인 것이나, 코어 2 듀오 CPU를 하나만 꽂았을 때, 펜티엄4의 하이퍼쓰레딩 모두 여러개의 CPU로 인식합니다.

WinDbg를 켜고 !cpuinfo 명령을 입력하면 현재 CPU의 개수를 확인할 수 있습니다.

0: kd> !cpuinfo
CP  F/M/S Manufacturer  MHz PRCB Signature    MSR 8B Signature Features
 0  6,15,11 GenuineIntel 2399 000000b600000000 >000000b600000000<80033fff
 1  6,15,11 GenuineIntel 2399 000000b600000000                   80033fff
 2  6,15,11 GenuineIntel 2399 000000b600000000                   80033fff
 3  6,15,11 GenuineIntel 2399 000000b600000000                   80033fff

코어2 쿼드 CPU에서 !cpuinfo 명령을 실행한 화면입니다. 0번 부터 3번까지 4개의 CPU가 확인되고 있습니다.

WinDbg에서 각 CPU의 레지스터, 스택, IDT 등을 확인하려면 먼저 확인하고자 하는 CPU로 전환해주어야 합니다. ~0s 라고 하면 0번 즉 첫번째 CPU를 선택하는 것입니다. 마찬가지로 1, 2, 3번 CPU로 전환하려면 ~1s, ~2s, ~3s와 같이 입력하면 됩니다.
0: kd> ~0s
0: kd> ~1s
1: kd> ~2s
2: kd> ~3s

Command 창의 kd> 프롬프트에 0:, 1:, 2:, 3:과 같은 숫자들이 현재 선택된 CPU를 뜻합니다.

CPU를 선택할 때마다 WinDbg의 Registers, Calls, Processes and Threads 창이 각 CPU의 내용으로 바뀌는 것을 확인할 수 있습니다.

또는 r 명령으로 레지스터 값 확인, k 명령 시리즈로 스택을 확인할 수 있습니다. 그리고 !idt 명령을 사용하면 선택한 CPU의 IDT를 확인할 수 있습니다.

CPU가 2개 달린 컴퓨터이거나 코어2 듀오 같은 CPU는 CPU가 2개 밖에 없으므로 ~0s, ~1s 처럼 0, 1번 CPU를 선택할 수 있습니다. 현재 CPU 개수를 벗어나면 다음과 같이 출력됩니다.
3: kd> ~4s
4 is not a valid processor number

명령어를 입력하지 않고 CPU를 전환하려면 Processes and Threads 창에서 각 CPU를 클릭하면 됩니다.
사용자 삽입 이미지



리눅스 커널의 include/asm-i386/atomic.h의 주요 함수를 Visual C++에서 컴파일 할 수 있도록 만들어 보았습니다.

Atomic 함수는 멀티 쓰레드, 멀티 프로세서 환경에서도 안전하게 변수의 값을 연산할 수 있도록 해주는 함수입니다. 이런 기능을 Atomic 연산이라고 합니다.

리눅스 및 유닉스 계열에서는 Atomic 함수라고 하고, 윈도우에서는 Interlocked 함수라고 합니다.

#ifndef __ARCH_I386_ATOMIC__
#define __ARCH_I386_ATOMIC__

#include <linux/compiler.h>
#include <asm/processor.h>

/*
 * Atomic operations that C can't guarantee us.  Useful for
 * resource counting etc..
 */


/*
 * Make sure gcc doesn't try to be clever and move things around
 * on us. We need to use _exactly_ the address the user gave us,
 * not some alias that contains the same information.
 */

typedef struct { int counter; } atomic_t;

#define ATOMIC_INIT(i)    (i)

/**
 * atomic_read - read atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically reads the value of @v.
 */

#define atomic_read(v)        ((v)->counter)

/**
 * atomic_set - set atomic variable
 * @v: pointer of type atomic_t
 * @i: required value
 *
 * Atomically sets the value of @v to @i.
 */

#define atomic_set(v,i)        (((v)->counter) = (i))

/**
 * atomic_add - add integer to atomic variable
 * @i: integer value to add
 * @v: pointer of type atomic_t
 *
 * Atomically adds @i to @v.
 */

static __inline__ void atomic_add(int i, atomic_t *v)
{
    __asm
    {
        mov eax, v
        mov ecx, i
        lock add dword ptr [eax], ecx
    }
}

/**
 * atomic_sub - subtract the atomic variable
 * @i: integer value to subtract
 * @v: pointer of type atomic_t
 *
 * Atomically subtracts @i from @v.
 */

static __inline__ void atomic_sub(int i, atomic_t *v)
{
    __asm
    {
        mov eax, v
        mov ecx, i
        lock sub dword ptr [eax], ecx
    }
}

/**
 * atomic_sub_and_test - subtract value from variable and test result
 * @i: integer value to subtract
 * @v: pointer of type atomic_t
 *
 * Atomically subtracts @i from @v and returns
 * true if the result is zero, or false for all
 * other cases.
 */

static __inline__ int atomic_sub_and_test(int i, atomic_t *v)
{
    unsigned char c;

    __asm
    {
        mov eax, v
        mov ecx, i
        lock sub dword ptr [eax], ecx
        sete c
    }
    return c;
}

/**
 * atomic_inc - increment atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically increments @v by 1.
 */

static __inline__ void atomic_inc(atomic_t *v)
{
    __asm
    {
        mov eax, v
        lock inc dword ptr [eax]
    }
}

/**
 * atomic_dec - decrement atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically decrements @v by 1.
 */

static __inline__ void atomic_dec(atomic_t *v)
{
    __asm
    {
        mov eax, v
        lock dec dword ptr [eax]
    }
}

/**
 * atomic_dec_and_test - decrement and test
 * @v: pointer of type atomic_t
 *
 * Atomically decrements @v by 1 and
 * returns true if the result is 0, or false for all other
 * cases.
 */

static __inline__ int atomic_dec_and_test(atomic_t *v)
{
    unsigned char c;

    __asm
    {
        mov eax, v
        lock dec dword ptr [eax]
        sete c
    }
    return c != 0;
}

/**
 * atomic_inc_and_test - increment and test
 * @v: pointer of type atomic_t
 *
 * Atomically increments @v by 1
 * and returns true if the result is zero, or false for all
 * other cases.
 */

static __inline__ int atomic_inc_and_test(atomic_t *v)
{
    unsigned char c;

    __asm
    {
        mov eax, v
        lock inc dword ptr [eax]
        sete c
    }
    return c != 0;
}

/**
 * atomic_add_negative - add and test if negative
 * @v: pointer of type atomic_t
 * @i: integer value to add
 *
 * Atomically adds @i to @v and returns true
 * if the result is negative, or false when
 * result is greater than or equal to zero.
 */

static __inline__ int atomic_add_negative(int i, atomic_t *v)
{
    unsigned char c;

    __asm
    {
        mov eax, v
        mov ecx, i
        lock add dword ptr [eax], ecx
        sets c
    }
    return c;
}

/**
 * atomic_add_return - add and return
 * @v: pointer of type atomic_t
 * @i: integer value to add
 *
 * Atomically adds @i to @v and returns @i + @v
 */

static __inline__ int atomic_add_return(int i, atomic_t *v)
{
    int __i;
#ifdef CONFIG_M386
    unsigned long flags;
    if(unlikely(boot_cpu_data.x86==3))
        goto no_xadd;
#endif
    /* Modern 486+ processor */
    __i = i;
    __asm
    {
        mov eax, v
        mov ecx, i
        lock xadd dword ptr [eax], ecx
        mov i, ecx
    }
    return i + __i;

#ifdef CONFIG_M386
no_xadd: /* Legacy 386 processor */
    local_irq_save(flags);
    __i = atomic_read(v);
    atomic_set(v, i + __i);
    local_irq_restore(flags);
    return i + __i;
#endif
}

static __inline__ int atomic_sub_return(int i, atomic_t *v)
{
    return atomic_add_return(-i,v);
}

#define atomic_cmpxchg(v, old, new) ((int)cmpxchg(&((v)->counter), old, new))
#define atomic_xchg(v, new) (xchg(&((v)->counter), new))

/**
 * atomic_add_unless - add unless the number is already a given value
 * @v: pointer of type atomic_t
 * @a: the amount to add to v...
 * @u: ...unless v is equal to u.
 *
 * Atomically adds @a to @v, so long as @v was not already @u.
 * Returns non-zero if @v was not @u, and zero otherwise.
 */

#define atomic_add_unless(v, a, u)                \
({                                \
    int c, old;                        \
    c = atomic_read(v);                    \
    for (;;) {                        \
        if (unlikely(c == (u)))                \
            break;                    \
        old = atomic_cmpxchg((v), c, c + (a));        \
        if (likely(old == c))                \
            break;                    \
        c = old;                    \
    }                            \
    c != (u);                        \
})
#define atomic_inc_not_zero(v) atomic_add_unless((v), 1, 0)

#define atomic_inc_return(v)  (atomic_add_return(1,v))
#define atomic_dec_return(v)  (atomic_sub_return(1,v))

/* Atomic operations are already serializing on x86 */
#define smp_mb__before_atomic_dec()    barrier()
#define smp_mb__after_atomic_dec()    barrier()
#define smp_mb__before_atomic_inc()    barrier()
#define smp_mb__after_atomic_inc()    barrier()

#include <asm-generic/atomic.h>
#endif




리눅스 커널의 include/asm-x86/bitops.h의 주요 함수를 Visual C++에서 컴파일 할 수 있도록 만들어 보았습니다.

#ifndef _I386_BITOPS_H
#define _I386_BITOPS_H

/*
 * Copyright 1992, Linus Torvalds.
 * Copyright 2008, Lee Jae-Hong (pyrasis)
 */


#include <linux/compiler.h>

#define inline __inline

/**
 * set_bit - Atomically set a bit in memory
 * @nr: the bit to set
 * @addr: the address to start counting from
 *
 * This function is atomic and may not be reordered.  See __set_bit()
 * if you do not require the atomic guarantees.
 *
 * Note: there are no guarantees that this function will not be reordered
 * on non x86 architectures, so if you are writting portable code,
 * make sure not to rely on its reordering guarantees.
 *
 * Note that @nr may be almost arbitrarily large; this function is not
 * restricted to acting on a single-word quantity.
 */

static inline void set_bit(int nr, volatile unsigned long * addr)
{
    __asm
    {
        mov eax, addr
        mov ecx, nr
        lock bts dword ptr [eax], ecx
    }
}

/**
 * clear_bit - Clears a bit in memory
 * @nr: Bit to clear
 * @addr: Address to start counting from
 *
 * clear_bit() is atomic and may not be reordered.  However, it does
 * not contain a memory barrier, so if it is used for locking purposes,
 * you should call smp_mb__before_clear_bit() and/or smp_mb__after_clear_bit()
 * in order to ensure changes are visible on other processors.
 */

static inline void clear_bit(int nr, volatile unsigned long * addr)
{
    __asm
    {
        mov eax, addr
        mov ecx, nr
        lock btr dword ptr [eax], ecx
    }
}

/**
 * change_bit - Toggle a bit in memory
 * @nr: Bit to change
 * @addr: Address to start counting from
 *
 * change_bit() is atomic and may not be reordered. It may be
 * reordered on other architectures than x86.
 * Note that @nr may be almost arbitrarily large; this function is not
 * restricted to acting on a single-word quantity.
 */

static inline void change_bit(int nr, volatile unsigned long * addr)
{
    __asm
    {
        mov eax, addr
        mov ecx, nr
        lock btc dword ptr [eax], ecx
    }
}

/**
 * test_and_set_bit - Set a bit and return its old value
 * @nr: Bit to set
 * @addr: Address to count from
 *
 * This operation is atomic and cannot be reordered. 
 * It may be reordered on other architectures than x86.
 * It also implies a memory barrier.
 */

static inline int test_and_set_bit(int nr, volatile unsigned long * addr)
{
    int oldbit;

    __asm
    {
        mov edx, addr
        mov eax, nr
        lock bts dword ptr [edx], eax
        sbb eax, eax
        mov oldbit, eax
    }
    return oldbit;
}

/**
 * test_and_clear_bit - Clear a bit and return its old value
 * @nr: Bit to clear
 * @addr: Address to count from
 *
 * This operation is atomic and cannot be reordered.
 * It can be reorderdered on other architectures other than x86.
 * It also implies a memory barrier.
 */

static inline int test_and_clear_bit(int nr, volatile unsigned long * addr)
{
    int oldbit;

    __asm
    {
        mov edx, addr
        mov eax, nr
        lock btr dword ptr [edx], eax
        sbb eax, eax
        mov oldbit, eax
    }
    return oldbit;
}

/**
 * test_and_change_bit - Change a bit and return its old value
 * @nr: Bit to change
 * @addr: Address to count from
 *
 * This operation is atomic and cannot be reordered. 
 * It also implies a memory barrier.
 */

static inline int test_and_change_bit(int nr, volatile unsigned long* addr)
{
    int oldbit;

    __asm
    {
        mov edx, addr
        mov eax, nr
        lock btc dword ptr [edx], eax
        sbb eax, eax
        mov oldbit, eax
    }
    return oldbit;
}

/**
 * __ffs - find first bit in word.
 * @_word: The word to search
 *
 * Undefined if no bit exists, so code should check against 0 first.
 */

static inline unsigned long __ffs(unsigned long _word)
{
    __asm
    {
        bsf eax, [_word]
        mov _word, eax
    }
    return _word;
}

/**
 * find_first_bit - find the first set bit in a memory region
 * @addr: The address to start the search at
 * @size: The maximum size to search
 *
 * Returns the bit-number of the first set bit, not the number of the byte
 * containing a bit.
 */

static inline unsigned find_first_bit(const unsigned long *addr, unsigned size)
{
    unsigned x = 0;

    while (x < size) {
        unsigned long val = *addr++;
        if (val)
            return __ffs(val) + x;
        x += (sizeof(*addr)<<3);
    }
    return x;
}

/**
 * find_next_bit - find the first set bit in a memory region
 * @addr: The address to base the search on
 * @offset: The bitnumber to start searching at
 * @size: The maximum size to search
 */

int find_next_bit(const unsigned long *addr, int size, int offset);

/**
 * ffz - find first zero in word.
 * @word: The word to search
 *
 * Undefined if no zero exists, so code should check against ~0UL first.
 */

static inline unsigned long ffz(unsigned long word)
{
    int x = ~word;

    __asm
    {
        bsf eax, [x]
    }
}

/**
 * ffs - find first bit set
 * @x: the word to search
 *
 * This is defined the same way as
 * the libc and compiler builtin ffs routines, therefore
 * differs in spirit from the above ffz() (man ffs).
 */

static inline int ffs(int x)
{
    __asm
    {
        bsf eax, [x]
        jnz L1
        mov eax, -1
    L1:
        inc eax
    }
}

#endif /* _I386_BITOPS_H */





윈도우 프로젝트 필수 유틸리티 - Subversion, Trac, CruiseControl.NET이 출간되었습니다.

요즘 제가 블로그에 글 쓰는 것이 뜸했습니다. 다 이 책 때문이었습니다. 원래는 좀더 일찍 나올 수 있었는데, 표지 결정이 늦어지면서 5월 30일에 출간하게 되었습니다. (이때를 틈타 막판에 원고를 수정할 수 있었습니다. 편집자들이 너무 고생을해서 저를 잡아먹으려고 작전을 세우고 있다는 소문이...)

윈도우 기반 환경에서 Subversion, Trac, CruiseControl.NET을 활용하는 방법에 관한 책입니다.

윈도우용 Subversion, TortoiseSVN의 기본적인 사용 방법과 Apache와 연동하기, Trac 설치 및 사용 방법, CruiseControl.NET 빌드 스크립트 문법과 사용 방법을 설명합니다.

그리고 PDB 파일에 Subversion 저장소 정보를 기록하고 심볼 서버 형태로 저장하는 방법도 포함되어 있습니다. 이 과정을 CruiseControl.NET과 연동하는 방법도 설명합니다.

배포 자동화를 위한 릴리스(Release) 서버 구축 방법, 그리고 문서화 도구인 Doxygen 사용 방법과 CruiseControl.NET을 연동하기, Subversion 저장소와 Trac 데이터베이스를 자동으로 백업하기, 윈도우 드라이버 자동 빌드 하기 등의 내용으로 구성되어 있습니다.

내용 설명이 길었습니다. 결론은 "개발자가 일하기 편한 환경 만들기"입니다. 개발 이외의 관리에 소비되는 시간을 절약하여 좀더 개발에 집중할 수 있게 하는 것이 목적입니다.

많은 개발자들이 삽질에서 벗어나 좀더 편한 환경에서 일할 수 있었으면 합니다.

자주 하는 질문
추가 팁
독자 후기
오탈자




윈도우에는 3종류의 APC가 있습니다.

먼저 유저 APC가 있는데, 이것은 유저모드 프로그래밍에서 QueueUserApc 함수로 APC를 등록하거나, overlapped I/O에서 사용됩니다.

노멀 APC는 PASSIVE_LEVEL에서 실행되며 유저 APC를 선점할 수 있습니다.

스페셜 APC는 APC_LEVEL에서 실행되며 유저모드 코드, 노멀 APC 및 PASSIVE_LEVEL에서 실행되는 쓰레드를 선점할 수 있습니다. 이 스페셜 APC는 I/O 완료에 사용됩니다.

이 노멀 APC와 스페셜 APC는 커널 모드에서 실행됩니다.



윈도우(XP 이상)에서 USB 키보드(USB Composite 장치)를 꽂으면 기본적으로 usbccgp.sys가 로드 됩니다. 마찬가지로 USB 메모리를 꽂으면 usbstor.sys가 로드 됩니다. 각 USB 장치를 빼면 해당 드라이버는 언로드 됩니다. (단 같은 종류의 USB 장치가 여러개 꽂혀 있을 때 모든 장치가 빠져야 해당 드라이버가 언로드 됩니다.)

여기서 중요한 것은 ObReferenceObjectByName과 같은 함수를 이용하여 \\Driver\usbccgp나 \\Driver\usbstor를 참조 했을 때 입니다. usbccgp.sys나 usbstor.sys와 같이 장치의 장착 여부에 따라 로드, 언로드 되는 드라이버는 ObReferenceObjectByName로 드라이버 오브젝트를 참조 한 뒤 반드시 ObDereferenceObject 함수로 레퍼런스 카운트를 감소시켜 주어야 합니다.

ObDereferenceObject 함수로 레퍼런스 카운트를 감소시켜 주지 않은 상태에서 USB 키보드나 USB 메모리를 제거하면 usbccgp.sys 또는 usbstor.sys 드라이버 자체는 언로드 되지만 드라이버 오브젝트는 계속 남아 있게 됩니다. 이 상태에서 USB 키보드나 USB 메모리를 다시 꽂게 되면 usbccgp.sys 또는 usbstor.sys 드라이버가 로드 되지만 해제 되지 않은 이전 드라이버 오브젝트 때문에 새로운 드라이버 오브젝트를 생성하는데 실패하게 됩니다.

드라이버 오브젝트 생성에 실패하면 usbccgp.sys 또는 usbstor.sys 드라이버는 정상 작동 하지 않기 때문에 USB 키보드 또는 USB 메모리가 인식되지 않게 되는 것입니다.

PNP 처리와는 무관하게 ObReferenceObjectByName 함수를 사용하고 ObDereferenceObject 함수로 레퍼런스 카운트를 감소시켜 주지 않는 것에 따라서 플러그 앤 플레이가 되지 않을 수 있으니 주의해야 합니다.

참고 : USB Composite 장치
다중 USB 인터페이스(multiple USB interface)를 지원하는 USB 장치를 Composite 장치라고 합니다.(보통 두개 이상의 Device Object를 생성합니다.) 이 Composite 장치를 꽂으면 usbccgp.sys가 로드 됩니다. 하지만 Composite 장치가 아니라면 usbccgp.sys는 로드 되지 않습니다.
USB Common Class Generic Parent Driver
USB Driver Stack for Windows XP and Later




Critical Region은 공유 자원을 보호하는 가장 기본적인 동기화 요소입니다.

KeEnterCriticalRegion 함수로 Critical Region에 진입하며 KTHREAD의 KernelApcDisable에서 1을 뺍니다. 반대로 KeLeaveCriticalRegion 함수로 Critical Region에서 벗어나며 KernelApcDisable에 1을 더합니다. KernelApcDisable의 초기값은 0 입니다. 이렇게 해서 노멀 APC를 차단합니다. 하지만 스페셜 APC는 차단하지 않습니다.

Critical Region으로 진입하더라도 IRQL은 변화시키지 않고 계속 PASSIVE_LEVEL에서 실행됩니다. 커널은 KTHREAD의 KernelApcDisable을 확인하여 이 값이 -1이라면 노멀 APC를 실행하지 않도록 합니다. 하지만 스페셜 APC는 APC_LEVEL에서 실행되기 때문에 PASSIVE_LEVEL에서 실행되고 있는 Critical Region은 스페셜 APC에게 실행권을 빼앗길 수 있습니다. (선점 당할 할 수 있습니다.)
 
APC_LEVEL에서 Critical Region으로 진입했다면 계속 APC_LEVEL에서 실행되며 PASSIVE_LEVEL에서 실행되는 노멀 APC는 KernelApcDisable의 여부와 상관 없이 실행이 차단 됩니다. 그리고 같은 IRQL에서 실행중인 스페셜 APC도 차단됩니다.

윈도우 2003에서는 새로운 함수가 추가 되었는데, 바로 Guarded Region입니다.

이 Guarded Region은 노멀 APC와 스페셜 APC를 모두 차단합니다. KeEnterGuardedRegion 함수로 Guarded Region에 진입하며 KTHREAD의 SpecialApcDisable에 1을 뺍니다. KeLeaveGuardedRegion으로 Guarded Region에서 벗어나며 SpecialApcDisable에 1을 더합니다. 커널은 SpecialApcDisable의 상태를 보고 노멀 APC, 스페셜 APC 모두 차단합니다.

앞의 Critical Region은 PASSIVE_LEVEL에서는 노멀 APC만 차단되고 APC_LEVEL에 와서야 노멀, 스페셜 APC 모두가 차단됩니다. 하지만 Guarded Region은 IRQL을 변화시키지 않고 스페셜 APC를 차단한다는 것입니다. 즉 PASSIVE_LEVEL에서도 노멀, 스페셜 APC가 모두 차단됩니다.

이것은 윈도우 2003 커널이 그렇게 바뀌었기 때문이고 2003 이전 운영체제인 2000, XP에서는 모든 APC를 차단하려면 IRQL을 APC_LEVEL로 높이는 수 밖에 없습니다.

윈도우 2003에 새롭게 추가된 Guarded Mutex는 내부적으로 이 Guarded Region을 이용합니다.