作業系統(十一) -- 記憶體的換入與換出及換出的基本演算法
文章目錄
前言
前面說過為了保證記憶體在使用者程式看起來是分段,而實際是分頁的效果,引入了虛擬記憶體。對於使用者來說,虛擬記憶體是一個完整的記憶體,使用者可以隨意使用該“記憶體”,假設為4G,那麼對於使用者來說就有4G的空間可以使用,即使實際記憶體只有2G甚至1G。那麼這是如何實現的呢?這就引出了換入和換出。
換入
假設虛擬記憶體4G,實際記憶體2G。首先程式訪問0 ~ 1G這段虛擬記憶體空間的時候,就將這一段記憶體與實體記憶體建立對映。接著如果程式訪問3~4G這段虛擬記憶體空間的時候,就將這段記憶體與實體記憶體建立對映,即只有在訪問的時候才對映。
換入概述
基本流程是:一個邏輯地址CS:IP,首先根據CS在段表中找到對應的基址,加上偏移得到虛擬地址:頁號+偏移。然後根據頁號在頁表中找到對應的頁框號,加上偏移得到實體地址。但是如果在頁表中找不到對應的頁號對應的頁框地址的話,就要從磁碟上將這一頁換入了。這個換入採用的是中斷的形式,如果load[addr]的時候,發現addr在頁表裡面沒有對應對映,那麼就將中斷向量暫存器的某一位置為一,說明有中斷產生。然後在中斷服務函式裡面將addr匯入到實體記憶體中。然後再次執行load[addr]這條語句。當然查表的操作是MMU做的。
一個實際系統的請求調頁
主要是看一下是如何將某頁從磁碟換入到記憶體的,從中斷服務函式開始。cpu知道有個中斷只會就去查詢這個中斷的中斷號,然後轉去執行該中斷服務程式。這些東西是在系統初始化的時候就做好了。
void trap_init(void)
{
set_trap_gate(14, &page_fault);
}
# define set_trap_gate(n, addr)\
_set_gate(&idt[n], 15, 0, addr);
//在linux/mm/page.s中
.globl _page_fault
xchgl %eax,(%esp)
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10, %edx
mov %dx, %ds
mov %dx, %es
mov %dx, %fs
movl %cr2, %edx
首先push一些資訊到堆疊中(包括錯誤碼),然後mov一些東西。畢竟從使用者態到核心態需要保留一下使用者態的情況。
pushl %edx
pushl %eax
testl $1, %eax // 測試標誌
jne 1f
call _do_no_page
jmp 2f
1: call _do_wp_page //保護
2: add $8, %esp
pop %fs
pop %es
pop %ds
pop %edx
pop %ecx
pop %eax
iret
一般push後面加了一個call呼叫c函式這種,前面push的都是壓入引數。調do_no_page();
//在linux/mm/memory.c中
void do_no_page(unsigned long error_code, unsigned long address)
{
address&=0xfffff000; //頁面地址
tmp=address–current->start_code; //頁面對應的偏移
if(!current->executable||tmp>=current->end_data) // 不是程式碼和資料
{
get_empty_page(address);
return;
}
page=get_free_page();
bread_page(page, current->executable->i_dev, nr);
put_page(page, address);
……
}
從這個函式名字也能看出 是當頁不存在的時候做的事情。
主要是這三句
page=get_free_page();
bread_page(page, current->executable->i_dev, nr);
put_page(page, address);
第一句得到一個空閒頁賦值給page;第二句磁碟裡面的頁讀到記憶體中;第三句建立對映。然後再次執行load[addr]; 再次執行load[addr] 這個要硬體設計好,因為一般情況下pc指標會加一。
//在linux/mm/memory.c中
unsigned long put_page(unsigned long page, //實體地址
unsigned long address)
{
unsigned long tmp, *page_table;
page_table=(unsigned long *)((address>>20)&ffc);
if((*page_table)&1)
page_table=(unsigned long*)(0xfffff000&*page_table);
else{
tmp=get_free_page();
*page_table=tmp|7;
page_table=(unsigned long*)tmp;}
page_table[(address>>12)&0x3ff] = page|7;
return page;
}
換出
前面說了頁換入,但是記憶體並不是無限的,有換入就會有換出,換出容易,但是找到哪一頁換出不容易,所以就出現了很多替換演算法。首先應該明白替換演算法的好壞是怎麼評判的,我們希望的應該是替換次數儘可能少,什麼演算法能滿足這點就是好的演算法。下面是幾種替換演算法:
FIFO(先入先出)
即每次缺頁的時候就替換掉最開始的那一頁,這種演算法肯定在這個方面肯定不是最好的演算法,因為它沒有任何機制保證替換次數儘可能少。
MIN演算法
選擇最遠將使用的頁淘汰,什麼意思呢?假設這個程式只分配了三個頁框,現在分別儲存了A、B、C三頁,假設程式後面使用的頁數依次是:C、B、A、D、C、B、A、B、C,使用C、B、A的時候不用換頁,當使用D的時候,先看一下後面最遠使用的頁是哪個,然後將它換掉;對於上例,最遠使用的頁是A,於是便將A換出,將D換入。可以看出這種方法是最好的方案,但是它實現不了。因為它需要事先知道程式後續要使用哪些頁,而這點是做不到的。
LRU演算法
唐太宗李世民在《舊唐書·魏徵傳》中說過:“以銅為鏡,可以正衣冠;以古為鏡,可以知興替;以人為鏡,可以明得失。”;其中“以古為鏡,可以知興替”說的就是可以通過歷史來預測未來(當然原句的翻譯是:用歷史當作鏡子,可以知道國家興亡的原因;)。雖然不能知曉未來,但是可以通過前面呼叫的頁的順序來推測未來哪些頁是常用的。理論基礎就是程式的空間區域性性。
LRU演算法就是這樣:選最近最長一段時間沒有使用的頁淘汰(最近最少使用)。
LRU演算法的準確實現:用時間戳
用時間戳來記錄每頁的訪問時間,比如某個程式訪問頁的順序為:A 、B 、C 、A 、B 、D 、A 、D 、B 、C 、B;該程式只有三個頁框。那麼使用時間戳
實現,如下圖:
第一次將A放入頁框中,並記錄當前時間為1;第二次將B放入頁框中,並記錄當前時間為2;第三次將C放入頁框中,並記錄當前時間為3;第四次又是訪問A頁,更新A頁訪問時間,第五次訪問B頁,更新B頁訪問時間;第六次訪問D頁,不存在,那麼就在A、B、C頁中選擇一個最早使用的也就是數字最小的替換,即C頁。這種方式理論上是可行的,但是每次地址訪問都要修改時間戳,需要維護一個全域性時鐘,需要找到最小值……實現代價太大了。
LRU演算法的準確時間:用頁碼棧
還是上面的例子,
每次地址訪問都需要修改棧,實現代價也比較大。其實LRU準確實現用的少,因此維護代價太高了。從上面兩種演算法可以看出,主要是在維護時間戳上面花費的時間比較多,但是能不能將LRU演算法做一個優化呢?或者說近似實現?
clock演算法
LRU的近似實現 - 將時間計數變為是和否
二次機會演算法
具體操作是這樣的,每頁增加一個引用位( R ),每一次訪問該頁時,就將該位置為1。當發生缺頁時用一個指標檢視每一頁的引用位,如果是1則將其置為0,如果是0就直接淘汰。如下圖:
用這種方式實現就不必維護時間戳了,提高了記憶體使用效率。但是這種演算法真的可以使用嗎?在實際中,缺頁的情況肯定不會很多;如果缺頁很多了,說明記憶體太小了或者演算法不行。那當這個演算法缺頁很少的情況會怎麼樣呢?假設初始狀態所有的頁的引用位都是1,這是很有可能的,因為缺頁情況少,程式使用的一直是記憶體裡面存在的頁。那麼當發生缺頁時,指標轉一圈之後將所有的頁的引用位都置為0,沒找到能替換的,繼續轉,這時候發現最開始那個頁引用位為0,將其置換出去,指標後移;然後又一段時間沒有發生缺頁,所有頁的引用位都為1,當發生缺頁之後,又會將這一輪最開始的那一頁置換出去;然後指標後移,一段時間之後發生缺頁,又會將這一輪最開始的那一頁置換出去。wait…,這不是變成FIFO了嗎。
究其原因還是因為指標掃描的時間太長了,也就是記錄了太長的歷史資訊。其中一個解決辦法是再加一個指標用來清除每一頁的引用位,可以放在時鐘中斷裡面,定時清除;這個時間可以事先設定好,也可以在軟體裡面實現。
給程式分配多少個頁框
嗯嗯,現在置換策略有了,但是還有一個問題需要解決:給程式分配多少個頁框。
如果分配的多了,那麼請求調頁導致的記憶體高效利用就沒有了。而且記憶體就那麼大,如果每一個程式分配很多的話,跑的程式數量就少了。如果分配的少,系統內程式增多,每個程式的缺頁率增大,當缺頁率大到一定程度,程式就會總等待調頁完成,導致cpu利用率降低。如下圖
中間那個急劇下降的狀態稱為顛簸。一種可以採用的方法是,先給程式分配一定數量的頁框,如果增加頁框能增加cpu利用率,就慢慢增加,如果導致cpu利用率減少,就降低頁框分配。當然實際情況下每個程式對應的頁框數量肯定是得動態調整的。
參考資料
哈工大李志軍作業系統
相關文章
- 記憶體瘋狂換頁!CPU怒批作業系統記憶體作業系統
- 【作業系統】頁面置換演算法(最佳置換演算法)(C語言實現)作業系統演算法C語言
- 淺談Linux作業系統的Swap交換區Linux作業系統
- 作業系統(八) -- 記憶體的分段與分頁作業系統記憶體
- 【換行符】Windows、Unix、Mac不同作業系統的回車符 和換行符 WindowsMac作業系統
- 論HPUX系統交換與偽交換UX
- 作業系統-記憶體管理作業系統記憶體
- 作業系統——記憶體管理作業系統記憶體
- 計算機作業系統——虛擬記憶體與實體記憶體計算機作業系統記憶體
- linux系統swappiness引數在記憶體與交換分割槽間最佳化LinuxAPP記憶體
- 核心替換國產作業系統是否可行?作業系統
- 《作業系統真象還原》核心記憶體分佈與載入作業系統記憶體
- 【作業系統】記憶體管理概述作業系統記憶體
- 作業系統的記憶體管理你知道嗎作業系統記憶體
- Dell R720 記憶體糾錯比率超限 更換記憶體引起的故障記憶體
- PCI-5565系列反射記憶體卡 反射記憶體交換機反射記憶體
- 作業系統-記憶體、檔案管理作業系統記憶體
- Linux作業系統記憶體淺析Linux作業系統記憶體
- 阿里雲伺服器可以更換作業系統嗎?阿里伺服器作業系統
- 如何更換win10系統_更換win10系統的方法Win10
- MySQL記憶體管理,記憶體分配器和作業系統MySql記憶體作業系統
- MRAM獨特功能替換現有記憶體記憶體
- C# 的輸入輸出,(同一行/換行)C#
- 作業系統——記憶體管理學習筆記作業系統記憶體筆記
- Ubuntu作業系統的基本配置與使用Ubuntu作業系統
- 資料的交換輸出 hd 2016
- 02-Tcl輸出、賦值與替換賦值
- 切換Windows的系統語言Windows
- Android-Fragment 切換造成記憶體溢位,導致記憶體增長AndroidFragment記憶體溢位
- C#--得到物件在記憶體中的大小和把記憶體中的位元組轉換為字串C#物件記憶體字串
- Linux作業系統中分割槽格式轉換LVM格式及多磁碟建立PV、VG、LVLinux作業系統LVM
- python輸出如何不換行?Python
- python輸出時如何換行Python
- Python pytorch 座標系變換與維度轉換PythonPyTorch
- 交換機的基本原理
- Qt - 座標系及轉換QT
- 教你如何進行 linux swap 交換記憶體擴容Linux記憶體
- 深入理解作業系統中程式與執行緒的區別及切換機制(上)作業系統執行緒