顯示具有 Device Driver Programming 標籤的文章。 顯示所有文章
顯示具有 Device Driver Programming 標籤的文章。 顯示所有文章

2018年6月4日 星期一

[ Windows DDP ] 驅動程式的基本結構 : WDM 式驅動的基本結構

前言 :
在 Windows 2000 以後, 微軟公司加入了新的驅動程式模型, 這就是 WDM. WDM 模式是建立在 NT 式驅動程式模型基礎上的. 因此有了前面對 NT 式驅動的理解, 對於 WDM 驅動的基本架構應該可以很容易上手.

實體裝置物件與功能裝置物件 :
在 WDM 模型中, 完成一個裝置的操作, 至少有兩個裝置物件共同完成. 其中一個是實體裝置物件 (Physical Device Object, 簡稱 PDO). 另一個是功能裝置物件 (Function Device Object, 簡稱 FDO). 其關係是 "附加" 與 "被附加" 的關係.
當 PC 插入某個裝置的時候, PDO 會自動建立. 確切的說是由匯流排驅動新建的. PDO 不能單獨操作裝置, 需要配合 FDO 一起使用. 系統會提示檢測到新裝置. 要求安裝驅動程式. 需要安裝的驅動程式指的就是 WDM 程式, 此驅動程式負責新建 FDO, 並且附加到 PDO 之上.
當一個 FDO 附加到 PDO 上的時候, PDO 裝置物件的子欄位 AttachedDevice 會記錄 FDO 的位置. PDO 被稱作底層驅動或者下層驅動, 而 FDO 被稱作高層驅動或者上層驅動. 這裡上層是指接近發出 I/O 請求的地方, 而 "下層" 指的是靠近實體裝置的地方. PDO 和 FDO 的關係可以從下圖得到更好理解 :


這是一個簡單的一種情況, 事實上要比這個複雜一些. 在 FDO 和 PDO 之間還會存在篩選驅動, 如下圖所示. 在 FDO 上面的篩選驅動被稱為上層篩選驅動. 在 FDO 的下層驅動被稱為下層篩選驅動. 另外每個裝置中, 有個 StackSize 子欄位表明操作這個裝置需要幾層才能達到最下層的實體裝置. 最上層篩選裝置物件的 StackSize 為 4, 也就是需要 4 個裝置才能到達最底層的實體裝置 :

篩選驅動可以巢狀嵌套, 也就是可以有多個高層篩選驅動, 也可以有多個底層篩選驅動. 篩選驅動不是必須存在. 在 WDM 模型中 PDO 和 FDO 是必須的. 可以看出 NT 式驅動和 WDM 驅動在設計思維上有所不同. NT 裝置是被動被裝入的, 例如當有裝置插入 PC 後, 系統不會提示, 使用者必須自己指定載入何種驅動. 而 WDM 驅動則會在插入裝置後, 系統自動新建出 PDO, 並且提示用戶安裝 FDO.
WDM 提示使用者載入 FDO, 如果該裝置已經由微軟提供, 則會自動進行安裝. 例如當 USB 滑鼠插入 PC 後, 系統就會找到對應驅動並載入. 這種設計思維導致了 WDM 模型支援隨插即用功能. 當然為了支持隨插即用功能, 這些還遠遠不夠. 匯流排驅動新建的 PDO 為程式設計員提供了很多隨插即用的服務, 這在以後的章節會接著介紹.

WDM 驅動的入口程式 :
和 NT 驅動一樣, WDM 驅動的入口程式也是 DriverEntry, 但是初始化作用被分散到其它常式中. 例如新建裝置物件的責任就不在 DriverEntry 中, 而被放在了 AddDrvice 常式中. 同時在 DriverEntry 中, 需要設置對 IRP_MJ_PNP 處理的派遣常式. 下面是 HelloWDM 的 DriverEntry 函式 :
- HelloWDM.cpp (DriverEntry 函式) :
  1. #pragma INITCODE   
  2. extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,  
  3.                                 IN PUNICODE_STRING pRegistryPath)  
  4. {  
  5.     KdPrint(("Enter DriverEntry\n"));  
  6.   
  7.     pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;  
  8.     pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;  
  9.     pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =   
  10.     pDriverObject->MajorFunction[IRP_MJ_CREATE] =   
  11.     pDriverObject->MajorFunction[IRP_MJ_READ] =   
  12.     pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloWDMDispatchRoutine;  
  13.     pDriverObject->DriverUnload = HelloWDMUnload;  
  14.   
  15.     KdPrint(("Leave DriverEntry\n"));  
  16.     return STATUS_SUCCESS;  
  17. }  

