掃描二維碼
隨時隨地手機看文章
作 者:道哥,10 年嵌入式開發(fā)老兵,專注于:C/C 、嵌入式、Linux。關(guān)注下方公眾號,回復【書籍】,獲取 Linux、嵌入式領(lǐng)域經(jīng)典書籍;回復【PDF】,獲取所有原創(chuàng)文章( PDF 格式)。
在上一篇文章中,使用單一的映射表來指向這些物理頁,導致了映射表自身占據(jù)了太多的物理內(nèi)存空間。
一個用戶程序中定義的幾個段,可能實際上只使用了很小的空間,完全用不到 4 GB。為了解決這個問題,可以把這個單一映射表拆分成1024個體積更小的映射表:但是仍然需要為它分配多達 4MB 的物理內(nèi)存空間來保存這個映射表,很浪費。
- 每一個映射表中,只有 1024 個表項,每一個表項仍然指向一個物理頁的起始地址;
- 一共使用 1024 個這樣的映射表;
這樣一來,1024(每個表中的表項個數(shù)) * 1024(表的個數(shù)),仍然可以覆蓋4GB的物理內(nèi)存空間。
計算過程:每一個頁表項指向一個 4KB 的物理頁,那么一個頁表中 1024 個頁表項,一共能覆蓋 4MB 的物理內(nèi)存;
那么 10MB 的程序,向上對齊取整之后(4MB 的倍數(shù),就是 12 MB),就需要 3 個頁表就可以了。
記住上圖中的一句話:一個頁表,可以覆蓋 4MB 的物理內(nèi)存空間(1024 * 4 KB)。
注意下面的這幾個屬性:
P(Present): 存在位。1 - 物理頁存在; 0 - 物理頁不存在;RW(Read/Write): 讀/寫位。1 - 這個物理頁可讀可寫; 0 - 這個物理頁只可讀;
D(Dirty): 臟位。表示這個物理頁中的數(shù)據(jù)是否被寫過;
操作系統(tǒng)在加載用戶程序的時候,不僅僅需要分配物理內(nèi)存,來存放程序的內(nèi)容;而且還需要分配物理內(nèi)存,用來保存程序的頁目錄和頁表。
再來算算賬:
其中的屬性字段,與頁表中的屬性類似,只不過它的描述對象是頁表。
當然,處理器中還有一個快表,用來加快從線性地址到物理地址的轉(zhuǎn)換過程。CR3寄存器的格式如下:
順便把官網(wǎng)上的其他幾個控制寄存器都貼出來:
其中,CR0寄存器的最高位PG,就是開啟頁處理單元的開關(guān)。
這里主要聊一下第3步,假設(shè)用戶程序文件在硬盤上的長度是20 MB,電腦中實際安裝的物理內(nèi)存是1 GB。
- 讀取程序 header 信息,解析出程序的總長度,從任務自己的虛擬內(nèi)存中分配一塊足夠的連續(xù)空間;
- 分配一個空閑物理頁,用作程序的頁目錄,頁目錄的地址會記錄在稍后創(chuàng)建的 TSS 段中;
- 使用虛擬內(nèi)存中的線性地址,分配一個物理頁(4 KB),登記到頁目錄和頁表中;
- 從硬盤上讀取 8 個扇區(qū)的數(shù)據(jù)(每個扇區(qū) 512 字節(jié)),存放到剛才分配的物理頁中;
- 檢查程序內(nèi)容是否讀取完畢:是-進入第 6 步;否-返回到第 3 步;
- 為用戶程序創(chuàng)建一些必要的內(nèi)核數(shù)據(jù)結(jié)構(gòu),比如:TSS、TCB/PCB 等等;
- 為用戶程序創(chuàng)建 LDT,并且在其中創(chuàng)建每一個段描述符;
- 把操作系統(tǒng)的頁目錄中高端地址部分的表項,復制給用戶程序的頁目錄表。
這樣的話,所有用戶程序的頁目錄中,高端地址的表項都指向相同的頁表地址,就達到了共享“操作系統(tǒng)空間”的目的。
可以先計算一下:頁目錄中,每一個表項覆蓋的空間是 4 MB,那么 20 MB的數(shù)據(jù),需要 5 個表項就可以了。在初始狀態(tài),頁目錄中的所有表項都是空的,其中的P位都是為0,表示頁表不存在。
注意:在“平坦”型分段模型下,線性地址等于虛擬地址。0x4000_0000 = 0100_0000_0000_0000___0000_0000_0000_0000
操作系統(tǒng)發(fā)現(xiàn)這個表項中為空,沒有指向任何一個頁表。
把頁表中的1024個表項全部清空,并且把頁表的物理地址0x0800_0000,登記在頁目錄中的第256個表項中:0x08000(上圖黃色部分)。
一個物理頁的地址一定是4KB對齊的(最后的12位全部為0),所以只需要記錄物理頁地址的高 20 位即可。用于存儲程序文件內(nèi)容的物理頁分配好了,下面就開始從硬盤中讀取程序文件的內(nèi)容了。
剛才已經(jīng)假設(shè):用戶程序文件在硬盤上的長度是20 MB。當讀取了一個物理頁的內(nèi)容后,通過計算發(fā)現(xiàn)用戶程序內(nèi)容還沒有讀取完,于是繼續(xù)重復以上流程。
- 線性地址增加 4KB:0x4000_1000 = 0100_0000_0000_0000___0001_0000_0000_0000;
- 前 10 位沒有變,仍然是頁目錄中的第 256 個表項,發(fā)現(xiàn)這個表項指向的頁表已經(jīng)存在了,于是就不用再分配物理頁用作頁表了;
- 分配一個空閑物理頁,用于存放程序內(nèi)容,假設(shè)在 0x0100_4000處找到一個,把這個地址登記在頁表中;
此時,線性地址的中間 10 位的索引值是 1,所以登記在頁表中的第 1 個表項。
- 從硬盤上讀取 8 個扇區(qū)的數(shù)據(jù),寫入這個物理頁;
因為頁目錄中一個表項所覆蓋的范圍是4 MB(也就是一個頁表中1024個表項所指向的物理頁空間的總和)。
后面的過程就不再嘮叨了,一樣一樣的~~
- 確定頁目錄表項:
0x4040_0000 = 0100_0000_0100_0000___0000_0000_0000_0000,前 10 位的索引值是 257;
- 發(fā)現(xiàn) 257 這個表項為空,于是分配一個空閑的物理頁,用作頁表;
- 分配一個物理頁,用作存儲程序文件的內(nèi)容,并把這個物理頁的地址記錄在頁表中;
線性地址 0x4040_0000 的中間 10 位的索引值是 0,所以登記在頁表的第一個表項中;
也就是如下圖所示:
- 用戶程序的長度是 20 MB,存放在虛擬內(nèi)存 0x4000_0000 ~ 0x4140_0000 (線性地址)這段空間內(nèi);
- 代碼段的長度是 8 MB,從虛擬內(nèi)存的 0x40C0_0000 處開始存放;
現(xiàn)在,用戶程序的內(nèi)容已經(jīng)全部讀取到內(nèi)存中了,頁目錄、頁表全部都安排妥當了。
0x4100_8800 = 0100_0001_0000_0000___1000_1000_0000_0000
頁目錄表的開始地址,肯定是從 CR3 寄存器獲取的;然后,根據(jù)線性地址的中間 10 位(00_0000___1000),得到頁表中的索引值為8。