Linux從頭學15:【頁目錄和頁表】-理論 + 實例 + 圖文的最完全、最接地氣詳解(Linux頁表)

      網友投稿 760 2025-04-04

      作 者:道哥,10+年嵌入式開發老兵,專注于:

      C/C++、嵌入式、linux

      關注下方公眾號

      ,回復【

      書籍

      】,獲取 linux、嵌入式領域經典書籍;回復【

      PDF

      】,獲取所有原創文章( PDF 格式)。

      目錄

      頁表的拆分過程

      頁目錄結構

      幾個相關的寄存器

      加載用戶程序時: 頁目錄、頁表的分配和填充過程

      線性地址到物理地址的查找、計算實例

      在x86系統中,為了能夠更加充分、靈活的使用

      物理內存

      ,把物理內存按照4KB的單位進行分頁。

      然后通過中間的映射表,把

      連續的虛擬內存

      空間,映射到

      離散的物理內存

      空間。

      映射表中的每一個表項,都指向一個物理頁的開始地址。

      但是這樣的映射表有一個明顯的

      缺點

      :映射表自身也是需保存在

      物理

      內存中的。

      在 32 位系統中,它使用了多達4MB的物理內存空間(每個表項4個字節,一共有4G/4K個表項)。

      為了解決這個問題,x86處理器使用了兩級轉換:

      頁目錄和頁表

      這篇文章,我們就從最基礎的底層計算過程入手,把這個最重要的內存管理機制搞定,以后再學習更深入的知識點時,就會更容易理解了。

      頁表的拆分過程

      在一個32位的系統中,

      物理

      內存的最大可表示空間就是0xFFFF_FFFF,也就是4GB。

      雖然實際安裝的物理內存可能遠遠沒有這么大,但是在設計內存管理機制的時候,還是需要按照

      最大的

      可尋址范圍來進行設計的。

      按照一個物理頁4KB的單位來劃分,4GB空間可以分割為1024 * 1024個物理頁:

      在上一篇文章中,使用

      單一的

      映射表來指向這些物理頁,導致了映射表自身占據了太多的物理內存空間。

      一個用戶程序中定義的幾個段,可能實際上只使用了很小的空間,完全用不到 4 MB。

      但是仍然需要為它分配多達 4MB 的物理內存空間來保存這個映射表,很浪費。

      為了解決這個問題,可以把這個單一映射表

      拆分

      成1024個體積更小的映射表:

      每一個映射表中,只有 1024 個表項,每一個表項仍然指向一個物理頁的起始地址;

      一共使用 1024 個這樣的映射表;

      這樣一來,1024(每個表中的表項個數) * 1024(表的個數),仍然可以覆蓋4GB的物理內存空間。

      這里的每一個表,就稱作

      頁表

      ,所以一共有1024個頁表。

      一個頁表中一共有1024個表項,每一個頁表項占用4個字節,所以一個頁表就占用4KB的物理內存空間,正好是一個物理頁的大小。

      也許有的小伙伴就開始算賬了:一個頁表自身占用4KB,那么1024個頁表一共就占用了4MB的物理內存空間,仍然是很多啊?

      是的,從總數上看是這樣,但是:一個應用程序是

      不可能完全使用全部的 4GB 空間的

      ,也許只要幾十個頁表就可以了。

      例如:一個用戶程序的代碼段、數據段、棧段,一共就需要10 MB的空間,那么使用3個頁表就足夠了,加上頁目錄,一共需要16 KB的空間。

      計算過程:

      每一個頁表項指向一個 4KB 的物理頁,那么一個頁表中 1024 個頁表項,一共能覆蓋 4MB 的物理內存;

      那么 10MB 的程序,向上對齊取整之后(4MB 的倍數,就是 12 MB),就需要 3 個頁表就可以了。

      Linux從頭學15:【頁目錄和頁表】-理論 + 實例 + 圖文的最完全、最接地氣詳解(Linux頁表)

      記住上圖中的一句話:

      一個頁表,可以覆蓋 4MB 的物理內存空間(1024 * 4 KB)。

      頁表中,每一個表項的格式如下:

      注意下面的這幾個屬性:

      P(Present): 存在位。1 - 物理頁存在; 0 - 物理頁不存在;

      RW(Read/Write): 讀/寫位。1 - 這個物理頁可讀可寫; 0 - 這個物理頁只可讀;

      D(Dirty): 臟位。表示這個物理頁中的數據是否被寫過;

      頁目錄結構

      現在,每一個物理頁,都被一個

      頁表

      中的一個表項來指向了,那么這1024個頁表的地址,應該怎么來管理呢?

      答案是:

      頁目錄表!

      顧名思義:

      在頁目錄中,每一個表項指向一個頁表的開始地址(物理地址)

      操作系統在加載用戶程序的時候,不僅僅需要分配物理內存,來存放程序的內容;

      而且還需要分配物理內存,用來保存程序的頁目錄和頁表。

      再來算算賬:

      剛才說過:

      每一個頁表

      覆蓋4MB的內存空間,那么頁目錄中一共有1024個表項,指向1024個頁表的物理地址。

      那么

      頁目錄

      能覆蓋的內存空間就是1024 * 4MB,也就是4GB,正好是32位地址的最大尋址范圍。

      頁目錄中,每一個表項的格式如下:

      其中的屬性字段,與頁表中的屬性類似,只不過它的描述對象是

      頁表

      還有一點:

      每一個用戶程序都有自己的頁目錄和頁表!

      下文有詳細說明。

      幾個相關的寄存器

      現在,所有

      頁表

      的物理地址被

      頁目錄

      表項指向了,那么

      頁目錄的物理地址,處理器是怎么知道的呢

      答案就是:CR3 寄存器,也叫做: PDBR(Page Table Base Register)。

      這個寄存器中,保存了當前

      正在執行

      的那個任務的頁目錄地址。

      每個任務(程序)都有自己的頁目錄和頁表,頁目錄表的地址被記錄在任務的TSS段中。

      當操作系統調度任務的時候,處理器就會找到即將執行的

      新任務

      的 TSS段信息,然后把

      新任務的頁目錄開始地址

      更新到CR3寄存器中。

      當新任務的指令開始被執行時,處理器在獲取指令、操作數據時,操作的是

      線性地址

      頁處理單元就會從CR3 寄存器中保存的頁目錄表開始,把這個

      線性地址

      最終轉換成

      物理地址

      當然,處理器中還有一個快表,用來加快從線性地址到物理地址的轉換過程。

      CR3寄存器的格式如下:

      順便把官網上的其他幾個控制寄存器都貼出來:

      其中,CR0寄存器的最高位PG,就是

      開啟頁處理單元的開關

      也即是說:

      當系統上電之后,剛開始的地址尋址方式一直是

      [段:偏移地址]

      的方式。

      當啟動代碼準備好頁目錄和頁表之后,就可以設置

      CR0.PG = 1

      此時,處理器中的頁處理單元就開始工作了:

      面對任何一個線性地址,都要經過頁處理單元之后,才得到一個物理地址。

      加載用戶程序時: 頁目錄、頁表的分配和填充過程

      在之前的文章中,介紹過一個用戶程序被操作系統加載的全過程,簡述如下:

      讀取程序 header 信息,解析出程序的總長度,從任務自己的虛擬內存中分配一塊足夠的連續空間;

      分配一個空閑物理頁,用作程序的頁目錄,頁目錄的地址會記錄在稍后創建的 TSS 段中;

      使用虛擬內存中的線性地址,分配一個物理頁(4 KB),登記到頁目錄和頁表中;

      從硬盤上讀取 8 個扇區的數據(每個扇區 512 字節),存放到剛才分配的物理頁中;

      檢查程序內容是否讀取完畢:是-進入第 6 步;否-返回到第 3 步;

      為用戶程序創建一些必要的內核數據結構,比如:TSS、TCB/PCB 等等;

      為用戶程序創建 LDT,并且在其中創建每一個段描述符;

      把操作系統的頁目錄中高端地址部分的表項,復制給用戶程序的頁目錄表。

      這樣的話,所有用戶程序的頁目錄中,高端地址的表項都指向相同的頁表地址,就達到了共享“操作系統空間”的目的。

      這里主要聊一下第3步,假設用戶程序文件在硬盤上的長度是20 MB,電腦中實際安裝的物理內存是1 GB。

      可以先計算一下:頁目錄中,每一個表項覆蓋的空間是 4 MB,那么 20 MB的數據,需要 5 個表項就可以了。

      在初始狀態,

      頁目錄

      中的所有表項都是空的,其中的P位都是為0,表示

      頁表不存在

      操作系統首先從

      虛擬內存

      中,分配一塊20 MB的空間,假設從1 GB(0x4000_0000)的地址處開始吧,這個地址是

      線性地址

      也就是說把應用程序的文件讀取到內存中,從地址0x4000_0000開始存放,向高地址方向增長。

      注意:在“平坦”型分段模型下,線性地址等于虛擬地址。

      0x4000_0000 = 0100_0000_0000_0000___0000_0000_0000_0000

      前10位表示該線性地址在

      頁目錄中的索引

      ,中間10位表示

      頁表中的索引

      ,最后12位表示

      物理頁中的偏移地址

      因此,前10位就是 0100_0000_00,表示這個線性地址位于頁目錄中的第256個表項:

      操作系統發現這個表項中為

      空,沒有指向任何一個頁表

      于是就從物理內存中,找一個

      空閑的物理頁

      ,用作頁目錄中第256個表項指向的

      頁表

      注意:這個物理頁是用作頁表,而

      不是

      用作存儲用戶程序文件。

      假設在物理內存上128 MB (0x0800_0000)的地址處,找到一個空閑的物理頁,用作這個頁表。

      把頁表中的1024個表項全部清空,并且把

      頁表的物理地址

      0x0800_0000,登記在

      頁目錄

      中的第256個表項中:0x08000(上圖黃色部分)。

      為什么不是 0x0800_0000?

      因為一個物理頁的地址一定是4KB對齊的(最后的12位全部為0),所以頁目錄的表項中只需要記錄頁表地址的

      高 20 位

      即可。

      現在,頁表也有了,下面就是

      分配一個物理頁來存儲程序的內容

      假設在剛才那個物理頁(用作

      頁表

      的那個)的上面,又找到一個空閑的物理頁,地址是:0x0800_1000。

      此時,這個用于存放程序內容的物理頁的地址,就需要記錄在

      頁表的一個表項中

      了。

      那么應該記錄在頁表中的什么位置呢?也就是應該登記在

      哪一個表項

      中呢?

      需要根據線性地址的

      中間 10 位

      來確定:

      0x4000_0000 = 0100_0000_0000_0000___0000_0000_0000_0000

      中間10位的全部是0,說明索引值就是0,也就是說頁表中的第0個表項,保存這個

      物理頁的地址

      ,如下圖所示:

      一個物理頁的地址一定是4KB對齊的(最后的12位全部為0),所以只需要記錄物理頁地址的

      高 20 位

      即可。

      用于存儲程序文件內容的物理頁分配好了,下面就開始從硬盤中讀取程序文件的內容了。

      一個物理頁的大小是4 KB,硬盤上一個扇區的大小是512 B,那么從硬盤上連續讀取8個扇區的數據就可以把一個物理頁寫滿。

      剛才已經假設:用戶程序文件在硬盤上的長度是20 MB。

      當讀取了一個物理頁的內容后,通過計算發現用戶程序內容還

      沒有讀取完

      ,于是繼續重復以上流程。

      線性地址增加 4KB:0x4000_1000 = 0100_0000_0000_0000___0001_0000_0000_0000;

      前 10 位沒有變,仍然是頁目錄中的第 256 個表項,發現這個表項指向的頁表已經存在了,于是就不用再分配物理頁用作頁表了;

      分配一個空閑物理頁,用于存放程序內容,假設在 0x0100_4000處找到一個,把這個地址登記在頁表中;

      此時,線性地址的中間 10 位的索引值是 1,所以登記在頁表中的第 1 個表項。

      從硬盤上讀取 8 個扇區的數據,寫入這個物理頁;

      因為

      頁目錄

      中一個表項所覆蓋的范圍是4 MB(也就是一個頁表中1024個表項所指向的物理頁空間的總和)。

      所以當讀取了4 MB的程序內容之后,這個

      頁表中的所有表項

      就被填滿了。

      此時,讀取的程序內容所占用的

      【線性地址】

      空間是:0x4000_0000 ~ 0x403F_FFFF。

      下面再繼續讀取新內容時,就從 0x4040_0000 這個線性地址處開始存放,讀取過程與上面都是一樣的:

      確定頁目錄表項:

      0x4040_0000 = 0100_0000_0100_0000___0000_0000_0000_0000,前 10 位的索引值是 257;

      發現 257 這個表項為空,于是分配一個空閑的物理頁,用作頁表;

      分配一個物理頁,用作存儲程序文件的內容,并把這個物理頁的地址記錄在頁表中;

      線性地址 0x4040_0000 的中間 10 位的索引值是 0,所以登記在頁表的第一個表項中;

      后面的過程就不再嘮叨了,一樣一樣的~~

      最終的

      頁目錄和頁表

      的布局,類似下面這張圖:

      線性地址到物理地址的查找、計算實例

      如果理解了上一個主題的內容,那么部分應該就可以不用再看了,因為它倆是

      相反的

      過程,而且查找過程更簡單一些。

      仍然繼續我們的假設:

      用戶程序的長度是 20 MB,存放在虛擬內存 0x4000_0000 ~ 0x4140_0000 (線性地址)這段空間內;

      代碼段的長度是 8 MB,從虛擬內存的 0x40C0_0000 處開始存放;

      也就是如下圖所示:

      現在,用戶程序的內容已經全部讀取到內存中了,

      頁目錄、頁表

      全部都安排妥當了。

      在頁目錄表中,一共有

      5 個表項

      ,正好表示這20MB的地址空間。

      其中,

      8 MB 的代碼

      所存儲的物理頁地址,登記在頁目錄表中的

      259 和 260

      這兩個表項中(上圖右側的綠色表項)。

      目標:

      處理器在執行代碼時,遇到一個

      線性地址

      0x4100_8800,頁處理單元需要把它轉換得到

      物理地址

      0x4100_8800 = 0100_0001_0000_0000___1000_1000_0000_0000

      首先,根據線性地址的

      前 10 位(0100_0001_00)

      ,得到它在頁目錄中的索引值為260。

      這個表項中記錄的頁表地址為 0x08040,因為頁表地址的低12位一定是bit0,因此這個頁表的地址就是0x0804_0000。

      頁目錄表的開始地址,肯定是從 CR3 寄存器獲取的;

      然后,根據線性地址的

      中間 10 位(00_0000___1000)

      ,得到頁表中的索引值為8。

      這個表項中記錄的物理頁地址為 0x02004,補上低位的12個bit0,就得到物理頁的開始地址是0x0200_4000。

      最后,根據線性地址的

      最后 12 位(1000_0000_0000)

      ,得到它在物理頁的偏移量 2048。

      也就是說:從物理頁的開始地址(0x0200_4000),偏移2048個字節的地方,就是這個

      線性地址

      (0x4100_8080)對應的

      物理地址

      (0x0200_4800)。

      大功告成!

      關于虛擬地址到物理地址的轉換、頁目錄和頁表的查找過程,基本就討論結束了。

      不知道客官您是否已經酒足飯飽?

      下周再寫一篇對頁目錄和頁表自身的

      “元操作”

      ,這個系列文章就基本結束了。

      如果還滿意的話,請您鼓勵一下,

      點個贊,轉發

      給朋友圈中的技術小伙伴,這也是對我最大的鼓勵,非常感謝!

      推薦閱讀

      【1】C語言指針-從底層原理到花式技巧,用圖文和代碼幫你講解透徹

      【2】一步步分析-如何用C實現面向對象編程

      【3】原來gdb的底層調試原理這么簡單

      【4】內聯匯編很可怕嗎?看完這篇文章,終結它!

      其他系列專輯

      :精選文章、C語言、Linux操作系統、應用程序設計、物聯網

      Linux 嵌入式

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:如何開 OKR 復盤
      下一篇:excel提示打開的文件.xls的格式與文件擴展名不一致怎么辦
      相關文章
      亚洲人成在线播放网站| 成人亚洲性情网站WWW在线观看| 伊人久久亚洲综合| AV在线亚洲男人的天堂| 亚洲AV无码专区日韩| 男人的天堂亚洲一区二区三区 | 欧美亚洲精品一区二区| 亚洲AV成人一区二区三区在线看 | 夜色阁亚洲一区二区三区| 18禁亚洲深夜福利人口| 亚洲午夜无码毛片av久久京东热| 国产色在线|亚洲| 亚洲字幕AV一区二区三区四区| 亚洲成人激情小说| 亚洲欧美国产日韩av野草社区| 亚洲精品蜜夜内射| 日本亚洲高清乱码中文在线观看| 狠狠综合亚洲综合亚洲色| 日韩亚洲翔田千里在线| 亚洲另类少妇17p| 在线观看亚洲成人| 国产V亚洲V天堂A无码| 亚洲av中文无码乱人伦在线r▽| 亚洲Av无码精品色午夜| 亚洲视频在线一区| 亚洲综合久久成人69| 亚洲人成电影在线观看青青| 亚洲综合色婷婷在线观看| 亚洲欧美第一成人网站7777| 国产精品亚洲一区二区无码| 久久久久亚洲AV成人网| 亚洲AV中文无码字幕色三| 亚洲第一精品电影网| 亚洲娇小性色xxxx| 亚洲A∨精品一区二区三区下载| 国产天堂亚洲国产碰碰| 在线A亚洲老鸭窝天堂| 亚洲av无码一区二区乱子伦as| 夜夜亚洲天天久久| 亚洲sss综合天堂久久久| 亚洲经典千人经典日产|