一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

驅(qū)動(dòng)編程學(xué)習(xí)筆記之IO處理(轉(zhuǎn))

 tuohuang0303 2011-03-29
驅(qū)動(dòng)編程學(xué)習(xí)筆記之IO處理(轉(zhuǎn))
關(guān)鍵詞i/o    IRP    IOCTL                                          

典型的i/o處理過(guò)程
=================

操作系統(tǒng)將所有的i/o請(qǐng)求都抽象成針對(duì)一個(gè)虛擬文件的操作,從而掩蓋了"一個(gè)i/o操作的目標(biāo)可能不是一個(gè)文件結(jié)構(gòu)的設(shè)備"這樣的事實(shí)。這一抽象也使得應(yīng)用程序?qū)ΥO(shè)備的接口變得泛化。

用戶(hù)模式api
   |
i/o系統(tǒng)服務(wù)api(Ntxxx)
   |
i/o管理器(Ioxxx)
   |
內(nèi)核模式
設(shè)備驅(qū)動(dòng)程序------驅(qū)動(dòng)程序支持例程(Io, Ex, Ke, Mm, Hal, FsRtl等等)
   |
HAL i/o訪問(wèn)例程
   |
i/o端口和寄存器


驅(qū)動(dòng)程序?qū)ο蠛驮O(shè)備對(duì)象
======================

驅(qū)動(dòng)程序?qū)ο螅╠river object)代表了系統(tǒng)中一個(gè)單獨(dú)的驅(qū)動(dòng)程序。i/o管理器從該驅(qū)動(dòng)程序?qū)ο笾蝎@得其每一個(gè)分發(fā)例程(入口點(diǎn))的地址;

設(shè)備對(duì)象(device object)代表了系統(tǒng)中一個(gè)物理的或邏輯的設(shè)備,并且描述了它的特征,比如它要求的緩沖區(qū)的對(duì)齊特性,以及它的設(shè)備隊(duì)列(用于存放進(jìn)來(lái)的irp)的位置。

當(dāng)一個(gè)驅(qū)動(dòng)程序被加載到一個(gè)系統(tǒng)中時(shí),i/o管理器創(chuàng)建一個(gè)驅(qū)動(dòng)程序?qū)ο螅缓笏{(diào)用該驅(qū)動(dòng)程序的初始化例程(DriverEntry),該例程會(huì)利用該驅(qū)動(dòng)程序的入口點(diǎn)來(lái)填充此對(duì)象的屬性。

設(shè)備對(duì)象指回到它的驅(qū)動(dòng)程序?qū)ο笊?,這正是當(dāng)i/o管理器接收到一個(gè)i/o請(qǐng)求時(shí),它如何知道該調(diào)用哪一個(gè)驅(qū)動(dòng)例程的原因。它利用該設(shè)備對(duì)象來(lái)找到一個(gè)驅(qū)動(dòng)程序?qū)ο?,此?duì)象代表了負(fù)責(zé)為該設(shè)備提供服務(wù)的驅(qū)動(dòng)程序。然后,它利用原始請(qǐng)求中所提供的功能代碼,索引到該驅(qū)動(dòng)程序?qū)ο笾?;每個(gè)功能代碼對(duì)應(yīng)于驅(qū)動(dòng)程序的一個(gè)入口點(diǎn)。

驅(qū)動(dòng)程序?qū)ο笸ǔS卸鄠€(gè)與之關(guān)聯(lián)的設(shè)備對(duì)象。這些設(shè)備對(duì)象的列表代表了該驅(qū)動(dòng)程序所控制的物理和邏輯設(shè)備。例如,一個(gè)硬盤(pán)的每個(gè)分區(qū)都有一個(gè)單獨(dú)的設(shè)備對(duì)象,該設(shè)備對(duì)象包含了與此分區(qū)相關(guān)的信息。然而,同樣的硬盤(pán)驅(qū)動(dòng)程序被用于訪問(wèn)所有這些分區(qū)。當(dāng)一個(gè)驅(qū)動(dòng)程序被從系統(tǒng)卸載時(shí),i/o管理器使用設(shè)備對(duì)象的隊(duì)列來(lái)決定哪些設(shè)備將會(huì)受到"該驅(qū)動(dòng)程序被移除"的影響。

 

IRP結(jié)構(gòu)
=======

MdlAddress (PMDL)域指向一個(gè)內(nèi)存描述符表(MDL),該表描述了一個(gè)與該請(qǐng)求關(guān)聯(lián)的用戶(hù)模式緩沖區(qū)。如果頂級(jí)設(shè)備對(duì)象的Flags域?yàn)?DO_DIRECT_IO,則I/O管理器為IRP_MJ_READ或IRP_MJ_WRITE請(qǐng)求創(chuàng)建這個(gè)MDL。如果一個(gè) IRP_MJ_DEVICE_CONTROL請(qǐng)求的控制代碼指定METHOD_IN_DIRECT或METHOD_OUT_DIRECT操作方式,則 I/O管理器為該請(qǐng)求使用的輸出緩沖區(qū)創(chuàng)建一個(gè)MDL。MDL本身用于描述用戶(hù)模式虛擬緩沖區(qū),但它同時(shí)也含有該緩沖區(qū)鎖定內(nèi)存頁(yè)的物理地址。為了訪問(wèn)用戶(hù)模式緩沖區(qū),驅(qū)動(dòng)程序必須做一點(diǎn)額外工作。

Flags(ULONG)域包含一些對(duì)驅(qū)動(dòng)程序只讀的標(biāo)志。但這些標(biāo)志與WDM驅(qū)動(dòng)程序無(wú)關(guān)。

AssociatedIrp (union)域是一個(gè)三指針聯(lián)合。其中,與WDM驅(qū)動(dòng)程序相關(guān)的指針是AssociatedIrp.SystemBuffer。 SystemBuffer指針指向一個(gè)數(shù)據(jù)緩沖區(qū),該緩沖區(qū)位于內(nèi)核模式的非分頁(yè)內(nèi)存中。對(duì)于IRP_MJ_READ和IRP_MJ_WRITE操作,如果頂級(jí)設(shè)備指定DO_BUFFERED_IO標(biāo)志,則I/O管理器就創(chuàng)建這個(gè)數(shù)據(jù)緩沖區(qū)。對(duì)于IRP_MJ_DEVICE_CONTROL操作,如果 I/O控制功能代碼指出需要緩沖區(qū)(見(jiàn)第九章),則I/O管理器就創(chuàng)建這個(gè)數(shù)據(jù)緩沖區(qū)。I/O管理器把用戶(hù)模式程序發(fā)送給驅(qū)動(dòng)程序的數(shù)據(jù)復(fù)制到這個(gè)緩沖區(qū),這也是創(chuàng)建IRP過(guò)程的一部分。這些數(shù)據(jù)可以是與WriteFile調(diào)用有關(guān)的數(shù)據(jù),或者是DeviceIoControl調(diào)用中所謂的輸入數(shù)據(jù)。對(duì)于讀請(qǐng)求,設(shè)備驅(qū)動(dòng)程序把讀出的數(shù)據(jù)填到這個(gè)緩沖區(qū),然后I/O管理器再把緩沖區(qū)的內(nèi)容復(fù)制到用戶(hù)模式緩沖區(qū)。對(duì)于指定了METHOD_BUFFERED 的I/O控制操作,驅(qū)動(dòng)程序把所謂的輸出數(shù)據(jù)放到這個(gè)緩沖區(qū),然后I/O管理器再把數(shù)據(jù)復(fù)制到用戶(hù)模式的輸出緩沖區(qū)。

IoStatus(IO_STATUS_BLOCK) 是一個(gè)僅包含兩個(gè)域的結(jié)構(gòu),驅(qū)動(dòng)程序在最終完成請(qǐng)求時(shí)設(shè)置這個(gè)結(jié)構(gòu)。IoStatus.Status域?qū)⑹盏揭粋€(gè)NTSTATUS代碼,而 IoStatus.Information的類(lèi)型為ULONG_PTR,它將收到一個(gè)信息值,該信息值的確切含義要取決于具體的IRP類(lèi)型和請(qǐng)求完成的狀態(tài)。Information域的一個(gè)公認(rèn)用法是用于保存數(shù)據(jù)傳輸操作,如IRP_MJ_READ,的流量總計(jì)。某些PnP請(qǐng)求把這個(gè)域作為指向另外一個(gè)結(jié)構(gòu)的指針,這個(gè)結(jié)構(gòu)通常包含查詢(xún)請(qǐng)求的結(jié)果。

RequestorMode將等于一個(gè)枚舉常量UserMode或KernelMode,指定原始I/O請(qǐng)求的來(lái)源。驅(qū)動(dòng)程序有時(shí)需要查看這個(gè)值來(lái)決定是否要信任某些參數(shù)。

PendingReturned(BOOLEAN)如果為T(mén)RUE,則表明處理該IRP的最低級(jí)派遣例程返回了STATUS_PENDING。完成例程通過(guò)參考該域來(lái)避免自己與派遣例程間的潛在競(jìng)爭(zhēng)。

Cancel(BOOLEAN)如果為T(mén)RUE,則表明IoCancelIrp已被調(diào)用,該函數(shù)用于取消這個(gè)請(qǐng)求。如果為FALSE,則表明沒(méi)有調(diào)用IoCancelIrp函數(shù)。取消IRP是一個(gè)相對(duì)復(fù)雜的主題,我將在本章的最后詳細(xì)描述它。

CancelIrql(KIRQL)是一個(gè)IRQL值,表明那個(gè)專(zhuān)用的取消自旋鎖是在這個(gè)IRQL上獲取的。當(dāng)你在取消例程中釋放自旋鎖時(shí)應(yīng)參考這個(gè)域。

CancelRoutine(PDRIVER_CANCEL)是驅(qū)動(dòng)程序取消例程的地址。你應(yīng)該使用IoSetCancelRoutine函數(shù)設(shè)置這個(gè)域而不是直接修改該域。

UserBuffer(PVOID) 對(duì)于METHOD_NEITHER方式的IRP_MJ_DEVICE_CONTROL請(qǐng)求,該域包含輸出緩沖區(qū)的用戶(hù)模式虛擬地址。該域還用于保存讀寫(xiě)請(qǐng)求緩沖區(qū)的用戶(hù)模式虛擬地址,但指定了DO_BUFFERED_IO或DO_DIRECT_IO標(biāo)志的驅(qū)動(dòng)程序,其讀寫(xiě)例程通常不需要訪問(wèn)這個(gè)域。當(dāng)處理一個(gè)METHOD_NEITHER控制操作時(shí),驅(qū)動(dòng)程序能用這個(gè)地址創(chuàng)建自己的MDL。

Tail.Overlay是Tail聯(lián)合中的一種結(jié)構(gòu),它含有幾個(gè)對(duì)WDM驅(qū)動(dòng)程序有潛在用途的成員。圖5-2是Tail聯(lián)合的組成圖。在這個(gè)圖中,以水平方向從左到右是這個(gè)聯(lián)合的三個(gè)可選成員,在垂直方向是每個(gè)結(jié)構(gòu)的成員描述。Tail.Overlay.DeviceQueueEntry(KDEVICE_QUEUE_ENTRY)和 Tail.Overlay.DriverContext(PVOID[4])是Tail.Overlayare內(nèi)一個(gè)未命名聯(lián)合的兩個(gè)可選成員(只能出現(xiàn)一個(gè))。I/O管理器把DeviceQueueEntry作為設(shè)備標(biāo)準(zhǔn)請(qǐng)求隊(duì)列中的連接域。當(dāng)IRP還沒(méi)有進(jìn)入某個(gè)隊(duì)列時(shí),如果你擁有這個(gè)IRP你可以使用這個(gè)域,你可以任意使用DriverContext中的四個(gè)指針。Tail.Overlay.ListEntry(LIST_ENTRY)僅能作為你自己實(shí)現(xiàn)的私有隊(duì)列的連接域。

CurrentLocation (CHAR)和Tail.Overlay.CurrentStackLocation(PIO_STACK_LOCATION)沒(méi)有公開(kāi)為驅(qū)動(dòng)程序使用,因?yàn)槟阃耆梢允褂孟驣oGetCurrentIrpStackLocation這樣的函數(shù)獲取這些信息。但意識(shí)到CurrentLocation就是當(dāng)前I/O堆棧單元的索引以及CurrentStackLocation就是指向它的指針,會(huì)對(duì)驅(qū)動(dòng)程序調(diào)試有一些幫助。

&nbp;


I/O堆棧
=======


irp是由兩部分組成的:一個(gè)固定的頭以及一個(gè)或者多個(gè)棧單元。固定部分包含一些諸如以下的信息:該請(qǐng)求的類(lèi)型和大小,該請(qǐng)求是同步的還是異步的,一個(gè)用于緩沖類(lèi)型i/o的緩沖區(qū)指針,以及一些該請(qǐng)求處理過(guò)程中會(huì)被改變的狀態(tài)信息。

irp的棧單元包含一個(gè)功能代碼(由一個(gè)主碼和一個(gè)次碼組成)與功能相關(guān)的參數(shù),以及一個(gè)指向調(diào)用者文件對(duì)象的指針。

MajorFunction (UCHAR)是該IRP的主功能碼。這個(gè)代碼應(yīng)該為類(lèi)似IRP_MJ_READ一樣的值,并與驅(qū)動(dòng)程序?qū)ο笾蠱ajorFunction表的某個(gè)派遣函數(shù)指針相對(duì)應(yīng)。如果該代碼存在于某個(gè)特殊驅(qū)動(dòng)程序的I/O堆棧單元中,它有可能一開(kāi)始是,例如IRP_MJ_READ,而后被驅(qū)動(dòng)程序轉(zhuǎn)換成其它代碼,并沿著驅(qū)動(dòng)程序堆棧發(fā)送到低層驅(qū)動(dòng)程序。我將在第十一章(USB總線)中舉一個(gè)這樣的例子,USB驅(qū)動(dòng)程序把標(biāo)準(zhǔn)的讀或?qū)懻?qǐng)求轉(zhuǎn)換成內(nèi)部控制操作,以便向 USB總線驅(qū)動(dòng)程序提交請(qǐng)求。

MinorFunction(UCHAR)是該IRP的副功能碼。它進(jìn)一步指出該IRP屬于哪個(gè)主功能類(lèi)。例如,IRP_MJ_PNP請(qǐng)求就有約一打的副功能碼,如IRP_MN_START_DEVICE、IRP_MN_REMOVE_DEVICE,等等。

Parameters (union)是幾個(gè)子結(jié)構(gòu)的聯(lián)合,每個(gè)請(qǐng)求類(lèi)型都有自己專(zhuān)用的參數(shù),而每個(gè)子結(jié)構(gòu)就是一種參數(shù)。這些子結(jié)構(gòu)包括Create (IRP_MJ_CREATE請(qǐng)求)、Read(IRP_MJ_READ請(qǐng)求)、StartDevice(IRP_MJ_PNP的 IRP_MN_START_DEVICE子類(lèi)型),等等。

DeviceObject(PDEVICE_OBJECT)是與該堆棧單元對(duì)應(yīng)的設(shè)備對(duì)象的地址。該域由IoCallDriver函數(shù)負(fù)責(zé)填寫(xiě)。

