當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > IOT物聯(lián)網(wǎng)小鎮(zhèn)
[導(dǎo)讀]作?者:道哥,10年嵌入式開(kāi)發(fā)老兵,專(zhuān)注于:C/C、嵌入式、Linux。關(guān)注下方公眾號(hào),回復(fù)【書(shū)籍】,獲取Linux、嵌入式領(lǐng)域經(jīng)典書(shū)籍;回復(fù)【PDF】,獲取所有原創(chuàng)文章(PDF格式)。目錄頁(yè)表的拆分過(guò)程頁(yè)目錄結(jié)構(gòu)幾個(gè)相關(guān)的寄存器加載用戶程序時(shí):頁(yè)目錄、頁(yè)表的分配和填充過(guò)程線性地...

作  者:道哥,10 年嵌入式開(kāi)發(fā)老兵,專(zhuān)注于:C/C 、嵌入式、Linux。


關(guān)注下方公眾號(hào),回復(fù)【書(shū)籍】,獲取 Linux、嵌入式領(lǐng)域經(jīng)典書(shū)籍;回復(fù)【PDF】,獲取所有原創(chuàng)文章( PDF 格式)。



目錄


  • 頁(yè)表的拆分過(guò)程


  • 頁(yè)目錄結(jié)構(gòu)


  • 幾個(gè)相關(guān)的寄存器


  • 加載用戶程序時(shí): 頁(yè)目錄、頁(yè)表的分配和填充過(guò)程


  • 線性地址到物理地址的查找、計(jì)算實(shí)例



在x86系統(tǒng)中,為了能夠更加充分、靈活的使用物理內(nèi)存,把物理內(nèi)存按照4KB的單位進(jìn)行分頁(yè)。


然后通過(guò)中間的映射表,把連續(xù)的虛擬內(nèi)存空間,映射到離散的物理內(nèi)存空間。


映射表中的每一個(gè)表項(xiàng),都指向一個(gè)物理頁(yè)的開(kāi)始地址。


但是這樣的映射表有一個(gè)明顯的缺點(diǎn):映射表自身也是需保存在物理內(nèi)存中的。


在 32 位系統(tǒng)中,它使用了多達(dá)4MB的物理內(nèi)存空間(每個(gè)表項(xiàng)4個(gè)字節(jié),一共有4G/4K個(gè)表項(xiàng))。


為了解決這個(gè)問(wèn)題,x86處理器使用了兩級(jí)轉(zhuǎn)換:頁(yè)目錄和頁(yè)表。


這篇文章,我們就從最基礎(chǔ)的底層計(jì)算過(guò)程入手,把這個(gè)最重要的內(nèi)存管理機(jī)制搞定,以后再學(xué)習(xí)更深入的知識(shí)點(diǎn)時(shí),就會(huì)更容易理解了。


頁(yè)表

在一個(gè)32位的系統(tǒng)中,物理內(nèi)存的最大可表示空間就是0xFFFF_FFFF,也就是4GB。


雖然實(shí)際安裝的物理內(nèi)存可能遠(yuǎn)遠(yuǎn)沒(méi)有這么大,但是在設(shè)計(jì)內(nèi)存管理機(jī)制的時(shí)候,還是需要按照最大的可尋址范圍來(lái)進(jìn)行設(shè)計(jì)的。


按照一個(gè)物理頁(yè)4KB的單位來(lái)劃分,4GB空間可以分割為1024 * 1024個(gè)物理頁(yè):


在上一篇文章中,使用單一的映射表來(lái)指向這些物理頁(yè),導(dǎo)致了映射表自身占據(jù)了太多的物理內(nèi)存空間。


一個(gè)用戶程序中定義的幾個(gè)段,可能實(shí)際上只使用了很小的空間,完全用不到 4 GB。


但是仍然需要為它分配多達(dá) 4MB 的物理內(nèi)存空間來(lái)保存這個(gè)映射表,很浪費(fèi)。


為了解決這個(gè)問(wèn)題,可以把這個(gè)單一映射表拆分成1024個(gè)體積更小的映射表:


  1. 每一個(gè)映射表中,只有 1024 個(gè)表項(xiàng),每一個(gè)表項(xiàng)仍然指向一個(gè)物理頁(yè)的起始地址;


  2. 一共使用 1024 個(gè)這樣的映射表;