從上面程式碼可以看出 WDM 驅動的 DriverEntry 和 NT 式驅動的 DriverEntry 有以下幾點不同 :
* 增加了對 AddDevice 函式的設置. 這是 WDM 驅動和 NT 驅動非常重要的不同點. 因為 NT 驅動是主動載入裝置的, 也就是驅動一旦被載入就新建裝置. 而 WDM 驅動是被動載入裝置的. 作業系統必須載入 PDO 後, 呼叫驅動的 AddDevice 常式, AddDevice 常式中負責新建 FDO 並附加到 PDO 之上.
* 新建裝置物件已經不在這個含是中了, 而在 AddDevice 常式中新建.
* 必須加入 IRP_MJ_PNP 的派遣 Callback 函式. IRP_MJ_PNP 主要是負責電腦中隨插即用的處理, 在 WDM 驅動中加入了很多隨插即用的處理.

WDM 驅動的 AddDevice 常式 :
AddDevice 常式是 WDM 驅動所獨有的, 在 NT 驅動中沒有該常式. 在 DriverEntry 中, 需要設置 AddDevice 常式的函式位址. 設置的方式是驅動物件中有個 DriverExtension 子欄位 DriverExtension 中有個 AddDevice 子欄位, 將該欄位指向 AddDevice 常式的函式位址.
pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;

和 DriverEntry 不同, AddDevice 常式的名字可以任意命名, 在 HelloWDM 例子中, 使用的名字就是 HelloWDMAddDevice :
- HelloWDM.cpp (HelloWDMAddDevice 函式) :
  1. #pragma PAGEDCODE  
  2. NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,  
  3.                            IN PDEVICE_OBJECT PhysicalDeviceObject)  
  4. {   
  5.     PAGED_CODE();  
  6.     KdPrint(("Enter HelloWDMAddDevice\n"));  
  7.   
  8.     NTSTATUS status;  
  9.     PDEVICE_OBJECT fdo;  
  10.     UNICODE_STRING devName;  
  11.     RtlInitUnicodeString(&devName,L"\\Device\\MyWDMDevice");  
  12.     // 新建裝置物件          
  13.     status = IoCreateDevice(  
  14.         DriverObject,  
  15.         sizeof(DEVICE_EXTENSION),  
  16.         &(UNICODE_STRING)devName,  
  17.         FILE_DEVICE_UNKNOWN,  
  18.         0,  
  19.         FALSE,  
  20.         &fdo);  
  21.     if( !NT_SUCCESS(status))  
  22.         return status;  
  23.     // 得到裝置擴充  
  24.     PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)fdo->DeviceExtension;  
  25.     // 將 FDO 附加在 PDO 上  
  26.     pdx->fdo = fdo;  
  27.     pdx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);  
  28.     UNICODE_STRING symLinkName;  
  29.     RtlInitUnicodeString(&symLinkName,L"\\DosDevices\\HelloWDM");  
  30.   
  31.     // 用裝置擴充記錄裝置名和符號連結  
  32.     pdx->ustrDeviceName = devName;  
  33.     pdx->ustrSymLinkName = symLinkName;  
  34.     // 新建符號連結  
  35.     status = IoCreateSymbolicLink(&(UNICODE_STRING)symLinkName,&(UNICODE_STRING)devName);  
  36.   
  37.     if( !NT_SUCCESS(status))  
  38.     {  
  39.         IoDeleteSymbolicLink(&pdx->ustrSymLinkName);  
  40.         status = IoCreateSymbolicLink(&symLinkName,&devName);  
  41.         if( !NT_SUCCESS(status))  
  42.         {  
  43.             return status;  
  44.         }  
  45.     }  
  46.     // 設置裝置標誌  
  47.     fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;  
  48.     fdo->Flags &= ~DO_DEVICE_INITIALIZING;  
  49.   
  50.     KdPrint(("Leave HelloWDMAddDevice\n"));  
  51.     return STATUS_SUCCESS;  
  52. }  

從上述程式碼可以看出, AddDevice 常是類似於 NT 驅動中的 DriverEntry 新建裝置物件的相關操作, 但是略有不同. AddDevice 函式有兩個輸入參數, 一個是 DriverObject, 一個是裝置物件 PhysicalDeviceObject. 驅動物件是 I/O 管理器新建的驅動物件. 裝置物件 PhysicalDeviceObject 就是底層匯流排驅動新建的 PDO 裝置物件. 傳進該參數的目的就是將 FDO 附加在 PDO 之上. 在 AddDevice 可以分為下面幾個步驟 :
1. 在 AddDevice 透過 IoCreateDevice 等函式, 新建了裝置物件, 該裝置物件就是 FDO, 即功能驅動裝置物件. 和 NT 驅動一樣, 可以設置驅動物件名稱, 也可以不設置.
2. 新建完 FDO 後, 需要將 FDO 的位址保存起來, 以便日後使用. 保存的位置是在裝置擴充中, 前面介紹過在驅動程式中應該盡量避免使用全域變數, 而使用裝置擴充. 如果該電腦中存在多個同類裝置, 例如插入兩個相同型號網卡, 作業系統會呼叫兩次 AddDevice 常式. 每個 AddDevice 常式會新建各自的 FDO 並記錄在各字裝置擴充中.
3. 驅動程式將新建的 FDO 附加在 PDO 上, 附加的這個動作是依靠 IoAttachDeviceToDeviceStack 函式 實現的.
4. 設置 FDO 的 Flags 子欄位. DO_BUFFERED_IO 是定義裝置為 "緩衝記憶體裝置". 另外 ~DO_DEVICE_INITIALIZING 是將 Flag 上的 DO_DEVICE_INITIALIZING 為清零. 保證裝置初始化完畢, 這一步是必需的.

