国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

IRP的同步

2019-11-10 23:27:03
字體:
來源:轉載
供稿:網友

應用層對設備的同步與異步操作

以WriteFile為例,一般的同步操作是調用WriteFile完成后,并不會返回,應用程序會在此處暫停,一直等到函數將數據寫入文件中并正常返回,而異步操作則是調用WriteFile后會馬上返回,但是操作系統有另一線程在繼續執行寫的操作,這段時間并不影響應用程序的代碼往下執行,一般異步操作都有一個事件用來通知應用程序,異步操作的完成,以下圖分別來表示同步和異步操作: 這里寫圖片描述 這里寫圖片描述 在調用這些函數時可以看做操作系統提供一個專門的線程來處理,然后如果選擇同步,那么應用層線程會等待底層的線程處理完成后接著執行才執行后面的操作,而異步則是應用層線程接著執行后面的代碼,而由操作系統來通知,因此一般來說異步相比較與同步少去了等待操作返回的過程,效率更高一些,但是選擇同步還是異步,應該具體問題具體分析

同步操作設備

如果需要對設備進行同步操作,那么在使用CreateFile時就需要以同步的方式打開,這個函數的第六個參數dwFlagsAndAttributes是同步和異步操作的關鍵,如果給定了參數FILE_FLAG_OVERLAPPED則是異步的,否則是同步的。一旦用這個函數指定了操作方式,那么以后在使用這個函數返回的句柄進行操作時就是該中操作方式,但是這個函數本身不存在異步操作方式,一來這個函數沒有什么耗時的操作,二來,如果它不正常返回,那么針對這個設備的操作也不能進行。 一般像WriteFile、ReadFile、DeviceIoControl函數最后一個參數lpOverlapped,是一個OVERLAPPED類型的指針,如果是同步操作,需要給這個參數賦值為NULL

異步操作方式

設置Overlapped參數實現同步

一般能夠異步操作的函數都設置一個OVERLAPPED類型的參數,它的定義如下

typedef struct _OVERLAPPED { ULONG_PTR Internal; ULONG_PTR InternalHigh; DWord Offset; DWORD OffsetHigh; HANDLE hEvent; } OVERLAPPED;

對于這個參數在使用時,其余的我們都不需要關心,一般只使用最后一個hEvent成員,這個成員是一個事件對象的句柄,在使用時,先創建一個事件對象,并設置事件對象無信號,并將句柄賦值給這個成員,一旦異步操作完成,那么系統會將這個事件設置為有信號,在需要同步的地方使用Wait系列的函數進行等待即可。

