'WDM구조'에 해당되는 글 2건

  1. 2008.12.03 Windows Driver Model(WDM)의 전반적인 이해
  2. 2008.12.03 WDM 드라이버의 기본 구조
Programming/Kernel / Driver2008. 12. 3. 11:57

WDM방식이란, 윈도우환경에서 디바이스드라이버를 작성하는 방법을 획일화 한 것이다. 이 방법으로 디바이스드라이버를 작성하면, 윈도우의 확장 버젼이 나오더라도, 쉽게 이 디바이스드라이버코드를 그대로 사용할 수 있는 장점을 가지고 있다.
그렇다고 모든 디바이스드라이버를 WDM방식으로 작성할 수 있는 것은 아니라는 것을 명심해야 한다. WDM방식의 디바이스드라이버를 작성하기 위해서는 윈도우가 어느 정도만큼 이런 모델을 지원하고 있는가를 알아야 한다
.

예를 들어, 윈도우 9x에서 사용하는 USB이동식디스크를 생각해보자. USB이동식디스크를 윈도우 9x에서 사용하려면 디바이스드라이버가 필요한데, 이때 WDM디바이스드라이버가 사용될 수 있을까? 정답은 그렇다 이다.

USB버스를 사용하는 디바이스를 구동하는 드라이버이기 때문에 WDM디바이스드라이버를 작성해야 한다. 결국, 윈도우에서는 USB버스를 사용하려면 WDM디바이스드라이버모델을 사용해야 한다는 것이 된다. 그런데, 윈도우 9x에서는 디스크드라이버로서는 WDM방식을 사용할 수 없다는 것도 알아야 한다. 이상하다. USB디스크드라이버는 그렇다면 어떤 드라이버모델을 사용해야 한다는 것 일까요?


결국 2가지를 사용해야 한다는 것이다. 하나는 USB버스를 사용하기 위한 WDM디바이스드라이버이고, 또 다른 하나는 윈도우9x에서 디스크드라이버로 사용되기 위해서 VxD라는 모델을 사용하는 디바이스드라이버가 된다.
만일, 윈도우9x가 디스크드라이버로서 WDM을 지원하고 있었다면, USB이동식디스크를 위한 디바이스드라이버는 달랑 WDM드라이버 하나만 잘 만들면 된다.
윈도우2000은 하나의 WDM드라이버만으로 이것들이 해결된다. 윈도우2000에서는 디스크드라이버의 역할을 WDM디바이스드라이버가 수행할 수 있거든. 윈도우가 지원하는 것이다.
그러니까, 윈도우2000에서는 USB버스를 사용하는 WDM디바이스드라이버의 코드 안에다 디스크역할을 수행하는 코드를 포함하면 간단히 USB이동식디스크드라이버가 만들어지는 거가 되다.

이렇게 WDM방식으로 디바이스드라이버를 만들게 되면, 이 드라이버가 어디까지 현재 사용되는 윈도우에서 지원되고 있는가를 분명히 알아야 한다.

만일, USB이동식디스크드라이버를 윈도우9x에서 만들려고 할 때, 실컷 WDM방식만을 생각하고 만들었다면, 나중에 땅을 치고 후회하게 된다. VxD를 생각해야 하니까..

WDM
방식을 조금 더 구체적인 부분까지 설명해 보도록 하자.

 

 

1. WDM디바이스드라이버의 의미


WDM
디바이스드라이버에서 사용되는 WDM이란 용어를 풀어서 보면, Win32 Driver Model이 된다. 물론, 어떤 책에서는 Windows Driver Model이라고도 설명하지만, 보통은 이보다 Win32 Driver Model이란 표현이 더 정확할 것이다.

Win32 Driver Model
이란 표현이 암시하는 것이 무엇일까? 그것은 Win32 Subset을 통해서만 디바이스드라이버에 접근한다는 것을 말한다. 이 의미는 응용프로그램에서 사용하는 SDK(API)중에서 Win32 API를 사용하여 디바이스드라이버에 접근할 수 있다는 것이다. 그것들이 어떤 함수이냐 하면, CreateFile, ReadFile, WriteFile, DeviceIoControl, CloseHandle 이다.

이런 함수의 용도를 보면, 원래는 실제 파일을 읽고 쓸 때 사용하는 거지만, 윈도우에서는 디바이스드라이버와 통신하는 모양을 마치 파일을 다루는 것과 동일하게 간주하려 한다는 것을 암시하고 있다.


파일을 여는 간단한 함수를 예를 들면,

  Handle = CreateFile( “C:\\TEST.DAT”, FILE_READ_DATA|FILE_WRITE_DATA…)

이렇게 함수를 사용하면 무엇을 기대하지? 그것은 아마도 C:\TEST.DAT라는 파일이 열릴 것으로 생각이 든다. 그런데, 이 모양을 조금 더 자세히 살펴보자.


C:\TEST.DAT
라는 문장은 크게 3가지로 구분 되어질 수 있다.
C:
이것은 볼륨을 나타내는 심볼 이름이고

\
이것은 볼륨과 그 다음을 구분하는 구분자 이고
..
TEST.DAT
는 볼륨아래에서 찾게 되는 파일이름이 된다
.

이렇게 3가지로 구분되어서 사용되는 이런 파일열기에서는 다음과 같은 일이 예상된다
.
응용프로그램 -> 파일열기(C:\TEST.DAT) -> 운영체제 -> C: 볼륨을 처리하는 디바이스드라이버(볼륨파일시스템) -> \ 단어를 구분하여, 그 아래에 존재하는 TEST.DAT라는 문장을 찾는다. -> 결국 TEST.DAT라는 파일을 찾기 위해 실제 디스크드라이버를 호출한다
.

이렇게 파일을 여는 단순한 명령을 보아도, 우리는 이곳에서 C:라는 볼륨을 처리하는 디바이스드라이버가 사용된다는 것을 알 수 있다. 이처럼 파일열기명령으로만 알았던 CreateFile이 실제로는 디바이스드라이버를 여는 명령이란 것을 알 수 있다
.
이렇게 CreateFile함수를 통해서 디바이스드라이버를 열게 되면, C:볼륨을 처리하는 디바이스드라이버는 적절한 핸들 값을 리턴 하여, 응용프로그램이 차후에 이 핸들을 사용하여 ReadFile, WriteFile등의 함수를 호출하게 되며, 이런 함수를 통해서 호출하는 Read, Write의 요청을 윈도우운영체제는 핸들 값을 통해서 적절한 디바이스드라이버를 찾게 된다
.

파일을 읽는 명령을 예를 들면

응용프로그램 -> 파일읽기(ReadFile) -> 운영체제, 사용된 파일핸들을 분석하여 이것이 C:볼륨파일시스템이라는 것을 찾아낸다. 그런 다음, C:볼륨을 처리하는 디바이스드라이버(파일시스템)를 호출한다 -> C: 볼륨을 처리하는 디바이스드라이버 ..
이렇게 호출관계가 되어진다는 것을 명심하면, WDM디바이스드라이버가 어떤 방법으로 응용프로그램과 통신하게 되는지를 추측할 수 있게 된다