這樣一來(lái),1024(每個(gè)表中的表項(xiàng)個(gè)數(shù)) * 1024(表的個(gè)數(shù)),仍然可以覆蓋4GB的物理內(nèi)存空間。


這里的每一個(gè)表,就稱作頁(yè)表,所以一共有1024個(gè)頁(yè)表。


一個(gè)頁(yè)表中一共有1024個(gè)表項(xiàng),每一個(gè)頁(yè)表項(xiàng)占用4個(gè)字節(jié),所以一個(gè)頁(yè)表就占用4KB的物理內(nèi)存空間,正好是一個(gè)物理頁(yè)的大小。


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


是的,從總數(shù)上看是這樣,但是:一個(gè)應(yīng)用程序是不可能完全使用全部的 4GB 空間的,也許只要幾十個(gè)頁(yè)表就可以了。


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


計(jì)算過(guò)程:


每一個(gè)頁(yè)表項(xiàng)指向一個(gè) 4KB 的物理頁(yè),那么一個(gè)頁(yè)表中 1024 個(gè)頁(yè)表項(xiàng),一共能覆蓋 4MB 的物理內(nèi)存;


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


記住上圖中的一句話:一個(gè)頁(yè)表,可以覆蓋 4MB 的物理內(nèi)存空間(1024 * 4 KB)。


頁(yè)表中,每一個(gè)表項(xiàng)的格式如下:


注意下面的這幾個(gè)屬性:


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


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


D(Dirty):       臟位。表示這個(gè)物理頁(yè)中的數(shù)據(jù)是否被寫(xiě)過(guò);


頁(yè)目錄

現(xiàn)在,每一個(gè)物理頁(yè),都被一個(gè)頁(yè)表中的一個(gè)表項(xiàng)來(lái)指向了,那么這1024個(gè)頁(yè)表的地址,應(yīng)該怎么來(lái)管理呢?


答案是:頁(yè)目錄表!


顧名思義:在頁(yè)目錄中,每一個(gè)表項(xiàng)指向一個(gè)頁(yè)表的開(kāi)始地址(物理地址)。


操作系統(tǒng)在加載用戶程序的時(shí)候,不僅僅需要分配物理內(nèi)存,來(lái)存放程序的內(nèi)容;


而且還需要分配物理內(nèi)存,用來(lái)保存程序的頁(yè)目錄和頁(yè)表。


再來(lái)算算賬:


剛才說(shuō)過(guò):每一個(gè)頁(yè)表覆蓋4MB的內(nèi)存空間,那么頁(yè)目錄中一共有1024個(gè)表項(xiàng),指向1024個(gè)頁(yè)表的物理地址。


那么頁(yè)目錄能覆蓋的內(nèi)存空間就是1024 * 4MB,也就是4GB,正好是32位地址的最大尋址范圍。


頁(yè)目錄中,每一個(gè)表項(xiàng)的格式如下:


其中的屬性字段,與頁(yè)表中的屬性類(lèi)似,只不過(guò)它的描述對(duì)象是頁(yè)表。


還有一點(diǎn):每一個(gè)用戶程序都有自己的頁(yè)目錄和頁(yè)表!下文有詳細(xì)說(shuō)明。


相關(guān)寄存器

現(xiàn)在,所有頁(yè)表的物理地址被頁(yè)目錄表項(xiàng)指向了,那么頁(yè)目錄的物理地址,處理器是怎么知道的呢


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


這個(gè)寄存器中,保存了當(dāng)前正在執(zhí)行的那個(gè)任務(wù)的頁(yè)目錄地址。


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


當(dāng)操作系統(tǒng)調(diào)度任務(wù)的時(shí)候,處理器就會(huì)找到即將執(zhí)行的新任務(wù)的TSS段信息,然后把新任務(wù)的頁(yè)目錄開(kāi)始地址更新到CR3寄存器中。


當(dāng)新任務(wù)的指令開(kāi)始被執(zhí)行時(shí),處理器在獲取指令、操作數(shù)據(jù)時(shí),操作的是線性地址。


