Linux從頭學15:【頁目錄和頁表】-理論 + 實例 + 圖文的最完全、最接地氣詳解(Linux頁表)
作 者:道哥,10+年嵌入式開發老兵,專注于:
C/C++、嵌入式、linux
。
關注下方公眾號
,回復【
書籍
】,獲取 linux、嵌入式領域經典書籍;回復【
】,獲取所有原創文章( 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 個頁表就可以了。
記住上圖中的一句話:
一個頁表,可以覆蓋 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小時內刪除侵權內容。