1. 程式人生 > 其它 >《作業系統原理》實驗報告四

《作業系統原理》實驗報告四

技術標籤:實驗報告作業系統linux頁面替換演算法c語言

一、實驗目的
(1)理解頁面淘汰演算法原理,編寫程式演示頁面淘汰演算法。
(2)驗證 Linux 虛擬地址轉化為實體地址的機制
(3)理解和驗證程式執行區域性性的原理。

二、實驗內容
(1)在Windows環境下編寫一個程式,模擬實現OPT,FIFO,LRU等頁面淘汰演算法。可以使用陣列模擬記憶體,陣列中的元素模擬為指令或資料。寫不同方式的程式去 訪問陣列來模擬 CPU 訪問記憶體的情況。分析運算結果,在分配不同的物理塊情況下, 各演算法的缺頁情況有什麼規律?可以 srand( )和 rand( )等函式定義和產生“指令”
序列,然後將指令序列變換成相應的頁地址流,並針對不同的演算法計算出相應的

命中率。 例如,實驗中可以產生 320 條“指令”,每個“虛擬頁”存放 10 條指令。 程序分配的頁框是 4(可變,例如 32)。
(2)在 Linux 環境下,編寫一個小程式,獲取該程式中的某個變數的虛擬地址,
虛擬頁號,頁內偏移地址,物理頁框號,頁內偏移地址,實體地址,並將它們打 印出來。建議使用/proc/pid/pagemap 技術。
(3)在Windows 環境下,編寫一個函式(特點:比較耗時,比如大型的多維數
組讀寫),用不同的方法測試其所花費的時間。在不同環境下比較其時間是否不同,並分析其含義。測量時間的函式請 baidu。

三、實驗過程
(一)實驗步驟
1)模擬實現OPT,FIFO,LRU等頁面淘汰演算法(Windows)

1.定義記憶體幀,指令序列,引用串,記憶體容量,虛擬記憶體容量;

1.		#define MAX_VIR_SIZE 64  
2.	#define MAX_MEM_SIZE 32  
3.	#define VIR_NO 2  
4.	#define MEM_NO 3  
5.	#define FALSE -1  
6.	  
7.	int mem_frame[MAX_MEM_SIZE];        //記憶體幀   
8.	  
9.	int instruction[MAX_VIR_SIZE * 10];   //指令序列   
10.	int reference[MAX_VIR_SIZE * 10]
; //引用串 11. 12. int mem_size[MEM_NO] = { 4, 18, 32 }; //記憶體容量 13. int vir_size[VIR_NO] = { 32, 64 }; //虛存容量
  1. 初始化頁地址流,指令序列全為-1,引用串全為-1;
  2. 產生頁地址流
1.	int generate_page(int vsize)  
2.	{  
3.	    srand((unsigned)time(NULL)); /*播種子*/  
4.	    //產生指令序列   
5.	    for (int i = 0; i < vsize * 10; i += 5) {  
6.	        instruction[i] = rand() % (vsize * 10 - 1);  
7.	        instruction[i + 1] = instruction[i] + 1;  
8.	        instruction[i + 2] = rand() % instruction[i + 1];  
9.	        instruction[i + 3] = instruction[i + 2] + 1;  
10.	        instruction[i + 4] = rand() % (vsize * 10 - instruction[i + 3] - 2) + instruction[i + 3] + 1;  
11.	    }  
12.	  
13.	    //將指令序列變換為對應的頁地址流  
14.	    for (int i = 0; i < vsize * 10; i++)  
15.	        instruction[i] /= 10;  
16.	  
17.	    reference[0] = instruction[0];  
18.	  
19.	    int base = 0, j = 1;  
20.	    for (int i = 1; i < vsize * 10; i++) {  
21.	        if (instruction[i] != instruction[base]) {  
22.	            reference[j] = instruction[i];  
23.	            j++;  
24.	  
25.	            base = i;  
26.	        }  
27.	    }  
28.	    return j;    //返回引用串,即頁地址流的長度   
29.	}  
  1. 用不同方式的演算法去訪問陣列來模擬 CPU 訪問記憶體的情況,計算命中率
    OPT演算法 :淘汰以後不再需要或最遠的將來才會用到的頁面
