Synchronous and Asynchronous IRP
IRP 只有一種,就是 IRP。會有 Synchronous 跟 Asynchronous 之分,是源自於 IRP 傳送的方式。如果你寫過 Socket 程式,那你應該會知道 Asynchronous I/O 跟 Synchronous I/O 的概念。 簡言之,便是 Synchronous IRP 一傳送,Driver 就得等這個 IRP 完成才能夠繼續執行,而 Asynchronous IRP 會繼續執行下去。 這個考量在 User mode Application 當然沒問題(其實還是會有啦,不然不會有一堆 Asynchronous API 出來),只要把這些東西放到 Thread 內,然後再用一些同步機制就可以搞定。 但是這一招在 Driver 裡面是不行的,Windows Driver 裡有定義每個函式能夠執行的 IRQL,而跑在 Dispatch Level 之上的 thread,是不能夠使用 Synchronous IRP 的, 因為它們不可以睡覺,而 Synchronous IRP 會讓該 thread 去睡覺。使用 Asynchronous IRP 也有很大的困擾:同步的問題。在 Driver 裡面同步可不比 Application mode,一不小心就 BSOD 了,麻煩。 另外,Asynchronous IRP 必須在 Driver 內部配置跟釋放,而 Synchronous IRP 則是 Driver 配置,系統(I/O manager)釋放。
在這邊以 USB 當例子。
Allocate IRP
- Synchronous IRP
- Asynchronous IRP
PIRP IoBuildDeviceIoControlRequest ( INULONG IoControlCode, INPDEVICE_OBJECT DeviceObject, INPVOID InputBuffer OPTIONAL, INULONG InputBufferLength, OUTPVOID OutputBuffer OPTIONAL, INULONG OutputBufferLength, INBOOLEAN InternalDeviceIoControl, INPKEVENT Event, OUTPIO_STATUS_BLOCK IoStatusBlock );
通常這樣就可以了。如果是 USB 的話,Driver 還需要先產生一個 URB:
VOID UsbBuildInterruptOrBulkTransferRequest ( IN OUTPURB Urb, INUSHORT Length, INUSBD_PIPE_HANDLE PipeHandle, INPVOID TransferBuffer OPTIONAL, INPMDL TransferBufferMDL OPTIONAL, INULONG TransferBufferLength, INULONG TransferFlags, INPURB Link );
如果 Driver 要建立的是一個 USB Bulk 或 Interrupt Transfer 的話,請呼叫 UsbBuildInterruptOrBulkTransferRequest()。
然後再設定 IRP 的參數:
PIO_STACK_LOCATION nextStack =IoGetNextIrpStackLocation (irp); nextStack->Parameters.Others.Argument1 = pUrb;
至於為甚麼要這樣?因為當初根本沒想到會有這種東西出現吧,所以只好用很奇怪的欄位塞入 URB。
PIRP IoAllocateIrp ( INCCHAR StackSize, INBOOLEAN ChargeQuota );
然後再設定 IRP 的參數:
PIO_STACK_LOCATION nextStack =IoGetNextIrpStackLocation (irp); nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; // Internal Device Control for USB nextStack->Parameters.Others.Argument1 = pUrb; // for USB nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB; // for USB submition
最後再設定 IoCompleteRoutine:
VOID IoSetCompletionRoutine ( INPIRP Irp, INPIO_COMPLETION_ROUTINE CompletionRoutine, INPVOID Context, INBOOLEAN InvokeOnSuccess, INBOOLEAN InvokeOnError, INBOOLEAN InvokeOnCancel );
NTSTATUS IoCompletion ( INPDEVICE_OBJECT DeviceObject, INPIRP Irp, INPVOID Context );
Send IRP
丟出 IRP 倒是很容易:NTSTATUS IoCallDriver ( INPDEVICE_OBJECT DeviceObject, IN OUTPIRP Irp );
- Synchronous IRP
- Asynchronous IRP 不需額外處理。
呼叫完 IoCallDriver() 之後,如果回傳值是 STATUS_PENDING 的話,還得呼叫 KeWaitForSingleObject() 等待 IRP 完成。
NTSTATUS KeWaitForSingleObject ( INPVOID Object, INKWAIT_REASON WaitReason, INKPROCESSOR_MODE WaitMode, INBOOLEAN Alertable, INPLARGE_INTEGER Timeout OPTIONAL );
Cancel IRP
- Synchronous IRP/Asynchronous IRP
BOOLEAN IoCancelIrp (INPIRP Irp);
一般而言,Calcel IRP 的動作幾乎只會在 Asynchronous IRP 內進行,這是因為 Synchronous IRP 一定會呼叫 KeWaitForSingleObject(),並且指定其 Timeout 為 INFINITE。 但是如果 Timeout 不是 INFINITE 呢?那可能得先為這個 IRP 安裝一個 Complete Routine,如果 KeWaitForSingleObject() 失敗的話, 再呼叫 IoCancelIrp(),然後再呼叫 KeWaitForSingleObject(),等待這個 IRP 被 Cancel。
為 Synchronous IRP 呼叫 IoCancelIrp() 的機會很少,只是我很倒楣,常常會遇到,因為硬體不穩啊,常常一個簡單的 IO command 就死掉了, 所以 KeWaitForSingleObject() 就得設 Timeout,理論上,Timeout 之後的 Synchronous IRP 是不需要去作特別處理的, 不過如果不取消它,Windows 會有機會 BSOD,所以還是乖乖呼叫 IoCancelIrp() 吧。
Release IRP
- Asynchronous IRP
- Synchronous IRP 不需要呼叫 IoFreeIrp()。
VOID IoFreeIrp (INPIRP Irp);
Callers of IoFreeIrp must be running at IRQL <= DISPATCH_LEVEL.
Conclusion
IRP 不困難,困難的是 IRP 的流程。舉例,如果你的 Driver 發了一堆 Asynchronous IRP,結果在這些 IRP 還沒完成之前,OS 要結束你的 Driver 了, 你得先確保這些 IRP 都已經被正確的完成或取消,才能呼叫 IoFreeIrp(),然後再離開。
又,如果你的 Driver 收到一堆 IRP,結果又把他們放到旁邊不管,在 Driver 結束之前,得先統統完成他們才行。
1 comment:
thanks
Post a Comment