1. 드라이버의 작동 방식.
운영체제는 하드웨어와 관련된 많은 동작을 수행하기 위해서 관련 서브 루틴 집합을 구현해 놓고있는데.
쉽게 말하면 드라이버는 이 서브루틴 집합을 담아두는 컨테이너라고 생각하면 된다.
이 컨테이너의 내용물은 DriverEntry, AddDevice, IRP(I/O request packet<- 앞으로 자주 나올놈이니 꼭 기억해두두록하자)처리를 위한 디스 패치 함수 등이 들어있다. 대부분의 드라이버는 몇 가지 종류의 IRP를 처리하는 디스패치 함수를 갖고있따.
WDM 드라이버 개발자는 이렇든 자신의 컨테이너에 포함시킬 기능들을 적절히 선택해서 조합하는 역활을 해야하는것이다.
*컨테이너*
기본드라이버루틴 I/O컨트롤루틴 디스패치루틴
DriverEntry AdapterControl DispatchPnp
AddDevice StartIO DispacthPower
Onlnterrupt DispatchWmi
DpcForlsr DispatchRead
.. DispatchWrite
..
2. 어플리케이션의 작동 방식
"서브루틴들의 패키지(pacage of subroutines)" 모델인 드라이버를
"주프로그램과 조력자(main program and helper)" 모델인 일반 어플리케이션과 대비해서 생각해 보자.
ex>
int main(int argc, char* argv[])
{
printf("HELLO, WORLD");
return 0;
}
이 프로그램은 main이라는 이름의 주프로그램과 보조 루틴들의 라이브러리로 구성되어있다.
보조 루틴중 하나인 printf는 표준 출력 파일에 메시지를 프린트한다.
주 프로그램이 들어있는 소스 모듈을 컴파일 하고, 이를 printf와 같은 보조 루틴들이 들어있는 런타임 라이버리와 링킹하고 나면, HELLO.EXE라는 실행 파일이 완성 될것이다.
* 다음은 어플리케이션에 대한 몇 가지 공통사항!
어플리케이션이 사용하는 특정 보조 루틴들은 정적 라이브러리에 포함되어 있고 linkage 에디터는 빌드 과정에서 이것들을 추출하게 된다. 일반적으로 printf같은것들이지. 일부 보조 루틴들은 시스템 DLL로부터 동적으로 링크되기도 한다. 이런 루틴들에 대해 linkage에디터는 실행 파일에 특별한 improt 레퍼런스를 삽입하여, 나중에 런타임 로더에서 이 레퍼런스가 실제 시스템 코드를 가리키도록 한다.
사실, 어플리케이션 프로그램에서 사용되는 모든 Win32 API는 동적으로 링크되어 잇기 때문에, Windows 프로그래밍에서 동적링크는 매우 중요한 개념임!!!
3. 디바이스 드라이버
Hello.EXE와 같이 드라이버도 역시 일종의 실행파일이란것을 알아두자!
파일 확장자는 ****.SYS이지만, 이 파일 구조는 다른 32비트 Windows나 콘솔 모드 어플리케이션과 동일함!!!!!<-
또한 HELLO.EXE와 마찬가지로 드라이버도 많은 보조 루틴을 사용하는데, 운영체제 커널이나 class driver나 다른 지원 라이브러리 에서 동적으로 링크된다. [ 또, 드라이버 파일도 심볼릭 디버깅 정보와 리소스데이터를 가지고있다. ]<- 심볼릭 디버깅등검색해보자!
*드라이버에 대한 시스템의 역활
HELLO.EXE와는 달리, 드라이버는 main 프로그램을 포함하고 있지 않으며 대신!! 시스템의 판단에 따라 호출할 수 있는 서브 루틴들의 집합을 가지고있다.
서브루틴들은 드라이버나 정적 라이브러리나 운영체제 내에있는 여러 보조 루틴들을 사용할수 있지만. 드라이버는 자신의 하드웨어 외 다른것에는 관여하지 않는다. 그외 다른 부분은 모두 시스템이 담당하고있다.(예를들어 드라이버의 실행 시점 판단이라던가...)
* 운영체제가 드라이버 내의 서브루틴을 호출하는 과정을 간단히 예를 들어보자!!
1) 사용자가 디바이스를 꽂으면, 시스템은 드라이버의 실행 파일을 가상 메모리에 올리고, DriverEntry 루틴을 호출!! DriverEntry는 몇가지 작업을 하고 리턴~
2) PNP(Plug and Play) Manger는 드라이버의 AddDevice 루틴을 호출!! 역시 작업후 리턴~
3) PNP Manger가 드라이버에 IRP를 전송하면 디스패치 함수가 이를 처리하고 리턴!
4) 어플리케이션이 디바이스의 핸들을 열면 시스템이 또 다른 IRP를 전송하고, 디스패치 함수가 처리후 리턴!
5) 어플리케이션이 데이터를 읽으려고 시도하면, 시스템이 해당 IRP를 전송! 이에 대해 디스패치 루틴은 IRP를 큐에 저장하고 리턴!!!
6) 드라이버에 연결되어 있는 하드웨어 인터럽트에 신호를 보내서 이전의 I/O 오퍼레이션을 완료시킴.
인터럽트 루틴에서는 DPC 스케줄링 등의 작업을 한후에 리턴!!!
7) DPC 루틴이 실행되어 5번작업에서 큐에 넣었던 IRP를 삭제하고 , 데이터를 읽도록 하드웨어를 작동시킨후 시스템으로 리턴~~~~~~~~~~
8) 시스템이 드라이버의 서브루틴들을 사용중. -ㅈ- 시간이 흐름.
9) 마침내, 유저가 디바이스를 제거. PNP Manger는 IRP를 드라이버로 전달하고 , 드라이버는 이를 처리한다음 리턴.
운영체제는 드라이버의 Driver UNload 루틴을 호출해서 몇가지 일을 처리하고 리턴.
최종적으로시스템은 가상메모리에서 드라이버 코드를 제거~~~~~~~~~~~~~~~~
4. 쓰레드와 드라이버 코드.
드라이버가 어플리케이션과 다른점!
드라이버는 코드를 실행시키기 위해 시스템이 특별히 쓰레드를 생성시키지는 않는다는것!
대신 시스템이 드라이버의 서브루틴을 호출하기로 결정하면 그 순간 실행되고 있던 쓰레드 컨텍스트에서 드라이버 코드를 실행시킴!!!!
하드웨어 인터럽트가 발생하는 순간 모든 쓰레드들 중에서 어떤 것이 우연히 실행되고 있을지 알순 없지.
이것을 임의의(arbitrary) 쓰레드라고 부르며 "임의의 쓰레드 컨텍스트에서 실행된다" 라고 말함!
즉, 드라이버의 서브루틴을 호출하려 할 때, 대부분 임의의 쓰레드 컨텍스트에서 돌아가고 있을것이다.
예를들어, 자신의 드라이버의 ISR(Interrupt Service Routine)이 통제권을 가지는 순간 어떤 컨텍스트가 올지 모른다는거~
흔히 storage 드라이버의 경우에서 볼수있는데, 파일시스템 드라이버가 읽기/쓰기에 최종적인 책임을 지고 연관된 드라이버를 컨트롤 하는 형태로 되어있음!
하지만 항상 임의의 쓰레드 컨텍스트에서 드라이버 코드를 실행시키는것은 아님!
PsCreateSystemThread를 사용해서 자신의 시스템 쓰레드를 생성하거나 work item을 스케줄링해서 특정쓰레드를 요청할수도있음.
쓰레드가 임의적인지 아닌지가 왜중요하냐!
첫번째로, 드라이버는 임의의 쓰레드를 "블록킹" 해서는 안된다는거.
다른 쓰레드에 도움이 되는 작업을 수행한다고 해서 또다른 쓰레드를 중지시키는건 공평치 못하겠지?
두번째로, 드라이버가 IRP를 생성해서 다른 드라이버에게 이를 전달하는 경우인데...
임의의 쓰레드에서는 비동기적 IRP만생성가능. 그러나 비임의 쓰레드에서는 동기적IRP도 생성가능하기때문이지.
동기적 방식에서는 I/O Manager는 IRP를 생성한 그 쓰레드의 컨텍스트에 IRP를 속하게 하고, 쓰레드가 종료되면 IRP를 자동으로 취소함.
그러나 비동기적 IRP인경우, I/O Manager는 이를 특정한 쓰레드에속하게 하지는 않음. 따라서 비동기적 IRP를 생성한 쓰레드는 드라이버에서 수행하는 I/O 오퍼레이션에 대해 직접적인 관계가 없으며, 이쓰레드가 종료됬다고 IRP를 취소시키지도 않는다는거지.
5. 시스템의 드라이버 로딩 방식
하드웨어가 플러그 앤 플레이(PNP) 를 지원하는지 여부에 따라 두 가지 조금 다른 방법을 사용한다.
1) 플러그 앤 플레이 디바이스는 시스템이 감지할 수 있는 전자 서명을 가지고 있음.
시스템 bus driver는 하드웨어의 존재 유무를 파악한 후, 어떤 종류의 하드웨어 인지 판단하기 위해 서명을 읽어옴
그 후 시스템은 레지스트리와 INF 파일을 이용해 자동으로 가장 적당한 드라이버를 로드하게 됨!!
2) Legacy 디바이스는 전자서명이 없다!!! 따라서 시스템이 자동으로 디바이스를 감지할수 없뜸.
그래서 사용자가 새 하드웨어 추가 마법사를 사용해서 검색해야함. 그러고나면 시스템은 레지스트리와 INF를 이용해 자동설치를한다!