Media Log

이전에 윈도우 드라이버를 공부하면서 정리해놨던 내용들이다.
여기에 있는 내용들은 전부 이 책에서 배운 내용들이다.
아마 지금까지 나온 윈도우 드라이버 책 중에 가장 좋은 책일 것이며, 앞으로도 이런 책은 안나올 것 같다.

만일 드라이버를 처음 접해서 아래 내용이 전혀 이해가 안되더라도 그냥 외워서 따라할 수 있도록 정리해놨다. 경험이 쌓여가면서 하나씩 이해가 될 것이다.

  • IRP를 완료시킬 때는 STATUS_PENDING 이라는 상태 코드를 써서는 안된다.
    물론 디스패치 루틴에서 STATUS_PENDING을 리턴할 수는 있지만, IoStatus.Status에 STATUS_PENDING을 할당한 뒤 I/O를 완료시키면 안된다는 뜻이다.
  • IoCompleteRequest를 호출 하기 전에는 IRP에 대해 지정한 취소 루틴을 제거해야 한다.
    Driver Verifier는 이런 경우를 적절히 찾아내서 버그체크를 띄워준다. 단, Irp가 큐잉된다면(StartPacket을 한다던지 Cancel-Safe Queue에 들어간다던지) 디큐될 때에 자동으로 취소 루틴이 제거된다.
  • Irp를 종료할 때, IoStatus.Status와 리턴 값은 일관성이 있어야 한다.
    즉 IoStatus.Status는 성공코드를 넣고 실패코드로 리턴하는 경우가 있으면 안된다.
    Driver Verifier가 이 오류 또한 적절히 발견해줄 것이다.
  • 디스패치 루틴에서 STATUS_PENDING을 리턴하기 전에는 Irp를 Pending으로 Marking해준다. 반대로 Irp를 Peding으로 마킹해 주지 않으면 STATUS_PENDING으로 리턴하면 안된다.
    둘은 항상 짝이 맞춰져서 다녀야 한다.
  • StartPacket을 호출하고 나면 더이상 IRP를 건드리지 않는다. 함수가 리턴되자마자 Irp는 이미 완료되었을 수 있다.
  • STATUS_MORE_PROCESSING_REQUIRED를 리턴하지 않는 모든 완료루틴에 대해서 다음의 코드를 넣는다.
    if(Irp->PendingReturned)
    {
        IoMarkIrpPending(Irp);
    }

    완료루틴을 설정하지 않았을 경우에는 시스템이 Irp를 완료시켜가면서 상위 스택으로 Pending 비트를 복사해주지만,
    완료 루틴이 설정되어 있으면 시스템이 이 작업을 해주지 않기 때문에 해당 완료 루틴에서 직접 PendingReturned 값을 체크하여 Irp를 Pending으로 마크해주어야 하기 때문이다. -어떤 경우라도 시스템은 PendingReturned 변수만큼은 항상 잘 셋팅해준다.
    만약 잘 이해되지 않는다면 그냥 외우기만 해도 좋다. 이는 언제나 참이다. 단, 완료 루틴에서만이다. 다른 루틴(디스패치 루틴이라던지)과 착각하면 안된다.
  • Lower Driver로 Irp를 건네주는 과정에서 완료루틴을 설정한다면 반드시 IoCopyCurrentIrpStackLocationToNext를 호출해야 한다.(IoSkipCurrentIrpStackLocation은 안된다.)
  • Next Driver에 Irp를 전달한 이후에는 더 이상 Irp는 자신의 소유가 아니며 건드리지 말아야 한다. 그 Irp는 다른 드라이버나 쓰레드에서 free되거나 완료되었을 수 있다. 하지만 만일 드라이버가 스택 아래로 Irp를 건네준 이후에 그 Irp에 접근하고 싶다면 반드시 완료 루틴을 설정해야만 한다.
    Io 매니저가 해당 완료루틴을 호출해 줄 때 완료루틴 내에서 잠시 다시 Irp는 내 드라이버의 소유가 되고, Irp에 액세스 할 수 있다.
    만약 Next Driver가 Irp를 완료 시킨 후에, 내 드라이버의 디스패치 루틴이 그 Irp에 접근해야만 한다면 완료루틴은 반드시 STATUS_MORE_PROCESSING_REQUIRED를 리턴해야만 한다. 이는 Irp의 소유를 디스패치 루틴에게로 건네준다. IO 매니저는 Irp의 처리를 중지하고 디스패치 루틴에게 궁극적으로 해당 Irp의 Completion을 맡긴다.
    디스패치 루틴은 이후에 IoCompleteRequest를 호출해서 Irp를 완료시킬 수도 있고 처리를 더 하기 위해 Pending으로 마킹할 수 있다.
  • 드라이버는 다음과 같은 경우 반드시 STATUS_PENDING을 리턴해야만 한다.
    • Irp가 완료되기 전에 디스패치 루틴이 리턴하는 경우
    • 해당 Irp가 다른 쓰레드에서 완료되는 경우
  • Arbitrary 쓰레드에서는 비동기적 Irp만을 생성할 수 있다. NonArbitrary 쓰레드에서는 동기 Irp도 생성할 수 있다.
  • IRQL이 Dispatch Level 이상인 경우에는 쓰레드를 대기 시켜서는 안된다. 즉, KeWaitFor- 같은 함수 들을 사용할 수 없다는 이야기이다.
    단, 쓰레드를 재우지 않고 단순히 오브젝트가 시그널 되었는지 Peek 해보는 것은 상관없다. 대기 함수들에 타임아웃값을 0으로 넘겨주면 Peek만 시도할 수 있는데, 유저모드에서는 WaitFor함수 패밀리에 dwMilliseconds를 0으로 건네주면 된다.
    커널 모드(KeWaitFor 함수 패밀리)에서는 유저모드처럼 그냥 0을 넣어버려서는 안된다. KeWaitFor 함수에 NULL 포인터를 전달하게 되면 Peek하겠다는 것이 아니라 무제한 기다리겠다는 의미이다. 반드시 LARGE_INTEGER 값을 0으로 셋팅해서 함수에 넘겨주어야만 한다.
    쓰레드를 재우면 안되는 이유는, IRQL이 Dispatch Level 이상이 되면 다시 재스케줄링 될 수 없기 때문이다.
    이는 비단 WaitFor 함수 패밀리뿐 아니라, 쓰레드를 대기 상태로 만드는 모든 함수를 사용 할 수 없다는 의미임을 알아야 한다.
  • IRQL이 디스패치 레벨 이상이 된 경우에는 특정 데이터를 참조하기 위해 페이지 폴트를 일으켜서는 안된다.
    페이지 폴트는 소프트웨어 예외 중 하나인데 사실 이는 디스패치 레벨 상태에서는 페이지 폴트 뿐만이 아니라 모든 경우의 Exception이 발생해서는 안된다는 의미이다. 이는 위에서 설명한 대기 함수를 사용할 수 없는 이유와 같다.
  • #pragma alloc_text(PAGE, Function Name)
    위와 같은 디렉티브를 선언했다면 그 함수의 시작부에 PAGED_CODE 매크로를 사용한다.
    이는 반드시 한 쌍으로 있어야 한다.(둘다 있거나 둘 다 없어야 한다.)
    Pagable에 대한 의미는 따로 설명하지 않는다.
    PAGED_CODE에 대한 더 자세한 내용은 여기에 써두었다.
  • 커널 스택은 x86에서 12K만큼 할당된다. amd64에서는 24K이다.
    단지 자신의 드라이버에서만 커널 스택을 사용할 것이라 착각해서는 안된다. 자신의 드라이버의 상위에 다른 필터 드라이버들이 있을 경우, 해당 필터드라이버가 사용하고 남은 커널 스택만큼을 받아서 사용하게 된다.
    따라서 평소엔 잘 돌아가던 드라이버가 백신같은 필터 드라이버가 설치된 환경에서는 블루스크린이 발생하기도 한다.
    이것은 모든 드라이버에서 최대한 스택을 아껴써야 함을 의미한다.
    유저모드에서 하듯이 다음 줄처럼 코딩 해서는 안되며
    WCHAR sz[MAX_PATH]; // 지역변수 하나가 커널 스택의 약 5%를 써버렸다.

    이는 좀 귀찮더라도 아래와 같은 방법으로 수정되어야 한다.
    PWCHAR sz = ExAllocatePoolWithTag(PagedPool, sizeof(WCHAR) * MAX_PATH, POOL_TAG);
    if(sz == NULL)
    {
        // Process the error.
    }
     
    __try
    {
        // Do something
    }
    __finally
    {
        if(sz)
        {
            ExFreePoolWithTag(sz, POOL_TAG);
        }
    }

  • 커널 스택은 pagedpool에 할당됨을 알고 있어야 한다. -함수가 대기 상태로 되면 시스템에 의해서 언제라도 Page out 될 수 있다.
    Event 객체는 NonPagedPool에 할당되어야 하는데, 스택에 할당했을 경우에는 KeWaitFor 함수의 WaitMode인자로 KernelMode를 전달함으로써 이 스택 메모리가 Page out 되지 않도록 할 수 있다.
  • DriverEntry가 아닌 곳에서 디바이스를 생성한다면, 초기화가 끝난 이후에 DO_DEVICE_INITIALIZING 플래그를 지워줘야 한다. -이는 AddDevice 루틴도 예외가 아니다.
    deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
    이것을 지워주지 않는다면, CreateFile같은 Api로 장치를 열 수 없다.
    만약 DriverEntry에서 만드는 경우에는 리턴되자마자 IoManager가 알아서 플래그를 지워주므로 생략해도 상관없다.
  • 캔슬 큐에 집어 넣고 pend 상태로 처리한 Irp는 나중에 완료하기 직전에 꼭 Cancel Queue에서 제거해주어야 한다.
    이 때 Cancel Queue에서 빼면서 리턴값을 꼭 검사해서 실제로 Queue에 존재했었고 제거된 경우에만 이 Irp를 완료시켜야 한다.

    // pIrpContext는 Insert할 때 넣어주었던 그 변수를 사용해야 한다.
    PIRP pPendedIrp = IoCsqRemoveIrp(&g_pDevExt->cancelIo.CancelSafeQueue, pIrpContext);
    if(pPendedIrp)
    {
        DriverLog(SHOW, "pIrp = 0x%08X\n", pIrp);
        pIrp->IoStatus.Status = status;
        pIrp->IoStatus.Information = information;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    }
    이렇게 해야 여러 요청이 동시에 들어왔을 때의 미묘한 동기화 문제를 피할 수 있다.(그렇지 않으면 같은 Irp를 2번 이상 완료시킬 수 있다)