前面介紹過當 FDO 附加到 PDO 上面時, PDO 會透過 AttachedDevice 子欄位知道它上面的裝置是 FDO (或是篩選裝置) . 但是 FDO 卻不知道自己的下層是什麼裝置. 解決辦法就是透過裝置擴充記錄 FDO 下層的裝置. 下面是 HelloWDM 的裝置擴充定義 :
  1. typedef struct _DEVICE_EXTENSION  
  2. {  
  3.     PDEVICE_OBJECT fdo;             // FDO  
  4.     PDEVICE_OBJECT NextStackDevice;     //下層驅動裝置  
  5.     UNICODE_STRING ustrDeviceName;  // 裝置名  
  6.     UNICODE_STRING ustrSymLinkName; // 符號連結名  
  7. } DEVICE_EXTENSION, *PDEVICE_EXTENSION;  
程式員可以根據自己需求訂制自己的裝置擴充. 子欄位 FDO 是為了保存 FDO 的位址, 以備日後使用. 子欄位 NextStackDevice 是為了定位裝置的下一層位置. 在附加操作完成後, 需要設定符號連結, 以便使用者應用程式可以存取該裝置.

DriverUnload 常式 :
在 NT 式驅動中, DriverUnload 常式主要負責作刪除裝置和取消符號連結. 而在 WDM 驅動中, 這部分的操作被 IRP_MN_REMOVE_DEVICE 的 IRP 處理函式所負責, 而 DriverUnload 常式顯得相對簡單. 如果在 DriverEntry 中有申請記憶體的操作, 可以在 DriverUnload 常式中回收. 在 HelloWDK 程式中, DriverUnload 常式除了列印兩行 log 資訊, 什麼也沒做.

對 IRP_MN_REMOVE_DEVICE IRP 的處理 :
關於 IRP 的介紹, 後面會有詳細說明. 驅動程式內部是由 IRP 所驅動. 新建 IRP 的原因有很多, IRP_MN_REMOVE_DEVICE 這個 IRP 是當裝置需要被卸載時, 由隨插即用管理器新建, 並發送到驅動程式中. IRP 一般由兩個號碼指定該 IRP 的具體意義, 一個是 IRP 號 (Major IRP), 另一個是輔 IRP 號 (Minor IRP). 每個 IRP 都由對應的派遣函式所處理, 而派遣函式是在 DriverEntry 中指定的, 例如 :
pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;

上面這行程式就是將 IRP_MJ_PNP 的派遣函式指定為 HelloWDMPnp 函式.
當裝置需要被卸載時, 會先後發出多個 IRP_MJ_PNP. 這些 IRP 的輔 IRP 號會有所不同. 其中之一是 IRP_MN_REMOVE_DEVICE. 在 WDM 驅動程式中, 對裝置的卸載一般是對 IRP_MN_REMOVE_DEVICE 處理函式中進行卸載. 在 HelloWDM 驅動程式中, 負責處理 IRP_MN_REMOVE_DEVICE 的函式是 HandleRemoveDevice , 其程式碼如 :
- HelloWDM.cpp (HandleRemoveDevice 函式) :
  1. #pragma PAGEDCODE  
  2. NTSTATUS HandleRemoveDevice(PDEVICE_EXTENSION pdx, PIRP Irp)  
  3. {  
  4.     PAGED_CODE();  
  5.     KdPrint(("Enter HandleRemoveDevice\n"));  
  6.   
  7.     Irp->IoStatus.Status = STATUS_SUCCESS;  
  8.     NTSTATUS status = DefaultPnpHandler(pdx, Irp);  
  9.     IoDeleteSymbolicLink(&(UNICODE_STRING)pdx->ustrSymLinkName);  
  10.   
  11.     //呼叫IoDetachDevice()把fdo從裝置堆疊中脫開:  
  12.     if (pdx->NextStackDevice)  
  13.         IoDetachDevice(pdx->NextStackDevice);  
  14.       
  15.     //刪除fdo:  
  16.     IoDeleteDevice(pdx->fdo);  
  17.     KdPrint(("Leave HandleRemoveDevice\n"));  
  18.     return status;  
  19. }  

