程序裝載進(jìn)入內(nèi)存

上一講溺蕉,我們看到了如何通過(guò)鏈接器某宪,把多個(gè)文件合并成一個(gè)最終可執(zhí)行文件福压。在運(yùn)行這些可執(zhí)行文件的時(shí)候掏秩,我們其實(shí)是通過(guò)一個(gè)裝載器或舞,解析 ELF 或者 PE 格式的可執(zhí)行文件。裝載器會(huì)把對(duì)應(yīng)的指令和數(shù)據(jù)加載到內(nèi)存里面來(lái)蒙幻,讓 CPU 去執(zhí)行映凳。

說(shuō)起來(lái)只是裝載到內(nèi)存里面這一句話(huà)的事兒,實(shí)際上裝載器需要滿(mǎn)足兩個(gè)要求邮破。

第一诈豌,可執(zhí)行程序加載后占用的內(nèi)存空間應(yīng)該是連續(xù)的,執(zhí)行指令的時(shí)候抒和,程序計(jì)數(shù)器是順序地一條一條指令執(zhí)行下去队询。這也就意味著,這一條條指令需要連續(xù)地存儲(chǔ)在一起构诚。

第二,我們需要同時(shí)加載很多個(gè)程序铆惑,并且不能讓程序自己規(guī)定在內(nèi)存中加載的位置范嘱。雖然編譯出來(lái)的指令里已經(jīng)有了對(duì)應(yīng)的各種各樣的內(nèi)存地址,但是實(shí)際加載的時(shí)候员魏,我們其實(shí)沒(méi)有辦法確保丑蛤,這個(gè)程序一定加載在哪一段內(nèi)存地址上。因?yàn)槲覀儸F(xiàn)在的計(jì)算機(jī)通常會(huì)同時(shí)運(yùn)行很多個(gè)程序撕阎,可能你想要的內(nèi)存地址已經(jīng)被其他加載了的程序占用了受裹。

要滿(mǎn)足這兩個(gè)基本的要求,我們很容易想到一個(gè)辦法虏束。那就是我們可以在內(nèi)存里面棉饶,找到一段連續(xù)的內(nèi)存空間,然后分配給裝載的程序镇匀,然后把這段連續(xù)的內(nèi)存空間地址照藻,和整個(gè)程序指令里指定的內(nèi)存地址做一個(gè)映射。

我們把指令里用到的內(nèi)存地址叫作虛擬內(nèi)存地址(Virtual Memory Address)汗侵,實(shí)際在內(nèi)存硬件里面的空間地址幸缕,我們叫物理內(nèi)存地址(Physical Memory Address)。

程序里有指令和各種內(nèi)存地址晰韵,我們只需要關(guān)心虛擬內(nèi)存地址就行了发乔。對(duì)于任何一個(gè)程序來(lái)說(shuō),它看到的都是同樣的內(nèi)存地址雪猪。我們維護(hù)一個(gè)虛擬內(nèi)存到物理內(nèi)存的映射表栏尚,這樣實(shí)際程序指令執(zhí)行的時(shí)候,會(huì)通過(guò)虛擬內(nèi)存地址浪蹂,找到對(duì)應(yīng)的物理內(nèi)存地址抵栈,然后執(zhí)行告材。因?yàn)槭沁B續(xù)的內(nèi)存地址空間,所以我們只需要維護(hù)映射關(guān)系的起始地址和對(duì)應(yīng)的空間大小就可以了古劲。

內(nèi)存分段

這種找出一段連續(xù)的物理內(nèi)存和虛擬內(nèi)存地址進(jìn)行映射的方法斥赋,我們叫分段(Segmentation)。這里的段产艾,就是指系統(tǒng)分配出來(lái)的那個(gè)連續(xù)的內(nèi)存空間疤剑。


分段的辦法很好,解決了程序本身不需要關(guān)心具體的物理內(nèi)存地址的問(wèn)題闷堡,但它也有一些不足之處隘膘,第一個(gè)就是內(nèi)存碎片(Memory Fragmentation)的問(wèn)題。