저작자 표시 비영리 동일 조건 변경 허락
신고
  1. 아저씨 at 2011.05.25 00:14 신고 [edit/del]

    좋은 글 감사합니다.

    Reply
  2. 오곡 at 2013.07.09 18:09 신고 [edit/del]

    WDM 의 핵심들 잘배우고 갑니다~!

    Reply

submit

PAGED_CODE 매크로

2011.02.27 19:09 | Programming
PAGED_CODE 는 다음과 같이 생긴 간단한 매크로이다.
#define PAGED_CODE() { \
    if (KeGetCurrentIrql() > APC_LEVEL) { \
        KdPrint(("EX: Pageable code called at IRQL %d\n", KeGetCurrentIrql())); \
        PAGED_ASSERT(FALSE); \
    } \
}
현재 IRQL을 체크해보고 IRQL이 APC_LEVEL 보다 높다면 시스템을 종료시킨다.

디바이스 드라이버는 유저 모드 프로그램들과는 다르게 텍스트 영역이 페이지 파일로 빠져나가지 않도록 설정된다. 즉 코드가 기본적으로 nonpaged 영역에 할당된다.
Making Drivers Pageable

물론 드라이버 프로그래머에게는 이를 제어할 수 있는 방법 또한 주어지며 코드나 데이터를 페이징 가능하도록 설정 할 수도 있다. #pragma alloc_text나 #pragma data_seg 디렉티브를 사용하면 컴파일 타임에 코드나 데이터들에 대한 페이징 여부를 결정할 수 있다.

드라이버 내의 특정 함수들을 페이징 가능하게 만들고 싶다면 다음처럼 쓴다.
#pragma alloc_text(PAGE, functionName)
이 구문은 해당 함수의 선언보다는 아래에 있어야 하고 정의보다는 위에 있어야 한다. 보통 c 모듈에서 헤더들을 include 한 뒤 그 바로 아래에 쓴다.

#pragma alloc_text 는 코드가 올라갈 섹션을 지정하는데, 섹션이란 PE 파일이 가지고 있는 바로 그 섹션을 뜻하며 위와 같이 쓰면 해당 PE파일에 PAGE라는 이름의 섹션이 새로 추가된다.
그리고 I/O 매니저는 드라이버 이미지를 로드할 때 이미지 내의 섹션 이름 중 앞의 4글자가 PAGE 또는 .EDA 가 있는지 찾아보고 있다면 해당 섹션을 paged pool에 넣어버린다.
이제 해당 코드는 시스템에 의해서 필요없을 땐 언제라도 페이지 파일로 빠져나가게 될 것이다.

디바이스 드라이버의 세계에서 중요한 규칙중 하나는 IRQL이 Dispatch 레벨 이상일 때는 페이지 폴트가 일어나서는 안된다는 것이다. IRQL >= DISPATCH_LEVEL 일 때 페이지 폴트가 일어나게 되면(페이지 폴트 뿐만아니라 다른 어떤 소프트웨어 예외라도 일어나게되면) 시스템은 크래시된다.