int main(){ HANDLE hDevice = CreateFile("test.dat", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此處設置FILE_FLAG_OVERLAPPED NULL ); if (hDevice == INVALID_HANDLE_VALUE) { 使用完成函數來實現異步操作

異步函數是在異步操作完成時由操作系統調用的函數,所以我們可以在需要同步的地方等待一個同步對象,然后在異步函數中將這個對象設置為有信號。 使用異步函數必須使用帶有Ex的設備操作函數,像ReadFileEx,WriteFileEx等等,Ex系列的函數相比于不帶Ex的函數來說,多了最后一個參數,LPOVERLAPPED_COMPLETION_ROUTINE 類型的回調函數,這個函數的原型如下:

VOID CALLBACK FileIOCompletionRoutine( __in DWORD dwErrorCode, __in DWORD dwNumberOfBytesTransfered, __in LPOVERLAPPED lpOverlapped);

第一個參數是一個錯誤碼,如果異步操作出錯,那么他的錯誤碼可以由這個參數得到,第二個參數是實際操作的字節數對于Write類型的函數來說這個就是實際讀取的字節數,第三個是一個異步對象。在使用這個方式進行異步時Ex函數中的OVERLAPPED參數一般不需要為其設置事件句柄,只需傳入一個已經清空的OVERLAPPED類型的內存地址即可。 當我們設置了該函數后,操作系統會將這個函數插入到相應的隊列中,一旦完成這個異步操作,系統就會調用這個函數,Windows中將這種機制叫做異步過程調用(APC Asynchronous Produre Call);這種機制也不是一定會執行,一般只有程序進入警戒狀態時才會執行,想要程序進入警戒狀態需要調用帶有Ex的等待函數,包括SleepEx,在其中的bAlertable設置為TRUE那么當其進入等待狀態時就會調用APC隊列中的函數,需要注意的是所謂的APC就是系統借當前線程的線程環境來執行我們提供的回調函數,是用當前線程環境模擬了一個輕量級的線程,這個線程沒有自己的線程上下文,所以在回調函數中不要進行耗時的操作,否則一旦原始線程等到的它的執行條件而被喚醒,而APC例程還沒有被執行完成的話,就會造成一定的錯誤。下面是使用這種方式進行異步操作的例子:

VOID CALLBACK MyFileIOCompletionRoutine( DWORD dwErrorCode, // 對于此次操作返回的狀態 DWORD dwNumberOfBytesTransfered, // 告訴已經操作了多少字節,也就是在IRP里的Infomation LPOVERLAPPED lpOverlapped // 這個數據結構){ SetEvent(lpOverlapped->hEvent); printf("IO
Operation end!/n");}int main(){ HANDLE hDevice = CreateFile("test.dat", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//此處設置FILE_FLAG_OVERLAPPED NULL ); if (hDevice == INVALID_HANDLE_VALUE) { printf("Read Error/n"); return 1; } UCHAR buffer[BUFFER_SIZE]; //初始化overlap使其內部全部為零 //不用初始化事件!! OVERLAPPED overlap={0}; //這里沒有設置OVERLAP參數,因此是異步操作 overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); ReadFileEx(hDevice, buffer, BUFFER_SIZE,&overlap,MyFileIOCompletionRoutine); //做一些其他操作,這些操作會與讀設備并行執行 printf("在此處可以執行其他操作/n"); //進入alterable,只是為了有機會執行APC函數 SleepEx(1000, TRUE); //在此處進行同步,只有當讀操作完成才關閉句柄 WaitForSingleObject(overlap.hEvent, INFINITE); CloseHandle(hDevice); return 0;}

在最后SleepEx讓線程休眠而使其有機會執行APC例程,然后使用WaitForSingleObject來等待事件,我們在APC例程中將事件置為有信號,這樣只有當異步操作完成,才會返回,利用這個可以在關鍵的位置實現同步,在這里按理來說可以直接用WaitForSingleObjectEx來替換這兩個函數的調用,但是不知道為什么使用WaitForSingleObjectEx時,即使我沒有設置為有信號的狀態它也能正常返回,所以為了體現這點,我是使用了SleepEx和WaitForSingleObject兩個函數。

IRP中的同步和異步操作

上述的同步和異步操作必須得到內核的支持,其實所有對設備的操作最終都會轉化為IRP請求,并傳遞到相應的派遣函數中,在派遣函數中可以直接結束IRP,或者讓派遣函數返回,在以后的某個時候處理,由于應用層會等待派遣函數返回,所以直接結束IRP的方式可以看做是同步,而先返回以后處理的方式可以看做是異步處理。 在CreateFile中沒有異步的方式,所以它會一直等待派遣函數調用IoCompleteRequest結束,所以當調用CreateFile打開一個自己寫的設備時需要編寫一個用來處理IRP_MJ_CREATE的派遣函數,并且需要在函數中結束IRP,否則CreateFile會報錯,之前本人曾經犯過這樣的錯誤,沒有為設備對象準備IRP__MJ_CREATE的派遣函數,結果CreateFile直接返回-1. 對于ReadFile和WriteFile來說,它們支持異步操作,在調用這兩個函數進行同步操作時,內部會生成一個事件并等待這個事件,這個事件會和IRP一起發送的派遣函數中,當IRP被結束時,事件會被置為有信號,這樣函數中的等待就可以正常返回。而異步操作就不會產生這個事件。而是使用函數中的overlapped參數,這時它內部不會等待這個事件,而由程序員自己在合適的位置等待。 而調用帶有Ex的I/O函數則略有不同,他不會設置overlapped參數中的事件,而是當進入警告模式時調用提供的APC函數。 在派遣函數中可以調用IoCompleteRequest函數來結束IRP的處理或者調用IoMarkIrpPending來暫時掛起IRP,將IRP進行異步處理。該函數原型如下:

VOID IoMarkIrpPending( IN OUT PIRP Irp );

下面的例子演示了如何進行IRP的異步處理

typedef struct IRP_QUEUE_struct{ LIST_ENTRY IRPlist; PIRP pPendingIrp;}IRP_QUEUE, *LPIRP_QUEUE;NTSTATUS DefaultDispatch( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ){ NTSTATUS status; PIO_STACK_LOCATION pIrpStack; pIrpStack = IoGetCurrentIrpStackLocation(Irp); switch(pIrpStack->MajorFunction) { case IRP_MJ_READ: { PLIST_ENTRY pQueueHead; LPIRP_QUEUE pQueue; Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_PENDING; pQueue = (LPIRP_QUEUE)ExAllocatePoolWithTag(PagedPool, sizeof(IRP_QUEUE), TAG); if(pQueue != NULL) { pQueue->pPendingIrp = Irp; pQueueHead = (PLIST_ENTRY)(DeviceObject->DeviceExtension); InsertHeadList(pQueueHead, &(pQueue->IRPlist)); } IoMarkIrpPending(Irp); return STATUS_PENDING; } break; case IRP_MJ_CLEANUP: { PLIST_ENTRY pQueueHead; LPIRP_QUEUE pQueue; PLIST_ENTRY pDelete; pQueueHead = (PLIST_ENTRY)(DeviceObject->DeviceExtension); if(NULL != pQueueHead) { while(!IsListEmpty(pQueueHead)) { pDelete = RemoveHeadList(pQueueHead); pQueue = CONTAINING_RECORD(pDelete, IRP_QUEUE, IRPlist); IoCompleteRequest(pQueue->pPendingIrp, IO_NO_INCREMENT); ExFreePoolWithTag(pQueue, TAG); pQueue = NULL; } } } default: { Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } break; }}VOID DriverUnload(IN PDRIVER_OBJECT DriverObject){ UNICODE_STRING uDeviceName; UNICODE_STRING uSymbolickName; UNREFERENCED_PARAMETER(DriverObject); RtlInitUnicodeString(&uDeviceName, DEVICE_NAME); RtlInitUnicodeString(&uSymbolickName, SYMBOLIC_NAME); IoDeleteSymbolicLink(&uSymbolickName); IoDeleteDevice(DriverObject->DeviceObject); DbgPrint("GoodBye World/n");}NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath){ NTSTATUS status; int i = 0; PDEVICE_OBJECT pDeviceObject; UNREFERENCED_PARAMETER(RegistryPath); DriverObject->DriverUnload = DriverUnload; status = CreateDevice(DriverObject, &pDeviceObject); for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION + 1; i++) { DriverObject->MajorFunction[i] = DefaultDispatch; } DbgPrint("Hello world/n"); return status;}NTSTATUS CreateDevice(PDRIVER_OBJECT pDriverObject,PDEVICE_OBJECT *ppDeviceObject){ NTSTATUS status; PLIST_ENTRY pIrpQueue = NULL; UNICODE_STRING uDeviceName; UNICODE_STRING uSymbolickName; RtlInitUnicodeString(&uDeviceName, &DEVICE_NAME); RtlInitUnicodeString(&uSymbolickName, SYMBOLIC_NAME); if(NULL != ppDeviceObject) { status = IoCreateDevice(pDriverObject, sizeof(LIST_ENTRY), &uDeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, ppDeviceObject); if(!NT_SUCCESS(status)) { return status; } (*ppDeviceObject)->Flags |= DO_BUFFERED_IO; status = IoCreateSymbolicLink(&uSymbolickName, &uDeviceName); if(!NT_SUCCESS(status)) { IoDeleteDevice(*ppDeviceObject); *ppDeviceObject = NULL; return status; } pIrpQueue = (PLIST_ENTRY)((*ppDeviceObject)->DeviceExtension); InitializeListHead(pIrpQueue); return status; } return STATUS_UNSUCCESSFUL;}

在上述代碼中,定義一個鏈表用來保存未處理的IRP,然后在DriverEntry中創建一個設備對象,將鏈表頭指針放入到設備對象的擴展中,在驅動的IRP_MJ_READ請求中,將IRP保存到鏈表中,然后直接調用IoMarkIrpPending,將IRP掛起。一般的IRP_MJ_CLOSE用來關閉內核創建的內核對象,對應用層來說也就是句柄,而IRP_MJ_CLEANUP用來處理被掛起的IRP,所以在這我們需要對CLEANUP的IRP進行處理,在處理它時,我們從鏈表中依次取出IRP,調用IoCompleteRequest直接結束并清除這個節點。對于其他類型的IRP則直接結束掉即可。 在應用層,利用異步處理的方式多次調用ReadFile,最后再IrpTrace工具中可以看到,有多個顯示狀態位Pending的IRP。 在處理IRP時除了調用IoCompleteRequest結束之外還可以調用IoCancelIrp來取消IRP請求。這個函數原型如下:

BOOLEAN IoCancelIrp( IN PIRP Irp );

當調用這個函數取消相關的IRP時,對應的取消例程將會被執行,在DDK中可以使用函數IoSetCancelRoutine,該函數可以通過第二個參數為IRP設置一個取消例程,如果第二個參數為NULL,那么就將之前綁定到IRP上的取消例程給清除。函數原型如下:

PDRIVER_CANCEL IoSetCancelRoutine( IN PIRP Irp, IN PDRIVER_CANCEL CancelRoutine );

在調用IoCancelIrp函數時系統在內部會獲取一個名為cancel的自旋鎖,然后進行相關操作,但是自旋鎖的釋放需要自己來進行,一般在取消例程中進行釋放操作。這個自旋鎖可以通過函數IoAcquireCancelSpinLock來獲取,通過IoReleaseCancelSpinLock來釋放,下面是一個演示如何使用取消例程的例子。

//取消例程VOID CancelReadIrp( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ){ Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_CANCELLED; IoCompleteRequest(Irp, IO_NO_INCREMENT); IoReleaseCancelSpinLock(Irp->CancelIrql);}//IRP_MJ_READ 處理case IRP_MJ_READ:{ Irp->IoStatus.Information = 0; Irp->IoStatus.Status = STATUS_PENDING; IoSetCancelRoutine(Irp, CancelReadIrp); IoMarkIrpPending(Irp); return STATUS_PENDING;}

在R3層可以利用CancelIO,來使系統調用取消例程。 這個API傳入的是設備的句柄,當調用它時所有針對該設備的被掛起的IRP都會調用對應的取消例程,在這就不需要像上面那樣保存被掛起的IRP,每當有READ請求過來時都會調用case里面的內容,將該IRP和取消例程綁定,每當有IRP被取消時都會調用對應的取消例程,就不再需要自己維護了。另外在取消時,系統會自己獲取這個cancel自旋鎖,并提升對應的IRQL,IRP所處的IRQL被保存在IRP這個結構的CancelIrql成員中,而調用IoReleaseCancelSpinLock函數釋放自旋鎖時需要的參數正是這個IRP對應的IRQL,所以這里直接傳入Irp->CancelIrql


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 罗城| 隆化县| 天峨县| 清远市| 海淀区| 长治市| 渝北区| 海阳市| 乌兰浩特市| 德钦县| 汾西县| 姚安县| 万州区| 松潘县| 岫岩| 永州市| 武胜县| 新宁县| 简阳市| 浦城县| 吉木乃县| 三门峡市| 内丘县| 湘潭县| 敦化市| 临湘市| 大方县| 浠水县| 永平县| 扎鲁特旗| 宣城市| 陇南市| 崇阳县| 凤凰县| 富民县| 宁武县| 慈溪市| 衡阳市| 西安市| 麻栗坡县| 清苑县|