虛擬存儲器是操作系統(tǒng)中的重要內(nèi)容,也是理解的難點(diǎn)和重點(diǎn),雖然程序員不用直接和虛擬存儲器打交道,但是理解虛擬存儲器能夠更好的理解操作系統(tǒng)的存儲器管理。
虛擬存儲器實(shí)質(zhì)就是硬盤中的一部分存儲器,可以將其作為緩存。
虛擬存儲器的最大好處是實(shí)現(xiàn)在小內(nèi)存系統(tǒng)(較小的物理內(nèi)存)的應(yīng)用。比如一個(gè)linux進(jìn)程的虛擬存儲器大小是4G,其中前3G作為用戶空間,后1G作為內(nèi)存的空間。但是實(shí)際的物理內(nèi)存是一個(gè)2G甚至更小的物理內(nèi)存時(shí)如何處理呢??這時(shí)虛擬存儲器的概念就體現(xiàn)其巨大的優(yōu)勢。
虛擬地址和物理地址之間又是如何的轉(zhuǎn)換呢?這些都是需要了解的。虛擬地址是由CPU產(chǎn)生,然后虛擬內(nèi)存需要通過MMU轉(zhuǎn)換為物理內(nèi)存。
虛擬存儲器和物理存儲器的關(guān)系:
虛擬存儲器文件系統(tǒng)將物理存儲器和虛擬存儲器分成等大小的頁面,比如4K一頁,這樣物理存儲器和虛擬存儲器分解成頁數(shù)量不同的存儲器頁。通過一種叫做頁表(PTE)的結(jié)構(gòu)體實(shí)現(xiàn)虛擬頁到物理頁的聯(lián)系。具體的聯(lián)系就是:
頁表常住在內(nèi)存中,頁表的大小一般等于虛擬存儲器的頁數(shù)。每一個(gè)頁表由一個(gè)有效位和一個(gè)地址位構(gòu)成。當(dāng)有效位是1事,說明虛擬頁中的數(shù)據(jù)緩存到物理內(nèi)存中的起始地址,而如果有效位為0,后面的地址位為null,則說明該虛擬頁還沒有分配。如果地址位不為null,則指出了該頁表中在虛擬存儲器的起始地址,但是還沒有緩存到物理內(nèi)存中。這樣就通過了頁表實(shí)現(xiàn)了虛擬頁和物理頁之間的聯(lián)系。因此理解頁表是很重要的。多級頁表結(jié)構(gòu)以及STB等技術(shù)都是為了減少常駐空間或者提速。
虛擬地址由VPN和VPO構(gòu)成,而物理地址由PPN和PPO,其中PPO=VPO,其中VPN是指虛擬存儲器中的頁號,而PPN是物理存儲器的頁號,這個(gè)頁號的轉(zhuǎn)換關(guān)系與頁表有關(guān)。VPO和PPO都是在該也中的偏移量。通過頁表和偏移量就能實(shí)現(xiàn)具體位置的訪問。
具體的加載方法就是將某個(gè)虛擬存儲頁得數(shù)據(jù)緩存到任意的物理存儲頁,這樣就實(shí)現(xiàn)了在虛擬存儲器中連續(xù)頁,映射到物理內(nèi)存中并不一定是連續(xù)。這樣也減小了對連續(xù)內(nèi)存的要求。通過一個(gè)頁表的引入就能實(shí)現(xiàn)虛擬頁到物理頁得映射關(guān)系。
在linux中內(nèi)核為每個(gè)進(jìn)程分配一個(gè)單獨(dú)的頁表。這樣每個(gè)進(jìn)程都有了相同大小的虛擬存儲空間(4G)。每個(gè)虛擬存儲的空間分配也是相同的,每個(gè)段區(qū)的起始地址也是相同的,這樣就簡化了共享、加載、鏈接等過程。
linux采用mmap實(shí)現(xiàn)文件到進(jìn)程虛擬存儲器的加載過程,加載兩種類型的文件(對象):普通文件、匿名文件(二進(jìn)制)。
同時(shí)對象的形式也是多樣的,主要是理解共享對象和私有對象,私有對象又主要理解私有寫時(shí)頁保護(hù)。
共享對象是指將一個(gè)共享對象映射到各個(gè)進(jìn)程的共享段中,然后通過頁表將各個(gè)進(jìn)程的共享段加載到統(tǒng)一的物理存儲器中,這樣各個(gè)進(jìn)程都能實(shí)現(xiàn)對共享,且在物理存儲器中只有一個(gè)對象的拷貝。各個(gè)進(jìn)程對共享對象的寫操作都會反應(yīng)到其他的進(jìn)程中以及磁盤文件中。
多個(gè)進(jìn)程對一個(gè)似有對象的映射比較有意思。私有對象的開始生命周期的方式與共享對象相似,即在物理存儲器中只有一個(gè)私有對象的拷貝,但是設(shè)置訪問的權(quán)限為可讀。如果只是讀過程,所有進(jìn)程對似有對象的訪問與共享對象沒有差別,但是如果是某一個(gè)進(jìn)程試圖對私有對象進(jìn)行寫操作就會引發(fā)保護(hù)故障,然后在故障處理程序中將(內(nèi)存中)被寫的頁面內(nèi)容也拷貝到一個(gè)新的存儲頁面中,并設(shè)置好PTE,將新創(chuàng)建的頁面設(shè)置為可寫。然后對新創(chuàng)建的頁面進(jìn)行寫操作,這樣的操作就不會導(dǎo)致對私有對象的寫操作反應(yīng)到磁盤文件中。這種技術(shù)稱之為“寫時(shí)頁拷貝”。
fork函數(shù)的理解主要就是弄清楚兩種方式的差別。fork中創(chuàng)建了一個(gè)子進(jìn)程,其中子進(jìn)程和父進(jìn)程的內(nèi)容相同,因此開始是兩個(gè)進(jìn)程共享一個(gè)空間,但是兩個(gè)進(jìn)程分別會試圖寫一些段(data、bss等),這時(shí)發(fā)生了“寫時(shí)頁拷貝”,將需要寫的頁內(nèi)容拷貝到另一個(gè)新創(chuàng)建的頁中,此時(shí)再次發(fā)生寫操作,這也就是為什么打開的一些文本描述符在子進(jìn)程也能找到,就是因?yàn)閮蓚€(gè)進(jìn)程對一個(gè)私有對象沒有進(jìn)行寫操作時(shí)是對一個(gè)物理存儲器的一份拷貝,當(dāng)某一個(gè)進(jìn)程寫操作時(shí),這時(shí)發(fā)生了寫時(shí)頁拷貝,再次對新的頁進(jìn)行操作即可完成且不會影響別的進(jìn)程。但是本來的共享區(qū)仍然滿足共享的特征。但是.text等段不會發(fā)生寫時(shí)頁拷貝,因此父子進(jìn)程還是共享代碼段。這就是為什么fork函數(shù)后兩個(gè)進(jìn)程是共享代碼段的原因(發(fā)生了私有對象的寫時(shí)頁拷貝)。