.
결국, WDM디바이스드라이버는 다음과 같은 호출관계에 놓이게 된다.
응용프로그램 -> API(File을 다루는 함수들) -> 운영체제 -> WDM디바이스드라이버
이럴 때 사용되던 API함수들이 Win32 API함수이기 때문에 우리는 Win32 Driver Model이란 표현을 사용하게 되다. 그 말은 이런 함수를 통하지 않고는 응용프로그램이 WDM디바이스드라이버와 통신할 수 없다는 것이 된다.
윈도우9x에서 사용되는 VxD는 상대적인 의미를 가지게 되다.
윈도우9x에서 단순히, 메모리를 읽거나, 쓴다든지, 아니면, 입출력포트를 접근해서 키보드 데이터를 읽어 오려 한다든지 하는 동작을 응용프로그램이 하려고 하면, 곧바로 응용프로그램 모르게 윈도우는 현재 제어를 VxD로 뺏어가 버린다. 이런 것과는 차원이 다르다.

 

 

2. WDM디바이스드라이버와 윈도우


윈도우 2000에서는 대부분의 디바이스드라이버를 WDM방식으로 작성할 수 있거든. 그러나, 윈도우9x에서는 그것이 불가능하다. 윈도우9x에서는 WDM보다는 VxD방식으로 작성된 디바이스드라이버를 대부분에 디바이스드라이버에 적용할 수 있도록 윈도우가 지원하고 있다.

윈도우2000이 되어야, VxD는 사라져버리고, WDM방식이 대부분의 디바이스드라이버를 지원할 수 있게 된다. 물론, 모든 디바이스드라이버를 다 WDM방식으로 사용할 수 있다는 것은 절대로 아니다. 예를 들어, 프린터드라이버, 디스플레이드라이버, 네트워크 드라이버 등은 WDM방식으로 만들 수 없다

 

 

3. WDM디바이스드라이버의 내부함수들


아까 1)에서 살펴보았던 내용 중에 응용프로그램 -> API(File을 다루는 함수들) -> 운영체제 -> WDM디바이스드라이버 라는 부분을 다시 한번 생각해보자.

이 내용대로라면, 운영체제 -> WDM디바이스드라이버가 된다는 것이다
.
이렇게 된다는 의미라면, 결국 운영체제가 직접 WDM디바이스드라이버를 호출하는 것이 된다. 이렇게 운영체제가 우리가 만든 WDM디바이스드라이버를 호출하려면 어떻게 WDM디바이스드라이버를 만들어야 할까? 운영체제가 알아서 여러분들을 호출할까? 그냥 호출하는 것이 아니라 어느 정도의 약속이 되어지는 부분이 필요하다.


그것이 호출약속이 된다. WDM디바이스드라이버는 최소한 하나의 함수를 외부에 Export해야 할 의무가 있다. 그래야, 운영체제는 이 함수를 통해서 WDM디바이스드라이버와 통신할 수 있다. 그 함수가 바로, DriverEntry()함수가 된다.


이 함수는 유일하게 Export되어지는 함수이기에, 반드시 C로 컴파일하는 것이 좋다. 만일, C++로 컴파일하게 되면 다 아는데로, 언어의 특징상 다형성이 가미되어 맹글링문제가 생겨서 실제로 DriverEntry()란 함수는 없어지고, 그 자리에 DriverEntry@Ad23dsd42ZSER 이런 이상한 이름의 함수가 만들어진다. 이런 함수가 만들어지면, 당연히 운영체제는 이 함수를 호출할 수 없다. 이름이 어려워진다.

이렇게 WDM 디바이스드라이버가 가진 DriverEntry()함수를 운영체제가 호출하면, 드라이버는 무엇을 하느냐? 그것은 바로, WDM디바이스드라이버가 가진 호출을 원하는 자신의 함수들을 등록하는 것이다. 결국, WDM디바이스드라이버는 10개정도의 함수를 가지고 있는 상태에서, 이 함수를 운영체제가 호출하지 못하니까 일단, DriverEntry()함수를 먼저 공개하고, 이 함수가 호출되었을 때, 나머지 자신의 함수들을 정해지 방법을 사용해서 운영체제에게 공개하는 것이다.

 

 

여기서는 함수들을 모두 이해하는 것은 어려울 것 같고.. 어떤 함수들이 존재하는 대강 알아두자..

 

. DriverEntry() : 함수이름이 반드시 유지되어져야 한다.
. SAMPLEAddDevice() :
함수이름자체는 무의미하다. 역할만 중요하다
.
. SAMPLEIRPDispatch() :
함수이름자체는 무의미하다. 역할만 중요하다
.
. SAMPLEDriverUnload() :
함수이름자체는 무의미하다. 역할만 중요하다
.
. SAMPLEIRPStartIo() :
함수이름자체는 무의미하다. 역할만 중요하다.

                                              

 

특별한 일을 수행하지 않는 간단한 WDM디바이스드라이버의 골격은 다음 4가지 엔트리로 구성된다.


. DriverEntry Routine
. AddDevice Routine
. IRP Dispatch Routine
. DriverUnload Routine


이 중에서도 DriverEntry Routine은 디바이스드라이버코드의 시작위치가 된다. 그리고, 이곳은 코딩상에서 함수명을 반드시 "DriverEntry"라고 가져야 하는 특징을 가진다. 그리고 다른 함수들은 실제 함수의 시작주소를 디바이스드라이버코드가 수행될 때 드라이버코드내에서 직접 구해서 IO MANAGER에게 알려주는 반면에 "DriverEntry"함수는 외부에 Export되는 유일한 이름이다. 따라서, "C++"언어를 사용하는 경우에는 특히, extern "C"명령을 사용해서 “DriverEntry"함수를 정의해야만 "C++"언어가 가지는 다형성을 위한 Mangling문제에서 벗어날 수 있다.


) extern  "C"  NTSTATUS  DriverEntry(..)

그리고, DriverEntry Routine AddDevice Routine은 윈도우즈가 제공하는 “SYSTEM" Process의 쓰래드아래서 호출된다. 쉽게 이해하자면, ”SYSTEM.EXE"라는 응용프로그램을 하나 만들어서, 이 프로그램이 DriverEntry, AddDevice Routine을 호출하는것이라고 생각해도 좋다. 윈도우에서는 특정 Process 아래에서 수행되는 Thread Task Switching을 허용한다. 그렇기 때문에 디바이스드라이버개발자들은 이곳에서 Event(이벤트)를 기다리는 행동을 한다든지, 아니면 이런 행동을 할것으로 기대되는 운영체제의 함수들을 호출하는 행동을 해도 좋다. 이렇게 Event를 두고 기다리는 행동은 자연히 태스크 스위칭을 일으키기 때문이다.

 

 

WDM디바이스드라이버가 가지는 함수 엔트리들은 이 밖에도 많이 있지만, 꼭 있어야 하는 중요한 엔트리 3가지만 살펴보도록 하자.

(1) DriverEntry Routine