FileObject(PFILE_OBJECT)是內(nèi)核文件對(duì)象的地址,IRP的目標(biāo)就是這個(gè)文件對(duì)象。驅(qū)動(dòng)程序通常在處理清除請(qǐng)求(IRP_MJ_CLEANUP)時(shí)使用FileObject指針,以區(qū)分隊(duì)列中與該文件對(duì)象無(wú)關(guān)的IRP。

CompletionRoutine (PIO_COMPLETION_ROUTINE)是一個(gè)I/O完成例程的地址,該地址是由與這個(gè)堆棧單元對(duì)應(yīng)的驅(qū)動(dòng)程序的更上一層驅(qū)動(dòng)程序設(shè)置的。你絕對(duì)不要直接設(shè)置這個(gè)域,應(yīng)該調(diào)用IoSetCompletionRoutine函數(shù),該函數(shù)知道如何參考下一層驅(qū)動(dòng)程序的堆棧單元。設(shè)備堆棧的最低一級(jí)驅(qū)動(dòng)程序并不需要完成例程,因?yàn)樗鼈儽仨氈苯油瓿烧?qǐng)求。然而,請(qǐng)求的發(fā)起者有時(shí)確實(shí)需要一個(gè)完成例程,但通常沒(méi)有自己的堆棧單元。這就是為什么每一級(jí)驅(qū)動(dòng)程序都使用下一級(jí)驅(qū)動(dòng)程序的堆棧單元保存自己完成例程指針的原因。

Context(PVOID)是一個(gè)任意的與上下文相關(guān)的值,將作為參數(shù)傳遞給完成例程。你絕對(duì)不要直接設(shè)置該域;它由IoSetCompletionRoutine函數(shù)自動(dòng)設(shè)置,其值來(lái)自該函數(shù)的某個(gè)參數(shù)。

 

I/O請(qǐng)求派遣機(jī)制
===============

Win2000的I/O請(qǐng)求是包驅(qū)動(dòng)的,當(dāng)一個(gè)I/O請(qǐng)求開(kāi)始,I/O管理器先創(chuàng)建一個(gè)IRP去跟蹤這個(gè)請(qǐng)求,另外,它存儲(chǔ)一個(gè)功能代碼在IRP的I/O堆棧區(qū)的MajorField域中來(lái)唯一的標(biāo)識(shí)請(qǐng)求的類(lèi)型。

MajorField 域是被I/O管理器用來(lái)索引驅(qū)動(dòng)程序?qū)ο蟮腗ajorFunction表,這個(gè)表包含一個(gè)指向一個(gè)特殊I/O請(qǐng)求的派遣例程的功能指針,如果驅(qū)動(dòng)程序不支持這個(gè)請(qǐng)求,MajorFunction表就會(huì)指向I/O管理器函數(shù)_IopInvalidDeviceRequest,該函數(shù)返回一個(gè)錯(cuò)誤給原始的調(diào)用者。驅(qū)動(dòng)程序的作者有責(zé)任提供所有的驅(qū)動(dòng)程序支持的派遣例程。


啟用特殊的功能代碼
==================

如果想要啟用特殊的功能代碼,驅(qū)動(dòng)程序必須聲明一個(gè)響應(yīng)這個(gè)請(qǐng)求的派遣例程,聲明機(jī)制是很簡(jiǎn)單的,只需要在DriverEntry例程中存儲(chǔ)派遣例程函數(shù)的地址到驅(qū)動(dòng)程序?qū)ο蟮腗ajorFunction表的適當(dāng)?shù)奈恢?,I/O功能代碼是這個(gè)表的索引。如下列代碼片段所示:

NTSTATUS DriverEntry (IN PDRIVER_OBJECT pDO, IN PUNICODE_STRING pRegPath)
{        :

    pDO->MajorFunction[ IRP_MJ_CREATE ]  = DispCreate;
    pDO->MajorFunction[ IRP_MJ_CLOSE ]   = DispClose;
    pDO->MajorFunction[ IRP_MJ_CLEANUP ] = DispCleanup;
    pDO->MajorFunction[ IRP_MJ_READ ]    = DispRead;
    pDO->MajorFunction[ IRP_MJ_WRITE ]   = DispWrite;

        :
    return STATUS_SUCCESS; 
}

注意,每一個(gè)I/O功能代碼(表的索引)是被一個(gè)IRP_MJ_XXX形式的符號(hào)所標(biāo)識(shí),它們被NTDDK.h和WDM.h文件定義,這些符號(hào)常量總是被用在固定的地方。

聲明方法也允許一個(gè)單一的例程被用來(lái)處理多個(gè)請(qǐng)求類(lèi)型。DriverEntry可以放置公共的派遣例程,因?yàn)镮RP命令包含請(qǐng)求代碼,公共的函數(shù)可以被作為合適的例程而被調(diào)用。

最后,驅(qū)動(dòng)程序不支持的例程應(yīng)該被DriverEntry例程忽略,I/O管理器在調(diào)用DriverEntry例程之前就已經(jīng)將整個(gè)的MajorFunction表填充為_(kāi)IopInvalidDeviceRequest。


決定需要支持那些功能代碼
========================

所有的驅(qū)動(dòng)程序必須支持IRP_MJ_CREATE功能代碼,因?yàn)檫@個(gè)功能代碼是用來(lái)響應(yīng)Win32用戶(hù)模式的CreateFile調(diào)用,如果不支持這功能代碼,Win32程序就沒(méi)有辦法獲得設(shè)備的句柄,類(lèi)似的,驅(qū)動(dòng)程序必須支持IRP_MJ_CLOSE功能代碼,因?yàn)樗脕?lái)響應(yīng)Win32用戶(hù)模式的 CloseHandle調(diào)用。順便提一下,系統(tǒng)自動(dòng)調(diào)用CloseHandle函數(shù),因?yàn)樵诔绦蛲顺龅臅r(shí)候,所有的句柄都沒(méi)有被關(guān)閉。

其它的功能代碼是否支持依賴(lài)它控制的設(shè)備的性質(zhì),當(dāng)編寫(xiě)分層的驅(qū)動(dòng)程序的時(shí)候,高層的驅(qū)動(dòng)程序必須支持連接所有的低層驅(qū)動(dòng)程序的例程,因?yàn)橛脩?hù)請(qǐng)求先通過(guò)高層的驅(qū)動(dòng)程序。


IRP功能代碼                           意義                           調(diào)用者
----------------------------------------------------------------------------------------
IRP_MJ_CREATE                    請(qǐng)求一個(gè)句柄                      CreateFile
IRP_MJ_CLEANUP                   在關(guān)閉句柄時(shí)取消懸掛的IRP         CloseHandle 
IRP_MJ_CLOSE                     關(guān)閉句柄                          CloseHandle
IRP_MJ_READ                      從設(shè)備得到數(shù)據(jù)                    ReadFile
IRP_MJ_WRITE                     傳送數(shù)據(jù)到設(shè)備                    WriteFile
IRP_MJ_DEVICE_CONTROL            控制操作                          DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL   控制操作(只能被內(nèi)核調(diào)用)          沒(méi)有Win32調(diào)用
IRP_MJ_QUERY_INFORMATION         得到文件的長(zhǎng)度                    GetFileSize
IRP_MJ_SET_INFORMATION           設(shè)置文件的長(zhǎng)度                    SetFileSize
IRP_MJ_FLUSH_BUFFERS             寫(xiě)輸出緩沖區(qū)或者丟棄輸入緩沖區(qū)    FlushFileBuffers
                                                                   FlushConsoleInputBuffer                                            &nbp;                                          PurgeComm
IRP_MJ_SHUTDOWN                  系統(tǒng)關(guān)閉                          InitiateSystemShutdown
-------------------------------------------------------------------------------------------
                表7.1常用的功能調(diào)用

 


擴(kuò)展派遣例程
============

大部分的I/O管理器的操作支持一個(gè)標(biāo)準(zhǔn)的讀寫(xiě)提取,請(qǐng)求者提供一個(gè)緩沖區(qū)和緩沖區(qū)的長(zhǎng)度和傳送到或者從設(shè)備的數(shù)據(jù)。不是所有的操作都適合這個(gè)抽象。例如,磁盤(pán)的格式化或者重新分區(qū)操作不適合一般的讀或者寫(xiě)操作。這種請(qǐng)求使用擴(kuò)展的I/O請(qǐng)求代碼處理,這些代碼允許任何數(shù)量的驅(qū)動(dòng)程序指定的操作,不被讀寫(xiě)抽象所約束。

1. IRP_MJ_DEVICE_CONTROL允許擴(kuò)展的I/O請(qǐng)求,使用用戶(hù)模式的DeviceIoControl函數(shù)來(lái)調(diào)用,I/O管理器創(chuàng)建一個(gè) IRP,這個(gè)IRP的MajorFunction和IoControlCode是被DeviceIoControl函數(shù)指定其內(nèi)容。

2.  IRP_MJ_INTERNAL_DEVICE_CONTROL允許內(nèi)核模式的請(qǐng)求,用戶(hù)模式?jīng)]有權(quán)力使用這個(gè)操作,它主要是用于在一個(gè)分層的堆棧中其它驅(qū)動(dòng)程序傳遞特殊的請(qǐng)求。這兩個(gè)版本的設(shè)備操作的其它的地方是相同的。請(qǐng)求者放置了一個(gè)IoControlCode值到IRP。

結(jié)果,這兩個(gè)中的任何一個(gè)的執(zhí)行都需要一個(gè)于IRP的IoControlCode值有關(guān)的二級(jí)派遣例程。這個(gè)值叫做設(shè)備I/O控制代碼(IOCTL)。因?yàn)槎?jí)派遣例程機(jī)制完全包含驅(qū)動(dòng)程序的私有例程。IOCTL值的意義是由驅(qū)動(dòng)程序指定的。下面詳細(xì)介紹設(shè)備控制接口。


定義私有的IOCTL
===============

傳遞給驅(qū)動(dòng)程序的IOCTL遵循一個(gè)特殊的結(jié)構(gòu),它有32-bit大小,DDK包含一個(gè)方便的產(chǎn)生IOCTL值的機(jī)制的宏,CTL_CODE。

  31-16    |     15-14      |     13-2    |     1-0
DeviceType | RequiredAccess | ControlCode | TransferType


管理IOCTL緩沖區(qū)
===============

IOCTL請(qǐng)求可以在同一個(gè)調(diào)用中指定同時(shí)指定一個(gè)輸入緩沖區(qū)和一個(gè)輸出緩沖區(qū)。這樣,提供了一個(gè)寫(xiě)之后再讀的抽象給調(diào)用者。有兩個(gè)訪問(wèn)用戶(hù)緩沖區(qū)的方法。

緩沖區(qū)傳輸機(jī)制是用IOCTL的ControlCode定義,獨(dú)立與整個(gè)的設(shè)備對(duì)象策略之外。
有兩個(gè)相關(guān)的緩沖區(qū),一個(gè)是輸入緩沖區(qū),一個(gè)是輸出緩沖區(qū)。
以下的部分描述不同的緩沖區(qū)策略


METHOD_BUFFERED
===============

I/O 管理器從非分頁(yè)的緩沖池分配一個(gè)單一的臨時(shí)緩沖區(qū),它足夠容納調(diào)用者的輸入或者輸出緩沖區(qū)中的數(shù)據(jù),這個(gè)緩沖池的地址放在IRP的 AssociatedIrp.SystemBuffer域。然后復(fù)制請(qǐng)求者的出入緩沖區(qū)到緩沖池,再設(shè)置IRP的UserBuffer域?yàn)橛脩?hù)空間輸出緩沖區(qū)的地址。

完成IOCTL請(qǐng)求之后,I/O管理器復(fù)制系統(tǒng)緩沖池中的數(shù)據(jù)到請(qǐng)求者的用戶(hù)空間緩沖區(qū),注意只有一個(gè)內(nèi)部的緩沖池提供給驅(qū)動(dòng)程序代碼,甚至用戶(hù)指定了獨(dú)立的輸入和輸出緩沖區(qū)。驅(qū)動(dòng)程序代碼必須在向緩沖池中寫(xiě)數(shù)據(jù)之前將收到的數(shù)據(jù)全部從緩沖池中讀出。

METHOD_IN_DIRECT
================

I/O管理器檢查請(qǐng)求者的輸入緩沖區(qū),然后將它鎖定到物理存儲(chǔ)空間中,為輸入緩沖區(qū)創(chuàng)建一個(gè)MDL,存儲(chǔ)指向MDL的指針到IRP的MdlAddress域。

也從非分頁(yè)的緩沖池中分配一個(gè)臨時(shí)的輸出緩沖區(qū),存儲(chǔ)這個(gè)緩沖區(qū)的地址到IRP的AssociatedIrp.SystemBuffer域。IRP的 UserBuffer域設(shè)置為調(diào)用者的輸出緩沖區(qū)地址。當(dāng)IOCTL IRP完成,系統(tǒng)緩沖區(qū)中的內(nèi)容被復(fù)制到調(diào)用者原始的輸出緩沖區(qū)。

METHOD_OUT_DIRECT
=================

I/O管理器檢查請(qǐng)求者的輸出緩沖區(qū),然后將它鎖定到物理存儲(chǔ)空間中,為輸出緩沖區(qū)創(chuàng)建一個(gè)MDL,存儲(chǔ)指向MDL的指針到IRP的MdlAddress域。

也從非分頁(yè)的緩沖池中分配一個(gè)臨時(shí)的輸入緩沖區(qū),存儲(chǔ)這個(gè)緩沖區(qū)的地址到IRP的AssociatedIrp.SystemBuffer域。調(diào)用者原始的輸入緩沖區(qū)的內(nèi)容被復(fù)制到系統(tǒng)緩沖區(qū),設(shè)置IRP的UserBuffer域?yàn)镹ULL。

METHOD_NEITHER
==============

I/O管理器放置調(diào)用者輸入緩沖區(qū)的地址到IRP的當(dāng)前I/O堆棧單元的Parameters.DeviceIoControl.Type3InputBuffer域,存儲(chǔ)輸出緩沖區(qū)的地址到IRP的UserBuffer域中,兩個(gè)都是用戶(hù)空間地址。


IOCTL參數(shù)傳遞方法
=================

擴(kuò)展的功能用IOCTL值來(lái)定義,它存儲(chǔ)在驅(qū)動(dòng)程序常常需要的輸入或者輸出的緩沖區(qū)中,驅(qū)動(dòng)程序常使用IOCTL值來(lái)匯報(bào)執(zhí)行的數(shù)據(jù),這個(gè)數(shù)據(jù)通過(guò)用戶(hù)提供的緩沖區(qū)傳輸。確實(shí),DeviceIoControl函數(shù)為兩個(gè)緩沖區(qū)定義參數(shù),一個(gè)用來(lái)輸入,另一個(gè)用來(lái)輸出。I/O管理器提供的緩沖區(qū)傳輸機(jī)制在 IOCTOL值中定義著,可以是緩沖區(qū)I/O或者是直接I/O。像以前介紹的,對(duì)于緩沖區(qū)I/O,I/O管理器復(fù)制用戶(hù)緩沖區(qū)到(假如是寫(xiě)操作)系統(tǒng)中非分頁(yè)的緩沖池,這時(shí)驅(qū)動(dòng)程序代碼才可以方便的操作。對(duì)于直接I/O驅(qū)動(dòng)程序直接訪問(wèn)用戶(hù)緩沖區(qū)。

