CPU阿甘之煩惱

真正的知識是深刻淺出的,碼農翻身」 公共號將苦澀難懂的計算機知識,用形象有趣的生活中實例呈現給咱們,讓咱們更好地理解。算法

本文源地址:CPU阿甘之煩惱編程

一、批處理系統

「最近比較煩,比較煩,比較煩...」,CPU阿甘在唱着。由於內存和硬盤一直看他不順眼,致使阿甘特別煩惱。緩存

阿甘內心很清楚,是本身幹活太快了,幹完了活就歇着喝茶,這時候內存和硬盤還在辛辛苦苦的忙活,他們確定以爲很不爽了。「木秀於林,風必摧之」、「不患貧而患不均」,這就是阿甘的處境。雖然阿甘本身也於心不忍,但是有什麼辦法?誰讓他們那麼慢!一個比本身慢100倍,另一個比本身慢100萬倍架構

這個世界的造物主爲何不把咱們的速度弄的同樣呢?性能

阿甘所在的是一個批處理的計算機系統,操做系統老大收集了一批任務之後,就會把這一批任務的程序逐個裝載的內存中,讓CPU去運行,大部分時候這些程序都是單純的科學計算,計算彈道軌跡什麼的。但有時候也會有IO相關的操做,這時候,內存和硬盤都在瘋狂的加班Load數據,(因爲運行速度差異實在是天壤之別)阿甘只能等待數據到來,只能坐那兒喝茶了。 學習

沒多久,內存向操做系統老大告了阿甘一狀,阿甘被老大叫去訓話了!「阿甘,你就不能多幹一點?總是歇着喝茶算是怎麼回事?」spa

阿甘委屈的說:「老大,這不能怪我啊!你看你每次只把一個程序搬到內存那裏讓我運行,正常狀況下,我能夠跑的飛快,能夠是一旦遇到IO相關的指令,勢必要去硬盤那裏找數據,硬盤實在是太慢了,我不得不等待啊!」操作系統

操做系統說!「臥槽,聽你的口氣仍是個人問題啊,一個程序遇到了IO指令,你不能把它掛起,存到到硬盤裏,而後再找另一個運行嗎?」翻譯

阿甘笑了:「老大我看你是氣昏頭了,我要是把正在運行的程序存到硬盤裏,暫時掛起,而後再從硬盤裝載另一個,這可都是IO操做啊,豈不更慢?」3d

 「這!」 操做系統語塞了,沉默了半天說:「這樣吧,我之後在內存裏多給你裝載幾個程序,一個程序被IO阻塞住了,你就去運行另一個。如何?」

「這得問問內存,看他願不肯意了,我把內存叫來,咱們一塊兒商量商量」 。阿甘以爲這個主意不錯。

內存心思縝密,聽了這個想法,心想:本身也沒什麼損失啊,原來同一時間在內存裏只有一個程序,如今要裝載多個,對我都同樣。

但是往深處一想,若是有多個程序,內存的分配可不是個簡單的事情,好比說下面這個例子:

圖1 :內存緊縮

  • (1) 內存一共90k,一開始有三個程序運行,佔據了80k的空間,剩餘10k;
  • (2) 而後第二個程序運行完了,空閒出來20k,如今總空閒是30K, 但這兩塊空閒內存是不連續的;
  •  (3) 第4個程序須要25k,沒辦法只好把第三個程序往下移動,騰出空間讓第四個程序來使用了。

內存把本身的想法給操做系統老大說了說。

 老大說:「阿甘,你要向內存學習啊!看看他思考的多麼深刻,不過這個問題我有解決辦法,須要涉及到幾個內存的分配算法,大家不用管了。我們就這麼肯定下來,先跑兩個程序試試。」  

二、地址重定位

次日一大早,試驗就正式開始,老大同時裝載了兩個程序到內存中:

圖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架構師建立,分享編程和職場的經驗教訓。

長按二維碼, 關注碼農翻身

 

相關文章
相關標籤/搜索