WDM
디바이스드라이버코드에서는 다음과 같이 정의하여 사용한다.

 
extern  "C"
 NTSTATUS
 DriverEntry( PDRIVER_OBJECT  pDriverObject, PUNICODE_STRING  pRegistryPath )
 {
 ................
 }

이곳은 WDM디바이스드라이버가 메모리에 적재되어 운영체제로부터 호출되는 가장 처음의 함수 엔트리가 된다. 디바이스드라이버가 메모리에 적재되는 경우에 가장 먼저 호출된다는 특징을 가지기에, 디바이스드라이버개발자들은 이곳에서 몇 가지 코드를 구현하려 한다. 이때, 주의할 점은 “DriverEntry"가 호출되는 것은 단지, 디바이스드라이버가 메모리에 적재되어서 처음 실행된다는 점을 생각 해야 한다. 그 외에 이 시기가 디바이스드라이버에 입장에서 현재 자신의 하드웨어장치를 마음대로 초기화하는 등의 작업을 하도록 허용된 시기는 아니라는 것이다. 운영체제는 이처럼 하드웨어장치를 사용하라는 의미로 ”DriverEntry"를 호출하는 것이 아니라, 드라이버가 메모리에 처음 적재되는 경우에 필요로 하는 내부 변수 등을 초기화하는 작업을 디바이스 드라이버 측에서 먼저 수행하라는 의미에서 호출하는 것을 명심해야 한다.

WDM
디바이스드라이버에서는 똑같은 하드웨어장치를 같은 PC에 여러 대 설치한다 하더라도, 똑같은 디바이스드라이버를 여러 번 메모리에 적재하지 않는다. 예를 들어, 같은 회사에서 만든 USB이동식 디스크를 하나의 PC 2대를 연결 한다 하더라도, 디바이스드라이버는 단 한번 메모리에 로딩된다는 것을 의미한다. 결국, “DriverEntry"는 디바이스드라이버입장에서 DriverEntry, 자신이 사용할 하드웨어장치가 똑같은 종류의 것이 2개건, 3개건간에 단 한번만 호출된다.

 

 

WDM디바이스드라이버는 사실 IO MANAGER의 내부 루틴 혹은 내부모듈에 불가하다. 따라서, IO MANAGER가 원하는 방법대로 함수 엔트리를 준비하지 않으면 WDM디바이스드라이버로서 역할을 수행할 수 없다. IO MANAGER “DriverEntry" 함수를 호출하는 목적을 단지, WDM디바이스드라이버 자체가 사용할 내부변수를 초기화하는 목적에만 두지 않는다. DriverEntry함수를 호출함으로써, WDM디바이스드라이버가 가지고 있는 다양한 함수 엔트리들, 결국 IO MANAGER가 필요로 하는 형식을 가지는 함수 엔트리들에 대한 주소를 IO MANAGER에게 알려주는 목적을 달성하게 된다. WDM디바이스드라이버의 함수 엔트리를 얻기 원하는 IO MANAGER의 목적은 “DriverEntry"함수의 파라미터로 전달되는 ”pDriverObject“를 통해서 완성되게 된다. IO MANAGER는 각각의 WDM디바이스드라이버들에게 하나씩의 ”pDriverObject"를 공급하게 되며, 이렇게 공급된 “pDriverObject"의 내용에는 WDM디바이스드라이버가 가진 자신의 함수 엔트리를 등록하도록 되어 있어서, 이를 통해서 IO MANAGER WDM디바이스드라이버가 가지는 함수 엔트리를 원하는시기에 언제든지 호출할 수 있는 정보를 가지게 된다. 따라서, ”DriverEntry"함수가 호출되면, WDM디바이스드라이버가 수행하는 일은 무엇보다도 “pDriverObject"의 내용 중에서 함수 엔트리를 등록하는 부분으로 자신의 함수 엔트리를 등록하는 일이 된다. 이제 “DriverEntry"를 무사히 수행한 WDM디바이스드라이버는 IO MANAGER로부터 언제든지 호출될 수 있는 준비가 되었다. WDM디바이스드라이버에서 이 구조체에 기록하는 자신의 함수포인터들은 2가지 그룹으로 나뉠 수 있다.

- IRP
명령어 처리그룹
  IRP
명령어를 받아들이는 함수들
  : Create, Close, Pnp, Read, Write, IoControl, ..
명령처리함수들

- IRP
명령어 비처리그룹
  IRP
명령어를 받아들이지 않는 함수들
  : AddDevice, Unload, ..
함수들

 

 

다음은 대게의 WDM 디바이스드라이버에서 작성되는 “DriverEntry"함수에서 이 ”pDriverObject"구조체를 다루는 예이다.


 pDriverObject->MajorFunction[IRP_MJ_CREATE] = SAMPLECreateDispatch;
 pDriverObject->MajorFunction[IRP_MJ_CLOSE] = SAMPLECloseDispatch;
 pDriverObject->MajorFunction[IRP_MJ_READ] = SAMPLEReadDispatch;
 pDriverObject->DriverExtension->AddDevice = SAMPLEAddDevice;

예를 보면, SAMPLECreateDispatch, SAMPLECloseDispatch, SAMPLEReadDispatch 함수들은 모두 IRP명령어를 받아들이는 함수들이며, SAMPLEAddDevice 함수는 IRP명령을 받아들이지 않는 함수이다.

함수이름을 보면, "SAMPLECreateDispatch"라고 하는데, 짐작으로는 무엇인가 “Create-열기"작업과 관련이 있어 보인다는 것은 추측이 가능하나, 이 함수 엔트리가 실제로 어떤 역할을 수행하는가는 왼쪽 편에 등록되는 맴버 변수를 통해서 정해져 있게 된다
.
결국, pDriverObject->MajorFunction[] 배열에 등록되는 함수엔트리들의 역할이 이미 등록되는 시기부터 정해져 버린다는것이다.

 

역시 pDriverObject -> DriverExtension -> AddDevice 변수도 역시 마찬가지이다. 이곳에 등록되는 것만으로도 그 역할이 정해져 버린다. pDriverObject->MajorFunction[]배열에 등록되는 함수엔트리들을 “IRP Dispatch Routine"이라고 부른다. 이곳은 명령어(IRP)를 직접 받아들이는 곳이기도 하다. 그리고 pDriverObject->DriverExtension->AddDevice배열에 등록되는 함수엔트리를 ”AddDevice Routine"이라고 부른다.

pDriverObject->MajorFunction[]
변수가 배열구조를 가지고 있으므로, 결국 이 배열에는 첨자가 사용된다. 이 첨자는 다음과 같이 사용되고 있다.

 

 

첨자의 의미를 통해서, 해당하는 첨자를 사용한 pDriverObject->MajorFunction[]배열의 내용이 되는 함수포인터의 역할을 짐작할 수 있게 된다.


"DriverEntry"
가 호출될 당시에 “pDriverObject"내의 함수포인터를 유지하는 대부분의 변수들은 0으로 초기화되어있는 반면, pDriverObject->MajorFunction[]배열의 내용은 “0”으로 초기화되어있는 것이 아니라, 특정 주소 값이 이미 기록된 상태이다. 이는 WDM디바이스드라이버에서 pDriverObject->MajorFunction[]배열의 값들을 변경하지 않더라도, 그 변수의 값은 이미 IO MANAGER가 유지하는디폴트 핸들러의 주소를 가지기 위해서이다
.
디폴트 핸들러는 다음과 같은 형태로 메모리 내에 이미 정의되어 있다
.

