碼農翻身之大話編程篇:10 CPU阿甘之煩惱

                       1    批處理系統

        CPU阿甘最近很煩。 原因很簡單,內存和硬盤看他不順眼。

        阿甘心裏很清楚,是自己幹活太快了,幹完了活就歇着喝茶,這時候內存和硬盤還在辛辛苦苦的忙活,他們肯定覺得很不爽了。中國有句古話叫什麼來着? 木秀於林,風必摧之不患貧而患不均,這就是阿甘的處境。雖然阿甘自己也於心不忍,可是有什麼辦法?誰讓他們那麼慢!一個比自己慢100倍,另外一個比自己慢100萬倍!這個世界的造物主爲什麼不把我們的速度弄的一樣呢?

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

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

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

        操作系統說:臥槽,聽你的口氣還是我的問題啊,  一個程序遇到了IO指令,你不能把它掛起,存到到硬盤裏,然後再找另外一個運行嗎?

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

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

        “這得問問內存,看他願不願意了,我把內存叫來,我們一起商量商量” 阿甘覺得這個主意不錯。

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

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


        (1) 內存一共90k, 一開始有三個程序運行,佔據了80k的空間,剩餘10k

        (2) 然後第二個程序運行完了,空閒出來20k , 現在總空閒是30K,但這兩塊空閒內存是不連續的。

         (3) 4個程序需要25k, 沒辦法只好把第三個程序往下移動,騰出空間讓第四個程序來使用了。

        內存把自己的想法給操作系統老大說了說。 老大說:阿甘,你要向內存學習啊,看看他思考的多麼深入,不過這個問題我有解決辦法,需要涉及到幾個內存的分配算法,你們不用管了。咱們就這麼確定下來,先跑兩個程序試試。 

                 2    地址重定位

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


        第一個程序被裝載到了內存的開始處,也就是地址0,運行了一會,遇到了一個IO指令,在等待數據的時候,老大立刻讓CPU開始運行第二個程序,這個程序被裝載到了地址10000處,剛開始運行的好好的,突然就來了這麼一條指令:

        MOV AX  [1000] ( AX是一個寄存器,你可以理解成在CPU內部的一個高速的存儲單位,這個指令的含義是把AX寄存器的值寫到內存地址1000)

        阿甘覺得似曾相識,隱隱約約的記得第一個程序中也這麼一條類似的指令:    

        MOV BX  [1000]

        “老大,壞了,這兩個程序操作了同一個地址!數據會被覆蓋掉” 阿甘趕緊向操作系統彙報。 

        操作系統一看就明白了,原來這個系統的程序引用的都是物理的內存地址,在批處理系統中,所有的程序都是從地址0開始裝載,現在是多道程序在內存中,第二個程序被裝載到了10000這個地址,但是程序沒有變化啊,還是假定從0開始,自然就出錯了。 

        “看來老大在裝載的時候得修改一下第二個程序的指令了,把每個地址都加上10000(即第二個程序的開始處),原來的指令就會變成 MOV AX [11000] ” 內存確實反應很快。(直接修改程序的指令,這叫靜態重定位)

        阿甘說:“ 如果用這種辦法,那做內存緊縮的時候可就麻煩了,因爲老大要到處移動程序啊,對每個移動的程序豈不還都得做重定位?這多累啊!

        操作系統老大陷入了沉思,阿甘說的沒錯,這個靜態重定位是很不方便,看來想在內存中運行多道程序不是想象的那麼容易。但是能不能改變下思路,在運行時把地址重定位呢?

        首先得記錄下每個程序的起始地址,可以讓阿甘再增加一個寄存器,專門用來保存初始地址。 

        例如對第一個程序,這個地址是  對第二個程序,這個地址是10000 運行第一個程序的時候,把寄存器的值置爲當切換到第二個程序的時候,寄存器的值也應該切換成10000 只要遇到了地址有關的指令,都需要把地址加上寄存器的值,這樣纔得到真正的內存地址,然後去訪問。(這叫地址的動態重定位)

        操作系統趕緊讓阿甘去加一個新的寄存器,重新裝載兩個程序,記錄下他們的開始地址,然後切換程序,這次成功了,不在有數據覆蓋的問題了。 

        只是阿甘有些不高興老大,這一下子我這裏的活可多了不少啊,你看每次訪問內存,我都得額外的做一次加法運算啊。

        老大說:沒辦法,能者多勞嘛,你看看我,我既需要考慮內存分配算法,還得做內存緊縮,還得記住每個程序的開始地址,切換程序的時候,才能刷新你的寄存器,我比你麻煩多了!

        內存突然說到:老大,我想到一個問題,假設有個不懷好意的惡意程序,它去訪問別人的空間怎麼辦?  比如說地址20003000屬於一個程序,但是這個程序來了一條這樣的指令MOV AX [1500],  我們在運行時會翻譯成MOV AX [3500]  , 這個3500有可能是別的程序的空間啊

        “唉,那就只好再加個寄存器了,阿甘,用這個新寄存器來記錄程序在內存中的長度吧,這樣每次訪問的時候拿那個地址和這個長度比較一下,我們就知道是不是越界了” 老大無可奈何了。 

        “好吧” 阿甘答應了,“ 我可以把這連個寄存器,以及計算內存地址的方法,封裝成一個新的模塊,就叫MMU (內存管理單元)吧,不過這個東西聽起來好像應該內存來管啊

        內存笑着說:那是不行的,阿甘,能夠高速訪問的寄存器只有你這裏纔有啊我就是一個比你慢100倍的存儲器而已!

                3分塊裝入程序

        多道程序最近在內存中運行的挺好,阿甘沒法閒下來喝茶了,經常是一個還沒運行完,很快就切換到另外一個。那些程序也都是好事之徒,聽說了這個新的系統,都拼了命,擠破頭的往內存中鑽。內存很小,很快就會擠滿,操作系統老大忙於調度,也是忙的不可開交。 

        更有甚者,程序開始越長越大,有些圖形處理的程序,還有些什麼叫Java的程序,動不動就要幾百M內存,就這還嚷嚷着說不夠。 操作系統頭都大了,把CPU和內存叫來商量。 

        “世風日下,人心不古啊” 內存一邊嘆氣一遍說原來批處理的時候那些程序規規矩矩的,現在是怎麼了?

        “這也不能怪那些程序,現在硬件的確比原來好多了,內存,你原來只有幾十K, 現在都好幾G了,CPU在摩爾定律的關照下,發展的更快,每隔18個月,你的速度就翻一翻”  操作系統老大說。    

        “那也趕不上這些程序的發展速度,他們對我要求越來越高,可是把我累壞了” CPU垂頭喪氣的。 

        “我們還是考慮下怎麼讓有限的內存裝下更多的程序吧” 

        “我有一個提議” 阿甘說對每個程序,不要全部裝入內存,要分塊裝載,例如先把最重要的代碼指令裝載進來,在運行中按需裝載別的東西。

        內存嘲笑說:阿甘,看來你又想偷懶喝茶了,哈哈,如果每個程序都這樣,IO操作得多頻繁,我和硬盤累死,你就整天歇着吧

        阿甘臉紅了,沉默了。 

        “慢着老大說阿甘,你之前不是發現過什麼原理嘛,就是從幾千億條指令中總結出的那個,叫什麼來着?

        “奧,那是局部性原理,有兩個:

        (1)  時間局部性:如果程序中的某條指令一旦執行,則不久之後該指令可能再次被執行; 如果某數據被訪問,則不久之後該數據可能再次被訪問。

        (2) 空間局部性:指一旦程序訪問了某個存儲單元,則不久之後。其附近的存儲單元也將被訪問。

        “這個局部性原理應該能拯救我們,阿甘,我們完全可以把一個程序分成一個個小塊,然後按塊來裝載到內存中,由於局部性原理的存在,程序會傾向於在這一塊或幾塊上執行,性能上應該不會有太大的損失。

        “這能行嗎?” 內存和阿甘不約而同的問。試一試就知道了,這樣我們把這一個個小塊叫做頁框(page frame),每個暫定4k大小,裝載程序的時候也按照頁框大小來

        實驗了幾天,果然不出老大所料,那些程序在大部分時間真的只運行在幾個頁框中,於是老大把這些頁稱爲工作集(working set)

                    4虛擬內存:分頁

        “既然一個程序可以用分塊的技術逐步調入內存,而不太影響性能,那就意味着,一個程序可以比實際的內存大的多啊。阿甘躺在牀上,突然間想到這一層,心頭突突直跳,這絕對是一個超級想法。 

        “我們可以給每個程序都提供一個超級大的空間,例如4G,只不過這個空間是虛擬的,程序中的指令使用的就是這些虛擬的地址,然後我的MMU把它們映射到真實的物理的內存地址上,那些程序們渾然不覺,哈哈,實在是太棒了

        內存聽說了這個想法,驚訝的瞪大了雙眼:阿甘,你瘋了吧

        “阿甘的想法是有道理的” 老大說只是我們還要堅持一點,那就是分塊裝入程序,我們把虛擬的地址也得分塊,就叫做頁(page大小和物理內存的頁框一樣,這樣好映射。

        “老大,看來你又要麻煩了,你得維持一個頁表,用來映射虛擬頁面和物理頁面

        “不僅如此,我還得記錄一個程序哪些頁已經被裝載到了物理內存,哪些沒有被裝載,如果程序訪問了這些沒被裝載的頁面,我還得從內存中找到一塊空閒的地方,如果內存已滿,只好把現有的頁框置換一個到硬盤上了,可是,怎麼確定那個物理內存的頁框可以置換呢?唉,又涉及到很多複雜的算法,需要大費一番周折。你看看,老大不是這麼容易當的。


        (這就是分頁的工作原理,需要注意的是虛擬地址的#4頁,在物理內存中不存在,如果程序訪問第4頁,就會產生缺頁的中斷,由操作系統去硬盤調取)

        內存想起來一個問題:  “如果程序運行時,每次都得查頁表來獲得物理的內存頁,而頁表也是在內存裏,而我比你慢100倍,你受得了嗎,阿甘?

        阿甘笑了:這個問題其實我也考慮了,所以我打算增強我的內存管理單元,把那些最常訪問的頁表項放到緩存裏,這樣不就快了嗎。  ”

        內存想想也是,還是局部性原理,太牛了。

                    5分段+分頁

        分頁系統運行了一段時間以後,又有程序表示不爽了,這些程序嚷嚷着說:

        “你們能不能把程序分家啊,例如代碼段,數據段,堆棧段,這多麼自然,並且有利於保護,要是程序試圖去寫這個只讀的代碼段,立刻就可以拋出保護異常!

        還有程序說:頁面太小了,實在不利於共享,我和哥們共享的那個圖形庫,高達幾十M , 得分成好多頁來共享,太麻煩了,你們要是做一個共享段該多好!”......這樣的聒噪聲多了,大家都不勝其煩,那就分家吧。 

        當然對每個程序都需要標準化,一個程序被分成代碼段,數據段和堆棧段等,操作系統老大記錄下每個段的開始和結束地址,每個段的保護位。 


        但是在每個段的內部,仍然按分頁的系統來處理,除了頁表之外,操作系統老大又被迫維護了一個段表這樣的東西

        一個虛擬的內存地址來了以後,首先根據地址中的段號先找到相應的段描述表,其中有頁表的地址,然後再從頁表中找到物理內存,過程類似這樣:


        所有事情都設置好了,大家都喘了口氣,覺得這樣的結構大家應該沒什麼異議了。 

        老大心情大好,覺得一切盡在掌握,他笑着對CPU阿甘說: 

        “阿甘,從今天開始,如果有程序想非法的訪問內存,例如一個不屬於他的段,我就立刻給他一個警告:Segmentation Fault !”阿甘說:那程序收到Segmentation Fault以後怎麼處理?

        老大說:通常情況下就被我殺死,然後給他產生一個叫core dump的屍體,讓那些碼農們拿走分析去吧!”

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章