위 규칙을 알고 나면 #pragma alloc_text를 이용해서 코드를 페이징 가능하게 만들었을 경우에, IRQL이 DISPATCH_LEVEL 이상인 상태에서 해당 함수가 불릴 경우 페이지 폴트가 발생해서 블루스크린이 발생할 수도 있다는 것을 알 수 있다. 따라서 IRQL이 높을 때 불릴 수 있는 함수들은 paged pool에 할당해서는 안된다. 그런데 운이 좋아서 해당 시점에 코드가 램에 잘 존재하고 있어서 페이지 폴트가 일어나지 않는다면 이는 버그를 감추어주게 되고 나중에 더 골치아픈 문제를 가져다준다.

페이징 가능한 함수들에 PAGED_CODE 매크로를 사용하면 이런 운에 의해 감추어지는 버그들을 없애 버리고 곧장 시스템을 크래시 시켜 버림으로서 문제가 있는 부분을 쉽게 찾아낼 수 있다.
그래서 페이징 가능한 모든 코드에는 함수의 시작부에 PAGED_CODE() 매크로를 사용하는 것이다.

위에서 말한 내용들이 너무 복잡하다면 다음 규칙만 외워서 따라해도 좋다.
#pragma alloc_text를 사용했다면 짝이 맞는 함수를 찾아가 시작부분에 PAGED_CODE 매크로를 써준다.
이 둘을 항상 짝으로 같이 다니게 하면 된다.(둘다 있거나 둘다 없거나)

여기까지 읽었으면,
"빌어먹을, 코드이든 데이터이든간에 무조건 nonpaged pool에 할당하는게 편한 것 아닌가?"
하는 생각이 드는게 정상일 것이다. 물론 그렇게 하면 프로그래머는 좀 더 편해지겠지만, 오랫동안 사용되지 않고 있는 코드와 데이터들이 계속 물리 메모리를 점유하고 있기 때문에 시스템의 성능을 떨어뜨릴 수 있다. 특히 넷북같은 꼬물 컴퓨터에서는 더 많은 영향을 끼칠 것이다.
저작자 표시 비영리 동일 조건 변경 허락
신고

'Programming' 카테고리의 다른 글

_countof 매크로  (0) 2011.03.15
FIELD_OFFSET 매크로  (1) 2011.03.01
PAGED_CODE 매크로  (5) 2011.02.27
디렉터리의 읽기 전용 속성  (4) 2011.02.20
알쏭달쏭한 typedef  (9) 2011.01.04
하위 디렉터리의 파일이 변경 되었는지 감지하는 법  (8) 2010.12.20
  1. 오곡 at 2013.07.09 17:40 신고 [edit/del]

    잘배우고 갑니다~!

    Reply
  2. 없다캐라 at 2014.02.11 17:50 신고 [edit/del]

    저두 잘 배우고 갑니다. prama alloc 관련 검색을 해서 알게되어 왔는데 다른 글보다 이해하기 쉬웠습니다.

    Reply
  3. Favicon of http://minosori.tistory.com BlogIcon minosori at 2014.12.05 14:53 신고 [edit/del]

    안녕하세요,
    좋은 글 잘 읽고 있습니다. 감사합니당~
    그런데, 이해가 안돼서 질문드려요;;
    PAGED_CODE() 매크로를 써서 IRQL이 DISPATCH_LEVEL 이상이면 해당 코드가 페이지되어있든 안되어있든, 그리고 페이징 가능한 코드로 만들어져 있든 말든 무조건 크래시를 발생시키잖아요?(이것도 블루스크린으로 뜨나요?)
    그럼 메모리 절약을 위해서 해당 함수를 페이징 가능한 코드로 만들었다고 했을 때, 크래시가 일어나지 않고 다시 메모리로 로드 되도록 하려면 어떻게 해야하나요?
    현재, Driver Unload때만 섹션을 PAGE로 할당하고 나머지는 디폴트로(모두 Non Paged Area에 들어가게 되죠?) 만들고 돌려보니, NON_PAGED_AREA에서 PAGING됐다고 블루스크린이 뜹니다...ㅜ_ㅡ
    설마, 헤더파일과 소스파일을 여러개로 나눠서 작업하는거랑 관련이 있나요? 디바이스 드라이버는 전혀 생각지도 못한 부분에서 문제가 터지는 경우가 많아서 혹시나 싶어서 여쭤봅니다...;

    Reply
    • Favicon of http://www.benjaminlog.com BlogIcon 김재호 at 2014.12.05 15:37 신고 [edit/del]

      PAGED_CODE() 매크로를 써서 IRQL이 DISPATCH_LEVEL 이상이면 해당 코드가 페이지되어있든 안되어있든, 그리고 페이징 가능한 코드로 만들어져 있든 말든 무조건 크래시를 발생시키잖아요?(이것도 블루스크린으로 뜨나요?)

      -> 네 블루스크린이 발생합니다.

      그럼 메모리 절약을 위해서 해당 함수를 페이징 가능한 코드로 만들었다고 했을 때, 크래시가 일어나지 않고 다시 메모리로 로드 되도록 하려면 어떻게 해야하나요?

      질문을 잘 이해하지 못하겠지만, 원글에 써있는대로 함수를 페이징 가능한 코드로 만들었다면 해당 함수가 IRQL 이 디스패치 레벨보다 낮을 때만 불려야 합니다.
      pagefault in non paged area 라는 에러는 드라이버를 작성하면서 가장 흔히 볼수있는 오류이며, 위 규칙만 잘 지켰다고 해결되는 것은 아닐 껍니다. 동적으로 할당하는 메모리들을 페이지드 풀에 할당했는지도 다 꼼꼼하게 살펴봐야하는데, dump파일을 windbg 로 잘 분석해보시면 어디가 문제인지 찾으실 수 있을 겁니다.

    • Favicon of http://minosori.tistory.com BlogIcon minosori at 2014.12.05 16:54 신고 [edit/del]

      동적 메모리 할당할 때는 분명 NonPagedPool로 할당했는데... 아직 원인을 알 수 없네요. 메모리 할당부분을 주석처리 했더니 크래시가 나지 않는걸 보니 분명 메모리 할당부분에 문제가 있는것 같긴 한데, 아직 초보라 분석하려면 오래 걸릴것 같네요;;
      아무튼 답변 감사합니다. :)