또 하나의 “DriverEntry"함수의 파라미터는 ”pRegistryPath"이다. 이곳은 디바이스드라이버를 설치하는 당시에 운영체제에 의해서 지정된 디바이스드라이버를 위한 소프트웨어 서비스키를 가리키는 위치정보를 담고 있다. “DriverEntry"시기에는 다른 레지스트리는 사용이 불가능할수도 있지만, 유독 이곳 ”pRegistryPath"가 가리키는 레지스트리키 만큼은 디바이스드라이버에서 항상 사용이 가능하다는 특징을 가진다. 윈도우즈시스템이 부팅되는 초반시기에 메모리에 적재되는 WDM디바이스드라이버들은 시스템레지스트리를 접근하는데에 있어서 제한을 받게 된다. 이때 “pRegistryPath"에서 가리키는 레지스트리키는 언제든지 접근이 가능하다는점에서 WDM디바이스드라이버들은 이곳을 정보보관장소로 사용하게 된다. 다음은 ”pRegistryPath"가 가리키는 값들의 예이다
.
 


   . “HKLM\System\CurrentControlSet001\Service\SAMPLE"
  . “HKLM\System\CurrentControlSet001\Service\USBSTOR"
  . “HKLM\System\CurrentControlSet001\Service\DISK"

 

[정리]


“DriverEntry Routine"
WDM 디바이스드라이버가 메모리에 적재되는 시기를 알려주는 곳이다. 이곳은 IO MANAGER가 현 WDM 디바이스드라이버가 메모리에 적재되는 것을 알리는 목적과 디바이스드라이버가 가지는 다양한 함수 엔트리들의 주소를 등록 받기 위한 목적을 가진다. 특히, WDM 디바이스드라이버에서는 이 시기에 자신이 사용하려는 하드웨어장치를 적절한 방법으로 초기화하는 것은 권장 하는 일이 못 된다. 하드웨어장치와는 전혀 관련이 없는 루틴이라는 사실을 명심해야 한다.

 

 

(2) AddDevice Routine


USB이동식디스크를 지원하는 디바이스드라이버가 하나 있다고 가정하자. 똑같은 장치 2개를 PC에 연결했을 때, 디바이스드라이버가 2번 메모리에 적재되는 것은 메모리낭비를 초래하는 비 능률적인 운용이 될 수 있다. 결국, 윈도우즈는 이렇게 같은 종류의 여려 개의 장치가 한대의 PC에 연결 되는 경우에 하나의 디바이스드라이버만으로도 이 장치들을 지원할 수 있도록 한다. 이렇게 하나의 디바이스드라이버에서 여러 대의 같은 하드웨어장치를 지원하기 위해서는 디바이스 문맥(Device Context) - “DeviceObject라는 용어를 사용해야 옳은 표현이지만, 지금 시기에는 이해하는 목적을 위해서 디바이스문맥이라는 용어를 임시로 사용한다유지라는 중요한 구현상 특징을 외면할 수 없다. 결국, 디바이스드라이버는 하나의 장치마다 하나의 문맥을 유지할 수만 있다면, 2대 이상의 장치가 연결되더라도 문맥을 통해서 이 장치를 관리할 수 있다. 디바이스문맥이란 용어는 디바이스근거라는 용어와 비슷한 의미로 이곳에서는 보여 질 수 있다. 예를 들어, 하나의 드라이버가 2가지 이상의 자신의 하드웨어장치를 지원하는 모습을 위해서 각각의 하드웨어장치에 대한 자신의 관리를 운영체제에게 알려주는 근거이기도 하기 때문이다.


여기서 디바이스 문맥이라는 것은 구체적으로 “DEVICE_OBJECT"라는 구조체로 표현되어 사용된다. 이 구조체는 뒤에서 자세히 배우지만, 이 곳에서는 ”AddDevice Routine"이 하는 역할을 이해하기 위해서 “DEVICE_OBJECT”구조체의 의미중 일부분에 대해서만 잠시 배우도록 하겠다.

똑같은 USB이동식디스크장치2대를 PC에 연결했다고 가정하자. 사용자는 탐색기를 통해서 2개의 드라이브명을 볼 수 있을 것이다. 이 드라이브명을 “G:", "H:"라고 가정한다. 이때, 사용자가 ”G:"를 접근하려 한다면, 사용자의 이런 접근은 USB이동식디스크장치를 지원하는 디바이스드라이버(이하 디스크드라이버)측으로 적절한 명령이 되어서 전달된다. “H:"드라이브를 사용자가 접근하는 경우에도 마찬가지로 해당하는 디스크드라이버로 명령이 전달된다.그렇다면, 디스크드라이버에서는 어떻게 이 2개의 물리적인 드라이브를 구분할 수 있을까? 앞에서 배운 대로 라면, "DriverEntry"에서 등록한 "MajorFunction[]"배열은 여러 개의 장치를 구분하는 방법을 제공하지 않았으며, 단지 외부로부터 디스크드라이버가 받게 되는 명령어(IRP)를 처리하는 함수 엔트리만을 등록하게 되어있다. 이대로라면, 디바이스드라이버는 자신이 등록한 함수 엔트리안에서 사용자가 접근한 ”G:", "H:"드라이브를 구분할 수 있어야 한다. 이럴 때 바로 문맥(Context)이 필요하다. 명령어(IRP)를 받는 “IRP Dispatch Routine"으로 전달되는 파라미터 중 첫 번째 파라미터 즉 "pDeviceObject"가 전달되는 목적이 바로 이것이다.

 

 

윈도우즈의 도움이 없는 황무지 같은 도스(MSDOS)같은 운영체제만 존재하는 컴퓨터에서 USB이동식디스크를 사용하는 것을 가정해보도록 하자. 윈도우즈에서 보는 탐색기와 비슷한 역할을 수행하는 프로그램이 있어야 한다. 이 프로그램 안에서는 USB이동식디스크를 마음대로 주무르는 코드를 가지고 있을 것이다. USB이동식디스크를 사용하는 사용자를 생각해보자. 이 사용자가 이 이동식디스크를 사용하는데 있어서 당신이 제공하는 탐색기와 같은 프로그램만을 사용하게 된다면 모르지만, 그렇지 않으면 당신은 여러 가지 탐색기 아류작을 만들어야 할 것이다. 사용자의 취향은 다양하기 때문이며, 목적도 다양하기 때문이다. 탐색기를 만들어서 사용자에게 제공하는 당신 입장에서는 앞으로 사용자가 원하는 그런 프로그램이 되기 위해서 계속된 업그래이드뿐만 아니라, 새로운 프로그램이 추가 작성될 수도 있다. 이렇게 사용자가 원하는 모양으로 프로그램이 추가되는 것 뿐 만아니라, 또 다른 기능을 가진 USB이동식디스크가 만들어지면, 이렇게 새롭게 만들어진 이동식디스크를 사용하기 위해서 역시 현재의 프로그램은 수정되어져야 한다.
이런식으로 수정 되어지는 부분은 크게 사용자가 보는 관점에서의 수정과 하드웨어(USB이동식디스크)의 변경으로 인한 수정으로 나뉠 수 있는데, 이렇게 수정되어지는 부분을 고려하기 위해서는 순수한 의미에서 응용프로그램과 하드웨어종속적인 코드를 담고 있는 디바이스드라이버라는 개념이 필요할 것이다.

