IRP IO_STACK_LOCATION 《寒江独钓》内核学习笔记(1)
在學(xué)習(xí)內(nèi)核過濾驅(qū)動(dòng)的過程中,遇到了大量的涉及IRP操作的代碼,這里有必要對(duì)IRP的數(shù)據(jù)結(jié)構(gòu)和與之相關(guān)的API函數(shù)做一下筆記。
?
1. 相關(guān)閱讀資料
《深入解析 windows 操作系統(tǒng)(第4版,中文版)》 --- 9章
《windows driver kit 幫助文檔》
http://support.microsoft.com/kb/115758/zh-cn? IRP 結(jié)構(gòu)中各地址字段的含義
http://www.programlife.net/io_stack_location-irp.html??? 代碼瘋子對(duì)IRP的研究
?
?
?
?
2. IRP的數(shù)據(jù)結(jié)構(gòu)
IRP是一個(gè)數(shù)據(jù)結(jié)構(gòu),其中包含了用來描述一個(gè)IO請(qǐng)求的完整信息。
IO管理器創(chuàng)建一個(gè)IRP來代表一個(gè)IO操作,并且將該IRP傳遞給正確的驅(qū)動(dòng)程序,當(dāng)此IO操作完成時(shí)再處理該請(qǐng)求包。相對(duì)的,驅(qū)動(dòng)程序(上層的虛擬設(shè)備驅(qū)動(dòng)或者底層的真實(shí)設(shè)備驅(qū)動(dòng))接收一個(gè)IRP,執(zhí)行該IRP指定的操作,然后將IRP傳回給IO管理器,告訴它,該操作已經(jīng)完成,或者應(yīng)該傳給另一個(gè)驅(qū)動(dòng)以進(jìn)行進(jìn)一步處理。
?
談到IRP,IRP是個(gè)總的概念,本質(zhì)上IRP由IRP Header和IRP Sub-Request組成
從數(shù)據(jù)結(jié)構(gòu)的角度上來說,其實(shí)數(shù)據(jù)結(jié)構(gòu) IRP 只是"I/O 請(qǐng)求包"IRP的頭部,在 IRP 數(shù)據(jù)結(jié)構(gòu)的后面還有一個(gè)IO_STACK_LOCATION 數(shù)據(jù)結(jié)構(gòu)的數(shù)組,數(shù)組的大小則取決于 IRP 數(shù)據(jù)結(jié)構(gòu)中的StackCount(我們之后會(huì)詳細(xì)分析),其數(shù)值來自設(shè)備堆棧中頂層設(shè)備對(duì)象的 StackSize 字段。
這樣,就在 IRP 中為目標(biāo)設(shè)備對(duì)象設(shè)備堆棧中的每一層即每個(gè)模塊(每個(gè)驅(qū)動(dòng))都準(zhǔn)備好了一個(gè) IO_STACK_LOCATION 數(shù)據(jù)結(jié)構(gòu)。而CurrentLocation,則是用于該數(shù)組的下標(biāo),說明目前是在堆疊中的哪一層,因而正在使用哪一個(gè) IO_STACK_LOCATION 數(shù)據(jù)結(jié)構(gòu)。
?
那這兩個(gè)結(jié)構(gòu)是怎么來的呢?有兩種渠道:
1. 程序員在代碼中手工地創(chuàng)建一個(gè)IRP(廣義的IRP)
PIRP IoAllocateIrp(IN CCHAR StackSize,IN BOOLEAN ChargeQuota);任何內(nèi)核模式程序在創(chuàng)建一個(gè)IRP時(shí),同時(shí)還創(chuàng)建了一個(gè)與之關(guān)聯(lián)的 IO_STACK_LOCATION 結(jié)構(gòu)數(shù)組:數(shù)組中的每個(gè)堆棧單元都對(duì)應(yīng)一個(gè)將處理該IRP的驅(qū)動(dòng)程序,堆棧單元中包含該IRP的類型代碼和參數(shù)信息以及完成函數(shù)的地址。
2. I/O管理器在接收到應(yīng)用層的設(shè)備讀寫請(qǐng)求后,將請(qǐng)求封裝為一個(gè)IRP請(qǐng)求(包括IRP頭部和IRP STACK_LOCATINO數(shù)組)發(fā)往對(duì)應(yīng)的設(shè)備的設(shè)備棧的最頂層的那個(gè)設(shè)備驅(qū)動(dòng)。
?
我們先從IRP的頭結(jié)構(gòu)開始學(xué)起:
下面是WDK上搬下來的解釋,我們來一條一條的學(xué)習(xí)。
IRPtypedef struct _IRP {..PMDL MdlAddress;ULONG Flags;union {struct _IRP *MasterIrp;..PVOID SystemBuffer;} AssociatedIrp;..IO_STATUS_BLOCK IoStatus;KPROCESSOR_MODE RequestorMode;BOOLEAN PendingReturned;..BOOLEAN Cancel;KIRQL CancelIrql;..PDRIVER_CANCEL CancelRoutine;PVOID UserBuffer;union {struct {..union {KDEVICE_QUEUE_ENTRY DeviceQueueEntry;struct {PVOID DriverContext[4];};};..PETHREAD Thread;..LIST_ENTRY ListEntry;..} Overlay;..} Tail; } IRP, *PIRP;MSDN 說IRP是一個(gè)半透明結(jié)構(gòu),開發(fā)者只能訪問其中透明的部分,所以其中的..代表是我們不能訪問的部分,所以我們?cè)诰幊讨谢旧弦膊粫?huì)用到它們,我們集中精力來觀察這些暴露出來的數(shù)據(jù)結(jié)構(gòu)。
?
?
?
1. IRP中的三種緩沖區(qū)
PMDL MdlAddress; union {struct _IRP *MasterIrp;..PVOID SystemBuffer;} AssociatedIrp; PVOID UserBuffer;IRP這個(gè)數(shù)據(jù)結(jié)構(gòu)中有3個(gè)地方可以描述緩沖區(qū)。?
1) irp->AssociatedIrp.SystemBuffer 2) irp->MdlAddress 3) irp->UserBuffer不同的IO類別,IRP的緩沖區(qū)不同。
1) AssociatedIrp.SystemBuffer
一般用于比較簡(jiǎn)單且不追求效率情況下的解決方案 把R3層中的內(nèi)存中的緩沖數(shù)據(jù)拷貝到內(nèi)核空間中。注意,是直接拷貝過來,有的書上會(huì)說這是"直接方式",不過我們要重點(diǎn)記住的是它是直接拷貝過來。
2) MdlAddress
通過構(gòu)造MDL就能實(shí)現(xiàn)這個(gè)R3到R0的地址映射功能。MDL可以翻譯為"內(nèi)存描述符鏈",本質(zhì)上就是一個(gè)指針,從這個(gè)MDL中可以讀出一個(gè)內(nèi)核空間的虛擬地址。這就彌補(bǔ)了UserBuffer的不足,同時(shí)比SystemBuffer的完全拷貝方法要輕量,因?yàn)檫@個(gè)內(nèi)存實(shí)際上還是在老地方,沒有拷貝。
3) UserBuffer
最追求效率的解決方案 R3的緩沖區(qū)地址直接放在UserBuffer里,在內(nèi)核空間中直接訪問。在當(dāng)前進(jìn)程和發(fā)送進(jìn)程一致的情況下,內(nèi)核訪問應(yīng)用層的內(nèi)存空間當(dāng)然是沒錯(cuò)的。但是一 旦內(nèi)核進(jìn)程已經(jīng)切換,這個(gè)訪問就結(jié)束了,訪問UserBuffer當(dāng)然是跳到其他進(jìn)程空間去了(我們還是訪問同一個(gè)地址,但是這個(gè)時(shí)候因?yàn)檫M(jìn)程的上下文切換了,同一個(gè)地址對(duì)應(yīng)的內(nèi)容自然不同了)。因?yàn)樵趙indows中,內(nèi)核空間是所有進(jìn)程共享的,而應(yīng) 用層空間則是各個(gè)進(jìn)程隔離的。當(dāng)然還有一個(gè)更簡(jiǎn)單的做法是把應(yīng)用層的地址空間映射到內(nèi)核空間,這需要在頁表中增加一個(gè)映射。
在驅(qū)動(dòng)在獲取這三種緩沖區(qū)的方法。
//獲得緩沖區(qū) PUCHAR buf = NULL; if(irp->MdlAddress != NULL) {buffer = (PUCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority); } else {buffer = (PUCHAR)irp->UserBuffer; } if(buffer = NULL) {buffer = (PUCHAR)irp->AssociatedIrp.SystemBuffer; }看到這里,就產(chǎn)生另一個(gè)問題了,這三種緩沖區(qū)是操作系統(tǒng)幫我們自動(dòng)填好的嗎?那在什么樣的IRP請(qǐng)求會(huì)使用到不同的緩沖區(qū)類型呢?
這里就要涉及到兩種內(nèi)存訪問方式: 直接方式DO_DIRECT_IO / 非直接方式(緩沖方式)DO_BUFFERD_IO
1) 在buffered(AssociatedIrp.SystemBuffer)方式中,I/O管理器先創(chuàng)建一個(gè)與用戶模式數(shù)據(jù)緩沖區(qū)大小相等的系統(tǒng)緩沖區(qū)。而你的驅(qū)動(dòng)程序?qū)⑹褂眠@個(gè)系統(tǒng)緩沖區(qū)工作。I/O管理器負(fù)責(zé)在系統(tǒng)緩沖區(qū)和用戶模式緩沖區(qū)之間復(fù)制數(shù)據(jù)。
2) 在direct(MdlAddress)方式中,I/O管理器鎖定了包含用戶模式緩沖區(qū)的物理內(nèi)存頁,并創(chuàng)建一個(gè)稱為MDL(內(nèi)存描述符表)的輔助數(shù)據(jù)結(jié)構(gòu)來描述鎖定頁。因此你的驅(qū)動(dòng)程序?qū)⑹褂肕DL工作。
3) 在neither(UserBuffer)方式中,I/O管理器僅簡(jiǎn)單地把用戶模式的虛擬地址傳遞給你。而使用用戶模式地址的驅(qū)動(dòng)程序應(yīng)十分小心。
?
繼續(xù)思考我們之前的問題,在IRP中具體是使用哪種緩沖方式呢?由誰來決定?
答案是在增加設(shè)備的時(shí)候就決定的了。即我們?cè)谛略鲆粋€(gè)設(shè)備的時(shí)候就要決定這個(gè)設(shè)備的緩沖區(qū)讀寫方式。
DRIVER_ADD_DEVICE AddDevice;NTSTATUSAddDevice(__in struct _DRIVER_OBJECT *DriverObject,__in struct _DEVICE_OBJECT *PhysicalDeviceObject ){...}NTSTATUS IoCreateDevice(IN PDRIVER_OBJECT DriverObject,IN ULONG DeviceExtensionSize,IN PUNICODE_STRING DeviceName OPTIONAL,IN DEVICE_TYPE DeviceType,IN ULONG DeviceCharacteristics,IN BOOLEAN Exclusive,OUT PDEVICE_OBJECT *DeviceObject);typedef struct _DEVICE_OBJECT {CSHORT Type;USHORT Size;LONG ReferenceCount;PDRIVER_OBJECT DriverObject;PDEVICE_OBJECT NextDevice;PDEVICE_OBJECT AttachedDevice;PIRP CurrentIrp;PIO_TIMER Timer;ULONG Flags;//notice ULONG Characteristics;__volatile PVPB Vpb;PVOID DeviceExtension;DEVICE_TYPE DeviceType;CCHAR StackSize;union {LIST_ENTRY ListEntry;WAIT_CONTEXT_BLOCK Wcb;} Queue;ULONG AlignmentRequirement;KDEVICE_QUEUE DeviceQueue;KDPC Dpc;ULONG ActiveThreadCount;PSECURITY_DESCRIPTOR SecurityDescriptor;KEVENT DeviceLock;USHORT SectorSize;USHORT Spare1;PDEVOBJ_EXTENSION DeviceObjectExtension;PVOID Reserved; } DEVICE_OBJECT, *PDEVICE_OBJECT;NTSTATUS AddDevice(DriverObject, PhysicalDeviceObject) { PDEVICE_OBJECT fdo; IoCreateDevice(..., &fdo); fdo->Flags |= DO_BUFFERED_IO; //或者是下面的代碼,這三句任取其一fdo->Flags |= DO_DIRECT_IO; //<or> fdo->Flags |= 0; }總結(jié)一下,在新增設(shè)備的時(shí)候這個(gè)物理設(shè)備的數(shù)據(jù)緩沖方式就被決定了。這之后你不能該變緩沖方式的設(shè)置,因?yàn)檫^濾器驅(qū)動(dòng)程序?qū)?fù)制這個(gè)標(biāo)志設(shè)置,并且,如果你改變了設(shè)置,過濾器驅(qū)動(dòng)程序沒有辦法知道這個(gè)改變。
接下來解決目前為止的最后一個(gè)疑問: 系統(tǒng)是如何來根據(jù)設(shè)備對(duì)象中的緩沖類型碼來進(jìn)行不同緩沖區(qū)類型的的緩沖區(qū)數(shù)據(jù)的實(shí)際填充過程的(即實(shí)際的緩沖區(qū)填充過程是什么)?
1) Buffered方式(有一個(gè)復(fù)制過程)
當(dāng)I/O管理器創(chuàng)建IRP_MJ_READ或IRP_MJ_WRITE請(qǐng)求時(shí)(讀寫請(qǐng)求中會(huì)用到數(shù)據(jù)緩沖區(qū),普通的文件屬性查詢請(qǐng)求或設(shè)置中最多用到一個(gè)指定的數(shù)據(jù)結(jié)構(gòu)大小的內(nèi)存空間即可),它探測(cè)設(shè)備的緩沖標(biāo)志(在創(chuàng)建設(shè)備時(shí)就決定的了)以決定如何描述新IRP中的數(shù)據(jù)緩沖區(qū)。如果DO_BUFFERED_IO(UserBuffer)標(biāo)志設(shè)置,I/O管理器將分配與用戶緩沖區(qū)大小相同的"非分頁"內(nèi)存(不會(huì)產(chǎn)生缺頁中斷的內(nèi)存空間)。
注意,它把緩沖區(qū)的地址和長(zhǎng)度保存到兩個(gè)十分不同的地方,下面是一段模擬代碼。你可以假定I/O管理器執(zhí)行下面代碼(注意這并不是Windows NT的源代碼):
可以看出,系統(tǒng)緩沖區(qū)地址被放在IRP的AssociatedIrp.SystemBuffer域中,而數(shù)據(jù)的長(zhǎng)度被放到stack->Parameters聯(lián)合中。I/O管理器把用戶模式虛擬地址(uva變量)保存到IRP的UserBuffer域中,這樣一來內(nèi)核驅(qū)動(dòng)代碼就可以找到這個(gè)地址。
?
2) Direct方式
如果你在設(shè)備對(duì)象中指定DO_DIRECT_IO(MDL)方式,I/O管理器將創(chuàng)建一個(gè)MDL用來描述包含該用戶模式數(shù)據(jù)緩沖區(qū)的鎖定內(nèi)存頁(本質(zhì)上還是一種映射,創(chuàng)建映射到同一內(nèi)存位置的內(nèi)核模式虛擬地址)。MDL結(jié)構(gòu)的聲明如下:
?
?StartVa成員給出了用戶緩沖區(qū)的虛擬地址,這個(gè)地址僅在擁有數(shù)據(jù)緩沖區(qū)的用戶模式進(jìn)程上下文中才有效(即用戶模式中的虛擬地址空間)。ByteOffset是緩沖區(qū)起始位置在一個(gè)頁幀中的偏移值,ByteCount是緩沖區(qū)的字節(jié)長(zhǎng)度。Pages數(shù)組沒有被正式地聲明為MDL結(jié)構(gòu)的一部分,在內(nèi)存中它跟在MDL的后面,包含用戶模式虛擬地址映射為物理頁幀的個(gè)數(shù)。
要注意的是,我們不可以直接訪問MDL的任何成員。應(yīng)該使用宏或訪問函數(shù):
宏或函數(shù) 描述 IoAllocateMdl 創(chuàng)建MDL(在文件系統(tǒng)驅(qū)動(dòng)的透明加密中你可能需要?jiǎng)?chuàng)建自己的臨時(shí)MDL以暫時(shí)替換系統(tǒng)的原始的MDL)IoBuildPartialMdl 創(chuàng)建一個(gè)已存在MDL的子MDLIoFreeMdl 銷毀MDLMmBuildMdlForNonPagedPool 修改MDL以描述內(nèi)核模式中一個(gè)非分頁內(nèi)存區(qū)域MmGetMdlByteCount 取緩沖區(qū)字節(jié)大小MmGetMdlByteOffset 取緩沖區(qū)在第一個(gè)內(nèi)存頁中的偏移MmGetMdlVirtualAddress 取虛擬地址MmGetSystemAddressForMdl 創(chuàng)建映射到同一內(nèi)存位置的內(nèi)核模式虛擬地址MmGetSystemAddressForMdlSafe 與MmGetSystemAddressForMdl相同,但Windows 2000首選MmInitializeMdl (再)初始化MDL以描述一個(gè)給定的虛擬緩沖區(qū)MmPrepareMdlForReuse 再初始化MDLMmProbeAndLockPages 地址有效性校驗(yàn)后鎖定內(nèi)存頁MmSizeOfMdl 取為描述一個(gè)給定的虛擬緩沖區(qū)的MDL所占用的內(nèi)存大小MmUnlockPages 為該MDL解鎖內(nèi)存頁對(duì)于I/O管理器執(zhí)行的Direct方式的讀寫操作,其過程可以想象為下面代碼:
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è)描述用戶緩沖區(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管理器最后也使用該成員來清除MDL。
這段代碼的關(guān)鍵地方是調(diào)用MmProbeAndLockPages。該函數(shù)校驗(yàn)?zāi)莻€(gè)數(shù)據(jù)緩沖區(qū)是否有效,是否可以按適當(dāng)模式訪問。如果我們向設(shè)備寫數(shù)據(jù),我們必須能讀緩沖區(qū)。如果我們從設(shè)備讀數(shù)據(jù),我們必須能寫緩沖區(qū)。另外,該函數(shù)鎖定了包含數(shù)據(jù)緩沖區(qū)的物理內(nèi)存頁,并在MDL的后面填寫了頁號(hào)數(shù)組。在效果上,一個(gè)鎖定的內(nèi)存頁將成為非分頁內(nèi)存池的一部分,直到所有對(duì)該頁內(nèi)存加鎖的調(diào)用者都對(duì)其解了鎖。
在Direct方式的讀寫操作中,對(duì)MDL你最可能做的事是把它作為參數(shù)傳遞給其它函數(shù)。例如,DMA傳輸?shù)腗apTransfer步驟需要一個(gè)MDL。另外,在內(nèi)部,USB讀寫操作總使用MDL。所以你應(yīng)該把讀寫操作設(shè)置為DO_DIRECT_IO方式,并把結(jié)果MDL傳遞給USB總線驅(qū)動(dòng)程序。
順便提一下,I/O管理器確實(shí)在stack->Parameters聯(lián)合中保存了讀寫請(qǐng)求的長(zhǎng)度,但驅(qū)動(dòng)程序應(yīng)該直接從MDL中獲得請(qǐng)求數(shù)據(jù)的長(zhǎng)度
?
?
3) Neither方式
如果你在設(shè)備對(duì)象中同時(shí)忽略了DO_DIRECT_IO和DO_BUFFERED_IO標(biāo)志設(shè)置,你將得到默認(rèn)的neither方式。對(duì)于這種方式,I/O管理器將簡(jiǎn)單地把用戶模式虛擬地址和字節(jié)計(jì)數(shù)直接交給你,其余的工作由你去做。這種情況下程序員將自己去解決因?yàn)檫M(jìn)程的切換導(dǎo)致的用戶模式地址失效問題。
至此,我們目前的疑問就全部解決了,我們知道了IRP中的三種不同的緩沖區(qū)是怎么來的(設(shè)備添加的時(shí)候決定的),是由誰填充的(操作系統(tǒng)自動(dòng)地把用戶模式地址空間的數(shù)據(jù)填充到IRP中的緩沖區(qū)中/或者直接給出用戶空間地址)。
接下來就可以引出IRP中(準(zhǔn)備說是IRP頭部的另一個(gè)成員域)
?
?
?
?
2. ULONG? Flags
File system drivers use this field, which is read-only for all drivers. Network and, possibly, highest-level device drivers also might read this field, which can be set with one or more of the following system-defined masks:在文件系統(tǒng)驅(qū)動(dòng)程序的編程中將使用到這個(gè)數(shù)據(jù)域,這對(duì)所有的驅(qū)動(dòng)程序來說是只讀的,它指示了這個(gè)IRP的操作類型。
IRP_NOCACHE IRP_PAGING_IO IRP_MOUNT_COMPLETION IRP_SYNCHRONOUS_API IRP_ASSOCIATED_IRP IRP_BUFFERED_IO IRP_DEALLOCATE_BUFFER IRP_INPUT_OPERATION IRP_SYNCHRONOUS_PAGING_IO IRP_CREATE_OPERATION IRP_READ_OPERATION IRP_WRITE_OPERATION IRP_CLOSE_OPERATION IRP_DEFER_IO_COMPLETION關(guān)于這個(gè)Flags字段,我們常常要注意的是,我們?nèi)绻谧鲞^濾/綁定/捕獲類型的驅(qū)動(dòng)類型的編程中,我們新創(chuàng)建的上層過濾設(shè)備的Flags字段的值一定要和下層的真實(shí)設(shè)備或底層驅(qū)動(dòng)的Flags保持一致。
比如我在做文件系統(tǒng)卷的過濾設(shè)備編程的時(shí)候就遇到這樣的代碼:
//設(shè)備標(biāo)志的復(fù)制 if (FlagOn( DeviceObject->Flags, DO_BUFFERED_IO )) {SetFlag( SFilterDeviceObject->Flags, DO_BUFFERED_IO ); } if (FlagOn( DeviceObject->Flags, DO_DIRECT_IO )) {SetFlag( SFilterDeviceObject->Flags, DO_DIRECT_IO ); }?
?
?
?
?
3.? IO_STATUS_BLOCK? IoStatus
typedef struct _IO_STATUS_BLOCK {union {NTSTATUS Status;PVOID Pointer;};ULONG_PTR Information; } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;IoStatus(IO_STATUS_BLOCK)是一個(gè)僅包含兩個(gè)域的結(jié)構(gòu),驅(qū)動(dòng)程序在最終完成請(qǐng)求時(shí)設(shè)置這個(gè)結(jié)構(gòu)。
IoStatus.Status 表示IRP完成狀態(tài)
ntdef.h
ntstatus.h
WDK的這兩個(gè)頭文件中定了所有的系統(tǒng)級(jí)的返回信息,在驅(qū)動(dòng)中,函數(shù)的返回值大多數(shù)情況下就是這樣結(jié)果狀態(tài)信息,當(dāng)然也有通過引用的方式獲得函數(shù)執(zhí)行的結(jié)果的,但是函數(shù)還是返回個(gè)執(zhí)行結(jié)果。
IoStatus.information的值與請(qǐng)求相關(guān),如果是數(shù)據(jù)傳輸請(qǐng)求,則將該域設(shè)置為傳輸?shù)淖止?jié)數(shù)(在windows編程中,基本上涉及到數(shù)據(jù)讀寫的函數(shù)一般都是返回這一類的結(jié)果,即操作的字節(jié)數(shù))。
?
?
?
?
4. KPROCESSOR_MODE? RequestorMode
RequestorMode將等于一個(gè)枚舉常量UserMode或KernelMode, 指定原始I/O請(qǐng)求的來源。驅(qū)動(dòng)程序有時(shí)需要查看這個(gè)值來決定是否要信任某些參數(shù)。
?
?
?
5. BOOLEAN PendingReturned
PendingReturned(BOOLEAN)如果為TRUE,則表明處理該 IRP的最低級(jí)派遣例程返回了STATUS_PENDING。完成例程通過參考該域來避免自己與派遣例程間的潛在競(jìng)爭(zhēng)。
If set to TRUE, a driver has marked the IRP pending. Each IoCompletion routine should check the value of this flag. If the flag is TRUE, and if the IoCompletion routine will not return STATUS_MORE_PROCESSING_REQUIRED, the routine should call IoMarkIrpPending to propagate the pending status to drivers above it in the device stack.這段話是什么意思呢?這和內(nèi)核驅(qū)動(dòng)中的多層設(shè)備棧有關(guān)系。為了解釋這個(gè)問題,我們先來看一段代碼demo:
.... Kevent event; KeInitializeEvent(&event, NotificatinoEvent, FALSE); IoCopyCurrentIrpStackLocationToNext(Irp); //設(shè)置完成回調(diào)函數(shù) IoSetCompletionRoutine(Irp,IrpComplete, //回調(diào)函數(shù)&event,TRUE,TRUE,TRUE ); status = IoCallDriver(DeviceObject, Irp); if(status == STATUS_PENDING) {//code to handle asynchronous response//異步 status = KeWaitForSingleObject( &waitEvent,Executive,KernelMode,FALSE,NULL );} ...//這是一個(gè)IRP完成回調(diào)函數(shù)的原型 NTSTATUS IrpComplete(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp,IN PVOID Context ) {...if(Irp->PendingReturned){//這個(gè)函數(shù)等價(jià)于: Irp->IoStatus.Status = STATUS_PENDING 即表名這個(gè)IRP處理流程依舊沒有結(jié)束 IoMarkIrpPending(Irp);}return Irp->IoStatus.Status; }請(qǐng)?jiān)徫覜]頭沒腦的給這出這段看起來不知所云的代碼。這是因?yàn)镮RP機(jī)制是一種基礎(chǔ)機(jī)制,往往是配合一些具體的過濾驅(qū)動(dòng)的編程而使用的,如果要給出完整的例程那這篇文章的篇幅就會(huì)無窮無盡了。
所以接下來我盡我最大的能力來解釋這段代碼的意思并給出它的利用場(chǎng)景。
IRP作為一個(gè)線程無關(guān)的調(diào)用棧
進(jìn)行一個(gè)設(shè)備的I/O操作通常需要調(diào)用這一設(shè)備相關(guān)的不止一個(gè)驅(qū)動(dòng)。每一個(gè)和這個(gè)設(shè)備相關(guān)的驅(qū)動(dòng)都會(huì)創(chuàng)建一個(gè)設(shè)備對(duì)象(Device Object),并且這些設(shè)備對(duì)象會(huì)垂直壓入(排列進(jìn))一個(gè)設(shè)備棧(Device Stack)。IRP會(huì)在設(shè)備棧中從上到下的一個(gè)個(gè)被傳遞進(jìn)去(從頂層的設(shè)備驅(qū)動(dòng)一直往下到底層的真實(shí)設(shè)備)。對(duì)于棧中的每一個(gè)驅(qū)動(dòng),IRP都會(huì)用一個(gè)指針標(biāo)識(shí)一個(gè)棧位置(IO_STACK_LOCATION和設(shè)備棧上的設(shè)備驅(qū)動(dòng)的一一對(duì)應(yīng)關(guān)系用這個(gè)指針來綁定)。由于驅(qū)動(dòng)可以異步地處理請(qǐng)求,因此IRP就像是一個(gè)線程無關(guān)的調(diào)用棧一樣
仔細(xì)看這張圖,每一級(jí)驅(qū)動(dòng)程序都使用下一級(jí)驅(qū)動(dòng)程序的堆棧單元保存自己完成例程指針。最底層的驅(qū)動(dòng)程序不應(yīng)該安裝一個(gè)完成例程,它應(yīng)該總是返回一個(gè)真實(shí)的硬件操作結(jié)果。即我們?cè)诋?dāng)前位置的設(shè)備驅(qū)動(dòng)中設(shè)置一個(gè)下層驅(qū)動(dòng)的完成回調(diào)函數(shù)時(shí),本質(zhì)上是在下層驅(qū)動(dòng)的棧空間中布置一個(gè)了一個(gè)回調(diào)函數(shù)地址。
將IRP傳遞到下一級(jí)驅(qū)動(dòng)程序(又被稱作轉(zhuǎn)發(fā)IRP)是指IRP等價(jià)于一個(gè)子例程調(diào)用。當(dāng)驅(qū)動(dòng)轉(zhuǎn)發(fā)一個(gè)IRP,這個(gè)驅(qū)動(dòng)程序必須向IRP參數(shù)組增加下一個(gè)I/O棧位置,告知這一IRP棧的指針,然后調(diào)用下一驅(qū)動(dòng)的分發(fā)例程(dispatch routine)。基本來說,就是驅(qū)動(dòng)向下調(diào)用IRP棧(calling down the IRP stack)
傳遞一個(gè)IRP,驅(qū)動(dòng)通常會(huì)采取以下幾種步驟
1) . 建立下一個(gè)I/O棧位置的參數(shù)。
1.1) 調(diào)用IoGetNextIrpStackLocation例程來得到一個(gè)指針指向下一個(gè)I/O棧位置,然后將請(qǐng)求參數(shù)數(shù)組復(fù)制到那個(gè)得到的位置
1.2) 調(diào)用CopyCurrentIrpStackLocationToNext例程(如果驅(qū)動(dòng)設(shè)置了IoCompletion例程)
或者
1.3) 調(diào)用IoSkipCurrentIrpStackLocation例程(沒有設(shè)置IoCompletion例程)來傳遞當(dāng)前位置所使用的同樣的參數(shù)組。
2). 如果需要的話,調(diào)用IoSetCompletionRoutine例程,為后期處理(post-processing)設(shè)置一個(gè)IoCompletion例程。如果驅(qū)動(dòng)設(shè)置了IoCompletion例程,那么他在上一步中必須使用IoCopyCurrentIrpStackLocationToNext。
3). 通過調(diào)用IoCallDriver例程將請(qǐng)求傳遞到下一個(gè)驅(qū)動(dòng)。這個(gè)例程會(huì)自動(dòng)通告IRP棧指針,并且調(diào)用下一個(gè)驅(qū)動(dòng)的分發(fā)例程。
理解這句話非常重要,這個(gè)IRP中的核心思想,也就是說,一旦你調(diào)用了IoCallDriver()把IRP傳遞給了下層的驅(qū)動(dòng),這個(gè)IRP就和你沒關(guān)系了。
如果驅(qū)動(dòng)需要訪問一個(gè)已經(jīng)在棧里傳下去的IRP,那么這個(gè)驅(qū)動(dòng)必須實(shí)現(xiàn)(設(shè)置)IoCompletion例程。當(dāng)I/O管理器(I/O Manager)調(diào)用IoCompletion例程時(shí)(當(dāng)下層完成處理后,自動(dòng)調(diào)用了回調(diào)函數(shù)),這個(gè)驅(qū)動(dòng)(之前把IRP下發(fā)的那個(gè)上層驅(qū)動(dòng))就能夠在IoCompletion例程執(zhí)行期間重新獲得對(duì)這一IRP的所有權(quán)。如此,IoCompletion例程就能夠訪問IRP中的域。
設(shè)置異步完成回調(diào)函數(shù)的方法上面的代碼已給出,這是一個(gè)經(jīng)典的模型,即創(chuàng)建一個(gè)事件對(duì)象->初始化這個(gè)事件對(duì)象->上層對(duì)這個(gè)事件進(jìn)行阻塞等 待->將IRP下發(fā)給下層驅(qū)動(dòng)->下層驅(qū)動(dòng)完成處理邏輯后設(shè)置設(shè)置這個(gè)事件(即觸發(fā)一個(gè)完成信號(hào),解除這個(gè)事件的互斥)->上層驅(qū)動(dòng)獲 得這個(gè)事件的釋放->上層驅(qū)動(dòng)繼續(xù)代碼邏輯,并根據(jù)下層驅(qū)動(dòng)的返回結(jié)構(gòu)來做進(jìn)一步的操作。
若是驅(qū)動(dòng)的分發(fā)例程(上層驅(qū)動(dòng))也必須在IRP被后面的驅(qū)動(dòng)(下層驅(qū)動(dòng))處理完成之后再處理它,這個(gè)IoCompletion例程(上層驅(qū)動(dòng)設(shè)置的完成回調(diào)函數(shù))必須返回STATUS_MORE_PROCESSING_REQUIRED,以將IRP的所有權(quán)返回給分發(fā)例程(上層驅(qū)動(dòng))。如此依賴,I/O管理器會(huì)停止IRP的處理(這指回卷處理,之后會(huì)解釋),將最終完成IRP的任務(wù)(調(diào)用IoCompleteRequest來完成這個(gè)IRP)留給分發(fā)例程。分發(fā)例程能夠在之后調(diào)用IoCompleteRequest來完成這個(gè)IRP,或者還能將這個(gè)IRP標(biāo)記為等待進(jìn)一步處理,繼續(xù)回傳給它之上的驅(qū)動(dòng)。
當(dāng)輸入、輸出操作(I/O)完成時(shí),完成這個(gè)I/O操作的驅(qū)動(dòng)會(huì)調(diào)用IoCompleteRequest例程,這個(gè)例程將IRP棧指針移到指向IRP棧的前一個(gè)(更上面)的位置。
如果一個(gè)驅(qū)動(dòng)在設(shè)備棧中向下傳遞IRP時(shí)設(shè)定了IoCompletion例程,I/O管理器就會(huì)在IRP棧指針再次指向這一驅(qū)動(dòng)的這個(gè)I/O棧位置的時(shí)候調(diào)用此例程,IoCompletion例程就表現(xiàn)為: 當(dāng)IRP在設(shè)備棧中傳遞時(shí),操作IRP的那些驅(qū)動(dòng)的返回地址。
當(dāng)每一個(gè)驅(qū)動(dòng)都完成了它對(duì)應(yīng)的子請(qǐng)求,I/O請(qǐng)求就完成了。I/O管理器從Irp->IoStatus.Status域取回請(qǐng)求的狀態(tài)信息,并且從Irp->IoStatus.Information域取回傳輸?shù)淖止?jié)數(shù)。
(這段話是我們自己根據(jù)MSDN和《寒江獨(dú)釣》的研究后總結(jié)的,說心理話,不敢保證100%正確,這塊內(nèi)容確實(shí)很復(fù)雜,如果看到這篇文章的牛牛知道真實(shí)的詳細(xì)細(xì)節(jié)的話,希望不吝賜教,分享一些好的思路)
至此,我們知道了PendingReturned是用來判斷下層的驅(qū)動(dòng)返回的處理狀態(tài)的。那它的利用場(chǎng)景是什么呢?這通常見于一些文件系統(tǒng)驅(qū)動(dòng)過濾的應(yīng)用中: 如磁盤透明加密, NTFS透明加密的編程中,我們的上層過濾驅(qū)動(dòng)要先捕獲到這個(gè)IRP_MJ_READ請(qǐng)求,然后下放這個(gè)IRP,讓它去調(diào)用磁盤驅(qū)動(dòng)讀取數(shù)據(jù),然后在完成回調(diào)函數(shù)中對(duì)剛才讀取到的數(shù)據(jù)進(jìn)行加解密,這是一個(gè)典型的應(yīng)用。
?
?
?
?
6. IRP操作取消機(jī)制
BOOLEAN Cancel; KIRQL CancelIrql; . . PDRIVER_CANCEL CancelRoutine;這三個(gè)字段同屬于IRP取消機(jī)制的范湊,我們一并研究。
Cancel(BOOLEAN)如果為TRUE,則表明IoCancelIrp已被調(diào)用,該函數(shù)用于取消這個(gè)請(qǐng)求。如果為FALSE,則表明沒有調(diào)用IoCancelIrp函數(shù)。
CancelIrql(KIRQL)是一個(gè)IRQL值,表明那個(gè)專用的取消自旋鎖是在 這個(gè)IRQL上獲取的。當(dāng)你在取消例程中釋放自旋鎖時(shí)應(yīng)參考這個(gè)域。
CancelRoutine(PDRIVER_CANCEL)是驅(qū)動(dòng)程序取消例程的地 址。你應(yīng)該使用IoSetCancelRoutine函數(shù)設(shè)置這個(gè)域而不是直接修改該域
IRP請(qǐng)求的最終結(jié)局無非有兩個(gè):要么被完成了,要么被取消了。完成IRP請(qǐng)求的過程已經(jīng)在前面講過了,這里仔細(xì)講一個(gè)IRP請(qǐng)求的取消。
為什么要取消IRP請(qǐng)求呢?一般來講,原因不外乎是本請(qǐng)求操作超時(shí)或設(shè)備故障導(dǎo)致的。具體理解,可以考慮如下兩種情形:
情形1:驅(qū)動(dòng)發(fā)送一個(gè)請(qǐng)求到下級(jí)驅(qū)動(dòng),下級(jí)驅(qū)動(dòng)由于忙,將它放到自己的請(qǐng)求隊(duì)中去,下級(jí)驅(qū)動(dòng)一直忙,請(qǐng)求一直沒有得到處理,而這個(gè)請(qǐng)求又比較重要,如果一直得不到處理就會(huì)造成系統(tǒng)處于死鎖。于是,驅(qū)動(dòng)就會(huì)給這個(gè)請(qǐng)求加上超時(shí)機(jī)制,若超過一定的時(shí)間還沒有得到處理結(jié)果,就通知下級(jí)驅(qū)動(dòng)直接取消該請(qǐng)求。
情形1:驅(qū)動(dòng)發(fā)送很多請(qǐng)求到下級(jí)驅(qū)動(dòng)去處理,下級(jí)驅(qū)動(dòng)返回了一個(gè)請(qǐng)求的結(jié)果。可是,這個(gè)結(jié)果是個(gè)錯(cuò)誤,而且是個(gè)很嚴(yán)重的錯(cuò)誤,比如設(shè)備出故障了。這時(shí),就要將設(shè)備進(jìn)行錯(cuò)誤恢復(fù),如重啟設(shè)備,同時(shí),其它送下去的請(qǐng)求都要同時(shí)取消掉。
驅(qū)動(dòng)如何被取消? 一般來講,取消的發(fā)起者一定是上層的驅(qū)動(dòng),而取消的實(shí)際執(zhí)行者,則是下層的驅(qū)動(dòng)。
?
?
?
?
7. Tail(一個(gè)很大的聯(lián)合體)
union {struct {..union {KDEVICE_QUEUE_ENTRY DeviceQueueEntry;struct {PVOID DriverContext[4];};};..PETHREAD Thread;..LIST_ENTRY ListEntry;..} Overlay;.. } Tail;Tail.Overlay是Tail聯(lián)合中的一種 結(jié)構(gòu),它含有幾個(gè)對(duì)WDM驅(qū)動(dòng)程序有潛在用途的成員。
在這個(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還沒有進(jìn)入某 個(gè)隊(duì)列時(shí),如果你擁有這個(gè)IRP你可以使用這個(gè)域,你可以任意使用DriverContext中的四個(gè)指針。
Tail.Overlay.ListEntry(LIST_ENTRY) 僅能作為你自己實(shí)現(xiàn)的私有隊(duì)列的連接域。我們?cè)谧鑫募到y(tǒng)驅(qū)動(dòng)過濾的時(shí)候往往對(duì)讀寫請(qǐng)求進(jìn)行串行化處理,這個(gè)時(shí)候就需要這個(gè)ListEntry來構(gòu)建一個(gè)IO請(qǐng)求的隊(duì)列,以解決并行請(qǐng)求的串行化問題。
?
?
?
至此,我們把IRP頭部的數(shù)據(jù)結(jié)構(gòu)分析完了,接下繼續(xù)學(xué)習(xí)下IRP Sub-Requst子請(qǐng)求部分的數(shù)據(jù)結(jié)構(gòu)(即設(shè)備棧中的每層驅(qū)動(dòng)對(duì)應(yīng)的IO_STACK_LOCATION結(jié)構(gòu))
我們知道,在IRP頭部后面跟有很多個(gè)相同的結(jié)構(gòu)。
PIO_STACK_LOCATION IoGetCurrentIrpStackLocation(IN PIRP Irp);使用這個(gè)函數(shù)可以獲得"本層"所對(duì)應(yīng)的那個(gè)IO_STACK_LOCATION。
我們來分析一下這個(gè)IO_STACK_LOCATION的數(shù)據(jù)結(jié)構(gòu)。
typedef struct _IO_STACK_LOCATION {UCHAR MajorFunction;UCHAR MinorFunction;UCHAR Flags;UCHAR Control;union {//// Parameters for IRP_MJ_CREATE // struct {PIO_SECURITY_CONTEXT SecurityContext;ULONG Options;USHORT POINTER_ALIGNMENT FileAttributes;USHORT ShareAccess;ULONG POINTER_ALIGNMENT EaLength;} Create;//// Parameters for IRP_MJ_READ // struct {ULONG Length;ULONG POINTER_ALIGNMENT Key;LARGE_INTEGER ByteOffset;} Read;//// Parameters for IRP_MJ_WRITE // struct {ULONG Length;ULONG POINTER_ALIGNMENT Key;LARGE_INTEGER ByteOffset;} Write;//// Parameters for IRP_MJ_QUERY_INFORMATION // struct {ULONG Length;FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass;} QueryFile;//// Parameters for IRP_MJ_SET_INFORMATION // struct {ULONG Length;FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass;PFILE_OBJECT FileObject;union {struct {BOOLEAN ReplaceIfExists;BOOLEAN AdvanceOnly;};ULONG ClusterCount;HANDLE DeleteHandle;};} SetFile;//// Parameters for IRP_MJ_QUERY_VOLUME_INFORMATION // struct {ULONG Length;FS_INFORMATION_CLASS POINTER_ALIGNMENT FsInformationClass;} QueryVolume;//// Parameters for IRP_MJ_DEVICE_CONTROL and IRP_MJ_INTERNAL_DEVICE_CONTROL // struct {ULONG OutputBufferLength;ULONG POINTER_ALIGNMENT InputBufferLength;ULONG POINTER_ALIGNMENT IoControlCode;PVOID Type3InputBuffer;} DeviceIoControl;//// Nonsystem service parameters.//// Parameters for IRP_MN_MOUNT_VOLUME // struct {PVOID DoNotUse1;PDEVICE_OBJECT DeviceObject;} MountVolume;//// Parameters for IRP_MN_VERIFY_VOLUME // struct {PVOID DoNotUse1;PDEVICE_OBJECT DeviceObject;} VerifyVolume;//// Parameters for Scsi using IRP_MJ_INTERNAL_DEVICE_CONTROL // struct { struct _SCSI_REQUEST_BLOCK *Srb;} Scsi;//// Parameters for IRP_MN_QUERY_DEVICE_RELATIONS // struct {DEVICE_RELATION_TYPE Type;} QueryDeviceRelations;//// Parameters for IRP_MN_QUERY_INTERFACE // struct {CONST GUID *InterfaceType;USHORT Size;USHORT Version;PINTERFACE Interface;PVOID InterfaceSpecificData;} QueryInterface;//// Parameters for IRP_MN_QUERY_CAPABILITIES // struct {PDEVICE_CAPABILITIES Capabilities;} DeviceCapabilities;//// Parameters for IRP_MN_FILTER_RESOURCE_REQUIREMENTS // struct {PIO_RESOURCE_REQUIREMENTS_LIST IoResourceRequirementList;} FilterResourceRequirements;//// Parameters for IRP_MN_READ_CONFIG and IRP_MN_WRITE_CONFIG // struct {ULONG WhichSpace;PVOID Buffer;ULONG Offset;ULONG POINTER_ALIGNMENT Length;} ReadWriteConfig;//// Parameters for IRP_MN_SET_LOCK // struct {BOOLEAN Lock;} SetLock;//// Parameters for IRP_MN_QUERY_ID // struct {BUS_QUERY_ID_TYPE IdType;} QueryId;//// Parameters for IRP_MN_QUERY_DEVICE_TEXT // struct {DEVICE_TEXT_TYPE DeviceTextType;LCID POINTER_ALIGNMENT LocaleId;} QueryDeviceText;//// Parameters for IRP_MN_DEVICE_USAGE_NOTIFICATION // struct {BOOLEAN InPath;BOOLEAN Reserved[3];DEVICE_USAGE_NOTIFICATION_TYPE POINTER_ALIGNMENT Type;} UsageNotification;//// Parameters for IRP_MN_WAIT_WAKE // struct {SYSTEM_POWER_STATE PowerState;} WaitWake;//// Parameter for IRP_MN_POWER_SEQUENCE // struct {PPOWER_SEQUENCE PowerSequence;} PowerSequence;//// Parameters for IRP_MN_SET_POWER and IRP_MN_QUERY_POWER // struct {ULONG SystemContext;POWER_STATE_TYPE POINTER_ALIGNMENT Type;POWER_STATE POINTER_ALIGNMENT State;POWER_ACTION POINTER_ALIGNMENT ShutdownType;} Power;//// Parameters for IRP_MN_START_DEVICE // struct {PCM_RESOURCE_LIST AllocatedResources;PCM_RESOURCE_LIST AllocatedResourcesTranslated;} StartDevice;//// Parameters for WMI Minor IRPs // struct {ULONG_PTR ProviderId;PVOID DataPath;ULONG BufferSize;PVOID Buffer;} WMI;//// Others - driver-specific// struct {PVOID Argument1;PVOID Argument2;PVOID Argument3;PVOID Argument4;} Others;} Parameters;PDEVICE_OBJECT DeviceObject;PFILE_OBJECT FileObject;... } IO_STACK_LOCATION, *PIO_STACK_LOCATION;?
1. IRP請(qǐng)求類型
UCHAR MajorFunction; UCHAR MinorFunction;在每個(gè)驅(qū)動(dòng)的入口函數(shù)DriverEntry中,我們經(jīng)常要做的是就是當(dāng)前驅(qū)動(dòng)的分發(fā)函數(shù)進(jìn)行賦值。即上層應(yīng)用會(huì)很多種不同的調(diào)用請(qǐng)求,這些請(qǐng)求被windows以IRP_MJ_XX這樣的主功能號(hào)進(jìn)行了分類。例如下面的代碼。
NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath) {...DriverObject->MajorFunction[IRP_MJ_CREATE] = SfCreate;DriverObject->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE] = SfCreate;DriverObject->MajorFunction[IRP_MJ_CREATE_MAILSLOT] = SfCreate;DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SfFsControl;DriverObject->MajorFunction[IRP_MJ_CLEANUP] = SfCleanupClose;DriverObject->MajorFunction[IRP_MJ_CLOSE] = SfCleanupClose;... }從面向接口編程的角度來理解,windows已經(jīng)在接口中實(shí)現(xiàn)了一個(gè)分發(fā)例程的原型,并以數(shù)組的形式把接口(即函數(shù)地址)放了出來,我們?cè)趯懘a的時(shí)候,如果想在本驅(qū)動(dòng)中對(duì)指定的IRP類型進(jìn)行處理,就必須去對(duì)"分發(fā)例程(就是這個(gè)數(shù)組)"進(jìn)行賦值,并對(duì)我們提供的例程函數(shù)進(jìn)行代碼實(shí)現(xiàn)。
IRP Major Function Codes
IRP_MJ_CREATE IRP_MJ_PNP IRP_MJ_POWER IRP_MJ_READ IRP_MJ_WRITE IRP_MJ_FLUSH_BUFFERS IRP_MJ_QUERY_INFORMATION IRP_MJ_SET_INFORMATION IRP_MJ_DEVICE_CONTROL IRP_MJ_INTERNAL_DEVICE_CONTROL IRP_MJ_SYSTEM_CONTROL IRP_MJ_CLEANUP IRP_MJ_CLOSE IRP_MJ_SHUTDOWN除了主功能號(hào)之外,還有子功能號(hào),這是在一些PnP manager(PnP類型操作的IRP), the power manager(電源管理), file system drivers(文件系統(tǒng))中需要通過子功能號(hào)來進(jìn)一步對(duì)IRP的操作類型進(jìn)行區(qū)分。所以每個(gè)類型的IRP操作(PnP/Power/filesystem)的子功能號(hào)都是不一樣的。我們以文件系統(tǒng)的子功能號(hào)為例子。
switch (irpSp->MinorFunction) { //磁盤卷掛載case IRP_MN_MOUNT_VOLUME: return SfFsControlMountVolume( DeviceObject, Irp ); //磁盤卷加載case IRP_MN_LOAD_FILE_SYSTEM: return SfFsControlLoadFileSystem( DeviceObject, Irp );//磁盤的請(qǐng)求case IRP_MN_USER_FS_REQUEST:{switch (irpSp->Parameters.FileSystemControl.FsControlCode) { case FSCTL_DISMOUNT_VOLUME:{..}}break;}}總之,這個(gè)功能號(hào)是用來對(duì)IRP請(qǐng)求的類型進(jìn)行區(qū)分的,它們只是一些代號(hào)而已。
?
?
?
2. UNION Parameters
接下來是一個(gè)很大的聯(lián)合體,我們仔細(xì)觀察,其實(shí)這個(gè)聯(lián)合體還是很有規(guī)律的,而且也很簡(jiǎn)單,因?yàn)橛幸?guī)律的東西往往會(huì)相對(duì)簡(jiǎn)單。
里面包含了每種IRP請(qǐng)求所需要的參數(shù),可以參考MSDN上的解釋。
http://msdn.microsoft.com/en-us/library/ff550659
?
?
?
3. PDEVICE_OBJECT? DeviceObject
指向這個(gè)IO_STACK_LOCATINO所對(duì)應(yīng)的設(shè)備對(duì)象。可能是中間的過濾設(shè)備,也可能是底層的真實(shí)設(shè)備。
?
?
?
4. PFILE_OBJECT? FileObject
指向一個(gè)這個(gè)IRP對(duì)應(yīng)的文件對(duì)象,這個(gè)文件對(duì)象是一個(gè)廣義的概念,在內(nèi)核中,磁盤/文件/目錄都算是一種文件。
typedef struct _FILE_OBJECT {CSHORT Type;CSHORT Size;PDEVICE_OBJECT DeviceObject;PVPB Vpb;PVOID FsContext;PVOID FsContext2;PSECTION_OBJECT_POINTERS SectionObjectPointer;PVOID PrivateCacheMap;NTSTATUS FinalStatus;struct _FILE_OBJECT *RelatedFileObject;BOOLEAN LockOperation;BOOLEAN DeletePending;BOOLEAN ReadAccess;BOOLEAN WriteAccess;BOOLEAN DeleteAccess;BOOLEAN SharedRead;BOOLEAN SharedWrite;BOOLEAN SharedDelete;ULONG Flags;UNICODE_STRING FileName;LARGE_INTEGER CurrentByteOffset;ULONG Waiters;ULONG Busy;PVOID LastLock;KEVENT Lock;KEVENT Event;PIO_COMPLETION_CONTEXT CompletionContext;KSPIN_LOCK IrpListLock;LIST_ENTRY IrpList;PVOID FileObjectExtension; } FILE_OBJECT, *PFILE_OBJECT;在文件系統(tǒng)的過濾驅(qū)動(dòng)的編程中,我們經(jīng)常要使用到這個(gè)參數(shù),來獲取這次操作所涉及到的文件對(duì)象,以此得到這個(gè)文件的相關(guān)信息。由于本次筆記重點(diǎn)是數(shù)據(jù)結(jié)構(gòu)的學(xué)習(xí),相關(guān)的使用場(chǎng)景打算在后續(xù)的學(xué)習(xí)筆記中進(jìn)行應(yīng)用。
?
總結(jié)
以上是生活随笔為你收集整理的IRP IO_STACK_LOCATION 《寒江独钓》内核学习笔记(1)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Element-Ui 复选框动态改变绑定
- 下一篇: 2019招商银行信用卡中心秋招IT笔试编