submit
CreateFile은 Wndows Api 들 중 가장 기본적이면서도 중요한 함수이다.
이 함수는 단지 파일을 생성하는 것 뿐만이 아니라 파일을 오픈할 수도 있고 디렉터리를 오픈할 수도 있으며 또한 여러 디바이스들까지 오픈 할 수 있다. 사실 CreateFile에서 File은 꼭 파일만이 아닌 여러 디바이스들을 추상화한 Virtual File을 뜻하는 셈이다.
이 함수를 통해 파일을 여는 순간에 동기 I/O를 할지 비동기 I/O를 할지 결정하게 되며, 내가 어떤 작업을 하려는지 내가 파일을 열고 있는 동안 다른 클라이언트들에게는 어떤 작업을 허용할지도 결정하게 된다.
생성하려는 파일의 읽기 전용, 숨김 파일 등의 속성도 정할 수 있으며, 캐시를 이용 할지 말지, 쓰기를 하는 족족 플러시 하게 할지 또 I/O를 순차적으로 할지 랜덤하게 할지 등의 힌트도 파일 시스템으로 전달해줄 수 있다.

이렇게 중요한 함수이니만큼 MSDN에는 CreateFile에 대한 문서가 아주 잘 나와있는데, 페이지 내의 링크들까지 하나하나  따라가면서 차근 차근 읽다보면 시스템 프로그래밍에 대해 배울 수 있는 것들도 많고 무엇보다도 아주 재밌다.

하지만 아무리 열심히 읽어도 글만 읽고서 지식을 자기 것으로 만들 수는 없는 법이다.
언제나 마지막은 실습으로 끝나야 한다. 글을 다 이해한 것 같아도 막상 진짜로 해보려고 하면 거기서 또 어려운 문제가 닥치기 마련이며, 이 것까지 해결하고 나서야 비로소 완전히 자기 지식으로 만들었다고 할 수 있다.

CreateFile의 파라메터는 몇 개 안되는 것 같지만 엄청난 플래그들의 조합이 가능하기 때문에 사실은 적은 갯수가 아니다.
실습을 해보기 위해서 항상 무거운 비주얼 스튜디오를 켜고 그 지겨운 파라메터들을 매번 입력하는 것은 손가락도 아프고 시간도 많이 들어가는 비효율적인 방식이다.

이런 실습을 위해 누군가가 이미 아주 훌륭한 도구를 만들어서 osronline.com에 올려놓았다.
이는 프로세스 모니터와 함께 내가 가장 즐겨쓰는 도구들 중 하나인데, 병들어가는 내 손가락을 조금이나마 쉴 수 있게 해주는 아주 고마운 친구이다.


위 그림에서 보이는 것 처럼 CreateFile 함수 형태 그대로 UI를 제공하고, 실험해보고 싶은 모든 플래그 조합을 넣어볼 수 있다.
생성뿐만이 아니라 읽기 쓰기도 해볼 수 있으며 조금 더 저수준 함수인 NtCreateFile까지도 다루어볼 수 있다.

우측의 버튼들을 클릭하면 아래처럼 또 다른 대화상자가 나와서 CreateFile의 많은 옵션들을 손쉽게 넣어서 테스트 할 수 있다.



파일 시스템과 관련이 있는 일은 하는 사람들은 두말 할 것도 없고, Wndows 플랫폼에서 개발하는 모든 개발자들이 알아두면 좋을 훌륭한 도구이다.
저작자 표시 비영리 동일 조건 변경 허락
신고
  1. 재호님 팬 at 2010.12.29 12:14 신고 [edit/del]

    재호님~ 파일시스템에 관심이 많으신거 같네요!
    저도 파일시스템에 관심있어요 ^^
    이병오님의 "윈도우 파일시스템" 책과 정명수님의 커널관련글 읽으면서 공부하고 있는데 좋은거 같습니다. 재호님도 화이팅! ^^

    Reply
    • Favicon of http://www.benjaminlog.com BlogIcon 김재호 at 2010.12.29 12:37 신고 [edit/del]

      윈도 파일시스템 책은 저도 가지고 있는데 정명수님 커널 관련글은 잘 모르겠어요. 혹시 글들 정리되어 있는 URL이 있으면 좀 가르쳐주세요.^^

  2. 재호님 팬 at 2010.12.29 20:19 신고 [edit/del]

    이런 제가 센스가 없어서 ㅋㅋ
    www.swblog.net 입니다. 저도 마이크로소프트 잡지를 통해서 알게 되었구요~ 이분 글로 공부하고 있어요 ^^

    Reply

submit
SysInternals의 대부분의 유틸리티들은 하나의 실행파일로 배포된다.

프로세스 모니터 같은 도구는 시스템 내에서 일어나는 모든 I/O등을 잡아채서 보여주는데 이 역시 EXE 파일 하나로 배포된다. 그렇다면 마크 러시노비치가 이것들을 유저모드 애플리케이션에서 구현했다는 말인가? 아니, 심지어 이런 것들을 유저모드에서 구현할 수나 있는 것일까?
물론 그렇지는 않다. 프로세스 모니터는 파일 시스템 레이어 상위에 장착되는 필터 드라이버와 응용 프로그램으로 구성되어져 있다. 아마도 필터 드라이버가 열심히 I/O를 엿본 다음에 그 정보를 잘 정리해서 응용 프로그램에게 전달한 뒤 응용 프로그램이 GUI로 출력하도록 구현되어져 있을 것이다.

그렇다면 프로세스 모니터의 드라이버 이미지는 어디에 숨어있는 것일까?
드라이버는 바로 EXE 파일 내의 리소스에 있다.


응용 프로그램 내에 커스텀 리소스를 하나 만들어서 그 곳에 바이너리 파일을(여기서는 프로세스 모니터 드라이버 이미지) 쑤셔 넣어둔다.
응용 프로그램이 처음 실행되면 FindResource, LoadResource, LockResource, SizeofResource 함수들을 사용해서 이 리소스를 읽어 오고 CreateFile과 WriteFile 같은 함수를 통해서 새로운 파일을(바로 그 프로세스 모니터 드라이버) 디스크 상에 만들어낸다.
이런 식으로 말이다.
HRSRC h = FindResourceW(0, MAKEINTRESOURCEW(IDR_BIN), L"BIN");
HANDLE hRes = LoadResource(0, h);
CONST CHAR* p = (CONST CHAR*)LockResource(hRes);
HANDLE hFile = CreateFileW(L"D:\\bin", GENERIC_ALL, 0, 0, CREATE_ALWAYS, 0, 0);
DWORD cb = SizeofResource(0, h);
DWORD dw;
WriteFile(hFile, p, cb, &dw, 0);
UnlockResource(hRes);
너무 간단하지 않은가?

이제 드라이버 파일이 생겼으므로 응용 프로그램 코드에서는 CreateServiceStartService 함수들을 통해서 드라이버를 설치하고 구동시킬 수 있다.
응용 프로그램이 종료 될 때는 물론 드라이버를 잘 멈추고 제거하고 파일을 지워버리는 등의 작업 또한 해주어야 할 것이다.

