這是我在簡書上的第一篇文章。最近在自學操作系統,然后發現網上很少有對內存規劃這一塊進行的比較直接簡單完整的介紹。我想根據自己的理解寫一些東西,作為記錄,也可以和別人分享。其中自然會有許多理解上的錯誤,希望能得到指證。
從而說起呢?我想自己意淫下。
想象下,一開始的電腦是很簡陋的。也就是只有幾十幾百KB的內存。所以這個內存就是程序所能用的空間的最大值。但是那時候程序也是很小的,所以夠用。后來科技發展了,程序員寫出了更加復雜的程序,這個時候,內存不夠用了。于是問題就來了。
該怎么解決這個問題呢?
虛擬內存。
例子1,看電影的時候,有時候一部電影得5G朝上,那么我們開始看的時候,有必要把后面時間段的視頻也載入內存嗎?明顯沒必要。所以我們把現在需要的一個時間段(working set)的數據載入內存,然后不停地那新的視頻數據來代替舊的數據,這樣就大大節約了內存。
例子2,玩魔獸世界的時候,不可能一下子把32G的游戲全部載入內存。就是根據你在哪兒,把相關信息載入內存,然后你不停的去新的地點,他就不停的載入新的數據來替換掉舊的數據。
對于一臺內存為256M的32bit x86主機來說,它的虛擬地址空間范圍是0~0xFFFFFFFF(4G),而物理地址空間范圍是0x000000000~0x0FFFFFFF(256M)
這就是虛擬內存。把內存的東西暫時存到硬盤里。需要的時候再取出來。
這就涉及到兩個問題。
1.放在硬盤的數據,如果內存里的進程需要了,怎么找回來?
2.硬盤的速度是內存的幾百分之一,很慢,如何設計系統才可以更好地提升計算機的效率。
問題二就可以分為兩個小問題,
a.程序進入內存時,該給進程配置多少頁
b.如果進程需要新的頁,而分給他的空間已經滿了,會用新頁代替舊頁,該選擇哪個代替(page replacement)
首先看第一個問題。科學家發明了一種叫做MMU(Memory Management Unit)的硬件。他可以實現虛擬地址和物理地址之間的映射。
過去的時候,沒有MMU,程序員寫程序,編譯器翻譯時,會把相關地址直接翻譯成物理地址,然后直接映射到內存的相應區域。現在有了MMU就不一樣了。編譯器可以把相關地址翻譯成邏輯地址,然后再由MMU轉換成相應的物理地址。這么做有什么好處呢?
如果沒有MMU,首先是肯定不能使用虛擬內存這個思想。其次是,因為沒有這么一個地址映射器,編譯器翻譯出來的地址就是物理地址,這樣為了確保程序運行,程序的數據段啊,代碼段啊,堆棧啊,必須放在一塊連續的物理內存里,這樣之后編譯器才能比較容易實現定位。因為棧指針,幀指針,全局數據指針的地址都是存放在寄存器里面的,寄存器通過加一減一這樣的簡單操作來選擇下面的數據。所以所有數據必須放在一塊連續的內存里,否則很可能通過指針就很難找到對應數據了。
如果有了MMU,那么就不一樣了。我只要保證邏輯地址是連續的就行了。把剛剛的那些問題重復在這個有MMU的環境下,那些問題就不再是問題了。邏輯地址連續,那么我的棧指針,幀指針,全局指針加一減一可以毫無問題的定位到相應的邏輯地址。而這些邏輯地址發到MMU里面,MMU把他們映射到不同的物理地址上,這樣仍然可以找到所需的數據。所以,有了MMU,邏輯地址是連續的,進程在物理空間上的映像卻再也不連續了。這有好處的,不連續的內存映像可以減少內存空洞(hole,內部的,外部的)的出現。這個原因具體自己查吧。
那么有了MMU之后,科學家又想出了一種軟件機制,分頁,(Page)。即把邏輯地址分頁,分成一頁一頁的小內存。然后再把物理內存分幀,(Frame),Frame大小和Page相等,Page是邏輯地址上的基本單位,Frame是物理地址上的基本單位,兩者通過MMU形成映射關系。
然后就必須要有頁表(Page Table)了。頁表記錄了邏輯地址上的一頁對應了物理地址上的一幀。一般是把邏輯地址輸入進MMU,同時給MMU載入頁表(這個過程下面會具體說),然后MMU輸出相對應的物理地址,從而實現定位,或者說,映射。
那么,這樣一個具體過程是怎么樣的呢?我談下自己的理解。
假設我們下載了一個程序。然后它會存在硬盤里。雙擊啟動它,程序被激活,變成了進程,會向操作系統申請內存,進入等待隊列。然后OS通過算法,根據該進程的頁(Page),從free pool里面分配一定數目的幀(Free Frame)。這就會涉及到問題二的a,之后談。
然后通過導入的頁和其對應的幀生成頁表,指向該頁表的指針儲存到該進程的PCB(Process Control Block)里面。這時候進程變成就緒狀態,等待CPU。CPU處理后,執行又一條instruction,如果需要新的頁,而分配給進程的空間已經滿了,這時候就會涉及到問題二的b,需要那新頁取代一些舊頁,算法之后談。進程進入等待隊列。
頁表的每一項邏輯地址都會有幾個狀態標記位。如,reference flag(1 被引用過 0 未被引用過),modified flag(1 被修改過 0 為被修改過), value bit(1 在內存里 0 無效頁或者在硬盤里) 還有一個標志位用來標志該頁在內存還是在硬盤里。
當上述情況發生時,OS會看該頁是否在內存里。如果在,那沒事了,直接通過邏輯地址和MMU定位到相應的物理地址上。如果在硬盤里,會觸發一個中斷,page fault,然后OS會去硬盤里調出相關的頁(同時將其value bit 設置為1),然后踢掉內存里相關的頁,將其value bit設置為0(還有種情況是進程空間未滿,那就不用踢了,這里不討論了),拿新頁代替。如果被代替的頁被修改過(modified flag = 1),那么它得再次回寫到硬盤里(backing store),如果未被修改過,那么直接把對應的frame放回free pool里面。下次直接覆蓋里面的內容即可。然后當頁條件滿足時,CPU會 restart the instruction,重新跑一下剛剛那條指令。
當然這里也有一個知識點,如果剛巧這個frame之后沒被用掉,然后那個頁又進內存了,那他可以直接找到該frame,也不需要從硬盤把數據復制到該內存塊上了,因為之前的還未被覆蓋。所以frame也有個標志位叫做dirty bit用來表示上面這個信息。然后frame的信息(哪些在被用,哪些已經被污染過了,哪些free)都保存在一個叫做frame table的數據結構中,然后OS知道它的位置。
感覺這就基本是OS-Memory Management的流程了。每個進程都有一個頁表,存在內存的不連續位置里。指向該頁表的指針存在PCB里。
需要調入新頁的時候,CPU會生成一個虛擬地址送給MMU,MMU生成頁表表項(PTE)地址,并從高速緩沖/主存中得到頁表。然后把該頁表返回給MMU,MMU通過該頁表和虛擬地址構造物理地址,并把該物理地址傳送給高速緩沖/主存,請求數據。所以需要訪問兩次主存。
然后,現在的虛擬空間一般很大,如果page size小的話,有時候用來儲存信息的頁表也過大。但是我們不想用一段連續的內存來存儲它,一般就會對應有三種解決方法。
1.采用分級的方法,兩級頁表等等。
2.采用Hashed Page Tables,每個表項里用鏈表連接。
3.采用反轉頁表,Inverted Page Table (IPT),采用物理內存順序(塊號)排序。用進程標識符和頁號來檢索該反向頁表。即通過物理內存來找對應的進程的頁號。這樣每次都得檢索一下整個反向頁表來找所需要的東西,費時,但是該反向頁表卻比頁表要小很多(這里我不知道為什么小很多。)
有了分頁技術,也就出現了,shared pages。共享內存。即不同的程序可以通過頁號指向相同的frame,以此共享該塊數據。然后通過修改該共享數據,也可以實現進程間的通信。
自然,shared pages也便宜了申請子進程的開銷。當父進程fork()出子進程后,有一個叫做copy-on-write的技術。一開始子進程的頁和父進程指向相同的內存塊。如果在子進程中修改了某個內存數據,OS會把該修改的信息拷貝到另外一個內存地址里,原內存不變,所以此時本來共享某內存塊的頁,父進程仍指向原來的內存塊,而子進程指向了新的內存塊。其他未被修改過的頁仍指向同一個內存塊。這樣大大減少了申請子進程的開銷,因為一開始不需要為其分配過多的內存。
共享數據還有一個叫做 Memory-Mapped Files,即把磁盤里面的文件直接拷貝到進程空間里,避免了之后頻繁從磁盤加載到內存,回寫等步驟,而且進程間可以通過該內存映射文件實現通信。
第一個問題差不多談到這里吧,下面說說第二個問題。分配算法。
a.程序進入內存時,該給進程配置多少頁
b.如果進程需要新的頁,而分給他的空間已經滿了,會用新頁代替舊頁,該選擇哪個代替(page replacement)
a.分配時必須給進程保證最少數目的頁 = 進程中單條指令最多所需的頁數
如果分配的不好,會出現thrash現象,即OS不斷地收到page fault中斷,不斷地向硬盤demand paging。造成效率降低。所以一般會根據不同的系統有一個working set,他有初始值,之后不斷變化。在一個working-set里面統計所有進程所需要的頁數,如果他大于目前物理內存還剩的空間,那么就得按照replacement算法,選擇一個進程,將其設為無效,把它的frame全部freee掉,空出足夠的空間來應對thrash。
b
Page-replacement Algorithm有好多種。比如 FIFO,OPT(不可能實現),LRU等等,再次不多說了
當然剛剛談的都是用戶空間,內核空間是完全不一樣的。
首先內核空間的數據結構大小不同,很多小于one-page size,如果采用分頁,浪費空間,而內核空間寸土寸金。
其次,用戶空間的程序對時間要求不高,但內核對響應時間有著嚴格的要求,所以如果采用分頁太浪費時間了,只能用連續空間來儲存。
因此有兩種分配算法。Buddy System 和 Slab Allocation,具體自己查吧。
最后說個題目,如果這道題理解了,那么基本對內存規劃也理解了。
one system, page size: 128 words, 整型(int)size: 1word
代碼1,
int i,j;
int[128][128]data;
for(j = 0;j < 128;j++)
? ?for(i = 0;i < 128;i++)
? ? ? ?data[i][j] = 0;
代碼2,
int i,j;
int[128][128]data;
for(i = 0;i < 128;i++)
for(j = 0;j < 128;j++)
data[i][j] = 0;
你覺得這兩種算法哪個效率更高?
后記:這是修改過的文章,我一開始發表的時候不知道為什么后面一大段全部沒了,這讓我對簡書的印象大打折扣。本著做一件像一件事的原則,我又重新打了。
寫完這篇萬丈感覺印象又加深了,但是感覺文章順序不太好,可能也是自己沒有準備提筆就寫的結果吧。希望有人會看吧,更加希望有人可以一起交流學習。
下面我就打算開始學習File Systems。加油
后記:
第一次寫文章,現在重讀,覺得寫的太差了。該突出的重點也沒有突出出來。有一塊甚至遺漏了。就是translation look-aside buffer (TLB), 中文翻譯應該就是快表。
OS會先去檢索快表,如果沒有命中,再根據頁號去內存請求數據。請求的數據先導入cache,然后更新快表,最后將所需要的數據送入CPU。
快表是一個鍵值對,page-frame。他之所以快是因為快表本身就在cache里面,和他相關的數據也全部都在cache里面。然后CPU需要什么,就檢索快表,找到了直接送入CPU。
真正的優勢在于,cache運行比內存快,而且距離CPU的物理距離近。
今天上課聽老師說馮諾依曼結構的瓶頸就在于內存和CPU的物理距離過大,現在CPU和內存的速度越做越快,但是他們之間的距離卻無法改變,而傳輸數據的速率-光速也無法改變。所以我們得把計算機經常用到的東西導入cache,避免計算機去內存要東西,更不應該讓計算機去硬盤要東西。
說到了cache,我今天也查了下,加深了理解。cache和buffer有啥區別呢?
1、Buffer(緩沖區)是系統兩端處理速度平衡(從長時間尺度上看)時使用的。它的引入是為了減小短期內突發I/O的影響,起到流量整形的作用。比如生產者——消費者問題,他們產生和消耗資源的速度大體接近,加一個buffer可以抵消掉資源剛產生/消耗時的突然變化。
2、Cache(緩存)則是系統兩端處理速度不匹配時的一種折衷策略。因為CPU和memory之間的速度差異越來越大,所以人們充分利用數據的局部性(locality)特征,通過使用存儲系統分級(memory hierarchy)的策略來減小這種差異帶來的影響。
3、假定以后存儲器訪問變得跟CPU做計算一樣快,cache就可以消失,但是buffer依然存在。比如從網絡上下載東西,瞬時速率可能會有較大變化,但從長期來看卻是穩定的,這樣就能通過引入一個buffer使得OS接收數據的速率更穩定,進一步減少對磁盤的傷害。
4、TLB(Translation Lookaside Buffer,翻譯后備緩沖器)名字起錯了,其實它是一個cache.
該段摘錄自 知乎 的一個匿名網友,寫的挺好的。我覺的buffer就是一個編程上的概念,讓我們解決速率突然暴增的問題。不只是計算機,通信系統,銀行,日常生活其實都有buffer的思想。而cache就是一個硬件設備。
最后再談一個問題,之前的文章沒有說清楚。
邏輯地址,虛擬地址,線性地址,物理地址之間的關系是什么?
我覺得我們在編譯器上寫的代碼,編譯器翻譯出來的最初版本是邏輯地址。然后通過一個 segmentation unit,將其分段。因為咱們的代碼里面有,有代碼段,數據段,棧,堆,還有一些庫函數,系統文件等,我們需要將其分段,即把一個程序分成各個不同的部分。這時候程序就變成了一個整體,有,系統空間,代碼段,數據段,堆棧等。這時候的地址就是虛擬地址,又稱為線性地址。(虛擬地址 = 線性地址), 然后在經過MMU就可以得到物理地址了。
送幾個截圖加深印象。
還有一篇剛剛看到的文章,具體地講述了cache運行的機理和硬件上的構造。有興趣可以看下。我是看懂了,但是很快會忘掉,其實有個大體的印象就可以了。其中有句話我思考了下,他說,每個進程都認為自己有4G的內存空間。為什么。因為每個進程的虛擬空間都有4G。虛擬空間是分頁的,你可以把程序寫成4G(此說法不太嚴謹,畢竟還有操作系統得占空間),然后會給你分頁的。你也可以寫成4M,然后沒用的空間首先就不會給你分頁。畢竟是虛擬地址。所以不會有多大影響。
鏈接如下:
http://www.cnblogs.com/pengdonglin137/p/3362274.html