Tuesday, September 04, 2007

Windows Driver programming[5]

Windows driver 永遠是被動者。實做 driver 要用哪種語言呢?當然是 C/C++啦。

既然我永遠都是被動者,那我怎麼知道你呼叫我的時候,我的狀態是什麼?舉個例子來說,當 OS 跟我說, 要我 clean up 的時候,我怎麼知道我要 clean up 什麼東西,有哪些東西是需要等待?Global 變數嗎?這是個好主意。 不過這種方式實在太下流了,連 MS 都不屑使用。這時,當然是 Driver 兩大主角要出場啦:Drvier Object 以及 Device Object。
typedef struct _DEVICE_OBJECT {
 CSHORT Type;
 USHORT Size;
 LONG ReferenceCount;
 struct _DRIVER_OBJECT *DriverObject;
 struct _DEVICE_OBJECT *NextDevice;
 struct _DEVICE_OBJECT *AttachedDevice;
 struct _IRP *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;
 struct _DEVOBJ_EXTENSION *DeviceObjectExtension;
 PVOID Reserved;
} DEVICE_OBJECT;

typedef struct _DRIVER_OBJECT {
 CSHORT Type;
 CSHORT Size;

 // The following links all of the devices created by a single driver together on a list, and the Flags word provides an extensible flag
 // location for driver objects.

 PDEVICE_OBJECT DeviceObject;
 ULONG Flags;

 // The following section describes where the driver is loaded.  The count
 // field is used to count the number of times the driver has had its
 // registered reinitialization routine invoked.

 PVOID DriverStart;
 ULONG DriverSize;
 PVOID DriverSection;
 PDRIVER_EXTENSION DriverExtension;

 // The driver name field is used by the error log thread
 // determine the name of the driver that an I/O request is/was bound.

 UNICODE_STRING DriverName;

 // The following section is for registry support.  Thise is a pointer
 // to the path to the hardware information in the registry

 PUNICODE_STRING HardwareDatabase;

 // The following section contains the optional pointer to an array of
 // alternate entry points to a driver for "fast I/O" support.  Fast I/O
 // is performed by invokzing the driver routine directly with separate
 // parameters, rather than using the standard IRP call mechanism.  Note
 // that these functions may only be used for synchronous I/O, and when
 // the file is cached.

 PFAST_IO_DISPATCH FastIoDispatch;

 // The following section describes the entry points to this particular
 // driver.  Note that the major function dispatch table must be the last
 // field in the object so that it remains extensible.

 PDRIVER_INITIALIZE DriverInit;
 PDRIVER_STARTIO DriverStartIo;
 PDRIVER_UNLOAD DriverUnload;
 PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];

} DRIVER_OBJECT;

typedef struct _DRIVER_EXTENSION {

 // Back pointer to Driver Object

 struct _DRIVER_OBJECT *DriverObject;

 // The AddDevice entry point is called by the Plug & Play manager
 // to inform the driver when a new device instance arrives that this
 // driver must control.

 PDRIVER_ADD_DEVICE AddDevice;

 // The count field is used to count the number of times the driver has
 // had its registered reinitialization routine invoked.

 ULONG Count;

 // The service name field is used by the pnp manager to determine
 // where the driver related info is stored in the registry.

 UNICODE_STRING ServiceKeyName;

 // Note: any new shared fields get added here.

} DRIVER_EXTENSION, *PDRIVER_EXTENSION;

很複雜對不對?不用擔心啦,先看 DEVICE_OBJECT,有沒有看到 DeviceExtension 啊?這個就是 Windows Driver 的奧妙啦(搞不好大家都是這樣玩)。 它的型別是 PVOID,也就是 void *。為甚麼要宣告成 PVOID,那是因為你的東西都可以放在裡面,而且不管是那一種 Driver,都可以把它自己需要紀錄的東西放到 DeviceExtension 內。 而且 Windows 保證,每當呼叫到你的 Driver 所提供的 function 時,一定會把 DEVICE_OBJECT 放到參數內,所以你可以不必擔心怎樣知道你的 Driver 的狀態啦。

接下來得進入正題,Driver 的 entry point:

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)

DriverEntry 就跟 C 的 main 一樣,只是 C 的 runtime 認 main,而 Windows 認 DriverEntry 罷了。 在 DriverEntry 內要做什麼事呢?記住,IN 表示這個參數是 OS 給的,表示我們要利用這個參數作某些事情,或是把這個參數給設定好。

回頭看 DRIVER_OBJECT,我們要填的東西可多了:有 DriverUnload、AddDevice function、以及 MajorFunction function pointer array。

DriverUnload 很簡單,就是 undo DriverEntry()。而 AddDevice 會等到 OS 真的要把你的 Driver instantiate 的時候,才會呼叫。MajorFunction array 可多了,基本上,OS 會透過這個 function pointer array 來呼叫你填入的 function。

DriverEntry() 呼叫完,OS 會接著呼叫你剛剛在 DRIVER_OBJECT 內填入的 AddDevice():

NTSTATUS AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT pdo)

DriverObject 就是 DriverEntry() 傳入的 DriverObject。pdo 呢,就是你的下家的 DeviceObject。 要知道,WDM driver stack 是由下往上長的,所以你會有下家幫你處理 IRP,而你的上家也會把 IRP 丟給你處理。 而要把 IRP 丟給下家,得先把這個 pdo 給記起來才行。記到哪裡?當然是萬惡的 DeviceExtension 啦。

問題來啦,DRIVER_OBJECT 是 OS 給的,那 DEVICE_OBJECT 呢?當然是呼叫 IoCreateDevice() 啦:

NTSTATUS IoCreateDevice(
    IN PDRIVER_OBJECT  DriverObject,
    IN ULONG  DeviceExtensionSize,
    IN PUNICODE_STRING  DeviceName  OPTIONAL,
    IN DEVICE_TYPE  DeviceType,
    IN ULONG  DeviceCharacteristics,
    IN BOOLEAN  Exclusive,
    OUT PDEVICE_OBJECT  *DeviceObject
    );

注意一下,DeviceExtensionSize 是指 OS allocate 出來的 DEVICE_OBJECT 所含的 DeviceExtension 大小。 換句話說,DeviceExtension 是 OS 幫你配置出來的。取得 DEVICE_OBJECT 之後,呼叫 IoAttachDeviceToDeviceStack(),把你的 DeviceObject 放到你的下家之上。大功告成。

當然,在 AddDevice() 內還要初始化許多東西,包括 spinlock/event、Dpc for ISR、double-linked list、DeviceExtension 等等的 resource。

No comments:

codeblock