頁(yè)處理單元就會(huì)從CR3寄存器中保存的頁(yè)目錄表開(kāi)始,把這個(gè)線性地址最終轉(zhuǎn)換成物理地址


當(dāng)然,處理器中還有一個(gè)快表,用來(lái)加快從線性地址到物理地址的轉(zhuǎn)換過(guò)程。


CR3寄存器的格式如下:


順便把官網(wǎng)上的其他幾個(gè)控制寄存器都貼出來(lái):


其中,CR0寄存器的最高位PG,就是開(kāi)啟頁(yè)處理單元的開(kāi)關(guān)。


也即是說(shuō):


當(dāng)系統(tǒng)上電之后,剛開(kāi)始的地址尋址方式一直是 [段:偏移地址] 的方式。


當(dāng)啟動(dòng)代碼準(zhǔn)備好頁(yè)目錄和頁(yè)表之后,就可以設(shè)置 CR0.PG = 1。


此時(shí),處理器中的頁(yè)處理單元就開(kāi)始工作了:面對(duì)任何一個(gè)線性地址,都要經(jīng)過(guò)頁(yè)處理單元之后,才得到一個(gè)物理地址。


加載用戶程序時(shí): 物理頁(yè)分配過(guò)程

在之前的文章中,介紹過(guò)一個(gè)用戶程序被操作系統(tǒng)加載的全過(guò)程,簡(jiǎn)述如下:


  1. 讀取程序 header 信息,解析出程序的總長(zhǎng)度,從任務(wù)自己的虛擬內(nèi)存中分配一塊足夠的連續(xù)空間;


  2. 分配一個(gè)空閑物理頁(yè),用作程序的頁(yè)目錄,頁(yè)目錄的地址會(huì)記錄在稍后創(chuàng)建的 TSS 段中;


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


  4. 從硬盤(pán)上讀取 8 個(gè)扇區(qū)的數(shù)據(jù)(每個(gè)扇區(qū) 512 字節(jié)),存放到剛才分配的物理頁(yè)中;


  5. 檢查程序內(nèi)容是否讀取完畢:是-進(jìn)入第 6 步;否-返回到第 3 步;


  6. 為用戶程序創(chuàng)建一些必要的內(nèi)核數(shù)據(jù)結(jié)構(gòu),比如:TSS、TCB/PCB 等等;


  7. 為用戶程序創(chuàng)建 LDT,并且在其中創(chuàng)建每一個(gè)段描述符;


  8. 把操作系統(tǒng)的頁(yè)目錄中高端地址部分的表項(xiàng),復(fù)制給用戶程序的頁(yè)目錄表。


這樣的話,所有用戶程序的頁(yè)目錄中,高端地址的表項(xiàng)都指向相同的頁(yè)表地址,就達(dá)到了共享“操作系統(tǒng)空間”的目的。


這里主要聊一下第3步,假設(shè)用戶程序文件在硬盤(pán)上的長(zhǎng)度是20 MB,電腦中實(shí)際安裝的物理內(nèi)存是1 GB。


可以先計(jì)算一下:頁(yè)目錄中,每一個(gè)表項(xiàng)覆蓋的空間是 4 MB,那么 20 MB的數(shù)據(jù),需要 5 個(gè)表項(xiàng)就可以了。


在初始狀態(tài),頁(yè)目錄中的所有表項(xiàng)都是空的,其中的P位都是為0,表示頁(yè)表不存在。


操作系統(tǒng)首先從虛擬內(nèi)存中,分配一塊20 MB的空間,假設(shè)從1 GB(0x4000_0000)的地址處開(kāi)始吧,這個(gè)地址是線性地址。


也就是說(shuō)把應(yīng)用程序的文件讀取到內(nèi)存中,從地址0x4000_0000開(kāi)始存放,向高地址方向增長(zhǎng)。


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


0x4000_0000 = 0100_0000_0000_0000___0000_0000_0000_0000


前10位表示該線性地址在頁(yè)目錄中的索引,中間10位表示頁(yè)表中的索引,最后12位表示物理頁(yè)中的偏移地址。


