真正的知識是深刻淺出的,碼農翻身」 公共號將苦澀難懂的計算機知識,用形象有趣的生活中實例呈現給咱們,讓咱們更好地理解。算法
本文源地址:CPU阿甘之煩惱編程
「最近比較煩,比較煩,比較煩...」,CPU阿甘在唱着。由於內存和硬盤一直看他不順眼,致使阿甘特別煩惱。緩存
阿甘內心很清楚,是本身幹活太快了,幹完了活就歇着喝茶,這時候內存和硬盤還在辛辛苦苦的忙活,他們確定以爲很不爽了。「木秀於林,風必摧之」、「不患貧而患不均」,這就是阿甘的處境。雖然阿甘本身也於心不忍,但是有什麼辦法?誰讓他們那麼慢!一個比本身慢100倍,另一個比本身慢100萬倍!架構
這個世界的造物主爲何不把咱們的速度弄的同樣呢?性能
阿甘所在的是一個批處理的計算機系統,操做系統老大收集了一批任務之後,就會把這一批任務的程序逐個裝載的內存中,讓CPU去運行,大部分時候這些程序都是單純的科學計算,計算彈道軌跡什麼的。但有時候也會有IO相關的操做,這時候,內存和硬盤都在瘋狂的加班Load數據,(因爲運行速度差異實在是天壤之別)阿甘只能等待數據到來,只能坐那兒喝茶了。 學習
沒多久,內存向操做系統老大告了阿甘一狀,阿甘被老大叫去訓話了!「阿甘,你就不能多幹一點?總是歇着喝茶算是怎麼回事?」spa
阿甘委屈的說:「老大,這不能怪我啊!你看你每次只把一個程序搬到內存那裏讓我運行,正常狀況下,我能夠跑的飛快,能夠是一旦遇到IO相關的指令,勢必要去硬盤那裏找數據,硬盤實在是太慢了,我不得不等待啊!」操作系統
操做系統說!「臥槽,聽你的口氣仍是個人問題啊,一個程序遇到了IO指令,你不能把它掛起,存到到硬盤裏,而後再找另一個運行嗎?」翻譯
阿甘笑了:「老大我看你是氣昏頭了,我要是把正在運行的程序存到硬盤裏,暫時掛起,而後再從硬盤裝載另一個,這可都是IO操做啊,豈不更慢?」3d
「這!」 操做系統語塞了,沉默了半天說:「這樣吧,我之後在內存裏多給你裝載幾個程序,一個程序被IO阻塞住了,你就去運行另一個。如何?」
「這得問問內存,看他願不肯意了,我把內存叫來,咱們一塊兒商量商量」 。阿甘以爲這個主意不錯。
內存心思縝密,聽了這個想法,心想:本身也沒什麼損失啊,原來同一時間在內存裏只有一個程序,如今要裝載多個,對我都同樣。
但是往深處一想,若是有多個程序,內存的分配可不是個簡單的事情,好比說下面這個例子:
圖1 :內存緊縮
內存把本身的想法給操做系統老大說了說。
老大說:「阿甘,你要向內存學習啊!看看他思考的多麼深刻,不過這個問題我有解決辦法,須要涉及到幾個內存的分配算法,大家不用管了。我們就這麼肯定下來,先跑兩個程序試試。」
次日一大早,試驗就正式開始,老大同時裝載了兩個程序到內存中:
圖2:內存裝入2個程序
第一個程序被裝在到了內存的開始處,也就是地址0,運行了一會,就遇到一個IO指令。在等待數據的時候,老大讓我運行第二個程序,這個程序就被裝在到了地址10000處,剛開始運行得挺好,忽然就來個一條指令:
MOV AX [1000]
AX是一個寄存器,能夠理解成CPU內部的一個高速存儲單位,該指令含義是將AX寄存器中的值寫到內存1000處。
此時,阿甘隱約記得第一個程序中也有一條相似的指令:
MOV BX [1000]
「老大,壞了!這兩個程序操做了同一個地址,數據會被覆蓋掉!」 阿甘趕忙向操做系統彙報。
操做系統一看就明白了,原來這個系統的程序引用的都是物理的內存地址。在批處理系統中,全部的程序都是從地址0開始裝在,如今是多道程序在內存中,第二個程序被裝在到了地址10000處。可是程序沒有變化,仍是假定從0開始,天然就出錯了。
「看來老大在裝載的時候得修改一下第二個程序的指令了,把每一個地址都加上10000(即第二個程序的開始處),原來的指令就會變成 MOV AX [11000]。 」 內存確實反應很快。---靜態重定位
阿甘說:「 若是用這種辦法,那作內存緊縮的時候可就麻煩了。由於老大要處處移動程序啊。對每一個移動的程序豈不還都得作重定位,這多累啊!」
操做系統老大陷入了沉思,阿甘說的沒錯。這個靜態重定位是很不方便,看來想在內存中運行多道程序不是想象的那麼容易。
可是能不能改變下思路,在運行時把地址重定位呢?
首先得記錄下每一個程序的起始地址,可讓阿甘再增長一個寄存器,專門保護初始地址。例如第一個程序地址爲0,第二個程序的地址是10000。運行第一個程序的時候,將寄存器的值置爲0,當切換到第二個程序的時候,寄存器的值應置爲10000。 只要遇到了地址相關的指令,都須要把地址加上寄存器的值,這樣才能夠獲得真正的內存地址,而後再去訪問。---動態重定位
操做系統趕忙讓阿甘去加一個新的寄存器,從新裝載兩個程序,記錄下他們的開始地址,而後切換程序,此次成功了,不在有數據覆蓋的問題了。
只是阿甘有些不高興:「老大,這一會兒我這裏的活可多了很多啊!你看每次訪問內存,我都得額外的作一次加法運算啊。」
老大說:「沒辦法,能者多勞嘛!你看看我,我既須要考慮內存分配算法,還得作內存緊縮,還得記住每一個程序的開始地址,切換程序的時候,才能刷新你的寄存器,我比你麻煩多了!」
內存忽然說道:「老大,我想到一個問題。假設有一個惡意程序,它去訪問別的程序空間怎麼辦?好比地址2000至3000屬於一個程序的空間,可是這個程序忽然帶來了一條指令(MOV AX [1500]),咱們在運行時會翻譯成"MOV AX [3500]",這個3500有多是別的程序的空間啊!」
「唉,那就只好再加個寄存器了。阿甘,用這個新寄存器來記錄程序在內存中的長度吧。這樣每次訪問的時候拿那個地址和這個長度比較一下,咱們就知道是否是越界了。」 ,老大迫不得已了。
「好吧」 ,阿甘答應了,「 我能夠把這連個寄存器,以及計算內存地址的方法,封裝成一個新的模塊,就叫MMU(內存管理單元)吧。不過這個東西聽起來好像應該內存來管啊。」
內存笑着說:「那是不行的。阿甘,可以高速訪問的寄存器只有你這裏纔有啊。我就是一個比你慢100倍的存儲器而已!」。
多道程序最近在內存中運行得挺好,阿甘沒辦法閒下來喝茶了,常常是一個尚未運行完,很快就切換到另外一個。
那些程序也都是好事之徒,據說了這個新的系統,都拼了命,擠破頭的往內存中鑽。
內存很小,很快就會擠滿,操做系統老大忙於調度,也是忙的不可開交。
更有甚者,程序開始越長越大,有些圖形處理的程序,還有些什麼叫Java的程序,動不動就要幾百M內存,就這還嚷嚷着說不夠。
操做系統頭都大了,把CPU和內存叫來商量。
「世風日下,人心不古啊!」 內存一邊嘆氣一遍說:「原來批處理的時候那些程序規規矩矩的,如今是怎麼了?」
「這也不能怪那些程序,如今硬件的確比原來好多了。內存,你原來只有幾十K,如今都好幾G了。CPU在摩爾定律的關照下,發展的更快,每隔18個月,你的速度就翻一番。」 操做系統老大說。
「那也趕不上這些程序的發展速度,他們對我要求愈來愈高,但是把我累壞了。」 阿甘垂頭喪氣的。
「咱們仍是考慮下怎麼讓有限的內存裝下更多的程序吧!」 內存說道。
「我有一個提議,對每一個程序不要所有裝入內存,要分塊裝載。例如先把最重要的代碼指令裝進來,在運行中按須要裝載別的指令。」阿甘提議道。
內存嘲笑說:「阿甘,你又想偷懶喝茶了。哈哈,若是每一個程序都這樣,IO操做得多頻繁。我和硬盤都得累死。」
阿甘臉紅了,沉默了。
「慢着」,老大說:「阿甘,你以前不是發現過什麼原理嘛!就是從幾千億條指令中總結出的那個,叫什麼來着?」
「奧,那是局部性原理,有兩個:
1)時間局部性:若是程序中的某條指令一旦執行,則不久以後該指令可能再次被執行;若是某數據被訪問,則不久以後該數據可能被再次訪問。
2)空間局部性:一旦程序訪問了某個存儲單元,則不久以後,其附近的存儲單元也將被訪問。」
「這個局部性原理應該能拯救咱們。阿甘,咱們徹底能夠把一個程序分紅一個個小塊,而後按塊來裝載到內存中,因爲局部性原理的存在,程序會傾向於在這一塊或幾塊上執行,性能上應該不會有太大的損失。」
「這能行嗎?」, 內存和阿甘不約而同的問。
「試一試就知道了,這樣咱們把這一個個小塊叫作頁框(page frame),每一個暫定4k大小,裝載程序的時候也按照頁框大小來。」
實驗了幾天,果真不出老大所料,那些程序在大部分時間真的只運行在幾個頁框中,因而老大把這些頁稱爲工做集(working set)。
「既然一個程序能夠用分塊的技術逐步調入內存,而不太影響性能,那就意味着,一個程序能夠比實際的內存大的多啊!」
阿甘躺在牀上,忽然間想到這一層,心頭突突直跳,這絕對是一個超級想法。
「咱們能夠給每一個程序都提供一個超級大的空間。例如4G,只不過這個空間是虛擬的,程序中的指令使用的就是這些虛擬的地址,而後個人MMU把它們映射到真實的物理的內存地址上,那些程序們渾然不覺,哈哈,實在是太棒了。」
內存據說了這個想法,驚訝的瞪大了雙眼:「阿甘,你瘋了吧?」
「阿甘的想法是有道理的」,老大說:「只是咱們還要堅持一點,那就是分塊裝入程序,咱們把虛擬的地址也得分塊,就叫作頁(page),大小和物理內存的頁框同樣,這樣好映射。」
「老大,看來你又要麻煩了,你得維持一個頁表,用來映射虛擬頁面和物理頁面。」
「不只如此,我還得記錄一個程序那些頁已經被裝載到了物理內存,那些沒有被裝載,若是程序訪問了這些沒被裝載的頁面,我還得從內存中找到一塊空閒的地方。若是內存已滿,只好把現有的頁框置換一個到硬盤上了。但是,怎麼肯定那個物理內存的頁框能夠置換呢? 唉,又涉及到不少複雜的算法,須要大費一番周折。你看看,老大不是這麼容易當的。」
圖3: 分頁
分頁的工做原理,須要注意的是虛擬地址的#4頁, 在物理內存中不存在,若是程序訪問第4頁,就會產生缺頁的中斷,由操做系統去硬盤調取。
內存想起來一個問題:「若是程序運行時,每次都得查頁表來得到物理的內存頁,而頁表也是在內存裏,而我比你慢100倍,你受得了嗎。阿甘?」
阿甘笑了:「這個問題其實我也考慮了,因此我打算加強個人內存管理單元,把那些最常訪問的頁表項放到緩存裏。這樣不就快了嗎。」
內存想一想也是,仍是局部性原理,太牛了。
分頁系統運行了一段時間之後,又有程序表示不爽了,這些程序嚷嚷着說:
「大家能不能把程序‘分家’啊。例如代碼段、數據段、堆棧段,這多麼天然,而且有利於保護,要是程序試圖去寫這個只讀的代碼段,馬上就能夠拋出保護異常!」
還有程序說:「頁面過小了,實在不利於共享,我和哥們共享的那個圖形庫,高達幾十M,得分紅好多頁來共享,太麻煩了。大家要是作一個共享段該多好!」......這樣的聒噪聲多了,你們都不勝其煩,那就「分家」吧。
固然對每一個程序都須要標準化,一個程序被分紅代碼段,數據段和堆棧段等。操做系統老大記錄下每一個段的開始和結束地址,每一個段的保護位。
圖4:Linux的虛擬內存示意圖
可是在每一個段的內部,仍然按分頁的系統來處理,除了頁表以外,操做系統老大又被迫維護了一個段表這樣的東西。
一個虛擬的內存地址來了之後,首先根據地址中的段號先找到相應的段描述表,其中有頁表的地址,而後再從頁表中找到物理內存,過程相似這樣:
圖5:一個簡化的段表和頁表
全部事情都設置好了,你們都喘了口氣,以爲這樣的結構你們應該沒什麼異議了。
老大心情大好,以爲一切盡在掌握,他笑着對CPU阿甘說:
「阿甘,從今天開始,若是有程序想非法的訪問內存,例如一個不屬於他的段,我就馬上給他一個警告:Segmentation Fault !」
阿甘說:「那程序收到Segmentation Fault之後怎麼處理?」
老大說:「一般狀況下就被我殺死,而後給他產生一個叫core dump的屍體,讓那些碼農們拿走分析去吧!」
(完)
「碼農翻身」 公共號 : 由工做15年的前IBM架構師建立,分享編程和職場的經驗教訓。
長按二維碼, 關注碼農翻身