內存管理算法介紹

        內存是計算機系統中除了處理器以外最爲重要的資源,任何一個程序的運行都離不開內存資源的有效使用。前面兩小節介紹了硬件支持的內存管理機制,尤其是如何將虛擬地址或者邏輯地址轉譯成物理內存地址。這一節我們將首先討論在一個地址空間內部如何有效地進行動態內存管理,然後介紹常用的頁面替換算法,以及在進程內存管理中常常用到的工作集概念和相應的算法。


        假設操作系統或者一個進程已經獲得了一塊連續地址的內存,系統或進程在執行過程中需要利用這塊內存來滿足各種內存請求。由於內存請求存在動態性,即每次請求的內存大小可能不相同,甚至差異很大,並且這些小內存塊的生命週期也不盡相同,所以,系統需要提供合適的算法來儘可能地滿足這些動態的內存請求。在現代計算機系統中,堆(heap )正是這樣一個提供動態內存分配能力的內存抽象。操作系統使用堆來滿足各種動態內存請求,應用程序通過堆獲得內存。我們常用的C/C++ 基本運行庫提供了堆內存管理的能力,所以,C/C++ 程序中的 malloc/free 和new/delete 可以直接在堆的接口上工作。
 
        就本質而言,內存管理算法可以分爲兩大類:位圖標記法和空閒鏈表法。位圖標記法的思路很簡單:假設總的待分配內存的大小爲N 字節,管理內存的粒度爲 M 字節,並且N= M×K,也就是說,內存管理的基本單元爲 M 字節,現在共有 K 個單元需要動態管理。爲了記錄這K 個內存單元的使用情況,位圖標記法將使用一個共有K 位的位圖(bitmap ),其中每一位的值(0 或者1)用來說明這一位所對應的內存單元是否空閒。由於位圖精確地記錄了每一個內存單元的空閒或已被使用的情況,所以,當內存管理器接收到一個新的內存申請時,只須掃描位圖,就能確定是否有合適的空閒內存可以滿足此請求。其做法是,根據所請求內存的大小,確定需要多少個連續的內存單元來滿足此請求,然後在位圖中掃描是否存在連續這麼多0 位,如找到了,則將它們所對應的內存分配給客戶,並且將這些位置成1。當釋放內存時,要求客戶指定待釋放內存的起始地址和大小,這樣內存管理器就可以計算出此次內存釋放對應於位圖中哪些連續位,並且將它們置成0。


        位圖標記法的實現比較簡單,但是需要額外的內存開銷,通常爲N/8 × M,所以,只需適當地選取M,就可以控制這部分額外開銷。當然,用戶請求的內存大小不一定是M 字節的倍數,因而在分配內存時有一定的浪費。平均而言,每次用戶請求將導致M/2 字節的浪費。另外,內存管理器在分配內存時需要掃描連續多個 0 位,此操作並不高效(複雜度爲O(K)),這是該算法的一個缺點。


        另外一類動態內存管理方法使用鏈表來描述已分配的和空閒的內存塊,稱爲空閒鏈表法。在初始時,整個內存塊被當做一個大的空閒塊加入到空閒鏈表中。以後,當內存管理器接收到一個內存分配請求時,將從空閒鏈表中找到一個合適的、能提供足夠內存的空閒塊,並從該空閒塊中分離出足夠多的內存,交給客戶,剩下的內存(如果還有的話)仍然是一個空閒塊,而已分配的內存則加入到已分配內存鏈表中。當釋放一塊內存時,內存管理器將已分配的內存塊從已分配鏈表轉移到空閒鏈表中,如果有可能,與相鄰的內存塊合併以便構成更大的空閒塊,從而儘可能地滿足客戶的大內存請求。在這一類方法中,當內存管理器接收到內存請求時,將按照以下不同的策略來查找適當的空閒內存塊:


(a)  最先匹配法(first-fit ),從空閒鏈表中找到第一個滿足客戶請求的空閒塊。


(b)  最佳匹配法(best-fit ),從空閒鏈表中找到最接近於客戶請求大小的空閒塊。


(c)  最差匹配法(worst-fit),從空閒鏈表中找到最大的空閒塊。