因此,前10位就是0100_0000_00,表示這個(gè)線性地址位于頁(yè)目錄中的第256個(gè)表項(xiàng):


操作系統(tǒng)發(fā)現(xiàn)這個(gè)表項(xiàng)中為空,沒(méi)有指向任何一個(gè)頁(yè)表。


于是就從物理內(nèi)存中,找一個(gè)空閑的物理頁(yè),用作頁(yè)目錄中第256個(gè)表項(xiàng)指向的頁(yè)表。


注意:這個(gè)物理頁(yè)是用作頁(yè)表,而不是用作存儲(chǔ)用戶程序文件。


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


把頁(yè)表中的1024個(gè)表項(xiàng)全部清空,并且把頁(yè)表的物理地址0x0800_0000,登記在頁(yè)目錄中的第256個(gè)表項(xiàng)中:0x08000(上圖黃色部分)。


為什么不是0x0800_0000?


因?yàn)橐粋€(gè)物理頁(yè)的地址一定是4KB對(duì)齊的(最后的12位全部為0),所以頁(yè)目錄的表項(xiàng)中只需要記錄頁(yè)表地址的高 20 位即可。


現(xiàn)在,頁(yè)表也有了,下面就是分配一個(gè)物理頁(yè)來(lái)存儲(chǔ)程序的內(nèi)容。


假設(shè)在剛才那個(gè)物理頁(yè)(用作頁(yè)表的那個(gè))的上面,又找到一個(gè)空閑的物理頁(yè),地址是:0x0800_1000。


此時(shí),這個(gè)用于存放程序內(nèi)容的物理頁(yè)的地址,就需要記錄在頁(yè)表的一個(gè)表項(xiàng)中了。


那么應(yīng)該記錄在頁(yè)表中的什么位置呢?也就是應(yīng)該登記在哪一個(gè)表項(xiàng)中呢?


需要根據(jù)線性地址的中間 10 位來(lái)確定:


0x4000_0000 = 0100_0000_0000_0000___0000_0000_0000_0000


中間10位的全部是0,說(shuō)明索引值就是0,也就是說(shuō)頁(yè)表中的第0個(gè)表項(xiàng),保存這個(gè)物理頁(yè)的地址,如下圖所示:


一個(gè)物理頁(yè)的地址一定是4KB對(duì)齊的(最后的12位全部為0),所以只需要記錄物理頁(yè)地址的高 20 位即可。


用于存儲(chǔ)程序文件內(nèi)容的物理頁(yè)分配好了,下面就開(kāi)始從硬盤(pán)中讀取程序文件的內(nèi)容了。


一個(gè)物理頁(yè)的大小是4 KB,硬盤(pán)上一個(gè)扇區(qū)的大小是512 B,那么從硬盤(pán)上連續(xù)讀取8個(gè)扇區(qū)的數(shù)據(jù)就可以把一個(gè)物理頁(yè)寫(xiě)滿。


剛才已經(jīng)假設(shè):用戶程序文件在硬盤(pán)上的長(zhǎng)度是20 MB。


當(dāng)讀取了一個(gè)物理頁(yè)的內(nèi)容后,通過(guò)計(jì)算發(fā)現(xiàn)用戶程序內(nèi)容還沒(méi)有讀取完,于是繼續(xù)重復(fù)以上流程。


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


  2. 前 10 位沒(méi)有變,仍然是頁(yè)目錄中的第 256 個(gè)表項(xiàng),發(fā)現(xiàn)這個(gè)表項(xiàng)指向的頁(yè)表已經(jīng)存在了,于是就不用再分配物理頁(yè)用作頁(yè)表了;


  3. 分配一個(gè)空閑物理頁(yè),用于存放程序內(nèi)容,假設(shè)在 0x0100_4000處找到一個(gè),把這個(gè)地址登記在頁(yè)表中;


此時(shí),線性地址的中間 10 位的索引值是 1,所以登記在頁(yè)表中的第 1 個(gè)表項(xiàng)。


  1. 從硬盤(pán)上讀取 8 個(gè)扇區(qū)的數(shù)據(jù),寫(xiě)入這個(gè)物理頁(yè);