저작자 표시 비영리 동일 조건 변경 허락
신고
  1. 관우 at 2011.01.05 23:15 신고 [edit/del]

    너무 궁금했는데 이렇게 간단할 줄은 몰랐네요. ㅎㅎ 잘 배워 갑니다.

    Reply

submit
osronlinepooltag reporter를 사용하면 디바이스 드라이버들이 사용하는 커널 힙 메모리의 태그들과 그 용량을 GUI를 통해 확인할 수 있다.
어떤 풀 태그를 사용해서 논 페이지드 영역이나 페이지드 영역에 얼마만큼의 메모리를 할당하고 또 해제했는지를 실시간으로 보여준다.
디바이스 드라이버에서의 메모리 누수 등을 확인하고 싶을 때 주로 사용되며, 특정 태그들만 골라내서 보여줄 수 있는 기능이 있는데 와일드 카드도 지원이 되어서 편리하다.

디바이스 드라이버를 개발하며 디버깅하다보면 남의 풀 태그들을 보고서 이건 누구의 태그일까 궁금할 때가 많이 있는데, 윈도우즈에 기본으로 탑재되어 있는 드라이버들의 태그는 알아낼 수 있는 방법이 있다.

WDKDebugging Tools for Windows를 설치하면 <debugger>\triage\pooltag.txt 라는 텍스트 파일이 있는데, 이 곳에 태그의 주인이 누구인지 적혀있다.

예를 들어 WDK를 설치했다면,
C:\WinDDK\7600.16385.1\Debuggers\triage\pooltag.txt 와 같은 경로가 될 것이다.

C:\>pooltag -g C:\WinDDK\7600.16385.1\Debuggers\triage\pooltag.txt

pooltag를 실행할 때 이렇게 뒤에 인자를 넣어주면 풀태그 애플리케이션이 텍스트 파일을 파싱해서 UI에 함께 보여준다.

참고로 논 페이지드 풀은 32비트 운영체제에서 물리메모리의 75%나 2GB중 작은 값만큼 최대로 할당할수 있으며, 64비트 운영체제에서는 물리 메모리의 75%나 128GB 중 작은 값 만큼 할당할 수 있다.
저작자 표시 비영리 동일 조건 변경 허락
신고

submit
유저모드에서는 CancelIo 함수를 통해서 해당 장치에 들어간 모든 I/O를 취소할 수 있고 CancelIoEx 함수를 통해서 특정 비동기 I/O만을 취소할 수도 있다.
비스타 이후부터는 CancelSynchronousIo 함수를 통해 CreateFile 같은 동기 함수도 취소할 수가 있다. 비동기가 지원이 되지 않는 함수들은 CreateFile 처럼 금방 수행되는 함수들인데, 이런 함수들을 과연 취소할 필요가 있는가 생각이 들수도 있지만, Network-redirector(SMB 같은)를 이용하여 원격지에 있는 파일에 접근할 경우 네트워크가 지연될 때 응용 프로그램에 꽤 오랜 블록킹이 발생할 수가 있다. 이런 함수를 잘 알고 이용하면 조금 더 응답성이 좋은 애플리케이션을 만들 수 있다.

사실 유저모드에서야 CancelIo를 부를 필요도 없이 애플리케이션을 꺼버리거나 I/O를 하는 장치의 핸들을 닫아버리면 알아서 취소가 되기 때문에 I/O의 취소에 대해서 그다지 고민할 일이 없지만 -대부분의 응용프로그래머들은 CancelIo같은 함수에 크게 관심을 갖지 않는다- 디바이스 드라이버에서는 조금 다르다.

디바이스 드라이버가 I/O의 취소를 제대로 구현해주지 않으면, 애플리케이션이 종료될 때에 Irp가 취소되지 못하고 드라이버에게 계속 잡혀있어서 애플리케이션이 제대로 종료되지 않은 채 계속 좀비로 남아있다거나, 운영체제가 셧다운되지도 않는 몹시 나쁜 상황을 맞이할 수 있기 때문에 I/O 취소의 올바른 구현은 필수적이다. -이런 경우를 조금이라도 방지하기 위해서 윈도우즈는 5분이 지나면 해당 Irp의 데이터구조는 삭제하지 않은채 취소를 시켜준다. 이것은 엄밀히 말하면 I/O의 취소라고는 할 수 없다.

그럼 디바이스 드라이버에서는 I/O를 어떻게 취소하는가.
디바이스 드라이버에게 I/O의 취소라는 것은 단순히 STATUS_CANCELLED 상태로 Irp를 완료시키는 것이다.
Irp에는 취소 루틴의 포인터가 담겨있는데, 우리가 이곳에 취소 로직을 적절히 구현하여 넣어주면 애플리케이션이 I/O의 취소를 요청할 때 I/O 매니저가 이 취소루틴을 호출 해주고 Irp는 취소로 완료될 수 있다.
하지만 드라이버는 취소루틴에서 STATUS_SUCCESS로 완료시켜버릴 수도 있고, STATUS_CANCELLED로 리턴하더라도 그 바로 직전 I/O가 정말로 완료되었을 수도 있기 때문에 유저모드에서 CancelIo 등을 사용해서 I/O가 완전히 끝났는지 잘 취소되었는지를 검사하는 것은 사실 별로 의미가 없다. 그냥 취소 했다는 것에만 의미를 두면 된다.

취소 루틴에 대해 간단하게 이야기 했지만,  취소루틴을 구현한다는 것은 생전 만나보지 못한 어려운 경쟁 상태를 해결해야 하기 때문에 많은 어려움이 따른다.
이런 어려움을 해소해주기 위해 마이크로소프트에서는 언제부턴가 Cancel-Safe Queue 라이브러리를 제공해주고 있다.

나는 다행히도 꽤 편한 세상에서 태어났고 디바이스 드라이버의 세상에 입문한지 얼마 되지 않았기 때문에, Cancel-Safe Queue를 사용하지 않고 직접 취소를 구현하는 드라이버는 구현해보지 않았다. -진심으로 다행이라 생각한다.

Cancel-Safe Queue를 이용하면 이런 골치 아픈 동기화 처리를 직접하지 않아도 된다.
우리는 라이브러리 루틴 내에서 제공하는 몇 가지 콜백함수들만 적절히 구현해주면 되는데, Csq 라이브러리가 자신들의 취소루틴을 붙였다 떼었다 하면서 우리의 콜백 루틴들을 동기화까지 포함해서 적절히 호출해주기 때문에 우리는 취소루틴을 제공할 필요도 없다. 대단하지 않은가. 이런 방식의 라이브러리를 제공한다는 것은 보통 일이 아니다.

앞으로 점점 많이 사용될 WDF에서는 우리가 취소에 관해 알아야 할 것들이 더욱 줄어들기 때문에 이런 처리가 더욱 쉬워지는데, 아직 WDF를 공부해보지 않아서 어떻게 동작하는 것인지는 모르겠다.

Cancel-Safe Queue의 예제 코드는 WDK 샘플 코드의 /src/general/cancel 위치에 있다.

Csq를 사용하기 위해 우리가 구현해줘야 할 콜백함수들은 다음과 같다.

  • XxxCsqInsertIrp
  • XxxCsqRemoveIrp
  • XxxCsqPeekNextIrp
  • XxxCsqAcquireLock
  • XxxCsqReleaseLock
  • XxxCsqCompleteCanceledIrp

이름에서 볼 수 있듯이 자료구조에 Irp를 넣고 찾고 빼고 잠그는 루틴들을 우리가 구현해주면 되는 것이다.
즉, 자료 구조와 동기화 방식을 우리가 결정할 수 있다. 자료구조는 거의 링크드 리스트를 이용하며, 어떤 커널모드 동기화 방식을 써서 구현해도 상관없지만 성능을 위해 보통 스핀락을 사용한다.
Csq를 쓰면 전역 캔슬 락을 사용해서 구현한 기존의 드라이버들 보다 성능에도 이점이 있다.

각 콜백 함수 구현에 대한 코드는 샘플 코드에서도 찾아볼 수 있지만, 이 문서에는 설명까지 덧붙여 잘 나와있으므로 한 번쯤 읽어보는 것이 좋겠다. 보통의 경우에는 샘플 코드를 복사해서 쓰는 것으로 충분할 것이므로 여기에 따로 코드를 적지는 않는다.

이 루틴들을 다 구현했으면 IoCsqInitialize 함수로 적절한 곳에서 초기화를 하며 콜백 함수들을 등록시켜준 뒤에, Irp가 들어올 때 IoCsqInsertIrp함수를 통해 큐에 집어넣고 나서 I/O 작업을 한다. 작업이 끝나면 IoCsqRemoveIrp함수를 통해 큐에서 빼고 잘 제거된지 확인 한 후에 여느 때처럼 IoCompleteRequest 함수로 Irp를 완료시켜주면 된다. 도중에 애플리케이션들로부터 취소가 요청되면 Csq가 적절히 우리가 작성한 취소 로직들을 이용하여 취소를 수행 해줄 것이다.

위의 설명에서 몇 가지 추가 설명을 해야 할 것들이 있는데, 콜백함수는 우리가 직접 부르는 것이 아니다. 우리 코드에서는 IoCsqInsertIrp처럼 IoCsqXxx 루틴들을 사용 한다. 이 루틴들이 내부에서 우리의 콜백함수를 적절한 곳에서 호출해줄 것이다.

초기화 할 때 IoCsqInitialize가 아니라 IoCsqInitializeEx함수를 이용하면 IoCsqInsertIrpEx 함수를 통해 추가적인 컨텍스트를 담을 수 있다. 이런 추가적인 컨텍스트는 우리가 Queue를 사용하는데 있어서 더 많은 유연함을 가능하게 한다. 

Csq를 사용할 때는 Csq에 관련된 데이터구조가 Irp->Tail.Overlay.DriverContext[3] 에 보관된다.
그러므로 Csq를 사용할 때는 이름이 DriverContext라고 해서 이 곳에 함부로 아무 데이터나 담아서는 안되겠다. 참고로 유저모드 파일시스템 드라이버 프레임워크인 Dokan에서는 Csq를 사용하지 않고 직접 취소루틴을 구현했는데, DriverContext[2]와 [3]에 추가적인 데이터를 담아 사용하고 있다.

위에서 작업이 끝나면 IoCsqRemoveIrp 함수를 통해 큐에서 빼고 잘 제거되었는지 확인하라고 했는데, 이는 그 사이에 취소가 들어왔을 경우 큐에서 이미 빠져버렸을 수 있기 때문이다. 만일 NULL이 리턴되었다면 도중에 취소 요청이 들어와 큐에서 이미 빠진 것이다. 그 Irp는 곧 XxxCsqCompleteCanceledIrp 루틴에 의해 완료되게 될 것이고 이 Irp를 우리가 또 완료시켜서는 안된다.

마지막으로 IRP_MJ_CLEANUP 디스패치 루틴에서는 내 디바이스의 핸들이 닫히는 경우에 큐에 Pending되어 있는 Irp들을 모두 완료 시켜주어야 한다.

저작자 표시 비영리 동일 조건 변경 허락
신고
  1. 디바이스 드라이버 at 2010.10.25 12:44 신고 [edit/del]

    우연히 찾게 되었는데 무척 좋은 글 잘 읽고 갑니다.^^

    Reply

submit
커널 모드에서 코드를 작성한다는 것은 유저모드에서 보다 어려운 사항이 많이 있다.
언어도 자유롭게 사용할 수 없고, 디버깅도 힘들며, 한 줄이라도 실수하면 여지없이 블루스크린이 발생한다.
그럼 커널 모드 디바이스 드라이버와 같은 것들을 유저 모드에서 구현할 수 는 없을까.

리눅스에는 FUSE라는 것이 있다.
File system in User Space 라는 뜻인데, 유저모드에서 파일 시스템을 구현하도록 제공되는 인터페이스이다.


윈도우에도 물론 비슷한 것들이 있다.
상용 제품인 Callback File System은 콜백 인터페이스를 제공하고 유저모드에서 이 콜백 인터페이스를 구현하기만 하면 CBFS가 알아서 이런 콜백들을 불러준다.


위 그림에서 보면 우리는 Your Application 부분만을 구현하면 되는 것이다.
우측에 있는 Callback File System에서 ReadFile WriteFile등 우리가 미리 등록해둔 콜백 오퍼레이션들을 호출해 줄텐데, 그런 함수들이 호출되면 파일들을 읽고 쓰도록 구현하면 된다.

내 또래의 일본인이 혼자서 열심히 만들고 있는 것 같아 보이는 Dokan 이라는 오픈소스도 있다.


파일 시스템 애플리케이션(우측 초록색)이 처음 구동되면 워커 쓰레드를 여러개 만들어 DeviceIoControl 함수를 호출해 Dokan File System Driver(아래 파랑색)에게 집어 넣어놓는다. DevceIoControl 함수는 비동기 호출도 가능하고 IOCP도 지원이 되지만 Dokan에서는 간단하게 구현하기 위해서 쓰레드를 여러개 만들어 동기적으로 호출한다.
애플리케이션들로(좌측 초록색) 부터 I/O가 들어오면 Dokan Driver(아래 파랑색)가 이 Irp들을 받아서 잘 정리한 뒤 파일 시스템 애플리케이션(우측 초록색)이 미리 넣어두었던 DeviceIoControl의 버퍼에 데이터를 복사하고 완료시킴으로 유저모드로 작업을 위임한다. 파일 시스템 애플리케이션에서는 해당 이벤트가 뭔지 확인해본 뒤에 실제로 처리를 한 후 파일 시스템 드라이버에게 다시 그 결과를 전달해준다. 그러면 파일 시스템 드라이버는 받은 결과 그대로 애플리케이션들의(좌측 초록색) Irp를 완료시킨다.

