選擇調出頁面的算法就稱為頁面置換算法。好的頁面置換算法應有較低的頁面更換頻率,也就是說,應將以后不會再訪問或者以后較長時間內不會再訪問的頁面先調出。
常見的置換算法有以下四種(以下來自操作系統課本)。
1. 最佳置換算法(OPT)
最佳(Optimal, OPT)置換算法所選擇的被淘汰頁面將是以后永不使用的,或者是在最長時間內不再被訪問的頁面,這樣可以保證獲得最低的缺頁率。但由於人們目前無法預知進程在內存下的若千頁面中哪個是未來最長時間內不再被訪問的,因而該算法無法實現。
最佳置換算法可以用來評價其他算法。假定系統為某進程分配了三個物理塊,並考慮有以下頁面號引用串:
7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1
進程運行時,先將7, 0, 1三個頁面依次裝入內存。進程要訪問頁面2時,產生缺頁中斷,根據最佳置換算法,選擇第18次訪問才需調入的頁面7予以淘汰。然后,訪問頁面0時,因為已在內存中所以不必產生缺頁中斷。訪問頁面3時又會根據最佳置換算法將頁面1淘汰……依此類推,如圖3-26所示。從圖中可以看出釆用最佳置換算法時的情況。
可以看到,發生缺頁中斷的次數為9,頁面置換的次數為6。
| 訪問頁面 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
| 物理塊1 | 7 | 7 | 7 | 2 | 2 | 2 | 2 | 2 | 7 | |||||||||||
| 物理塊2 | 0 | 0 | 0 | 0 | 4 | 0 | 0 | 0 | ||||||||||||
| 物理塊3 | 1 | 1 | 3 | 3 | 3 | 1 | 1 | |||||||||||||
| 缺頁否 | √ | √ | √ | √ | √ | √ | √ | √ |
2. 先進先出(FIFO)頁面置換算法
優先淘汰最早進入內存的頁面,亦即在內存中駐留時間最久的頁面。該算法實現簡單,只需把調入內存的頁面根據先后次序鏈接成隊列,設置一個指針總指向最早的頁面。但該算法與進程實際運行時的規律不適應,因為在進程中,有的頁面經常被訪問。
| 訪問頁面 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
| 物理塊1 | 7 | 7 | 7 | 2 | 2 | 2 | 4 | 4 | 4 | 0 | 0 | 0 | 7 | 7 | 7 | |||||
| 物理塊2 | 0 | 0 | 0 | 3 | 3 | 3 | 2 | 2 | 2 | 1 | 1 | 1 | 0 | 0 | ||||||
| 物理塊3 | 1 | 1 | 1 | 0 | 0 | 0 | 3 | 3 | 3 | 2 | 2 | 2 | 1 | |||||||
| 缺頁否 | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ |
FIFO算法還會產生當所分配的物理塊數增大而頁故障數不減反增的異常現象,這是由 Belady於1969年發現,故稱為Belady異常,如圖3-28所示。只有FIFO算法可能出現Belady 異常,而LRU和OPT算法永遠不會出現Belady異常。
| 訪問頁面 | 1 | 2 | 3 | 4 | 1 | 2 | 5 | 1 | 2 | 3 | 4 | 5 |
| 物理塊1 | 1 | 1 | 1 | 4 | 4 | 4 | 5 | 5 | 5 | |||
| 物理塊2 | 2 | 2 | 2 | 1 | 1 | 1 | 3 | 3 | ||||
| 物理塊3 | 3 | 3 | 3 | 2 | 2 | 2 | 4 | |||||
| 缺頁否 | √ | √ | √ | √ | √ | √ | √ | √ | √ | |||
| 1 | 1 | 1 | 5 | 5 | 5 | 5 | 4 | 4 | ||||
| 物理塊2* | 2 | 2 | 2 | 2 | 1 | 1 | 1 | 1 | 5 | |||
| 物理塊3* | 3 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | ||||
| 物理塊4* | 4 | 4 | 4 | 4 | 3 | 3 | 3 | |||||
| 缺頁否 | √ | √ | √ | √ | √ | √ | √ | √ | √ |
3. 最近最久未使用(LRU)置換算法
選擇最近最長時間未訪問過的頁面予以淘汰,它認為過去一段時間內未訪問過的頁面,在最近的將來可能也不會被訪問。該算法為每個頁面設置一個訪問字段,來記錄頁面自上次被訪問以來所經歷的時間,淘汰頁面時選擇現有頁面中值最大的予以淘汰。再對上面的實例釆用LRU算法進行頁面置換,如圖3-29所示。進程第一次對頁面2訪問時,將最近最久未被訪問的頁面7置換出去。然后訪問頁面3時,將最近最久未使用的頁面1換出。
| 訪問頁面 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
| 物理塊1 | 7 | 7 | 7 | 2 | 2 | 4 | 4 | 4 | 0 | 1 | 1 | 1 | ||||||||
| 物理塊2 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 3 | 3 | 0 | 0 | |||||||||
| 物理塊3 | 1 | 1 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 7 | ||||||||||
| 缺頁否 | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ |
在圖3-29中,前5次置換的情況與最佳置換算法相同,但兩種算法並無必然聯系。實際上,LRU算法根據各頁以前的情況,是“向前看”的,而最佳置換算法則根據各頁以后的使用情況,是“向后看”的。
LRU性能較好,但需要寄存器和棧的硬件支持。LRU是堆棧類的算法。理論上可以證明,堆棧類算法不可能出現Belady異常。FIFO算法基於隊列實現,不是堆棧類算法。
4. 時鍾(CLOCK)置換算法
LRU算法的性能接近於OPT,但是實現起來比較困難,且開銷大;FIFO算法實現簡單,但性能差。所以操作系統的設計者嘗試了很多算法,試圖用比較小的開銷接近LRU的性能,這類算法都是CLOCK算法的變體。簡單的CLOCK算法是給每一幀關聯一個附加位,稱為使用位。當某一頁首次裝入主存時,該幀的使用位設置為1;當該頁隨后再被訪問到時,它的使用位也被置為1。對於頁替換算法,用於替換的候選幀集合看做一個循環緩沖區,並且有一個指針與之相關聯。當某一頁被替換時,該指針被設置成指向緩沖區中的下一幀。當需要替換一頁時,操作系統掃描緩沖區,以查找使用位被置為0的一幀。每當遇到一個使用位為1的幀時,操作系統就將該位重新置為0;如果在這個過程開始時,緩沖區中所有幀的使用位均為0,則選擇遇到的第一個幀替換;如果所有幀的使用位均為1,則指針在緩沖區中完整地循環一周,把所有使用位都置為0,並且停留在最初的位置上,替換該幀中的頁。由於該算法循環地檢查各頁面的情況,故稱為CLOCK算法,又稱為最近未用(Not Recently Used, NRU)算法。
CLOCK算法的性能比較接近LRU,而通過增加使用的位數目,可以使得CLOCK算法更加高效。在使用位的基礎上再增加一個修改位,則得到改進型的CLOCK置換算法。這樣,每一幀都處於以下四種情況之一:
- 最近未被訪問,也未被修改(u=0, m=0)。
- 最近被訪問,但未被修改(u=1, m=0)。
- 最近未被訪問,但被修改(u=0, m=1)。
- 最近被訪問,被修改(u=1, m=1)。
算法執行如下操作步驟:
- 從指針的當前位置開始,掃描幀緩沖區。在這次掃描過程中,對使用位不做任何修改。選擇遇到的第一個幀(u=0, m=0)用於替換。
- 如果第1)步失敗,則重新掃描,查找(u=0, m=1)的幀。選擇遇到的第一個這樣的幀用於替換。在這個掃描過程中,對每個跳過的幀,把它的使用位設置成0。
- 如果第2)步失敗,指針將回到它的最初位置,並且集合中所有幀的使用位均為0。重復第1步,並且如果有必要,重復第2步。這樣將可以找到供替換的幀。
改進型的CLOCK算法優於簡單CLOCK算法之處在於替換時首選沒有變化的頁。由於修改過的頁在被替換之前必須寫回,因而這樣做會節省時間。
#include <iostream> #include<map> #include<set> #include <algorithm> #include<cstdio> #include<cstring> #include<cmath> #define N 200 using namespace std; int page[N];//頁面引用號 int block[N];//物理塊,內存 int dist[N][N];//表示第i次訪問內存的時候,內存中的頁面j 在以后被訪問的最小時間 int n;//頁面引用號個數 int m;//物理塊數目 int page_max;//最大頁面號 int pre[N];//page[i]在page中的索引 int opt(){//最佳頁面置換算法 int page_lack = 0; memset(pre, 0, sizeof(pre)); memset(dist, 0x3f, sizeof(dist)); memset(block, -1, sizeof(block)); for(int i=n; i>=1; --i){ for(int j=0; j<=page_max; ++j) if(pre[j]) dist[i][j] = pre[j] - i; pre[page[i]] = i; } for(int i=1; i<=n; ++i){//開始訪問頁面,初始是內存中沒有分頁 int j; int max_dist = 0, p; for(j=1; j<=m; ++j){ if(block[j] == -1){//改塊沒有放入頁面,則直接放入, 產生缺頁 block[j] = page[i]; cout<<"頁面"<<page[i]<<"不在內存,直接放入物理塊"<<j<<"中!"<<endl; page_lack++; break; } else if(block[j] == page[i])//頁面存在內存中 break; if(max_dist < dist[i][block[j]]){ max_dist = dist[i][block[j]];//說明block[j] 對應的頁面以后會長時間不會用到 p = j;//block[] 第j個頁面會被替換掉 } } if(j > m){//此時內存中不能在放入新的分頁,而且沒有找到page[i]對應的分頁,接下來進行頁面替換 cout<<"頁面"<<page[i]<<"不在內存,將物理塊"<<p<<"中的頁面" <<block[p]<<"替換掉!"<<endl; block[p] = page[i]; page_lack++; } cout<<endl<<"當前內存中頁面的情況:"<<endl; for(int k=1; k<=m; ++k)//內存中頁面加載情況 cout<<block[k]<<" "; cout<<endl<<endl; } return page_lack;//返回缺頁次數 } int lru(){//最近最久未使用算法和opt算法差不多,只不過是lru是向前看, opt是向后看 int page_lack = 0; memset(pre, 0, sizeof(pre)); memset(dist, 0x3f, sizeof(dist)); memset(block, -1, sizeof(block)); for(int i=1; i<=n; ++i){ for(int j=0; j<=page_max; ++j) if(pre[j]) dist[i][j] = i - pre[j]; pre[page[i]] = i; } for(int i=1; i<=n; ++i){//開始訪問頁面,初始是內存中沒有分頁 int j; int max_dist = 0, p; for(j=1; j<=m; ++j){ if(block[j] == -1){//改塊沒有放入頁面,則直接放入, 產生缺頁 block[j] = page[i]; cout<<"頁面"<<page[i]<<"不在內存,直接放入物理塊"<<j<<"中!"<<endl; page_lack++; break; } else if(block[j] == page[i])//頁面存在內存中 break; if(max_dist < dist[i][block[j]]){ max_dist = dist[i][block[j]];//說明block[j] 對應的頁面以后會長時間不會用到 p = j;//block[] 第j個頁面會被替換掉 } } if(j > m){//此時內存中不能在放入新的分頁,而且沒有找到page[i]對應的分頁,接下來進行頁面替換 cout<<"頁面"<<page[i]<<"不在內存,將物理塊"<<p<<"中的頁面" <<block[p]<<"替換掉!"<<endl; block[p] = page[i]; page_lack++; } cout<<endl<<"當前內存中頁面的情況:"<<endl; for(int k=1; k<=m; ++k)//內存中頁面加載情況 cout<<block[k]<<" "; cout<<endl<<endl; } return page_lack;//返回缺頁次數 } set<int>page_set; int fifo(){//先進先出頁面置換算法 int page_lack = 0; memset(block, -1, sizeof(block)); int index = 1; for(int i=1; i<=n; ++i){ if(index > m) index = 1; set<int>::iterator it; it = page_set.find(page[i]); if(it == page_set.end()){ if(block[index] != -1) page_set.erase(block[index]); page_set.insert(page[i]); block[index++] = page[i]; ++page_lack; } for(int k=1; k<=m; ++k) cout<<block[k]<<" "; cout<<endl; } return page_lack; } int nru[N];//表示 物理塊 i 最近時候被訪問過 int page_in_block[N];//頁面 i 在 block的下標索引 int clock(){ int index = 1; int page_lack = 0; memset(block, -1, sizeof(block)); for(int i=1; i<=n; ++i){ if(page_in_block[page[i]]){//如果page[i]已經在內存中 nru[page_in_block[page[i]]] = 1;//重新標記這個物理塊中的頁面被訪問過了 cout<<endl<<"第"<<i<<"次: 頁面"<<page[i]<<"已經存在物理塊"<< page_in_block[page[i]] << "中!"<<endl; } else { while(true){ if(index > m) index = 1; if(block[index] == -1) { nru[index] = 1; page_in_block[page[i]] = index; block[index++] = page[i]; ++page_lack; break; } if(block[index] == page[i]){ nru[index++] = 1; break; } else { if(nru[index] == 0){//替換該頁面 nru[index] = 1; page_in_block[block[index]] = 0; cout<<endl<<"第"<<i<<"次: 物理塊"<<index<<"中的頁面"<< block[index] <<"最近未被使用,將要被頁面"<<page[i]<<"替換!"<<endl; page_in_block[page[i]] = index; block[index++] = page[i]; ++page_lack; break; } else nru[index++] = 0; } } } for(int k=1; k<=m; ++k) cout<<block[k]<<" "; cout<<endl; } return page_lack; } int main(){ cin>>n>>m; for(int i=1; i<=n; ++i){ cin>>page[i]; page_max = max(page_max, page[i]) ; } cout<<"opt缺頁中斷次數:"<<opt()<<endl; cout<<"***********************************"<<endl; cout<<"lru缺頁中斷次數:"<<lru()<<endl; cout<<"***********************************"<<endl; cout<<"fifo缺頁中斷次數:"<<fifo()<<endl; cout<<"***********************************"<<endl; cout<<"clock缺頁中斷次數:"<<clock()<<endl; return 0; } /* 20 3 7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1 12 3 2 3 2 1 5 2 4 5 3 2 5 2 */