我們來(lái)看這樣一個(gè)例子杠览。我現(xiàn)在手頭的這臺(tái)電腦弯菊,有 1GB 的內(nèi)存。我們先啟動(dòng)一個(gè)圖形渲染程序踱阿,占用了 512MB 的內(nèi)存管钳,接著啟動(dòng)一個(gè) Chrome 瀏覽器,占用了 128MB 內(nèi)存软舌,再啟動(dòng)一個(gè) Python 程序才漆,占用了 256MB 內(nèi)存。這個(gè)時(shí)候佛点,我們關(guān)掉 Chrome醇滥,于是空閑內(nèi)存還有 1024 - 512 - 256 = 256MB。按理來(lái)說(shuō)超营,我們有足夠的空間再去裝載一個(gè)200MB 的程序鸳玩。但是,這 256MB 的內(nèi)存空間不是連續(xù)的演闭,而是被分成了兩段 128MB 的內(nèi)存怀喉。因此,實(shí)際情況是船响,我們的程序沒(méi)辦法加載進(jìn)來(lái)躬拢。


當(dāng)然,這個(gè)我們也有辦法解決见间。解決的辦法叫內(nèi)存交換(Memory Swapping)聊闯。

我們可以把 Python 程序占用的那 256MB 內(nèi)存寫(xiě)到硬盤(pán)上,然后再?gòu)挠脖P(pán)上讀回來(lái)到內(nèi)存里面米诉。不過(guò)讀回來(lái)的時(shí)候菱蔬,我們不再把它加載到原來(lái)的位置,而是緊緊跟在那已經(jīng)被占用了的 512MB 內(nèi)存后面。這樣拴泌,我們就有了連續(xù)的 256MB 內(nèi)存空間魏身,就可以去加載一個(gè)新的200MB 的程序。如果你自己安裝過(guò) Linux 操作系統(tǒng)蚪腐,你應(yīng)該遇到過(guò)分配一個(gè) swap 硬盤(pán)分區(qū)的問(wèn)題箭昵。這塊分出來(lái)的磁盤(pán)空間,其實(shí)就是專(zhuān)門(mén)給 Linux 操作系統(tǒng)進(jìn)行內(nèi)存交換用的回季。

虛擬內(nèi)存家制、分段,再加上內(nèi)存交換泡一,看起來(lái)似乎已經(jīng)解決了計(jì)算機(jī)同時(shí)裝載運(yùn)行很多個(gè)程序的問(wèn)題颤殴。不過(guò),你千萬(wàn)不要大意鼻忠,這三者的組合仍然會(huì)遇到一個(gè)性能瓶頸涵但。硬盤(pán)的訪(fǎng)問(wèn)速度要比內(nèi)存慢很多,而每一次內(nèi)存交換帖蔓,我們都需要把一大段連續(xù)的內(nèi)存數(shù)據(jù)寫(xiě)到硬盤(pán)上贤笆。所以,如果內(nèi)存交換的時(shí)候讨阻,交換的是一個(gè)很占內(nèi)存空間的程序,這樣整個(gè)機(jī)器都會(huì)顯得卡頓篡殷。

內(nèi)存分頁(yè)

既然問(wèn)題出在內(nèi)存碎片和內(nèi)存交換的空間太大上钝吮,那么解決問(wèn)題的辦法就是,少出現(xiàn)一些內(nèi)存碎片板辽。另外奇瘦,當(dāng)需要進(jìn)行內(nèi)存交換的時(shí)候,讓需要交換寫(xiě)入或者從磁盤(pán)裝載的數(shù)據(jù)更少一點(diǎn)劲弦,這樣就可以解決這個(gè)問(wèn)題耳标。這個(gè)辦法,在現(xiàn)在計(jì)算機(jī)的內(nèi)存管理里面邑跪,就叫作內(nèi)存分頁(yè)(Paging)次坡。