在處理 IRP_MN_REMOVE_DEVICE 函式中, 它功能類似於 NT 驅動中的 DriverUnload 函式. 除了需要刪除裝置, 取消符號連結外, 在此函式還需要將 FDO 從 PDO 上的堆疊中 "摘除" 下來. 使用的函式是 IoDetachDevice.
在 FDO 從裝置鏈中被刪除後, 但 PDO 還是存在. PDO 的刪除不是由程式設計師負責, 而是由作業系統負責.

This message was edited 11 times. Last update was at 18/12/2010 16:06:05

2012年3月8日 星期四

[ Windows DDP ] Index



起步走 :

驅動程式的基本結構 :

Windows 記憶體管理 :

Windows 內核函式 :

派遣函式 :

驅動程式的同步處理 :

This message was edited 8 times. Last update was at 08/03/2012 18:09:37

2011年12月6日 星期二

[ Windows DDP ] 驅動呼叫驅動 : 以檔案控制程式碼形式呼叫其他驅動程式


前言 :
這裡將介紹以檔案控制程式碼的方式呼叫其他驅動程式. 這種方式類似於在應用程式中呼叫驅動程式, 使用方法簡單, 不需要程式師對 Windows 底層瞭解過的的門檻.

準備一個標準驅動程式 :
在介紹驅動程式呼叫其他驅動程式之前, 首先準備一個標準驅動程式, 作為 "目標" 驅動程式. 本章後面介紹的所有驅動程式, 都以不同方法呼叫這個 "目標" 驅動程式.
這個 "目標" 驅動程式新建一個模擬裝置, 模擬裝置支援非同步讀取操作. 事先規定每次對裝置讀取需耗時 3s, 因為這樣可以很好地演示非同步讀取操作. 對於這樣的驅動程式, 應該設置一個計時器, 計時器的間隔設置為 3s. 另外在 IRP_MJ_READ 的派遣函式中不結束 IRP 請求, 而是將 IRP 請求 Pending, 並且再派遣函式退出前開啟計時器. 這樣 3s 後就會進入計時器的回呼函式中, 並在回乎函式中結束 IRP 請求.
我們把這個 "目標" 驅動程式命名為 DriverA, 呼叫 DriverA 的驅動程式被命名為 DriverB. 以下列出的是 DriverA 的部分程式碼 :
- Driver.cpp : IRP_MJ_READ 的派遣函式
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverA:Enter A HelloDDKRead\n"));  
  5.   
  6.     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)  
  7.             pDevObj->DeviceExtension;  
  8.   
  9.     //將IRP設置為掛起  
  10.     IoMarkIrpPending(pIrp);  
  11.   
  12.     //將掛起的IRP記錄下來  
  13.     pDevExt->currentPendingIRP = pIrp;  
  14.   
  15.     //定義3秒後將IRP_MJ_READ的IRP完成  
  16.     ULONG ulMicroSecond = 3000000;  
  17.   
  18.     //將32位元整數轉化成64位元整數  
  19.     LARGE_INTEGER timeout = RtlConvertLongToLargeInteger(-10*ulMicroSecond);  
  20.       
  21.     KeSetTimer(  
  22.         &pDevExt->pollingTimer,  
  23.         timeout,  
  24.         &pDevExt->pollingDPC );  
  25.   
  26.     KdPrint(("DriverA:Leave A HelloDDKRead\n"));  
  27.   
  28.     //返回pending狀態  
  29.     return STATUS_PENDING;  
  30. }  

- Driver.cpp : 超時 DPC 常式
  1. VOID OnTimerDpc( IN PKDPC pDpc,  
  2.                       IN PVOID pContext,  
  3.                       IN PVOID SysArg1,  
  4.                       IN PVOID SysArg2 )   
  5. {  
  6.     PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)pContext;  
  7.     PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;  
  8.   
  9.     PIRP currentPendingIRP = pdx->currentPendingIRP;  
  10.   
  11.     KdPrint(("DriverA:complete the Driver A IRP_MJ_READ irp!\n"));  
  12.   
  13.     //設置完成狀態為STATUS_CANCELLED  
  14.     currentPendingIRP->IoStatus.Status = STATUS_SUCCESS;  
  15.     currentPendingIRP->IoStatus.Information = 0// bytes xfered  
  16.     IoCompleteRequest( currentPendingIRP, IO_NO_INCREMENT );  
  17. }  

