就內存管理而言 Linux 系統相對於 Windows 有哪些優勢?

linux好久不重啟,速度也不會比開機時慢到哪裡去。想知道就內存管理這方面而言,Linux是怎麼好於Windows的。Linux系統的內存管理和Windows的內存管理有何不同


windows的內存管理很是嚴謹,使用內存必須首先分配,當然每個操作系統都是這樣,然而windows的嚴謹在於分配的過程,分為保留和提交兩個階段,其中保留的含義就是在進程的虛擬地址空間保留一塊空間,不能用作他用,保留的概念是針對虛擬地址空間的,而提交的含義是將剛才保留的虛擬地址空間的虛擬內存塊映射到物理內存,這裡windows擴展了物理內存的含義,包括內存條代表的物理內存和磁碟頁文件以及任何可以和真正的物理內存進行換入換出操作的後備存儲,提交的概念其實就是一個映射,為了將虛擬內存變得可用而做的一個到實際物理存儲的一個映射,就是將假的變真了。

windows的保留和提交兩階段方式涉及到幾件事情,一個就是頁表什麼時候確立,我們可以設想一種合理的方式,就是在內存塊保留的時候,不操作頁表,僅僅將虛擬內存段插入到一個便於查找和插入,刪除的數據結構中,而在提交階段操作頁表,當然此時內存不一定已經到了真正的物理內存,很有可能只在頁文件中為之分配了一個slot而已,此情形下,頁表的相關位就可以用來描述這個slot的位置以及別的信息,只要頁表的存在位為0即可,這一點和linux可以一樣,事實上,提交內存只是在擴展物理內存含義的前提下才表示映射物理內存,虛擬內存真正被映射到原始物理內存只有在該內存被訪問的時候才會發生,這是絕對懶惰的。事實上我們可以看到windows的方式不夠懶惰,linux沒有保留和提交的概念,當一個執行緒調用mmap或者malloc或者brk等等不同層次的函數時,實際上就等於保留了內存區域,而只有在該內存被訪問的時候,才會直接映射到物理內存而在這之前,根本不會將虛擬內存和物理的事實有任何聯繫,真對於假只有在不能再隱蔽事實的的時候才會顯露,linux的內存管理是一種絕對的懶惰,訪問內存其實就可以被看做內存提交。windows之所以採用這一種的方式來管理內存其實是為了用一種更加統一的方式去管理所有的內存,只要內存提交了,那麼內存管理器就要跟蹤這塊內存,不管它在物理存儲器還是在磁碟頁文件。linux的方式看來更加不規範,linux使用頁表來充當雙面角色,既可以查找物理存儲器又可以查找交換分區內存的位置,並且linux中沒有一種機制來統一管理物理存儲器和交換分區的空間,靠強大的文件系統功能和高效的內存管理和文件管理數據結構就可以輕易做到內存的高效換入換出,解除了物理存儲器和交換分區的耦合,相反,在windows下,統一華麗的外表下扭曲著混亂不堪的繁雜,比如說如果想修改頁文件的格式,那麼必須涉及內存提交時的邏輯,而在linux中只需要換一個file_operations就可以了,統一華麗的外表完全不是僅僅帶來了觀感上的舒服,同樣也付出了代價,比如平衡進程間內存數量的任務就交給了用戶,其實用戶只要可以在本進程內存分配和管理內存上保持高度靈活就可以了,進程間的內存平衡這樣的任務顯然是操作系統應該擔負起來的,由於只要提交內存,或者在物理存儲器或者在磁碟頁文件會佔據一定的空間,而這些空間是所有的進程共享的,如果一個進程瘋狂的提交了過多的內存,那麼別的進程就要忍飢挨餓,這一點上操作系統作為一個協調者實際上幫不上什麼忙,頂多將貪婪者滅掉了事,物理內存在各個進程間的分配比例完全取決於進程自己而失去了別的進程的監督以及內核機制的協調,這一點看起來不如linux,在linux中內存管理模塊盡量使內存在進程間公平的分配,即使一個進程自己分配了大量的內存,只要它不訪問這些內存,這些內存連交換分區都不會佔據更別說物理存儲器了,當然如果這個貪婪的進程要是訪問了這些內存,那結果就和windows一樣了,從程序的行為應該很容易辨別出這個進程,不過不管怎樣也比windows那種允許占著茅坑不拉屎的策略要好得多,雖然內存已經很便宜,但是對於同樣增長的應用來講內存仍然是稀缺資源,因此完全懶惰式的分配方式應該就是最節省的方式。

