Linux 2.6內核筆記【內存管理】

4月14日

 

很多硬件的功能,物盡其用卻未必好過軟實現,Linux出於可移植性及其它原因,常常選擇不去過分使用硬件特性。

比如 Linux只使用四個segment,分別是__USER_CS、__USER_DS、__KERNEL_CS、__KERNEL_DS,因爲Paging可以完成segmentation的工作,而且可以完成的更好。而且這樣簡化了很多,統一了邏輯地址和線性地址。

而TSS存在每CPU一個的GDT中,雖然每個process的TSS不同,但Linux 2.6卻不利用其中的hardware context switch(雖然低版本使用)以一個far jmp來實現任務轉換,而用一系列的mov指令來實現。這樣做的原因是:

1、可以檢驗ds和es的值,以防惡意的forge。
2、硬轉換和軟轉換所用時間相近,而且硬轉換是無法再優化的,軟轉換則可以。

 

 

4月15日

 

Paging也就是將linear地址轉成物理地址的機制。

內存被視爲一堆4k的小page frame(就像空的格子),在歸OS管的Paging機制的苟延殘喘下,彷彿地存放着多於page frame數目的page(數據)。要通過兩層索引(directroy和table)來尋到page,再加offset尋到址。這兩層索引中的entry包含一些標誌表明該page在不在內存裏,是否被改寫過,最近是否訪問過,以及讀/寫訪問權限。

如果page entry裏的Page Size標誌和cr4的PSE標誌設置了的話(Extended Paging),就是4M一片page frame,這樣就只用directory一層索引了。

從奔騰pro開始,adress針腳非常神奇地從32增加到36,有了一個叫做PAE的機制,它啓用(cr4的PAE標誌設置)的時候就是2M一片page frame了。這樣可以尋址64GB,遠遠超越了沒啓用前4GB的理論極限(實際極限1GB)。但這樣的尋址非常彆扭,因爲物理地址雖然因此變成了36位,線性地址仍是32位,要想尋址超過4GB,要用cr3去指向不同的PDPT或在31-30bit指定PDPT中entry。不過,更鬱悶的是,這並不能改變process的地址空間4GB的限制,僅僅是內核可以用這麼多內存來運行更多的process。

 

在64位機器上,由於如果只用兩層的話,索引條目會太多,嚴重消耗內存,所以只好再加層數,alpha、ia64、ppc64、sh64都是3層(雖然每層bit數不一),x86_64非常神奇地用了4層。

 

Paging換的是page,Cache換的是line。但是如何在Cache中確定某個內存地址在不在呢?或者說,某內存地址附近的數據,放在Cache中什麼位置好呢?不能一對一映射過來(direct mapping),這樣會導致巨大的Cache;也不能隨意放(fully associative)然後在旁邊標記(tag)說是什麼地址附近的,這樣會導致每次找Cache都是線性查找。一個浪費空間一個浪費時間,因此有一種折衷叫做N-Way Set Associative,有點像Hash。首先把Cache分成很多個N line的集合,然後弄個hash函數把一個地址唯一地映射到某個集合裏,之後至於放在這N line中的哪一line就無所謂了。找的時候,先一瞬間找到集合,然後對N line進行線性查找。

 

讀的時候,自然有cache hit和cache miss。對於寫操作,cache hit的話,可能有兩種不同的處理方法:write-through(Cache和RAM都寫)和wirte-back(line換出時寫RAM)。Linux清空PCD (Page Cache Disable)和PWT (Page Write-Through),永遠啓用cache並使用write-back策略。

 

哈哈,TLB(Translation Lookaside Buffers )解決了我心中的一大疑問:每次尋址(將linear翻譯成physical),都要非常艱辛地查directroy和table,訪問多次RAM(你以爲這些東西不是放在RAM裏啊?!),豈不累死。幸好,我們有TLB,這樣最近翻譯的成果就可以緩存在裏面,這樣就省得每次翻譯啦。

 

4月17日

 

Linux用了四層索引來做Paging。這樣既可以通過隱藏掉中間兩層來做無PAE的32位paging,又可以隱藏掉pud來支持有PAE的3位paging,還可以支持64位的paging。

 

pte_t     Page Table

pmd_t   Page Middle Directory

pud_t    Page Upper Directory

pgd_t    Page Global Directory

 

每個進程的內存空間中0到PAGE_OFFSET(0xc0000000,即3G)-1是用戶空間,PAGE_OFFSET到0xffffffff(4G)則是內核空間(只有內核態才能尋址)。

 

啓動的時候,Linux問BIOS內存格局如何,保留第1個MB(machine_specific_memory_setup()),然後把自己放在第2個MB開始的地方(從_text到_etext是內核代碼,從_etext到_edata是初始化了的內核數據)。

 

在這個過程中:

 

Linux首先建立初始(provisional)頁表(startup_32()),使RAM前8M(兩頁)可以用兩種方式尋址,用來存放最小的自己(text、data、初始頁表、128k的堆空間)。

 

初始pgd放在swapper_pg_dir中。所有項爲0,但0、1與0x300、0x301分別完成線性地址的前8M和3G+8M到物理地址前8M的映射。

 

接着,Linux建立最終頁表。

 

線性地址最高的128M保留給Fix-Mapped Linear Addresses和Noncontiguous Memory Allocation用,所以,最終頁表只需要把PAGE_OFFSET後面的896M映射到物理地址的前896M。剩餘RAM由Dynamic Remapping來完成。然後用zap_low_mapping()把原先那個初始頁表清掉。

 

paging_init()會執行:

pagetable_init() //一個循環,初始化了swapper_pg_dir

cr3 <- swapper_pg_dir

cr4 |= PAE

__flush_tlb_all()

 

Linux利用CPU有限的指令和行爲模式,實現了一系列操縱tlb的函數,應用於不同的情境。

 

值得一記的是Lazy TLB模式,在多CPU系統中,它可以避免無意義的TLB刷新。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章