和分段這樣分配一整段連續(xù)的空間給到程序相比,分頁(yè)是把整個(gè)物理內(nèi)存空間切成一段段固定尺寸的大小画畅。而對(duì)應(yīng)的程序所需要占用的虛擬內(nèi)存空間砸琅,也會(huì)同樣切成一段段固定尺寸的大小。這樣一個(gè)連續(xù)并且尺寸固定的內(nèi)存空間轴踱,我們叫頁(yè)(Page)症脂。從虛擬內(nèi)存到物理內(nèi)存的映射,不再是拿整段連續(xù)的內(nèi)存的物理地址,而是按照一個(gè)一個(gè)頁(yè)來(lái)的诱篷。頁(yè)的尺寸一般遠(yuǎn)遠(yuǎn)小于整個(gè)程序的大小壶唤。在 Linux 下,我們通常只設(shè)置成 4KB棕所。你可以通過(guò)命令看看你手頭的 Linux 系統(tǒng)設(shè)置的頁(yè)的大小闸盔。

getconf PAGE_SIZE

由于內(nèi)存空間都是預(yù)先劃分好的,也就沒(méi)有了不能使用的碎片橙凳,而只有被釋放出來(lái)的很多4KB 的頁(yè)蕾殴。即使內(nèi)存空間不夠,需要讓現(xiàn)有的岛啸、正在運(yùn)行的其他程序钓觉,通過(guò)內(nèi)存交換釋放出一些內(nèi)存的頁(yè)出來(lái),一次性寫(xiě)入磁盤(pán)的也只有少數(shù)的一個(gè)頁(yè)或者幾個(gè)頁(yè)坚踩,不會(huì)花太多時(shí)間荡灾,讓整個(gè)機(jī)器被內(nèi)存交換的過(guò)程給卡住。


更進(jìn)一步地瞬铸,分頁(yè)的方式使得我們?cè)诩虞d程序的時(shí)候批幌,不再需要一次性都把程序加載到物理內(nèi)存中。我們完全可以在進(jìn)行虛擬內(nèi)存和物理內(nèi)存的頁(yè)之間的映射之后嗓节,并不真的把頁(yè)加載到物理內(nèi)存里荧缘,而是只在程序運(yùn)行中,需要用到對(duì)應(yīng)虛擬內(nèi)存頁(yè)里面的指令和數(shù)據(jù)時(shí)拦宣,再加載到物理內(nèi)存里面去截粗。

實(shí)際上,我們的操作系統(tǒng)鸵隧,的確是這么做的绸罗。當(dāng)要讀取特定的頁(yè),卻發(fā)現(xiàn)數(shù)據(jù)并沒(méi)有加載到物理內(nèi)存里的時(shí)候豆瘫,就會(huì)觸發(fā)一個(gè)來(lái)自于 CPU 的缺頁(yè)錯(cuò)誤(Page Fault)珊蟀。我們的操作系統(tǒng)會(huì)捕捉到這個(gè)錯(cuò)誤,然后將對(duì)應(yīng)的頁(yè)外驱,從存放在硬盤(pán)上的虛擬內(nèi)存里讀取出來(lái)育灸,加載到物理內(nèi)存里。這種方式昵宇,使得我們可以運(yùn)行那些遠(yuǎn)大于我們實(shí)際物理內(nèi)存的程序描扯。同時(shí),這樣一來(lái)趟薄,任何程序都不需要一次性加載完所有指令和數(shù)據(jù)绽诚,只需要加載當(dāng)前需要用到就行了。

通過(guò)虛擬內(nèi)存、內(nèi)存交換和內(nèi)存分頁(yè)這三個(gè)技術(shù)的組合恩够,我們最終得到了一個(gè)讓程序不需要考慮實(shí)際的物理內(nèi)存地址卒落、大小和當(dāng)前分配空間的解決方案。這些技術(shù)和方法蜂桶,對(duì)于我們程序的編寫(xiě)儡毕、編譯和鏈接過(guò)程都是透明的。這也是我們?cè)谟?jì)算機(jī)的軟硬件開(kāi)發(fā)中常用的一種方法扑媚,就是加入一個(gè)間接層腰湾。

