內(nèi)存 “舞臺(tái)” 上,進(jìn)程如何 “翩翩起舞”?
在數(shù)字世界里,計(jì)算機(jī)的每一次高效運(yùn)轉(zhuǎn)都離不開(kāi)內(nèi)存與進(jìn)程的默契配合。內(nèi)存,恰似一座宏大且有序的舞臺(tái),為進(jìn)程提供了施展拳腳的空間。而進(jìn)程,則如同舞臺(tái)上的舞者,它們?cè)趦?nèi)存的舞臺(tái)上,遵循著一套復(fù)雜而精妙的規(guī)則,靈動(dòng)地 “翩翩起舞”。
從啟動(dòng)程序的那一刻起,進(jìn)程便踏上了這片舞臺(tái),開(kāi)始了它與內(nèi)存的互動(dòng)之旅。這一過(guò)程,關(guān)乎計(jì)算機(jī)系統(tǒng)的高效穩(wěn)定運(yùn)行,也與我們?nèi)粘J褂玫母黝愜浖?、?yīng)用的流暢體驗(yàn)緊密相連。接下來(lái),就讓我們一同揭開(kāi)進(jìn)程在內(nèi)存舞臺(tái)上的精彩 “舞步”,探尋它們背后的神秘機(jī)制 。
一、內(nèi)存相關(guān)概述
在深入了解進(jìn)程與內(nèi)存的關(guān)系之前,我們先來(lái)認(rèn)識(shí)一下內(nèi)存這個(gè)計(jì)算機(jī)的關(guān)鍵部件。內(nèi)存,也被稱為內(nèi)存儲(chǔ)器或主存儲(chǔ)器,它就像是計(jì)算機(jī)的 “臨時(shí)倉(cāng)庫(kù)”,在計(jì)算機(jī)運(yùn)行程序時(shí)扮演著至關(guān)重要的角色。
內(nèi)存是計(jì)算機(jī)中重要的部件之一,它是與CPU進(jìn)行溝通的橋梁。計(jì)算機(jī)中所有程序的運(yùn)行都是在內(nèi)存中進(jìn)行的,因此內(nèi)存的性能對(duì)計(jì)算機(jī)的影響非常大。內(nèi)存(Memory)也被稱為內(nèi)存儲(chǔ)器,其作用是用于暫時(shí)存放CPU中的運(yùn)算數(shù)據(jù),以及與硬盤等外部存儲(chǔ)器交換的數(shù)據(jù)。只要計(jì)算機(jī)在運(yùn)行中,CPU就會(huì)把需要運(yùn)算的數(shù)據(jù)調(diào)到內(nèi)存中進(jìn)行運(yùn)算,當(dāng)運(yùn)算完成后CPU再將結(jié)果傳送出來(lái),內(nèi)存的運(yùn)行也決定了計(jì)算機(jī)的穩(wěn)定運(yùn)行。
內(nèi)存又稱主存,是CPU能直接尋址的存儲(chǔ)空間,由半導(dǎo)體器件制成。內(nèi)存的特點(diǎn)是存取速率快。內(nèi)存是電腦中的主要部件,它是相對(duì)于外存而言的。我們平常使用的程序,如Windows操作系統(tǒng)、打字軟件、游戲軟件等,一般都是安裝在硬盤等外存上的,但僅此是不能使用其功能的,必須把它們調(diào)入內(nèi)存中運(yùn)行,才能真正使用其功能,我們平時(shí)輸入一段文字,或玩一個(gè)游戲,其實(shí)都是在內(nèi)存中進(jìn)行的。就好比在一個(gè)書房里,存放書籍的書架和書柜相當(dāng)于電腦的外存,而我們工作的辦公桌就是內(nèi)存。通常我們把要永久保存的、大量的數(shù)據(jù)存儲(chǔ)在外存上,而把一些臨時(shí)的或少量的數(shù)據(jù)和程序放在內(nèi)存上,當(dāng)然內(nèi)存的好壞會(huì)直接影響電腦的運(yùn)行速度。
內(nèi)存就是暫時(shí)存儲(chǔ)程序以及數(shù)據(jù)的地方,比如當(dāng)我們?cè)谑褂肳PS處理文稿時(shí),當(dāng)你在鍵盤上敲入字符時(shí),它就被存入內(nèi)存中,當(dāng)你選擇存盤時(shí),內(nèi)存中的數(shù)據(jù)才會(huì)被存入硬(磁)盤。
內(nèi)存一般采用半導(dǎo)體存儲(chǔ)單元,包括隨機(jī)存儲(chǔ)器(RAM),只讀存儲(chǔ)器(ROM),以及高速緩存(CACHE)。只不過(guò)因?yàn)镽AM是其中最重要的存儲(chǔ)器。(synchronous)SDRAM同步動(dòng)態(tài)隨機(jī)存取存儲(chǔ)器:SDRAM為168腳,這是目前PENTIUM及以上機(jī)型使用的內(nèi)存。SDRAM將CPU與RAM通過(guò)一個(gè)相同的時(shí)鐘鎖在一起,使CPU和RAM能夠共享一個(gè)時(shí)鐘周期,以相同的速度同步工作,每一個(gè)時(shí)鐘脈沖的上升沿便開(kāi)始傳遞數(shù)據(jù),速度比EDO內(nèi)存提高50%。DDR(DOUBLE DATA RATE)RAM :SDRAM的更新?lián)Q代產(chǎn)品,他允許在時(shí)鐘脈沖的上升沿和下降沿傳輸數(shù)據(jù),這樣不需要提高時(shí)鐘的頻率就能加倍提高SDRAM的速度。
當(dāng)我們打開(kāi)電腦上的某個(gè)程序,比如一款圖像處理軟件,這個(gè)程序的代碼和運(yùn)行時(shí)所需的數(shù)據(jù)并不會(huì)直接從硬盤讀取然后被 CPU 處理。因?yàn)橛脖P的讀取速度相對(duì)較慢,如果 CPU 直接從硬盤讀取數(shù)據(jù),計(jì)算機(jī)的運(yùn)行效率會(huì)極其低下。這時(shí),內(nèi)存就發(fā)揮了關(guān)鍵的 “中介” 作用。在程序啟動(dòng)時(shí),操作系統(tǒng)會(huì)將程序的代碼和初始數(shù)據(jù)從硬盤加載到內(nèi)存中。內(nèi)存的讀取速度比硬盤快得多,通常是硬盤的幾十倍甚至上百倍 ,這使得 CPU 能夠快速地從內(nèi)存中讀取指令和數(shù)據(jù)進(jìn)行處理,大大提高了計(jì)算機(jī)的運(yùn)行速度。在圖像處理過(guò)程中,當(dāng)我們對(duì)圖片進(jìn)行裁剪、調(diào)色等操作時(shí),相關(guān)的數(shù)據(jù)會(huì)在內(nèi)存中被快速地讀取和修改,處理結(jié)果也會(huì)暫時(shí)存儲(chǔ)在內(nèi)存中,直到我們保存圖像時(shí),數(shù)據(jù)才會(huì)被寫入硬盤進(jìn)行長(zhǎng)期存儲(chǔ)。內(nèi)存就像是一個(gè)高速運(yùn)轉(zhuǎn)的中轉(zhuǎn)站,協(xié)調(diào)著 CPU 與硬盤之間的數(shù)據(jù)傳輸,讓計(jì)算機(jī)能夠高效地完成各種復(fù)雜的任務(wù)。
內(nèi)存的內(nèi)部是由各種IC電路組成的,它的種類很龐大,但是其主要分為三種存儲(chǔ)器:
- 只讀存儲(chǔ)器(ROM):ROM表示只讀存儲(chǔ)器(Read Only Memory),在制造ROM的時(shí)候,信息(數(shù)據(jù)或程序)就被存入并永久保存。這些信息只能讀出,一般不能寫入,即使機(jī)器停電,這些數(shù)據(jù)也不會(huì)丟失。ROM一般用于存放計(jì)算機(jī)的基本程序和數(shù)據(jù),如BIOS ROM。其物理外形一般是雙列直插式(DIP)的集成塊。
- 隨機(jī)存儲(chǔ)器(RAM):隨機(jī)存儲(chǔ)器(Random Access Memory)表示既可以從中讀取數(shù)據(jù),也可以寫入數(shù)據(jù)。當(dāng)機(jī)器電源關(guān)閉時(shí),存于其中的數(shù)據(jù)就會(huì)丟失。我們通常購(gòu)買或升級(jí)的內(nèi)存條就是用作電腦的內(nèi)存,內(nèi)存條(SIMM)就是將RAM集成塊集中在一起的一小塊電路板,它插在計(jì)算機(jī)中的內(nèi)存插槽上,以減少RAM集成塊占用的空間。目前市場(chǎng)上常見(jiàn)的內(nèi)存條有1G/條,2G/條,4G/條等。
- 高速緩沖存儲(chǔ)器(Cache):Cache也是我們經(jīng)常遇到的概念,也就是平??吹降囊患?jí)緩存(L1 Cache)、二級(jí)緩存(L2 Cache)、三級(jí)緩存(L3 Cache)這些數(shù)據(jù),它位于CPU與內(nèi)存之間,是一個(gè)讀寫速度比內(nèi)存更快的存儲(chǔ)器。當(dāng)CPU向內(nèi)存中寫入或讀出數(shù)據(jù)時(shí),這個(gè)數(shù)據(jù)也被存儲(chǔ)進(jìn)高速緩沖存儲(chǔ)器中。當(dāng)CPU再次需要這些數(shù)據(jù)時(shí),CPU就從高速緩沖存儲(chǔ)器讀取數(shù)據(jù),而不是訪問(wèn)較慢的內(nèi)存,當(dāng)然,如需要的數(shù)據(jù)在Cache中沒(méi)有,CPU會(huì)再去讀取內(nèi)存中的數(shù)據(jù)。
二、虛擬內(nèi)存技術(shù)
當(dāng)進(jìn)程在內(nèi)存這個(gè) “臨時(shí)倉(cāng)庫(kù)” 中運(yùn)行時(shí),虛擬內(nèi)存則是背后的 “魔法”,讓進(jìn)程能夠高效地使用內(nèi)存資源。
2.1為什么需要使用虛擬內(nèi)存
進(jìn)程需要使用的代碼和數(shù)據(jù)都放在內(nèi)存中,比放在外存中要快很多。問(wèn)題是內(nèi)存空間太小了,不能滿足進(jìn)程的需求,而且現(xiàn)在都是多進(jìn)程,情況更加糟糕。所以提出了虛擬內(nèi)存,使得每個(gè)進(jìn)程用于3G的獨(dú)立用戶內(nèi)存空間和共享的1G內(nèi)核內(nèi)存空間。(每個(gè)進(jìn)程都有自己的頁(yè)表,才使得3G用戶空間的獨(dú)立)這樣進(jìn)程運(yùn)行的速度必然很快了。而且虛擬內(nèi)存機(jī)制還解決了內(nèi)存碎片和內(nèi)存不連續(xù)的問(wèn)題。為什么可以在有限的物理內(nèi)存上達(dá)到這樣的效果呢?
2.2虛擬內(nèi)存詳解
虛擬內(nèi)存是計(jì)算機(jī)系統(tǒng)內(nèi)存管理的一種技術(shù),它就像是給應(yīng)用程序戴上了一副 “魔法眼鏡”,讓應(yīng)用程序以為自己擁有連續(xù)可用的內(nèi)存,即一個(gè)連續(xù)完整的地址空間 。但實(shí)際上,這些內(nèi)存通常被分隔成多個(gè)物理內(nèi)存碎片,還有部分暫時(shí)存儲(chǔ)在外部磁盤存儲(chǔ)器上。當(dāng)計(jì)算機(jī)缺少運(yùn)行某些程序所需的物理內(nèi)存時(shí),操作系統(tǒng)就會(huì)使用硬盤上的虛擬內(nèi)存進(jìn)行替代。這就好比你有一個(gè)小書架(物理內(nèi)存),放不下所有的書(程序數(shù)據(jù)),于是你在旁邊放了一個(gè)大箱子(虛擬內(nèi)存),把暫時(shí)不看的書放在箱子里,等需要的時(shí)候再拿出來(lái)。在 32 位的操作系統(tǒng)中,每個(gè)進(jìn)程可以擁有 4GB 的虛擬內(nèi)存空間,但實(shí)際的物理內(nèi)存可能遠(yuǎn)遠(yuǎn)小于這個(gè)數(shù)字。
例如:對(duì)于程序計(jì)數(shù)器位數(shù)為32位的處理器來(lái)說(shuō),他的地址發(fā)生器所能發(fā)出的地址數(shù)目為2^32=4G個(gè),于是這個(gè)處理器所能訪問(wèn)的最大內(nèi)存空間就是4G。在計(jì)算機(jī)技術(shù)中,這個(gè)值就叫做處理器的尋址空間或?qū)ぶ纺芰Α?/span>
照理說(shuō),為了充分利用處理器的尋址空間,就應(yīng)按照處理器的最大尋址來(lái)為其分配系統(tǒng)的內(nèi)存。如果處理器具有32位程序計(jì)數(shù)器,那么就應(yīng)該按照下圖的方式,為其配備4G的內(nèi)存:
圖片
這樣,處理器所發(fā)出的每一個(gè)地址都會(huì)有一個(gè)真實(shí)的物理存儲(chǔ)單元與之對(duì)應(yīng);同時(shí),每一個(gè)物理存儲(chǔ)單元都有唯一的地址與之對(duì)應(yīng)。這顯然是一種最理想的情況。
但遺憾的是,實(shí)際上計(jì)算機(jī)所配置內(nèi)存的實(shí)際空間常常小于處理器的尋址范圍,這是就會(huì)因處理器的一部分尋址空間沒(méi)有對(duì)應(yīng)的物理存儲(chǔ)單元,從而導(dǎo)致處理器尋址能力的浪費(fèi)。例如:如下圖的系統(tǒng)中,具有32位尋址能力的處理器只配置了256M的內(nèi)存儲(chǔ)器,這就會(huì)造成大量的浪費(fèi):
圖片
另外,還有一些處理器因外部地址線的根數(shù)小于處理器程序計(jì)數(shù)器的位數(shù),而使地址總線的根數(shù)不滿足處理器的尋址范圍,從而處理器的其余尋址能力也就被浪費(fèi)了。例如:Intel8086處理器的程序計(jì)數(shù)器位32位,而處理器芯片的外部地址總線只有20根,所以它所能配置的最大內(nèi)存為1MB:
圖片
在實(shí)際的應(yīng)用中,如果需要運(yùn)行的應(yīng)用程序比較小,所需內(nèi)存容量小于計(jì)算機(jī)實(shí)際所配置的內(nèi)存空間,自然不會(huì)出什么問(wèn)題。但是,目前很多的應(yīng)用程序都比較大,計(jì)算機(jī)實(shí)際所配置的內(nèi)存空間無(wú)法滿足。
實(shí)踐和研究都證明:一個(gè)應(yīng)用程序總是逐段被運(yùn)行的,而且在一段時(shí)間內(nèi)會(huì)穩(wěn)定運(yùn)行在某一段程序里。
這也就出現(xiàn)了一個(gè)方法:如下圖所示,把要運(yùn)行的那一段程序自輔存復(fù)制到內(nèi)存中來(lái)運(yùn)行,而其他暫時(shí)不運(yùn)行的程序段就讓它仍然留在輔存。
圖片
當(dāng)需要執(zhí)行另一端尚未在內(nèi)存的程序段(如程序段2),如下圖所示,就可以把內(nèi)存中程序段1的副本復(fù)制回輔存,在內(nèi)存騰出必要的空間后,再把輔存中的程序段2復(fù)制到內(nèi)存空間來(lái)執(zhí)行即可:
圖片
在計(jì)算機(jī)技術(shù)中,把內(nèi)存中的程序段復(fù)制回輔存的做法叫做“換出”,而把輔存中程序段映射到內(nèi)存的做法叫做“換入”。經(jīng)過(guò)不斷有目的的換入和換出,處理器就可以運(yùn)行一個(gè)大于實(shí)際物理內(nèi)存的應(yīng)用程序了。或者說(shuō),處理器似乎是擁有了一個(gè)大于實(shí)際物理內(nèi)存的內(nèi)存空間。于是,這個(gè)存儲(chǔ)空間叫做虛擬內(nèi)存空間,而把真正的內(nèi)存叫做實(shí)際物理內(nèi)存,或簡(jiǎn)稱為物理內(nèi)存。
那么對(duì)于一臺(tái)真實(shí)的計(jì)算機(jī)來(lái)說(shuō),它的虛擬內(nèi)存空間又有多大呢?計(jì)算機(jī)虛擬內(nèi)存空間的大小是由程序計(jì)數(shù)器的尋址能力來(lái)決定的。例如:在程序計(jì)數(shù)器的位數(shù)為32的處理器中,它的虛擬內(nèi)存空間就為4GB。
可見(jiàn),如果一個(gè)系統(tǒng)采用了虛擬內(nèi)存技術(shù),那么它就存在著兩個(gè)內(nèi)存空間:虛擬內(nèi)存空間和物理內(nèi)存空間。虛擬內(nèi)存空間中的地址叫做“虛擬地址”;而實(shí)際物理內(nèi)存空間中的地址叫做“實(shí)際物理地址”或“物理地址”。處理器運(yùn)算器和應(yīng)用程序設(shè)計(jì)人員看到的只是虛擬內(nèi)存空間和虛擬地址,而處理器片外的地址總線看到的只是物理地址空間和物理地址。
由于存在兩個(gè)內(nèi)存地址,因此一個(gè)應(yīng)用程序從編寫到被執(zhí)行,需要進(jìn)行兩次映射。第一次是映射到虛擬內(nèi)存空間,第二次時(shí)映射到物理內(nèi)存空間。在計(jì)算機(jī)系統(tǒng)中,第兩次映射的工作是由硬件和軟件共同來(lái)完成的。承擔(dān)這個(gè)任務(wù)的硬件部分叫做存儲(chǔ)管理單元MMU,軟件部分就是操作系統(tǒng)的內(nèi)存管理模塊了。
在映射工作中,為了記錄程序段占用物理內(nèi)存的情況,操作系統(tǒng)的內(nèi)存管理模塊需要建立一個(gè)表格,該表格以虛擬地址為索引,記錄了程序段所占用的物理內(nèi)存的物理地址。這個(gè)虛擬地址/物理地址記錄表便是存儲(chǔ)管理單元MMU把虛擬地址轉(zhuǎn)化為實(shí)際物理地址的依據(jù),記錄表與存儲(chǔ)管理單元MMU的作用如下圖所示:
圖片
綜上所述,虛擬內(nèi)存技術(shù)的實(shí)現(xiàn),是建立在應(yīng)用程序可以分成段,并且具有“在任何時(shí)候正在使用的信息總是所有存儲(chǔ)信息的一小部分”的局部特性基礎(chǔ)上的。它是通過(guò)用輔存空間模擬RAM來(lái)實(shí)現(xiàn)的一種使機(jī)器的作業(yè)地址空間大于實(shí)際內(nèi)存的技術(shù)。
從處理器運(yùn)算裝置和程序設(shè)計(jì)人員的角度來(lái)看,它面對(duì)的是一個(gè)用MMU、映射記錄表和物理內(nèi)存封裝起來(lái)的一個(gè)虛擬內(nèi)存空間,這個(gè)存儲(chǔ)空間的大小取決于處理器程序計(jì)數(shù)器的尋址空間。
可見(jiàn),程序映射表是實(shí)現(xiàn)虛擬內(nèi)存的技術(shù)關(guān)鍵,它可給系統(tǒng)帶來(lái)如下特點(diǎn):
- 系統(tǒng)中每一個(gè)程序各自都有一個(gè)大小與處理器尋址空間相等的虛擬內(nèi)存空間;
- 在一個(gè)具體時(shí)刻,處理器只能使用其中一個(gè)程序的映射記錄表,因此它只看到多個(gè)程序虛存空間中的一個(gè),這樣就保證了各個(gè)程序的虛存空間時(shí)互不相擾、各自獨(dú)立的;
- 使用程序映射表可方便地實(shí)現(xiàn)物理內(nèi)存的共享。
2.3虛擬地址空間布局
Linux 內(nèi)核給每個(gè)進(jìn)程都提供了一個(gè)獨(dú)立的虛擬地址空間,并且這個(gè)地址空間是連續(xù)的。這樣,進(jìn)程就可以很方便地訪問(wèn)內(nèi)存,更確切地說(shuō)是訪問(wèn)虛擬內(nèi)存。
虛擬地址空間的內(nèi)部又被分為內(nèi)核空間和用戶空間兩部分,不同字長(zhǎng)(也就是單個(gè)CPU指令可以處理數(shù)據(jù)的最大長(zhǎng)度)的處理器,地址空間的范圍也不同。比如最常見(jiàn)的 32 位和 64 位系統(tǒng),它們的虛擬地址空間,如下所示:
圖片
通過(guò)這里可以看出,32位系統(tǒng)的內(nèi)核空間占用 1G,位于最高處,剩下的3G是用戶空間。而 64 位系統(tǒng)的內(nèi)核空間和用戶空間都是 128T,分別占據(jù)整個(gè)內(nèi)存空間的最高和最低處,剩下的中間部分是未定義的。
進(jìn)程在用戶態(tài)時(shí),只能訪問(wèn)用戶空間內(nèi)存;只有進(jìn)入內(nèi)核態(tài)后,才可以訪問(wèn)內(nèi)核空間內(nèi)存。雖然每個(gè)進(jìn)程的地址空間都包含了內(nèi)核空間,但這些內(nèi)核空間,其實(shí)關(guān)聯(lián)的都是相同的物理內(nèi)存。這樣,進(jìn)程切換到內(nèi)核態(tài)后,就可以很方便地訪問(wèn)內(nèi)核空間內(nèi)存。
既然每個(gè)進(jìn)程都有一個(gè)這么大的地址空間,那么所有進(jìn)程的虛擬內(nèi)存加起來(lái),自然要比實(shí)際的物理內(nèi)存大得多。所以,并不是所有的虛擬內(nèi)存都會(huì)分配物理內(nèi)存,只有那些實(shí)際使用的虛擬內(nèi)存才分配物理內(nèi)存,并且分配后的物理內(nèi)存,是通過(guò)內(nèi)存映射來(lái)管理的;內(nèi)存映射,其實(shí)就是將虛擬內(nèi)存地址映射到物理內(nèi)存地址。為了完成內(nèi)存映射,內(nèi)核為每個(gè)進(jìn)程都維護(hù)了一張頁(yè)表,記錄虛擬地址與物理地址的映射關(guān)系,如下圖所示:
圖片
頁(yè)表實(shí)際上存儲(chǔ)在 CPU 的內(nèi)存管理單元 MMU中,這樣,正常情況下,處理器就可以直接通過(guò)硬件,找出要訪問(wèn)的內(nèi)存;而當(dāng)進(jìn)程訪問(wèn)的虛擬地址在頁(yè)表中查不到時(shí),系統(tǒng)會(huì)產(chǎn)生一個(gè)缺頁(yè)異常,進(jìn)入內(nèi)核空間分配物理內(nèi)存、更新進(jìn)程頁(yè)表,最后再返回用戶空間,恢復(fù)進(jìn)程的運(yùn)行。
另外,TLB(Translation Lookaside Buffer,轉(zhuǎn)譯后備緩沖器)會(huì)影響 CPU 的內(nèi)存訪問(wèn)性能,TLB 其實(shí)就是 MMU 中頁(yè)表的高速緩存。由于進(jìn)程的虛擬地址空間是獨(dú)立的,而 TLB 的訪問(wèn)速度又比 MMU 快得多,所以,通過(guò)減少進(jìn)程的上下文切換,減少TLB的刷新次數(shù),就可以提高TLB 緩存的使用率,進(jìn)而提高CPU的內(nèi)存訪問(wèn)性能;不過(guò)要注意,MMU 并不以字節(jié)為單位來(lái)管理內(nèi)存,而是規(guī)定了一個(gè)內(nèi)存映射的最小單位,也就是頁(yè),通常是 4 KB大小。這樣,每一次內(nèi)存映射,都需要關(guān)聯(lián) 4 KB 或者 4KB 整數(shù)倍的內(nèi)存空間。
頁(yè)的大小只有4 KB ,導(dǎo)致的另一個(gè)問(wèn)題就是,整個(gè)頁(yè)表會(huì)變得非常大。比方說(shuō),僅 32 位系統(tǒng)就需要 100 多萬(wàn)個(gè)頁(yè)表項(xiàng)(4GB/4KB),才可以實(shí)現(xiàn)整個(gè)地址空間的映射。為了解決頁(yè)表項(xiàng)過(guò)多的問(wèn)題,Linux 提供了兩種機(jī)制,也就是多級(jí)頁(yè)表和大頁(yè)(HugePage)。
多級(jí)頁(yè)表就是把內(nèi)存分成區(qū)塊來(lái)管理,將原來(lái)的映射關(guān)系改成區(qū)塊索引和區(qū)塊內(nèi)的偏移。由于虛擬內(nèi)存空間通常只用了很少一部分,那么,多級(jí)頁(yè)表就只保存這些使用中的區(qū)塊,這樣就可以大大地減少頁(yè)表的項(xiàng)數(shù)。
Linux用的正是四級(jí)頁(yè)表來(lái)管理內(nèi)存頁(yè),如下圖所示,虛擬地址被分為5個(gè)部分,前4個(gè)表項(xiàng)用于選擇頁(yè),而最后一個(gè)索引表示頁(yè)內(nèi)偏移。
圖片
大頁(yè),就是比普通頁(yè)更大的內(nèi)存塊,常見(jiàn)的大小有 2MB 和 1GB。大頁(yè)通常用在使用大量?jī)?nèi)存的進(jìn)程上,比如Oracle、DPDK等。
通過(guò)這些機(jī)制,在頁(yè)表的映射下,進(jìn)程就可以通過(guò)虛擬地址來(lái)訪問(wèn)物理內(nèi)存了。
以 Linux 23位系統(tǒng)為例,進(jìn)程的虛擬地址空間布局從低到高主要包含以下幾個(gè)部分:
- LOAD Segments:這部分包含了代碼段(.text)、數(shù)據(jù)段(.data)和 BSS 段等。代碼段存儲(chǔ)著 CPU 執(zhí)行的機(jī)器指令,它是只讀的,防止指令被其他程序修改 。數(shù)據(jù)段用于存儲(chǔ)初始化的全局變量和靜態(tài)變量,BSS 段則用來(lái)存放未初始化的全局變量和靜態(tài)變量。
- 堆(Heap):堆是用于保存程序運(yùn)行時(shí)動(dòng)態(tài)申請(qǐng)的內(nèi)存空間的區(qū)域,比如使用malloc或new申請(qǐng)的內(nèi)存空間就來(lái)自堆 。堆的地址空間是 “向上增加” 的,即當(dāng)堆上保存的數(shù)據(jù)越多,堆的地址就越高。
- 共享庫(kù)數(shù)據(jù):很多進(jìn)程會(huì)共享一些庫(kù)文件,這些共享庫(kù)的數(shù)據(jù)就存儲(chǔ)在這里。通過(guò)共享庫(kù),多個(gè)進(jìn)程可以共享相同的代碼和數(shù)據(jù),節(jié)省內(nèi)存空間。
- 棧(Stack):棧主要用于保存函數(shù)的局部變量(不包括static聲明的靜態(tài)變量,靜態(tài)變量存放在數(shù)據(jù)段或 BSS 段)、參數(shù)、返回值、函數(shù)返回地址以及調(diào)用者環(huán)境信息(如寄存器值)等 。棧的內(nèi)存由系統(tǒng)進(jìn)行管理,在函數(shù)完成執(zhí)行后,系統(tǒng)會(huì)自行釋放棧區(qū)內(nèi)存,不需要用戶手動(dòng)管理。整個(gè)程序的棧區(qū)大小可以由用戶自行設(shè)定,Windows 默認(rèn)的棧區(qū)大小為 1M ,64 位的 Linux 默認(rèn)棧大小為 10MB。
- 內(nèi)核數(shù)據(jù):這是操作系統(tǒng)內(nèi)核使用的內(nèi)存區(qū)域,用戶進(jìn)程一般不能直接訪問(wèn)。它包含了內(nèi)核代碼、內(nèi)核數(shù)據(jù)結(jié)構(gòu)以及一些系統(tǒng)調(diào)用的相關(guān)信息。
圖片
通過(guò)這張圖可以看到,用戶空間內(nèi)存,從低到高分別是五種不同的內(nèi)存段:
- 只讀段,包括代碼和常量等。
- 數(shù)據(jù)段,包括全局變量等。
- 堆,包括動(dòng)態(tài)分配的內(nèi)存,從低地址開(kāi)始向上增長(zhǎng)。
- 文件映射段,包括動(dòng)態(tài)庫(kù)、共享內(nèi)存等,從高地址開(kāi)始向下增長(zhǎng)。
- 棧,包括局部變量和函數(shù)調(diào)用的上下文等。棧的大小是固定的,一般是 8 MB。
在這五個(gè)內(nèi)存段中,堆和文件映射段的內(nèi)存是動(dòng)態(tài)分配的。比如說(shuō),使用 C 標(biāo)準(zhǔn)庫(kù)的 malloc() 或者 mmap() ,就可以分別在堆和文件映射段動(dòng)態(tài)分配內(nèi)存;其實(shí)64位系統(tǒng)的內(nèi)存分布也類似,只不過(guò)內(nèi)存空間要大得多。
2.4虛擬內(nèi)存使用方式
進(jìn)程在啟動(dòng)時(shí),操作系統(tǒng)會(huì)為其分配虛擬內(nèi)存空間,并建立虛擬地址到物理地址的映射關(guān)系。在進(jìn)程運(yùn)行過(guò)程中,當(dāng)需要訪問(wèn)內(nèi)存時(shí),CPU 會(huì)生成虛擬地址,這個(gè)虛擬地址會(huì)經(jīng)過(guò)內(nèi)存管理單元(MMU)的轉(zhuǎn)換,找到對(duì)應(yīng)的物理地址,然后訪問(wèn)物理內(nèi)存。如果所需的內(nèi)存頁(yè)不在物理內(nèi)存中,就會(huì)發(fā)生缺頁(yè)中斷,操作系統(tǒng)會(huì)從磁盤的虛擬內(nèi)存中讀取相應(yīng)的內(nèi)存頁(yè)到物理內(nèi)存中,并更新映射關(guān)系。
在 Linux 系統(tǒng)中,進(jìn)程可以通過(guò)mmap、sbrk和brk等函數(shù)來(lái)操作虛擬內(nèi)存。mmap函數(shù)用于將一個(gè)文件或者其它對(duì)象映射進(jìn)內(nèi)存 ,比如將共享庫(kù)映射到進(jìn)程的虛擬地址空間中。sbrk和brk函數(shù)則用于改變進(jìn)程數(shù)據(jù)段的大小,從而實(shí)現(xiàn)內(nèi)存的動(dòng)態(tài)分配和釋放。當(dāng)malloc分配小于 128k 的內(nèi)存時(shí),會(huì)使用brk分配內(nèi)存,將數(shù)據(jù)段的最高地址指針往高地址推;當(dāng)malloc分配大于 128k 的內(nèi)存時(shí),會(huì)使用mmap在堆和棧之間找一塊空閑內(nèi)存分配 。
malloc() 是 C 標(biāo)準(zhǔn)庫(kù)提供的內(nèi)存分配函數(shù),對(duì)應(yīng)到系統(tǒng)調(diào)用上,有兩種實(shí)現(xiàn)方式,即 brk() 和 mmap()。
- 對(duì)小塊內(nèi)存(小于128K),C 標(biāo)準(zhǔn)庫(kù)使用 brk() 來(lái)分配,也就是通過(guò)移動(dòng)堆頂?shù)奈恢脕?lái)分配內(nèi)存。這些內(nèi)存釋放后并不會(huì)立刻歸還系統(tǒng),而是被緩存起來(lái),這樣就可以重復(fù)使用。
- 而大塊內(nèi)存(大于 128K),則直接使用內(nèi)存映射 mmap() 來(lái)分配,也就是在文件映射段找一塊空閑內(nèi)存分配出去。
這兩種方式,自然各有優(yōu)缺點(diǎn):
- brk() 方式的緩存,可以減少缺頁(yè)異常的發(fā)生,提高內(nèi)存訪問(wèn)效率。不過(guò),由于這些內(nèi)存沒(méi)有歸還系統(tǒng),在內(nèi)存工作繁忙時(shí),頻繁的內(nèi)存分配和釋放會(huì)造成內(nèi)存碎片。
- mmap() 方式分配的內(nèi)存,會(huì)在釋放時(shí)直接歸還系統(tǒng),所以每次 mmap 都會(huì)發(fā)生缺頁(yè)異常。在內(nèi)存工作繁忙時(shí),頻繁的內(nèi)存分配會(huì)導(dǎo)致大量的缺頁(yè)異常,使內(nèi)核的管理負(fù)擔(dān)增大。這也是malloc 只對(duì)大塊內(nèi)存使用 mmap 的原因。
了解這兩種調(diào)用方式后,還需要清楚一點(diǎn),那就是,當(dāng)這兩種調(diào)用發(fā)生后,其實(shí)并沒(méi)有真正分配內(nèi)存。這些內(nèi)存,都只在首次訪問(wèn)時(shí)才分配,也就是通過(guò)缺頁(yè)異常進(jìn)入內(nèi)核中,再由內(nèi)核來(lái)分配內(nèi)存。
整體來(lái)說(shuō),Linux 使用伙伴系統(tǒng)來(lái)管理內(nèi)存分配。這些內(nèi)存在MMU中以頁(yè)為單位進(jìn)行管理,伙伴系統(tǒng)也一樣,以頁(yè)為單位來(lái)管理內(nèi)存,并且會(huì)通過(guò)相鄰頁(yè)的合并,減少內(nèi)存碎片化(比如brk方式造成的內(nèi)存碎片);在用戶空間,malloc 通過(guò) brk() 分配的內(nèi)存,在釋放時(shí)并不立即歸還系統(tǒng),而是緩存起來(lái)重復(fù)利用;在內(nèi)核空間,Linux 則通過(guò) slab 分配器來(lái)管理小內(nèi)存。可以把slab 看成構(gòu)建在伙伴系統(tǒng)上的一個(gè)緩存,主要作用就是分配并釋放內(nèi)核中的小對(duì)象。
對(duì)內(nèi)存來(lái)說(shuō),如果只分配而不釋放,就會(huì)造成內(nèi)存泄漏,甚至?xí)谋M系統(tǒng)內(nèi)存。所以,在應(yīng)用程序用完內(nèi)存后,還需要調(diào)用 free() 或 unmap(),來(lái)釋放這些不用的內(nèi)存。
當(dāng)然,系統(tǒng)也不會(huì)任由某個(gè)進(jìn)程用完所有內(nèi)存。在發(fā)現(xiàn)內(nèi)存緊張時(shí),系統(tǒng)就會(huì)通過(guò)一系列機(jī)制來(lái)回收內(nèi)存,比如下面這三種方式:
- 回收緩存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的內(nèi)存頁(yè)面;
- 回收不常訪問(wèn)的內(nèi)存,把不常用的內(nèi)存通過(guò)交換分區(qū)直接寫到磁盤中;
- 殺死進(jìn)程,內(nèi)存緊張時(shí)系統(tǒng)還會(huì)通過(guò) OOM(Out of Memory),直接殺掉占用大量?jī)?nèi)存的進(jìn)程。
其中,第二種方式回收不常訪問(wèn)的內(nèi)存時(shí),會(huì)用到交換分區(qū)(以下簡(jiǎn)稱 Swap)。Swap 其實(shí)就是把一塊磁盤空間當(dāng)成內(nèi)存來(lái)用。它可以把進(jìn)程暫時(shí)不用的數(shù)據(jù)存儲(chǔ)到磁盤中(這個(gè)過(guò)程稱為換出),當(dāng)進(jìn)程訪問(wèn)這些內(nèi)存時(shí),再?gòu)拇疟P讀取這些數(shù)據(jù)到內(nèi)存中(這個(gè)過(guò)程稱為換入)。
所以,可以發(fā)現(xiàn),Swap 把系統(tǒng)的可用內(nèi)存變大了。不過(guò)要注意,通常只在內(nèi)存不足時(shí),才會(huì)發(fā)生 Swap 交換。并且由于磁盤讀寫的速度遠(yuǎn)比內(nèi)存慢,Swap 會(huì)導(dǎo)致嚴(yán)重的內(nèi)存性能問(wèn)題。
第三種方式提到的 OOM(Out of Memory),其實(shí)是內(nèi)核的一種保護(hù)機(jī)制。它監(jiān)控進(jìn)程的內(nèi)存使用情況,并且使用 oom_score 為每個(gè)進(jìn)程的內(nèi)存使用情況進(jìn)行評(píng)分:
- 一個(gè)進(jìn)程消耗的內(nèi)存越大,oom_score 就越大;
- 一個(gè)進(jìn)程運(yùn)行占用的 CPU 越多,oom_score 就越小。
這樣,進(jìn)程的 oom_score 越大,代表消耗的內(nèi)存越多,也就越容易被 OOM 殺死,從而可以更好保護(hù)系統(tǒng)。
當(dāng)然,為了實(shí)際工作的需要,管理員可以通過(guò) /proc 文件系統(tǒng),手動(dòng)設(shè)置進(jìn)程的 oom_adj ,從而調(diào)整進(jìn)程的 oom_score;oom_adj 的范圍是 [-17, 15],數(shù)值越大,表示進(jìn)程越容易被 OOM 殺死;數(shù)值越小,表示進(jìn)程越不容易被 OOM 殺死,其中 -17 表示禁止OOM。
比如用下面的命令,就可以把 sshd 進(jìn)程的 oom_adj 調(diào)小為 -16,這樣, sshd 進(jìn)程就不容易被 OOM 殺死。
1 echo -16 > /proc/$(pidof sshd)/oom_adj
三、進(jìn)程與內(nèi)存的交互舞步
3.1進(jìn)程啟動(dòng)時(shí)的內(nèi)存加載
當(dāng)我們啟動(dòng)一個(gè)進(jìn)程時(shí),操作系統(tǒng)就像是一個(gè)忙碌的 “搬運(yùn)工”,開(kāi)始了一系列復(fù)雜而有序的內(nèi)存加載工作。以 Windows系統(tǒng)為例,當(dāng)我們雙擊一個(gè).exe 可執(zhí)行文件時(shí),操作系統(tǒng)首先會(huì)讀取該文件的頭部信息,這個(gè)頭部信息就像是一個(gè) “導(dǎo)航圖”,包含了程序運(yùn)行所需的各種關(guān)鍵信息,如程序的入口點(diǎn)、依賴的動(dòng)態(tài)鏈接庫(kù)(DLL)等。
操作系統(tǒng)會(huì)為進(jìn)程分配虛擬內(nèi)存空間,這個(gè)空間就像是一個(gè) “虛擬舞臺(tái)”,進(jìn)程將在上面進(jìn)行各種操作 。然后,操作系統(tǒng)會(huì)根據(jù)可執(zhí)行文件頭部的信息,將程序的主要代碼段和初始化數(shù)據(jù)加載到虛擬內(nèi)存的相應(yīng)位置。這些代碼段和數(shù)據(jù)是程序啟動(dòng)和運(yùn)行的基礎(chǔ),就像是一場(chǎng)演出的核心演員和基本道具。在加載代碼段時(shí),CPU 會(huì)讀取其中的指令,開(kāi)始執(zhí)行程序的初始化工作,比如初始化全局變量、設(shè)置程序的運(yùn)行環(huán)境等。
對(duì)于依賴的動(dòng)態(tài)鏈接庫(kù),操作系統(tǒng)會(huì)在內(nèi)存中查找是否已經(jīng)加載了這些庫(kù)。如果已經(jīng)加載,就直接將庫(kù)的地址映射到進(jìn)程的虛擬地址空間中,讓進(jìn)程可以共享這些庫(kù)的代碼和數(shù)據(jù) ,就像多個(gè)進(jìn)程可以共用同一個(gè)舞臺(tái)道具;如果沒(méi)有加載,操作系統(tǒng)會(huì)從磁盤中讀取相應(yīng)的動(dòng)態(tài)鏈接庫(kù)文件,并將其加載到內(nèi)存中,然后再進(jìn)行地址映射。動(dòng)態(tài)鏈接庫(kù)的使用可以節(jié)省內(nèi)存空間,提高程序的可維護(hù)性和可擴(kuò)展性 。許多應(yīng)用程序都會(huì)依賴于系統(tǒng)提供的一些通用的動(dòng)態(tài)鏈接庫(kù),如 Windows 系統(tǒng)中的 Kernel32.dll,它提供了許多基本的操作系統(tǒng)功能調(diào)用。
3.2運(yùn)行時(shí)內(nèi)存分配
在進(jìn)程運(yùn)行過(guò)程中,常常需要?jiǎng)討B(tài)分配內(nèi)存來(lái)存儲(chǔ)一些臨時(shí)數(shù)據(jù)。以 C 語(yǔ)言中的malloc函數(shù)為例,它是進(jìn)程運(yùn)行時(shí)動(dòng)態(tài)內(nèi)存分配的一個(gè)典型工具。當(dāng)我們調(diào)用malloc函數(shù)時(shí),它會(huì)向操作系統(tǒng)申請(qǐng)一定大小的內(nèi)存空間。
在 32 位的 Linux 系統(tǒng)中,malloc的內(nèi)存分配機(jī)制如下:當(dāng)請(qǐng)求的內(nèi)存小于 128KB 時(shí),malloc會(huì)使用brk系統(tǒng)調(diào)用,通過(guò)移動(dòng)堆頂指針來(lái)分配內(nèi)存 。假設(shè)堆頂指針初始指向地址 0x1000,我們調(diào)用malloc(100)申請(qǐng) 100 字節(jié)的內(nèi)存,malloc會(huì)將堆頂指針移動(dòng)到 0x1064(假設(shè)系統(tǒng)內(nèi)存對(duì)齊為 8 字節(jié),100 字節(jié)向上取整為 104 字節(jié),加上一些元數(shù)據(jù),假設(shè)為 8 字節(jié),共 112 字節(jié),即 0x70,所以堆頂指針移動(dòng)到 0x1000 + 0x70 = 0x1070),并返回 0x1008 這個(gè)地址給用戶程序,用戶程序就可以使用這塊內(nèi)存來(lái)存儲(chǔ)數(shù)據(jù)。
當(dāng)請(qǐng)求的內(nèi)存大于等于 128KB 時(shí),malloc會(huì)使用mmap系統(tǒng)調(diào)用,在堆和棧之間的內(nèi)存區(qū)域中找一塊合適的空閑內(nèi)存進(jìn)行分配 。mmap會(huì)在虛擬地址空間中創(chuàng)建一個(gè)新的映射,將磁盤上的文件或者匿名內(nèi)存區(qū)域映射到進(jìn)程的虛擬地址空間中。這樣,進(jìn)程就可以像訪問(wèn)普通內(nèi)存一樣訪問(wèn)這個(gè)映射區(qū)域。
在動(dòng)態(tài)內(nèi)存分配過(guò)程中,虛擬內(nèi)存的寫時(shí)復(fù)制(Copy - on - Write,COW)策略發(fā)揮了重要作用。當(dāng)一個(gè)進(jìn)程通過(guò)fork系統(tǒng)調(diào)用創(chuàng)建子進(jìn)程時(shí),子進(jìn)程會(huì)共享父進(jìn)程的內(nèi)存頁(yè)面。在子進(jìn)程或父進(jìn)程沒(méi)有對(duì)這些共享頁(yè)面進(jìn)行寫操作之前,它們實(shí)際上共享的是相同的物理內(nèi)存頁(yè)面 ,只有當(dāng)其中一個(gè)進(jìn)程試圖對(duì)共享頁(yè)面進(jìn)行寫操作時(shí),操作系統(tǒng)才會(huì)為寫操作的進(jìn)程復(fù)制一份物理內(nèi)存頁(yè)面,使得父子進(jìn)程擁有各自獨(dú)立的物理內(nèi)存頁(yè)面,這樣可以節(jié)省內(nèi)存資源,提高系統(tǒng)的效率。
如果進(jìn)程訪問(wèn)的內(nèi)存頁(yè)面不在物理內(nèi)存中,就會(huì)發(fā)生缺頁(yè)中斷。操作系統(tǒng)會(huì)根據(jù)頁(yè)表信息,從磁盤的虛擬內(nèi)存中找到對(duì)應(yīng)的頁(yè)面,并將其加載到物理內(nèi)存中 。然后,操作系統(tǒng)會(huì)更新頁(yè)表,將虛擬地址與新加載的物理內(nèi)存頁(yè)面建立映射關(guān)系,使得進(jìn)程能夠繼續(xù)訪問(wèn)該內(nèi)存頁(yè)面。
3.3內(nèi)存回收與管理
當(dāng)進(jìn)程結(jié)束運(yùn)行或者內(nèi)存不足時(shí),操作系統(tǒng)就會(huì)進(jìn)行內(nèi)存回收工作,以釋放內(nèi)存空間供其他進(jìn)程使用。當(dāng)一個(gè)進(jìn)程結(jié)束時(shí),操作系統(tǒng)會(huì)回收該進(jìn)程所占用的所有虛擬內(nèi)存空間,并將這些空間標(biāo)記為空閑 。操作系統(tǒng)會(huì)檢查進(jìn)程使用的堆內(nèi)存、棧內(nèi)存以及其他動(dòng)態(tài)分配的內(nèi)存區(qū)域,將這些內(nèi)存歸還給內(nèi)存管理系統(tǒng),就像是一場(chǎng)演出結(jié)束后,工作人員會(huì)將舞臺(tái)上的道具和設(shè)備清理干凈,為下一場(chǎng)演出做準(zhǔn)備。
在內(nèi)存不足的情況下,操作系統(tǒng)會(huì)采用內(nèi)存置換算法來(lái)決定哪些內(nèi)存頁(yè)面可以被暫時(shí)置換到磁盤上,以騰出物理內(nèi)存空間。常見(jiàn)的內(nèi)存置換算法有最近最少使用(LRU,Least Recently Used)算法 。LRU 算法的核心思想是,如果一個(gè)內(nèi)存頁(yè)面在最近一段時(shí)間內(nèi)沒(méi)有被訪問(wèn)過(guò),那么它在未來(lái)被訪問(wèn)的可能性也較小,因此可以將其置換出去。操作系統(tǒng)會(huì)維護(hù)一個(gè)內(nèi)存頁(yè)面的訪問(wèn)時(shí)間記錄,當(dāng)需要置換頁(yè)面時(shí),選擇訪問(wèn)時(shí)間最早的頁(yè)面進(jìn)行置換。假設(shè)內(nèi)存中有三個(gè)頁(yè)面 A、B、C,它們的訪問(wèn)時(shí)間依次為 10:00、10:10、10:20,當(dāng)內(nèi)存不足需要置換頁(yè)面時(shí),LRU 算法會(huì)選擇頁(yè)面 A 進(jìn)行置換,因?yàn)樗亲罹脹](méi)有被訪問(wèn)的頁(yè)面。
除了 LRU 算法,還有先進(jìn)先出(FIFO,F(xiàn)irst In First Out)算法,它是將最早進(jìn)入內(nèi)存的頁(yè)面置換出去;時(shí)鐘(Clock)算法,它是 LRU 算法的一種近似實(shí)現(xiàn),通過(guò)一個(gè)循環(huán)鏈表和一個(gè)訪問(wèn)位來(lái)模擬 LRU 算法的行為 。這些算法各有優(yōu)缺點(diǎn),操作系統(tǒng)會(huì)根據(jù)具體的應(yīng)用場(chǎng)景和系統(tǒng)需求選擇合適的算法,以確保系統(tǒng)的內(nèi)存管理高效、穩(wěn)定。
四、內(nèi)存管理的底層奧秘
4.1MMU與地址映射
在進(jìn)程使用內(nèi)存的過(guò)程中,內(nèi)存管理單元(MMU,Memory Management Unit)扮演著至關(guān)重要的角色,它就像是一個(gè)精準(zhǔn)的 “翻譯官”,負(fù)責(zé)將進(jìn)程的虛擬地址動(dòng)態(tài)翻譯為物理地址 。
當(dāng) CPU 需要訪問(wèn)內(nèi)存中的數(shù)據(jù)時(shí),它會(huì)首先產(chǎn)生一個(gè)虛擬地址。這個(gè)虛擬地址會(huì)被發(fā)送到 MMU。MMU 內(nèi)部包含了一個(gè)高速緩存,即轉(zhuǎn)換后備緩沖器(TLB,Translation Lookaside Buffer),以及與進(jìn)程相關(guān)的頁(yè)表 。TLB 中存儲(chǔ)了近期使用過(guò)的虛擬地址到物理地址的映射關(guān)系,就像是一個(gè)常用詞匯的快速翻譯手冊(cè)。當(dāng) MMU 接收到虛擬地址后,會(huì)首先在 TLB 中查找對(duì)應(yīng)的映射關(guān)系。如果在 TLB 中命中,MMU 可以快速地獲取到對(duì)應(yīng)的物理地址,從而大大提高了地址轉(zhuǎn)換的速度 。
如果在TLB中沒(méi)有命中,MMU就需要通過(guò)進(jìn)程的頁(yè)表來(lái)查找映射關(guān)系。頁(yè)表是一個(gè)存儲(chǔ)虛擬地址與物理地址映射關(guān)系的數(shù)據(jù)結(jié)構(gòu),它就像是一本完整的翻譯詞典 。MMU會(huì)根據(jù)虛擬地址中的頁(yè)號(hào),在頁(yè)表中查找對(duì)應(yīng)的物理頁(yè)框號(hào)。找到物理頁(yè)框號(hào)后,再結(jié)合虛擬地址中的頁(yè)內(nèi)偏移量,就可以計(jì)算出最終的物理地址。在 32 位的系統(tǒng)中,如果頁(yè)面大小為4KB,那么虛擬地址可以被劃分為20位的頁(yè)號(hào)和12位的頁(yè)內(nèi)偏移量 。假設(shè)虛擬地址為 0x00401000,其中 0x0040 是頁(yè)號(hào),0x1000 是頁(yè)內(nèi)偏移量。MMU 通過(guò)頁(yè)號(hào) 0x0040 在頁(yè)表中查找對(duì)應(yīng)的物理頁(yè)框號(hào),假設(shè)找到的物理頁(yè)框號(hào)為 0x0080,那么最終的物理地址就是 0x00801000(0x0080 << 12 | 0x1000)。
4.2頁(yè)表與多級(jí)頁(yè)表
進(jìn)程的頁(yè)表是管理虛擬地址與物理地址映射關(guān)系的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)。它就像是一個(gè)精心編排的 “映射目錄”,每個(gè)表項(xiàng)都記錄了一個(gè)虛擬頁(yè)到物理頁(yè)框的映射關(guān)系 。在簡(jiǎn)單的分頁(yè)系統(tǒng)中,頁(yè)表可能是一個(gè)線性的數(shù)組,數(shù)組的索引是虛擬頁(yè)號(hào),數(shù)組的值是對(duì)應(yīng)的物理頁(yè)框號(hào) 。
然而,對(duì)于32位甚至64 位的地址空間來(lái)說(shuō),如果采用簡(jiǎn)單的線性頁(yè)表,會(huì)占用大量的內(nèi)存空間。在 32 位系統(tǒng)中,若頁(yè)面大小為4KB,進(jìn)程的虛擬地址空間為4GB,那么頁(yè)表將包含 1M 個(gè)頁(yè)表項(xiàng)(4GB / 4KB = 1M) 。假設(shè)每個(gè)頁(yè)表項(xiàng)占用 4 個(gè)字節(jié),那么僅頁(yè)表就會(huì)占用4MB 的連續(xù)內(nèi)存空間,這對(duì)于內(nèi)存資源來(lái)說(shuō)是一種巨大的浪費(fèi)。
為了解決這個(gè)問(wèn)題,現(xiàn)代操作系統(tǒng)通常采用多級(jí)頁(yè)表。以二級(jí)頁(yè)表為例,它將虛擬地址空間進(jìn)一步劃分。在 32 位系統(tǒng)中,可能將 20 位的頁(yè)號(hào)再劃分為 10 位的外層頁(yè)號(hào)和 10 位的內(nèi)層頁(yè)號(hào) 。外層頁(yè)表的每個(gè)表項(xiàng)指向一個(gè)內(nèi)層頁(yè)表,內(nèi)層頁(yè)表的表項(xiàng)才真正記錄虛擬頁(yè)到物理頁(yè)框的映射關(guān)系 。這樣,只有當(dāng)外層頁(yè)表中對(duì)應(yīng)的表項(xiàng)被訪問(wèn)時(shí),才會(huì)加載相應(yīng)的內(nèi)層頁(yè)表,大大減少了內(nèi)存的占用 。假設(shè)外層頁(yè)號(hào)為 0x001,內(nèi)層頁(yè)號(hào)為 0x002,通過(guò)外層頁(yè)表找到對(duì)應(yīng)的內(nèi)層頁(yè)表,再在內(nèi)層頁(yè)表中通過(guò)內(nèi)層頁(yè)號(hào) 0x002 找到物理頁(yè)框號(hào),從而實(shí)現(xiàn)虛擬地址到物理地址的轉(zhuǎn)換。
多級(jí)頁(yè)表不僅減少了內(nèi)存占用,還支持離散存儲(chǔ)。由于頁(yè)表可以離散地存儲(chǔ)在物理內(nèi)存中,不再需要連續(xù)的內(nèi)存空間來(lái)存放整個(gè)頁(yè)表,提高了內(nèi)存的使用效率和靈活性 。
4.3緩存機(jī)制與局部性原理
為了進(jìn)一步提高內(nèi)存訪問(wèn)的速度,計(jì)算機(jī)系統(tǒng)引入了多種緩存機(jī)制,其中 CPU 緩存、Cache 和 TLB 表起著關(guān)鍵作用,它們的工作原理都基于局部性原理。
局部性原理包括時(shí)間局部性和空間局部性。時(shí)間局部性是指如果一個(gè)數(shù)據(jù)項(xiàng)被訪問(wèn),那么在不久的將來(lái)它很可能再次被訪問(wèn) 。在一個(gè)循環(huán)結(jié)構(gòu)中,循環(huán)變量和循環(huán)體內(nèi)頻繁使用的數(shù)據(jù)會(huì)被多次訪問(wèn),這就體現(xiàn)了時(shí)間局部性??臻g局部性是指如果一個(gè)數(shù)據(jù)項(xiàng)被訪問(wèn),那么與它相鄰的數(shù)據(jù)項(xiàng)很可能也會(huì)被訪問(wèn) 。當(dāng)我們?cè)L問(wèn)一個(gè)數(shù)組時(shí),通常會(huì)按照順序依次訪問(wèn)數(shù)組中的元素,這就利用了空間局部性。
CPU 緩存(Cache)是位于 CPU 和主存之間的高速存儲(chǔ)部件,它利用了局部性原理來(lái)提高內(nèi)存訪問(wèn)命中率。當(dāng) CPU 需要訪問(wèn)內(nèi)存數(shù)據(jù)時(shí),首先會(huì)在 Cache 中查找 。如果在 Cache 中命中,CPU 可以快速地獲取數(shù)據(jù),因?yàn)?Cache 的訪問(wèn)速度比主存快得多,通??梢赃_(dá)到主存訪問(wèn)速度的幾十倍甚至上百倍 。如果在 Cache 中沒(méi)有命中,才會(huì)訪問(wèn)主存,并將主存中的數(shù)據(jù)塊加載到 Cache 中,以便后續(xù)訪問(wèn)。
TLB 表作為 MMU 中的高速緩存,同樣利用了局部性原理。它緩存了近期使用過(guò)的虛擬地址到物理地址的映射關(guān)系 。當(dāng) MMU 接收到虛擬地址時(shí),先在 TLB 中查找映射關(guān)系,如果命中,就可以快速完成地址轉(zhuǎn)換,避免了通過(guò)頁(yè)表進(jìn)行查找的開(kāi)銷 。由于TLB的訪問(wèn)速度極快,幾乎與 CPU 的速度同步,所以 TLB 的命中率對(duì)于地址轉(zhuǎn)換的效率至關(guān)重要 。