因?yàn)?span>頁(yè)目錄中一個(gè)表項(xiàng)所覆蓋的范圍是4 MB(也就是一個(gè)頁(yè)表中1024個(gè)表項(xiàng)所指向的物理頁(yè)空間的總和)。


所以當(dāng)讀取了4 MB的程序內(nèi)容之后,這個(gè)頁(yè)表中的所有表項(xiàng)就被填滿了。


此時(shí),讀取的程序內(nèi)容所占用的【線性地址】空間是:0x4000_0000 ~ 0x403F_FFFF。


下面再繼續(xù)讀取新內(nèi)容時(shí),就從0x4040_0000這個(gè)線性地址處開(kāi)始存放,讀取過(guò)程與上面都是一樣的:


  1. 確定頁(yè)目錄表項(xiàng):
0x4040_0000 = 0100_0000_0100_0000___0000_0000_0000_0000,前 10 位的索引值是 257;


  1. 發(fā)現(xiàn) 257 這個(gè)表項(xiàng)為空,于是分配一個(gè)空閑的物理頁(yè),用作頁(yè)表;


  2. 分配一個(gè)物理頁(yè),用作存儲(chǔ)程序文件的內(nèi)容,并把這個(gè)物理頁(yè)的地址記錄在頁(yè)表中;


線性地址 0x4040_0000 的中間 10 位的索引值是 0,所以登記在頁(yè)表的第一個(gè)表項(xiàng)中;


后面的過(guò)程就不再嘮叨了,一樣一樣的~~


最終的頁(yè)目錄和頁(yè)表的布局,類(lèi)似下面這張圖:


線性地址到物理地址的變換過(guò)程

如果理解了上一個(gè)主題的內(nèi)容,那么部分應(yīng)該就可以不用再看了,因?yàn)樗鼈z是相反的過(guò)程,而且查找過(guò)程更簡(jiǎn)單一些。


仍然繼續(xù)我們的假設(shè):


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


  2. 代碼段的長(zhǎng)度是 8 MB,從虛擬內(nèi)存的 0x40C0_0000 處開(kāi)始存放;


也就是如下圖所示:


現(xiàn)在,用戶程序的內(nèi)容已經(jīng)全部讀取到內(nèi)存中了,頁(yè)目錄、頁(yè)表全部都安排妥當(dāng)了。


在頁(yè)目錄表中,一共有 5 個(gè)表項(xiàng),正好表示這20MB的地址空間。


其中,8 MB 的代碼所存儲(chǔ)的物理頁(yè)地址,登記在頁(yè)目錄表中的 259 和 260 這兩個(gè)表項(xiàng)中(上圖右側(cè)的綠色表項(xiàng))。


目標(biāo):處理器在執(zhí)行代碼時(shí),遇到一個(gè)線性地址0x4100_8800,頁(yè)處理單元需要把它轉(zhuǎn)換得到物理地址。


0x4100_8800 = 0100_0001_0000_0000___1000_1000_0000_0000


首先,根據(jù)線性地址的前 10 位(0100_0001_00),得到它在頁(yè)目錄中的索引值為260。


這個(gè)表項(xiàng)中記錄的頁(yè)表地址為0x08040,因?yàn)轫?yè)表地址的低12位一定是bit0,因此這個(gè)頁(yè)表的地址就是0x0804_0000。


頁(yè)目錄表的開(kāi)始地址,肯定是從 CR3 寄存器獲取的;


然后,根據(jù)線性地址的中間 10 位(00_0000___1000),得到頁(yè)表中的索引值為8。


這個(gè)表項(xiàng)中記錄的物理頁(yè)地址為0x02004,補(bǔ)上低位的12個(gè)bit0,就得到物理頁(yè)的開(kāi)始地址是0x0200_4000。


最后,根據(jù)線性地址的最后 12 位(1000_0000_0000),得到它在物理頁(yè)的偏移量2048。


也就是說(shuō):從物理頁(yè)的開(kāi)始地址(0x0200_4000),偏移2048個(gè)字節(jié)的地方,就是這個(gè)線性地址(0x4100_8080)對(duì)應(yīng)的物理地址(0x0200_4800)。


大功告成!



------ End ------


本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