(d)  下一個匹配法(next-fit ),從空閒鏈表的當前位置往後掃描,找到第一個滿足客戶請求的空閒塊。


        除了以上介紹的位圖標記法和空閒鏈表法以外,還有兩種內存管理算法也值得介紹一下:slab 算法和夥伴系統(buddy system )。Slab算法實際上是以上介紹的位圖標記法和空閒鏈表法的結合,它針對某個閾值以下的內存塊使用位圖法,按照2 的冪次,每一階有一塊內存和對應的位圖;在此閾值以上,slab 算法使用鏈表來管理內存。Linux 和Solaris系統的內核使用了slab 算法。


        Slab 算法對於小內存的分配非常快速高效,但也有空間浪費,當所請求的內存大小介於2 的兩個連續冪次之間時,所分配的內存塊(大小爲2 的冪次)就存在部分空間浪費。浪費的部分稱爲內碎片(internal fragmentation ),因爲它位於已分配的內存塊內部。對應地,如果碎片位於已分配的兩個內存塊之間,則稱爲外碎片(external fragmentation)。例如,前面介紹的空閒鏈表法在經過一段時間的動態內存分配和釋放以後,往往會造成很多外碎片,因此,即使實際的空閒內存還有很多,但由於外碎片的原因,對於較大內存的申請也往往無法滿足。


        解決外碎片問題的一個算法是夥伴系統(buddy system)。下面以二進制夥伴系統爲例來說明它的主要思想。首先,假設待分配的整塊內存的大小爲2 的冪次,比如說2m字節,每個字節相對於基地址的偏移量爲0,1,2,…,2m−1;另有一個數組avail[m],其中每個元素avail[i] 記錄了大小爲 2i+1的內存塊的空閒鏈表頭。內存的分配按照2 的冪次進行,也就是說,分配給客戶的內存塊總是2 的冪次方,不管客戶是否真正需要這麼多內存。對於任一大小爲 2i的內存塊,假設其相對於基地址的偏移量爲 p,則該內存塊的夥伴被定義爲,p 的第i+1 位取補碼加上基地址,這樣得到的地址所指向的同樣大小的內存塊。圖4. 9 演示了夥伴內存塊和非夥伴內存塊。


        夥伴系統在初始時,整個待分配內存塊都是空閒的,所以,avail[m-1]鏈表指向此內存塊,該數組中其他所有的鏈表都是空的,當客戶申請一塊大小爲k 的內存塊時,夥伴系統在所有≥   logk  的avail[] 鏈表中查找空閒塊,從第一個找到的非空鏈表中找出一個內存塊,經過分裂變成合適大小,然後返回給客戶。因此,夥伴系統的內存分配過程實際上是大內存塊分裂成小內存塊的過程,分裂得到的小內存塊一部分給客戶,剩下的掛到適當的空閒鏈表中,以備下次繼續分配給客戶。


        在內存回收過程中,如果待回收的內存塊與鏈表中已有的一塊內存互爲夥伴,則它們可以合併成更大的內存塊,從而轉移到大內存塊對應的空閒鏈表中。因此,內存塊的合併在互爲夥伴的內存塊之間進行,並且從小到大,一直到不能合併爲止。


        夥伴系統的內存分配和回收的執行效率比較高(O(logn) ),但也有問題:第一,空間利用率的問題,由於它總是按照 2 的冪次來分配內存塊,所以,如果客戶總是按照略大於2 的冪次來申請內存,則空間浪費的現象將較爲嚴重;第二,外碎片問題仍然存在,例如,兩個相鄰的非夥伴內存塊即使能滿足客戶的內存要求,夥伴系統也不會把它們連起來分配給客戶。有一些改進的夥伴系統能緩解這些問題,但效率可能不如二進制夥伴系統這麼好。關於夥伴系統的更詳細信息,讀者可以參考相關文獻[TAOCP-1]。


        接下來我們討論在頁式內存管理系統中,當物理內存緊缺時,該從哪些進程中選擇哪些頁面,把它們的內容寫到磁盤上,從而騰出這些頁面所對應的物理頁面,以便用於後續的內存需要。由於在一個實際的多進程、多任務系統中,所有進程使用的頁面總數可能會超過系統中的頁面數量,因此,當一個進程向系統請求更多的物理頁面時,系統必須有一套算法或策略來保證適時地滿足該進程的需要。對於操作系統而言,這實際上也是在頁面粒度上的物理內存管理,其中的算法往往稱爲頁面替換算法。通常以下一些算法是值得考慮的[MOS]:


        最優頁面替換算法(The Optimal Page Replacement Algorithm)。這是一個理論上最優的算法,它要求能夠預測每個頁面下次使用的時間,從而確定該頁面還需要等待多長時間纔會真正使用,所以,在選擇該換出哪些頁面時,優先考慮那些等待時間最長的頁面。此算法的基本原理是,越是頻繁使用的頁面,越應該留在物理內存中;相對而言,如果要換出換入的話,應儘可能選擇那些不會被頻繁使用的頁面,或者在某段時間內不會被頻繁使用的頁面。最終達到的效果是減少頁面換入換出的次數。但是,這一算法的問題是,預測一個頁面下次被使用的時間在實際系統中往往是不可行的,除非是在做事後分析。因此這種算法可以當做一個基準來對已有的算法做性能評價,具體做法是,在一個系統中記錄下每次頁面被訪問的歷史痕跡數據,有了這些數據以後,就可以在模擬環境中,使用最優頁面替換算法,得到最少的換頁次數,作爲被評估算法的一個理論最優參照。


        最近未使用(NRU)頁面替換算法(The Not Recently Used Page Replacement Algorithm)。這一算法的思路是,當系統需要物理頁面時,檢查所有的頁面,優先替換那些最近(所謂最近,是一個相對時間,比如,最近的幾個時鐘滴答)一直沒有被訪問或修改的頁面。爲了建立起最近是否被訪問或修改的參考依據,通常每個頁面需要記錄下它被訪問的情況,從效率來考慮,這往往需要硬件的支持。有兩個標誌位具有特別的意義:訪問位R 和修改位M。當一個頁面被初次使用時,它的訪問位 R 被置上,若頁面被修改,則修改位M 被置上。在進程的運行過程中,R 位被定期清除,這樣系統就能區分最近未被訪問過的頁面和最近被訪問過的頁面。當需要替換頁面時,首先找那些未被訪問過的頁面;如果還不夠,則繼續找已被訪問但未被修改過的頁面;如果還需要更多的頁面,則只好找那些已被訪問且已被修改過的頁面。這一算法相對簡單並且易實現,尤其在硬件的支持下可以有較好的性能,雖然它不是最優的,但在實用中是有效的。


        先進先出頁面替換算法(The First-In First-Out Page Replacement Algorithm)。顧名思義,這種算法的思路是把所有已在內存中的頁面組織成一個隊列(也可以是一個鏈表),每次當有頁面換入到內存中的時候,就添加到隊列的末尾;當需要頁面換出時,直接從隊列中移除頁面。這一算法聽起來很有道理,把留在內存中時間最長的頁面換出內存,但實際上,經常要訪問的頁面也不得不在隊列中流動,從而會造成不必要的換出和換入開銷。由於這樣的原因,在實踐中,這一算法很少被單獨使用。


        第二次機會頁面替換算法(The Second Chance Page Replacement Algorithm)。這是對先進先出頁面替換算法的改進,很自然,對於最老的頁面,即隊列頭的頁面,如果它的訪問位R 爲0,則說明這個頁面不僅老,而且很久沒用了,理應換出去;如果R 位非0,則說明該頁面最近被訪問過,因此再給它一次機會,做法是,先把 R 位清零,然後把它移到隊列尾,就好像它是一個新換入的頁面一樣。然後系統再進一步檢查隊列頭的頁面。如果隊列中的頁面最近都被訪問過,那麼,它們將被依次檢查一遍並清除其訪問位,然後在下次再被檢查到的時候被依次換出內存。


        第二次機會頁面替換算法在具體實現的時候,有一種優化方法,它可以避免在隊列中頻繁地移動頁面,而是把頁面組織成環形鏈表,然後用一個指針指向時間上最早加入的頁面。這樣做的好處是,如果有一個頁面被檢查了訪問位以後,並不從鏈表中清除,則只須清除其訪問位,並移動指針指向下一個頁面即可,無須將頁面從鏈表頭移到鏈表尾。由於這種做法就像一個時鐘的指針在鐘面上移動一樣,因此該算法有時候也被稱爲時鐘頁面替換算法(The Clock Page Replacement Algorithm )。其實質跟第二次機會頁面替換算法完全相同,只是實現上不同而已。


        最近最久未使用(LRU )頁面替換算法(The Least Recently Used Page Replacement Algorithm)。這一算法的思路是,當系統在選擇換出一個頁面的時候,優先考慮最近最久未使用的那個頁面。此算法實際上是對最優頁面替換算法的一個估計,其依據是頁面訪問的局部性原理。既然無法直接測量頁面將來被訪問的時間,不妨用最近一段時間內頁面被訪問的頻率來猜測它將來被訪問的情況,從而協助作出頁面替換決策。若在最近一段時間內,某些頁面被頻繁地訪問,則在將來的一段時間內,它們還可能會被頻繁地訪問。反之,若某些頁面長時間未被訪問,則在將來,它們極有可能仍然長時間不會被訪問,所以,在選擇頁面的時候優先考慮這些頁面。


        最近最久未使用(LRU)頁面替換算法與前面介紹的最近未使用(NRU)算法並不一樣。NRU算法基於頁面的訪問位和修改位來作出決定,而 LRU算法基於頁面最近被訪問的時間長短來作出選擇。在實現 LRU算法時,要求能夠定位到最久沒有使用過的頁面,這可以通過維護一個頁面鏈表來實現,但每次訪問一個頁面都要把這個頁面移到鏈表首,表示它剛剛被訪問過。因而算法的維護成本較高,難以硬件實現。


        最不經常使用(NFU)頁面替換算法(The Not Frequently Used Page Replacement Algorithm)。此算法的思路與 LRU一致,它主要是提供了一種軟件實現。其做法是,爲每個頁面維護一個計數器,初值爲 0。每次時鐘中斷時,系統對所有頁面,把它們的訪問位(0 或者1)加到計數器上,這樣,經常被訪問的頁面就有機會增加其計數器,而不常訪問的頁面其計數器相對較少得到增加。但這個算法的問題在於,計數器只增不減,這意味着頁面的歷史會長久地影響頁面替換算法的決策。


        對NFU算法的一個改進算法稱爲頁面老化算法(page aging algorithm)。它對 NFU做了修改,使其更好地模擬LRU算法。其做法是,在時鐘中斷修改頁面計數器的時候,並不是簡單地遞增計數器,而是先把計數器的值右移一位,然後把訪問位R 加到計數器的最左邊位上,而不是最右邊位。經過這樣修改以後,如果一個頁面經過了一段頻繁訪問的時間過後,它慢慢地不再被訪問了,則因頻繁訪問而對計數器的影響在經過幾次右移位以後,逐漸消除了,取而代之的是最近該頁面被訪問的情況。


        老化算法只用有限個位來模擬頁面最近被訪問的情況,它提供的計數器並非精確的時間計數值,而只是一個相對的最近被訪問的參照,但是其優點在於,它能夠逐漸地抹去歷史的影響,而讓最近一段時間的被訪問情況參與到決策中。在實踐中,這些相對久遠的歷史對於決策的重要性並不高,所以,老化算法比較具有實際意義。


        以上介紹了操作系統在替換頁面時的一些常用算法和選擇依據。現在我們來看一看,系統在進程層次上是如何管理和控制物理內存資源的。這對於多進程系統有重要的意義,因爲儘管每個進程都有非常大的虛擬地址空間(比如在32位Windows 上有2 GB或3 GB私有的虛擬空間),但它們能得到的物理內存往往只是相對較少的一部分,進程之間實際上是在爭搶有限的物理內存資源。所以,操作系統必須小心地平衡每個進程的需求和分配給它的內存。爲了衡量進程得到的物理內存資源,這裏首先介紹進程工作集的概念。


        當一個進程被創建並開始運行時,最初所有的頁面都還在磁盤上(包括進程的可執行文件),隨着控制流不斷前進,全局數據和棧的地址範圍被不停地訪問到,並且動態內存的需求也開始出現,該進程逐漸獲得越來越多的物理內存頁面。對內存頁面的請求和滿足通常是以中斷或異常的方式來完成的。隨着進程佔有的物理內存越來越多,它的運行趨向平滑,因爲對於物理內存的需求開始減少。操作系統根據需要而分配物理頁面的做法稱爲按需換頁(demand paging)。


        結合前面介紹的頁面替換策略,我們可以理解:操作系統在內存緊缺時將根據一定的算法和規則,向進程要回物理頁面(也就是說,選擇哪些頁面被替換);而進程則在必要的時候向系統請求更多的物理頁面。操作系統就在這兩者之間管理着有限的物理內存資源。那麼,對於一個進程而言,一方面,當它佔用物理內存太多時,自然地,有些頁面會被操作系統收回去;另一方面,當它的執行邏輯需要更多物理內存時,操作系統可以把當前空閒的或者收回來的(既可能從其他進程收回來,也可能從它自身所佔的內存中收回來)物理頁面分配給它。在任一給定時刻,進程所佔的物理內存是確定的;從一個過程來看,它所佔的內存數量呈現出動態變化的特性。工作集模型正是刻畫一個進程的內存使用情況的模型。這裏,工作集(working set)指一個進程當前正在使用的物理頁面的集合。


        應用程序在工作時,對於內存的訪問通常呈現出一定的局部性,也就是說,在一段時間內,程序對內存的訪問往往集中在一定的範圍內。這也意味着,進程的工作集的變化相對而言是緩慢的。進程工作集變化越緩慢,則單位時間內頁面換入換出發生的次數越少,這當然有利於進程的運行,它的性能自然越好;另一方面,進程的工作集也是由操作系統來管理和控制的,工作集越大,則它的變化自然越緩慢。因此,工作集管理也是操作系統內存管理的一個重要方面。
 
        在工作集理論模型中,進程的工作集可以用一個二元函數 w( t ,   δ  )來表示,其中 t 代表時間點,δ   代表一段時間間隔,也稱爲工作集窗口。w( t ,   δ  )表示t - δ  與t 兩個時間點之間進程所訪問到的頁面集合,顯然,隨着δ  的增大,w( t ,   δ  )只可能增加而不會減小,即 w( t ,   δ  )是δ  的單調非遞減函數。但由於程序的內存訪問的局部性原則,w( t ,   δ  )的遞增在δ  較小時很快,然後就會穩定下來,其曲線大致如圖4. 10所示。當δ  大到一定程度,w  ( t ,  δ  )可能又會有一段快速增長,然後穩定下來。當δ  較大時,w 曲線取決於程序的執行邏輯。


        工作集理論模型可以用來指導對進程頁面的有效管理,例如,在進程初始執行期間,進程的工作集很快增長,但到一定時候,工作集就會穩定下來。因此,一種有效的優化手段是,記錄下工作集穩定下來以後某一時刻的工作集內容(即進程中的哪些頁面被訪問了),下次該進程啓動時,直接爲這些頁面賦予物理內存,並且從磁盤加載相應的內容,這樣可以避免以按需換頁的方式逐漸地從磁盤讀取文件內容,從而大大加快進程的啓動速度。Windows 使用了這種優化手段,稱爲邏輯預取器(Logical Prefetcher)。


        那麼,如何維護進程工作集信息呢?一種簡便的做法是,記錄每個頁面最近被訪問的時間,這樣,根據預設的δ  值,一旦當前時間超過了該頁面最近被訪問的時間再加上δ 值,則從工作集中刪除此頁面。根據工作集理論模型,預設的δ   值(當然不能太小)對於工作集並沒有很大的影響。而且,工作集的這一維護機制可以與頁面老化算法有機地結合起來,在實踐中這不難做到,例如直接根據頁面的老化程度來決定是否從工作集中移除一個頁面。


        有了進程工作集的信息以後,我們可以用這些信息來改進頁面替換算法。例如,在前面介紹的時鐘頁面替換算法中,如果指針所指的頁面的訪問位爲0,則意味着該頁面可以被替換,但現在有了工作集信息以後,需進一步檢查此頁面是否屬於當前進程的工作集,如果是,則不被替換,算法繼續往前查找其他的頁面。這一改進算法稱爲WSClock[WSCLOCK]。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章