作為以上討論的直接結果,我們來看一下兩個系統中的堆棧。在windows中堆棧的分配是靜態的,也就是說在PE文件中確定了線程堆棧的大小並且一般不能在運行時動態改變,在對堆棧進行管理的時候,windows使用了一種稍微複雜一點但是考慮的很周到的方法,windows儘力去保護自己的堆棧不會溢出,怎麼保護呢?在《windows核心編程》上有詳細的描述,大致就是說首先為你的堆棧確定一個大小,然後將這段如此大小的內存塊的第一個和最後一個頁面設置為保留,其餘的頁面遵循以下原則:假設堆棧向下增長,windows將依次把正在被使用的下一個頁面設置為保護提交,當然正在被使用的頁面肯定是提交的了,每當保護提交頁面被訪問時系統會得到通知,注意得到通知而不是出錯信息,並沒有什麼嚴重的錯誤,因為保護提交頁面可以被訪問,它已經提交了,只不過由於具有保護屬性,所有要告知系統這一件事,系統得知後可以將保護提交屬性設置給後一個頁面,依次類推,堆棧有著嚴格的順序訪問特性,就是說首先是高地址被訪問,在略低的地址不被使用之前更低的地址不會被使用,當然除非你使用彙編語言完全脫離堆棧的概念,這樣的話,線程的堆棧空間頁面將按照從高到底的順序一個個被提交,而緊接著被提交的頁面將被設置為保護提交,直到最後,到達堆棧的末尾的時候,windows會檢測到,此時不再將最後一個頁面設置為保護提交,而是引發一個棧溢出異常。windows的這種機制的結果就是有效地保護了堆棧後面的數據不被堆棧數據覆蓋,但是這種機制並不是每次都奏效的,比如一個足以使棧溢出的大數組分配在棧上,數組的起始其實已經出了堆棧,如果我直接存取這第一個元素的話,並且恰好該元素覆蓋的內存已經被提交,那就完蛋了,如果你覺得上述實例會被編譯器發現的話,那麼考慮下面的例子:

char s[1];

s -= 100000;

*s = 100;

看看linux是怎麼做的,很簡單,十分懶惰,linux沒有為堆棧分配靜態的大小,而是利用缺頁中斷使得堆棧在運行期動態增長,當然沒有了固定的大小也就不存在溢出的問題了,只要虛擬內存足夠,動態增長的需求就有可能被滿足,那麼linux有沒有什麼辦法來保護非堆棧數據被堆棧數據損壞或者反過來的情況呢?說實話,沒有,主要是因為一來實現那個機制很複雜,維護引入的額外數據結構肯定會影響效率,二來這是用戶空間的事情,程序員如果不合格直接開掉他就是了,內核不用為他擦屁股,實際上內核如果真的用雕蟲小技幫他擦了屁股,沒有會說內核很高明的,因此開源的linux沒有這種複雜而且單單對內核沒有什麼用的機制,實際上如果程序員不合格,那麼他寫的程序是防不勝防的,機器能和人PK嗎?很顯然不能,再好的操作系統面對一般爛的程序員也是無力去愛誰啊!

最後討論一下「如何分配內存以及在哪裡分配到底要不要讓用戶看到」這個有點哲學味道的問題,這個問題關鍵要看分配的內存做什麼用以及這種作用和系統機制的聯繫的緊密程度,比如說我需要一塊內存保存一些我程序裡面的結構,比如大型資料庫緩衝,比如一個字元串,這種情況下分配越透明越好,因為程序沒有必要和實現機制交流,這樣程序可以更加集中精力解決所謂的業務問題,但是如果一塊內存被一個管理機制需要,那麼就有必要導出給用戶更多的信息,因為這種需求往往都是關注實現本身的需求,而不是介面需求,比如線程棧的位置,因為線程是操作系統的一種機制,目的是優化程序執行,它其實和業務邏輯沒有什麼太大的關係,線程更多的被程序流程的管理機制使用而不是被業務流程使用。在這一點點上,linux要比windows好得多,看看clone系統調用的參數,用戶必須為線程分配棧空間,而這在windows中卻是被默默執行的,實際上windows儘力去向用戶隱藏底層的很多重要的信息,然而類似線程棧的位置這樣的信息很多用戶空間的管理機制還是要用到的,因此最好將這一切都交給用戶,系統不要管的太多。


windows在提交階段雖然做了記錄,但是並沒有分配實際的物理內存,只有在嘗試訪問該地址範圍內的內存發生page fault時才會真正分配物理內存

windows和linux在很多方面的差異歸根結底還是設計思想上的差異,一個想把什麼都做好然後轉交給你(實際上當你想深入了解它怎麼做的時候會發現內部看起來很亂),另一個提供了你自己動手的基礎結構,很方便的就能自己DIY


推薦閱讀:

現在哪些編程語言適合寫操作系統?
如果讀取硬碟上某個文件,使用完畢後釋放內存,然後立即再讀取這個文件,會從內存中還是從硬碟上再讀取?
多核CPU中,利用多線程進行排序中出現了一些奇怪的現象,不知道其背後的原因是什麼,希望有人能給予解答?
Linux內核是如何管理內存的換入換出,以及是如何實現磁碟緩存的?
微軟在 Windows 10 裡面加入 Linux,這是他們認輸了嗎?

TAG:MicrosoftWindows | 操作系統 | Linux | 內存管理 |