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] ” 內存確實反應很快。(直接修改程序的指令,這叫靜態重定位)
阿甘說:“ 如果用這種辦法,那做內存緊縮的時候可就麻煩了,因爲老大要到處移動程序啊,對每個移動的程序豈不還都得做重定位?這多累啊!”
操作系統老大陷入了沉思,阿甘說的沒錯,這個靜態重定位是很不方便,看來想在內存中運行多道程序不是想象的那麼容易。但是能不能改變下思路,在運行時把地址重定位呢?
首先得記錄下每個程序的起始地址,可以讓阿甘再增加一個寄存器,專門用來保存初始地址。
例如對第一個程序,這個地址是0 , 對第二個程序,這個地址是10000, 運行第一個程序的時候,把寄存器的值置爲0 ,當切換到第二個程序的時候,寄存器的值也應該切換成10000。 只要遇到了地址有關的指令,都需要把地址加上寄存器的值,這樣纔得到真正的內存地址,然後去訪問。(這叫地址的動態重定位)
操作系統趕緊讓阿甘去加一個新的寄存器,重新裝載兩個程序,記錄下他們的開始地址,然後切換程序,這次成功了,不在有數據覆蓋的問題了。
只是阿甘有些不高興:“老大,這一下子我這裏的活可多了不少啊,你看每次訪問內存,我都得額外的做一次加法運算啊。”
老大說:“沒辦法,能者多勞嘛,你看看我,我既需要考慮內存分配算法,還得做內存緊縮,還得記住每個程序的開始地址,切換程序的時候,才能刷新你的寄存器,我比你麻煩多了!”
內存突然說到:”老大,我想到一個問題,假設有個不懷好意的惡意程序,它去訪問別人的空間怎麼辦? 比如說地址2000至3000屬於一個程序,但是這個程序來了一條這樣的指令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的屍體,讓那些碼農們拿走分析去吧!”