有趣的是,驅(qū)動(dòng)程序整個(gè)的緩沖區(qū)處理策略(在DriverEntry例程中定義)對(duì)于IOCTL傳輸不是強(qiáng)制的,也就是說(shuō),可以使用也可以不使用。確實(shí),I/O管理器使用IOCTL結(jié)構(gòu)的一個(gè)域來(lái)確定傳輸方法,這樣每個(gè)IOCTL有不同的傳輸方法。這給執(zhí)行DeviceIoControl操作最大的靈活性。


CTL_CODE宏參數(shù)                      意義
-------------------------------------------------------------------
DeviceType                  指定給IoCreateDevice的FILE_DEVICE_XXX值
                            0x0000到0x7FFF-保留給Microsoft
                            0x8000到0xFFFF-用戶(hù)定義
--------------------------------------------------------------------
ControlCode                 驅(qū)動(dòng)過(guò)程定義的IOCTL代碼
                            0x000 to 0x7FF -保留給Microsoft
                            0x800 to 0xFFF -用戶(hù)定義
--------------------------------------------------------------------
TransferType                這個(gè)控制代碼的緩沖區(qū)傳輸機(jī)制
                            ETHOD_BUFFERED,ETHOD_IN_DIRECT,
                            ETHOD_OUT_DIRECT,ETHOD_NEITHER
--------------------------------------------------------------------
RequiredAccess              調(diào)用CreateFile的訪問(wèn)條件
                            ILE_ANY_ACCESS,
                            ILE_READ_DATA, 
                            ILE_WRITE_DATA,
                            ILE_READ_DATA | FILE_WRITE_DATA
---------------------------------------------------------------------
                表7.3 CTL_CODE宏參數(shù)

IOCTL的TransferType域有two-bits寬,它定義了下面的其中一個(gè):

METHOD_BUFFERED. I/O管理器使用一個(gè)中介的非分頁(yè)的緩沖池在用戶(hù)緩沖區(qū)和驅(qū)動(dòng)程序間交換數(shù)據(jù)。 
METHOD_IN_DIRECT. I/O管理器提供一個(gè)繞用戶(hù)緩沖區(qū)的頁(yè)表。驅(qū)動(dòng)程序使用著個(gè)列表提供從設(shè)備
                  到用戶(hù)空間(這是讀操作)直接的I/O。
 
METHOD_OUT_DIRECT. I/O管理器提供一個(gè)環(huán)繞用戶(hù)緩沖區(qū)的頁(yè)表。驅(qū)動(dòng)程序使用著個(gè)列表提供從用
                    戶(hù)空間到設(shè)備(這是寫(xiě)操作)直接的I/O。 

METHOD_NEITHER.  I/O管理器不援助緩沖區(qū)傳輸,給驅(qū)動(dòng)程序的是用戶(hù)的原始的緩沖區(qū)地址(大概來(lái)
                  自分頁(yè)的存儲(chǔ)空間)。
 
因?yàn)門(mén)ransferType域是IOCTL代碼的一部分,Microsoft定義了一些指定I/O緩沖機(jī)制的公共的IOCTL代碼。可以定義任何適當(dāng)?shù)膫鬏敊C(jī)制(私有的驅(qū)動(dòng)過(guò)程定義的IOCTL代碼)。緩沖區(qū)I/O適用于小的,慢的數(shù)據(jù)傳輸。直接I/O適用于快的,大量的數(shù)據(jù)傳輸。

 

處理IOCTL請(qǐng)求
=============

一旦驅(qū)動(dòng)程序聲明了IRP_MJ_DEVICE_CONTROL或者IRP_MJ_INTERNAL_DEVICE_CONTROL功能代碼的派遣例程,嚴(yán)格的解釋IOCTL設(shè)備控制代碼是驅(qū)動(dòng)程序的職責(zé)。I/O管理器不檢驗(yàn)IOCTL代碼的各個(gè)域,請(qǐng)求者傳遞的任何隨機(jī)數(shù)都可以傳遞到驅(qū)動(dòng)程序,所以驅(qū)動(dòng)程序必須作檢驗(yàn)的工作。

因此,設(shè)備控制派遣例程的典型的結(jié)構(gòu)是一個(gè)大的switch語(yǔ)句。如以下的例子:

NTSTATUS DispatchIoControl (IN PDEVICE_OBJECT pDO, IN PIRP pIrp)
{
    NTSTATUS status = STATUS_SUCCESS;

    PDEVICE_EXTENSION pDE;
    PVOID userBuffer;
    ULONG inSize;   
    ULONG outSize;
    ULONG controlCode;                 // IOCTL請(qǐng)求代碼
   PIO_STACK_LOCATION pIrpStack;      //堆棧區(qū)域存儲(chǔ)了用戶(hù)緩沖區(qū)信息

   pIrpStack = IoGetCurrentIrpStackLocation (pIrp);

    // 取出IOCTL請(qǐng)求代碼
    controlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;

    // 得到請(qǐng)求緩沖區(qū)大小
    inSize  = pIrpStack-> Parameters.DeviceIoControl.InputBufferLength;
    OutSize = pIrpStack-> Parameters.DeivceIoControl.OutputBufferLength;

    //現(xiàn)在執(zhí)行二次派遣  
    switch (controlCode)
    {
    case IOCTL_MISSLEDEVICEAIM:
        // 檢驗(yàn)參數(shù)
        if (inSize < sizeof(AIM_IN_BUFFER) ||
           (outSize < sizeof(AIM_OUT_BUFFER) )
        {
            status = STATUS_INVALID_BUFFER_SIZE;
            break;
        }

        IoMarkIrpPending (pIrp);              // 有效的IRP值- 準(zhǔn)備設(shè)備
        IoStartPacket (pDO, pIrp, 0, NULL);   // calls the driver's StartIo routine
        return STATUS_PENDING;

    case IOCTL_DEVICE_LAUNCH:
         if (inSize > 0 || outSize > 0)
         {
             // Is it really an error to pass buffers
             // to a function that doesn't use them? Maybe not, but the caller is
             //now forced to re-think the purpose of the call.
             status = STATUS_INVALID_PARAMETER;            
             break;
         }

         // Same kind of processing start the device
         return STATUS_PENDING; 

    default:    // 驅(qū)動(dòng)程序收到了未被承認(rèn)的控制代碼

        status = STATUS_INVALID_DEVICE_REQUEST;  
        break;
    }

    // Valid control code cases returned above. Execution here means an error
    // occurred. Fail the IRP request...pIrp->IoStatus.Status = status;

    pIrp->IoStatus.Information = 0;  // 數(shù)據(jù)沒(méi)有傳輸
    IoCompleteRequest (pIrp, IO_NO_INCREMENT) ;     
    return status;
}

 

編寫(xiě)IOCTL頭文件
===============

因?yàn)轵?qū)動(dòng)程序工程和所有的驅(qū)動(dòng)程序的客戶(hù)需要IOCTL代碼的符號(hào)定義,通常,驅(qū)動(dòng)程序作者提供一個(gè)單獨(dú)的驅(qū)動(dòng)過(guò)程控制代碼定義的頭文件。這個(gè)頭文件應(yīng)該包含描述特殊控制操作的緩沖區(qū)內(nèi)容和所有的結(jié)構(gòu)定義。一個(gè)WIN32程序需要在包含驅(qū)動(dòng)程序的IOCTL頭文件之前包含WINIOCTL.h文件,驅(qū)動(dòng)程序工程需要在包含驅(qū)動(dòng)程序指定的IOCTL頭文件之前包含DEVIOCTL.h文件。這些文件中定義了CTL_CODE宏,下前是一個(gè)IOCTL頭文件的例子:

#define IOCTL_MISSLEDEVICE_AIM CTL_CODE(  \
              FILE_DEVICE_UNKNOWN, 0x801, \
              METHOD_BUFFERED, FILE_ACCESS_ANY )

                              
// IOCTL_MISSLEDEVICE_AIM使用的結(jié)構(gòu)
typedef struct _AIM_IN_BUFFER
{
    ULONG Longitude;
    ULONG Latitude;
} AIM_IN_BUFFER, *PAIM_IN_BUFFER;

typedef struct _AIM_OUT_BUFFER

    ULONG ExtendedStatus; 
} AIM_OUT_BUFFER, *PAIM_OUT_BUFFER;


#define IOCTL_MISSLEDEVICE_LAUNCH CTL_CODE( \
             FILE_DEVICE_UNKNOWN,    0x802, \   
             METHOD_NEITHER,  FILE_ACCESS_ANY )

 

IRP 緩沖區(qū)管理
==============

當(dāng)一個(gè)應(yīng)用程序或者設(shè)備驅(qū)動(dòng)程序間接地利用NtReadFile, NtWriteFile或NtDeviceIoControlFile系統(tǒng)服務(wù)(或者對(duì)應(yīng)于這些服務(wù)的Windows API函數(shù),即ReadFile,WriteFile和DeviceIoControl)來(lái)創(chuàng)建一個(gè)IRP時(shí),


處理讀寫(xiě)請(qǐng)求
============

最基本的I/O請(qǐng)求是在用戶(hù)緩沖區(qū)和設(shè)備之間交換數(shù)據(jù),I/O管理器提供了以傳統(tǒng)的讀寫(xiě)抽象來(lái)請(qǐng)求這樣的數(shù)據(jù)傳輸,給驅(qū)動(dòng)程序majorfunction域是IRP_MJ_READ或者IRP_MJ_WRITE的IRP的形式來(lái)傳遞請(qǐng)求,另一個(gè)IRP的域指定請(qǐng)求者的緩沖區(qū)地址。I/O管理器分配和維護(hù)的緩沖區(qū)地址是否是一個(gè)直接的虛擬地址或者一個(gè)中介的非分頁(yè)緩沖池是由設(shè)備對(duì)象的Flags域決定的。無(wú)論如何,在這個(gè)緩沖區(qū)和設(shè)備件的數(shù)據(jù)傳送是讀寫(xiě)派遣例程的工作。

緩沖的I/O
==========

在讀或者寫(xiě)操作的開(kāi)始,I/O管理器確認(rèn)用戶(hù)緩沖區(qū)的所有的虛擬存儲(chǔ)頁(yè),對(duì)于緩沖區(qū)I/O,它然后分配一個(gè)足夠用戶(hù)請(qǐng)求所使用的大小的非分頁(yè)緩沖池,這個(gè)暫時(shí)的非分頁(yè)緩沖區(qū)地址被存放在IRP的 AssociatedIrp.SystemBuffer域中,這個(gè)地址在數(shù)據(jù)傳輸過(guò)程中保持有效(也就是直到IRP被標(biāo)記上完成)。

對(duì)于讀操作,I/O管理器在IRP的UserBuffer域中紀(jì)錄原始的用戶(hù)緩沖區(qū)地址。然后使用這個(gè)保持的地址完成從非分頁(yè)的緩沖池到用戶(hù)存儲(chǔ)空間的數(shù)據(jù)拷貝工作。

對(duì)于寫(xiě)操作,I/O管理器在調(diào)用寫(xiě)派遣例程之前拷貝用戶(hù)緩沖區(qū)中的數(shù)據(jù)到非分頁(yè)的緩沖池中。然后設(shè)置IRP的UserBuffer域?yàn)镹ULL,因?yàn)闆](méi)有必要保持這個(gè)狀態(tài)。

直接I/O
======

在開(kāi)始操作之前,I/O管理器確認(rèn)用戶(hù)緩沖區(qū)使用的整個(gè)頁(yè)表,然后創(chuàng)建一個(gè)存儲(chǔ)器描述符表(MDL)的數(shù)據(jù)結(jié)構(gòu),然后存儲(chǔ)MDL的地址到IRP的 MdlAddress域,AssociatedIrp.SystemBuffer域和UserBuffer域被設(shè)置為NULL。

對(duì)于 DMA操作,MDL結(jié)構(gòu)被直接用來(lái)和adapter對(duì)象一起執(zhí)行數(shù)據(jù)傳輸。對(duì)于過(guò)程控制I/O,MDL結(jié)構(gòu)被用來(lái)和 MmGetSystemAddressForMdl函數(shù)一起去得到用戶(hù)緩沖區(qū)的系統(tǒng)映射地址,通過(guò)這個(gè)地址可以直接訪問(wèn)用戶(hù)緩沖區(qū)。使用這個(gè)技術(shù),用戶(hù)緩沖區(qū)被鎖定到了物理存儲(chǔ)器(也就是強(qiáng)制被變成非分頁(yè)地址),當(dāng)I/O請(qǐng)求完成,用戶(hù)緩沖區(qū)被解除鎖定。


其它的方法
==========

在設(shè)備對(duì)象的Flags域中有兩個(gè)bits,它指定DO_BUFFERED_IO或者DO_DIRECT_IO。如果都沒(méi)有設(shè)定,I/O管理器不會(huì)執(zhí)行上面的兩種方式。代替的是,它簡(jiǎn)單的將用戶(hù)空間的請(qǐng)求的緩沖區(qū)地址放到IRP的UserBuffer域, AssociatedIrp.SystemBuffer和MdlAddress域被設(shè)置為NULL。

一個(gè)簡(jiǎn)單的用戶(hù)模式的地址不是十分有用,驅(qū)動(dòng)程序中的大多數(shù)例程這時(shí)不能確定映射的這個(gè)原始的頁(yè)表。因此,用戶(hù)空間地址通常是無(wú)用的。只有一個(gè)例外:在高層驅(qū)動(dòng)程序的派遣例程被調(diào)用的時(shí)候,執(zhí)行使用原始的請(qǐng)求線程,這時(shí)用戶(hù)空間地址是有效的。中間驅(qū)動(dòng)程序或者任何DPC或者中斷服務(wù)例程從來(lái)都不依賴(lài)用戶(hù)空間緩沖區(qū)。

讀寫(xiě)請(qǐng)求實(shí)例
============

這是一個(gè)有趣的例子,雖然它很簡(jiǎn)單。通過(guò)保留一個(gè)分頁(yè)的緩沖池和拷貝用戶(hù)的數(shù)據(jù)到這個(gè)暫時(shí)的緩沖區(qū),來(lái)完成寫(xiě)操作,這個(gè)緩沖區(qū)一直保持到讀請(qǐng)求產(chǎn)生,這時(shí)這個(gè)暫時(shí)的緩沖區(qū)中的數(shù)據(jù)將被復(fù)制到用戶(hù)緩沖區(qū),然后釋放暫時(shí)的緩沖區(qū)。下面例子示范了讀寫(xiě)派遣例程和用戶(hù)緩沖區(qū)的訪問(wèn):