응용프로그램과 디바이스드라이버를 나눈다는 것은 현실적으로는 하나의 프로그램이 아니라, 2개의 프로그램을 만든다는 것이다. 당신은 어느 쪽이 전문가이겠는가? 응용프로그램이겠는가? 아니면 디바이스드라이버이겠는가? 이 책을 읽는 독자라면, 아마 디바이스드라이버라고 대답할 것이다. 그렇다면 응용프로그램을 보다 더 전문가에게 맡기고, 당신은 디바이스드라이버만을 만들기 위해서는 일반인들이 자신이 만든 응용프로그램이 당신의 디바이스드라이버와 통신하는 환경을 만들어야 할 것이다. 이런 경우에 응용프로그램과 디바이스드라이버는 특정 인터패이스를 가지는 경우를 가정해보자. 인터패이스 계층이 존재하게 되면, 응용프로그램개발자는 이런 인터패이스에 맞추어서 자신의 프로그램을 만들게 되며, 디바이스드라이버 역시 인터패이스에 맞추어서 자신의 디바이스드라이버프로그램을 만들게 된다.
인터패이스를 가지게 됨으로써, 보다 확장성 있는 응용프로그램과 디바이스드라이버가 만들어질 수 있을 것이다.
이제, 조금 더 깊숙히 들어가보자. 이제 당신이 선택한 디바이스드라이버는 응용프로그램적인 요소는 완전히 제거되었다. 그런데, USB이동식디스크를 PC에 연결한다는 것은 사실 PC가 가진 USB HostController라는 장치가 제공하는 허브(HUB)에 이 이동식디스크를 연결한다는 의미이다. 그러므로, 디바이스드라이버가 이동식디스크를 관리하기 위해서라면, 필수적으로 PC에 내장된 USB HostController를 이해할 수 밖에 없다. PC에 내장된 USB HostController가 한가지 종류만 있는 것이 아니다. 크게 2가지의 형태로 시중에 나와 있기때문이다. 하나는 “Universal Host Controller"이며 또 하나는 ”Open Host Controller"이다. 이렇게 서로 다른 “USB HostController"에 대해서 여러분이 만든 USB이동식디스크드라이버는 모두를 고려하고 있어야 한다. , 이런 USB HostController가 사용하는 메인보드의 버스(BUS) ISA BUS인가? 아니면, PCMCIA BUS인가? 그것도 아니면 PCI BUS인가? 해당하는 버스에 연결되어 사용되려면, BUS에 대해서 고려하는 코드 역시 디바이스 드라이버내에서 구현 되어져야 한다. 예를 들어, PCI BUS를 사용하는 경우라면, PCI BUS가 요구하는 적절한 "PlugAndPlay Resource Enumeration"을 해야 한다. 또한, ”PCI Device Start"역시 제공해야 한다. “PCI Device Start"란 의미는 모든 PCI카드는 부팅과 PCIBIOS에 의해서 동작명령즉 Start명령을 받도록 설계되어 있기에, 이런 작업을 "PCI Device Start"라고 부른다. 과연, 당신은 USB이동식디스크를 위한 디바이스드라이버를 만들면서 이 모든 것들을 다 고려할 것인가? 해답은 그렇지 않는 것이 유리하다는 것이다. 꼭 그래야 한다면 할 수 없지만, 이 모든 것을 다 지원한다면 코드의 양은 무척 커지게 될 것이며, 코드의 신뢰도를 확인하는 작업도 만만치 않을 것 이다. 또한 코드의 확장성와 유연성에 있어서도 단점을 가지게 된다. 그런 이유로 인해서 당신이 만들게 되는 USB이동식디스크드라이버역시 몇 가지 부분으로 나뉘어 보다 전문성있는 개발자들이 손을 대는 부분을 염두하게 된다

 

 

결국, 디바이스드라이버라는 영역이 크게 3가지 영역으로 확장 되어진 결과가 되었다.
윈도우즈에서는 이렇게 구분되어서 작성된 디바이스드라이버를 많이 보게 된다
.
윈도우즈에서는 실제로 USB이동식디스크드라이버를 위한 드라이버코드는 크게 4부분으로 나뉘어디스크드라이버”, “USB HUB드라이버”, “USB HostController드라이버”, "PCI Bus드라이버등으로 나뉘게 된다. 이렇게 나뉘어지는 이유는 보다 전문적인 코드모듈을 사용하기 위해서이다. 이런 경우, 4가지의 드라이버들은 하나의 USB이동식디스크드라이버를 운용하기 위해서 함께 동작하는 하나의 모델을 그리게 되는데, 이런 모델을 그리기 위해서는 디바이스 스택(Device Stack)이란 개념이 필요하다.


윈도우즈에서 USB버스를 사용하는 장치는 이동식디스크만 있는 것이 아니라, USB Network카드, USB 카메라장치, USB 오디오 장치등등 많은 장치들이 있다. 따라서, USB이동식디스크드라이버에서 언급한 "USB HUB드라이버“, ”USB HostController드라이버그리고 ”PCI Bus드라이버등은 비단 USB이동식디스크에만 적용되지 않아야 한다. 다른 장치들도 이런 드라이버들이 필요하기 때문이다. 그런 이유에서 디바이스드라이버들은 자기자신이 개입해야 하는 장치들을 위해서 DeviceObject(Device Context)를 만들어서 사용하게 된다.

결국, 모든 드라이버는 자신이 포함 되어질 대상의 논리적인 하나의 디바이스를 위해서 하나의 DeviceObject를 생성하여 포함시키게 된다. 그렇기 때문에, 동일한 USB이동식디스크가 2개 사용되는 PC에서는 우리가 2개의 DeviceObject를 생성 해야 하는 이유가 여기에 있다고 할 수 있다.

이렇게 열거자와 주기능자로서의 역할을 모두 가지고 있는 드라이버들은 내부적으로 2가지의 DeviceObject를 생성하여 관리하고 있다. 이때, 열거자로서의 역할을 수행하는 DeviceObject Physical Device Object라고 부른다. 결국 하나의 논리적인 의미에서 PCI IDE CDROM디바이스를 구성하는 디바이스 스택은 크게는 하나의 DeviceStack이 사용되며, 세부적으로는 3가지의 DeviceStack으로 나뉠 수 있다.

 

 