1.	void OPT(int ref_len, int vsize)  
2.	{  
3.	    int mem_no, msize, i, find, miss, j, k;  
4.	    int first;  
5.	    int longest, rep;  
6.	    float rate = 0;  
7.	  
8.	    printf("OPT");  
9.	  
10.	    for (mem_no = 0; mem_no < MEM_NO; mem_no++) {  
11.	        miss = 0;  
12.	        ini_mem();                          //初始化記憶體工作區   
13.	        msize = mem_size[mem_no];  
14.	        for (i = 0; i < ref_len; i++) {  
15.	            find = search(msize, reference[i]);  
16.	            if (find != FALSE && mem_frame[find] == -1) {     //記憶體工作區未滿   
17.	                miss++;  
18.	                mem_frame[find] = reference[i];//頁置換   
19.	            }  
20.	            else if (find == FALSE) {        //記憶體工作區已滿且沒找到  
21.	                miss++;  
22.	                longest = 0;  
23.	                first = 0;  
24.	                for (j = 0; j < msize; j++) {  
25.	                    for (k = i + 1; k < ref_len; k++) {  
26.	                        if (mem_frame[j] == reference[k]) {  
27.	                            if (k > longest) {  
28.	                                longest = k;  
29.	                                rep = j;     //找到向前看距離最遠的一幀   
30.	                            }  
31.	                            break;  
32.	                        }  
33.	                        if (k == ref_len && first == 0) {  
34.	                            longest = k;  
35.	                            first = 1;  
36.	                            rep = j;  
37.	                        }  
38.	                    }  
39.	                }  
40.	                mem_frame[rep] = reference[i];//頁置換   
41.	            }  
42.	            else//找到頁對應的幀                
43.	        }  
44.	        rate = 1 - ((float)miss) / ((float)ref_len);          //計算命中率   
45.	        printf("\t\t%4d/%2d\t\t%.2f\n", msize, vsize, rate);  
46.	    }  
47.	} 

FIFO演算法:淘汰在記憶體中停留時間最長的頁面

1.	void FIFO(int ref_len, int vsize)  
2.	{  
3.	    printf("FIFO");  
4.	    for (int mem_no = 0; mem_no < MEM_NO; mem_no++) {  
5.	        int miss = 0;  
6.	        ini_mem(); //初始化記憶體工作區                            
7.	        int msize = mem_size[mem_no];  
8.	        int rep = 0;  
9.	        for (int i = 0; i < ref_len; i++) {  
10.	            int find = search(msize, reference[i]);  
11.	            //記憶體工作區未滿   
12.	            if (find != FALSE && mem_frame[find] == -1) {  
13.	                miss++;  
14.	                mem_frame[find] = reference[i];  
15.	            }  
16.	            //記憶體工作區已滿且沒找到  
17.	            else if (find == FALSE) {  
18.	                miss++;  
19.	                mem_frame[rep] = reference[i];//頁置換  
20.	                //下一個將要被置換的幀的位置   
21.	                rep = (rep + 1) % msize;  
22.	            }  
23.	            else//找到頁對應的幀  
24.	                ;  
25.	        }  
26.	        float rate = 1 - ((float)miss) / ((float)ref_len);           //計算命中率   
27.	        printf("\t\t%4d/%2d\t\t%.2f\n", msize, vsize, rate);  
28.	    }  
29.	}  

LRU演算法:淘汰最長時間未被使用的頁面

1.	void LRU(int ref_len, int vsize)  
2.	{  
3.	    int mem_no, msize, i, find, miss, j, k, longest, rep, dis;  
4.	    float rate = 0;  
5.	  
6.	    printf("LRU");  
7.	  
8.	    for (mem_no = 0; mem_no < MEM_NO; mem_no++) {  
9.	        miss = 0;  
10.	        ini_mem();  //初始化記憶體工作區                           
11.	        msize = mem_size[mem_no];  
12.	        for (i = 0; i < ref_len; i++) {  
13.	            find = search(msize, reference[i]);  
14.	            //記憶體工作區未滿   
15.	            if (find != FALSE && mem_frame[find] == -1) {  
16.	                miss++;  
17.	                mem_frame[find] = reference[i];//頁置換   
18.	            }  
19.	            //記憶體工作區已滿且沒找到  
20.	            else if (find == FALSE) {  
21.	                miss++;  
22.	                longest = 0;  
23.	                for (j = 0; j < msize; j++) {  
24.	                    for (k = i - 1; k >= 0; k--) {  
25.	                        if (mem_frame[j] == reference[k]) {  
26.	                            dis = i - k;  
27.	                            if (dis > longest) {  
28.	                                longest = dis;  
29.	                                rep = j;     //找到向後看距離最遠的一幀   
30.	                            }  
31.	                            break;  
32.	                        }  
33.	                    }  
34.	                }  
35.	                mem_frame[rep] = reference[i];//頁置換   
36.	            }  
37.	            else//找到頁對應的幀  
38.	                ;  
39.	        }  
40.	        rate = 1 - ((float)miss) / ((float)ref_len);          //計算命中率   
41.	        printf("\t\t%4d/%2d\t\t%.2f\n", msize, vsize, rate);  
42.	    }  
43.	}  