NTSTATUS DispatchWrite (IN PDEVICE_OBJECT pDO, IN PIRP pIrp)
{
    NTSTATUS status = STATUS_SUCCESS;      
    PDEVICE_EXTENSION pDE;
    PVOID userBuffer;                         
    ULONG xferSize;  

    // 堆棧中包含用戶(hù)緩沖區(qū)的信息
    PIO_STACK_LOCATION pIrpStack;
    pIrpStack = IoGetCurrentIrpStackLocation (pIrp);  

    // 假定使用緩沖區(qū)I/O
    userBuffer = pIrp->AssociatedIrp.SystemBuffer;
    xferSize = pIrpStack->Parameters.Write.Length;

    // 暫時(shí)的緩沖區(qū)指針在DEVICE_EXTENSION 中
    pDE = (PDEVICE_EXTENSION) pDO->DeviceExtension;  

    // 如果已經(jīng)有一個(gè)緩沖區(qū)的話,釋放它
    if (pDE->deviceBuffer != NULL)
    { 
        ExFreePool (pDE->deviceBuffer);
        PDE->deviceBuffer = NULL;     
        xferSize = 0;    
     }
 
     pDE->deviceBuffer = ExAllocatePool (PagedPool, xferSize);

    if (pDE->deviceBuffer == NULL)
   {
         // 沒(méi)有分配緩沖區(qū)
         status = STATUS_INSUFFICIENT_RESOURCES;  
         xferSize = 0; 
    }
    else // 復(fù)制數(shù)據(jù)
    {    
         pDE->deviceBufferSize = xferSize;
         RtlCopyMemory (pDE->deviceBuffer, userBuffer, xferSize);  
    }

    // 完成IRP 
    pIrp->IoStatus.Status = status;    
    pIrp->IoStatus.Information = xferSize;

    IoCompleteRequest (pIrp, IO_NO_INCREMENT);     
    return status;    
}

 

NTSTATUS DispatchRead (IN PDEVICE_OBJECT pDO, IN PIRP pIrp)

    NTSTATUS status = STATUS_SUCCESS;  
    PDEVICE_EXTENSION pDE;
    PVOID userBuffer;                      
    ULONG xferSize;

    //堆棧中包含用戶(hù)緩沖區(qū)的信息
    PIO_STACK_LOCATION pIrpStack;
 
    pIrpStck = IoGetCurrentIrpStackLocation (pIrp);
    userBuffer = pIrp->AssociatedIrp.SystemBuffer;
    xferSize = pIrpStack->Parameters.Read.Length;  

    // 暫時(shí)的緩沖區(qū)指針在DEVICE_EXTENSION 中
    pDE = (PDEVICE_EXTENSION) pDO->DeviceExtension;

    // 僅傳輸用戶(hù)請(qǐng)求數(shù)量的數(shù)據(jù)
    xferSize = (xferSize < pDE->deviceBufferSize) ?
                  xferSize : pDE->deviceBufferSize;

    // 復(fù)制臨時(shí)緩沖區(qū)到用戶(hù)空間
    RtlCopyMemory (userBuffer, pDE->deviceBuffer, xferSize);

    // 釋放臨時(shí)分頁(yè)緩沖池
    ExFreePool (pDE->deviceBuffer);  
    pDE->deviceBuffer = NULL;
    pDE->deviceBufferSize = 0;          

    // 完成I/O請(qǐng)求
    pIrp->IoStatus.Status = status;
    pIrp->IoStatus.Information = xferSize;
    IoCompleteRequest (pIrp, IO_NO_INCREMENT);  
    return status;       
}

 

 


創(chuàng)建IRP
=======

IRP 開(kāi)始于某個(gè)實(shí)體調(diào)用I/O管理器函數(shù)創(chuàng)建它。在上圖中,我使用術(shù)語(yǔ)"I/O管理器"來(lái)描述這個(gè)實(shí)體,盡管系統(tǒng)中確實(shí)有一個(gè)單獨(dú)的系統(tǒng)部件用于創(chuàng)建IRP。事實(shí)上,更精確地說(shuō),應(yīng)該是某個(gè)實(shí)體創(chuàng)建了IRP,并不是操作系統(tǒng)的某個(gè)例程創(chuàng)建了IRP。但是你的驅(qū)動(dòng)程序有時(shí)也會(huì)創(chuàng)建IRP。

可以使用下面任何一種函數(shù)創(chuàng)建IRP:

IoBuildAsynchronousFsdRequest 創(chuàng)建異步IRP(不需要等待其完成)。該函數(shù)和下一個(gè)函數(shù)僅適用于創(chuàng)建某些類(lèi)型的IRP。
IoBuildSynchronousFsdRequest 創(chuàng)建同步IRP(需要等待其完成)。
IoBuildDeviceIoControlRequest 創(chuàng)建一個(gè)同步IRP_MJ_DEVICE_CONTROL或IRP_MJ_INTERNAL_DEVICE_CONTROL請(qǐng)求。
IoAllocateIrp 創(chuàng)建上面三個(gè)函數(shù)不支持的其它種類(lèi)的IRP。
 
前兩個(gè)函數(shù)中的Fsd表明這些函數(shù)專(zhuān)用于文件系統(tǒng)驅(qū)動(dòng)程序(FSD)。雖然FSD是這兩個(gè)函數(shù)的主要使用者,但其它驅(qū)動(dòng)程序也可以調(diào)用這些函數(shù)。DDK還公開(kāi)了一個(gè)IoMakeAssociatedIrp函數(shù),該函數(shù)用于創(chuàng)建某些IRP的從屬I(mǎi)RP。WDM驅(qū)動(dòng)程序不應(yīng)該使用這個(gè)函數(shù)。


操作IRP
=======

一些IRP訪問(wèn)函數(shù)在IRP頭,其它的是處理特殊的IRP堆棧函數(shù)。了解訪問(wèn)函數(shù)需要指向整個(gè)IRP,或者僅僅指向IRP堆棧是非常重要的。

I/O管理器提供多個(gè)處理IRP的函數(shù),表4.3列出了常用的一些。

函數(shù)                  描述                      調(diào)用者
----------------------------------------------------------------
IoStartPacket      發(fā)送IRP到Start I/O例程        Dispatch
IoCompleteRequest  表示所有的處理完成            DpcForIsr
IoStartNextPacket  發(fā)送下一個(gè)IRP到Start I/O例程  DpcForIsr
IoCallDriver       發(fā)送IRP給另一個(gè)驅(qū)動(dòng)程序       Dispatch
IoAllocateIrp      請(qǐng)求另外的IRP                 Dispatch
IoFreeIrp      &nbp;   釋放驅(qū)動(dòng)程序分配的IRP         I/O Completion
-----------------------------------------------------------------
                     表4.3處理整個(gè)IRP的函數(shù)

I/O管理器同樣提供多個(gè)函數(shù),驅(qū)動(dòng)程序可以用它們?cè)L問(wèn)IRP的堆棧部分,這些函數(shù)列在表4.4中:

函數(shù)                           描述                                        調(diào)用者
----------------------------------------------------------------------------------- 
IoGetCurrentIrpStackLocation  得到調(diào)用者堆棧的指針  
IoMarkIrpPending              為進(jìn)一步的處理標(biāo)記調(diào)用者I/O堆棧             Dispatch
IoGetNextIrpStackLocation     得到下一個(gè)低層的驅(qū)動(dòng)程序的I/O堆棧單元的指針 Dispatch
IoSetNextIrpStackLocation     將I/O堆棧指針壓入堆棧                       Dispatch
IoSetCompleteRoutine          把I/O Completion例程連接到下一個(gè)低層的驅(qū)動(dòng)
                              程序的I/O堆棧單元                           Dispatch
------------------------------------------------------------------------------------ 
                        表4.4  IO_STACK_LOCATION訪問(wèn)函數(shù)

 


發(fā)往派遣例程
============

創(chuàng)建完IRP后,你可以調(diào)用IoGetNextIrpStackLocation函數(shù)獲得該IRP第一個(gè)堆棧單元的指針。然后初始化這個(gè)堆棧單元。在初始化過(guò)程的最后,你需要填充MajorFunction代碼。堆棧單元初始化完成后,就可以調(diào)用IoCallDriver函數(shù)把IRP發(fā)送到設(shè)備驅(qū)動(dòng)程序:

PDEVICE_OBJECT DeviceObject;   

//something gives you this

PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation (Irp);
stack->MajorFunction = IRP_MJ_Xxx;

<other initialization of "stack">

NTSTATUS status = IoCallDriver(DeviceObject, Irp);
 

IoCallDriver函數(shù)的第一個(gè)參數(shù)是你在某處獲得的設(shè)備對(duì)象的地址。我將在本章的結(jié)尾處描述獲得設(shè)備對(duì)象指針的兩個(gè)常用方法。在這里,我們先假設(shè)你已經(jīng)有了這個(gè)指針。

IRP 中的第一個(gè)堆棧單元指針被初始化成指向該堆棧單元之前的堆棧單元,因?yàn)镮/O堆棧實(shí)際上是IO_STACK_LOCATION結(jié)構(gòu)數(shù)組,你可以認(rèn)為這個(gè)指針被初始化為指向一個(gè)不存在的"-1"元素,因此當(dāng)我們要初始化第一個(gè)堆棧單元時(shí)我們實(shí)際需要的是"下一個(gè)"堆棧單元。IoCallDriver將沿著這個(gè)堆棧指針找到第0個(gè)表項(xiàng),并提取我們放在那里的主功能代碼,在上例中為IRP_MJ_Xxx。然后IoCallDriver函數(shù)將利用 DriverObject指針找到設(shè)備對(duì)象中的MajorFunction表。IoCallDriver將使用主功能代碼索引這個(gè)表,最后調(diào)用找到的地址 (派遣函數(shù))。

你可以把IoCallDriver函數(shù)想象為下面代碼:

NTSTATUS IoCallDriver(PDEVICE_OBJECT device, PIRP Irp)
{
    IoSetNextIrpStackLocation(Irp);
    PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
    stack->DeviceObject = device;
    ULONG fcn = stack->MajorFunction;
    PDRIVER_OBJECT driver = device->DriverObject;
    return (*driver->MajorFunction[fcn])(device, Irp);
}

 

管理自己的IRP
=============

現(xiàn)在,我已經(jīng)解釋完IRP處理的所有底層結(jié)構(gòu),我們可以返回到創(chuàng)建IRP的主題上。我曾提到過(guò)有四種不同的服務(wù)函數(shù)可以用來(lái)創(chuàng)建IRP。但我不得不推遲到現(xiàn)在才討論如何選擇它們。下面事實(shí)供你在選擇時(shí)參考:

IoBuildAsynchronousFsdRequest和IoBuildSynchronousFsdRequest函數(shù)僅能用于創(chuàng)建主功能碼在表5-3中列出的IRP。
IoBuildDeviceIoControlRequest僅能用于創(chuàng)建主功能碼為IRP_MJ_DEVICE_CONTROL或IRP_MJ_INTERNAL_DEVICE_CONTROL的IRP。
 
當(dāng)調(diào)用IoCompleteRequest時(shí),有些代碼可能會(huì)釋放IRP占用的內(nèi)存。
你應(yīng)該事先做好安排以便該IRP能被IoCancelIrp調(diào)用取消。
表5-3.  適用于IoBuildXxxFsdRequest的IRP類(lèi)型。

     主功能碼
---------------------
IRP_MJ_READ
IRP_MJ_WRITE
IRP_MJ_FLUSH_BUFFERS
IRP_MJ_SHUTDOWN
IRP_MJ_PNP
IRP_MJ_POWER
----------------------

 

使用IoBuildSynchronousFsdRequest
================================

IoBuildSynchronousFsdRequest函數(shù)的調(diào)用格式如下:

PIRP Irp = IoBuildSynchronousFsdRequest(MajorFunction,
     DeviceObject,
     Buffer,
     Length,
     StartingOffset,
     Event,
     IoStatusBlock);
 

MajorFunction (ULONG)是新IRP的主功能碼(見(jiàn)表5-3)。DeviceObject(PDEVICE_OBJECT)是該IRP最初要發(fā)送到的設(shè)備對(duì)象的地址。對(duì)于讀寫(xiě)請(qǐng)求,你必須提供Buffer(PVOID)、Length(ULONG)、StartingOffset(PLARGE_INTEGER) 參數(shù)。Buffer是一個(gè)內(nèi)核模式數(shù)據(jù)緩沖區(qū)的地址,Length是讀寫(xiě)操作的字節(jié)長(zhǎng)度,StartingOffset是讀寫(xiě)操作在目標(biāo)文件中的定位。對(duì)于該函數(shù)創(chuàng)建的其它請(qǐng)求,這些參數(shù)將被忽略。(這就是為什么該函數(shù)在WDM.H中的原型將這些參數(shù)定為"可選的",但對(duì)于讀寫(xiě)請(qǐng)求它們則是必需的) I/O管理器假定你給出的緩沖區(qū)地址在當(dāng)前進(jìn)程上下文中是有效的。

Event(PKEVENT)是一個(gè)事件對(duì)象的地址, IoCompleteRequest應(yīng)該在操作完成時(shí)設(shè)置這個(gè)事件,IoStatusBlock(PIO_STATUS_BLOCK)是一個(gè)狀態(tài)塊的地址,該狀態(tài)塊用于保存IRP結(jié)束狀態(tài)和信息。在操作完成前,事件對(duì)象和狀態(tài)塊必須一直存在于內(nèi)存中。

如果創(chuàng)建的是讀寫(xiě)IRP,那么在提交該 IRP前你不需要做任何事。如果創(chuàng)建的是其它類(lèi)型的IRP,你需要用附加的參數(shù)信息完成第一個(gè)堆棧單元;MajorFunction已經(jīng)被設(shè)置,不能設(shè)置未公開(kāi)的Tail.Overlay.OriginalFileObject域,這樣做將使文件對(duì)象在IRP完成時(shí)被錯(cuò)誤地廢除。同樣也不可能設(shè)置 RequestorMode,它已經(jīng)被初始化成KernelMode。(我提到這兩點(diǎn)是因?yàn)槲一叵肫鹪x過(guò)該函數(shù)的一個(gè)公開(kāi)討論,該討論認(rèn)為應(yīng)該做這兩件事,但我認(rèn)為沒(méi)有必要) 現(xiàn)在,你可以提交該IRP并等待其完成:

PIRP Irp = IoBuildSynchronousFsdRequest(...);
NTSTATUS status = IoCallDriver(DeviceObject, Irp);
if (status == STATUS_PENDING)
  KeWaitForSingleObject(Event, Executive, KernelMode, FALSE, NULL);
 

IRP完成后,你可以通過(guò)察看你的I/O狀態(tài)塊來(lái)了解該IRP的結(jié)束狀態(tài)和相關(guān)信息。
很明顯,在等待操作完成前你應(yīng)該運(yùn)行在PASSIVE_LEVEL級(jí)上和非任意線程上下文中。


使用IoBuildAsynchronousFsdRequest
=================================

IoBuildAsynchronousFsdRequest是用于創(chuàng)建表5-3中列出的IRP的另一個(gè)例程。該函數(shù)的原型如下:

PIRP IoBuildAsynchronousFsdRequest(ULONG MajorFunction,
       PDEVICE_OBJECT DeviceObject,
       PVOID Buffer,
       ULONG Length,
       PLARGE_INTEGER StartingOffset,
       PIO_STATUS_BLOCK IoStatusBlock);
 

這個(gè)原型與IoBuildSynchronousFsdRequest不同,它沒(méi)有Event參數(shù),并且IoStatusBlock指針可以為NULL。你仍需要為該函數(shù)創(chuàng)建的IRP安裝一個(gè)完成例程,這個(gè)完成例程的工作就是調(diào)用IoFreeIrp并返回 STATUS_MORE_PROCESSING_REQUIRED。

