計算機操做系統內存管理是十分重要的,由於其中涉及到不少設計不少算法。《深刻理解計算機系統》這本書曾提到過,如今操做系統存儲的設計就是「帶着鐐銬跳舞」,形成計算機一種一種容量多,速度快的假象。
程序員
包括如今不少系統好比數據庫系統的設計和操做系統作法類似。因此在學習操做系統之餘我來介紹並總結一些操做系統的內存管理。面試
首先咱們看一下計算機的存儲層次結構算法
按照金字塔結構能夠分爲四種類型: 寄存器,快速緩存,主存和外存。而數據庫
寄存器和L1緩存
都在Processor內部。在金字塔中,越往下價格越低速度越慢但容量越大。緩存
還有兩種存儲空間須要分清:bash
•地址空間:又稱邏輯地址空間,源程序通過編譯後獲得的目標程序,存在於它所限定的地址範圍內,這個範圍稱爲地址空間。地址空間是邏輯地址的集合。•內存空間: 又稱存儲空間或物理地址空間。是指主存中一系列存儲信息的物理單元(劃重點)的集合,這些單元的編號稱爲物理地址或絕對地址。服務器
簡言之就這兩個空間分別是程序員可以觀測到的存儲空間和真實的物理空間。數據結構
需求:併發
•每一個程序員但願沒有第三方因素干擾程序運行•計算機但願將有限的資源儘量爲多個用戶提供服務app
爲了知足需求的目標:
•計算機至少同時存在一個用戶程序和一個服務器程序(操做系統內核管理)•每一個程序互不干擾,因此其地址空間應該相互獨立。•每一個程序使用的空間應該被保護,最怕運行的時候程序中斷。就和看電影的時候沒法播放同樣難受。
操做系統在內存中的位置有如下三種可能
此時整個內存只有兩個程序,即用戶程序和操做系統。
操做系統所佔的空間是固定的,則用戶程序空間也是固定的,所以能夠將用戶程序永遠加載到同一個地址,即用戶程序永遠從同一個地方開始運行。這種狀況下,用戶程序地址能夠在運行以前就能夠計算出來。
咱們經過加載器計算程序運行以前的物理地址靜態翻譯。此時既不須要額外實現地址獨立和地址保護。由於用戶不須要知道物理內存的相關知識,並且也沒有其它用戶程序。
此時用戶的程序空間須要經過分區來分給多個不一樣的程序了。每一個應用程序佔用一個或幾個分區,這種分配支持多個程序併發執行,但難以進行內存分區的共享。
其中分區有兩種方法:
顧名思義就是把內存劃分爲若干個固定大小的連續分區,這幾個分區或者大小相等以適合多個相同程序併發,或者大小不等的分區以適合不一樣大小的程序。
這種分配方法優勢很明顯,在於很是容易實現,開銷小。
缺點就是會產生不少內部碎片(也就是未被利用的存儲空間),固定的分區總數也限制了併發執行的程序數目。咱們簡單介紹下靜態分配的幾種方法。
•單一隊列的分配方式
•
多隊列分配方式
•固定分區管理
先使用表進行大小初始化,固定分區大小
此時分區的邊界能夠移動,但也產生了分區與分區之間狹小的外部碎片。
在可變分區中,知道內存的空閒空間大小就十分重要了。OS經過跟蹤內存使用計算出內存有多少空閒。跟蹤的方法有兩種:
位圖表示法:
也就是所謂的bitmap
,用每一位來存放某種狀態。將內存每個分配單元賦予一個判斷的用於判斷狀態的字位,字位取值位0
表示單元閒置;字位爲1
表示單元被佔用
•特色
•空間成本固定:不受內存程序數量影響• 時間成本低:• 操做的時候只須要將狀態值改變缺乏容錯能力:因爲內存單元發生錯誤的時會將狀態值改變,對操做系統來說,這個狀態值是由於發生錯誤發生的改變仍是原來的狀態很難判斷。
鏈表表示法
將分配單元按照是否閒置連接,P表明這個空間被佔用,H表明這個這是一片閒置空間。爲了方便遍歷查詢,每一個程序空間的結點接着一個空閒空間的結點每一個鏈表結點還有一個起始地址,與分配單元的大小,用代碼表示爲
enum Status{P=0,H=1};struct LinkNode{ enum Status status;//P表示程序,H表示空閒 struct LinkNode *begin_address;//起始地址 size_t size;//閒置空間大小 struct LinkNode *next;};複製代碼
•特色
•空間成本:取決於程序數量• 時間成本:鏈表不停的遍歷速度很慢,同時還要進行鏈表的插入和刪除修改。• 有必定的容錯能力,能夠經過程序空間結點和空閒空間結點相互驗證。
可變分區的內存分配
OS經過上面兩種跟蹤方法知道內存空閒容量,而如今操做系統通常都以鏈表的形式進行內存空閒容量跟蹤。若是有新的程序須要讀入內存,可變分區就要對空閒的分區進行內存分配。
內存分配使用兩張表:已分配分區表和未分配分區表。用C++描述以下:
//未分配分區表struct FreeBlock { int id; // 內存分區號 int address; // 該分區的首地址 unsigned length; // 分區長度};//已分配分區表struct AllocatedBlock { int id; // 內存分區號 int address; // 該分區的首地址 int pid; // 進程 ID unsigned length; // 分區長度};複製代碼
而後OS用雙向鏈表將全部未分配分區表進行串聯
struct{ FreeBlock data; Node* prior; Node* next;}Node;複製代碼
未分配分區表在整個系統空間上的結構以下:
這裏咱們介紹四種基於順序搜索的尋找空閒存儲空間的算法:
•首次適應算法( First Fit ) :每一個空白區按其地址順序連在一塊兒,從這個空白區域鏈的始端開始查找,選擇第一個足以知足請求的空白塊。• 下次適應算法( Next Fit ) :將存儲空間中空白區構成一個循環鏈,每次爲存儲請求查找合適的分區時,老是從上次查找結束的下一個空閒塊開始,只要找到一個足夠大的空白區,就將它劃分後分配出去。• 最佳適應算法( Best Fit ) : 爲一個做業選擇分區時,老是尋找其大小最接近(小於等於)於做業所要求的存儲區域。• 最壞適應算法( Worst Fit ) :爲做業選擇存儲區域時,老是尋找最大的空白區。
算法舉例!!
系統中空閒分區表以下按照地址遞增次序排列,現有三個做業分配申請內存空間100K
,30K
,7K
。
區號 | 大小 | 地址 | 狀態 |
1 | 32K | 20K | 未分配 |
2 | 8K | 52K | 未分配 |
3 | 120K | 60K | 未分配 |
4 | 331K | 180K | 未分配 |
•首次適應:
從上到下尋找合適的大小
•申請做業100K
,從低地址到高地址找到3號分區,分配完後3號分區起始地址變爲100K+60K=160K
,剩餘空間爲120K-100K=20K
•申請做業30K
,從低地址到高地址找到1號分區,分配完後1號分區起始地址變爲20K+30K=50K
,剩餘空間爲32K-30K=2K
•申請做業7K
,從低地址到高地址找到2號分區,分配完後2號分區起始地址變爲52K+7K=59K
,剩餘空間爲8K-7K=1K
•結論:優先利用內存低地址部分的空閒分區。但因爲低地址部分不斷被劃分,留下許多難以利用的很小的空閒分區(碎片或零頭) ,而每次查找又都是從低地址部分開始,增長了查找可用空閒分區的開銷。
•
下次適應
•申請做業100K
,找到3號分區,分配完後3號分區起始地址變爲100K+60K=160K
,剩餘空間爲120K-100K=20K
•申請做業30K
,從3號分區後繼續出發,找到4號分區,分配完後4號分區起始地址變爲180K+30K=210K
,剩餘空間爲331K-30K=301K
•申請做業7K
,從4號分區後繼續出發,找到1號分區,分配完後1號分區起始地址變爲20K+7K=27K
,剩餘空間爲32K-7K=25K
•結論:使存儲空間的利用更加均衡,不導致小的空閒區集中在存儲區的一端,但這會致使缺少大的空閒分區。
•
最佳適應算法
•申請做業100K
,找到最適合的3號分區,分配完後3號分區起始地址變爲100K+60K=160K
,剩餘空間爲120K-100K=20K
•申請做業30K
,找到最適合的1號分區,分配完後1號分區起始地址變爲20K+30K=50K
,剩餘空間爲32K-30K=2K
•申請做業7K
,找到最適合的2號分區,分配完後1號分區起始地址變爲52K+7K=59K
,剩餘空間爲8K-7K=1K
結論:若存在與做業大小一致的空閒分區,則它必然被選中,若不存在與做業大小一致的空閒分區,則只劃分比做業稍大的空閒分區,從而保留了大的空閒分區。最佳適應算法每每使剩下的空閒區很是小,從而在存儲器中留下許多難以利用的小空閒區(碎片) 。
•
最壞適應算法
•申請做業100K
,找到4號分區,分配完後3號分區起始地址變爲180K+60K=240K
,剩餘空間爲331K-100K=231K
•申請做業30K
,此時被分配過的4號分區依然容量最大,因而仍是找到4號分區,分配完後4號分區起始地址變爲240+30K=250K
,剩餘空間爲231K-30K=201K
•申請做業7K
,此時被分配過的4號分區依然容量最大,找到4號分區,分配完後4號分區起始地址變爲250+7K=257K
,剩餘空間爲201K-7K=194K
結論:老是挑選知足做業要求的最大的分區分配給做業。這樣使分給做業後剩下的空閒分區也較 大,可裝下其它做業。因爲最大的空閒分區老是因首先分配而劃分,當有大做業到來時,其存儲空間的申請每每會得不到知足。
基於順序搜索的分配算法實際上只適合小型的操做系統,大中型系統使用了是比較複雜的索引搜索的動態分配算法。
•回收分區上鄰接一個空閒分區,合併後首地址爲空閒分區的首地址,大小爲兩者之和。•回收分區下鄰接一個空閒分區,合併後首地址爲回收分區的首地址,大小爲兩者之和。•回收分區上下鄰接空閒分區,合併後首地址爲上空閒分區的首地址,大小爲三者之和。•回收分區不鄰接空閒分區,這時在空閒分區表中新建一表項,並填寫分區大小等信息。
用iPad
畫了一個簡單的示意圖以下:
內存分配其實是操做系統很是重要的一環,若是僅限於理論而不寫代碼實踐則容易迷惘,不少具體的實現與都比較困難。如上面的基於順序搜索的最佳適應算法,好比幾個分區的表示方法,都用到了數據結構和算法的知識。若是能用C或者C++完成上述幾個算法和操做的具體實現,相信必定會大有脾益的。
Java 極客技術公衆號,是由一羣熱愛 Java 開發的技術人組建成立,專一分享原創、高質量的 Java 文章。若是您以爲咱們的文章還不錯,請幫忙讚揚、在看、轉發支持,鼓勵咱們分享出更好的文章。
關注公衆號,你們能夠在公衆號後臺回覆「掘金」,得到做者 Java 知識體系/面試必看資料。