探究Windows 2000/XP原型PTE(http://webcrazy.yeah.net)

來源:互聯網
上載者:User
探究Windows 2000/XP原型PTE
              WebCrazy(http://webcrazy.yeah.net)

    記憶體管理可以說是作業系統實現中最重要的環節,也是最為複雜的一環節。對於相對貧乏的記憶體資源,記憶體共用也成了一個很重要的有效手段。Windows 2000/XP在此方面的實現藉助於一個稱為原型PTE(Prototype PTE,PPTE)的軟體機制。在《小議Windows NT/2000分頁機制》中我詳細的介紹了Intel X86實現分段、分頁的硬體PTE工作方式。我們來回顧一下這種機制:

    假設我們的一個進程映射了從虛擬位址0xXXXXXXXX(假設位於分配粒度上)開始的4M空間,而這4M空間當前都相應的映射了實際的實體記憶體(鑒於Lazy evaluation等的先進思想,這種情況在Windows 2000/XP中比較少見)。我們將這4M空間分成1000塊的4K(PAGE_SIZE,X86處理器決定),對於第n個4K(0<=n<1000),其虛擬位址(0xXXXXXXXX+n*4K),我們都有一個對應的硬體PTE,指出目前這4K駐留於實體記憶體的位置。通過由PDBR(CR3寄存器)與虛擬位址可定位這個硬體PTE(具體請參閱《小議Windows NT/2000分頁機制》)。

    現在讓我們來考慮這樣一種情況,我們有一個檔案其大小也為4M,我們知道通常我們要使用這個檔案都要將它讀入記憶體。試想同時有兩個或更多的進程需讀寫這個檔案,這就需要解決記憶體共用的問題。實際上就算當前只有一個進程訪問這個檔案,對於這種潛在的需要共用的檔案,Windows 2000/XP均會事先考慮共用情況。她通過一個稱為Section的核心對象來實現這樣的目的。仔細想想,這種情況下記憶體共用決不僅僅是記憶體資源的充分利用,就算我們可以為每個進程各分配4M空間,但是這將導致各個進程某種時刻可能得不到這個檔案的最新內容。這是非常糟糕的情況。在內部Windows 2000/XP利用原型PTE來解決這樣的情況。基於硬體PTE相同的原理,對於這樣一個4M的檔案,在映射這個檔案時,Windows 2000/XP同樣的將這個檔案分成1000塊,每塊4K(PAGE_SIZE)大小。然後從頁交換區分配1000個DWORD,每個DWORD值都是原型PTE,它們組成原型PTE表。對於這個檔案的第n個4K(0<=n<1000),如果當前其駐留在實體記憶體中的話,其對應的PPTE的Valid位(bit 0,與硬體PTE一致)為1,然後這個PPTE的Page Frame Number(PPTE的高20位)用於指示實體記憶體。如果當前其仍然在磁碟中的話,Valid位為0。針對這種情況,通過PPTE的高20位(PFN Entry),尋找Page Frame Datbase(由MmPfnDatabase定位),通過PFN Entry的Subsection PTE(windbg中稱為restore pte,《Inside Windows 2000》中稱為original pte,Windows XP內部稱為Subsection PTE),定位Subsection,然後通過Subsection指向的Control Area的FILE_OBJECT,與PPTE在PPTE表的位移n,通過公式:

    PFN Entry Subsection PTE->Subsection->Control Area->FileObject + n * 4K

定位所要訪問的檔案位移,這樣Windows 2000/XP使用頁面調入IO讀入這頁內容,更新PPTE表的這個PPTE。以上的這一系列定位轉換演算法,如Subsection PTE如何定位Subsection,我將另行介紹。上面的描述解決了一個非常重要的問題,我們不需要更新所有引用這一頁面的進程的硬體PTE,因為此時所有進程的PTE均指向PPTE,我們只要更新PPTE就能達到目的。至於進程PTE如何指向PPTE,下面我會涉及到這個內容。這兒你只要有一個概念,進程的PTE為了指向PPTE,肯定是一個Invalid PTE,即bit 0為0,而且其bit 10為1(PPTE標誌,具體請看我在《探尋Windows NT/2000 Copy On Write機制》列出的HARDWARE_PTE_X86結構)。

    對於PPTE,因為X86處理器沒有提供這樣一種方式,像處理硬體PTE一樣,由CPU直接進行地址轉換。Windows 2000/XP記憶體管理器在處理Page Fault時,通過軟體機制來類比這種實現,這可以說是硬體PTE與PPTE的一個本質區別。

    應該重點提出的是PPTE存在於頁交換區(由MmPagedPoolStart與MmPagedPoolEnd指定的位置,從虛擬位址0xE1000000開始),其本身也有可能被Page Out,Windows 2000/XP通過MiCheckProtoPtePageState判斷是否被Page Out,還有頁交換區的起始地址0xE10000000將用於從無效PTE轉化成原型PTE所在的地址,這等一下我會介紹到的。

    照例我們用SoftICE來驗證一下我們前面的描述:

    :bpint e

    只要我們截獲這個硬體中斷,我們就知道肯定發生了Page Fault,但是我們並不能確定這都是由於指向PPTE的無效PTE導致的。事實上Copy On Write等等其他機制,均會發生Page Fault(《探尋Windows NT/2000 Copy On Write機制》有詳細討論)。但是正如我們前面提及的PPTE的bit 10為1,我們還是很容易的判定一個Page Fault是不是由於指向PPTE的無效PTE導致的。由於發生Page Fault的虛擬位址由CR2寄存器指定,經過幾次嘗試以後,我們繼續以下的討論:

    Break due to BPINT 0E   (ET=2.23 Seconds)
    :cpu

    Processor 00 Registers
    ----------------------
    CS:EIP=0008:801648A4  SS:ESP=0010:FCBEADC8
    EAX=C002100B  EBX=77E74A02  ECX=00000102  EDX=00000000
    ESI=00085108  EDI=000493E0  EBP=0140FF74  EFL=00000006
    DS=0023  ES=0023  FS=0038  GS=0000

    CR0=8000003B PE MP TS ET NE PG
    CR2=77D3BB26   //發生Page Fault的虛擬位址。
       .
       .
       .

    :page 77d3bb26
    Linear     Physical   Attributes
    77D3BB26   NP 01A714F6

    從PTE值01A714F6的bit 10為1我們知道這是一個指向PPTE的無效PTE。通過query命令我們可以找到CR2指定的地址,位於模組rpcrt4.dll中。從下面可以看到:

    :query 77d30000
    Context   Address Range      Flags     MMCI      PTE       Name
    explorer  77D20000-77D8E000  07100001  FF8D1328  E169C580  rpcrt4.dll

    結合我文章開始的介紹,通過以下的計算:

    :? (77d3bb26-77d20000)/1000*4+e169c580
    unsigned long = 0xE169C5EC, -513161748, "/xE1i/xC5/xEC"

    我們可以得到其實PTE 01A714F6應該指向0xE169C5EC位置。這時候由MMCI指向的Control Area,根據我上面提到的計算公式,即可以讀出rpcrt4.dll位移(0xE169C5EC-0XE169C580)/4*1000處,即0x1B000處的4K位元組,讀入虛擬位址77D3B000中((0xE169C5EC-0XE169C580)/4*1000+77D20000),而CR2指定的地址77D3BB26肯定在這4K之中。

    其實這樣我們已經描述了MmAccessFault處理指向PPTE的無效PTE的一個典型過程。這裡只是示範了原型PTE指向的頁面未駐留在實體記憶體的情況,試想如果我們的頁面已經在實體記憶體了,我們還有必要去費時的尋找VAD嗎?這就要涉及到無效的PTE如何定位原型PTE,所以我一直使用指向PPTE的無效PTE的叫法。《Inside Windows 2000》中指出指向PPTE的無效PTE的具體格式,但我發現其描述的不盡正確,我一直深信像作者那樣能觸及Windows 2000代碼的人肯定不會有什麼問題,所以我在理解PPTE時一直卡在此處。後來通過反組譯碼實現時發現實際上通過下面的方式來計算PPTE的位置:

   (PTE>>2) & 0x3FFFFE00 + (PTE & 0x000000FF) << 1 + 0xE1000000

    其中PTE為指向PPTE的無效PTE,0xE10000000是頁交換區的起始地址。同樣我們使用上面的例子來示範這個演算法:

    上面的無效PTE為01A714F6,有了這個值,我們可以得到:

       PPTE Address = (0x01A714F6 >> 2) & 0x3FFFFE00 + (0x01A714F6 & 0x000000FF) << 1 + 0xE1000000
                    = 0x0069C53D & 0x3FFFFE00 + 0xF6 << 1 + 0xE1000000
                    = 0x69C400 + 0x1EC + 0xE1000000
                    = 0xE169C5EC

    與我們通過VAD尋找到的PPTE位置0xE169C5EC一致。

    為了更好的理解PPTE,我們再來看一個例子。我們知道在Windows 2000/XP中ntdll.dll是個非常重要的dll,只要作業系統正常啟動,ntdll肯定會被多個進程共用。我們用SoftICE作如下分析:

    :query -x 77f50000
    Context   Address Range      Flags     MMCI      PTE       Name
    smss      77F50000-77FF8000  07100005  80E6FA50  E131F9E8  ntdll.dll
        .
        .
        .
    explorer  77F50000-77FF8000  07100005  80E6FA50  E131F9E8  ntdll.dll
        .
        .
        .

    :addr smss
    :mod ntdll
    hMod Base     PEHeader Module Name      File Name
         77F50000 77F500E8 ntdll            /WINDOWS/system32/ntdll.dll

    根據ntdll的基地址77F50000,我們查看其硬體PTE:

    :dd 1df*1000+350*4+c0000000 l 4 //詳細請參考《小議Windows NT/2000分頁機制》
    0010:C01DFD40 02267027  02F2E005  02F2F005  00C7E4FA      'p&.............

    從smss進程的這些頁表,我們很容易知道ntdll.dll第1至3個4K均駐留於實體記憶體地址中,因為它們都是有效硬體PTE,而第四個PTE(00C7E4FA),雖然其是一個無效PTE(bit 0為0),但由於其是一個指向PPTE的PTE(bit 10為1),所以我們不能僅憑此PTE是個無效PTE,就斷定ntdll.dll的第4個4K就不在實體記憶體中。我們要進一步的分析這個PTE,找出指向的PPTE判斷這第4個4K是不是真的就是在磁碟中。OK,通過上面提及的演算法,我們很容易的算出PPTE Address為E131F9F4,我們來看看這個PPTE的值:

    :dd e131f9f4 l 4 
    0010:E131F9F4 02F30121  02F31121  02F32121  02F33121      !...!...!!..!1..

    從值02F30121我們這時就可以判定這第4個4K也存在於物理地址中,位於Page Frame Number為02F30的實體記憶體中,剩下的就是查PFN Database了。

    我們也可以來查看查看explorer進程的ntdll.dll映射情況,來驗證一下這種情況:

    :addr explorer
    :dd 1df*1000+350*4+c0000000 l 4
    0010:C01DFD40 02267025  02F2E025  02F2F025  02F30025      %p&.%...%...%...

    這回清楚了吧。文章開頭我提及:“我們不需要更新所有引用這一頁面的進程的硬體PTE,因為此時所有進程的PTE均指向PPTE,我們只要更新PPTE就能達到目的了”。從中我們也可以看到ntdll.dll的第4個4K實際上位於實體記憶體中,但Windows 2000/XP並沒有更新每個引用此頁面的PTE,就正如smss進程一樣。而PPTE卻已經指向其實際地址了。當smss進程首次訪問這個地區時,記憶體管理器才將02F30025(假設屬性與explorer進程使用這頁的屬性一樣且為考慮訪問位標誌)這個有效硬體PTE更新上面的00C7E4FA,現在一切都明朗了吧。

    本文雖然著重點在於介紹PPTE,但實際上我已將Section對象的內部機制說得非常清楚。這也是我原先將文章標題定為剖析Section之類的。關於PPTE,我的理解也經曆了較多時間,主要是目前這部分資料實在是沒有,僅有的《Inside Windows 2000》在沒深入介紹的同時其指向PPTE的無效PTE格式未明確指出(特別是加上0xE1000000,這讓我吃盡了苦頭),本文介紹的這個格式我已經在Windows 2000及XP上測試過,實際上本文的兩個例子一個是在Windows 2000 Server Build 2195,另一個在XP專業版Build 2600上示範的。

    在這次介紹PPTE後,我們來回顧一下記憶體管理器內部的幾個千絲萬縷的聯絡:

    FILEOBJECT的SECTION_OBJECT_POINTERS->DataSectionObject或SECTION_OBJECT_POINTERS->ImageSectionObject(決定於Section對象映射的檔案的開啟檔案)指向Control Area,同時進程描述這檔案對應的虛擬位址的VAD的MMCI成員(SoftICE叫法)也指向這個Control Area,Control Area底下存在一至多個SubSection,SubSection指向PPTE,PPTE table一般位於Control Area指向的Segment結構的底部。Section對象指向Segment;進程Page Table指向PPTE;這一切現在已描述的比較清楚了。還有一個主要的聯絡,即PFN Entry的Restore PTE(Original PTE)指向Subsection,這個關係我將在下次予以介紹。

    從《小議Windows NT/2000分頁機制》到今天這篇介紹PPTE,我對Windows 2000/XP的記憶體管理部分才有了比較深入的理解,至於未提及到的Working Set等概念也是非常重要的。經曆過很多的模糊,對記憶體管理器也總算有了些許概念了。所有討論均基於自己的理解,對錯請多多指教(tsu00@263.net)。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.