我想知道這兩個(gè)函數(shù)在創(chuàng)建IRP上有什么不同之處,因此我稍微深入了一些。這兩個(gè)函數(shù)的代碼基本上是相同的。實(shí)際上,IoBuildAsynchronousFsdRequest是 IoBuildSynchronousFsdRequest的子函數(shù)。IoBuildSynchronousFsdRequest僅有的額外操作就是保存事件指針到IRP中(I/O管理器需要找到這個(gè)事件對(duì)象并把它置成信號(hào)態(tài)),并把該IRP放到當(dāng)前線程的IRP隊(duì)列中。這可以使IRP線程死亡時(shí)能被取消。

你可能在這樣兩種情況下需要調(diào)用IoBuildAsynchronousFsdRequest:第一種情況,當(dāng)你發(fā)現(xiàn)自己執(zhí)行在任意線程上下文中并需要?jiǎng)?chuàng)建一個(gè)IRP時(shí),IoBuildAsynchronousFsdRequest函數(shù)就是理想選擇,因?yàn)楫?dāng)前線程(任意線程)的結(jié)束過(guò)程不能取消這個(gè)新創(chuàng)建的IRP。第二種情況,當(dāng)你運(yùn)行在APC_LEVEL級(jí)的非任意線程上下文時(shí),你需要同步執(zhí)行一個(gè)IRP。 IoBuildSynchronousFsdRequest不能滿(mǎn)足這個(gè)要求,因?yàn)檫@種IRQL將阻塞設(shè)置事件的APC。所以你應(yīng)該調(diào)用 IoBuildAsynchronousFsdRequest并在某個(gè)事件上等待,而這個(gè)事件將由你的完成例程去置成信號(hào)態(tài)。第二種情況不經(jīng)常出現(xiàn)在設(shè)備驅(qū)動(dòng)程序中。

通常情況下,與IoBuildAsynchronousFsdRequest配合使用的完成例程不僅僅是調(diào)用 IoFreeIrp。實(shí)際上,你需要實(shí)現(xiàn)I/O管理器用于清除已完成IRP的內(nèi)部例程(IopCompleteRequest)。你不能依賴(lài)I/O管理器來(lái)清除IoBuildAsynchronousFsdRequest創(chuàng)建的IRP。因?yàn)樵诋?dāng)前版本的Windows 98和Windows 2000中清除操作需要一個(gè)APC,并且在任意線程下執(zhí)行APC是錯(cuò)誤的,所以I/O管理器不能為你做清除工作,你必須自己做全部的清除工作。

如果IRP的目標(biāo)設(shè)備對(duì)象設(shè)置了DO_DIRECT_IO標(biāo)志,IoBuildAsynchronousFsdRequest將創(chuàng)建一個(gè)MDL,這個(gè)MDL必須由你自己釋放,如下面代碼:

NTSTATUS CompletionRoutine(...)
{
  PMDL mdl;
  while ((mdl = Irp->MdlAddress))
  {
    Irp->MdlAddress = mdl->Next;
    IoFreeMdl(mdl);
  }
  ...
  IoFreeIrp(Irp);
  return STATUS_MORE_PROCESSING_REQUIRED;
}
 

如果IRP的目標(biāo)設(shè)備對(duì)象設(shè)置了DO_BUFFERED_IO標(biāo)志,IoBuildAsynchronousFsdRequest將分配一個(gè)系統(tǒng)緩沖區(qū),但這個(gè)緩沖區(qū)需要你來(lái)釋放。如果你正在做一個(gè)輸入操作,那么在釋放這個(gè)緩沖區(qū)之前你還需要把輸入數(shù)據(jù)從系統(tǒng)緩沖區(qū)復(fù)制到你自己真正的輸入緩沖區(qū)。并且要保證這個(gè)真正的緩沖區(qū)在非分頁(yè)內(nèi)存中,因?yàn)橥瓿衫绦枰贒ISPATCH_LEVEL級(jí)上運(yùn)行。你還需要確保你得到的是緩沖區(qū)的內(nèi)核地址,因?yàn)橥瓿衫踢\(yùn)行在任意線程上下文中。如果這些限制還不足以使你在使用IoBuildAsynchronousFsdRequest(用于DO_BUFFERED_IO設(shè)備)時(shí)感到泄氣,那么想一想你還必須在完成例程中檢測(cè)未公開(kāi)標(biāo)志位IRP_BUFFERED_IO、IRP_INPUT_OPERATION、 IRP_DEALLOCATE_BUFFER。我不將給出關(guān)于這個(gè)函數(shù)的代碼,因?yàn)槲以WC過(guò)不在本書(shū)中使用未公開(kāi)的內(nèi)部技術(shù)。

我的建議是,僅在你知道IRP的目標(biāo)設(shè)備不使用DO_BUFFERED_IO標(biāo)志時(shí),才使用IoBuildAsynchronousFsdRequest。

 

使用IoAllocateIrp
=================

如果你想更深入一些,你可以使用IoAllocateIrp函數(shù)創(chuàng)建任何類(lèi)型的IRP:

PIRP Irp = IoAllocateIrp(StackSize, ChargeQuota);
 
StackSize (CCHAR)是與IRP一同分配的I/O堆棧單元的數(shù)目,ChargeQuota(BOOLEAN)指出該內(nèi)存分配是否應(yīng)充入進(jìn)程限額。通常,你可以從該IRP對(duì)應(yīng)的設(shè)備對(duì)象中獲得StackSize參數(shù),ChargeQuota參數(shù)指定為FALSE,例如:

PDEVICE_OBJECT DeviceObject;
PIRP Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
 
當(dāng)你使用IoAllocateIrp時(shí),必須為它創(chuàng)建的IRP安裝一個(gè)完成例程,并且該完成例程必須返回 STATUS_MORE_PROCESSING_REQUIRED。另外,你還要負(fù)責(zé)釋放該IRP以及任何相關(guān)的對(duì)象。如果你不打算取消該IRP,你的完成例程應(yīng)該象這樣:

NTSTATUS OnComplete(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context)
{
  IoFreeIrp(Irp);
  return STATUS_MORE_PROCESSING_REQUIRED;
}
 
如果IRP的發(fā)起線程提前結(jié)束,則IoAllocateIrp創(chuàng)建的IRP不能被自動(dòng)取消。


設(shè)備對(duì)象指針從哪來(lái)?
===================

IoCallDriver函數(shù)需要一個(gè)PDEVICE_OBJECT作為它的第一個(gè)參數(shù)。你也許想知道我是從哪得到設(shè)備對(duì)象的指針的。

一個(gè)最明顯的獲得設(shè)備對(duì)象指針的方式是調(diào)用IoAttachDeviceToDeviceStack函數(shù),這也是每個(gè)WDM驅(qū)動(dòng)程序的AddDevice函數(shù)應(yīng)做的一步。在本書(shū)的所有例子驅(qū)動(dòng)程序中,你都將看到下面一行代碼:

pdx->LowerDeviceObject = IoAttachDeviceToDeviceStack(fdo, pdo);
 
任何時(shí)候我們需要沿著設(shè)備堆棧向下傳送一個(gè)IRP時(shí),我們就使用這個(gè)設(shè)備對(duì)象指針。
另一個(gè)常用的定位設(shè)備對(duì)象的方法是使用對(duì)象名稱(chēng):

PUNICODE_STRING DeviceName;   //  something gives you this
PDEVICE_OBJECT DeviceObject;  //  an output from this process
PFILE_OBJECT FileObject;      //  another output
NTSTATUS status = IoGetDeviceObjectPointer(DeviceName, <access mask>, &FileObject, &DeviceObject);

通過(guò)指定設(shè)備對(duì)象名稱(chēng)和與其相關(guān)的文件對(duì)象指針你可以得到這個(gè)設(shè)備對(duì)象的指針。文件對(duì)象就是文件句柄指向的對(duì)象。最后,你還需要解除文件對(duì)象參考,如下:

ObDereferenceObject(FileObject); //  DeviceObject now poison!

解除文件對(duì)象參考后,你還要釋放設(shè)備對(duì)象的隱含引用。如果你要繼續(xù)使用該設(shè)備對(duì)象,應(yīng)該先做設(shè)備對(duì)象引用:

ObReferenceObject(DeviceObject);
ObDereferenceObject(FileObject); //  DeviceObject still okay
 
然而,你不應(yīng)該機(jī)械地把前兩行代碼加入到你的驅(qū)動(dòng)程序中。實(shí)際上,當(dāng)你向地址由IoGetDeviceObjectPointer獲得的設(shè)備對(duì)象發(fā)送IRP時(shí),你應(yīng)該帶著文件對(duì)象:

PIRP Irp = IoBuildXxxRequest(...);
PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);
stack->FileObject = FileObject;
IoCallDriver(DeviceObject, Irp);
 
這里是這個(gè)額外語(yǔ)句的解釋。在內(nèi)部,IoGetDeviceObjectPointer打開(kāi)設(shè)備對(duì)象的一個(gè)常規(guī)句柄,驅(qū)動(dòng)程序應(yīng)創(chuàng)建與這個(gè)文件對(duì)象相關(guān)聯(lián)的某些輔助數(shù)據(jù)結(jié)構(gòu),處理IRP_MJ_CREATE之后的IRP也許會(huì)用到這些結(jié)構(gòu)。處理IRP_MJ_CLOSE時(shí)將銷(xiāo)毀這些結(jié)構(gòu)。因此,你需要在發(fā)往驅(qū)動(dòng)程序的每個(gè)IRP的第一個(gè)堆棧單元中設(shè)置FileObject指針。

你不必總在新IRP中設(shè)置文件對(duì)象指針,如果你在 IRP_MJ_CREATE的對(duì)應(yīng)例程中獲得了文件對(duì)象,那么你下面的驅(qū)動(dòng)程序就沒(méi)有必要查看該文件對(duì)象。而在前面描述的情況中,文件對(duì)象的所有者是你用 IoGetDeviceObjectPointer函數(shù)獲得設(shè)備對(duì)象的驅(qū)動(dòng)程序,在這種情況下,你必須設(shè)置文件對(duì)象指針。

 

尋址數(shù)據(jù)緩沖區(qū)
==============

當(dāng)應(yīng)用程序發(fā)起一個(gè)讀或?qū)懖僮鲿r(shí),通過(guò)給出一個(gè)用戶(hù)模式虛擬地址和長(zhǎng)度,應(yīng)用程序向I/O管理器提供了一個(gè)數(shù)據(jù)緩沖區(qū)。正如我在第三章中提到的,內(nèi)核模式驅(qū)動(dòng)程序幾乎從不使用用戶(hù)模式虛擬地址訪問(wèn)內(nèi)存,因?yàn)槟悴荒馨丫€程上下文確定下來(lái)。Windows 2000為驅(qū)動(dòng)程序訪問(wèn)用戶(hù)模式數(shù)據(jù)緩沖區(qū)提供了三種方法:

在buffered方式中,I/O管理器先創(chuàng)建一個(gè)與用戶(hù)模式數(shù)據(jù)緩沖區(qū)大小相等的系統(tǒng)緩沖區(qū)。而你的驅(qū)動(dòng)程序?qū)⑹褂眠@個(gè)系統(tǒng)緩沖區(qū)工作。I/O管理器負(fù)責(zé)在系統(tǒng)緩沖區(qū)和用戶(hù)模式緩沖區(qū)之間復(fù)制數(shù)據(jù)。
在direct方式中,I/O管理器鎖定了包含用戶(hù)模式緩沖區(qū)的物理內(nèi)存頁(yè),并創(chuàng)建一個(gè)稱(chēng)為MDL(內(nèi)存描述符表)的輔助數(shù)據(jù)結(jié)構(gòu)來(lái)描述鎖定頁(yè)。因此你的驅(qū)動(dòng)程序?qū)⑹褂肕DL工作。
在neither方式中,I/O管理器僅簡(jiǎn)單地把用戶(hù)模式的虛擬地址傳遞給你。而使用用戶(hù)模式地址的驅(qū)動(dòng)程序應(yīng)十分小心。

指定緩沖方式
============

為了指定設(shè)備讀寫(xiě)的緩沖方式,你應(yīng)該在AddDevice函數(shù)中,在創(chuàng)建設(shè)備對(duì)象后,立即設(shè)置其中的標(biāo)志位:

NTSTATUS AddDevice(...)
{
  PDEVICE_OBJECT fdo;
  IoCreateDevice(..., &fdo);
  fdo->Flags |= DO_BUFFERED_IO;
           <or>
  fdo->Flags |= DO_DIRECT_IO;
           <or>
  fdo->Flags |= 0;  // i.e., neither direct nor buffered
}
 
這之后你不能該變緩沖方式的設(shè)置,因?yàn)檫^(guò)濾器驅(qū)動(dòng)程序?qū)?fù)制這個(gè)標(biāo)志設(shè)置,并且,如果你改變了設(shè)置,過(guò)濾器驅(qū)動(dòng)程序沒(méi)有辦法知道這個(gè)改變

Buffered方式
============

當(dāng)I/O 管理器創(chuàng)建IRP_MJ_READ或IRP_MJ_WRITE請(qǐng)求時(shí),它探測(cè)設(shè)備的緩沖標(biāo)志以決定如何描述新IRP中的數(shù)據(jù)緩沖區(qū)。如果 DO_BUFFERED_IO標(biāo)志設(shè)置,I/O管理器將分配與用戶(hù)緩沖區(qū)大小相同的非分頁(yè)內(nèi)存。它把緩沖區(qū)的地址和長(zhǎng)度保存到兩個(gè)十分不同的地方,見(jiàn)下面代碼片段中用粗體字表示的語(yǔ)句。你可以假定I/O管理器執(zhí)行下面代碼(注意這并不是Windows NT的源代碼):

PVOID uva;             //  user-mode virtual buffer address
ULONG length;          //  length of user-mode buffer

PVOID sva = ExAllocatePoolWithQuota(NonPagedPoolCacheAligned, length);
if (writing)
  RtlCopyMemory(sva, uva, length);

Irp->AssociatedIrp.SystemBuffer = sva;

PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);
if (reading)
  stack->Parameters.Read.Length = length;
else
  stack->Parameters.Write.Length = length;

<code to send and await IRP>

if (reading)
  RtlCopyMemory(uva, sva, length);

ExFreePool(sva);

可以看出,系統(tǒng)緩沖區(qū)地址被放在IRP的AssocatedIrp.SystemBuffer域中,而數(shù)據(jù)的長(zhǎng)度被放到stack-> Parameters聯(lián)合中。在這個(gè)過(guò)程中還包含作為驅(qū)動(dòng)程序開(kāi)發(fā)者不必了解的額外細(xì)節(jié)。例如,讀操作之后的數(shù)據(jù)復(fù)制工作實(shí)際發(fā)生一個(gè)APC期間,在原始線程的上下文中,由一個(gè)與構(gòu)造該IRP完全不同的子例程執(zhí)行。I/O管理器把用戶(hù)模式虛擬地址(uva變量)保存到IRP的UserBuffer域中,這樣一來(lái)復(fù)制操作就可以找到這個(gè)地址。但你不要使代碼依賴(lài)這些事實(shí),因?yàn)樗鼈冇锌赡軙?huì)改變。IRP最終完成后,I/O管理器將釋放系統(tǒng)緩沖區(qū)所占用的內(nèi)存。

 


Direct方式
==========

如果你在設(shè)備對(duì)象中指定DO_DIRECT_IO方式,I/O管理器將創(chuàng)建一個(gè)MDL用來(lái)描述包含該用戶(hù)模式數(shù)據(jù)緩沖區(qū)的鎖定內(nèi)存頁(yè)。MDL結(jié)構(gòu)的聲明如下:

typedef struct _MDL {
  struct _MDL *Next;
  CSHORT Size;
  CSHORT MdlFlags;
  struct _EPROCESS *Process;
  PVOID MappedSystemVa;
  PVOID StartVa;
  ULONG ByteCount;
  ULONG ByteOffset;
} MDL, *PMDL;
 

圖7 -3顯示了MDL扮演的角色。StartVa成員給出了用戶(hù)緩沖區(qū)的虛擬地址,這個(gè)地址僅在擁有數(shù)據(jù)緩沖區(qū)的用戶(hù)模式進(jìn)程上下文中才有效。 ByteOffset是緩沖區(qū)起始位置在一個(gè)頁(yè)幀中的偏移值,ByteCount是緩沖區(qū)的字節(jié)長(zhǎng)度。Pages數(shù)組沒(méi)有被正式地聲明為MDL結(jié)構(gòu)的一部分,在內(nèi)存中它跟在MDL的后面,包含用戶(hù)模式虛擬地址映射為物理頁(yè)幀的個(gè)數(shù)。

用于訪問(wèn)MDL的宏和訪問(wèn)函數(shù)

   宏或函數(shù)                                   描述
-----------------------------------------------------------------------------
IoAllocateMdl                 創(chuàng)建MDL
IoBuildPartialMdl             創(chuàng)建一個(gè)已存在MDL的子MDL
IoFreeMdl                     銷(xiāo)毀MDL
MmBuildMdlForNonPagedPool     修改MDL以描述內(nèi)核模式中一個(gè)非分頁(yè)內(nèi)存區(qū)域
MmGetMdlByteCount             取緩沖區(qū)字節(jié)大小
MmGetMdlByteOffset            取緩沖區(qū)在第一個(gè)內(nèi)存頁(yè)中的偏移
MmGetMdlVirtualAddress        取虛擬地址
MmGetSystemAddressForMdl      創(chuàng)建映射到同一內(nèi)存位置的內(nèi)核模式虛擬地址
MmGetSystemAddressForMdlSafe  與MmGetSystemAddressForMdl相同,但Windows 2000首選
MmInitializeMdl               (再)初始化MDL以描述一個(gè)給定的虛擬緩沖區(qū)
MmPrepareMdlForReuse          再初始化MDL
MmProbeAndLockPages           地址有效性校驗(yàn)后鎖定內(nèi)存頁(yè)
MmSizeOfMdl                   取為描述一個(gè)給定的虛擬緩沖區(qū)的MDL所占用的內(nèi)存大小
MmUnlockPages                 為該MDL解鎖內(nèi)存頁(yè)
--------------------------------------------------------------------------------

對(duì)于I/O管理器執(zhí)行的Direct方式的讀寫(xiě)操作,其過(guò)程可以想象為下面代碼:

KPROCESSOR_MODE mode;      //  either KernelMode or UserMode
PMDL mdl = IoAllocateMdl(uva, length, FALSE, TRUE, Irp);
MmProbeAndLockPages(mdl, mode, reading ? IoWriteAccess : IoReadAccess);

<code to send and await IRP>

MmUnlockPages(mdl);
ExFreePool(mdl);
 

I/O 管理器首先創(chuàng)建一個(gè)描述用戶(hù)緩沖區(qū)的MDL。IoAllocateMdl的第三個(gè)參數(shù)(FALSE)指出這是一個(gè)主數(shù)據(jù)緩沖區(qū)。第四個(gè)參數(shù)(TRUE)指出內(nèi)存管理器應(yīng)把該內(nèi)存充入進(jìn)程配額。最后一個(gè)參數(shù)(Irp)指定該MDL應(yīng)附著的IRP。在內(nèi)部,IoAllocateMdl把Irp-> MdlAddress設(shè)置為新創(chuàng)建MDL的地址,以后你將用到這個(gè)成員,并且I/O管理器最后也使用該成員來(lái)清除MDL。

這段代碼的關(guān)鍵地方是調(diào)用MmProbeAndLockPages。該函數(shù)校驗(yàn)?zāi)莻€(gè)數(shù)據(jù)緩沖區(qū)是否有效,是否可以按適當(dāng)模式訪問(wèn)。如果我們向設(shè)備寫(xiě)數(shù)據(jù),我們必須能讀緩沖區(qū)。如果我們從設(shè)備讀數(shù)據(jù),我們必須能寫(xiě)緩沖區(qū)。另外,該函數(shù)鎖定了包含數(shù)據(jù)緩沖區(qū)的物理內(nèi)存頁(yè),并在MDL的后面填寫(xiě)了頁(yè)號(hào)數(shù)組。在效果上,一個(gè)鎖定的內(nèi)存頁(yè)將成為非分頁(yè)內(nèi)存池的一部分,直到所有對(duì)該頁(yè)內(nèi)存加鎖的調(diào)用者都對(duì)其解了鎖。

在Direct方式的讀寫(xiě)操作中,對(duì)MDL你最可能做的事是把它作為參數(shù)傳遞給其它函數(shù)。例如,DMA傳輸?shù)腗apTransfer步驟需要一個(gè)MDL。另外,在內(nèi)部,USB讀寫(xiě)操作總使用MDL。所以你應(yīng)該把讀寫(xiě)操作設(shè)置為DO_DIRECT_IO方式,并把結(jié)果MDL傳遞給USB總線驅(qū)動(dòng)程序。

順便提一下,I/O管理器確實(shí)在stack->Parameters聯(lián)合中保存了讀寫(xiě)請(qǐng)求的長(zhǎng)度,但驅(qū)動(dòng)程序應(yīng)該直接從MDL中獲得請(qǐng)求數(shù)據(jù)的長(zhǎng)度。

ULONG length = MmGetMdlByteCount(mdl);


內(nèi)核同步對(duì)象
============

Windows NT提供了五種內(nèi)核同步對(duì)象(Kernel Dispatcher Object),你可以用它們控制非任意線程(普通線程)的流程。表4-1列出了這些內(nèi)核同步對(duì)象的類(lèi)型及它們的用途。在任何時(shí)刻,任何對(duì)象都處于兩種狀態(tài)中的一種:信號(hào)態(tài)或非信號(hào)態(tài)。有時(shí),當(dāng)代碼運(yùn)行在某個(gè)線程的上下文中時(shí),它可以阻塞這個(gè)線程的執(zhí)行,調(diào)用KeWaitForSingleObject或 KeWaitForMultipleObjects函數(shù)可以使代碼(以及背景線程)在一個(gè)或多個(gè)同步對(duì)象上等待,等待它們進(jìn)入信號(hào)態(tài)。內(nèi)核為初始化和控制這些對(duì)象的狀態(tài)提供了例程。

表4-1. 內(nèi)核同步對(duì)象

對(duì)象                   數(shù)據(jù)類(lèi)型                 描述
------------------------------------------------------------------------------
Event(事件)           KEVENT            阻塞一個(gè)線程直到其它線程檢測(cè)到某事件發(fā)生 
Semaphore(信號(hào)燈)     KSEMAPHORE        與事件對(duì)象相似,但可以滿(mǎn)足任意數(shù)量的等待 
Mutex(互斥)           KMUTEX            執(zhí)行到關(guān)鍵代碼段時(shí),禁止其它線程執(zhí)行該代碼段 
Timer(定時(shí)器)         KTIMER            推遲線程執(zhí)行一段時(shí)期
Thread(線程)          KTHREAD           阻塞一個(gè)線程直到另一個(gè)線程結(jié)束
--------------------------------------------------------------------------------

 

何時(shí)阻塞和怎樣阻塞一個(gè)線程
==========================

為了理解WDM驅(qū)動(dòng)程序何時(shí)以及如何利用內(nèi)核同步對(duì)象阻塞一個(gè)線程,你必須先對(duì)線程有一些基本了解。通常,如果在線程執(zhí)行時(shí)發(fā)生了軟件或硬件中斷,那么在內(nèi)核處理中斷期間,該線程仍然是"當(dāng)前"線程。而內(nèi)核模式代碼執(zhí)行時(shí)所在的上下文環(huán)境就是指這個(gè)"當(dāng)前"線程的上下文。為了響應(yīng)各種中斷,Windows NT調(diào)度器可能會(huì)切換線程,這樣,另一個(gè)線程將成為新的"當(dāng)前"線程。

術(shù)語(yǔ)"任意線程上下文(arbitrary thread context)"和"非任意線程上下文(nonarbitrary thread context)"用于精確描述驅(qū)動(dòng)程序例程執(zhí)行時(shí)所處于的上下文種類(lèi)。如果我們知道程序正處于初始化I/O請(qǐng)求線程的上下文中,則該上下文不是任意上下文。然而,在大部分時(shí)間里,WDM驅(qū)動(dòng)程序無(wú)法知道這個(gè)事實(shí),因?yàn)榭刂颇膫€(gè)線程應(yīng)該激活的機(jī)會(huì)通常都是在中斷發(fā)生時(shí)。當(dāng)應(yīng)用程序發(fā)出I/O請(qǐng)求時(shí),將產(chǎn)生一個(gè)從用戶(hù)模式到內(nèi)核模式的轉(zhuǎn)換,而創(chuàng)建并發(fā)送該IRP的I/O管理器例程將繼續(xù)運(yùn)行在非任意線程的上下文中。我們用術(shù)語(yǔ)"最高級(jí)驅(qū)動(dòng)程序"來(lái)描述第一個(gè)收到該IRP的驅(qū)動(dòng)程序。

通常,只有給定設(shè)備的最高級(jí)驅(qū)動(dòng)程序才能確切地知道它執(zhí)行在一個(gè)非任意線程的上下文中。這是因?yàn)轵?qū)動(dòng)程序派遣例程通常把請(qǐng)求放入隊(duì)列后立即返回調(diào)用者。之后通過(guò)回調(diào)函數(shù),請(qǐng)求被提出隊(duì)列并下傳到低級(jí)驅(qū)動(dòng)程序。一旦派遣例程掛起某個(gè)請(qǐng)求,所有對(duì)該請(qǐng)求的后期處理必須發(fā)生在任意線程上下文中。

解釋完線程上下文后,我們可以陳訴出關(guān)于線程阻塞的簡(jiǎn)單規(guī)則:

當(dāng)我們處理某個(gè)請(qǐng)求時(shí),僅能阻塞產(chǎn)生該請(qǐng)求的線程。

 


中斷請(qǐng)求級(jí)
==========

Windows NT為每個(gè)硬件中斷和少數(shù)軟件事件賦予了一個(gè)優(yōu)先級(jí),即中斷請(qǐng)求級(jí)(interrupt request level - IRQL)。IRQL為CPU上的活動(dòng)提供了同步方法,它基于下面規(guī)則:

一旦某CPU執(zhí)行在高于PASSIVE_LEVEL的IRQL上時(shí),該CPU上的活動(dòng)僅能被擁有更高IRQL的活動(dòng)搶先。

圖4 -1顯示了x86平臺(tái)上的IRQL值范圍。(通常,這個(gè)IRQL數(shù)值要取決于你所面對(duì)的平臺(tái)) 用戶(hù)模式程序執(zhí)行在PASSIVE_LEVEL上,可以被任何執(zhí)行在高于該IRQL上的活動(dòng)搶先。許多設(shè)備驅(qū)動(dòng)程序例程也執(zhí)行在 PASSIVE_LEVEL上。第二章中討論的DriverEntry和AddDevice例程就屬于這類(lèi),大部分IRP派遣例程也屬于這類(lèi)。

某些公共驅(qū)動(dòng)程序例程執(zhí)行在DISPATCH_LEVEL上,而DISPATCH_LEVEL級(jí)要比PASSIVE_LEVEL級(jí)高。這些公共例程包括 StartIo例程,DPC(推遲過(guò)程調(diào)用)例程,和其它一些例程。這些例程的共同特點(diǎn)是,它們都需要訪問(wèn)設(shè)備對(duì)象和設(shè)備擴(kuò)展中的某些域,它們都不受派遣例程的干擾或互相干擾。當(dāng)任何一個(gè)這樣的例程運(yùn)行時(shí),上面陳述的規(guī)則可以保證它們不被任何驅(qū)動(dòng)程序的派遣例程搶先,因?yàn)榕汕怖瘫旧韴?zhí)行在更低級(jí)的 IRQL上。另外,它們也不會(huì)被同類(lèi)例程搶先,因?yàn)槟切├踢\(yùn)行的IRQL與它們自己的相同。只有擁有更高IRQL的活動(dòng)才能搶先它們。

注意

派遣例程(Dispatch routine)和DISPATCH_LEVEL級(jí)名稱(chēng)類(lèi)似。之所以稱(chēng)做派遣例程是因?yàn)镮/O管理器向這些函數(shù)派遣I/O請(qǐng)求。而存在派遣級(jí) (DISPATCH_LEVEL)這個(gè)名稱(chēng)是因?yàn)閮?nèi)核線程派遣器運(yùn)行在這個(gè)IRQL上,它決定下一次該執(zhí)行哪個(gè)線程。(現(xiàn)在,線程調(diào)度程序通常運(yùn)行在 SYNCH_LEVEL級(jí)上)

----------------
HIGH_LEVEL    31
POWER_LEVEL   30
IPI_LEVEL     29
CLOCK2_LEVEL  28
CLOCK1_LEVEL  28
PROFILE_LEVEL 27
    ...
DISPATCH_LEVEL 2
APC_LEVEL      1
PASSIVE_LEVEL  0
----------------

在DISPATCH_LEVEL 級(jí)和PROFILE_LEVEL級(jí)之間是各種硬件中斷級(jí)。通常,每個(gè)有中斷能力的設(shè)備都有一個(gè)IRQL,它定義了該設(shè)備的中斷優(yōu)先級(jí)別。WDM驅(qū)動(dòng)程序只有在收到一個(gè)副功能碼為IRP_MN_START_DEVICE的IRP_MJ_PNP請(qǐng)求后,才能確定其設(shè)備的IRQL。設(shè)備的配置信息作為參數(shù)傳遞給該請(qǐng)求,而設(shè)備的IRQL就包含在這個(gè)配置信息中。我們通常把設(shè)備的中斷級(jí)稱(chēng)為設(shè)備IRQL,或DIRQL。

其它IRQL級(jí)的含義有時(shí)需要依靠具體的CPU結(jié)構(gòu)。這些IRQL通常僅被Windows NT內(nèi)核內(nèi)部使用,因此它們的含義與設(shè)備驅(qū)動(dòng)程序的編寫(xiě)不是特別密切相關(guān)。例如,我將要在本章后面詳細(xì)討論的APC_LEVEL,當(dāng)系統(tǒng)在該級(jí)上為某線程調(diào)度APC(異步過(guò)程調(diào)用)例程時(shí)不會(huì)被同一CPU上的其它線程所干擾。在HIGH_LEVEL級(jí)上系統(tǒng)可以執(zhí)行一些特殊操作,如系統(tǒng)休眠前的內(nèi)存快照、處理bug check、處理假中斷,等等。

IRQL的變化
==========

