您当前的位置: 首页> 新闻详情
 

MDL 學步園

2020-02-27 05:12:03 来源:88bifa-必发娱乐-必发娱乐官方网站 浏览次数 54

  內存描述符列表 (MDL) 是一個系統定義的結構,通過一系列物理地址描述緩衝區。執行直接 I/O 的驅動程序從 I/O 管理器接收一個 MDL 的指針,並通過 MDL 讀寫數據。一些驅動程序在執行直接 I/O 來滿足設備 I/O 控制請求時也使用 MDL。

  這裡有完整的內容,但該文章是機器人翻譯過來的,所以看起來有點頭疼.

  因此通俗的解釋一下,MDL僅僅運用於內核中,在應用層並不會涉及這個結構,由於內核中的驅動有跟應用層程序通信的需要,因此可能會接收到來自進程空間的虛擬地址,而在windows的分頁機制下,進程空間中的任何一個虛擬地址所屬的頁面都有可能被內存管理器從RAW置換到頁文件中,或者,進程被釋放或是取消地址的映射。這些都會導致嚴重的錯誤發生。因此內核創建一個MDL,並將其與來自進程空間的虛擬地址相關聯,當需要對這些虛擬地址進行讀寫的時候調用相關的內核函數,鎖定這些虛擬地址對應的物理頁面和邏輯頁面,防止物理頁面被置換,邏輯頁面被修改或者釋放。

  另外一種情況下一個驅動程序在執行純內核任務中也可以使用MDL,特別的僅僅調用非分頁內存的話,這些頁面是不會置換到頁文件中的,因此不需要考慮鎖定頁面的問題。

  Next:MDL可以連接成一個單鏈表,因此可以將分散的虛擬機地址串接起來。

  Size:一個MDL並不單單包含結構里這些東西,在內存中緊接著一個MDL結構,存著這個MDL對應的各個物理頁面編號,由於一個物理頁面一定是4KB對齊的,所以這個編號相當於一個物理頁面起始地址的高20位。Size的值減去sizeof(MDL),等於存放編號的區域的大小。比如該MDL需要三個物理頁面來映射虛擬地址空間,則Size-sizeof(MDL)==4*3==12;

  MdlFlags:很重要的欄位,用於描述和操控虛擬地址的各種屬性。

  Process:如果虛擬地址是某一進程的用戶地址空間,那麼MDL代表的這塊虛擬地址必須是從屬於某一個進程,這個成員指向從屬進程的結構

  返回,則MappedSystemVa不會與StartVa有這樣的對應關係,因為此時對應的物理頁面還沒有被映射到內核空間。(此處未定,MmProbeAndLockPages是否會到PDE與PTE中建立映射,未知。)

  StartVa:虛擬地址空間的首地址,當這塊虛擬地址描述的是一個用戶進程地址空間的一塊時,這個地址從屬於某一個進程。

  ByteCount:虛擬地址塊的大小,位元組數

  由於WDK文檔中表述得比較模糊,上面的說明有些僅僅是猜測。我們可以通過DBG調試一個驅動,觀察它的內存原始數據來證實我們的推測。

  下面是取自tdifw中的一段代碼,它為一個IPV4地址ctx-tai 分配一個非分頁內存塊,然後調用IoAllocateMdl創建一個針對這個虛擬地址的MDL,最後調用MmBuildMdlForNonPagedPool來建立虛擬地址與物理頁面直接的映射。

  可以看到tai的首地址是0x82040928,這是一個非分頁內存中的虛擬地址,長度是0x55。

  startva是0x82040000,這說明startva目前表示的是tai所指向的虛擬地址的頁起始地址,bytecount是55,這代表了虛擬地址的大小,byteoffset是0x929,因此這個位元組表示的是虛擬地址相對於頁的偏移地址。

  mdlflags變成了12,mappedsystemva真正指向了虛擬地址,process被置0,說明這是一個非分頁地址,它不屬於任何一個進程的地址空間。

  實際上size的大小並不等於MDL結構的大小,因為在MDL後面緊跟著一個表示物理頁面的數組,只是沒有在結構體中表現出來,這應該是為了避免一般的驅動程序直接修改這些物理數組,因為虛擬地址和物理頁面的映射只應該由內存管理器來維護。在剛才的調試中觀察內存發現,在MDL後面只有一個物理頁面編號。此編號在調用IoAllocateMdl的時候並未初始化,而是在 MmBuildMdlForNonPagedPool(mdl)中被賦的值。有些人認為 MmBuildMdlForNonPagedPool是把物理頁面映射到系統地址空間中,這種說法應該是錯誤的,因為對於非分頁內存,在調用ExAllocatePool系列函數的時候,內存管理器就建立了映射關係,否則這些內存根本無法使用,實際上, MmBuildMdlForNonPagedPool的作用是把這種映射保存到MDL中,使其變得不透明,以滿足某些驅動的需求。

  典型的,當運行在內核中的一個驅動向另一個驅動發送請求的時候,其中一種數據傳輸方式將運用到MDL。

  順便說一下,我們不可以直接訪問MDL的任何成員。應該使用宏或訪問函數,

  對於I/O管理器執行的Direct方式的讀寫操作,其過程可以想像為下面代碼:

  I/O管理器首先創建一個描述用戶緩衝區的MDL。IoAllocateMdl的第三個參數(FALSE)指出這是一個主數據緩衝區。第四個參數(TRUE)指出內存管理器應把該內存充入進程配額。最後一個參數(Irp)指定該MDL應附著的IRP。在內部,IoAllocateMdl把Irp-MdlAddress設置為新創建MDL的地址,以後你將用到這個成員,並且I/O管理器最後也使用該成員來清除MDL。

  這段代碼的關鍵地方是調用MmProbeAndLockPages(以粗體字顯示)。該函數校驗那個數據緩衝區是否有效,是否可以按適當模式訪問。如果我們向設備寫數據,我們必須能讀緩衝區。如果我們從設備讀數據,我們必須能寫緩衝區。另外,該函數鎖定了包含數據緩衝區的物理內存頁,並在MDL的後面填寫了頁號數組。在效果上,一個鎖定的內存頁將成為非分頁內存池的一部分,直到所有對該頁內存加鎖的調用者都對其解了鎖。

  在Direct方式的讀寫操作中,對MDL你最可能做的事是把它作為參數傳遞給其它函數。例如,DMA傳輸的MapTransfer步驟需要一個MDL。另外,在內部,USB讀寫操作總使用MDL。所以你應該把讀寫操作設置為DO_DIRECT_IO方式,並把結果MDL傳遞給USB匯流排驅動程序。

  順便提一下,I/O管理器確實在stack-Parameters聯合中保存了讀寫請求的長度,但驅動程序應該直接從MDL中獲得請求數據的長度。

 
88bifa-必发娱乐-必发娱乐官方网站