“AddDevice Routine"함수가 호출되는 이유가 DeviceObject를 생성하라는데 있다고 보았다. 보다 더 정확한 의미에서 호출이유를 정의한다면, 그것은 바로 자신이 속할 DeviceStack을 위해서 필요로 하는 DeviceObject를 생성하라는 의미가 된다. , Physical Device Object(열거자로서의 역할) ”AddDevice Routine"에서 생성하는 것이 아니라는 점을 유념해야 한다. Physical Device Object가 생성되어진 이후에 DeviceStack이 생성되는 것 이기 때문이다. 결국 Physical Device Object가 없으면 디바이스스택자체도 생성될 수 있는 근거가 없는 것이다.

“AddDevice Routine"
함수의 프로토타입을 보자.


NTSTATUS  SAMPLEAddDevice(
         PDRIVER_OBJECT pDriverObject,
         PDEVICE_OBJECT pPhysicalDeviceObject );

파라미터에서 “pDriverObject" ”DriverEntry"함수에서 보았던 우리를 위해서 IOManager가 만들어 놓은 자료구조이다. “pPhysicalDeviceObject” DeviceObject로서, 현재 USB이동식디스크드라이버를 위해서 구축되고 있는 디바이스 스택의 가장 하위 DeviceObject, USB Bus드라이버가 생성한 DeviceObject를 의미한다. “AddDevice Routine"이 호출될 당시, 우리에게는 이처럼 우리가 속할 디바이스 스택 상의 버스드라이버가 생성한 가장하위 DeviceObject에 대한 주소를 넘겨받는다는 것을 알 수 있다.

현재 윈도우즈가 제공하는 디스크드라이버(DISK.SYS) USBHUB열거자(USBHUB.SYS)가 생성하는 PhysicalDeviceObject에 대해서 잘 어울리는(?) 성격을 가지고 있을까? 다시 말하면, 디스크드라이버(DISK.SYS)가 자신이 원할 때 어떤 일을 수행하는데 있어서, USBHUB열거자(USBHUB.SYS)드라이버를 호출하는 것이 자유스러운가 하는 이야기이다. 정답은 그렇지 않다는 것이다. USBHUB가 생성하는 열거자로서의 DeviceObject는 이런일을 하기에 너무 부족하다. USBHUB가 생성하는 열거자로서의 DeviceObject는 이런 USB이동식디스크장치에만 사용되는 것이 아니라, USB버스를 사용하는 거의 모든 USB디바이스에는 다 사용되는 성격을 가지고 있기 때문에 구태여 디스크드라이버를 위한 코드를 담고 있지는 않다는 것이다. 여기서, 우리는 필터드라이버의 역할을 배울수 있다. 지금같이 USBHUB드라이버와 DISK드라이버간의 매끄러운 연결을 지원하기 위해서 필터드라이버가 필요한 것이다
.

디바이스 스택 이란 의미는 여러 개의 DeviceObject들이 어떤 규칙을 가지고 서로 연결되어 있는데, 이런 연결된 DeviceObject가 생성되고 제거되는 순서가 마치 자료구조의 스택(Stack)과 같은 순서대로 이루어진다는 것이다
.

우리가 고려된 USB이동식디스크의 디스크 디바이스 스택이 형성되는 순서는 가장 하위의 USBHUB드라이버가 생성한 PhysicalDeviceObject가 가장 먼저 생성 되어 져야 하고, 그 다음에 우리가 개입하여 FilterDeviceObject를 생성해야 하고, 그 다음에 디스크드라이버가 개입하여 DeviceObject를 생성해야 하는 것이다. 그들간에는 서로 "AttachedDevice"라는 DeviceObject내의 맴버 변수에 의해서 연결되어 있다. 이런 연결된 관계는 후에 해체시기가 되면 역순으로 이루어지는데, 순서는 가장 상위의 디스크드라이버의 DeviceObject가 먼저 제거되고, 그 다음 우리가 개입한 FilterDeviceObject가 제거되고, 마지막으로 USBHUB드라이버가 생성한 PhysicalDeviceObject가 제거된다.


“AddDevice Routine"
이 하는 일은 여기까지이다. 이곳이 호출되는 것은 우리가 앞으로 사용하게 될 하드웨어장치의 문맥(DeviceObject)에 대한 생성과 특정 pPhysicalDeviceObject 로부터 시작된 디바이스 스택 상에 우리의 DeviceObject를 올려놓는일이다. 그렇기 때문에 ”AddDevice Routine" "Add My DeviceObject(Context) To Current Device(Context) Stack"으로 풀어서 설명할 수 있는 것이다. 주의할 점은 이곳 “AddDevice Routine"은 디바이스 스택을 형성하는 루틴이지, 하드웨어장치를 사용하는 루틴이 아니라는 점이다. 결국, USB이동식디스크드라이버를 작성하는 우리 역시, 이곳에서 USB이동식디스크를 사용하는 행동을 해서는 않 된다는 것이다. 디바이스 스택에 대한 더 자세한 내용은 3장에서 배우도록 하자.

[
정리]
AddDevice Routine
은 우리가 지원할 하드웨어장치가 PC에 연결되어 운영체제가 이를 인식하는 시기에 호출되는 루틴으로서, “DriverEntry"와 달리, 같은 종류의 하드웨어장치가 연결되는 횟수에 따라서 그와 같은 횟수로 호출되는 성질을 가진다.
이 곳에서는 우리가 앞으로 사용할 DeviceObject를 생성하여, DeviceObject DeviceStack상에 올려놓는 역할만 수행해야지, 이곳에서 역시 자신의 하드웨어장치를 사용하려는 행동을 해서는 안 된다. 그 이유는 아직 디바이스 스택이 완전히 완성된 것이 아니라, 완성하고 있는 과정 이기 때문이다.
 

 

 

(3) "IRP Dispatch Routine"


이곳은 외부로부터 명령어(IRP)를 받는 곳이다. 명령어(IRP)는 특별한 형식으로 되어 있는데, 이것은 단순한 명령어라고 하기 보다도, Device Stack에 전달되기에 적당한 형식으로 작성되어져 있다는 것이다. 명령어란 대게, 보내는 자와 받는 자와의 일대일 관계를 서술하는 것이 일반적이지만, IRP명령어는 보내는 자는 하나지만, 받는 자가 여러 개의 Device Object로 구성된 Device Stack이라는 점이 특이한 것이다.

일단, 이함수의 프로토타입을 보자.


NTSTATUS SAMPLEPnpDispatch(
       PDEVICE_OBJECT pDeviceObject, PIRP pIrp );

 첫 번째 파라미터인 pDeviceObject는 앞부분에서 서술했던, 우리가 생성한 DeviceObject를 의미한다. 이것을 통해서 2가지 이상의 같은 종류의 하드웨어를 구분하여 프로그래밍할 수 있는 문맥을 삼을 수 있다. 두 번째 파라미터는 명령어, IRP이다.
이 함수는 외부로부터 IRP를 받아드린다는 점에서 중요한 역할을 수행한다. WDM디바이스드라이버가 이런 IRP Dispatch함수를 두지 않으면, 외부로부터 완전히 고립된다는 의미를 가지기도 하기 때문이다. WDM디바이스드라이버가 IRP를 받지 않는다는 것은 현실적으로는 어떤 서비스로 수행하지 못한다는 것을 의미하며, 따라서 그런 드라이버는 실제로 존재가치가 무의미하다
.
따라서, WDM디바이스드라이버가 어떤 DeviceStack에 동참하고 있는 한, 외부로부터 명령어(IRP)를 받는 것은 당연하다고 할 수 있다
.
우리가 형성하고 있는 DeviceStack으로 보내지는 명령어들은 크게 몇 가지로 구분된다
.