이렇게 드라이버가 유저모드의 구현을 위한 인터페이스만을 제공함으로서 파일 시스템 로직 구현을 유저모드로 옮길 수 있으며, 유저모드 개발시의 여러 장점들을 가져올 수 있다.
그림에서 보이는 것처럼 두번씩 왔다 갔다 해야하는 것이 성능의 저하를 가져올 수 있고, 유저모드로 구현이 넘어감에 따라 커널 모드 라이브러리 루틴들을 마음껏 쓸 수 없다는 것은 단점이라 할 수 있겠다.
저작자 표시 비영리 동일 조건 변경 허락
신고
  1. 오곡 at 2012.11.26 11:04 신고 [edit/del]

    좋은 내용 잘봤습니다 ^^

    Reply

submit
코드로 읽는 리눅스 디바이스 드라이버 - 8점
스리크슈난 벤카테스와란 지음, 박재호 옮김/에이콘출판
에이콘 출판사에서 코드로 읽는 리눅스 디바이스 드라이버라는 새 책이 출간되었다.
이 책의 원제는 Essential Linux Device Drivers이며 2008년도에 발매되었다.

나는 아마존에서 'device driver' 로 자주 검색을 해보는데 이 책은 언제나 1위로 검색이 되어서 잘 기억하고 있다.

리눅스건 윈도우건 디바이스 드라이버에 대한 책은 그렇게 많지 않은데다가 2000년대 초반, 심지어 90년대의 책들이 수두룩하다. 생각해보니 윈도우가 리눅스보다 더 심한 것 같다.
디바이스 드라이버 세계에서 2008이라는 숫자는 엄청난 최신 버전이므로 이런 책이 번역되어져 나왔다는 것은 참 반가운 일이 아닐 수 없다.

지금은 윈도우 드라이버만 만들고 있지만 앞으로 리눅스에서 드라이버를 개발하게 될지도 모르고, 언제나 그렇듯이 다른 플랫폼을 공부하는 것은 현재 플랫폼을 잘 이해하는데 큰 도움이 되기 때문에 이 책도 꼭 읽어볼 생각이다.

책 목차를 보면 상당히 방대한 부분을 다루고 있는데, 얼마나 자세한 내용인지는 모르겠다.
나는 FUSE를 통해 파일 시스템을 만드는 것에 특히 관심이 있는데 이런 내용은 없는 것 같아서 좀 아쉽긴 하다.

반가운 점이 또 하나 있는데 바로 책의 가격이다.
이 책의 정가는 35,000원인데, 나는 책의 가격과 출판사를 몇번이나 눈알을 왔다 갔다 하며 쳐다보았다.
에이콘의 책은 가격이 아주 비싼 편인데, 이제부터는 가격을 좀 낮게 책정하기로 결정한건지는 모르겠지만 어쨌거나 독자들에게는 좋은 일이다. 앞으로도 잘 부탁해요 에이콘. 크크.

오늘 아침에 잠시 조엘의 책을 읽는데 재밌는 내용이 있었다.
그가 인터뷰를 하거나 혹은 이력서를 읽을 때의 이야기이다.

나는 자바보다 한결 오래된 언어인 OCaml 로 작업한 사람을 보고 매우 감동 받은 적이 있다. 또 아득한 향수가 깃든 어셈블러나 디바이스 드라이버 또는 커널로 작업한 프로그램을 보면 비주얼 베이직이나 PHP로 작업한 것보다 한결 가슴이 뭉클해진다.

디바이스 드라이버를 만드는 사람들이나 공부하는 사람들에게 조금이나마 힘이되는 글 아닌가? 크크.

좋은 책을 번역해주신 역자께 감사한다.


저작자 표시 비영리 동일 조건 변경 허락
신고
  1. Favicon of http://jhrogue.blogspot.com BlogIcon jrogue at 2010.08.28 17:41 신고 [edit/del]

    이벤트 당첨 선물을 보내드리려고 합니다. 우편물 받으실 주소를 jrogue 에뜨 쥐메일.com으로 보내주시면 감사하겠습니다. ;)

    - 박재호 올림

    Reply
  2. at 2010.09.01 17:35 [edit/del]

    비밀댓글입니다

    Reply
  3. Favicon of http://namoda.springnote.com BlogIcon 나모 at 2010.10.28 14:10 신고 [edit/del]

    박재호님이 옮기신 책을 김재호님이 블로깅하셨군요. ^^

    Reply

submit
이제는 마이크로소프트에 흡수된 구 Sysinternals가 만든 여러 유용한 툴 중 Process Monitor는 내가 가장 좋아하는 툴이다.
많은 사람들이 Process Explorer만을 사용하는데, 아마도 Process Explorer의 직관적인 사용자 인터페이스 덕분이리라.
Process Monitor는 잘 사용하려면, 툴에도 익숙해져야 하지만 Windows API를 많이 알고 있어야 하기 때문에 개발자가 아닌 QA팀 같은 곳에서는 사용하기 힘들다.
하지만 프로세스가 수행하는 모든 동작들을 잡아채서 보여주기 때문에 어떤 응용프로그램을 분석할 때 유용한 많은 정보들을 얻어 낼 수 있다.

DbgView 또한 우리 개발자들이 디버깅 할 때 많이 사용하는 애플리케이션 중 하나다. 응용프로그램이나 디바이스 드라이버에서는 OutputDebugString이나 DbgPrint 같은 함수로 로그를 작성한 후 이 툴을 통해서 프로그램의 상태를 추적하고는 한다.

파일 시스템 필터드라이버를 만들게 되면 이 두가지를 같이 병행하고 싶은 경우들이 생길 수 있다.
응용프로그램들이 어떤 Irp를 보내는지를 모니터하고, 내 필터 드라이버는 어떤 동작을 하는지를 함께 보고 싶은 것이다.

두가지 툴을 번갈아 가면서 쳐다보는 일은 순서를 제대로 예측하기가 어렵기 때문에 정말 열받는 일인데, 이번에 프로세스 모니터에서 이런 기능을 해결하기 위한 인터페이스가 하나 추가되었다.

이 아이디어는 디버깅 애플리케이션으로 유명한 존 로빈스생각해내고 제안했는데, 현재 MS 최고의 프로그래머 중 하나인 마크 루시노비치를 자신의 꼬붕 프로그래머라고 농을 치는 것이 너무 웃긴다.