2)編寫程式獲取、列印該程式中的某個變數的資訊(Linux 環境)
重要函式和相關計算
getpagesize(); //呼叫此函式獲取系統設定的頁面大小
v_pageIndex = vaddr / pageSize; //計算此虛擬地址相對於0x0的經過的頁面數
v_offset = v_pageIndex * sizeof(uint64_t); //計算在/proc/pid/page_map檔案中的偏移量
page_offset = vaddr % pageSize; //計算虛擬地址在頁面中的偏移量
uint64_t phy_pageIndex = (((uint64_t)1 << 55) - 1) & item; //計算物理頁號,即取item的bit0-54
*paddr = (phy_pageIndex * pageSize) + page_offset; //再加上頁內偏移量就得到了實體地址

1.	void mem_addr(unsigned long vaddr, unsigned long *paddr)  
2.	{  
3.	    int pageSize = getpagesize(); //呼叫此函式獲取系統設定的頁面大小  
4.	    printf("頁面大小為%d\n", pageSize);    
5.	    unsigned long v_pageIndex = vaddr / pageSize; //計算此虛擬地址相對於0x0的經過的頁面數  
6.	    printf("頁面數為:%u\n", v_pageIndex);  
7.	    unsigned long v_offset = v_pageIndex * sizeof(uint64_t); //計算在/proc/pid/page_map檔案中的偏移量  
8.	    unsigned long page_offset = vaddr % pageSize;            //計算虛擬地址在頁面中的偏移量  
9.	    printf("偏移量為:%x\n", page_offset);  
10.	    uint64_t item = 0;                             //儲存對應項的值  
11.	    int fd = open("/proc/self/pagemap", O_RDONLY); //以只讀方式開啟/proc/pid/page_map  
12.	  
13.	    lseek(fd, v_offset, SEEK_SET);            //將遊標移動到相應位置,即對應項的起始地址且判斷是否移動失敗  
14.	    read(fd, &item, sizeof(uint64_t)) != sizeof(uint64_t); //讀取對應項的值,並存入item中,且判斷讀取資料位數是否正確    
15.	    if ((((uint64_t)1 << 63) & item) == 0) //判斷present是否為0  
16.	    {  
17.	        printf("page present is 0\n");  
18.	        return;  
19.	    }  
20.	    printf("物理頁號為%u\n", ((uint64_t)1 << 63) & item);  
21.	    uint64_t phy_pageIndex = (((uint64_t)1 << 55) - 1) & item; //計算物理頁號,即取item的bit0-54  
22.	    printf("物理頁號為%u\n", item);  
23.	    *paddr = (phy_pageIndex * pageSize) + page_offset; //再加上頁內偏移量就得到了實體地址  
24.	}  

3)用不同的方法測試函式所花費的時間(Windows)
1.所測試函式為迴圈1000000000的迴圈;
2.使用了四種不同的方法
clock_t clock(void);
返回程序啟動到呼叫函式時所經過的CPU時鐘計時單元(clock tick)數,在MSDN中稱之為掛鐘時間(wal-clock),以毫秒為單位。clock_t實際是個long長整型typedef long clock_t;
標頭檔案:#include <time.h>

1.	clock_t start = clock();  
2.	   test(); //待測試函式  
3.	   clock_t end = clock();  
4.	   double runtime = (double)(end - start) / CLOCKS_PER_SEC;  

高精度計時,以微秒為單位(1毫秒=1000微秒)
先看二個函式的定義BOOL QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);得到高精度計時器的值(如果存在這樣的計時器)。BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);返回硬體支援的高精度計數器的頻率(次每秒),返回0表示失敗。
再看看LARGE_INTEGER它其實是一個聯合體,可以得到__int64 QuadPart;也可以分別得到低32位DWORD LowPart和高32位的值LONG HighPart。在使用時,先使用QueryPerformanceFrequency()得到計數器的頻率,再計算二次呼叫QueryPerformanceCounter()所得的計時器值之差,用差去除以頻率就得到精確的計時了。
標頭檔案:直接使用#include <windows.h>就可以了。