為了演示IRQL的重要性,參見(jiàn)圖 4-2,該圖顯示了發(fā)生在單CPU上的一系列事件。在時(shí)間序列的開(kāi)始處,CPU執(zhí)行在PASSIVE_LEVEL級(jí)上。在t1時(shí)刻,一個(gè)中斷到達(dá),它的服務(wù)例程執(zhí)行在DIRQL1上,該級(jí)是在DISPATCH_LEVEL和PROFILE_LEVEL之間的某個(gè)DIRQL。在t2時(shí)刻,另一個(gè)中斷到達(dá),它的服務(wù)例程執(zhí)行在DIRQL2上,比DIRQL1低一級(jí)。我們討論過(guò)搶先規(guī)則,所以CPU將繼續(xù)服務(wù)于第一個(gè)中斷。當(dāng)?shù)谝粋€(gè)中斷服務(wù)例程在t3時(shí)刻完成時(shí),該中斷服務(wù)程序可能會(huì)請(qǐng)求一個(gè)DPC。而DPC例程是執(zhí)行在DISPATCH_LEVEL上。所以當(dāng)前存在的未執(zhí)行的最高優(yōu)先級(jí)的活動(dòng)就是第二個(gè)中斷的服務(wù)例程,所以系統(tǒng)接著執(zhí)行第二個(gè)中斷的服務(wù)例程。這個(gè)例程在t4時(shí)刻結(jié)束,假設(shè)這之后再?zèng)]有其它中斷發(fā)生,CPU將降到DISPATCH_LEVEL 級(jí)上執(zhí)行第一個(gè)中斷的DPC例程。當(dāng)DPC例程在t5時(shí)刻完成后,IRQL又落回到原來(lái)的PASSIVE_LEVEL級(jí)。

基本同步規(guī)則
============

遵循下面規(guī)則,你可以利用IRQL的同步效果:

所有對(duì)共享數(shù)據(jù)的訪問(wèn)都應(yīng)該在同一(提升的)IRQL上進(jìn)行。

換句話說(shuō),不論何時(shí)何地,如果你的代碼訪問(wèn)的數(shù)據(jù)對(duì)象被其它代碼共享,那么你應(yīng)該使你的代碼執(zhí)行在高于PASSIVE_LEVEL的級(jí)上。一旦越過(guò) PASSIVE_LEVEL級(jí),操作系統(tǒng)將不允許同IRQL的活動(dòng)相互搶先,從而防止了潛在的沖突。然而這個(gè)規(guī)則不足以保護(hù)多處理器機(jī)器上的數(shù)據(jù),在多處理器機(jī)器中你還需要另外的防護(hù)措施--自旋鎖(spin lock)。如果你僅關(guān)心單CPU上的操作,那么使用IRQL就可以解決所有同步問(wèn)題。但事實(shí)上,所有WDM驅(qū)動(dòng)程序都必須設(shè)計(jì)成能夠運(yùn)行在多處理器的系統(tǒng)上。

IRQL與線程優(yōu)先級(jí)
================

線程優(yōu)先級(jí)是與IRQL非常不同的概念。線程優(yōu)先級(jí)控制著線程調(diào)度器的調(diào)度動(dòng)作,決定何時(shí)搶先運(yùn)行線程以及下一次運(yùn)行什么線程。然而,當(dāng)IRQL級(jí)高于或等于DISPATCH_LEVEL級(jí)時(shí)線程切換停止,無(wú)論當(dāng)前活動(dòng)的是什么線程都將保持活動(dòng)狀態(tài)直到IRQL降到DISPATCH_LEVEL級(jí)之下。而此時(shí)的"優(yōu)先級(jí)"僅指IRQL本身,由它控制到底哪個(gè)活動(dòng)該執(zhí)行,而不是該切換到哪個(gè)線程的上下文。


IRQL和分頁(yè)
==========

執(zhí)行在提升的IRQL級(jí)上的一個(gè)后果是,系統(tǒng)將不能處理頁(yè)故障(系統(tǒng)在APC級(jí)處理頁(yè)故障)。這意味著:

執(zhí)行在高于或等于DISPATCH_LEVEL級(jí)上的代碼絕對(duì)不能造成頁(yè)故障。

這也意味著執(zhí)行在高于或等于DISPATCH_LEVEL級(jí)上的代碼必須存在于非分頁(yè)內(nèi)存中。此外,所有這些代碼要訪問(wèn)的數(shù)據(jù)也必須存在于非分頁(yè)內(nèi)存中。最后,隨著IRQL的提升,你能使用的內(nèi)核模式支持例程將會(huì)越來(lái)越少。

DDK文檔中明確指出支持例程的IRQL限定。例如,KeWaitForSingleObject例程有兩個(gè)限定:

調(diào)用者必須運(yùn)行在低于或等于DISPATCH_LEVEL級(jí)上。
如果調(diào)用中指定了非0的超時(shí),那么調(diào)用者必須嚴(yán)格地運(yùn)行在低于DISPATCH_LEVEL的IRQL上。
上面這兩行想要說(shuō)明的是:如果KeWaitForSingleObject真的被阻塞了指定長(zhǎng)的時(shí)間(你指定的非0超時(shí)),那么你必定運(yùn)行在低于 DISPATCH_LEVEL的IRQL上,因?yàn)橹挥性谶@樣的IRQL上線程阻塞才是允許的。如果你所做的一切就是為了檢測(cè)事件是否進(jìn)入信號(hào)態(tài),則可以執(zhí)行在DISPATCH_LEVEL級(jí)上。但你不能在ISR或其它運(yùn)行在高于DISPATCH_LEVEL級(jí)上的例程中調(diào)用 KeWaitForSingleObject例程。

 

內(nèi)核同步對(duì)象
============

Windows NT提供了五種內(nèi)核同步對(duì)象(Kernel Dispatcher Object),你可以用它們控制非任意線程(普通線程)的流程。表4-1列出了這些內(nèi)核同步對(duì)象的類(lèi)型及它們的用途。在任何時(shí)刻,任何對(duì)象都處于兩種狀態(tài)中的一種:信號(hào)態(tài)或非信號(hào)態(tài)。有時(shí),當(dāng)代碼運(yùn)行在某個(gè)線程的上下文中時(shí),它可以阻塞這個(gè)線程的執(zhí)行,調(diào)用KeWaitForSingleObject或 KeWaitForMultipleObjects函數(shù)可以使代碼(以及背景線程)在一個(gè)或多個(gè)同步對(duì)象上等待,等待它們進(jìn)入信號(hào)態(tài)。內(nèi)核為初始化和控制這些對(duì)象的狀態(tài)提供了例程。

表4-1. 內(nèi)核同步對(duì)象
-----------------------------------------------------------------------------
對(duì)象               數(shù)據(jù)類(lèi)型                       描述
-----------------------------------------------------------------------------
Event(事件)        KEVENT         阻塞一個(gè)線程直到其它線程檢測(cè)到某事件發(fā)生 
Semaphore(信號(hào)燈)  KSEMAPHORE     與事件對(duì)象相似,但可以滿(mǎn)足任意數(shù)量的等待 
Mutex(互斥)        KMUTEX         執(zhí)行到關(guān)鍵代碼段時(shí),禁止其它線程執(zhí)行該代碼段 
Timer(定時(shí)器)      KTIMER         推遲線程執(zhí)行一段時(shí)期
Thread(線程)       KTHREAD        阻塞一個(gè)線程直到另一個(gè)線程結(jié)束
------------------------------------------------------------------------------
在下幾段中,我將描述如何使用內(nèi)核同步對(duì)象。我將從何時(shí)可以調(diào)用等待原語(yǔ)阻塞線程開(kāi)始講起,然后討論用于每種對(duì)象的支持例程。最后討論與線程警惕(thread alert)和提交APC(異步過(guò)程調(diào)用)相關(guān)的概念。


何時(shí)阻塞和怎樣阻塞一個(gè)線程
==========================

為了理解WDM驅(qū)動(dòng)程序何時(shí)以及如何利用內(nèi)核同步對(duì)象阻塞一個(gè)線程,你必須先對(duì)線程有一些基本了解。通常,如果在線程執(zhí)行時(shí)發(fā)生了軟件或硬件中斷,那么在內(nèi)核處理中斷期間,該線程仍然是"當(dāng)前"線程。而內(nèi)核模式代碼執(zhí)行時(shí)所在的上下文環(huán)境就是指這個(gè)"當(dāng)前"線程的上下文。為了響應(yīng)各種中斷,Windows NT調(diào)度器可能會(huì)切換線程,這樣,另一個(gè)線程將成為新的"當(dāng)前"線程。

術(shù)語(yǔ)"任意線程上下文(arbitrary thread context)"和"非任意線程上下文(nonarbitrary thread context)"用于精確描述驅(qū)動(dòng)程序例程執(zhí)行時(shí)所處于的上下文種類(lèi)。如果我們知道程序正處于初始化I/O請(qǐng)求線程的上下文中,則該上下文不是任意上下文。然而,在大部分時(shí)間里,WDM驅(qū)動(dòng)程序無(wú)法知道這個(gè)事實(shí),因?yàn)榭刂颇膫€(gè)線程應(yīng)該激活的機(jī)會(huì)通常都是在中斷發(fā)生時(shí)。當(dāng)應(yīng)用程序發(fā)出I/O請(qǐng)求時(shí),將產(chǎn)生一個(gè)從用戶(hù)模式到內(nèi)核模式的轉(zhuǎn)換,而創(chuàng)建并發(fā)送該IRP的I/O管理器例程將繼續(xù)運(yùn)行在非任意線程的上下文中。我們用術(shù)語(yǔ)"最高級(jí)驅(qū)動(dòng)程序"來(lái)描述第一個(gè)收到該IRP的驅(qū)動(dòng)程序。

通常,只有給定設(shè)備的最高級(jí)驅(qū)動(dòng)程序才能確切地知道它執(zhí)行在一個(gè)非任意線程的上下文中。這是因?yàn)轵?qū)動(dòng)程序派遣例程通常把請(qǐng)求放入隊(duì)列后立即返回調(diào)用者。之后通過(guò)回調(diào)函數(shù),請(qǐng)求被提出隊(duì)列并下傳到低級(jí)驅(qū)動(dòng)程序。一旦派遣例程掛起某個(gè)請(qǐng)求,所有對(duì)該請(qǐng)求的后期處理必須發(fā)生在任意線程上下文中。

解釋完線程上下文后,我們可以陳訴出關(guān)于線程阻塞的簡(jiǎn)單規(guī)則:

當(dāng)我處理某個(gè)請(qǐng)求時(shí),僅能阻塞產(chǎn)生該請(qǐng)求的線程。

通常,僅有設(shè)備的最高級(jí)驅(qū)動(dòng)程序才能應(yīng)用這個(gè)規(guī)則。但有一個(gè)重要的例外,IRP_MN_START_DEVICE請(qǐng)求,所有驅(qū)動(dòng)程序都以同步方式處理這個(gè)請(qǐng)求。即驅(qū)動(dòng)程序不排隊(duì)或掛起該類(lèi)請(qǐng)求。當(dāng)你收到這種請(qǐng)求時(shí),你可以直接從堆棧中找到請(qǐng)求的發(fā)起者。正如我在第六章中講到的,處理這種請(qǐng)求時(shí)你必須阻塞那個(gè)線程。

下面規(guī)則表明在提升的IRQL級(jí)上不可能發(fā)生線程切換:

執(zhí)行在高于或等于DISPATCH_LEVEL級(jí)上的代碼不能阻塞線程。

這個(gè)規(guī)則表明你只能在DriverEntry函數(shù)、AddDevice函數(shù),或驅(qū)動(dòng)程序的派遣函數(shù)中阻塞當(dāng)前線程。因?yàn)檫@些函數(shù)都執(zhí)行在 PASSIVE_LEVEL級(jí)上。沒(méi)有必要在DriverEntry或AddDevice函數(shù)中阻塞當(dāng)前線程,因?yàn)檫@些函數(shù)的工作僅僅是初始化一些數(shù)據(jù)結(jié)構(gòu)。

在單同步對(duì)象上等待
==================

你可以按下面方法調(diào)用KeWaitForSingleObject函數(shù):

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
LARGE_INTEGER timeout;
NTSTATUS status = KeWaitForSingleObject(object, WaitReason, WaitMode, Alertable, &timeout);
 
ASSERT語(yǔ)句指出必須在低于或等于DISPATCH_LEVEL級(jí)上調(diào)用該例程。

在這個(gè)調(diào)用中,object指向你要等待的對(duì)象。注意該參數(shù)的類(lèi)型是PVOID,它應(yīng)該指向一個(gè)表4-1中列出的同步對(duì)象。該對(duì)象必須在非分頁(yè)內(nèi)存中,例如,在設(shè)備擴(kuò)展中或其它從非分頁(yè)內(nèi)存池中分配的數(shù)據(jù)區(qū)。在大部分情況下,執(zhí)行堆棧可以被認(rèn)為是非分頁(yè)的。

WaitReason 是一個(gè)純粹建議性的值,它是KWAIT_REASON枚舉類(lèi)型。實(shí)際上,除非你指定了WrQueue參數(shù),否則任何內(nèi)核代碼都不關(guān)心此值。線程阻塞的原因被保存到一個(gè)不透明的數(shù)據(jù)結(jié)構(gòu)中,如果你了解這個(gè)數(shù)據(jù)結(jié)構(gòu),那么在調(diào)試某種死鎖時(shí),你也許會(huì)從這個(gè)原因代碼中獲得一些線索。通常,驅(qū)動(dòng)程序應(yīng)把該參數(shù)指定為Executive,代表無(wú)原因。

WaitMode是MODE枚舉類(lèi)型,該枚舉類(lèi)型僅有兩個(gè)值:KernelMode和UserMode。

Alertable 是一個(gè)布爾類(lèi)型的值。它不同于WaitReason,這個(gè)參數(shù)以另一種方式影響系統(tǒng)行為,它決定等待是否可以提前終止以提交一個(gè)APC。如果等待發(fā)生在用戶(hù)模式中,那么內(nèi)存管理器就可以把線程的內(nèi)核模式堆棧換出。如果驅(qū)動(dòng)程序以自動(dòng)變量(在堆棧中)形式創(chuàng)建事件對(duì)象,并且某個(gè)線程又在提升的IRQL級(jí)上調(diào)用了KeSetEvent,而此時(shí)該事件對(duì)象剛好又被換出內(nèi)存,結(jié)果將產(chǎn)生一個(gè)bug check。所以我們應(yīng)該總把a(bǔ)lertable參數(shù)指定為FALSE,即在內(nèi)核模式中等待。

最后一個(gè)參數(shù)&timeout是一個(gè) 64位超時(shí)值的地址,單位為100納秒。正數(shù)的超時(shí)表示一個(gè)從1601年1月1日起的絕對(duì)時(shí)間。調(diào)用KeQuerySystemTime函數(shù)可以獲得當(dāng)前系統(tǒng)時(shí)間。負(fù)數(shù)代表相對(duì)于當(dāng)前時(shí)間的時(shí)間間隔。如果你指定了絕對(duì)超時(shí),那么系統(tǒng)時(shí)鐘的改變也將影響到你的超時(shí)時(shí)間。如果系統(tǒng)時(shí)間越過(guò)你指定的絕對(duì)時(shí)間,那么永遠(yuǎn)都不會(huì)超時(shí)。相反,如果你指定相對(duì)超時(shí),那么你經(jīng)過(guò)的超時(shí)時(shí)間將不受系統(tǒng)時(shí)鐘改變的影響。

