《作業系統原理》實驗報告四
一、實驗目的
(1)理解頁面淘汰演算法原理,編寫程式演示頁面淘汰演算法。
(2)驗證 Linux 虛擬地址轉化為實體地址的機制
(3)理解和驗證程式執行區域性性的原理。
二、實驗內容
(1)在Windows環境下編寫一個程式,模擬實現OPT,FIFO,LRU等頁面淘汰演算法。可以使用陣列模擬記憶體,陣列中的元素模擬為指令或資料。寫不同方式的程式去 訪問陣列來模擬 CPU 訪問記憶體的情況。分析運算結果,在分配不同的物理塊情況下, 各演算法的缺頁情況有什麼規律?可以 srand( )和 rand( )等函式定義和產生“指令”
序列,然後將指令序列變換成相應的頁地址流,並針對不同的演算法計算出相應的
(2)在 Linux 環境下,編寫一個小程式,獲取該程式中的某個變數的虛擬地址,
虛擬頁號,頁內偏移地址,物理頁框號,頁內偏移地址,實體地址,並將它們打 印出來。建議使用/proc/pid/pagemap 技術。
(3)在Windows 環境下,編寫一個函式(特點:比較耗時,比如大型的多維數
組讀寫),用不同的方法測試其所花費的時間。在不同環境下比較其時間是否不同,並分析其含義。測量時間的函式請 baidu。
三、實驗過程
(一)實驗步驟
1)模擬實現OPT,FIFO,LRU等頁面淘汰演算法(Windows)
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. 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. }
- 用不同方式的演算法去訪問陣列來模擬 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環境下計算程式執行消耗時間的幾種方法。
但通過本次實驗,我發現自己的動手寫程式碼的能力仍有不足,經常出錯,需要加強。