디바이스 드라이버란
디바이스 드라이버란 응용 프로그램이 HARDWARE를 제어할수 있도록 인터페이스를 제공해주고 프로그래머로 하여금 HARDWARE를 인식하지 않고도 프로그램을 작성할수 있도록 하는 것이다. 그래서 작성 방법이 까다롭고 일반 프로그래밍하고는 많이 다를뿐더라 API함수 또한 많이 다르다. 굳이 다른 것과 비교를 한다면 그 형식에 있어서는 DLL 프로그래밍과 그래도 가장 많이 유사하지 않을까 싶다.
아래 그림 1을 보면 드라이버가 OS에서 위치하는 곳을 나타내어는데 커널모드에 I/O 매니저 아래에 위치하고 있다. 이러한 위치때문에 설치 뿐만 아니라 프로그램 작성하는것도 까다롭다. 이처럼 커널모드(Intel에서는 특권모드, Ring 0)에서 동작되기 때문에 혹 조금이라도 잘못 작성되면 전체 시스템에 영향을 주기 때문에 신중히 작성되어야 한다.
그림 1 윈도우 2000 의 구조 - 그림수정
디바이스 드라이버의 종류
SYS
MS-DOS시절에는 컴퓨터상의 하드웨어들의 드라이버 코드들이 그냥 ROM-BIOS(Basic Input Output System)와 DOS내부에 있었다. 시스템 소프트웨어들은 소프트웨어 인터럽트(Int 10h, Int 13h, Int 16h 등)라는 방식을 통하여 BIOS로부터 각종 서비스를 받아서 간접적으로 컴퓨터 하드웨어 장치들을 제어할 수 있게 되는 것이다. BIOS는 또한 자체적으로 하드웨어 인터럽트들을 처리한다. DOS는 이러한 BIOS의 서비스들을 토대로 하여 장치들을 제어하며, 자신의 위에 있는 계층의 소프트웨어인 도스 애플리케이션들에게 장치를 다룰 수 있도록 API(Application Programming Interface)를 제공한다. BIOS는 전적으로 컴퓨터의 마더보드와 마더보드에 기본적으로 연결되어 있는 장치들을 시스템 버스들을 통하여 제어하고, 그러한 기능들을 서비스하는 것만을 목적으로 하지만, DOS의 경우에는 여러 가지 주변장치들이 확장될 것을 고려하여 도스용 디바이스 드라이버를 만들 수 있도록 발전되어졌다. 이러한 도스용 디바이스 드라이버가 바로 Config.sys 파일에 적어 넣는 "device=xxx.sys"에서의 xxx.sys라는 파일들인 것이다.
DRV
윈도우의 초창기 버전에서는 BIOS와 DOS가 여전히 핵심적인 소프트웨어의 코어였다. 그러나 Intel사에서 80286 마이크로 프로세서가 개발되어 나오면서부터 윈도우는 운영체제로서 도스의 몫들을 가져가기 시작했다. 80286 프로세서는 8086과의 호환성을 위한 real-mode외에도 286 protected-mode를 제공하였는데, 286보호모드에서는 16MB까지의 물리적인 메모리에 접근할 수 있었다. 그 당시 윈도우는 프로세서의 모드를 리얼모드와 보호모드를 오가면서 윈도우용 애플리케이션에서도 BIOS서비스와 DOS서비스들을 사용할 때에만 리얼모드로 잠시 변화되어 수행된 후 다시 보호모드로 돌아오는 동작 메커니즘을 가지고 있었다. 이러한 방식을 286 일반모드(standard-mode)라고 부른다. 하지만 키보드나 마우스 입력과 같이 항상 사용하여야 하는 하드웨어 서비스를 받기 위하여 CPU의 모드를 계속 전환시키는 것은 너무나도 무모한 방법이므로, 당시의 윈도우는 해결책을 찾아보게 되었고 해결책은 보호모드에서 동작하는 디바이스 드라이버를 만들고 그것이 동작할 수 있도록 해주는 것이었다. 그것이 바로 *.drv라는 이름의 파일들이다.
VXD
Intel사의 80386 이상의 프로세서들은 386 강화모드(Enhanced mode)를 지원한다. 이로써 Windows는 실제의 DOS없이 CPU차원에서 도스를 흉내내기(emulate)함으로써 도스용 애플리케이션들을 윈도우에서 실행시킬 수 있게 되었다. Windows 95/98에서 흔히 보는 '도스창'이 바로 V86모드의 가상화(virtualize)된 도스이며, 동시에 여러개를 조그만 창으로 띄울 수 있다.
Windows95/98에서부터 OS는 가상기계(VM : Virtural Machine)를 다루는 본격적인 운영체제가 되었으며, 이러한 VM의 개념은 원래의 미니컴퓨터 이상의 UNIX에서 온 것이다. VM은 각각 자기 혼자 컴퓨터의 모든 자원, 예를 들면 키보드, 모니터, 마우스 등을 혼자서 차지하고 있는 것처럼 애플리케이션을 속이는 완벽한 환경이다. 여기서부터 윈도우 디바이스 드라이버에 "가상"이라는 말이 들어가게 된다. 왜냐하면 실제로 하나뿐인 장치가 각각의 VM마다 개별적으로 각각 존재하는 것처럼 속여주는 책임을 가상 디바이스 드라이버가 하게 되었기 때문이다. 이때부터 디바이스 드라이버의 확장자는 *.vxd가되었고 그 역할도 단순히 하드웨어를 제어하는 것을 넘어서서 VM을 관리하고 결국은 OS의 속모습이 되어버린 것이다. MOUSE.drv와 같은 구식 윈도우 드라이버들은 Windows98에서는 일종의 껍데기에 불과하며, 실제의 모든 동작은 VxD에서 하고 있다. Windows98에서는 이러한 *.drv의 디바이스 드라이버들을 Ring 3 DLL드라이버(Ring 3 DLL Driver)라고 부른다. 즉, *.drv가 Windows98에서 원래의 디바이스 드라이버 역할을 한다기보다는 일종의 Windows 3.1의 16비트 DLL과 똑같은 역할을 하고 있기 때문이다. 실제로 OS는 단지 VxD의 로우레벨 서비스를 DLL등으로 껍데기를 하나 씌워서 편리한 인터페이스를 구축하는 역할이 되어버렸고, 실제 모든 OS의 서비스는 사실상 VxD가 해주어야만 한다.
VMM의 역할은 사용자를 위해 컴퓨터 시스템 전체를 가상화(Virtualize)하는 것이다. 즉, 운영체제의 진정한 임무인데, 컴퓨터 시스템은 수많은 요소들로 구성되어 있으며, 수많은 기능들을 체계적으로 지원해 주어야만 한다. 이 모든 기능을 VMM이라는 하나의 실행파일에 모두 넣어둘 수는 없으며 VMM은 최종적인 모든 권한들을 가지고서 자신의 하부조직인 VxD를 조종하는 것이다.
시스템에 어떤 주변장치를 새로 달게 되면 그 주변장치를 위한 VxD들이 추가된다. 참고로 윈도우 95/98의 VxD 개발이 가장 힘이 들고 어렵고 그 다음 윈도우 NT, 리눅스(UNIX 개열 모두)로 갈수록 디바이스 드라이버 개발이 쉬운데, 이것은 각각의 운영체제의 탄생배경과 성장과정이 다르기 때문에 나타나는 특성이다. 윈도우 95/98이 가장 난해하고 지저분한 이유는 이것이 PC의 성장과정과 함께 도스 시절부터 Windows 3.1, DOS Extender, Windows for Workgroup, Netware 등의 파란만장한 변천과정 속에서 이전 모습들의 꼬리뼈들을 계속 몸에 지니고 있기 때문이다. 또한 윈도우 98은 윈도우 2000에서 윈도우 NT와 하나로 합쳐지는 중간단계에서 윈도우 NT의 여러 가지 특성들도 갖추도록 노력을 많이 하였기 때문에, PC와 운영체제의 역사를 모르는 경우에 윈도우 98의 내부 구조가 왜 그런 모습이 되었는지 이해 하기가 어려울 수 밖에 없다.
WDM
윈도우 2000으로 넘어오면서 새로이 생겨난 모델(Windows Driver Model)이다. 윈도우 2000에서는 크게 사용자 모드 드라이버와 커널 모드 드라이버를 제공한다. 사용자 모드 드라이버는 사용자 모드에서 동작하는 시스템 레벨의 코드이다. 커널 모드 드라이버는 커널 모드에서 동작하는 코드이며 직접적으로 하드웨어를 컨트롤한다.
커널 모드 드라이버는 아래 그림 2에서 보는거와 같이 일반 Legacy(NT형태) 드라이버와 WDM 드라이버가 있으며 각각 상위레벨, 중간레벨, 하위레벨 드라이버로 분류된다. WDM과 NT형태의 드라이버는 본질적으로 제작방법은 같지만 작성방법에 있어서 차이가 있다. WDM은 장치가 추가되거나 제거될때 PnP관리자가 이를 알려주므로 이에 대한 적절한 설치파일(INF)을 통해 설치한다.
상위 레벨 드라이버의 경우는 하위 레벨 드라이버에 의해서 이미 하드웨어가 동작하거나 서비스 되고 있고 추가적으로 새로운 형태의 추상화를 제공하고자 할때로 파일 시스템 드라이버가 이에 해당한다.
중간 레벨 드라이버는 디스크 미러, 클래스, 미니, 필터 드라이버들을 말하며 이러한 드라이버들은 상위와 하위 드라이버 사이에 위치하여 두 드라이버 사이에 요청을 전달하거나 추가적인 작업을 수행한다.
클래스 드라이버 : 코드 재사용에 있어 좋은 방법이다. 특정 타입의 드라이버들이 공통적인 부분들을 일반 클래스 드라이버에 위치시키는 것이다.
미니 드라이버 : 제조업체 전용 드라이버 혹은 장치 전용 드라이버들은 클래스 드라이버와 연동되는 미니 드라이버 형태로 작성된다.
필터 드라이버 : 이미 존재하는 드라이버들의 요청을 중간에서 가로채어 특별한 펑션 드라이버 : WDM 드라이버 내에서는 중간레벨의 드라이버는 펑션 드라이버로 구성될수 있다. 이는 DDK내에서 클래스나 미니 드라이버로 불릴수도 있다.
하위 레벨 드라이버는 SCSI Host Bus Adapter와 같은 하드웨어 버스를 위한 컨트롤러 드라이버들을 말한다. 이러한 드라이버들은 HAL과 통신하거나 하드웨어와 직접 통신하기도 한다.
그외 특별한 드라이버들 - 비디오 드라이버, 프린터 드라이버, 멀티미디어 드라이버, 네트워크 드라이버
비디오 드라이버는 일반적인 I/O 드라이버와 구조가 다르다. I/O관리자와 직접 통신을 하지 않고 GDI라는 커널을 통해 통신을 한다. 그림에서와 같이 칩셋(nvida, ati등)에서 비디오 미니 포트 클래스 드라이버를 제공하면 그래픽 카드 제조사등에서는 디스플레이 드라이버를 제작한다. GDI(상위 레벨 드라이버라 할수 있겠다)에서는 그래픽 카드 제조사에서 제공한 디스플레이 드라이버의 함수(DDI-Device Driver Interface라고 알려진 Drv접미사를 가진 함수들)를 호출한다. 또한 디스플레이 드라이버에서는 GDI안에 구현되어 있는 일반 그래픽 라이브러리(Eng라는 접미사를 가진다)를 호출한다. 음영처리가 되지 않은 것이 만들어야 하는 드라이버이다.
프린터 드라이버는 Spooling이라는 구조로 이루어져 있다. 응용 프로그램에서 보내진 데이타는 GDI를 거쳐 Printer Driver와 상호작용을 통해 만들어진 Spool Data(Raw)를 Spooler를 통해 관리(프린팅 작업을 Thread를 통해 CPU가 바쁘지 않은 시간을 통해 내보냄으로써 프린팅을 하면서도 다른 작업을 할수 있도록 했고 네트워크상에 연결되어 있는 프린터에게도 데이타를 보낼수 있다) 되어져 결국 최종 Print Provider에게 보내진다.
멀티미디어 드라이버는 사운드카드나 TV튜너 같은 멀티미디어 디바이스를 지원하기 위하여 커널 스트리밍이라는 필터와 펑션 드라이버로 구성된 모델을 제공한다.
네트워크 드라이버는 NDIS(Network Driver Interface Specification)는 NIC 드라이버를 지원하기 위한 라이브러리를 제공한다. NIC 제조사는 하드웨어를 제공하기 위해 NDIS 미니 포트 드라이버를 작성, 제공한다. 그 상위 레벨로 NDIS 중간레벨 드라이버와 NDIS 프로토콜 드라이버가 있다.
일반적인 디바이스 드라이버 프로그램 구조
드라이버는 일반적인 프로그램과 달리 OS(I/O 관리자)에의해 호출(CallBack)되는 함수들의 집합으로 구성되어 있다. 그러므로 개발자는 각각 호출될때 맞는 기능을 구현하면 된다. 호출되는 경우는 드라이버가 로드될때나 언로드 될때, 삽입 또는 제거될때, 응용프로그램에서의 호출, 인터럽트등 여러가지 경우가 있다.
DriverEnty는 그 드라이버의 초기화를 수행하고 DRIVER_OBJECT라는 구조체에 일련의 콜백 루틴(Create, Close, PnP, Read, Write등을 처리하기 위한 함수)에 대한 포인터등을 저장한다. 일반 NT형(Legacy) 드라이버는 이곳에서 디바이스 인스턴스를 생성하지만 WDM에서는 AddDevice에서 수행해야 한다. I/O 관리자가 드라이버를 처음 로드할때 DriverEntry루틴을 호출한다.
AddDevice는 PnP를 지원하며 통보가 왔을 경우 실제 디바이스 인스턴스를 생성한다.
Reinitialize는 DriverEntry루틴에서 초기화 작업을 완전히 수행하지 못하는 경우가 있다. 이런 경우는 초기화 되지 못하는 다른 드라이버의 종속적인 경우등 여러가지 경우가 있는데 DriverEntry루틴에 Reinitialize루틴을 지정함으로 다시 초기화 할수 있도록 한다.
Unload는 메모리로 부터 제거하기 전에 I/O관리자로 호출된다. 드라이버의 자원 및 디바이스 이름을 제거하는 과정을 수행하여야 한다. WDM 드라이버에서는 RemoveDevice에서 수행한다.
Shutdown은 컴퓨터가 꺼질때 Unload가 불리지 않기 때문에 컴퓨터가 꺼지기 이전에 해야 할 특별한 작업을 할수 있도록 한다.
Open, Close에 디바이스 드라이버를 사용하기 위해 Win32에서 CreateFile, CloseHandle등에 대한 요청을 처리하는 루틴이다. DriverEntry에서 DRIVER_OBJECT의 MajorFunction에 IRP_MJ_CREATE, IRP_MJ_CLOSE등으로 함수의 포인터를 저장한다.
Read, Write에 디바이스 드라이버에 Win32의 WriteFile, ReadFile등에 대한 요청을 처리하는 루틴이다. DriverEntry에서 DRIVER_OBJECT의 MajorFunction에 IRP_MJ_WRITE, IRP_MJ_READ등으로 함수의 포인터를 저장한다.
일반 프로그램에서 디바이스 드라이버를 사용하기 위해서는 CreateFile에 디바이스명을 인자로 주어 생성한 Handle로 WriteFile, ReadFile의 win32 명령어를 사용한다. CreateFile이란 말그대로 파일을 만드는것이 아니라 장치에 대한 핸들을 얻는것이다.
Start I/O는 데이타 전송 시점에 I/O관리자에 의해 불려진다. 모든 I/O 요청을 큐잉하고 직렬화(I/O 요청을 차례차례 순서대로 진행시킨다)한다.
ISR는 디비이스가 인터럽트를 생성할때 마다 이 루틴을 호출하게 된다. 이곳에서 너무 많은 시간을 끌면 안된다. 되도록 최소한 코드로 수행되어야 하고 처리해야할 작업이 더 남았다면 DPC를 스케쥴링 한다.
DPC는 드라이버의 동작을 완료하기 위하여 이 루틴을 제공한다. 자원을 해제하거나 에러를 리포팅하거나 I/O 요청의 완료를 지정하여 디바이스의 다음 동작을 수행한는등의 작업을 한다.
배치 파일 프로그래밍 참 좋아하는데; 요즘에는 거의가 Windows XP를 사용해서, Command Prompt를 사용할 기회가 많이 줄어든 것 같습니다.
일렬로 자신이 해야 할 작업들을 빠르고 쉽게 해결할 수 있는 방법도 많았는데...
컴퓨터 성능이 오르면서 Config.sys 와 Autoexec.bat 의 수정/보완을 통해서 메모리를 절약하고 시스템 환경을 늘리는 일은 이제 쓸모없는 일이 되어 버렸습니다.
옛날의 향수..