頁表是軟體實現的,但是頁表的查詢是mmu完成的,所以硬體定義了頁表的實現規則,軟體可以做的只有選擇頁表的級數,是否使用huge page以及填充對應的許可權標誌位。前面的文章主要介紹了頁表的實現規則,本文將討論linux系統中頁表的具體實現。
相關資料結構
還是那個**頁表,但增加了很多內容。
頁表pgd的首位址是放在前面介紹到的mm_struct中的,pgd_offset(), pmd_offset(), pmd_offset()分別用於從虛擬(線性)位址中提取查詢pgd, pmd和pte表所需的index。
linux中用page_shift表示乙個pte所對應的記憶體範圍(也就是乙個page的大小)所需占用的bit數(也就是12), pmd_shift和pgdir_shift分別表示乙個pmd和乙個pgd所對應的記憶體範圍所需占用的bit數。
page_size, pmd_size, pgdir_size則分別表示乙個pte, pmd, pgd所對應的記憶體範圍的大小。
page_mask, pmd_mask, pgdir_mask則是用於提取位址位的掩碼。
#define page_mask (~(page_size-1))
對於頁表描述符中的標誌位, linux中基本都有對應的函式來處理。比如pte中的r/w位,linux用_page_write巨集表示,pte_write()用於檢測該位,判斷page是否可寫;pte_mkwrite()可將該位置1,設定page為可寫;pte_wrprotect()可將該位清0,設定page為不可寫。
和硬體的配合
不同的處理器支援的頁表級數是不一樣的,比如ia-32只支援2級,x64支援4級甚至5級,而linux作為乙個通用的作業系統,需要相容不同的硬體平台,如果針對每種硬體平台進行不同的頁表實現,那就太麻煩了。試想,如果linux採用2級頁表,對ia-32到是沒問題,對x64就無能為力了,那如果linux統一採用4級或5級頁表,對x64的配合倒是剛剛好,那對ia-32呢?
其實不難,只要把表示pud中pmd entry個數的"ptrs_per_pmd"和表示pmd中pte entry個數的"ptrs_per_pmd"都設為1就可以了,相當於把中間兩級的pud和pmd摺疊了起來,pgd就等同於直接指向pte了。
程序頁表的建立
核心載入乙個程序後,需要為該程序建立屬於它的頁表。pgd_alloc(), pmd_alloc()和pte_alloc()分別用於建立pgd, pmd和pte。
因為頁表本身也是放在記憶體中的, 其本質上是由若干物理pages組成的,分配物理記憶體本身就是耗時的,加之在分配過程中需要關中斷,而頁表的建立和銷毀又是很頻繁的操作,而且現在的頁表多達四級甚至五級,所以整個過程的系統開銷是不可小覷的。
那怎麼加速這一過程呢?加速最常用的就是cache啦,具體的做法是把要提供給頁表建立的物理pages放到一組叫"quicklist"的鍊錶中,以後銷毀頁表釋放的頁就往這個quicklist裡送,建立頁表需要的頁就直接從這個quicklist裡找,其實就是乙個專用的freelist(原理和slab差不多),當然要比去整個物理記憶體的free pages list分配要快啦。
如果建立了新的對映關係,則需要呼叫mk_pte()建立乙個新的頁表項,因為頁表描述符由物理頁面號和許可權控制位組成,所以新建頁表項的工作主要就是填充這兩部分內容,組裝完成就可以呼叫set_pte()插入到對應的頁表中去了。
核心頁表的建立
核心剛被載入的時候,paging還沒有開啟,還工作在實模式。x86要求在開啟paging之前(置位cr0暫存器的pg位),軟體需要至少設定乙個page directory和乙個page table,從linux實現的角度,就是至少要有乙個pgd和乙個pte。
試想,開啟paging後,作業系統給出的位址對於cpu來說就是虛擬位址了,這時如果還沒有任何的頁表,怎麼去獲得實體地址,進而操作物理記憶體呢?
為此,linux採用的做法是:假設核心初始化時需要用到的物理記憶體不超過8mb(0x00000000到0x007fffff),若乙個page為4kb,則這8mb記憶體共含有2048個pages,需要2048個pte來對映。
乙個pte佔4個位元組,因此一共需要8kb的page(也就是2個page)來儲存,這2個用作pte頁表的page用pg0和pg1表示,分別放在實體地址為"0x00102000"和"0x00103000"的位置。
頁表是使用虛擬位址作為index的,在32位兩級頁表中,高10位是pgd表的index,對於"0x00000000"到"0x007fffff"的位址,index就是0和1。對於核心空間來說,"0x00000000"到"0x007fffff"的實體地址在保護模式下對應的虛擬位址為"0xc0000000"到0xc07fffff,高10位是"1100000000b和1100000001b",換算成10進製就是768和769。
這裡每個pgd entry和pte的最後4個bit都是0111b(7),結合頁表描述符的介紹可以知道,表示的是這8mb的記憶體是writable的,user和kernel都可以訪問的,present的。
movl %eax,%cr3 /* set the page table pointer.. */
置pg位為1,進入paging保護模式了
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* set paging (pg) bit */
參考:페이지 테이블 api - arm32 頁 頁表 頁表項
作業系統和計算機組成原理裡都講到記憶體管理的頁式管理,但是本人以及很多初次學習分頁的時候,都會迷茫頁表大小和頁表項大小之間的關係,本人仔細分析了後寫了這篇blog,僅當學習交流,個人理解之用,如果有錯或者分析不夠嚴謹,歡迎指正。按字 圖結合起來分析,相信還是比較容易看懂。號單元開始編址,稱之為目標模...
頁 頁表 頁表項
作業系統和計算機組成原理裡都講到記憶體管理的頁式管理,但是本人以及很多初次學習分頁的時候,都會迷茫頁表大小和頁表項大小之間的關係,本人仔細分析了後寫了這篇blog,僅當學習交流,個人理解之用,如果有錯或者分析不夠嚴謹,歡迎指正。按字 圖結合起來分析,相信還是比較容易看懂。號單元開始編址,稱之為目標模...
頁面大小和頁表項
單級頁表 假設某個系統物理記憶體大小為4gb,頁面大小為4kb,記憶體會被分成 2 20個記憶體塊,因此至少需要20個二進位制位,若系統按位元組定址 至少要3個位元組來表示頁表項。m號頁對應的頁表項存放位址x 3 m x為頁表在記憶體中存放的起始位址 因為乙個頁框大小等於頁面大小為4kb,每個程序對...