IRP
DeviceStack을 사용하려는 외부의 Client는 항상 준비해야 하는 구조체이기도 하다. IRP구조체역시 뒷장에서 자세히 배우므로, 이곳에서는 DeviceStack해체를 수행하는 명령어인 IRP_MJ_PNP(IRP_MN_REMOVE_DEVICE)에 대해서만 살펴보도록 한다.


case  IRP_MN_REMOVE_DEVICE:
 
     IoDetachDevice(ptargetDevice);
      IoDeleteDevice(pDeviceObject);

 

문장을 볼수있는데, 이 문장은 현재 우리가 참여하고 있는 DeviceStack에서 우리를 제거한다는 의미를 가진다.

 

IoDetachDevice()함수는 앞 부분에서 배운 IoAttachDeviceToDeviceStack()함수의 반대역할을 수행하는 함수이다. 이 함수를 사용함으로써 현재 우리와 우리아래에 연결된 DeviceObject와의 연결관계가 끊어진다.

IoDeleteDevice()
함수는 앞 부분에서 배운 IoCreateDevice()함수의 반대역할을 수행한다. 이 함수를 사용함으로써 현재 우리가 생성했던 DeviceObject를 제거한다.

출처 : http://blog.naver.com/netrabbit?Redirect=Log&logNo=20055180390

 

Posted by skensita
Programming/Kernel / Driver2008. 12. 3. 11:54

WDM 드라이버 기본 구조

 

디바이스 드라이버 : 운영체제가 하드웨어 디바이스와 관련된 다양한 동작을 하기 위해 호출할 수 있는 서브루틴의 집합을 담을 수 있는 컨테이너의 개념.

 

디바이스 드라이버 계층화

 

WDM은 일정한 규격에 맞게 드라이버의 레이어가 생성되도록 한다.

DO(Device Object)들은 시스템이 소프트웨어의 하드웨어 관리를 돕기 위해 만들어 낸 자료 구조.

 

스택의 가장 낮은 레벨의 디바이스 객체는 PDO(Physical device object)라고 한다.

디바이스 객체의 가운데쯤 있는 것이 FDO(Functional device Object)라고 불리우는 객체이다.

 

FDO의 위쪽과 아래쪽에는 filter device object들의 모임이 존재할 것이다. FDO의 위에 위치한 객체는 상위필터(upper filter)라고 하고, 아래에 위치한 객체(PDO보다는 위에 있지만..)는 하위필터(lower filter)라고 한다.

 

Filter device object FiDO라는 약어로 사용하는 것을 볼 수 있지만 공식적인 것이 아니다. 하지만 FiDO라고 해도 알아듣도록 하자.

 

운영체제의 PnP 매니저 컴포넌트는 디바이스 드라이버의 요청에 의해 디바이스 객체의 스택을 형성한다.

 

Bus에 대한 드라이버의 역할 중에 하나는 bus에 부착된 디바이스를 나열하고, 그들 각각에 대한 PDO를 생성한다. PDO를 생성한 다음 PnP 매니저는 filter driver FDO를 찾기 위해 레지스트리 데이터베이스를 참고한다. 레지스트리 엔트리는 스택에 나타날 드라이버의 순서를 정한다. 그 결과 가장 낮은 레벨의 filter driver를 로딩하고 그것의 AddDevice 함수를 호출 함으로써 PnP가 시작된다.

AddDevice filter driver를 만들고 filter driver와 드라이버 사이의 수평 링크를 생성한다. 그리고 나서 PDO filter driver에 연결한다.

 

계층화의 목적.

디바이스에 영향을 주는 오퍼레이션에 대한 요청 각각은 IRP(I/O Request Packet)를 사용한다.

IRP는 디바이스에 대해 최상위에 위치한 드라이버로 정상적으로 보내지고, 다른 드라이버로 스택을 타고 내려올 수 있다. 각 레벨에서 드라이버는 IRP로 무엇을 할 것인가를 결정한다.

 

하나의 하드웨어를 위해 스택을 점유하고 있는 다양한 드라이버들은 각기 다른 역할을 수행한다.

FDO는 디바이스를 관리하고, bus driver(PDO)는 디바이스와 컴퓨터 사이의 연결을 관리한다.

Filter driver IRP의 스트림을 감시하거나 수정하는 역할을 한다.

 

PDO FDO와 완전히 다른 작업을 수행한다. FDO IRP PDO에게 내려보냄으로써 PDO에게 어떤 작업을 위임한다”.

 

시스템이 드라이버를 로드하는 방법

 

재귀적 계산(enumeration)

 

PnP 매니저는 실제로 존재하지 않는 루트(root)” 버스에 대한 빌트-(built-in, 내장)드라이버를 가지고 있다. PCI 같은 Primary hardware bus를 포함해서 루트 버스 드라이버는 레지스트리로부터 컴퓨터에 대한 정보를 얻는다.

 

Primary bus에 대한 function driver는 자신의 하드웨어를 전기적으로 나열(enumerate)한다.

PCI 버스는 각각의 부착된 디바이스에 대한 특별한 구성 공간(디바이스에 대한 디스크립션과 리소스 요구사항이 포함되어 있다)을 액세스하는 방법을 제공한다.

Bus driver가 하드웨어를 나열할 때 버스 드라이버는 일반적인 function driver로 가장하여 작동하다가 하드웨어를 감지한 후에는 드라이버의 역할이 바뀌게된다(감지된 하드웨어에 대한 새로운 PDO)를 생성하게 된다.

 

레지스트리의 역할

 

레지스트리 데이터베이스가 드라이버를 로드하고 디바이스를 구성하는 과정에서 결정적인 역할을 한다. 그러므로 어떤 레지스트리 키들이 관련되어 있고 그것들이 무엇을 담고 있는지 설명한다.

 

세가지 다른 레지스트리 키(하드웨어 키, 클래스 키, 서비스 키)들은 구성(configuration)과 관계가 있다. 저 세가지의 이름은 적절한 이름이 아니다.

하드웨어 키는 단일 디바이스에 대한 정보를 포함한다. (인스턴스 키라는 이름을 사용하기도 한다.)

클래스 키는 같은 타입의 모든 디바이스들과 관련이 된다.

서비스 키는 드라이버들에 대한 정보를 포함한다. (소프트웨어 키라는 이름을 사용하기도 한다.)

 

하드웨어 키 :

레지스트리의 HKEY_LOCAL_MACHINE\System\CurrentSet\Enum 서브키에서 나타난다.

Enum키 아래에 있는 첫번째 레벨에 위치한 서브키들은 시스템에 있는 다른 버스 나열자와 대응된다. 모든(과거 또는 현재) USB 디바이스들의 디스크립션이 …\Enum\USB 서브키안에 존재한다.

