為什么進(jìn)程使用的內(nèi)存尺寸(虛擬存儲(chǔ))可以比物理內(nèi)存還大?
為什么一個(gè)進(jìn)程所需的存儲(chǔ)空間大小能超過物理內(nèi)存的大???操作系統(tǒng)是如何管理機(jī)器上運(yùn)行的多個(gè)進(jìn)程的內(nèi)存的?進(jìn)程間共享存儲(chǔ)是如何做到的?通過top命令查看的VIRT和RES指標(biāo)有什么不同?所有這些問題都跟虛擬存儲(chǔ)這個(gè)概念相關(guān),虛擬存儲(chǔ)是計(jì)算機(jī)系統(tǒng)的重要概念,可以說,理解好虛擬存儲(chǔ)便是掌握了內(nèi)存管理的鑰匙。
- 進(jìn)程是執(zhí)行中的程序,一個(gè)進(jìn)程就是一個(gè)執(zhí)行中的程序?qū)嵗?,同一個(gè)程序可以有多個(gè)執(zhí)行的實(shí)例,對(duì)應(yīng)多個(gè)進(jìn)程。
- 系統(tǒng)上同時(shí)運(yùn)行的多個(gè)進(jìn)程共享機(jī)器的CPU和存儲(chǔ)資源,每個(gè)進(jìn)程(線程)有一個(gè)獨(dú)立的邏輯執(zhí)行流,它提供一種假象,好像在獨(dú)占的使用處理器;同時(shí),每個(gè)進(jìn)程有一個(gè)私有地址空間,這提供另一個(gè)假象,它好像在獨(dú)占的使用存儲(chǔ)器。
- 虛擬存儲(chǔ)是一層抽象,它為每個(gè)進(jìn)程提供了一致的、私有的地址空間,借助這一層關(guān)鍵抽象,計(jì)算機(jī)科學(xué)中最深刻最成功的概念“進(jìn)程”才得以實(shí)施。
- 虛擬存儲(chǔ)簡化了存儲(chǔ)器管理,鏈接器以獨(dú)立于內(nèi)存物理地址的方式構(gòu)建可執(zhí)行程序。
虛擬存儲(chǔ)空間被分為內(nèi)核空間和用戶空間兩部分,并非所有虛擬地址空間都會(huì)分配物理內(nèi)存,只有實(shí)際使用的才會(huì)分配物理內(nèi)存,這也體現(xiàn)在通過top命令查看進(jìn)程的VIRT和RES兩項(xiàng)指標(biāo)的數(shù)值差異上。
- 物理內(nèi)存可視為一個(gè)巨大的字節(jié)數(shù)組,每個(gè)元素占用一個(gè)字節(jié),有自己的獨(dú)立編號(hào)(也就是地址),這個(gè)地址叫物理地址(Physical Address, PA)。
- 物理內(nèi)存資源被運(yùn)行在系統(tǒng)中的所有進(jìn)程共享,就像CPU資源被運(yùn)行在系統(tǒng)中的所有進(jìn)程/線程共享一樣。
- 應(yīng)用程序中所使用的地址叫虛擬地址(Virtual Address, VA),每個(gè)進(jìn)程都擁有獨(dú)立的私有虛擬地址空間,進(jìn)程之間的虛擬存儲(chǔ)是隔離的,既進(jìn)程A不通過特殊手段無法訪問進(jìn)程B的某個(gè)虛擬地址。
- 虛擬地址在訪問真正的物理內(nèi)存的時(shí)候,需要被轉(zhuǎn)換為物理地址, 虛擬地址到物理地址的轉(zhuǎn)換過程叫地址翻譯。
- CPU硬件和操作系統(tǒng)合作完成地址翻譯,CPU芯片上的存儲(chǔ)管理單元(MMU)查詢內(nèi)存中的頁表動(dòng)態(tài)完成地址翻譯,而頁表的內(nèi)容由操作系統(tǒng)管理維護(hù),這項(xiàng)工作由底層系統(tǒng)默默完成,對(duì)應(yīng)用程序透明。
- 進(jìn)程X和進(jìn)程Y中的同一虛擬地址會(huì)被映射到不同物理地址,多個(gè)進(jìn)程也可以通過共享存儲(chǔ)技術(shù)(Shared Memory)映射到同一塊物理內(nèi)存。
- 32位系統(tǒng)的虛擬地址范圍是[0-2^32],總共4G Byte,這個(gè)地址的集合叫地址空間,64位系統(tǒng)擁有更大的地址空間,但不是2^64。
雖然進(jìn)程擁有如此大的虛擬地址空間,但它通常不會(huì)真正占用這么大存儲(chǔ)空間。
系統(tǒng)以頁為單位管理存儲(chǔ),32位系統(tǒng)上,PageSize通常為4K字節(jié),64位系統(tǒng)上,PageSize通常為8K字節(jié)。
物理存儲(chǔ)器(內(nèi)存)被以PageSize為單位分割為物理頁(Physical Page, PP),[0-4096)的連續(xù)空間被分為第1頁,[4096,8192)的連續(xù)空間被分為第2頁,以此類推,PageSize大小的物理頁也被稱為頁幀。
虛擬存儲(chǔ)空間也被按同樣的PageSize分割成虛擬頁(Virtual Page, VP),虛擬頁分為已分配和未分配兩種狀態(tài),而已分配的頁又被細(xì)分為未緩存和已緩存兩種狀態(tài)。
進(jìn)程的所有已分配的虛擬存儲(chǔ)頁構(gòu)成進(jìn)程的有效虛擬存儲(chǔ)空間,對(duì)未分配的虛擬存儲(chǔ)空間的訪問非法,將觸發(fā)異常(例如常見的段錯(cuò)誤)
通過調(diào)用malloc/new/mmap等編程接口分配虛擬存儲(chǔ)頁,對(duì)物理內(nèi)存頁的分配由系統(tǒng)負(fù)責(zé)。
分頁后,一個(gè)虛擬地址被分為2部分:頁編號(hào) + 頁內(nèi)偏移。
操作系統(tǒng)在內(nèi)存中為每個(gè)進(jìn)程維護(hù)一個(gè)頁表(PageTable, PT), 地址翻譯硬件通過查詢存放在物理存儲(chǔ)器中的頁表,來找到對(duì)應(yīng)的物理地址。
每個(gè)虛擬頁對(duì)應(yīng)一個(gè)頁表?xiàng)l目(Page Table Entry, PTE),頁表視為頁表?xiàng)l目的數(shù)組,頁號(hào)就是數(shù)組下標(biāo)(索引)。
每個(gè)頁表?xiàng)l目包含該虛擬頁是否已分配,對(duì)未分配的頁的訪問非法,如果已分配,則又要區(qū)分是否已緩存(對(duì)應(yīng)到物理內(nèi)存頁)和未緩存。
如果已緩存(頁命中),則頁表?xiàng)l目包含該虛擬頁對(duì)應(yīng)的物理地址;如果未緩存(頁命失),則頁表?xiàng)l目包含該虛擬頁對(duì)應(yīng)磁盤上該虛擬頁的起始位置,系統(tǒng)在物理存儲(chǔ)器中挑選一個(gè)犧牲頁,并將虛擬頁從磁盤拷貝到內(nèi)存,這個(gè)過程叫換頁(swapping)
犧牲頁的內(nèi)容需要從內(nèi)存拷貝到磁盤虛擬頁,這個(gè)過程叫換出(swap out),被緩存的新頁需要從磁盤拷貝到內(nèi)存,這個(gè)過程叫換入(swap in),因?yàn)闋砍兜酱疟P操作,過程中,一直等待,成本很高,這個(gè)成本被稱為命失懲罰,但根據(jù)局部性原理,程序經(jīng)歷啟動(dòng)階段的初始后,通常只會(huì)在一個(gè)較小的活動(dòng)頁面集上工作,這便能保證,雖然懲罰的成本看似很高,但實(shí)際上,它依然工作的很好。
通過頁面調(diào)度,我們的程序能夠在超過物理內(nèi)存容量的虛擬存儲(chǔ)空間下工作,且多個(gè)進(jìn)程間,能有效的共享稀缺的內(nèi)存資源。
理解這個(gè)過程對(duì)掌握內(nèi)存管理和優(yōu)化技術(shù)至關(guān)重要。
linux進(jìn)程虛擬存儲(chǔ)空間
linux進(jìn)程的虛擬存儲(chǔ)空間自底向上分為:代碼段、全局變量段、堆、共享存儲(chǔ)區(qū)、棧、內(nèi)核段。
程序啟動(dòng)時(shí),加載器會(huì)將編譯后的可執(zhí)行程序文件中的.text節(jié)拷貝到代碼段,.data拷貝到全局變量段,每個(gè)函數(shù)(內(nèi)聯(lián)除外)編譯后都會(huì)占存儲(chǔ)空間,進(jìn)文本節(jié),程序執(zhí)行中,會(huì)從代碼段源源不斷的加載指令。
堆向上生長,brk指向堆頂,通過malloc/new等編程接口從堆分配內(nèi)存,動(dòng)態(tài)分配的內(nèi)存塊需要手動(dòng)釋放。
棧向下生長,局部變量位于棧中,函數(shù)調(diào)用時(shí)的參數(shù)也經(jīng)棧傳遞,函數(shù)調(diào)用鏈所需內(nèi)存由棧提供,棧是自動(dòng)伸縮的,每個(gè)線程會(huì)有獨(dú)立的棧,每個(gè)棧的空間有限(4/8M,可調(diào)節(jié)),所以不能局部變量不能過大,遞歸過深有爆棧分險(xiǎn)。
堆內(nèi)存和棧內(nèi)存本質(zhì)上都是存儲(chǔ)區(qū),位于進(jìn)程的不同區(qū)段,只有使用方式上的不同,沒有物理介質(zhì)上的不同,都會(huì)經(jīng)地址翻譯到物理內(nèi)存。
棧和堆之間是共享存儲(chǔ)區(qū),通過共享存儲(chǔ)編程接口shmget創(chuàng)建的存儲(chǔ)段位于該段,標(biāo)準(zhǔn)庫的代碼在進(jìn)程間也被共享,這樣能夠節(jié)省內(nèi)存。
內(nèi)核段存儲(chǔ)空間,用戶態(tài)代碼不可訪問,但用戶代碼調(diào)用系統(tǒng)調(diào)用、或者觸發(fā)異常,進(jìn)程陷入內(nèi)核,會(huì)執(zhí)行內(nèi)核段的代碼+訪問內(nèi)核態(tài)數(shù)據(jù)。
注意:代碼段并非從0開始,而是從特定地址開始,0x8048000(32位系統(tǒng)),0x400000(64位系統(tǒng))
段頁式內(nèi)存管理
早期計(jì)算機(jī)系統(tǒng),采用段頁式的內(nèi)存管理,既有分頁,又有分段,段內(nèi)包含頁,但linux系統(tǒng)簡化了這個(gè)管理方式,進(jìn)程的虛擬地址空間只有1段,所以相當(dāng)于變相的廢棄了分段。
如果以4K為一頁,那么32位系統(tǒng),虛擬內(nèi)存空間為4G(2^32),因?yàn)轫摫硎沁M(jìn)程私有,所以,一個(gè)進(jìn)程的虛擬地址空間包含1M頁,需要1M頁表項(xiàng)(64位更多),而系統(tǒng)中經(jīng)常成百上千個(gè)進(jìn)程,這個(gè)內(nèi)存占用量太大了,所以,實(shí)際上,操作系統(tǒng)使用多級(jí)頁表巧妙的解決了這個(gè)問題。
多級(jí)頁表是壓縮頁表內(nèi)存占用的慣用法,引入多級(jí)頁表后,頂級(jí)頁表的一個(gè)表項(xiàng)不再表示4K/8K的地址范圍,它大的多,只有一級(jí)表項(xiàng)表示的范圍被分配,其對(duì)應(yīng)的2級(jí)頁表才會(huì)被存儲(chǔ),所以極大的節(jié)省了內(nèi)存空間,而因?yàn)檫M(jìn)程實(shí)際分配的虛擬頁,只是整個(gè)地址空間的很小一部分,所以,我們得以以小的存儲(chǔ)代價(jià),支撐很大的地址范圍。
每次地址翻譯,都需要查詢內(nèi)存頁表,如果頁表?xiàng)l目不在緩存中,則開銷很大,為了加快內(nèi)地翻譯速度,便引入了TLB(翻譯后備緩存),TLB是MMU中的一個(gè)PTE的小的緩存,MMU在走地址翻譯的時(shí)候,先從TLB查找PTE,失敗了再去PageTable里找,還是因?yàn)榫植啃?,TLB極大的加速了地址翻譯的過程。
軟硬件協(xié)作
- 地址翻譯,需要操作系統(tǒng)、MMU(TLB)協(xié)作完成
- 操作系統(tǒng)為每個(gè)進(jìn)程維護(hù)頁表
- MMU先查TLB緩存,沒找到,再查頁表
- 進(jìn)程調(diào)度后,頁表基址寄存器會(huì)修改指向新的運(yùn)行進(jìn)程的頁表其實(shí)地址
page fault
- page fault:實(shí)際上并不是真正的錯(cuò)誤,沒有名字看起來這么嚴(yán)重,指令執(zhí)行時(shí),引發(fā)缺頁(page fault)會(huì)觸發(fā)異常,操作系統(tǒng)的異常處理程序會(huì)妥善處置這個(gè)異常,并再次發(fā)射這個(gè)指令,這時(shí)候,因?yàn)檎?qǐng)求的地址已經(jīng)被裝載到內(nèi)存,指令得以正常進(jìn)行。
- major page fault: 也叫hard page fault,major page fault主要是由swapping機(jī)制引入的,因?yàn)闋砍兜酱疟Pio,主要由軟件完成,所以速度較慢,可通過swapon/swapoff開關(guān)系統(tǒng)交換分區(qū),也可以設(shè)置修改系統(tǒng)設(shè)置:swappiness
- minor page fault:也叫soft page fault,指需要訪問的代碼/數(shù)據(jù)已經(jīng)在物理內(nèi)存頁中,但頁表中的映射還沒有建立,需要MMU建立物理內(nèi)存和虛擬地址空間的映射關(guān)系。當(dāng)一個(gè)進(jìn)程在調(diào)用malloc獲取虛擬空間地址后,首次訪問該地址會(huì)發(fā)生一次soft page fault。多個(gè)進(jìn)程訪問同一個(gè)共享內(nèi)存中的數(shù)據(jù),當(dāng)某些進(jìn)程還沒有建立起映射關(guān)系,訪問時(shí)也會(huì)出現(xiàn)soft page fault。
可以通過命令:ps -eo min_flt,maj_flt,cmd 查看系統(tǒng)各個(gè)進(jìn)程的page fault情況。
當(dāng)程序通過malloc分配1G字節(jié)的內(nèi)存時(shí),系統(tǒng)并不會(huì)為進(jìn)程分配1G的物理內(nèi)存,而只是分配1G的虛擬內(nèi)存頁,當(dāng)之后真正訪問某個(gè)頁的時(shí)候,系統(tǒng)才會(huì)為它分配物理內(nèi)存,如果系統(tǒng)物理內(nèi)存被耗盡,則會(huì)挑選一個(gè)內(nèi)存頁,交換出去(把頁的內(nèi)容寫入磁盤頁),通過這樣的策略,我們能獲得比物理內(nèi)存更大的存儲(chǔ)空間,提高了內(nèi)存資源的利用率,也使得進(jìn)程之間共享存儲(chǔ)變得可能。