模擬實現FIFO,LRU,OPT內存淘汰策略
策略簡介
在頁式存儲管理中,內存以頁框爲單位分配使用。程序運行時以頁爲單位裝入內存,只把當前需要的若干頁裝入內存,且這些頁佔用的頁框不必相鄰。程序運行需要新的頁時,按需從外存上調入內存。但當物理內存中的頁不夠用的時候,要裝入新的頁就必須淘汰物理內存中某些頁框的內容.
FIFO:first input first output的縮寫,很容易理解這種策略的思想.
這種算法的實質是:總是選擇在主存中停留時間最長(即最老)的一頁置換,即先進入內存的頁,先退出內存.
理由是:最早調入內存的頁,其不再被使用的可能性比剛調入內存的可能性大.
建立一個FIFO隊列,收容所有在內存中的頁。被置換頁面總是在隊列頭上進行。當一個頁面被放入內存時,就把它插在隊尾上.
優點:實現簡單:頁面按進入內存的時間排序,淘汰隊頭頁面.LRU:Least Recently Used,最久未使用.
LRU算法是與每個頁面最後使用的時間有關的。當必須置換一個頁面時,LRU算法選擇過去一段時間裏最久未被使用的頁面。
LRU算法是經常採用的頁面置換算法,,並被認爲是相當好的,但是存在如何實現它的問題.1.硬件方法:
頁面設置一個移位寄存器R。每當頁面被訪問則將其重置1.
週期性地(週期很短)將所有頁面的R左移一位(右邊補0)
當需要淘汰頁面時選擇R值最大的頁.
R的位數越多且移位週期越小就越精確,但硬件成本高.2.計數器:
保存時間:最簡單的情況是使每個頁表項對應一個使用時間字段,並給CPU增加一個邏輯時鐘或計數器。每次存儲訪問,該時鐘都加1。每當訪問一個頁面時,時鐘寄存器的內容就被複制到相應頁表項的使用時間字段中。這樣我們就可以始終保留着每個頁面最後訪問的“時間”。
在置換頁面時,選擇該時間值最小的頁面。
這樣做,不僅要查頁表,而且當頁表改變時(因CPU調度)要維護這個頁表中的時間,還要考慮到時鐘值溢出的問題。3.棧:
用一個棧保留頁號。
每當訪問一個頁面時,就把它從棧中取出放在棧頂上。這樣一來,棧頂總是放有目前使用最多的頁,而棧底放着目前最少使用的頁。
由於要從棧的中間移走一項,所以要用具有頭尾指針的雙向鏈連起來。
在最壞的情況下,移走一頁並把它放在棧頂上需要改動6個指針。每次修改都要有開銷,但需要置換哪個頁面卻可直接得到,用不着查找,因爲尾指針指向棧底,其中有被置換頁。OPT:optimal
該策略的思想是,淘汰不再需要或最遠的將來纔會使用到的頁面。
特點:理論上最佳,但實踐中無法實現。因爲當缺頁發生時,操作系統無法知道各個頁面下一次是在什麼時候被訪問
雖然這個算法不可能實現,但是最佳頁面置換算法可以用於對可實現算法的性能進行衡量比較。
模擬實現(C語言)
- 這裏我們僅僅用簡單的數組實現。假設一虛擬頁中存放10條指令,虛擬頁數爲32,物理頁數4-32
- 定義指令集合結構體
typedef struct commandList{
int command;//10條指令的集合,代表所在的虛擬頁頁號
int offset;//指令偏移地址
int lastTimeHit;//最後一次命中的時間
int enterNum;//模擬進入的順序
};
- 定義物理內存頁,這裏簡單的寫入加載虛擬頁的頁號
typedef struct physicalPage{
int command;//裝入的指令集,-1代表沒有裝入
};
- 虛擬頁中存放些什麼數據呢?這裏我們用隨機數模擬虛擬頁中的指令.
其中COMMAND_NUM代表指令個數,這裏將虛擬頁裝滿取320
void initVirtualPage(int * vp){
srand((unsigned)time(NULL));
for (int i = 0; i < COMMAND_NUM; i++)
{
vp[i] = rand()% COMMAND_NUM;
// printf("%d ", vp[i]);
}
}
- 定義全局變量
//物理頁數
int physicalPageNum;
//虛擬頁,int存儲的是虛擬頁中的指令,該指令隨機產生
int* mVirtualPages;
//物理頁
physicalPage* mPhysicalPage;
//指令集
commandList* mCommandList;
//缺頁次數
int disaffect = 0;
//缺頁率
float disaffectRate;
- 初始化變量
//初始化指令集合
void initCommandList(commandList* cl){
for (int i = 0; i < COMMAND_NUM; i++)
{
//方便起見,指令集和下標即爲該指令
(cl + i)->lastTimeHit = -1;
(cl + i)->command = i / PAGE_CONTENT;
(cl + i)->offset = i % PAGE_CONTENT;
(cl + i)->enterNum = 0;
}
}
//初始化物理內存頁
void initPhysicalPage(physicalPage* pp){
for (int i = 0; i < physicalPageNum; i++)
{
(pp+i)->command = -1;
}
}
- 實現核心判斷:是否命中(是否存在物理頁中)
這裏用簡單的循環判斷,若存在則返回所在位置,否則返回-1
int isExistInPhysicalPage(int command, physicalPage*pp, int length){
for (int i = 0; i < length; i++)
{
if ((pp + i)->command == command) return i;
}
return FALSE;
}
- FIFO策略淘汰的頁號.
int whichIsToSelectFIFO(physicalPage*pp, int length){
int returnValue = 0;
int target = (mCommandList + pp->command*PAGE_CONTENT)->enterNum;
for (int i = 0; i < length; i++)
{
//若物理內存爲空,則直接返回該位置
if ((pp + i)->command == -1) return i;
//若不爲空,則返回最先進入內存的頁面
else
{
int tmp = (mCommandList + (pp + i)->command*PAGE_CONTENT)->enterNum;
if (tmp < target){
target = tmp;
returnValue = i;
}
}
}
return returnValue;
}
這裏將commandList中的PAGE_CONTENT倍數的指令記錄最近訪問時間
(因爲mPAGE_CONTENT到(m+1)PAGE_CONTENT-1的指令要運行,都要將m虛擬頁加載到內存中),即記錄某虛擬頁請求分配內存並進入內存的最初時間.
模擬FIFO訪問內存
void FIFO(){
for (int i = 0; i < COMMAND_NUM; i++)
{
int command =* (mVirtualPages+i)/PAGE_CONTENT;
#ifdef DEBUG
printf_s("---------------------------------------------------------------------------\n");
printf_s("指令%d請求分配內存,", *(mVirtualPages + i));
#endif
//是否存在物理頁?若存在,返回所在位置,否則返回-1
int index = isExistInPhysicalPage(command, mPhysicalPage, physicalPageNum);
// 若不存在
if (index == FALSE){
disaffect++;
/*模擬執行裝入內存*/
int targetIndex = whichIsToSelectFIFO(mPhysicalPage, physicalPageNum);
//寫入
(mPhysicalPage + targetIndex)->command = command;
#ifdef DEBUG
printf_s("物理內存%d上的指令被淘汰,取而代之的是%d\n", targetIndex, *(mVirtualPages + i));
#endif
//更新使用情況
(mCommandList + command*PAGE_CONTENT)->enterNum = i+1;
(mCommandList + command*PAGE_CONTENT)->lastTimeHit = i;
}
//否則直接更新
else
{
#ifdef DEBUG
printf_s("該指令已經存在內存中!\n");
#endif
(mCommandList + command*PAGE_CONTENT)->lastTimeHit = i;
}
#ifdef DEBUG
printf_s("內存使用情況:\n");
//輸出內存使用情況
for (int k = 0; k < physicalPageNum; k++)
{
printf_s(" %d ", (mPhysicalPage + k)->command);
}
printf_s("\n");
#endif
}
}
for循環模擬不斷從虛擬頁取指令,取出指令賦給command(代表虛擬頁號),判斷該虛擬頁是否存在物理頁中,若存在,返回位置,並更新該虛擬頁最近命中的時間,若不存在,則根據FIFO策略找出被淘汰的內存並更新
- LRU淘汰策略
int whichIsToSelectLRU(physicalPage*pp, int length){
int returnValue = 0;
int target = (mCommandList + pp->command*PAGE_CONTENT)->lastTimeHit;
for (int i = 0; i < length; i++)
{
//若物理內存爲空,則直接返回該位置
if ((pp + i)->command == -1) return i;
//若不爲空,則返回最先進入內存的頁面
else
{
int tmp = (mCommandList + (pp + i)->command*PAGE_CONTENT)->lastTimeHit;
if (tmp < target){
target = tmp;
returnValue = i;
}
}
}
return returnValue;
}
和FIFO一樣,用PAGE_CONTENT倍數的指令記錄虛擬頁訪問內存的信息,用指標lastTimeHit記錄最近一次被命中的時間。lastTimeHit最小,則表明中間不使用的時間越長。
模擬LRU訪問內存,模擬過程同上
void LRU(){
for (int i = 0; i < COMMAND_NUM; i++)
{
int command = *(mVirtualPages + i)/PAGE_CONTENT;
#ifdef DEBUG
printf_s("---------------------------------------------------------------------------\n");
printf_s("指令%d請求分配內存,", *(mVirtualPages+i));
#endif
//是否存在物理頁?若存在,返回所在位置,否則返回-1
int index = isExistInPhysicalPage(command, mPhysicalPage, physicalPageNum);
// 若不存在
if (index == FALSE){
disaffect++;
/*模擬執行裝入內存*/
int targetIndex = whichIsToSelectLRU(mPhysicalPage, physicalPageNum);
//寫入
(mPhysicalPage + targetIndex)->command = command;
#ifdef DEBUG
printf_s("物理內存%d上的指令被淘汰,取而代之的是%d\n", targetIndex, *(mVirtualPages + i));
#endif
//更新使用情況
//
(mCommandList + command*PAGE_CONTENT)->enterNum = i + 1;
(mCommandList + command*PAGE_CONTENT)->lastTimeHit = i;
}
//存在內存中直接更新
else
{
#ifdef DEBUG
printf_s("該指令已經存在內存中!\n");
#endif
(mCommandList + command*PAGE_CONTENT)->lastTimeHit = i;
}
#ifdef DEBUG
printf_s("內存使用情況:\n");
//輸出內存使用情況
for (int k = 0; k < physicalPageNum; k++)
{
printf_s(" %d ", (mPhysicalPage + k)->command);
}
printf_s("\n");
#endif
}
}
- OPT淘汰策略
int whichIsToSelectOPT(physicalPage*pp, int length,int startNum){
int returnValue = 0;
int target=0;
int tmp=-1;
for (int i = startNum; i < COMMAND_NUM; i++){
//若找到與當前內存相同的最近的指令
if (pp->command == *(mVirtualPages + i) / PAGE_CONTENT) {
target = i - startNum + 1;
break;
}
else
{
//否則是最遠距離
target = COMMAND_NUM - startNum + 1;
}
}
//第一個已經考慮過了
for (int i = 1; i < length; i++)
{
//若物理內存爲空,則直接返回該位置
if ((pp + i)->command == -1) return i;
//若不爲空,則返回最先進入內存的頁面
else
{
for (int k= startNum; k< COMMAND_NUM; k++){
//若找到與當前內存相同的最近的指令
if ((pp+i)->command == *(mVirtualPages + k) / PAGE_CONTENT) {
tmp = k- startNum + 1;
break;
}
else
{
//否則最遠距離
tmp = COMMAND_NUM - startNum + 1;
}
}
if (tmp >target){
target = tmp;
returnValue = i;
}
}
}
return returnValue;
}
上面的代碼片段中,從startNum開始考慮最遠的,或者是不需要的頁面
模擬OPT策略
void OPT(){
for (int i = 0; i < COMMAND_NUM; i++)
{
int command = *(mVirtualPages + i) / PAGE_CONTENT;
#ifdef DEBUG
printf_s("---------------------------------------------------------------------------\n");
printf_s("指令%d請求分配內存,", *(mVirtualPages + i));
#endif
//是否存在物理頁?若存在,返回所在位置,否則返回-1
int index = isExistInPhysicalPage(command, mPhysicalPage, physicalPageNum);
// 若不存在
if (index == FALSE){
//缺頁
disaffect++;
int targetIndex;
/*模擬執行裝入內存*/
//若已經到達數列末尾
if (i + 1 == COMMAND_NUM) targetIndex = 0;
else targetIndex= whichIsToSelectOPT(mPhysicalPage, physicalPageNum,i+1);
//寫入
(mPhysicalPage + targetIndex)->command = command;
#ifdef DEBUG
printf_s("物理內存%d上的指令被淘汰,取而代之的是%d\n", targetIndex, *(mVirtualPages + i));
#endif
//更新使用情況
(mCommandList + command)->enterNum = i + 1;
(mCommandList + command)->lastTimeHit = i;
}
//否則直接更新
else
{
#ifdef DEBUG
printf_s("該指令已經存在內存中!\n");
#endif
(mCommandList + command)->lastTimeHit = i;
}
#ifdef DEBUG
printf_s("內存使用情況:\n");
//輸出內存使用情況
for (int k = 0; k < physicalPageNum; k++)
{
printf_s(" %d ", (mPhysicalPage + k)->command);
}
printf_s("\n");
#endif
}
}
上面的代碼中,有一個小細節,當i到達指令末尾且未命中時,直接替代0號物理內存頁
- 有了上面的函數後,就能計算各策略的命中率了,主函數給出如下
int _tmain(int argc, _TCHAR* argv[])
{
while (true)
{
while (true)
{
puts("請輸入物理頁數:");
scanf_s("%d", &physicalPageNum);
//初始化全局變量
if (physicalPageNum < 4 || physicalPageNum>32)
{
puts("輸入錯誤,請輸入大於4小於32的整數");
}
else
{
break;
}
}
mPhysicalPage = (physicalPage*)malloc(physicalPageNum*sizeof(physicalPage));
//這裏模擬32個不同的指令集合
mCommandList = (commandList*)malloc(COMMAND_NUM*sizeof(commandList));
mVirtualPages = (int*)malloc(COMMAND_NUM*sizeof(int));
initCommandList(mCommandList);
initPhysicalPage(mPhysicalPage);
initVirtualPage(mVirtualPages);
disaffect = 0;
printf_s("FIFO策略:\n");
FIFO();
disaffectRate = (float)disaffect / (float)COMMAND_NUM;
printf_s("命中率爲%f\n", 1 - disaffectRate);
//重置某些數據
initCommandList(mCommandList);
initPhysicalPage(mPhysicalPage);
disaffect = 0;
printf_s("\n\nLRU策略:\n");
LRU();
disaffectRate = (float)disaffect / (float)COMMAND_NUM;
printf_s("命中率爲%f\n", 1 - disaffectRate);
//重置某些數據
initCommandList(mCommandList);
initPhysicalPage(mPhysicalPage);
disaffect = 0;
printf_s("\n\nOPT策略:\n");
OPT();
disaffectRate = (float)disaffect / (float)COMMAND_NUM;
printf_s("命中率爲%f\n", 1 - disaffectRate);
}
system("pause");
return 0;
}
運行截圖: