現(xiàn)代操作系統(tǒng)都使用了虛擬內(nèi)存系統(tǒng)來(lái)進(jìn)行內(nèi)存管理
1. 進(jìn)程直接訪問(wèn)物理內(nèi)存造成的問(wèn)題
早期的操作系統(tǒng)撑教,進(jìn)程直接訪問(wèn)物理內(nèi)存吨灭,存在以下問(wèn)題
- 內(nèi)存不足
當(dāng)進(jìn)程訪問(wèn)的內(nèi)存地址超出物理內(nèi)存范圍時(shí)吴叶,內(nèi)存不足煤率,程序會(huì)崩潰剃毒。
舉個(gè)例子,32位CPU的系統(tǒng)纠亚,地址線為32條,可以訪問(wèn)的尋址空間為 筋夏,如果機(jī)器只有1GB內(nèi)存蒂胞,當(dāng)進(jìn)程訪問(wèn)一個(gè)超過(guò) 1GB 范圍的內(nèi)存地址時(shí),程序崩潰条篷。 - 內(nèi)存碎片化
程序頻繁啟動(dòng)和退出骗随,會(huì)導(dǎo)致內(nèi)存的頻繁申請(qǐng)和釋放岳瞭,進(jìn)而產(chǎn)生內(nèi)存碎片,當(dāng)進(jìn)行內(nèi)存分配申請(qǐng)時(shí)蚊锹,有可能沒(méi)有任何一塊連續(xù)的內(nèi)存區(qū)域足夠大瞳筏,而導(dǎo)致分配失敗,盡管有足夠多的碎片牡昆,總量超過(guò)申請(qǐng)量姚炕。 - 內(nèi)存訪問(wèn)沖突
多個(gè)進(jìn)程可能訪問(wèn)相同的物理內(nèi)存地址,造成數(shù)據(jù)誤修改丢烘、錯(cuò)亂等柱宦。因?yàn)檫M(jìn)程并不知道哪些內(nèi)存被占用了。
如何解決上述這些問(wèn)題呢播瞳?操作系統(tǒng)引入了虛擬內(nèi)存系統(tǒng)來(lái)解決掸刊。
2. 什么是虛擬內(nèi)存
基于以上問(wèn)題,操作系統(tǒng)使用了虛擬內(nèi)存的技術(shù)方案來(lái)進(jìn)行進(jìn)程見(jiàn)內(nèi)存管理赢乓∮遣啵可以理解成進(jìn)程和物理內(nèi)存之間的一層映射或容器,進(jìn)程使用虛擬內(nèi)存地址牌芋,也叫邏輯地址蚓炬,被MMU硬件單元(Memory Manager Unit)映射到對(duì)應(yīng)的物理內(nèi)存地址。我們可以看看虛擬內(nèi)存系統(tǒng)是如何解決上述的直接訪問(wèn)物理內(nèi)存的問(wèn)題躺屁?
- 解決內(nèi)存不足
當(dāng)進(jìn)程申請(qǐng)內(nèi)存肯夏,發(fā)現(xiàn)物理內(nèi)存不足時(shí),系統(tǒng)會(huì)根據(jù)頁(yè)面置換算法把暫時(shí)不用的內(nèi)存置換到硬盤(pán)上犀暑,并把對(duì)應(yīng)的邏輯地址映射到硬盤(pán)驯击,而把釋放出來(lái)的物理內(nèi)存返回給最新的進(jìn)程,并維護(hù)該邏輯地址到物理地址的映射關(guān)系耐亏,產(chǎn)生一種無(wú)限內(nèi)存的錯(cuò)覺(jué)徊都。 - 解決內(nèi)存碎片
虛擬內(nèi)存系統(tǒng)映射表可以找到多塊物理內(nèi)存碎片合并到一個(gè)邏輯塊中。 - 內(nèi)存訪問(wèn)沖突
不同進(jìn)程相同的虛擬地址苹熏,但是每個(gè)進(jìn)程有自己的映射表碟贾,通常會(huì)映射到不同的物理內(nèi)存,不會(huì)互相干擾轨域。
有些情況下袱耽,進(jìn)程間需要共享內(nèi)存,那么就是不同進(jìn)程的虛擬地址可以映射到相同的物理地址即可干发。
3. 虛擬內(nèi)存的原理
3.1 內(nèi)存分段
最早的虛擬內(nèi)存管理方式為內(nèi)存分段的方式朱巨,在進(jìn)程啟動(dòng)時(shí),為進(jìn)程分配一塊連續(xù)的內(nèi)存枉长,劃分為代碼段冀续、數(shù)據(jù)段琼讽、棧段、堆段等部分洪唐,進(jìn)程維護(hù)一個(gè)段表钻蹬,記錄每個(gè)段在物理內(nèi)存的起始地址(段基地址)和該段的最大偏移(段界限),虛擬地址由段號(hào)和段內(nèi)偏移值組成凭需,在映射時(shí)问欠,根據(jù)段號(hào)從段表中查詢物理地址起始位置,再加上偏移值得到物理內(nèi)存地址
內(nèi)存分段容易導(dǎo)致兩個(gè)問(wèn)題
- 物理內(nèi)存容易出現(xiàn)內(nèi)存碎片
- 當(dāng)需要進(jìn)行內(nèi)存置換到硬盤(pán)時(shí)粒蜈,只能以段為單位顺献,導(dǎo)致內(nèi)存交換效率較低
3.2 內(nèi)存分頁(yè)
為了解決內(nèi)存分段方案中較大內(nèi)存碎片和內(nèi)存交換空間大導(dǎo)致效率低的問(wèn)題,內(nèi)存分頁(yè)的方案被提出枯怖。該方案的主要做法是:
- 將整個(gè)虛擬和物理內(nèi)存空間分成若干份固定尺寸的大小注整,每一份稱為一頁(yè),通常在 Linux 下度硝,每一頁(yè)大小為 4KB
- 虛擬地址和物理地址通過(guò)頁(yè)表來(lái)進(jìn)行映射
3.2.1 如何解決內(nèi)存分段的大塊外部碎片肿轨?
內(nèi)存分頁(yè),最小的內(nèi)存分配單位為頁(yè)塘淑,通常要比段小的多萝招,頁(yè)之間不需要保證連續(xù),可以將多個(gè)不連續(xù)的頁(yè)組裝成一塊較大的內(nèi)存區(qū)域存捺,因此不會(huì)產(chǎn)生大塊的外部?jī)?nèi)存
3.2.2 如何解決內(nèi)存分段的內(nèi)存交換效率低問(wèn)題?
內(nèi)存分段在產(chǎn)生內(nèi)存 Swap Out 和 Swap In 到硬盤(pán)時(shí)曙蒸,都是以段為單位的捌治,通常較大,而內(nèi)存分頁(yè)可以以頁(yè)為單位纽窟,更小更精細(xì)肖油,在交換時(shí)的效率更高碑诉。
3.2.3 虛擬地址如何映射到物理地址
虛擬地址由頁(yè)號(hào)和業(yè)內(nèi)偏移量組成陶因,內(nèi)存中存放一份頁(yè)表,保存了虛擬頁(yè)和物理頁(yè)的對(duì)應(yīng)關(guān)系径密,根據(jù)虛擬地址中的虛擬頁(yè)號(hào)到頁(yè)表中讀取物理頁(yè)號(hào)审孽,再加上虛擬地址中的偏移量县袱,就能得到物理地址
3.2.4 內(nèi)存分頁(yè)有什么缺陷嗎?
(1) 有內(nèi)部碎片
頁(yè)的大小固定為4KB佑力,當(dāng)程序分配的內(nèi)存需求小于 4KB 時(shí)式散,操作系統(tǒng)也要分配4KB,容易造成頁(yè)內(nèi)碎片
(2)頁(yè)表占用內(nèi)存較大
頁(yè)表本身也占用了內(nèi)存打颤,例如在 32 位環(huán)境下暴拄,虛擬地址空間為 漓滔,一個(gè)頁(yè)大小是 ,那么頁(yè)數(shù)為 大約為 100 萬(wàn)頁(yè)乖篷,每個(gè)頁(yè)表項(xiàng)需要 4 個(gè)字節(jié)响驴,則需要 4MB 的空間來(lái)存儲(chǔ)頁(yè)表,每個(gè)進(jìn)程都有自己的一份頁(yè)表撕蔼,4MB 還是有點(diǎn)大了踏施,我們可以通過(guò)多級(jí)頁(yè)表 來(lái)減少頁(yè)表占用的內(nèi)存大小
3.3 段頁(yè)式管理
3.3.1 實(shí)現(xiàn)方式
分段和分頁(yè)并不是對(duì)立的,Linux 將兩種方式組合起來(lái)罕邀,得到段頁(yè)式內(nèi)存管理方案
- 現(xiàn)將進(jìn)程內(nèi)存進(jìn)行分段畅形、每段有各自的邏輯意義
- 再將每個(gè)段進(jìn)行分頁(yè)
- 虛擬地址由段號(hào)、段內(nèi)頁(yè)號(hào)和頁(yè)內(nèi)偏移量組成
-
每個(gè)進(jìn)程對(duì)應(yīng)一張段表诉探、每個(gè)段對(duì)應(yīng)一張頁(yè)表日熬,段表中的地址為頁(yè)表的起始地址、頁(yè)表中的地址為該頁(yè)的物理地址
3.3.2 地址映射
段頁(yè)式內(nèi)存的地址映射過(guò)程:
- 從虛擬地址中取出段號(hào)肾胯,訪問(wèn)段表竖席,得到頁(yè)表起始地址
- 從虛擬地址中取出段內(nèi)頁(yè)號(hào),訪問(wèn)對(duì)應(yīng)的頁(yè)表敬肚,得到物理頁(yè)號(hào)
- 從虛擬地址中取出頁(yè)內(nèi)偏移毕荐,和物理頁(yè)號(hào)組合,得到物理地址
4. Linux 程序的內(nèi)存布局
4.1 用戶空間和內(nèi)核空間
Linux 系統(tǒng)將虛擬地址空間劃分為內(nèi)核空間和用戶空間艳馒,對(duì)于 32 位環(huán)境憎亚,低地址的 3G 為用戶空間,高地址 1G 為內(nèi)核空間
內(nèi)核空間和用戶空間的區(qū)別:
- 進(jìn)程在用戶態(tài)時(shí)弄慰,只能訪問(wèn)用戶空間內(nèi)存
- 進(jìn)程只有進(jìn)入內(nèi)核態(tài)時(shí)第美,才能訪問(wèn)內(nèi)核空間內(nèi)存
雖然每個(gè)進(jìn)程都有各自獨(dú)立的虛擬內(nèi)存空間,但是 <font color=red>每個(gè)進(jìn)程的內(nèi)核地址陆爽,都關(guān)聯(lián)相同的物理內(nèi)存地址</font>
4.2 用戶空間分段
用戶空間被劃分成 6 種不同的內(nèi)存段
- 代碼段:程序編譯后的可執(zhí)行代碼(指令)存放區(qū)域什往,在編譯時(shí)確定了,同一個(gè)程序在不同機(jī)器上慌闭、在同一個(gè)機(jī)器上的不同次運(yùn)行别威,同一個(gè)方法的入口地址都是確定的
- 數(shù)據(jù)段:存放程序已初始化的靜態(tài)常量和全局變量
- BSS 段:存放未初始化的靜態(tài)常量和全局變量
- 堆:動(dòng)態(tài)分配的內(nèi)存區(qū)域,從低地址向高地址增長(zhǎng)驴剔,大小不固定
- 棧:存放局部變量省古、函數(shù)調(diào)用上下文等,棧的大小在程序啟動(dòng)時(shí)就固定了仔拟,一般是 8MB衫樊,系統(tǒng)提供了參數(shù)可以修改