前言 :
在前一節介紹了如何在應用程式中對裝置進行同步, 非同步作業. 但是這些同步, 非同步作業必須得到驅動程式的支援. 所有裝置的操作都會轉化為 IRP 請求, 並傳遞到對應的派遣函式中. 可以有兩種方式處理 IRP 請求, 第一種是在派遣函式中直接結束 IRP 請求, 可以認為這是一種同步處理的方法, 前面介紹的例子都是基於這種處理方式, 也是最簡單的處理方式. 另一種方法是在派遣函式中不結束 IRP 請求, 而是讓派遣函式直接返回. IRP 在以後的某個時候在進行處理. 非同步處理是這裡要介紹的內容.
IRP 的同步完成 :
下面將介紹 Win32 API 函式是如何一層層透過呼叫進入到派遣函式 :
1. 在應用程式中呼叫 CreateFile Win32 API 函式, 這個函式打開裝置.
2. CreateFile Win32 API 函式內部呼叫了 ntdll.dll 中的 NtCreateFile 函式.
3. ntdll.dll 中的 NtCreateFile 函式進入核心模式, 然後呼叫 ntoskrnl.exe 中的 NtCreateFile 函式.
4. 核心模式中 ntoskrnl.exe 中的 NtCreateFile 函式新建 IRP_MJ_CREATE 類型的 IRP, 然後呼叫對應驅動程式的派遣函式, 並將 IRP 的指標傳遞給該派遣函式.
5. 派遣函式呼叫 IoCompleteRequest 將 IRP 請求結束.
6. 作業系統按原路返回, 一直到退出 CreateFile Win32 API 函式. 至此 CreateFile 函式返回.
7. 如果需要讀取裝置, 應用程式會呼叫 ReadFile Win32 API 函式.
8. ReadFile Win32 API 會呼叫 ntdll.dll 中的 NtReadFile 函式.
9. ntdll.dll 中的 NtReadFile 函式會進入核心模式, 並呼叫 ntoskrnl.exe 中的 NtReadFile 函式.
10. ntoskrnl.exe 中的 NtReadFile 函式新建 IRP_MJ_READ 類型的 IRP 並將之傳入對應的派遣函式中.
對裝置進行讀取可以有三種方式, 第一種是用 ReadFile 函式進行同步讀取, 第二種方式是用 ReadFile 函式進行非同步讀取. 第三種方法是用 ReadFileEx 函式進行非同步讀取.
- 用 ReadFile 函式進行同步讀取
- 用 ReadFile 函式進行非同步讀取
- 用 ReadFileEx 函式進行非同步讀取
IRP 的同步處理就是在派遣函式中, 將 IRP 處理完畢. 這裡指處理完畢就是呼叫 IoCompleteRequest 函式.
IRP 的非同步完成 :
IRP 被 "非同步完成" 指的就是不在派遣函式中呼叫 IoCompleteRequest 內核函式. 呼叫 IoCompleteRequest 函式意味 IRP 請求的結束, 也標誌本次對裝置操作的結束.
IRP 是被非同步完成, 而發起 IRP 的應用程式會有三種型式發起 IRP 請求, 分別是用 ReadFile 函式同步讀取裝置, 用 ReadFile 函式非同步讀取裝置, 用 ReadFileEx 非同步讀取裝置. 以下分別列出這三種方式 :
* IRP 是由 ReadFile 的同步操作引起 :
* IRP 是由 ReadFile 的非同步作業引起 :
* IRP 是由 ReadFileEx 的非同步作業引起 :
如果派遣函式不呼叫 IoCompleteRequest 函式, 則需要告訴作業系統此 IRP 處於 "Pending" 狀態. 這時需要呼叫內核函式 IoMarkIrpPending. 同時派遣函式應該返回 STATUS_PENDING. 下面為範例代碼 :
為了演示非同步處理 IRP, 假設 IRP_MJ_READ 的派遣函式僅僅是返回 "Pending" . 應用程式關閉裝置的時後會產生 IRP_MJ_CLEANUP 類型的 IRP. 在 IRP_MJ_CLEANUP 的派遣函式中結束那些 "Pending" 的 IRP_MJ_READ.
為了能儲存有哪些 IRP_MJ_READ IRP 被 "Pending", 這裡使用一個佇列, 也就把每個掛起的 IRP_MJ_READ 的指標都插入佇列, 最後 IRP_MJ_CLEANUP 的派遣函式將一個個 IRP 取出佇列並且呼叫 IoCompleteRequest 函式將它們結束. 首先要定義好佇列的資料結構, 該資料結構僅有一個子欄位來記錄 IRP 指標 :
在裝置擴充中加入 "佇列" 這個變數, 這樣驅動程式的所有派遣函式都可以使用該佇列. 在 DriverEntry 中初始化該佇列, 並在 DriverUnload 常式中回收佇列. 在 IRP_MJ_READ 的派遣函式中, 將 IRP 插入堆疊, 然後返回 "Pending" 狀態. 下面程式碼示範了非同步處理 IRP 的派遣函式 :
再關閉裝置的時候, 會產生 IRP_MJ_CLEANUP 類型的 IRP. 其派遣函式抽取佇列中每一個 "Pending" 的 IRP, 並呼叫 IoCompleteRequest 設置完成.
在應用程式中非同步作業該裝置, 先非同步讀兩次, 這樣會新建兩個 IRP_MJ_READ. 接著這兩個 IRP 被插入佇列. 在關閉裝置時, 會導致驅動程式呼叫 IRP_MJ_CLEAN_UP 的派遣函式 :
底下為執行結果 :
取消 IRP :
前面介紹了如何 "Pending" IRP 並將之插入佇列, 並且在關閉裝置時, 將 "Pending" 的 IRP 結束. 還有另一個方法可以將 "Pending" 的 IRP 逐個結束, 這就是取消 IRP 請求. 內核函式IoSetCancelRoutine 可以設置取消 IRP 請求的回呼函式, 其宣告如下 :
參數說明 :
IoSetCancelRoutine 可以將一個取消常式與該 IRP 關聯, 一旦取消 IRP 請求時, 這個常式就會被執行. IoSetCancelRoutine 函式也可以用來刪除取消常式, 當輸入的 CancelRoutine 參數為空指標時, 則刪除原來設置的取消常式.
程式設計師可以用 IoCancelIrp 函式指定取消 IRP 請求. 在 IoCancelIrp 內部, 需要進行同步. DDK 在 IoCancelIrp 內使用一個叫做 cancel 的自旋鎖進行同步. IoCancelIrp 在內部會首先獲得該自旋鎖, IoCancelIrp 會呼叫取消常式, 因此釋放該自旋鎖的任務就留給了取消常式. 獲得取消自旋鎖的函式是 IoAcquireCancelSpinLock 函式, 而釋放取消自旋鎖的函式是IoReleaseCancelSpinLock 函式.
在應用程式中, 可以呼叫 CancelIo Win32 API 函式取消 IRP 請求. 在 CancelIo 的內部會枚舉所有沒有被完成的 IRP, 然後依次呼叫 IoCancelIrp. 另外如果應用程式沒有呼叫 CancelIo 函式, 應用程式在關閉裝置時同樣會自動呼叫 CancelIo. 下面程式示範如何編寫取消常式 :
下面我們在應用程式中呼叫 CancelIO Win32 API. 這樣可以導致 IRP 的取消常式被呼叫 :
在取消常式中要注意同步問題是當退出取消常式, 一定要釋放 cancel 自旋鎖, 否則會導致系統崩潰. 另外 cancel 自旋鎖是全域自旋鎖, 所有驅動程式都會使用這個自旋鎖, 因此占用自旋鎖時間不宜過長. 底下是執行結果 :
在前一節介紹了如何在應用程式中對裝置進行同步, 非同步作業. 但是這些同步, 非同步作業必須得到驅動程式的支援. 所有裝置的操作都會轉化為 IRP 請求, 並傳遞到對應的派遣函式中. 可以有兩種方式處理 IRP 請求, 第一種是在派遣函式中直接結束 IRP 請求, 可以認為這是一種同步處理的方法, 前面介紹的例子都是基於這種處理方式, 也是最簡單的處理方式. 另一種方法是在派遣函式中不結束 IRP 請求, 而是讓派遣函式直接返回. IRP 在以後的某個時候在進行處理. 非同步處理是這裡要介紹的內容.
IRP 的同步完成 :
下面將介紹 Win32 API 函式是如何一層層透過呼叫進入到派遣函式 :
1. 在應用程式中呼叫 CreateFile Win32 API 函式, 這個函式打開裝置.
2. CreateFile Win32 API 函式內部呼叫了 ntdll.dll 中的 NtCreateFile 函式.
3. ntdll.dll 中的 NtCreateFile 函式進入核心模式, 然後呼叫 ntoskrnl.exe 中的 NtCreateFile 函式.
4. 核心模式中 ntoskrnl.exe 中的 NtCreateFile 函式新建 IRP_MJ_CREATE 類型的 IRP, 然後呼叫對應驅動程式的派遣函式, 並將 IRP 的指標傳遞給該派遣函式.
5. 派遣函式呼叫 IoCompleteRequest 將 IRP 請求結束.
6. 作業系統按原路返回, 一直到退出 CreateFile Win32 API 函式. 至此 CreateFile 函式返回.
7. 如果需要讀取裝置, 應用程式會呼叫 ReadFile Win32 API 函式.
8. ReadFile Win32 API 會呼叫 ntdll.dll 中的 NtReadFile 函式.
9. ntdll.dll 中的 NtReadFile 函式會進入核心模式, 並呼叫 ntoskrnl.exe 中的 NtReadFile 函式.
10. ntoskrnl.exe 中的 NtReadFile 函式新建 IRP_MJ_READ 類型的 IRP 並將之傳入對應的派遣函式中.
對裝置進行讀取可以有三種方式, 第一種是用 ReadFile 函式進行同步讀取, 第二種方式是用 ReadFile 函式進行非同步讀取. 第三種方法是用 ReadFileEx 函式進行非同步讀取.
- 用 ReadFile 函式進行同步讀取
- 用 ReadFile 函式進行非同步讀取
- 用 ReadFileEx 函式進行非同步讀取
IRP 的同步處理就是在派遣函式中, 將 IRP 處理完畢. 這裡指處理完畢就是呼叫 IoCompleteRequest 函式.
IRP 的非同步完成 :
IRP 被 "非同步完成" 指的就是不在派遣函式中呼叫 IoCompleteRequest 內核函式. 呼叫 IoCompleteRequest 函式意味 IRP 請求的結束, 也標誌本次對裝置操作的結束.
IRP 是被非同步完成, 而發起 IRP 的應用程式會有三種型式發起 IRP 請求, 分別是用 ReadFile 函式同步讀取裝置, 用 ReadFile 函式非同步讀取裝置, 用 ReadFileEx 非同步讀取裝置. 以下分別列出這三種方式 :
* IRP 是由 ReadFile 的同步操作引起 :
* IRP 是由 ReadFile 的非同步作業引起 :
* IRP 是由 ReadFileEx 的非同步作業引起 :
如果派遣函式不呼叫 IoCompleteRequest 函式, 則需要告訴作業系統此 IRP 處於 "Pending" 狀態. 這時需要呼叫內核函式 IoMarkIrpPending. 同時派遣函式應該返回 STATUS_PENDING. 下面為範例代碼 :
- NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
- IN PIRP pIrp)
- {
- //....
- IoMarkIrpRequest(pIrp);
- // 返回 Pending 狀態
- return STATUS_PENDING;
- }
為了能儲存有哪些 IRP_MJ_READ IRP 被 "Pending", 這裡使用一個佇列, 也就把每個掛起的 IRP_MJ_READ 的指標都插入佇列, 最後 IRP_MJ_CLEANUP 的派遣函式將一個個 IRP 取出佇列並且呼叫 IoCompleteRequest 函式將它們結束. 首先要定義好佇列的資料結構, 該資料結構僅有一個子欄位來記錄 IRP 指標 :
- typedef struct _MY_IRP_ENTRY
- {
- PIRP pIRP; // 記錄 IRP 指標
- LIST_ENTRY ListEntry;
- } MY_IRP_ENTRY, *PMY_IRP_ENTRY;
- NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
- IN PIRP pIrp)
- {
- KdPrint(("Enter HelloDDKRead\n"));
- PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension;
- PMY_IRP_ENTRY pIrp_entry = (PMY_IRP_ENTRY) ExAllocatePool(PagedPool, sizeof(MY_IRP_ENTRY));
- pIrp_entry->pIrp = pIrp;
- // 插入佇列
- InsertHeadList(pDevExt->pIRPLinkListHead, &pIrp_entry->ListEntry);
- // 將 IRP 設置為 "Pending"
- IoMarkIrpPending(pIrp);
- KdPrint(("Leave HelloDDKRead\n"));
- // 返回 Pending 狀態
- return STATUS_PENDING;
- }
在應用程式中非同步作業該裝置, 先非同步讀兩次, 這樣會新建兩個 IRP_MJ_READ. 接著這兩個 IRP 被插入佇列. 在關閉裝置時, 會導致驅動程式呼叫 IRP_MJ_CLEAN_UP 的派遣函式 :
底下為執行結果 :
取消 IRP :
前面介紹了如何 "Pending" IRP 並將之插入佇列, 並且在關閉裝置時, 將 "Pending" 的 IRP 結束. 還有另一個方法可以將 "Pending" 的 IRP 逐個結束, 這就是取消 IRP 請求. 內核函式IoSetCancelRoutine 可以設置取消 IRP 請求的回呼函式, 其宣告如下 :
參數說明 :
IoSetCancelRoutine 可以將一個取消常式與該 IRP 關聯, 一旦取消 IRP 請求時, 這個常式就會被執行. IoSetCancelRoutine 函式也可以用來刪除取消常式, 當輸入的 CancelRoutine 參數為空指標時, 則刪除原來設置的取消常式.
程式設計師可以用 IoCancelIrp 函式指定取消 IRP 請求. 在 IoCancelIrp 內部, 需要進行同步. DDK 在 IoCancelIrp 內使用一個叫做 cancel 的自旋鎖進行同步. IoCancelIrp 在內部會首先獲得該自旋鎖, IoCancelIrp 會呼叫取消常式, 因此釋放該自旋鎖的任務就留給了取消常式. 獲得取消自旋鎖的函式是 IoAcquireCancelSpinLock 函式, 而釋放取消自旋鎖的函式是IoReleaseCancelSpinLock 函式.
在應用程式中, 可以呼叫 CancelIo Win32 API 函式取消 IRP 請求. 在 CancelIo 的內部會枚舉所有沒有被完成的 IRP, 然後依次呼叫 IoCancelIrp. 另外如果應用程式沒有呼叫 CancelIo 函式, 應用程式在關閉裝置時同樣會自動呼叫 CancelIo. 下面程式示範如何編寫取消常式 :
- VOID CancelReadIRP(
- IN PDEVICE_OBJECT DeviceObject,
- IN PIRP Irp
- )
- {
- KdPrint(("Enter CancelReadIRP\n"));
- PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
- DeviceObject->DeviceExtension;
- //設置完成狀態為STATUS_CANCELLED
- Irp->IoStatus.Status = STATUS_CANCELLED;
- Irp->IoStatus.Information = 0; // bytes xfered
- IoCompleteRequest( Irp, IO_NO_INCREMENT );
- //釋放Cancel自旋鎖
- IoReleaseCancelSpinLock(Irp->CancelIrql);
- KdPrint(("Leave CancelReadIRP\n"));
- }
- NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,IN PIRP pIrp)
- {
- KdPrint(("Enter HelloDDKRead\n"));
- PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
- pDevObj->DeviceExtension;
- IoSetCancelRoutine(pIrp,CancelReadIRP);
- //將IRP設置為掛起
- IoMarkIrpPending(pIrp);
- KdPrint(("Leave HelloDDKRead\n"));
- //返回pending狀態
- return STATUS_PENDING;
- }
在取消常式中要注意同步問題是當退出取消常式, 一定要釋放 cancel 自旋鎖, 否則會導致系統崩潰. 另外 cancel 自旋鎖是全域自旋鎖, 所有驅動程式都會使用這個自旋鎖, 因此占用自旋鎖時間不宜過長. 底下是執行結果 :
This message was edited 18 times. Last update was at 17/01/2011 10:31:19
作者已經移除這則留言。
回覆刪除