指定0超時(shí)將使 KeWaitForSingleObject函數(shù)立即返回,返回的狀態(tài)代碼指出對(duì)象是否處于信號(hào)態(tài)。如果你的代碼執(zhí)行在DISPATCH_LEVEL級(jí)上,則必須指定0超時(shí),因?yàn)樵谶@個(gè)IRQL上不允許阻塞。每個(gè)內(nèi)核同步對(duì)象都提供一組KeReadStateXxx服務(wù)函數(shù),使用這些函數(shù)可以直接獲得對(duì)象的狀態(tài)。然而,取對(duì)象狀態(tài)與0超時(shí)等待不完全等價(jià):當(dāng)KeWaitForSingleObject發(fā)現(xiàn)等待被滿(mǎn)足后,它執(zhí)行特殊對(duì)象要求的附加動(dòng)作。相比之下,取對(duì)象狀態(tài)不執(zhí)行任何附加動(dòng)作,即使對(duì)象已經(jīng)處于信號(hào)態(tài)。

超時(shí)參數(shù)也可以指定為NULL指針,這代表無(wú)限期等待。

該函數(shù)的返回值指出幾種可能的結(jié)果。STATUS_SUCCESS結(jié)果是你所希望的,表示等待被滿(mǎn)足。即在你調(diào)用 KeWaitForSingleObject時(shí),對(duì)象或者已經(jīng)進(jìn)入信號(hào)態(tài),或者后來(lái)進(jìn)入信號(hào)態(tài)。如果等待以第二種情況滿(mǎn)足,則有必要在同步對(duì)象上執(zhí)行附加動(dòng)作。當(dāng)然,這個(gè)附加動(dòng)作還要參考對(duì)象的類(lèi)型,我將在后面討論具體對(duì)象類(lèi)型時(shí)再解釋這一點(diǎn)。(例如,一個(gè)同步類(lèi)型的事件在你的等待滿(mǎn)足后需要重置該事件)

返回值STATUS_TIMEOUT指出在指定的超時(shí)期限內(nèi)對(duì)象未進(jìn)入信號(hào)態(tài)。如果指定0超時(shí),則函數(shù)將立即返回。返回代碼為 STATUS_TIMEOUT,代表對(duì)象處于非信號(hào)態(tài),返回代碼為STATUS_SUCCESS,代表對(duì)象處于信號(hào)態(tài)。如果指定NULL超時(shí),則不可能有返回值。

其它兩個(gè)返回值STATUS_ALERTED和STATUS_USER_APC表示等待提前終止,對(duì)象未進(jìn)入信號(hào)態(tài)。原因是線程接收到一個(gè)警惕(alert)或一個(gè)用戶(hù)模式的APC

在多同步對(duì)象上等待
==================

KeWaitForMultipleObjects函數(shù)用于同時(shí)等待一個(gè)或多個(gè)同步對(duì)象。該函數(shù)調(diào)用方式如下:

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
LARGE_INTEGER timeout;
NTSTATUS status = KeWaitForMultipleObjects(count,
        objects,
        WaitType,
        WaitReason,
        WaitMode,
        Alertable,
        &timeout,
        waitblocks);
 

在這里,objects指向一個(gè)指針數(shù)組,每個(gè)數(shù)組元素指向一個(gè)同步對(duì)象,count是數(shù)組中指針的個(gè)數(shù)。count必須小于或等于 MAXIMUM_WAIT_OBJECTS值(當(dāng)前為64)。這個(gè)數(shù)組和它所指向的所有對(duì)象都必須在非分頁(yè)內(nèi)存中。WaitType是枚舉類(lèi)型,其值可以為WaitAll或WaitAny,它指出你是等到所有對(duì)象都進(jìn)入信號(hào)態(tài),還是只要有一個(gè)對(duì)象進(jìn)入信號(hào)態(tài)就可以。

waitblocks參數(shù)指向一個(gè)KWAIT_BLOCK結(jié)構(gòu)數(shù)組,內(nèi)核用這個(gè)結(jié)構(gòu)數(shù)組管理等待操作。你不需要初始化這些結(jié)構(gòu),內(nèi)核僅需要知道這個(gè)結(jié)構(gòu)數(shù)組在哪里,內(nèi)核用它來(lái)記錄每個(gè)對(duì)象在等待中的狀態(tài)。如果你僅需要等待小數(shù)量的對(duì)象(不超過(guò)THREAD_WAIT_OBJECTS,該值當(dāng)前為3),你可以把該參數(shù)指定為 NULL。如果該參數(shù)為NULL,KeWaitForMultipleObjects將使用線程對(duì)象中預(yù)分配的等待塊數(shù)組。如果你等待的對(duì)象數(shù)超過(guò) THREAD_WAIT_OBJECTS,你必須提供一塊長(zhǎng)度至少為count * sizeof(KWAIT_BLOCK)的非分頁(yè)內(nèi)存。

其余參數(shù)與KeWaitForSingleObject中的對(duì)應(yīng)參數(shù)作用相同,而且大部分返回碼也有相同的含義。

如果你指定了WaitAll,則返回值STATUS_SUCCESS表示等待的所有對(duì)象都進(jìn)入了信號(hào)態(tài)。如果你指定了WaitAny,則返回值在數(shù)值上等于進(jìn)入信號(hào)態(tài)的對(duì)象在objects數(shù)組中的索引。如果碰巧有多個(gè)對(duì)象進(jìn)入了信號(hào)態(tài),則該值僅代表其中的一個(gè),可能是第一個(gè)也可能是其它。你可以認(rèn)為該值等于STATUS_WAIT_0加上數(shù)組索引。你可以先用NT_SUCCESS測(cè)試返回碼,然后再?gòu)钠渲刑崛?shù)組索引:

NTSTATUS status = KeWaitForMultipleObjects(...);
if (NT_SUCCESS(status))
{
  ULONG iSignalled = (ULONG) status - (ULONG) STATUS_WAIT_0;
  ...
}
 

如果KeWaitForMultipleObjects返回成功代碼,它也將執(zhí)行等待被滿(mǎn)足的那個(gè)對(duì)象的附加動(dòng)作。如果多個(gè)對(duì)象同時(shí)進(jìn)入信號(hào)態(tài)而你指定的WaitType參數(shù)為WaitAny,那么該函數(shù)僅執(zhí)行返回值指定對(duì)象的附加動(dòng)作。

內(nèi)核事件
========

表4-2列出了用于處理內(nèi)核事件的服務(wù)函數(shù)。為了初始化一個(gè)事件對(duì)象,我們首先應(yīng)該為其分配非分頁(yè)存儲(chǔ),然后調(diào)用KeInitializeEvent:

ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);
KeInitializeEvent(event, EventType, initialstate);
 
event 是事件對(duì)象的地址。EventType是一個(gè)枚舉值,可以為NotificationEvent或SynchronizationEvent。通知事件 (notification event)有這樣的特性,當(dāng)它進(jìn)入信號(hào)態(tài)后,它將一直處于信號(hào)態(tài)直到你明確地把它重置為非信號(hào)態(tài)。此外,當(dāng)通知事件進(jìn)入信號(hào)態(tài)后,所有在該事件上等待的線程都被釋放。這與用戶(hù)模式中的手動(dòng)重置事件相似。而對(duì)于同步事件(synchronization event),只要有一個(gè)線程被釋放,該事件就被重置為非信號(hào)態(tài)。這又與用戶(hù)模式中的自動(dòng)重置事件相同。而KeWaitXxx函數(shù)在同步事件對(duì)象上執(zhí)行的附加動(dòng)作就是把它重置為非信號(hào)態(tài)。最后的參數(shù)initialstate是布爾量,為T(mén)RUE表示事件的初始狀態(tài)為信號(hào)態(tài),為FALSE表示事件的初始狀態(tài)為非信號(hào)態(tài)。

表4-2. 用于內(nèi)核事件對(duì)象的服務(wù)函數(shù)
----------------------------------------------------------------
服務(wù)函數(shù)                        描述
----------------------------------------------------------------
KeClearEvent       把事件設(shè)置為非信號(hào)態(tài),不報(bào)告以前的狀態(tài)
KeInitializeEvent  初始化事件對(duì)象
KeReadStateEvent   取事件的當(dāng)前狀態(tài)
KeResetEvent       把事件設(shè)置為非信號(hào)態(tài),返回以前的狀態(tài)
KeSetEvent         把事件設(shè)置為信號(hào)態(tài),返回以前的狀態(tài)
----------------------------------------------------------------
調(diào)用KeSetEvent函數(shù)可以把事件置為信號(hào)態(tài):

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
LONG wassignalled = KeSetEvent(event, boost, wait);
 

在上面代碼中,ASSERT語(yǔ)句強(qiáng)制你必須在低于或等于DISPATCH_LEVEL級(jí)上調(diào)用該函數(shù)。event參數(shù)指向一個(gè)事件對(duì)象,boost值用于提升等待線程的優(yōu)先級(jí)。wait參數(shù)的解釋見(jiàn)文字框"KeSetEvent的第三個(gè)參數(shù)",WDM驅(qū)動(dòng)程序幾乎從不把wait參數(shù)指定為T(mén)RUE。如果該事件已經(jīng)處于信號(hào)態(tài),則該函數(shù)返回非0值。如果該事件處于非信號(hào)態(tài),則該函數(shù)返回0。

多任務(wù)調(diào)度器需要人為地提升等I/O操作或同步對(duì)象的線程的優(yōu)先級(jí),以避免餓死長(zhǎng)時(shí)間等待的線程。這是因?yàn)楸蛔枞木€程往往是放棄自己的時(shí)間片并且不再要求獲得CPU,但只要這些線程獲得了比其它線程更高的優(yōu)先級(jí),或者其它同一優(yōu)先級(jí)的線程用完了自己的時(shí)間片,它們就可以恢復(fù)執(zhí)行。注意,正處于自己時(shí)間片中的線程不能被阻塞。

用于提升阻塞線程優(yōu)先級(jí)的boost值不太好選擇。一個(gè)較好的笨方法是指定IO_NO_INCREMENT值,當(dāng)然,如果你有更好的值,可以不用這個(gè)值。如果事件喚醒的是一個(gè)處理時(shí)間敏感數(shù)據(jù)流的線程(如聲卡驅(qū)動(dòng)程序),那么應(yīng)該使用適合那種設(shè)備的boost值(如IO_SOUND_INCREMENT)。重要的是,不要為一個(gè)愚蠢的理由去提高等待者的優(yōu)先級(jí)。例如,如果你要同步處理一個(gè)IRP_MJ_PNP請(qǐng)求,那么在你要停下來(lái)等待低級(jí)驅(qū)動(dòng)程序處理完該IRP時(shí),你的完成例程應(yīng)調(diào)用KeSetEvent。由于PnP請(qǐng)求對(duì)于處理器沒(méi)有特殊要求并且也不經(jīng)常發(fā)生,所以即使是聲卡驅(qū)動(dòng)程序也也應(yīng)該把boost參數(shù)指定為 IO_NO_INCREMENT。


使用完成例程
============


通常,你需要知道發(fā)往低級(jí)驅(qū)動(dòng)程序的I/O請(qǐng)求的結(jié)果。為了了解請(qǐng)求的結(jié)果,你需要安裝一個(gè)完成例程,調(diào)用IoSetCompletionRoutine函數(shù):

IoSetCompletionRoutine(Irp,
         CompletionRoutine,
         context,
         InvokeOnSuccess,
         InvokeOnError,
         InvokeOnCancel);
 

Irp就是你要了解其完成的請(qǐng)求。CompletionRoutine是被調(diào)用的完成例程的地址,context是任何一個(gè)指針長(zhǎng)度的值,將作為完成例程的參數(shù)。InvokeOnXxx參數(shù)是布爾值,它們指出在三種不同的環(huán)境中是否需要調(diào)用完成例程:

InvokeOnSuccess 你希望完成例程在IRP以成功狀態(tài)(返回的狀態(tài)代碼通過(guò)了NT_SUCCESS測(cè)試)完成時(shí)被調(diào)用。
InvokeOnError 你希望完成例程在IRP以失敗狀態(tài)(返回的狀態(tài)代碼未通過(guò)了NT_SUCCESS測(cè)試)完成時(shí)被調(diào)用。
InvokeOnCancel 如果驅(qū)動(dòng)程序在完成IRP前調(diào)用了IoCancelIrp例程,你希望在此時(shí)調(diào)用完成例程。IoCancelIrp將在IRP中設(shè)置取消標(biāo)志,該標(biāo)志也是調(diào)用完成例程的條件。一個(gè)被取消的IRP最終將以STATUS_CANCELLED(該狀態(tài)代碼不能通過(guò)NT_SUCCESS測(cè)試)或任何其它狀態(tài)完成。如果IRP以失敗方式完成,并且你也指定了InvokeOnError參數(shù),那么是InvokeOnError本身導(dǎo)致了完成例程的調(diào)用。相反,如果 IRP以成功方式完成,并且你也指定了InvokeOnSuccess參數(shù),那么是InvokeOnSuccess本身導(dǎo)致了完成例程的調(diào)用。在這兩種情況中,InvokeOnCancel參數(shù)將是多余的。如果你省去InvokeOnSuccess和InvokeOnError中的任何一個(gè)參數(shù)或兩個(gè)都省去,并且IRP也被設(shè)置了取消標(biāo)志,那么InvokeOnCancel參數(shù)將導(dǎo)致完成例程的調(diào)用。
這三個(gè)標(biāo)志中至少有一個(gè)設(shè)置為T(mén)RUE。注意,IoSetCompletionRoutine是一個(gè)宏,所以你應(yīng)避免使用有副作用的參數(shù)。這三個(gè)標(biāo)志參數(shù)和一個(gè)函數(shù)指針參數(shù)在宏中被引用了兩次。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多

    欧美日韩少妇精品专区性色| 99精品国产自在现线观看| 日本丁香婷婷欧美激情| 人妻内射在线二区一区| 久草视频在线视频在线观看| 丰满的人妻一区二区三区| 亚洲国产日韩欧美三级| 日韩欧美综合在线播放| 欧美精品亚洲精品日韩精品| 国产精品免费自拍视频| 欧美日韩国产福利在线观看| 五月情婷婷综合激情综合狠狠| 丰满人妻熟妇乱又乱精品古代 | 国产精品大秀视频日韩精品| 91亚洲熟女少妇在线观看| 91在线播放在线播放观看| 日本91在线观看视频| 四十女人口红哪个色好看| 国产极品粉嫩尤物一区二区| 婷婷色国产精品视频一区| 人妻乱近亲奸中文字幕| 午夜日韩在线观看视频| 中文字幕一区二区三区大片| 欧美人妻免费一区二区三区| 国产伦精品一一区二区三区高清版 | 一区二区三区18禁看| 中文字幕人妻av不卡| 精品偷拍一区二区三区| 日本不卡在线视频中文国产| 欧美黑人巨大一区二区三区| 高跟丝袜av在线一区二区三区| 亚洲一级在线免费观看| 国产伦精品一一区二区三区高清版 | 老司机亚洲精品一区二区| 午夜精品久久久99热连载| 久久这里只有精品中文字幕| 亚洲精品成人午夜久久| 少妇特黄av一区二区三区| 色婷婷国产熟妇人妻露脸| 色婷婷日本视频在线观看| 在线观看视频国产你懂的|