驱动开发基础知识 偶然发现的,很全面
?????????
1、三種類型的WDM驅動程序
?? 總線驅動程序(bus driver)
?? 功能驅動程序(function driver)
?? 過濾驅動程序(filter driver)
2、其他分類方法
?? 類驅動程序(class driver)
?? 端口驅動程序(port driver)
?? 小端口驅動程序(miniort driver)
3、驅動對象(DRIVER_OBJECT)主要成員
?? DeviceObject: 指向一個設備對象鏈表,每個設備對象代表一個設備。
?? DriverExtension: 一個結構體, 該結構只有AddDevice成員可以直接訪問。
?? DriverStartIo: 指向驅動程序中處理I/O請求的函數。
?? DriverUnload: 指向驅動程序中的清除函數。
?? MajorFunction: 為一個函數指針表, 指向存在于驅動程序中的各個IRP處理函數, 它定義了I/O請求如何進入驅動程序。
4、設備對象(DEVICE_OBJECT)主要成員
?? DriverObject: 指向與該設備對象相關的驅動程序對象。過濾驅動程序有時需要用這個指針來尋找被過濾設備的驅動程序對象。
?? CurrentIrp: 指向最近發往驅動程序StartIo函數的I/O請求包。
?? Flags: 包含一組標志位
????? DO_BUFFERED_IO: 讀寫操作使用緩沖方式(系統復制緩沖區)訪問用戶模式數據
????? DO_EXCLUSIVE: 一次只允許一個線程打開設備句柄
????? DO_DIRECT_IO: 讀寫操作使用直接方式(內存描述符表)訪問用戶模式數據
????? DO_DEVICE_INITIALIZING: 設備對象正在初始化
????? DO_POWER_PAGABLE: 必須在PASSIVE_LEVEL級上處理IRP_MJ_PNP請求
????? DO_POWER_INRUSH: 設備上電期間需要大電流
?? Characteristics: 包含另一組標志位,描述設備的可選特征
????? FILE_REMOVABLE_MEDIA: 可更換媒介設備
????? FILE_READ_ONLY_DEVICE: 只讀設備
????? FILE_FLOPPY_DISKETTE: 軟盤驅動器設備
????? FILE_WRITE_ONCE_MDEIA: 只寫一次設備
????? FILE_REMOTE_DEVICE: 通過網絡連接訪問的設備
????? FILE_DEVICE_IS_MOUNTED: 物理媒介已在設備中
????? FILE_DEVICE_SECURE_OPEN: 在打開操作中檢查設備對象的安全屬性
?? DeviceType: 一個枚舉常量,描述設備類型。
????? FILE_DEVICE_PRINTER: 打印機
????? FILE_DEVICE_SCANNER: 掃描儀
????? ...
????? FILE_DEVICE_UNKNOWN: 未知設備
5、驅動程序對象與設備對象的關系
?? 一方面, 驅動程序對象通常有多個與它相關的設備對象,因此它利用DeviceObject指針指向一個設備對象列表,該列表表示驅動程序可以控制的物理設備。
?? 另一方面, 設備對象反過來指向它自己的驅動程序對象, 這樣I/O管理器就知道在接收一個I/O請求時應該調用哪個驅動程序。每個功能碼都對應一個驅動程序的入口點。
6、I/O請求包(IRP)
?? IRP是I/O系統用來存儲處理I/O請求所需信息的地方, I/O管理器在IRP中保存一個指向調用者文件對象的指針。從編程的角度看, IRP是I/O管理器在響應一個I/O請求時從非分頁系統內存中分配的一塊可變大小的數據結構內存, I/O管理器每收到一個來自用戶的請求就創建一個該結構,并將其作為參數傳給驅動程序的DispatchXxx、StartIo等例程。該結構中存放有請求的類型、用戶緩沖區的首地址、用戶請求數據的長度等信息。驅動程序處理完這個請求后, 也在該結構中添加處理結果的有關信息, 然后調用IoCompleteRequest將其返回給I/O管理器, 用戶程序的請求隨即返回。
???
?? 每個IRP可以被看成由兩部分組成: 固定部分和一個I/O堆棧。IRP的固定部分包含關于請求的信息, I/O堆棧則包含一系列I/O堆棧單元(I/O Stack location), 單元的數目應與驅動程序堆棧中處理這一請求的驅動程序數目相同, 每個單元對應一個將處理該IRP的驅動程序。
???
?? IRP固定部分的域
????? MdlAddress(Memory Descriptor List, MDL): 指向一個內存描述表。當驅動程序使用直接I/O時, MDL用來描述一個與該請求相關聯的用戶模式緩沖區
????? AssociatedIrp: 該域是一個三指針聯合, 其中與WDM驅動程序相關的指針是AssociatedIrp.SystemBuffer。如果設備執行緩沖I/O, 則SystemBuffer指針指向系統空間緩沖區, 否則為NULL。
????? RequestorMode: 取值為一個枚舉常量UserMode或KernelMode, 指定請求初始化的模式為用戶模式還是核心模式。驅動程序有時需要查看這個值來決定是否需要信任某些參數
????? Cancel: 該域為BOOLEAN類型。如果為TRUE, 則表明IoCancelIrp已被調用, 該函數用于取消這個請求。如果為FALSE, 則表明沒有調用IoCancelIrp函數
????? CancelIrql: 一個IRQL(I/O Request Query Level)值, 表明那個專用的取消自旋鎖是在這個IRQL上獲取的。當驅動程序在取消例程中釋放自旋鎖時應該參考這個域
????? CancelRoutine: 指向驅動程序取消例程的地址。應該使用IoSetCancelRoutine函數設置CancelRoutine域而不是直接修改該域
?? IRP堆棧單元中的域
????? 任何內核模式程序在創建一個IRP時, 同時還創建了一個與之關聯的I/O堆棧。堆棧中的I/O堆棧單元由IO_STACK_LOCATION結構定義, 每個堆棧單元都對應一個將處理的IRP的驅動程序。為了在一個給定的IRP中確定當前的IRP I/O堆棧單元, 驅動程序可以調用IoGetCurrentIrpStackLocation函數, 該函數返回當前I/O堆棧單元的指針。
????? MajorFunction: 該IRP的主要功能代碼, 它指出所要執行的I/O操作類型。例如: 主功能代碼IRP_MJ_READ表示通過Win32 API函數CreateFile發送的請求。主功能代碼與驅動程序對像的MajorFunction表中的某個分發函數指針相對應。
????? MinorFunction: 該IRP的副功能代碼, 它進一步指出該IRP屬于哪個主功能類。例如: IRP_MJ_PNP請求有十幾個副功能代碼, 包括IRP_MN_START_DEVICE、IRP_MN_REMOVE_DEVICE等。
????? DeviceObject: 指向該堆棧單元對應的設備對象地址, 該域由IoCallDriver函數負責填寫。
????? FileObject: 指向與一個I/O請求有關的文件對象地址。
?? I/O功能代碼
????? IRP_MJ_CREATE: 打開設備? CreateFile
????? IRP_MJ_CLEANUP: 在關閉設備時, 取消掛起的I/O請求? CloseHandle
????? IRP_MJ_CLOSE: 關閉設備? CloseHandle
????? IRP_MJ_READ: 從設備獲得數據? ReadFile
????? IRP_MJ_WRITE: 向設備發送數據? WriteFile
????? IRP_MJ_DEVICE_CONTROL: 對用戶模式或內核模式客戶可用的控制操作? DeviceControl
????? IRP_MJ_INTERNAL_DEVICE_CONTROL: 只對內核模式客戶程序可用的控制操作? 沒有對應的Win32 API
????? IRP_MJ_QUERY_INFORMATION: 得到文件的長度? GetFileLength
????? IRP_MJ_SET_INFORMATION: 設置文件的長度? SetFileLength
????? IRP_MJ_FLUSH_BUFFERS: 寫輸出緩沖區或丟棄輸入緩沖區? FlushFileBuffers FlushConsoleInputBuffer PureComm
????? IRP_MJ_SHUTDOWN: 系統關閉? InitialSystemShutdown
7、單層驅動程序的I/O請求
????? I/O請求經過子系統DLL
????? 子系統DLL調用I/O管理器的NtWriteFile服務
????? I/O管理器分配一個描述該請求的的IRP, 并通過調用IoCallDriver函數向驅動程序(這里指設備驅動程序)發送請求
????? 驅動程序將IRP中的數據傳輸到設備并啟動I/O操作
????? 通過中斷CPU, 驅動程序發信號進行I/O完成操作
????? 在設備完成操作并且中斷CPU時, 設備驅動程序服務于中斷
????? 驅動程序調用IoCompleteRequest函數表明它已經處理完IRP請求, 接管I/O管理器完成I/O請求
??
?
?
驅動程序可用的內核態例程:
Ex...?????執行支持
Hal...?????硬件抽象層(僅NT/Windows 2000)
Io...?????I/O管理器(包括即插即用例程)
Ke...?????內核
Mm...?????內存管理器
Ob...?????對象管理器
Po...?????電源管理
Ps...?????進程結構
Rtl...?????運行時庫
Se...?????安全引用監視
Zw...?????其他例程
總線驅動程序和類特定的例程:
BatteryClass...????小類驅動程序的電池類例程
Hid...?????人工輸入設備(HID)例程
Pc...?????用于小端口驅動程序的SCSI Tape類例程
Usb...?????用于USB客戶驅動程序的通用串行總線驅動程序接口例程
?
IRP_MJ_CREATE????創建或打開設備文件
IRP_MJ_CLOSE????關閉句柄
IRP_MJ_READ????讀
IRP_MJ_WRITE????寫
IRP_MJ_CLEANUP????取消文件句柄上的任何等待的IRP
IRP_MJ_DEVICE_CONTROL???設備I/O控制
IRP_MJ_INTERNAL_DEVICE_CONTROL??來自高層驅動程序的I/O控制
IRP_MJ_SYSTEM_CONTROL???WMI
IRP_MJ_POWER????電源管理請求
IRP_MJ_PNP????即插即用消息
IRP_MJ_SHUTDOWN????關閉通知
?
UNICODE_STRING結構定義:
typedef struct _UNICODE_STRING{
?USHORT Length;
?USHORT MaximumLength;
?PWSTR? Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
Buffer域指向一個寬16位字符緩沖區,該字符串通常不是NULL終止的,而是由Length域指定字符串的當前大小(字符數)
UNICODE_STRING函數:
RtlAnsiStringToUnicodeString:??把ANSI字符串轉換成Unicode字符串,可選地分配一個緩沖區
RtlAppendUnicodeStringToString:??把一個Unicode字符串追加到另一個Unicode字符串,直到目標緩沖區的最大長度
RtlAppendUnicodeToString:??追加一個寬字符串到Unicode字符串,直到目標緩沖區的最大長度
RtlCompareUnicodeString:??比較兩個Unicode字符串,可選擇不去分大小寫
RtlCopyUnicodeString:???把一個Unicode字符串復制到另一個Unicode字符串,直到目標緩沖區的最大長度
RtlEqualUnicodeString:???如果兩個Unicode字符串相等,返回TRUE,可選擇不區分大小寫
RtlInitUnicodeString:???設置Unicode字符串緩沖區指向指定的寬字符串,并設置長度域為匹配值
RtlIntegerToUnicodeString:??把ULong值轉換成指定基的Unicode字符串,字符串緩沖區必須提前初始化
RtlPrefixUnicodeString:???檢查一個Unicode字符串是否是另一個Unicode字符串的前綴,可選擇不區分大小寫
RtlUnicodeStringToAnsiString:??把Unicode字符串轉換成ANSI,如果分配目標ANSI緩沖區,最終使用RtlFreeAnsiString釋放它
RtlUnicodeStringToInteger:??把Unicode字符串轉換成一個整數
RtlUpcaseUnicodeString:???把Unicode字符串轉換成大寫,可選地分配緩沖區
????IoCreateDevice函數:
------------------------------------------------
NTSTATUS IoCreateDevice???IRQL == PASSIVE_LEVEL
------------------------------------------------
IN PDRIVER_OBJECT DriverObject??驅動程序對象
IN ULONG DeviceExtensionSize??要求的設備擴展的大小
IN PUNICODE_STRING DeviceName??設備名稱,或者NULL
IN DEVICE_TYPE DeviceType??設備的類型,標準頭文件WDM.H或NTDDK.H中列出的FILE_DEVICE_XXX值之一
IN ULONG DeviceCharacteristics??各種常量用OR組合在一起,指示可刪除介質、只讀等
IN BOOLEAN Exclusive???如果一次只有一個線程可以訪問該設備,為TRUE
OUT PDEVICE_OBJECT* DeviceObject?返回的設備對象
------------------------------------------------
????IoRegisterDeviceInterface函數
------------------------------------------------
NTSTATUS IoRegisterDeviceInterface?IRQL == PASSIVE_LEVEL
------------------------------------------------
參數?????描述
------------------------------------------------
IN PDEVICE_OBJECT PhysicalDeviceObject?設備PDO
IN CONST GUID* InterfaceClassGuid?被注冊的GUID
IN PUNICODE_STRING ReferenceString?通常是NULL,引用字符串或成為接口名稱的一部分,所以可以用于區分同一設備的不同接口
OUT PUNICODE_STRING SymbolicLinkName?輸出接口符號連接名,完成使用時,不要忘記使用RtlFreeUnicodeString釋放這個Unicode字符串
------------------------------------------------
????IoRegisterDeviceInterface函數
------------------------------------------------
NTSTATUS IoRegisterDeviceInterface
------------------------------------------------
參數?????描述
------------------------------------------------
IN PDEVICE_OBJECT PhysicalDeviceObject?設備PDO
IN CONST GUID* InterfaceClassGuid?被注冊的GUID
IN PUNICODE_STRING ReferenceString?通常是NULL,引用字符串成為接口名稱的一部分,所以可以用于區分統一設備的不同接口
OUT PUNICODE_STRING SymbolicLinkName?輸出接口符號鏈接名。完成使用時,不要忘記使用RtlFreeUnicodeString釋放這個Unicode字符串
設備名:
符號鏈接:這個名字向內核標識設備,而不是向Win32標識設備。根據約定,內核設備名從0開始編號,而符號鏈接名從1開始編號。用IoCreateSymbolicLink創建。
設備接口:驅動程序使用設備接口使它的設備對Win32程序可見,主要思想是:每個設備使一個定義的應用程序編程接口(API)可用,全局唯一標識符(GUID)用于標識這個接口。
?
操作系統版本的確定:
可以使用注冊表設置在運行時確定是否正運行Windows 98。在NT和Windows 2000中,可用以下注冊表值:HKLM/System/CurrentControlSet/Control/ProductOptions/ProductType。對Workstation/Professional Windows版本,這個值是"WinNT";對于Server/Enterprise版本,這個值是"LanmanNT"或者是"ServerNT"。在Windows 98中,這個注冊表值不可用。
驅動程序出錯的方式:
1、操作系統崩潰( 訪問不存在的內存,在DISPATCH_LEVEL或以上的中斷級訪問分頁內存 )
2、內核轉儲
3、驅動程序不啟動
4、掛起
5、遺漏資源
6、時間依賴性
?
檢查版本:
#if DGB
? DbgPrint( "Wdm1 checked" );
#else
? DbgPrint( "Wdm1 free" );
?
可重入性:就是說它可以被"同時"調用處理連個不同的IRP。在多處理器的Windows 2000系統中,一個處理器可能是用一個IRP調用Wdm1Read,另一個CPU上的第二個進程也同時使用另一個IRP調用Wdm1Read。
使一個例程是可重入的第一個技術是使用局部變量,
?
????DebugPrint格式說明符
------------------------------------------------
符號??格式說明符????類型
------------------------------------------------
%c??ANSI字符????char
%C??寬字符?????wchar_t
%d,%i??十進制有符號整數???int
%D??十進制_int64????_int64
%I??IRP主功能代碼和次功能代碼??PIRP
%L??十六進制的LARGE_INTEGER???LARGE_INTEGER
%s??NULL終止的ANSI字符串???char*
%S??NULL終止的寬字符串???wchar_t*
%T??UNICODE_STRING????PUNICODE_STRING
%u??十進制的ULONG????ULONG
%x??十六進制的ULONG????ULONG
------------------------------------------------
?
????一些IRP首部結構域
------------------------------------------------
域?????描述
------------------------------------------------
IO_STATUS_BLOCK IoStatus??IRP的完成狀態
PVOID AssociatedIrp.SystemBuffer?系統空間緩沖區(用于緩沖I/O)
BOOLEAN Cancel????設置IRP是否已經被取消
ULONG Flags????IRP標志
------------------------------------------------
????CTL_CODE宏參數
------------------------------------------------
參數?????描述
------------------------------------------------
DeviceType????指定IoCreateDevice的FILE_DEVICE_XXX值
ControlCode????IOCTL功能代碼
?????0x000 ~ 0x7FF 為Microsoft保留
?????0x800 ~ 0xFFF是私有代碼
TransferType????METHOD_BUFFERED
?????METHOD_IN_DIRECT
?????METHOD_OUT_DIRECT
?????METHOD_OUT_DIRECT
?????METHOD_NEITHER
RequiredAccess????FILE_ANY_ACCESS
?????FILE_READ_DATA
?????FILE_WRITE_DATA
?????FILE_READ_DATA | FILE_WRITE_DATA
------------------------------------------------
?
緩沖I/O:
??? 驅動程序可以使用兩個主要的方法訪問用戶緩沖區,這兩個方法是緩沖I/O和直接I/O。在創建設備時,必須設置新設備對象的Flags域中的DO_BUFFERED_IO位來使用緩沖I/O,或設置DO_DIRECT_IO位來使用直接I/O。
如果使用緩沖I/O,內核使用戶的緩沖區在某個非分頁內存中可用,并在IRP首部的AssociatedIrp.SystemBuffer域中存儲合適的指針。在驅動程序中簡單地讀或寫這個內存。
這個技術是驅動程序開發者最容易使用的一個技術。但是,它總體上速度要慢些,因為操作系統通常必須要把用戶緩沖區復制到非分頁內存或者從非分頁內存復制出來。
直接I/O:
??? 使用內存描述符列表(MDL)速度要快些,但這僅可用于執行直接內存訪問(DMA)的硬件。用戶緩沖區的MDL放在IRP首部的MdlAddress域中。
DeviceIoControl緩沖區
?
自旋鎖:內核自旋鎖在BufferLock提供這個保護。自旋鎖可以用在代碼需要短時訪問某種資源的地方。
KSPIN_LOCK BufferLock;???//? 聲明一個自旋鎖對象
KeInitializeSpinLock( &BufferLock );?//? 初始化
KeAcquireSpinLock( &BufferLock, XXX );?//? 獲得自旋鎖
KeReleaseSpinLock( &BufferLock, XXX );?//? 釋放自旋鎖
"自旋(spin)"是指KeAcquireSpinLock不斷進行監視。由于這個原因,我們只能持有一個自旋鎖很短的時間。DDK建議不要持有自旋鎖超過25微秒。
我們在KeAcquireSpinLock的調用中,必須提供一個指向KIRQL變量的指針,這個變量存儲在提升到DISPATCH_LEVEL之前的原始IRQL級。在必要時,KeReleaseSpinLock的調用降低IRQL。如果肯定代碼在DISPATCH_LEVEL IRQL運行,可以使用KeAcquireSpinLockAtDpcLevel和KeReleaseSpinLockFromDpcLevel例程得到更好的性能。
在持有一個自旋鎖時,不要訪問分頁代碼或數據,因為系統幾乎肯定會崩潰。在持有一個自旋鎖時,千萬不要退出主分發例程。
?
設備驅動程序工作的次序:
1) 一個標準總線驅動程序檢測何時添加一個設備
2) 設備標識符用于發現驅動程序。
3) 驅動程序被裝入,并告訴它添加了一個新設備。
4) 進一步的消息告訴用戶使用了哪些資源。
5) 然后驅動程序與設備通信,這可能要使用一個標準驅動程序的服務來實現。
當一個設備拔出時,Windows檢測到這個事件,并告訴驅動程序該設備已經不存在。
?
????即插即用設備檢測
------------------------------------------------
目標?????在加電時檢測全部設備
?????裝入最合適的驅動程序
?????為每個驅動程序分配資源
?????對于可熱插拔的設備,在添加或刪除設備時處理
解決方案????提供枚舉器總線驅動程序,發現新設備,并檢測何時添加或刪除設備
?????每個設備提供標識符,用于發現最合適的驅動程序
?????總線驅動程序或INF文件提供設備的資源分配
?????提供仲裁器確定給每個設備分配哪些資源
?????發送消息指示設備事件
缺點?????正在運行的設備可能必須停止,這樣才可以重新分配它們資源
------------------------------------------------
?
過濾驅動程序只是在設備第一次安裝時安裝,所以在設備棧構造后就不能插入過濾驅動程序。
?
單一驅動程序和分層驅動程序:鍵盤驅動程序為單一驅動程序,因為它接管處理鍵盤要求的所有處理(即它不是用其它的驅動程序)。相反,USB驅動程序使用分層方法,為了使用USB鍵盤,鍵盤驅動程序需要使用USB類驅動程序的服務。
?
????即插即用次功能代碼
------------------------------------------------
常用PnP IRP?????功能
------------------------------------------------
IRP_MN_START_DEVICE????分配資源并啟動一個設備
IRP_MN_QUERY_REMOVE_DEVICE???詢問一個設備是否可以被刪除
IRP_MN_CANCEL_REMOVE_DEVICE???取消查詢刪除請求
IRP_MN_REMOVE_DEVICE????設備被拔出或卸下取消資源分配并刪除設備
IRP_MN_SURPRISE_REMOVAL(僅Windows 2000)??用戶在意外的情況下拔出設備
IRP_MN_QUERY_STOP_DEVICE???取消查詢停止請求
IRP_MN_STOP_DEVICE????停止設備進行資源重新分配
不常用PnP IRP
IRP_MN_QUERY_DEVICE_RELATIONS???查詢有特定特征的PDO
IRP_MN_QUERY_INTERFACE????讓驅動程序導出直接調用接口
IRP_MN_QUERY_CAPABILITIES???查詢設備功能(如它是否可以被鎖定貨彈出)
IRP_MN_QUERY_RESOURCES????取出設備的引導配置資源
IRP_MN_QUERY_RESOURCE_REQUIREMENTS??查詢設備的資源要求
IRP_MN_QUERY_DEVICE_TEXT???得到設備的描述或位置字符串
IRP_MN_FILTER_RESOURCE_REQUIREMENTS??讓過濾驅動程序和功能驅動程序過濾設備的資源要求
IRP_MN_READ_CONFIG????讀配置空間信息
IRP_MN_WRITE_CONFIG????設置配置空間信息
IRP_MN_EJECT?????從設備槽彈出設備
IRP_MN_SET_LOCK?????設置設備鎖定狀態
IRP_MN_QUERY_ID?????設置設備的硬件、兼容性和實例ID
IRP_MN_QUERY_PNP_DEVICE_STATE???設置設備狀態位映射中的位
IRP_MN_QUERY_BUS_INFORMATION???得到副總線的類型和實例號
IRP_MN_DEVICE_USAGE_NOTIFICATION??通知設備是在分頁文件、休眠文件還是崩潰轉儲文件的路徑中
IRP_MN_QUERY_LEGACY_BUS_INFORMATION??返回遺留總線信息(僅Windows 2000)
------------------------------------------------
?
當一個設備被添加到系統時,Windows查找正確的驅動程序,并調用它的DriverEntry例程,告訴它添加了一個設備。就是這個時候,驅動程序創建他自己的的設備對象,即功能設備對象(FDO)。但是驅動程序還不試圖訪問它的設備硬件。
在處理的過程中,驅動程序收到一個IRP_MN_START_DEVICE IRP,包括設備被分配的資源的信息,然后他開始與設備硬件進行合適的對話。
如果一個設備要被拔出,Windows使用IRP_MN_REMOVE_DEVICE IRP查詢驅動程序設備是否可以被刪除。如果驅動程序同意,則發送一個IRP_MN_REMOVE_DEVICE IRP刪除設備。如果驅動程序不希望設備被刪除(如它正處于一個長的傳輸過程中),它將拒絕刪除請求,然后發送IRP_MN_CANCEL_REMOVE_DEVICE IRP,使它回到開始的狀態。
如果用戶偶然拔出一個設備,在Windows 98中給驅動程序發送一個IRP_MN_REMOVE_DEVICE IRP,在Windows 2000中發送IRP_MN_SUPPRISE_REMOVE IRP。我們必須處理好中斷的傳輸。
在刪除請求的響應中,IRP_MN_QUERY_STOP_DEVICE IRP詢問是否可以停止設備。如果可以停止設備,則發出IRP_MN_STOP_DEVICE IRP,使設備進入停止狀態。如果不能停止設備,則發出IRP_MN_CANCEL_STOP_DEVICE IRP,使設備回到啟動狀態。當設備停止時,驅動程序不能訪問它的設備,一個IRP_MN_START_DEVICE IRP通知驅動程序設備的新資源并再次啟動設備。
注意:當設備處于停止狀態或等待狀態時可能會收到刪除設備消息。
?
PnP配置管理器為驅動程序將基本的系統資源分類為:I/O端口、內存地址、DMA通道和中斷。
?
當Windows啟動時,它并不知道哪些設備連接到計算機上。它可以發現自己的一些基本信息,如有多少內存,但是如何發現其他的信息呢?
Windows使用驅動程序枚舉可用的硬件。枚舉是指查找并列出任何可用的設備,然后使用仲裁器調整所有的資源要求。對每個設備插找合適的驅動程序,然后告訴這些驅動程序使用哪些資源,并運行這些驅動程序。
?
?查找
根設備 |________ 串行設備
??????|
??????|查找
??????|________ 鍵盤
??????|查找???? 查找???查找
??????|________ PCI總線? _______ PnP ISA 總線 ________ 聲卡
????? |
????? |
????? |查找???查找
????? |______ USB 總線 _____________ USB 鍵盤
??????? |
??????? |
??????? |?查找
??????? |____________ USB 打印機
?
有兩類電路可以用于PC到物理USB總線的接口:開放主機控制接口(OpenHCI)和通用主機控制器接口(UHCI)。Windows選擇OpenHCI.sys驅動程序或者UHCI.sys驅動程序作為到這些電路接口的驅動程序層。
?
為一個設備服務的主驅動程序稱為功能驅動程序。安裝INF文件可能指定多個功能驅動程序放在一個設備棧中。
?
每個功能驅動程序或過濾驅動程序中的AddDevice例程在創建新的設備棧時調用。對每個AddDevice例程傳遞一個指向相同總線驅動程序PDO的指針,然后AddDevice創建一個FDO,掛接到該設備棧。AddDevice例程的調用順序確定設備棧中驅動程序的順序。這樣,設備棧自底向上構造。類似地,當一個設備被刪除時,設備棧通過先刪除最上面的驅動程序來析構。PDO作為這個設備棧的固定點,因為設備棧中的每個驅動程序有相同的PDO指針。
?
????USB 鍵盤設備棧
------------------------------------------------
驅動程序????設備棧
------------------------------------------------
HID鍵盤驅動程序????HID鍵盤設備FDO
?????HID類設備FDO
?????PDO(由USB集線器總線驅動程序創建)
USB集線器????USB集線器設備FDO
?????PDO(由USB主機控制器總線驅動程序創建)
USB主機控制器????USB主機控制器設備FDO
?????FDO(由PCI總線驅動程序創建)
PCI適配器????PCI設備FDO
?????PDO(由根總線驅動程序創建)
------------------------------------------------
?
支持即插即用主要是指實現一個AddDevice例程和一個IRP_MJ_PNP處理程序。這個PnP IRP有8個次功能代碼,大多數的WDM驅動程序需要支持這些次功能代碼:
IRP_MN_START_DEVICE(啟動設備)
IRP_MN_QUERY_REMOVE_DEVIE(查詢刪除)
IRP_MN_REMOVE_DEVICE(刪除設備)
IRP_MN_CANCEL_REMOVE_DEVICE(取消刪除)
IRP_MN_STOP_DEVICE(停止設備)
IRP_MN_QUERY_STOP_DEVICE(查詢停止)
IRP_MN_CANCEL_STOP_DEVICE(取消停止設備)
IRP_MN_SURPPISE_REMOVE(意外刪除)
?
PnP的處理:
處理設備的添加和刪除
得到分配的資源
處理查詢停止和查詢刪除消息
處理停止設備消息
處理意外刪除消息。
但是,上面只是一些基本的處理功能,驅動程序顯然還要做以下的工作:
僅在設備啟動時允許I/O請求
在有任何打開的句柄時不允許刪除設備
在設備每次啟動時,排隊I/O請求
在處理刪除請求前,等待任何I/O請求完成
在低層設備啟動后處理啟動設備消息
沿設備棧向下傳遞不支持的IRP
?
AddDevice例程的工作是創建和初始化一個設備對象供當前驅動程序使用,并把該對象連接到設備棧。如果一個驅動程序的AddDevice例程失敗,將給它下面的任何驅動程序(其AddDeivice已成功)發送一個刪除設備消息。在AddDevice例程完成后,準備好接收刪除設備消息。
?
當添加一個Wdm2設備時,Unknown總線驅動程序為它創建一個PDO。PnP管理器把這個PDO傳遞給Wdm2。下面給出的Wdm2AddDevice例程調用IoCreateDevice創建Wdm2功能設備對象,并最后調用IoAttachDeviceToDeviceStack,把該設備對象掛接到設備棧。在這個過程中,FDO和它的設備擴展被初始化,并為它建立一個設備接口。
刪除設備消息處理程序的最終工作是停止設備,并刪除FDO。在Wdm2中,最終調用IoDetachDevice從設備棧卸下該設備,并調用IoDeleteDevice刪除設備對象,即它的設備擴展內存。注意刪除設備請求是驅動程序卸載前的最后一個IRP。
?
考慮各種PnP停止消息。當某個新硬件添加到系統或者新設備插入時,發生PnP停止消息。PnP管理器可能決定,它只有通過重新分配現有的設備目前在使用的資源,才能接納新的設備。它首先發出一個“查詢停止”消息。如果設備棧中的所有設備都同意停止,則PnP管理器發出一個“停止設備”請求。如果設備棧中的任何驅動程序不希望設備停止,PnP管理器發出“取消停止”消息,并可能通知用戶目前沒有足夠的資源可用,所以需要重新啟動。當資源被重新分配時,它發送“啟動設備”消息,帶有新的資源分配。
在這整個過程中,已存在的設備上的I/O請求似乎肯定正常執行。實際上,這意味著不開始任何新的請求,并在一個隊列中保存這些請求,當設備再次啟動時處理。所以用戶可能注意到資源重新分配時有短暫的停頓,但I/O請求不會失敗。
?
當用戶想刪除一個設備時,PnP管理器總是使用“查詢刪除”請求詢問刪除這個設備是否合適。所以,對于主“刪除設備”請求,驅動程序可以確定設備上沒有在處理的I/O請求。
但是,一些設備是可熱插拔的(即用戶可以帶點插拔設備)。在這種情況下,PnP驅動程序接受不到“查詢刪除”消息,在Windows 98中,它只是得到一個“刪除設備”消息。當所有打開的句柄關閉時,Windows 2000首先發送“意外刪除”消息,然后發送“刪除設備”請求。
?
記錄有多少I/O請求正在處理過程中的最好方法是什么呢?答案是在設備擴展中的UsageCount域中記錄打開的I/O請求的數目。在每個IRP請求的開始時,進行對LockDevice的調用,對UsageCount計數器增1。在每個IRP完成時,調用UnlockDevice,對UsageCount計數器減1。
?
Windows 2000提供標準的例程替我們的LockDevice和UnLockDevice例程,并提供相關的變量。在設備擴展中必須提供一個IO_REMOVE_LOCK域,在AddDevice例程中使用IoInitializeRemoveLock函數初始化這個域。使用IoAcquireRemoveLock替換LockDevice的所有調用,使用IoReleaseRemoveLock替換對UnLockDevice的所有調用。DDK建議在傳遞出對代碼的引用(如定時器、DPC或任何其他的回調例程)時,要調用IoAcquireRemoveLock。當這些回調例程禁止時,調用IoReleaseRemoveLock,這通常在“刪除設備”處理程序中發生。
總結
以上是生活随笔為你收集整理的驱动开发基础知识 偶然发现的,很全面的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++编译预处理:宏定义指令、文件包含指
- 下一篇: 私有云促进企业管理变革 助力企业快步前行