What I really wanted was for my trace statements to be part of the Process Monitor viewing so that way it would be trivial mapping the I/O activity to operations in my code. Fortunately, I have a personal developer at my disposal that is keen to tackle these kinds of challenges. He’s a very nice guy named Mark Russinovich who happens to be the author of Process Monitor. Mark is always eager to hear feature requests for his tools and I think he’s implemented at least 30 features in Sysinternals tools over the years that I thought would be great to have. Don’t hesitate to email Mark with feature ideas so he can be your personal developer as well.

존 로빈스의 유머 감각은 정말 끝내주는데 그의 디버깅 애플리케이션만큼 웃기는 컴퓨터 책을 아직도 만나보지 못했다.
이런 멋진 해커이자 명저자가 다시는 책을 안쓰기로 결정한 것은 정말 슬픈 일이다.

어쨌거나 마크는 콘트롤 코드를 하나 추가해서 DeviceIoControl 함수를 통해 인터페이스 할 수 있도록 기능을 제공해주었고, 최신버전의 프로세스 모니터를 보면 도움말에서 아래 코드를 찾아볼 수 있다.

#define FILE_DEVICE_PROCMON_LOG 0x00009535
#define IOCTL_EXTERNAL_LOG_DEBUGOUT (ULONG) CTL_CODE( FILE_DEVICE_PROCMON_LOG, 0x81, METHOD_BUFFERED, FILE_WRITE_ACCESS )

int main( int argc, char * argv[] )
{
  HANDLE hDevice = CreateFile( L"\\\\.\\Global\\ProcmonDebugLogger", 
                       GENERIC_READ|GENERIC_WRITE,
                       FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
                       NULL,
                       OPEN_EXISTING,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL );

  if ( hDevice != INVALID_HANDLE_VALUE ) {
    WCHAR text[] = L"Debug out";
    DWORD textlen = (_wcslen(text)+1) *sizeof(WCHAR)
    DWORD nb = 0;

    BOOL ok = DeviceIoControl( hDevice,
               IOCTL_EXTERNAL_LOG_DEBUGOUT, text, textlen, NULL, 0, &nb, NULL );

    if ( ok ) {
      printf( "wrote %d\n", i );
    } else {
      printf( "error 0x%x\n", GetLastError() );
    }
  } else {
    printf( "error %d opening Process Monitor\n", GetLastError() );
  }
  return 0;
}
존 로빈스는 .NET과 C/C++에서 좀 더 편하게 사용할 수 있는 Wrapper 코드를 만들어서 올려놓았다.
커널 드라이버에서 사용하고 싶다면,
ZwDeviceIoControl
ZwDeviceIoControlFile 함수를 통해서 직접 Wrapper를 작성해야 한다. 존도 이제는 늙어서 커널 코드는 만들어주기가 귀찮은가보다.


이런 식으로 애플리케이션이나 드라이버에서 프로세스 모니터에게 직접 디버그 메세지를 보낼수가 있다.

기존에 OutputDebugString이나 DbgPrint로 찍은 함수를 Process Monitor가 잡아채서 찍어주는 것이 아니고, 우리가 직접 커스텀 함수를 호출해야만 로그 메세지를 보낼 수 있는 것에 주의하자.
즉, 꽁짜로 얻을 수 있는 것은 아니고 기존에 사용하던 애플리케이션이나 드라이버의코드를 고쳐야 한다는 것.(래퍼 함수를 작성하고, 필요한 곳에서 호출하는 만큼의 비용은 지불해야 한다.)

저작자 표시 비영리 동일 조건 변경 허락
신고

submit
Programming The Microsoft Windows Driver Model 2nd Edition - 10점
Walter Oney 지음, 소현우 옮김/정보문화사

드라이버 공부를 하기 위해 이런 저런 책들을 찾아봤는데 이 책 만큼 좋은 책은 없는 것 같다.

이 책은 WDM 드라이버를 만들기 위해서 알아야 할 모든 내용을 다루고 있는데, 2004년도에 국내에 번역이 되었고 Windows XP 까지 다루고 있으며, 현재는 절판되었다.

비스타나 Windows 7이 나왔다고해서 이 책의 3판이 나올 것 같지는 않다.
이제는 새 책이 나오더라도 WDF에 대한 책이나 몇 권 더 나오고 말지 않을까. 그나마 드라이버에 관한 새 책이 나오더라도 어디에서도 번역하려하지 않을까봐 두렵다.

이 책은 전체적으로 사람들이 궁금해할만한 내용들에 대해서 잘 설명해주고 있으며, 그 중 5장의 Irp의 처리과정은 이 책의 꽃이라 할 수 있다. 나는 이 챕터를 5번도 넘게 읽은 것 같다.
맨 처음 읽을 때는 도대체 무슨 소리인지 알수가 없었지만 한번씩 다시 읽을 때마다 점점 명확해져 가는 것을 느낀다.

책의 내용 중 설명이 부족하거나 잘 이해가 가지 않는 내용이 있으면 ReactOS의 코드를 함께 살펴보고는 하는데, 이는 아주 많은 도움이 된다.
물론 ReactOS의 구현이 윈도우즈의 구현과 완전히 같을 것이라 믿어서는 안되지만 참고용으로 보기에는 아주 훌륭하다.
ctags 같은 도구를 통해 전체 코드를 태깅 해놓고 필요할 때 빠르게 찾아갈 수 있도록 해두면 좋을 것이다.

재미있는 것은, ReactOS의 코드를 읽으면서 코드 작성자 중 눈에 익은 이름을 하나 발견했는데 바로 Windows Internals 5판의 공동저자로 참여한 Alex Ionescu 였다.

어느 날, 이런 ReactOS가 잘 돌아는갈까 갑자기 궁금해져서 이미지를 받아 설치해봤었는데, Windows 95보다도 못한 그 조잡함에 경악을 하며 동시에 마이크로소프트가 얼마나 대단한지 새삼 깨달았다.
ReactOS는 10년넘게 개발되면서 아직도 버전이 0.3인데, 얼른 얼른 발전해서 참고할 수 있는 코드들이 더 많아졌으면 좋겠다.

다음 글들은
osronline
osronline
과 msdn에서 찾은 윈도우즈의 Irp 처리 방식을 다루는 좋은 문서들이다.
이 책에서 이미 이 내용들을 다 설명하고 있지만, 함께 참고해서 읽다보면 이해를 도와줄 것이다.
신고
  1. at 2010.09.29 00:14 [edit/del]

    비밀댓글입니다

    Reply
  2. at 2011.01.07 12:14 [edit/del]

    비밀댓글입니다

    Reply
  3. at 2011.01.14 01:44 [edit/del]

    비밀댓글입니다

    Reply
  4. at 2012.08.27 20:27 [edit/del]

    비밀댓글입니다

    Reply

submit