通過(guò)引入虛擬內(nèi)存、頁(yè)映射和內(nèi)存交換疆股,我們的程序本身费坊,就不再需要考慮對(duì)應(yīng)的真實(shí)的內(nèi)存地址、程序加載旬痹、內(nèi)存管理等問(wèn)題了附井。任何一個(gè)程序,都只需要把內(nèi)存當(dāng)成是一塊完整而連續(xù)的空間來(lái)直接使用两残。

總結(jié)延伸

現(xiàn)在回到開(kāi)頭我問(wèn)你的問(wèn)題永毅,我們的電腦只要 640K 內(nèi)存就夠了嗎?很顯然,現(xiàn)在來(lái)看人弓,比爾·蓋茨的這個(gè)判斷是不合理的沼死,那為什么他會(huì)這么認(rèn)為呢?因?yàn)樗彩且粋€(gè)很優(yōu)秀的程序員啊!

在虛擬內(nèi)存、內(nèi)存交換和內(nèi)存分頁(yè)這三者結(jié)合之下崔赌,你會(huì)發(fā)現(xiàn)意蛀,其實(shí)要運(yùn)行一個(gè)程序,“必需”的內(nèi)存是很少的峰鄙。CPU 只需要執(zhí)行當(dāng)前的指令,極限情況下太雨,內(nèi)存也只需要加載一頁(yè)就好了吟榴。再大的程序,也可以分成一頁(yè)囊扳。每次吩翻,只在需要用到對(duì)應(yīng)的數(shù)據(jù)和指令的時(shí)候,從硬盤(pán)上交換到內(nèi)存里面來(lái)就好了锥咸。以我們現(xiàn)在 4K 內(nèi)存一頁(yè)的大小狭瞎,640K 內(nèi)存也能放下足足 160 頁(yè)呢,也無(wú)怪乎在比爾·蓋茨會(huì)說(shuō)出“640K ought to be enough for anyone”這樣的話(huà)搏予。

不過(guò)呢熊锭,硬盤(pán)的訪(fǎng)問(wèn)速度比內(nèi)存慢很多,所以我們現(xiàn)在的計(jì)算機(jī),沒(méi)有個(gè)幾 G 的內(nèi)存都不好意思和人打招呼碗殷。

那么精绎,除了程序分頁(yè)裝載這種方式之外,我們還有其他優(yōu)化內(nèi)存使用的方式么?下一講锌妻,我們就一起來(lái)看看“動(dòng)態(tài)裝載”代乃,學(xué)習(xí)一下讓兩個(gè)不同的應(yīng)用程序,共用一個(gè)共享程序庫(kù)的辦法仿粹。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搁吓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子吭历,更是在濱河造成了極大的恐慌堕仔,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毒涧,死亡現(xiàn)場(chǎng)離奇詭異贮预,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)契讲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)仿吞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人捡偏,你說(shuō)我怎么就攤上這事唤冈。” “怎么了银伟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵你虹,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我彤避,道長(zhǎng)傅物,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任琉预,我火速辦了婚禮董饰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘圆米。我一直安慰自己卒暂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布娄帖。 她就那樣靜靜地躺著也祠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪近速。 梳的紋絲不亂的頭發(fā)上诈嘿,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天堪旧,我揣著相機(jī)與錄音,去河邊找鬼永淌。 笑死崎场,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的遂蛀。 我是一名探鬼主播谭跨,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼李滴!你這毒婦竟也來(lái)了螃宙?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤所坯,失蹤者是張志新(化名)和其女友劉穎谆扎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體芹助,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡堂湖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了状土。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片无蜂。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蒙谓,靈堂內(nèi)的尸體忽然破棺而出斥季,到底是詐尸還是另有隱情,我是刑警寧澤累驮,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布酣倾,位于F島的核電站,受9級(jí)特大地震影響谤专,放射性物質(zhì)發(fā)生泄漏躁锡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一置侍、第九天 我趴在偏房一處隱蔽的房頂上張望映之。 院中可真熱鬧,春花似錦墅垮、人聲如沸惕医。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至螟够,卻和暖如春灾梦,著一層夾襖步出監(jiān)牢的瞬間峡钓,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工若河, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留能岩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓萧福,卻偏偏與公主長(zhǎng)得像拉鹃,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鲫忍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容