ClassGUID는 디바이스 클래스를 식별케 해주는 globally unique indentifier(GUID)의 고유 ASCII표현방식이며 디바이스의 클래스키에 대한 포인터이다.

Service서비스 키에 대한 포인터이다.

 

클래스 키:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Class에서 나타난다.

클래스 키들의 키 이름은 마이크로소프트에서 지정한 GUID이다.

각 디바이스는 클래스 키 아래에 자신의 서브키(0000 같은)를 가지고 있다. 이 키의 이름은 디바이스 하드웨어 키에 있는 드라이버 값이다.

 

서비스 키:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services에 나타난다.

이것은 드라이버의 실행 가능한 파일이 디스크에 놓인 위치를 나타내고 드라이버가 로드되는 방식을 제어하는 다른 매개 변수들을 포함한다.

서비스키에 대한 참고링크 : http://support.microsoft.com/kb/103000

 

 

드라이버 로딩의 순서

 

PnP 매니저가 새로운 디바이스를 접하게 되면 하드웨어와 클래스 키를 열고, 드라이버를 로드하기 위해 다음과 같은 순서로 작동한다.

1.     디바이스에 대한 하드웨어 키에서 지정된 하위 필터 드라이버. LowerFilters 값은 REG_MULTI_SZ 타입이기 때문에 하나 이상의 드라이버를 지정할 수 있다.

2.     클래스 키에서 지정된 하위 필터 드라이버, 또다시 하위 필터 드라이버, 이것들이 LowerFilter 값의 데이터 스트링에 나타난 순서대로 로드된다.

3.     하드웨어 키에서 Service 값에 의해 지정된 드라이버

4.     하드웨어 키에서 지정된 상위 필터 드라이버 그리고 UpperFilters 데이터 스트링에 나타난 순서

5.     마찬가지로 또다시 클래스 키에서 지정된 상위 필터 드라이버 그리고 UpperFilters 데이터 스트링에 나타난 순서대로 로딩한다.

 

시스템이 드라이버를 로드한다고 말할 때 이것은

드라이버의 이미지를 가상 메모리로 맵핑하고,

재배치 가능한 레퍼런스들을 수정하며,

드라이버의 메인 엔트리 포인트를 호출하는 것

을 의미한다. 메인 엔트리는 보통 DriverEntry라고 부른다.

 

디바이스 객체가 상호 관계를 가지는 방법

 

디바이스 객체 스택의 트리에서 IRP가 반드시 PDO에서 꼭대기의 filter driver object로 흘러가야 한다는 것을 의미하지 않는다. 간단하게 말하면 트리의 구조에 있는대로 다음의 드라이버객체로 IRP를 보낼 수도 있고 보내지 않을 수도 있다는 이야기이다.

 

이외에 설명이 더 있는데 이 부분은 잘 이해가 안된다.. L

 

 

드라이버 객체

 

I/O 매니저는 각각의 디바이스 드라이버를 나타내기 위해 드라이버 객체 자료 구조를 사용한다.

이 드라이버 객체 자료 구조는 부분적으로 불투명(opaque)하다. _DRIVER_OBJECT에서 강조되지 않은 부분이 그에 해당한다.

 

typedef struct _DRIVER_OBJECT{

   CSHORT Type;

   CSHORT Size;

   PDEVICE_OBJECT DeviceObject;

   ULONG Flags;

   PVOID DriverStart;

   ULONG DriverSize;

   PVOID DriverSection;

   PDRIVER_EXTENSION DriverExtension;

   UNICODE_STRING DriverName;

   PUNICODE_STRING HardwareDatabase;

   PFAST_IO_DISPATCH FastIoDispatch;

   PDRIVER_INITIALIZE DriverInit;

   PDRIVER_STARTIO DriverStartIo;

   PDRIVER_UNLOAD DriverUnload;

   PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];

} DRIVER_OBJECT, *PDRIVER_OBJECT;

 

헤더는 구조체를 DRIVER_OBJECT라는 타입명으로 정의한다. 또한 포인터 타입(PDRIVER_OBJECT)을 선언하고 구조체 태그(_DRIVER_OBJECT)를 할당한다.

 

드라이버 객체 구조체의 액세스 가능한 필드들에 대해 간단하게 알아본다.

DeviceObject(PDEVICE_OBJECT) : 드라이버에 의해 관리되는 각각의 디바이스에 대한 디바이스 객체 자료 구조의 리스트를 가리킨다.

DriverExtension(PDRIVER_EXTENSION) : 오직 AddDevice(PDRIVER_ADD_DEVICE) 멤버만을 액세스 할 수 있는 작은 하위 구조체를 가리킨다. 그 안에서 드라이버가 드라이버 객체를 만들어 내는 함수에 대한 포인터이다.

HardwareDatabase(PUNICODE_STRING) : 디바이스에 대한 하드웨어 데이터베이스 레지스트리 키의 이름이 들어 있는 문자열을 나타낸다. \Registry\Machine\Hardware\Description\System과 같은 이름이다.

FastIoDispatch(PFAST_IO_DISPATCH) : 파일 시스템과 네트워크 드라이버가 익스포트(export)하는 함수 포인터들의 테이블을 가리킨다.

DriverStartIo(PDRIVER_STARTIO) : 직렬화(serialize) I/O 요청을 처리하는 드라이버 함수를 가리킨다.

DriverUnload(PDRIVER_UNLOAD) : 드라이버의 클린업 함수를 가리킨다.

MajorFunction(PDRIVER_DISPATCH의 배열) : I/O 요청 타입의 각각을 처리하는 드라이버의 함수에 대한 포인터들의 테이블이다.

 

디바이스 객체

 

다음은 DEVICE_OBJECT의 자료 구조이다.

 

typedef struct _DEVICE_OBJECT {
  CSHORT  Type;
  USHORT  Size;
  LONG  ReferenceCount;
  PDRIVER_OBJECT  DriverObject;
  PDEVICE_OBJECT  NextDevice;  PDEVICE_OBJECT  AttachedDevice;
  PIRP  CurrentIrp;
  PIO_TIMER  Timer;
  ULONG  Flags;
  ULONG  Characteristics;

  __volatile PVPB  Vpb;
 
PVOID  DeviceExtension;
  DEVICE_TYPE  DeviceType;
  CCHAR  StackSize;  union {
    LIST_ENTRY  ListEntry;
    WAIT_CONTEXT_BLOCK  Wcb;
  } Queue;
  ULONG  AlignmentRequirement;
  KDEVICE_QUEUE  DeviceQueue;
  KDPC  Dpc;
  ULONG  ActiveThreadCount;
  PSECURITY_DESCRIPTOR  SecurityDescriptor;
  KEVENT  DeviceLock;
  USHORT  SectorSize;
  USHORT  Spare1;
  PDEVOBJ_EXTENSION  DeviceObjectExtension;
  PVOID  Reserved;
} DEVICE_OBJECT, *PDEVICE_OBJECT;

 

DEVICE_OBJECT에 대한 자세한 내용 : http://msdn.microsoft.com/en-us/library/aa491677.aspx

 

출처 : http://wert.egloos.com/4502911

 

Posted by skensita