1、緒論java
操做系統的各類內存管理策略都出於同一個目的:同時將多個進程存放在內存中,以便容許多道程序設計。不過,這些策略都須要在進程執行以前將整個進程放在內存中。動態載入雖然能減輕這個限制,但須要程序員當心應用,而且花費額外的工做。程序員
而虛擬內存則容許執行進程部分在內存中,一個顯著的優勢是程序能夠比物理內存大。並且虛擬內存將內存抽象成一個巨大的數組,將用戶視界的邏輯內存與物理內存分離,使得程序員不受內存存儲的限制。簡而言之,虛擬內存展示在程序員面前的是一個比物理內存要大得多的、地址連續的內存空間,而事實上是映射到支離破碎的物理內存,乃至磁盤上。算法
然而,虛擬內存的實現並不容易,使用不當反而可能大大地下降性能。數據庫
2、按需調頁數組
一、基本概念緩存
頁須要用到的時候才調入內存。數據結構
這種方案須要硬件支持區分哪些頁在內存,哪些在磁盤。採用有效/無效位來表示。當頁表中,一個條目的該位爲有效時,表示該頁合法且在內存中;反之,可能非法,也可能合法但不在內存中。app
當進程試圖訪問這些還沒有調入內存的頁時,會引發頁錯誤陷阱(page-fault trap)。按如下步驟進行處理:性能
1)檢查進程內部頁表,一般與PCB一塊兒保存。以肯定該引用的合法性spa
2)若是非法,進程終止;不然進行調入:
3)找到一個空閒幀
4)調度一個磁盤操做,將所需頁調入剛分配的幀
5)磁盤讀操做完成後,修改內部表和頁表(有效無效位?),表示該頁已在內存中
6)從新開始因陷阱而中斷的指令。
二、按需調頁的性能
對於按需調頁,下降頁錯誤率相當重要。
另外是對交換空間的處理的使用。磁盤IO到交換空間一般比到文件系統要快,由於交換空間是按大塊進行分配,並不使用文件查找和間接分配方法。所以,在進程開始時將整個文件鏡像複製到交換空間,並從空間交換執行按頁調度,那麼有可能得到更好的性能。
另外一種選擇是開始時從文件系統進行按需調頁,但置換出來的頁寫入交換空間,然後的調頁則從交換空間中讀取。這種方法確保只有須要的頁才從文件系統中調入,又能夠保證必定的性能。
3、寫時複製
有些進程,好比fork()出來的子進程,並不須要按需調頁,而是一開始與父進程共享頁面,當子進程須要修改頁的時候,纔對該頁複製一個副本,在副本上進行修改。是爲寫時複製。
當一個頁須要寫時複製的時候,從哪裏分配空閒頁很重要。許多操做系統爲此提供空閒緩衝池。
4、頁面置換
內存有時會過分分配,進程須要使用的頁大於可分配內存;加上內存並不只用於進程的頁,IO緩存也須要使用大量的內存,會出現內存相對需求僧多粥少的局面,這時進程發生頁錯誤的時候,操做系統準備好了要調入的所需頁,卻發現沒有空閒幀可供分配。正所謂房子看好了,車也看好了,一切都看雙色球了。
一、頁置換
遇到這種狀況,操做系統能夠選擇終止該嗷嗷待哺的進程,也能夠交換出一個倒黴的進程。更多的時候,會採用頁置換的方式:
若是沒有空閒幀,就查找當前沒有使用的幀,將其釋放,空出來保存進程出錯的頁(也就是須要換入的頁)。
若是換出的頁有修改的話,還必須將頁寫回磁盤。能夠經過設置修改位或髒位來提升性能。
頁置換算法:
二、FIFO頁置換
最簡單的頁置換算法。選擇最舊的頁進行置換。具體爲建立一個FIFO隊列來管理內存中的全部頁,隊列中的首頁被置換,而新調入的頁則加到隊列的尾部。
FIFO算法容易理解和實現,但性能不老是很好。所替代的頁可能仍在使用,換出去之後立刻報頁錯誤,要求換回來。
三、最優置換
置換最長時間不使用的頁(不是久未使用,而是預測其將來通過最長時間才被使用?)。這種算法頁錯誤率最低。
這種算法問題在於難以實現。
四、LRU頁置換
最優置換的近似。最優置換與FIFO的關鍵區別在於,FIFO使用的是頁調入時間,而最優置換看重的是頁未來使用的時間。若是使用離過去最近做爲不遠未來的近似,那麼可置換最長時間沒有使用的頁。根據過去來猜想將來。這種方法稱爲 最近最少使用算法。
實現LRU算法,可用計數器,也可用棧:凡用過的頁,就放到頂部,不用的就沉到棧底。
五、近似LRU頁置換
不多有計算機系統能提供足夠的硬件來支持真正的LRU頁置換。然而,許多系統經過引用位方式來進行近似置換:
頁表內的每一個條目都關聯一個引用位,每當引用一個頁時,相應的引用位就被硬件置位;
剛開始時,全部引用位都清零,後來許多被置爲1。經過檢查引用位,能夠知道哪些頁使用過而哪些沒有。這個信息是近似LRU置換算法的基礎。
近似LRU置位算法有幾種:
1)附加引用位算法
每頁有一個8位的字節作引用位,按期刷新引用位。有引用的時候該字節最高位置1,其餘位右移,擠掉原來的最低位。那麼,引用位爲最小值的頁就能夠被置換。
2)二次機會算法
當一個倒黴的頁被選中時,檢查其引用位,若是爲0,直接置換掉;若是引用位爲1,就給它一次機會,放過它,繼續找下一張倒黴頁。那張得到重生機會的頁,其引用位清零,重置時間。在全部頁都被尋找過一遍以前,它起碼不會被替換掉。
3)加強型二次機會算法
經過將引用位和修改位做爲一有序對來考慮:
(0,0)最近無使用也無修改:換吧,別猶豫了
(0,1)最近無使用但有修改:置換前要寫回磁盤,請三思!
(1,0)最近有使用但無修改:可能很快又要使用
(1,1)最近有使用且有修改:可能很快又要使用,且置換前要寫回磁盤,請三思!
六、基於計數的頁置換
爲每一個頁設置一個計數器,造成兩種方案
1)最不常用頁置換算法(LFU)
置換計數最小頁。理由是活動頁應該有更大的引用次數。但可能有以下問題:一個頁可能開始時使用不少,但之後就再也不使用。解決方法是按期將次數寄存器右移一位,以造成指數衰減的平均使用次數。
2)最常使用頁置換算法(MFU)
置換計數最大頁。理由:最小次數頁可能剛剛調進來,且還沒使用。
七、頁緩衝算法
保留一個空閒幀緩衝池。
1)維護一個已修改頁的列表。每當調頁設備空閒時,就選擇一個修改頁寫到磁盤上,並重置其修改位。這種方案增長了乾淨頁,下降了置換時寫出的機率。
2)保留一個空閒幀池,記住頁與幀的對應關係。當幀須要重用時,就先從池中取,沒有磁盤IO。
八、應用程序與頁置換
有時,應用程序經過操做系統使用虛擬內存結果會更壞。數據庫就是一個例子。由於數據庫可提供本身的內存管理和IO緩衝,由於它更能理解本身的內存使用和磁盤使用。基於此,操做系統容許特殊程序將磁盤當成邏輯塊數組使用,而無需經過操做系統的文件系統。
5、幀分配
如何在各個進程之間分配必定的空閒內存?
簡單辦法是將幀掛在空閒幀鏈表上,當發生頁錯誤之時即進行分配。進程終止時幀再次放回空閒幀鏈表。
幀分配策略受到多方面限制。例如, 分配數不能超過可用幀數,也必須分配至少最少數量。保證最少許的緣由之一是性能。頁錯誤增長會減慢進程的執行。而且,在指令完成前出現頁錯誤,該指令必須從新執行。因此有足夠的幀相當重要。
每一個進程幀的最少數量由體系結構決定,而最大數量是由可用物理內存數量決定。
一、幀分配算法有
1)平均分配,每一個進程同樣多
2)按進程大小使用比例分
3)按進程優先級分
4)大小和優先級組合分
二、全局分配和局部分配
全局置換容許進程從全部幀集合中選擇一個進行置換,而無論該幀是否已分配給其餘進程,即它能夠從其餘進程搶奪幀,好比高優先級搶奪低優先級的幀;局部分配則要求每一個進程只能從本身的分配幀中分配。
全局置換一般有更好的吞吐量,且更爲經常使用。
6、系統顛簸
進程若是沒有它所須要的幀,那麼很快產生頁錯誤,這時必須置換某個頁。然而全部頁都在使用,置換一個,馬上又要換回來,頁錯誤頻繁在發生,稱爲顛簸。
顛簸致使嚴重的性能問題。操做系統時刻注視CPU的使用率,若是CPU使用率過低,系統會引入新進程。採用全局置換算法,可無論頁屬於哪一個進程,搶到就換。假設一個進程須要更多幀,開始出現頁錯誤,從其餘進程搶到幀。被搶的進程從就緒隊列移出,CPU使用率降低;CPU調度程序發現後,調入更多進程,企圖讓CPU嗨起來。新進來的進程嗷嗷待哺,幀被搶奪得更激烈,等待隊列更長,CPU使用率進一步降低,CPU調度程序更努力地調入更多的進程。。。
最終,進程主要忙於調頁,系統不能完成一件工做。
使用局部置換能夠限制系統顛簸,但不能徹底解決這個問題。
一、工做集合模型
爲了防止顛簸,進程必須得到足夠多的幀才能夠啓動。操做系統跟蹤每一個進程的工做集合,爲其分配大於其工做集合的幀數。若是還有空閒,纔有可能啓動另外一進程。若是某個進程全部工做集合之和超過了可用幀總數,那麼會被暫停,其幀分配給其餘進程。掛起的進程等待之後重啓。此爲工做集合模型。困難在於跟蹤工做集合。
二、頁錯誤頻率策略
除了工做集合,另外一種防止顛簸的方案是頁錯誤頻率策略。
若是一個進程,頁錯誤頻率過高,說明須要更多的幀,給它!若是頁錯誤頻率過低,說明幀有富餘,分些給別人。爲進程設置頁錯誤率上下限,機動地分配幀。
與工做集合模型同樣,若是須要幀卻無幀可分配,那麼進程應該暫停,釋放給其餘一樣高頁錯誤頻率的進程。
7、內存映射文件
一般,文件每次訪問都須要一個系統調用和磁盤訪問,但還有另外一種方法:使用虛擬內存技術將文件IO做爲普通內存進行訪問。意思就是說,訪問文件就像訪問內存同樣。
一、基本機制
將磁盤塊映射成內存頁(一頁或多頁)。剛開始時,頁面調度,會產生頁錯誤,這樣,文件內容陸續讀入物理內存矣。文件的讀寫就像內存訪問同樣,經過內存操做文件而不是系統調用read()和write(),從而簡化。
其中,對文件的寫可能不會當即寫到磁盤上,除非髒頁置換或操做系統按期檢查,或者文件關閉?
若是一個文件多個進程共用,那麼將其映射到各自的虛擬內存中,以容許數據共享。任一進程修改虛擬內存中的數據,其餘進程均可以見到。若是有修改,則是修改各自的副本,寫時複製。可能還有互斥。
二、WIN32 API 的共享內存
將存在於磁盤的文件放進一個進程的虛擬地址空間,並在該進程的虛擬地址空間中產生一個區域用於「存放」該文件,這個空間就叫作File View(存放在進程的虛擬內存中),系統並同時產生一個File Mapping Object(存放於物理內存中)用於維持這種映射關係,這樣當多個進程須要讀寫那個文件的數據時,它們的File View其實對應的都是同一個File Mapping Object,這樣作可節省內存和保持數據的同步性,並達到數據共享的目的。
三、內存映射IO
將IO設備映射到內存,那麼對該部份內存進行讀寫,就如同對IO設備進行讀寫,而沒必要直接操做IO設備。好比說,屏幕上每個點都對應一個內存地址,程序控制內存,就能控制屏幕顯示。
8、內核內存的分配
當用戶態進程須要額外內存時,能夠從內核所維護的空閒頁幀鏈表中獲取頁。一般,頁幀分散在物理內存中,可是內核內存一般從空閒內存池中獲取,主要由兩個緣由:
1)內核須要爲大小不一樣的數據結構分配內存,所以必須節省使用,並儘可能減低碎片浪費。許多操做系統的內核代碼與數據不受分頁系統控制
2)有的硬件須要直接與物理內存打交道,而不通過虛擬內存接口,所以須要內存常駐在連續的物理頁中
內核進程進行內存管理的兩個方法:
一、Buddy系統
從物理上連續、大小固定的段上進行分配,按2的冪大小來進行分配,如4K、8K等。優勢是可經過合併而快速造成更大的段,但容易產生碎片。
二、slab分配
按照內核對象的數據結構要求的大小,預先分配好若干內存塊,等待召喚使用。
具體來講,內核對象對應有高速緩存,而高速緩存含有若干個slab(就是尺寸合適的內存塊?)。slab可有三種狀態:滿的、空的、部分。當分配的時候,先從空閒狀態部分分配,不夠從空的部分分配;還不夠就從物理連續頁上分配新的。
優勢:
1)尺寸因應內核對象要求可變,沒有碎片
2)預先準備,可快速知足要求
9、其餘考慮
一、預調頁
純按需調頁的一個顯著特性是當一個進程開始時會出現大量頁錯誤。而預調頁的策略是同時將所需的全部頁調入內存。關鍵是成本是否小於相應頁錯誤的成本。
二、頁大小
該用大頁仍是小頁,是個問題。
1)大頁有利於減小頁表
2)小頁有利於減小碎片,可更好地利用內存
3)小頁傳輸快,大頁IO好,但又不必定,小頁由於尋址、傳輸快,局部性得以改善,總的IO就會下降,那麼,應該用小頁?
4)然而,大頁能夠下降頁錯誤數量
……
切克鬧,如今你告訴我,該用大頁仍是小頁?
三、TLB範圍
TLB可提升內存訪問速度,若是沒有TLB,則每次取數據都須要兩次訪問內存,即查頁表得到物理地址和取數據。
TLB只維護頁表中的一小部分條目,邏輯地址轉換物理地址過程當中,先在TLB中查找,若是找到,那麼物理地址唾手可得;若是TLB中沒有,那麼使用置換算法,將相關條目置換進TLB,而後再獲得物理地址。
那麼提升TLB命中率相當重要。
提升TLB命中率可增長TLB條數,但代價不小,由於用於構造TLB的相關內存既昂貴又費電。另外一個方法是增長頁的大小,或提供多種頁大小。
四、反向頁表
反向頁表能夠節省內存,不過,當進程所引用的頁不在內存中時,仍然須要一個外部頁表以得到物理幀保存哪一個虛擬內存頁面的信息。所幸這只是在頁錯誤時才須要用到,外部頁表自己能夠換出換入,不苛求必定完備。
五、程序結構
咱們日常寫程序,對內存根本不用關心。但有時瞭解一點內存知識可改善系統性能:
比方說,有一個128*128的二維數組,數據按行存放,如何遍歷性能高?
int i,j; int[128][128] data;
for(int j=0;j<128;j++) for(int i=0;i<128;i++) data[i][j] = 0;
但假如這樣寫:
for(int i=0;i<128;i++) for(int j=0;j<128;j++) data[i][j] = 0;
六、I/O互鎖
容許頁在內存中被鎖住。
在全局置換算法中,一個進程發出IO請求,被加入到IO設備等待隊列,而CPU交給了其餘進程。這些進程發生頁錯誤,恰恰置換了等待進程用於IO的緩存頁,這些頁被換出。好了,請求IO的進程等待到了IO設備,針對指定地址進行IO,然而幀早被其餘進程的不一樣頁所使用。
對這個問題,一般有兩種解決方法:
1)毫不對用戶內存進行IO,若是要進行IO,將用戶內存數據複製到系統內存。要複製一次,開銷過高了。
2)物理幀有一個鎖住位,容許頁鎖在內存中。若是鎖住,則不能置換。當IO完成,頁被解鎖。
鎖住位用處多多,好比操做系統內核頁一般加鎖;低優先級進程的頁至少要運行一次才能解鎖被置換。