以上就是需要準備的 "標準" 驅動程式 DriverA. 接下來將介紹幾種不同方法, 使另外的驅動程式 (DriverB) 可以呼叫 DriverA 的讀取功能. 為了演示 DriverB 呼叫 DriverA, 並使讀者可以看清楚 log 資訊來自哪個驅動程式, 這裡讓 DriverA 在輸出 log 資訊前加上 "DriverA:" 這個Prefix, 而讓 DriverB 在輸出 log 資訊之前加上 "DriverB:" 的 Prefix.
在前面 (計時器) 的例子中, 都是讓應用程式做為驅動程式的 "客戶端". 而這裡 DriverA 的用戶端不再是應用程式, 而變成了另外的驅動程式 (DriverB). 其呼叫關係如下所示 :


獲得裝置控制碼 :
前言準備了 DriverA, 接著要介紹如何編寫 DriverB 並呼叫 DriverA. 下面回憶一下應用程式是如何呼叫驅動程式吧. 應該先用 CreateFile 函式打開裝置, 然後使用 ReadFile 函式讀取裝置, 最後用 CloseHandle 函式關閉裝置.
在驅動程式中, 打開裝置使用 ZwCreateFile 內核函式, 它會返回裝置控制碼. 這裡要討論一下如何用 ZwCreateFile 內核函式打開 "同步" 裝置和 "非同步" 裝置. 其函式宣告如下 :
- Syntax :
  1. NTSTATUS ZwCreateFile(  
  2.   __out     PHANDLE FileHandle,  
  3.   __in      ACCESS_MASK DesiredAccess,  
  4.   __in      POBJECT_ATTRIBUTES ObjectAttributes,  
  5.   __out     PIO_STATUS_BLOCK IoStatusBlock,  
  6.   __in_opt  PLARGE_INTEGER AllocationSize,  
  7.   __in      ULONG FileAttributes,  
  8.   __in      ULONG ShareAccess,  
  9.   __in      ULONG CreateDisposition,  
  10.   __in      ULONG CreateOptions,  
  11.   __in_opt  PVOID EaBuffer,  
  12.   __in      ULONG EaLength  
  13. );  

ZwCreateFile 內核函式的大多數參數已經詳細介紹過, 這裡只介紹打開 "同步" 裝置和 "非同步" 裝置的區別. 如果打開 "同步" 裝置, 第二個參數 DesiredAccess 需要設置為 SYNCHRONIZE, 並且倒數第三個參數 CreateOptions 需要指定為 FILE_SYNCHRONOUS_IO_NONALERT 或者 FILE_SYNCHRONOUS_IO_ALERT. 範例代碼如下 :
  1. // 同步打開裝置  
  2. // 設定了 FILE_SYNCHRONOUS_IO_NONALERT 或者 FILE_SYNCHRONOUS_IO_ALERT 為同步打開裝置  
  3. ntStatus = ZwCreateFile(&hDevice,  
  4.                                         FILE_READ_ATTRIBUTES|SYNCHRONIZE,  
  5.                                         &objectAttributes,  
  6.                                         &status_block,  
  7.                                         NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,  
  8.                                         FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);  
如果打開了 "非同步" 裝置, 第二個參數 DesiredAccess 不能設置為 SYNCHRONIZE, 並且倒數第三個參數 CreateOptions 不能指定 FILE_SYNCHRONOUS_IO_NONALERT 或 FILE_SYNCHRONOUS_IO_ALERT.

同步呼叫 :
在應用程式中, ReadFile 函式既可以用來讀取檔案, 也可以用來讀取裝置. 同樣在驅動程式中, ZwReadFile 內核函式既可以用來讀取檔案, 也可以讀取裝置. 如果用 ZwReadFile 內核函式同步讀取裝置, 操作對應的 IRP_MJ_READ 請求被結束後, 函式才會返回, 否則函式會一直等待 IRP_MJ_READ 請求被結束. 而這裡介紹的 DriverB 是利用同步讀取方式呼叫 DriverA, 如用 ZwReadFile 內核函式同步讀取 DriverA 的裝置, 它的內部操作過程如下 :
1. 在 DriverB 中用 ZwReadFile 內核函式讀取 DriverA 裝置物件. ZwReadFile 內核函式內部會新建 IRP_MJ_READ 類型的 IRP, 然後將這個 IRP 當作參數傳遞給 DriverA 的派遣函式.
2. DriverA 的派遣函式沒有結束 IRP 請求, 而是將 IRP 請求 Pending.
3. ZwReadFile 函式會一直等待 IRP 中的一個事件, 此時當前執行緒進入睡眠狀態.
4. 3s 後, 觸發 DriverA 的計時器常式, 這時候 IRP 請求被結束, IRP 中的相關事件也被設置.
5. 由於相關事件被設置, 剛才休眠的執行緒恢復執行, ZwReadFile 內核函式退出.

明白上述過程後, 使用 ZwReadFile 內核函式進行同步讀取就變的簡單了. 下面程式碼演示了如何使用 ZwReadFile 內核函式讀取 DriverA 的裝置. 另外 DriverB 還需要應用程式的用戶端, 因此 DriverB 的派遣函式還需要結束來自應用程式的 IRP 請求, 底下是相關範例代碼 :
- Driver.cpp of DriverB : READ IRP 派遣函式
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverB:Enter B HelloDDKRead\n"));  
  5.     NTSTATUS ntStatus = STATUS_SUCCESS;  
  6.   
  7.     UNICODE_STRING DeviceName;  
  8.     RtlInitUnicodeString( &DeviceName, L"\\Device\\MyDDKDeviceA" );  
  9.   
  10.     //初始化objectAttributes  
  11.     OBJECT_ATTRIBUTES objectAttributes;  
  12.     InitializeObjectAttributes(&objectAttributes,   
  13.                             &DeviceName,  
  14.                             OBJ_CASE_INSENSITIVE,   
  15.                             NULL,   
  16.                             NULL );  
  17.   
  18.     HANDLE hDevice;  
  19.     IO_STATUS_BLOCK status_block;  
  20.     //同步打開裝置  
  21.     //設定了FILE_SYNCHRONOUS_IO_NONALERT或者FILE_SYNCHRONOUS_IO_ALERT為同步打開裝置  
  22.     ntStatus = ZwCreateFile(&hDevice,  
  23.         FILE_READ_ATTRIBUTES|SYNCHRONIZE,  
  24.         &objectAttributes,  
  25.         &status_block,  
  26.         NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ,  
  27.         FILE_OPEN_IF,FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);  
  28.   
  29.     if (NT_SUCCESS(ntStatus))  
  30.     {  
  31.         ZwReadFile(hDevice,NULL,NULL,NULL,&status_block,NULL,0,NULL,NULL);  
  32.     }  
  33.       
  34.     ZwClose(hDevice);  
  35.   
  36.     // 完成IRP  
  37.     pIrp->IoStatus.Status = ntStatus;  
  38.     pIrp->IoStatus.Information = 0;  // bytes xfered  
  39.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  40.     KdPrint(("DriverB:Leave B HelloDDKRead\n"));  
  41.     return ntStatus;  
  42. }  

底下是執行結果 :


非同步呼叫方法一 :
非同步讀取主要是指 ZwReadFile 內核函式在沒有等待 DriverA 真正結束 IRP 請求時就已經退出. 如果 ZwReadFile 內核函式非同步讀取 DriverA 裝置時, 它的內部操作過程如下 :
1. ZwReadFile 內核函式新建 IRP_MJ_READ 類型的 IRP, 然後將這個 IRP 傳遞給 DriverA 派遣函式
2. DriverA 派遣函式沒有結束 IRP 請求, 而是 "Pending" 該 IRP.
3. ZwReadFile 內核函式發現 DriverA 將 IRP_MJ_READ "Pending", 於是它直接返回, 返回值是 STATUS_PENDING, 這代表讀取操作正在進行中.

ZwReadFile 內核函式退出後, 無法得知 "Pending" 的 IRP 何時被結束. 因此在呼叫 ZwReadFile 前可以為IRP 設置一個完成常式. 當 IRP 結束時就觸發這個完成常式. 在本例中將一個事件的指標傳遞給該完成常式, 在完成常式中觸發該事件. 一旦這個事件被觸發, 就可以知道 IRP_MJ_READ 請求已經結束. 下面程式碼演示了如何用 ZwReadFile 內核函式進行非同步讀取操作 :
- Driver.cpp of DriverB : Read IRP 派遣函式
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverB:Enter B HelloDDKRead\n"));  
  5.     NTSTATUS ntStatus = STATUS_SUCCESS;  
  6.   
  7.     UNICODE_STRING DeviceName;  
  8.     RtlInitUnicodeString( &DeviceName, L"\\Device\\MyDDKDeviceA" );  
  9.   
  10.     //初始化objectAttributes  
  11.     OBJECT_ATTRIBUTES objectAttributes;  
  12.     InitializeObjectAttributes(&objectAttributes,   
  13.                             &DeviceName,  
  14.                             OBJ_CASE_INSENSITIVE,   
  15.                             NULL,   
  16.                             NULL );  
  17.   
  18.     HANDLE hDevice;  
  19.     IO_STATUS_BLOCK status_block;  
  20.     //非同步打開裝置  
  21.     //沒有設定了FILE_SYNCHRONOUS_IO_NONALERT和FILE_SYNCHRONOUS_IO_ALERT為非同步打開裝置  
  22.     ntStatus = ZwCreateFile(&hDevice,  
  23.         FILE_READ_ATTRIBUTES,//沒有設SYNCHRONIZE  
  24.         &objectAttributes,  
  25.         &status_block,  
  26.         NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ,  
  27.         FILE_OPEN_IF,0,NULL,0);  
  28.   
  29.     KEVENT event;  
  30.     //初始化事件,用於非同步讀  
  31.     KeInitializeEvent(&event,SynchronizationEvent,FALSE);  
  32.   
  33.     LARGE_INTEGER offset = RtlConvertLongToLargeInteger(0);  
  34.     if (NT_SUCCESS(ntStatus))  
  35.     {  
  36.         ntStatus = ZwReadFile(hDevice,NULL,CompleteDriverA_Read,&event,&status_block,NULL,0,&offset,NULL);  
  37.     }  
  38.   
  39.     if (ntStatus==STATUS_PENDING)  
  40.     {  
  41.         KdPrint(("DriverB:ZwReadFile return STATUS_PENDING!\n"));  
  42.         KdPrint(("DriverB:Waiting..."));  
  43.         KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,NULL);  
  44.     }  
  45.     ZwClose(hDevice);  
  46.   
  47.     ntStatus = STATUS_SUCCESS;  
  48.     // 完成IRP  
  49.     pIrp->IoStatus.Status = ntStatus;  
  50.     pIrp->IoStatus.Information = 0;  // bytes xfered  
  51.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  52.     KdPrint(("DriverB:Leave B HelloDDKRead\n"));  
  53.     return ntStatus;  
  54. }  

