聊一聊Linux虛擬內(nèi)存技術(shù)
以存儲(chǔ)單元為單位來(lái)管理顯然不現(xiàn)實(shí),因此Linux把虛存空間分成若干個(gè)大小相等的存儲(chǔ)分區(qū),Linux把這樣的分區(qū)叫做 頁(yè)。為了換入、換出的方便,物理內(nèi)存也就按頁(yè)的大小分成若干個(gè)塊。由于物理內(nèi)存中的塊空間是用來(lái)容納虛存頁(yè)的容器,所以物理內(nèi)存中的塊叫做 頁(yè)框。頁(yè)與頁(yè)框是Linux實(shí)現(xiàn)虛擬內(nèi)存技術(shù)的基礎(chǔ)。
虛擬內(nèi)存的頁(yè)、物理內(nèi)存的頁(yè)框及頁(yè)表
在Linux中,頁(yè)與頁(yè)框的大小一般為4KB。當(dāng)然,根據(jù)系統(tǒng)和應(yīng)用的不同,頁(yè)與頁(yè)框的大小也可有所變化。
物理內(nèi)存和虛擬內(nèi)存被分成了頁(yè)框與頁(yè)之后,其存儲(chǔ)單元原來(lái)的地址都被自然地分成了兩段,并且這兩段各自代表著不同的意義:高位段分別叫做頁(yè)框碼和頁(yè)碼,它們是識(shí)別頁(yè)框和頁(yè)的編碼;低位段分別叫做頁(yè)框偏移量和頁(yè)內(nèi)偏移量,它們是存儲(chǔ)單元在頁(yè)框和頁(yè)內(nèi)的地址編碼。下圖就是兩段虛擬內(nèi)存和物理內(nèi)存分頁(yè)之后的情況:
為了使系統(tǒng)可以正確的訪問(wèn)虛存頁(yè)在對(duì)應(yīng)頁(yè)框中的映像,在把一個(gè)頁(yè)映射到某個(gè)頁(yè)框上的同時(shí),就必須把頁(yè)碼和存放該頁(yè)映像的頁(yè)框碼填入一個(gè)叫做 頁(yè)表的表項(xiàng)中。這個(gè)頁(yè)表就是之前提到的映射記錄表。一個(gè)頁(yè)表的示意圖如下所示:
頁(yè)模式下,虛擬地址、物理地址轉(zhuǎn)換關(guān)系的示意圖如下所示:
也就是說(shuō):處理器遇到的地址都是虛擬地址。虛擬地址和物理地址都分成頁(yè)碼(頁(yè)框碼)和偏移值兩部分。在由虛擬地址轉(zhuǎn)化成物理地址的過(guò)程中,偏移值不變。而頁(yè)碼和頁(yè)框碼之間的映射就在一個(gè)映射記錄表——頁(yè)表中。
請(qǐng)頁(yè)與交換
虛存頁(yè)面到物理頁(yè)框的映射叫做頁(yè)面的 加載。
當(dāng)處理器試圖訪問(wèn)一個(gè)虛存頁(yè)面時(shí),首先到頁(yè)表中去查詢?cè)擁?yè)是否已映射到物理頁(yè)框中,并記錄在頁(yè)表中。如果在,則MMU會(huì)把頁(yè)碼轉(zhuǎn)換成頁(yè)框碼,并加上虛擬地址提供的頁(yè)內(nèi)偏移量形成物理地址后去訪問(wèn)物理內(nèi)存;如果不在,則意味著該虛存頁(yè)面還沒(méi)有被載入內(nèi)存,這時(shí)MMU就會(huì)通知操作系統(tǒng):發(fā)生了一個(gè)頁(yè)面訪問(wèn)錯(cuò)誤(頁(yè)面錯(cuò)誤),接下來(lái)系統(tǒng)會(huì)啟動(dòng)所謂的“請(qǐng)頁(yè)”機(jī)制,即調(diào)用相應(yīng)的系統(tǒng)操作函數(shù),判斷該虛擬地址是否為有效地址。
如果是 有效的地址,就從虛擬內(nèi)存中將該地址指向的頁(yè)面讀入到內(nèi)存中的一個(gè)空閑頁(yè)框中,并在頁(yè)表中添加上相對(duì)應(yīng)的表項(xiàng),***處理器將從發(fā)生頁(yè)面錯(cuò)誤的地方重新開始運(yùn)行;如果是 無(wú)效的地址,則表明進(jìn)程在試圖訪問(wèn)一個(gè)不存在的虛擬地址,此時(shí)操作系統(tǒng)將終止此次訪問(wèn)。
當(dāng)然,也存在這樣的情況:在請(qǐng)頁(yè)成功之后,內(nèi)存中已沒(méi)有空閑物理頁(yè)框了。這是,系統(tǒng)必須啟動(dòng)所謂地 交換機(jī)制,即調(diào)用相應(yīng)的內(nèi)核操作函數(shù),在物理頁(yè)框中尋找一個(gè)當(dāng)前不再使用或者近期可能不會(huì)用到的頁(yè)面所占據(jù)的頁(yè)框。找到后,就把其中的頁(yè)移出,以裝載新的頁(yè)面。對(duì)移出頁(yè)面根據(jù)兩種情況來(lái)處理:如果該頁(yè)未被修改過(guò),則刪除它;如果該頁(yè)曾經(jīng)被修改過(guò),則系統(tǒng)必須將該頁(yè)寫回輔存。
系統(tǒng)請(qǐng)頁(yè)的處理過(guò)程如下所示:
為了公平地選擇將要從系統(tǒng)中拋棄的頁(yè)面,Linux系統(tǒng)使用 最近最少使用(LRU)頁(yè)面的衰老算法。這種策略根據(jù)系統(tǒng)中每個(gè)頁(yè)面被訪問(wèn)的頻率,為物理頁(yè)框中的頁(yè)面設(shè)置了一個(gè)叫做 年齡的屬性。頁(yè)面被訪問(wèn)的次數(shù)越多,則頁(yè)面的年齡最小;相反,則越大。而年齡較大的頁(yè)面就是待換出頁(yè)面的***候選者。
快表
在系統(tǒng)每次訪問(wèn)虛存頁(yè)時(shí),都要在內(nèi)存的所有頁(yè)表中尋找該頁(yè)的頁(yè)框,這是一個(gè)很費(fèi)時(shí)間的工作。但是,人們發(fā)現(xiàn),系統(tǒng)一旦訪問(wèn)了某一個(gè)頁(yè),那么系統(tǒng)就會(huì)在一段時(shí)間內(nèi)穩(wěn)定地工作在這個(gè)頁(yè)上。所以,為了提高訪問(wèn)頁(yè)表的速度,系統(tǒng)還配備了一組正好能容納一個(gè)頁(yè)表的 硬件寄存器,這樣當(dāng)系統(tǒng)再訪問(wèn)虛存時(shí),就首先到這組硬件寄存器中去訪問(wèn),系統(tǒng)速度就快多了。這組存放當(dāng)前頁(yè)表的寄存器叫做 快表。
總之,使用虛擬存儲(chǔ)技術(shù)時(shí),處理器必須配備一些硬件來(lái)承擔(dān)內(nèi)存管理的一部分任務(wù)。承擔(dān)內(nèi)存管理任務(wù)的硬件部分叫做存儲(chǔ)管理單元MMU。存儲(chǔ)管理單元MMU的工作過(guò)程如下圖所示:
頁(yè)的共享
在多程序系統(tǒng)中,常常有多個(gè)程序需要共享同一段代碼或數(shù)據(jù)的情況。在分頁(yè)管理的存儲(chǔ)器中,這個(gè)事情很好辦:讓多個(gè)程序共享同一個(gè)頁(yè)面即可。
具體的方法是:使這些相關(guān)程序的虛擬空間的頁(yè)面在頁(yè)表中指向內(nèi)存中的同一個(gè)頁(yè)框。這樣,當(dāng)程序運(yùn)行并訪問(wèn)這些相關(guān)頁(yè)面時(shí),就都是對(duì)同一個(gè)頁(yè)框中的頁(yè)面進(jìn)行訪問(wèn),而該頁(yè)框中的頁(yè)就被這些程序所共享。下圖是3個(gè)程序共享一個(gè)頁(yè)面的例子:
頁(yè)的保護(hù)
由上可知,頁(yè)表實(shí)際上是由虛擬空間轉(zhuǎn)到物理空間的入口。因此,為了保護(hù)頁(yè)面內(nèi)容不被沒(méi)有該頁(yè)面訪問(wèn)權(quán)限的程序所破壞,就應(yīng)在頁(yè)表的表項(xiàng)中設(shè)置一些訪問(wèn)控制字段,用于指明對(duì)應(yīng)頁(yè)面中的內(nèi)容允許何種操作,從而禁止非法訪問(wèn)。
下圖是頁(yè)表項(xiàng)中存放控制信息的一種可能的形式:
注意:其中的PCD位表示著是否允許高速緩存(cache)。
如果程序?qū)σ粋€(gè)頁(yè)試圖進(jìn)行一個(gè)該頁(yè)控制字段所不允許的操作,則會(huì)引起操作系統(tǒng)的一次中斷——非法訪問(wèn)中斷,并拒絕這種操作,從而保護(hù)該頁(yè)的內(nèi)容不被破壞。
多級(jí)頁(yè)表
需要注意的是,頁(yè)表是操作系統(tǒng)創(chuàng)建的用于內(nèi)存管理的表格。因此,一個(gè)程序在運(yùn)行時(shí),其頁(yè)表也要存放到內(nèi)存空間。如果一個(gè)程序只需要一個(gè)頁(yè)表,則不會(huì)有什么問(wèn)題。但如果,程序的虛擬空間很大的話,就會(huì)出現(xiàn)一個(gè)比較大的問(wèn)題。
比如:一個(gè)程序的虛擬空間為4GB,頁(yè)表以4KB為一頁(yè),那么這個(gè)程序空間就是1M頁(yè)。為了存儲(chǔ)這1M頁(yè)的頁(yè)指針,那么這個(gè)頁(yè)表的長(zhǎng)度就相當(dāng)大了,對(duì)內(nèi)存的負(fù)擔(dān)也很大了。所以,***對(duì)頁(yè)表也進(jìn)行分頁(yè)存儲(chǔ),在程序運(yùn)行時(shí)只把需要的頁(yè)復(fù)制到內(nèi)存,而暫時(shí)不需要的頁(yè)就讓它留在輔存中。為了管理這些頁(yè)表頁(yè),還要建立一個(gè) 記錄頁(yè)表頁(yè)首地址的頁(yè)目錄表,于是單級(jí)頁(yè)表就變成了二級(jí)頁(yè)表。二級(jí)頁(yè)表的地址轉(zhuǎn)換如下圖所示:
當(dāng)然,如果程序的虛擬空間更大,那么也可以用三級(jí)頁(yè)表來(lái)管理。為了具有通用性,Linux系統(tǒng)使用了三級(jí)頁(yè)表結(jié)構(gòu):頁(yè)目錄(Page Directory,PGD)、中間頁(yè)目錄(Page Middle Directory,PMD)、頁(yè)表(Page Table,PTE)。
Linux的頁(yè)表結(jié)構(gòu)
為了通用,Linux系統(tǒng)使用了三級(jí)頁(yè)表結(jié)構(gòu):頁(yè)目錄、中間頁(yè)目錄和頁(yè)表。PGD為 ***頁(yè)表,是一個(gè)pgdt數(shù)據(jù)類型的數(shù)組,每個(gè)數(shù)組元素指向一個(gè)中間頁(yè)目錄;PMD為 二級(jí)頁(yè)表,是一個(gè)pmdt數(shù)據(jù)結(jié)構(gòu)的數(shù)組,每個(gè)數(shù)組元素指向一個(gè)頁(yè)表;PTE則是 頁(yè)表,是一個(gè)pte_t數(shù)據(jù)類型的數(shù)組,每個(gè)元素中含有物理地址。
為了應(yīng)用上的靈活,Linux使用一系列的宏來(lái)掩蓋各種平臺(tái)的細(xì)節(jié)。用戶可以在配置文件 config中根據(jù)自己的需要對(duì)頁(yè)表進(jìn)行配置,以決定是使用三級(jí)頁(yè)表還是使用二級(jí)頁(yè)表。
在系統(tǒng)編譯時(shí),會(huì)根據(jù)配置文件 config中的配置,把目錄 include/asm符號(hào)連接到具體CPU專用的文件目錄中。例如,對(duì)于i386CPU,該目錄符號(hào)會(huì)連接到include/asm-i386,并在文件pgable-2level-defs.h中定義了二級(jí)頁(yè)表的基本結(jié)構(gòu),如下圖:
其中還定義了:
- #define PGDIR_SHIFT 22 //PGD在線性地址中的起始地址為bit22
- #define PTRS_PER_PGD 1024 //PGD共有1024個(gè)表項(xiàng)
- #define PTRS_PER_PTE 1024 //PTE共有1024個(gè)表項(xiàng)
- #endif
在文件include/asm-i386/pgtable.h中定義了頁(yè)目錄和頁(yè)表項(xiàng)的數(shù)據(jù)結(jié)構(gòu),如下:
- typedof struct { unsigned long pte_low; } pte_t ; //頁(yè)表中的物理地址,頁(yè)框碼
- typedof struct { unsigned long pgd; } pgd_t ; //指向一個(gè)頁(yè)表
- typedof struct { unsigned long pgprot; } pgprot_t ; //頁(yè)表中的各個(gè)狀態(tài)信息和訪問(wèn)權(quán)限
從定義可知,它們都是只有一個(gè) 長(zhǎng)整型類型(32位)的結(jié)構(gòu)體。
注意:如上文的“頁(yè)的保護(hù)”部分,頁(yè)框碼代表物理地址,只需要高20位就夠了(因?yàn)轫?yè)框的長(zhǎng)度為4KB,因此頁(yè)內(nèi)偏移12位)。而后12位可以存放各個(gè)狀態(tài)信息和訪問(wèn)權(quán)限。但是Linux并沒(méi)有這樣做,反而重新定義了一個(gè)結(jié)構(gòu)體來(lái)存放,通過(guò)“或”運(yùn)算來(lái)將兩者結(jié)合。




