1.	  LARGE_INTEGER BegainTime;  
2.	   LARGE_INTEGER EndTime;  
3.	   LARGE_INTEGER Frequency;  
4.	   QueryPerformanceFrequency(&Frequency);  
5.	   QueryPerformanceCounter(&BegainTime);  
6.	   test(); //待測試函式  
7.	   QueryPerformanceCounter(&EndTime);  
8.	   double runtime = (double)(EndTime.QuadPart - BegainTime.QuadPart) / 

Frequency.QuadPart;

time_t time(time_t *timer);
	返回以格林尼治時間(GMT)為標準,從1970年1月1日00:00:00到現在的此時此刻所經過的秒數。
	time_t實際是個long長整型typedef long time_t;

標頭檔案:#include <time.h>

1.	time_t timeBegin, timeEnd;  
2.	   timeBegin = time(NULL);  
3.	   test();  
4.	   timeEnd = time(NULL);  
5.	   double runtime = (double)(timeEnd - timeBegin); 

DWORD timeGetTime(VOID);
返回系統時間,以毫秒為單位。系統時間是從系統啟動到呼叫函式時所經過的毫秒數。注意,這個值是32位的,會在0到2^32之間迴圈,約49.71天。標頭檔案:#include <Mmsystem.h>
引用庫:#pragma comment(lib, “Winmm.lib”)

6.	DWORD  dwBegin, dwEnd;  
7.	   dwBegin = timeGetTime();  
8.	   test();  
9.	   dwEnd = timeGetTime();  
10.	   double runtime = (double)(dwEnd - dwBegin)/1000;  

(二)解決錯誤和優化
1.編譯錯誤。錯誤訊息:使用了未定義型別“type”,型別只有經過定義才能使用。若要解決該錯誤,請確保在引用型別前已對其進行了完全定義。有可能宣告一個指向已
2.宣告但未定義的型別的指標。但是 Visual C++ 不允許引用未定義的型別。
編譯錯誤。錯誤訊息:非法 break,break 僅在 do、for、while 或 switch 語句中合法。
3.執行時錯誤。由於在OPT、FIFO演算法函式內部未初始化記憶體難以得到預期結果,故需要在每個頁面淘汰演算法函式內加上ini_mem(),初始化記憶體工作區。
4.特殊語法錯誤, C26451 算術溢位: 使用 4 位元組值上的運算子 - ,然後將結果轉換到 8 位元組值。在呼叫運算子 - 之前將值強制轉換為寬型別可避免溢位(io.2)。
5.編譯錯誤,錯誤 LNK2019 無法解析的外部符號 [email protected],該符號在函式 “void __cdecl method_4(void)” ([email protected]@YAXXZ) 中被引用,原因是未加標頭檔案。
Windows系統API函式timeGetTime()、GetTickCount()及QueryPerformanceCounter() DWORD timeGetTime(VOID);返回系統時間,以毫秒為單位。系統時間是從系統啟動到呼叫函式時所經過的毫秒數。注意,這個值是32位的,會在0到2^32之間迴圈,約49.71天。必須加標頭檔案:#include <Mmsystem.h> 引用庫:#pragma comment(lib, “Winmm.lib”)
6. 特殊語法錯誤,linux c之提示format‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long int’ 。解決辦法md,m為指定的輸出欄位的寬度。如果資料的位數小於m,則左端補以空格,若大於m,則按實際位數輸出。%ld(%mld 也可),輸出長整型資料。最後 printf(“data is %ld”, data)解決。

四、實驗結果
1)模擬實現OPT,FIFO,LRU頁面淘汰演算法
在這裡插入圖片描述
從左到右以此為演算法、記憶體容量/虛擬記憶體容量、命中率。

2)編寫程式獲取、列印該程式中的某個變數的資訊
在這裡插入圖片描述

3)用不同的方法測試函式所花費的時間
在這裡插入圖片描述
四種方法用時如上。

五、體會
通過本次實驗,我對頁面淘汰演算法有了更深的瞭解,對每一種演算法的特點有了更好的掌握,學習到了如何驗證Linux虛擬地址轉化為實體地址的機制,通過在網上查閱也瞭解並實現了WINDOWS環境下計算程式執行消耗時間的幾種方法。
但通過本次實驗,我發現自己的動手寫程式碼的能力仍有不足,經常出錯,需要加強。