以下是完成常式的程式碼. 完成常式主要是用一個事件通知 IRP 請求被結束.
- Driver.cpp of DriverB : 完成常式 CompleteDriverA_Read
  1. VOID CompleteDriverA_Read(PVOID context,PIO_STATUS_BLOCK pStatus_block,ULONG)  
  2. {  
  3.     KdPrint(("DriverB:The Driver A Read completed now!\n"));  
  4.     KeSetEvent((PKEVENT)context,IO_NO_INCREMENT,FALSE);  
  5. }  

底下是執行結果 :


非同步呼叫方法二 :
非同步方法一在非同步讀取時, 將一個事件的控制碼傳遞給 ZwReadFile 內核函式, 這個事件可以用來通知讀取操作何時完成. 而非同步方法二不用將事件控制碼傳遞給 ZwReadFile 函式, 而是透過檔案物件判斷是否讀取操作完畢. 每打開一個裝置, 都會伴隨一個關聯的檔案物件 (FILE_OBJECT). 利用內核函式 ObReferenceObjectByHandle 可以獲取和裝置相關的檔案物件指標.當 IRP_MJ_READ 請求被結束後, 檔案物件的子欄位 Event 會被設置, 因此用檔案物件的 Event 子欄位可以當做同步處理使用. 以下是相關的部分程式碼 :
- Driver.cpp of DriverB : Read IRP 的派遣函式
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverB:Enter B HelloDDKRead\n"));  
  5.     NTSTATUS ntStatus = STATUS_SUCCESS;  
  6.   
  7.     UNICODE_STRING DeviceName;  
  8.     RtlInitUnicodeString( &DeviceName, L"\\Device\\MyDDKDeviceA" );  
  9.   
  10.     //初始化objectAttributes  
  11.     OBJECT_ATTRIBUTES objectAttributes;  
  12.     InitializeObjectAttributes(&objectAttributes,   
  13.                             &DeviceName,  
  14.                             OBJ_CASE_INSENSITIVE,   
  15.                             NULL,   
  16.                             NULL );  
  17.   
  18.     HANDLE hDevice;  
  19.     IO_STATUS_BLOCK status_block;  
  20.       
  21.     //非同步打開裝置  
  22.     ntStatus = ZwCreateFile(&hDevice,  
  23.         FILE_READ_ATTRIBUTES,//沒有設SYNCHRONIZE  
  24.         &objectAttributes,  
  25.         &status_block,  
  26.         NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ,  
  27.         FILE_OPEN_IF,0,NULL,0);  
  28.   
  29.     LARGE_INTEGER offset = RtlConvertLongToLargeInteger(0);  
  30.     if (NT_SUCCESS(ntStatus))  
  31.     {  
  32.         ntStatus = ZwReadFile(hDevice,NULL,NULL,NULL,&status_block,NULL,0,&offset,NULL);  
  33.     }  
  34.   
  35.     if (ntStatus==STATUS_PENDING)  
  36.     {  
  37.         KdPrint(("DriverB:ZwReadFile return STATUS_PENDING!\n"));  
  38.   
  39.         PFILE_OBJECT FileObject;  
  40.         ntStatus = ObReferenceObjectByHandle(hDevice, EVENT_MODIFY_STATE, *ExEventObjectType,  
  41.                         KernelMode, (PVOID*) &FileObject, NULL);  
  42.         if (NT_SUCCESS(ntStatus))  
  43.         {  
  44.             KdPrint(("DriverB:Waiting..."));  
  45.             KeWaitForSingleObject(&FileObject->Event,Executive,KernelMode,FALSE,NULL);  
  46.             KdPrint(("DriverB:Driver A Read IRP completed now!\n"));  
  47.             ObDereferenceObject(FileObject);  
  48.         }  
  49.     }  
  50.     ZwClose(hDevice);  
  51.   
  52.     ntStatus = STATUS_SUCCESS;  
  53.     // 完成IRP  
  54.     pIrp->IoStatus.Status = ntStatus;  
  55.     pIrp->IoStatus.Information = 0;  // bytes xfered  
  56.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  57.     KdPrint(("DriverB:Leave B HelloDDKRead\n"));  
  58.     return ntStatus;  
  59. }  


透過符號連結打開裝置 :
前面介紹的例子都是透過裝置名打開裝置. 但是很多情況, 使用者是不容易知道具體的裝置名, 而只知道符號連結. 例如 "C:" 代表第一個硬碟分割區. 而 "C:" 就是一個符號連結, 他指向一個磁碟分割裝置. 尤其在 WDK 驅動程式中, 透過符號連結打開裝置是常用的方法.
利用 ZwOpenSymbolicLinkObject 內核函式先得到符號連結的控制碼, 然後使用 ZwQuerySymbolicLinkObject 內核函式找到裝置名. 透過裝置名就可以方便的打開裝置了. 底下示相關的範例代碼 :
- Driver.cpp of DriverB : Read IRP 派遣函式
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverB:Enter B HelloDDKRead\n"));  
  5.     NTSTATUS ntStatus = STATUS_SUCCESS;  
  6.   
  7.     UNICODE_STRING DeviceSymbolicLinkName;  
  8.     RtlInitUnicodeString( &DeviceSymbolicLinkName, L"\\??\\HelloDDKA" );  
  9.   
  10.     //初始化objectAttributes  
  11.     OBJECT_ATTRIBUTES objectAttributes;  
  12.     InitializeObjectAttributes(&objectAttributes,   
  13.                             &DeviceSymbolicLinkName,  
  14.                             OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,   
  15.                             NULL,   
  16.                             NULL );  
  17.   
  18.     HANDLE hSymbolic;  
  19.     //設定了FILE_SYNCHRONOUS_IO_NONALERT或者FILE_SYNCHRONOUS_IO_ALERT為同步打開裝置  
  20.     ntStatus = ZwOpenSymbolicLinkObject(&hSymbolic,FILE_ALL_ACCESS,&objectAttributes);  
  21. #define UNICODE_SIZE 50  
  22.     UNICODE_STRING LinkTarget;  
  23.     LinkTarget.Buffer = (PWSTR)ExAllocatePool(PagedPool,UNICODE_SIZE);  
  24.     LinkTarget.Length = 0;  
  25.     LinkTarget.MaximumLength = UNICODE_SIZE;  
  26.   
  27.     ULONG unicode_length;  
  28.     ntStatus = ZwQuerySymbolicLinkObject(hSymbolic,&LinkTarget,&unicode_length);  
  29.   
  30.     KdPrint(("DriverB:The device name is %wZ\n",&LinkTarget));  
  31.   
  32.     InitializeObjectAttributes(&objectAttributes,   
  33.                             &LinkTarget,  
  34.                             OBJ_CASE_INSENSITIVE,   
  35.                             NULL,   
  36.                             NULL );  
  37.       
  38.     HANDLE hDevice;  
  39.     IO_STATUS_BLOCK status_block;  
  40.     //設定了FILE_SYNCHRONOUS_IO_NONALERT或者FILE_SYNCHRONOUS_IO_ALERT為同步打開裝置  
  41.     ntStatus = ZwCreateFile(&hDevice,  
  42.         FILE_READ_ATTRIBUTES|SYNCHRONIZE,  
  43.         &objectAttributes,  
  44.         &status_block,  
  45.         NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ,  
  46.         FILE_OPEN_IF,FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);  
  47.   
  48.     if (NT_SUCCESS(ntStatus))  
  49.     {  
  50.         ZwReadFile(hDevice,NULL,NULL,NULL,&status_block,NULL,0,NULL,NULL);  
  51.     }  
  52.       
  53.     ZwClose(hDevice);  
  54.     ZwClose(hSymbolic);  
  55.     ExFreePool(LinkTarget.Buffer);  
  56.   
  57.     ntStatus = STATUS_SUCCESS;  
  58.     // 完成IRP  
  59.     pIrp->IoStatus.Status = ntStatus;  
  60.     pIrp->IoStatus.Information = 0;  // bytes xfered  
  61.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  62.     KdPrint(("DriverB:Leave B HelloDDKRead\n"));  
  63.     return ntStatus;  
  64. }  

底下是執行結果 :
This message was edited 22 times. Last update was at 08/02/2011 17:44:20

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...