【HUST】网安|操作系统实验|实验三 内存管理
文章目錄
- 任務
- 任務1 Win/Linux編寫二維數組遍歷程序,理解局部性的原理。
- 1. 提示
- 2. 任務代碼
- 3. 結果及說明
- 任務2 Windows/Linux模擬實現OPT和LRU淘汰算法。
- 1. 提示
- 2. 任務代碼
- 3. 結果及說明
- 任務3 Linux下利用/proc/pid/pagemap技術計算某個變量或函數虛擬地址對應的物理地址等信息。
- 1. 提示
- 2. 任務代碼
- 3. 結果及說明
任務
一、實驗目的
1)理解頁面淘汰算法原理,編寫程序演示頁面淘汰算法。
2)驗證Linux虛擬地址轉化為物理地址的機制
3)理解和驗證程序運行局部性的原理。
二、實驗內容
1)Win/Linux編寫二維數組遍歷程序,理解局部性的原理。
2)Windows/Linux模擬實現OPT和LRU等淘汰算法。
3)Linux下利用/proc/pid/pagemap技術計算某個變量或函數虛擬地址對應的物理地址等信息。
任務1 Win/Linux編寫二維數組遍歷程序,理解局部性的原理。
1. 提示
提示1:數組盡可能開大一些,并嘗試改變數組大小,改變內外重循環次序。例如從[2048] X[2048]變化到[10240] x [20480],觀察它們的遍歷效率。
提示2:在任務管理中觀察它們的缺頁次數。
2. 任務代碼
#include <iostream>
#include <ctime>
#include <Windows.h>
using namespace std;
int MyArray[10240][20480];
const string str[4] = { "局部性好","局部性差","2048*2048","10240*20480" };
int main() {
DWORD pid = GetCurrentProcessId();
cout << "當前進程的PID是"<<pid<<",您可以根據進程號在任務管理器查看進程。" << endl;
cout << "模式(1. 局部性好;2. 局部性差)" << endl;
cout << "數組大小(1. 2048*2048;2. 10240*20480)" << endl << endl;
int op1 = 0, op2 = 0;
while (1)
{
cout << "******* INPUT 2 INT TO USE *******" << endl;
cin >> op1 >> op2;
cout << "您選擇了組合("<<str[op1-1]<<","<<str[op2+1]<<"),運行時間為:";
int sizex=1, sizey=1;
if (op2 == 1)
{
sizex = sizey = 2048;
}
else if (op2 == 2)
{
if (op1 == 1) {
sizex = 10240;
sizey = 20480;
}
else if (op1 == 2)
{
sizey = 10240;
sizex = 20480;
}
}
clock_t start, end;
start = clock();
for (int i = 0; i < sizex; i++)
for (int j = 0; j < sizey; j++) {
if (op1 == 1) {
MyArray[i][j] = 0;
}
else if(op1==2){
MyArray[j][i] = 0;
}
}
end = clock();
double time1 = (double)(end - start) / CLOCKS_PER_SEC;
cout << time1 << "秒" << endl << endl;
}
return 0;
}
3. 結果及說明
1)任務平臺:Windows 10, Visual Studio 2019。
2)如何觀察缺頁次數:在任務管理器-詳細信息中,右鍵列名,“選擇列”,添加“頁面錯誤”。
3)實驗變量:
①程序局部性;②頁面是否被調入內存。
4)實驗前猜測:
①訪問數組時會將部分頁面調到內存中,占用內存增加、頁面錯誤增加。
②局部性差的情況需要消耗更多的時間。
③局部性好壞不影響缺頁次數。
④頁面被調入內存后,再次訪問,效率會提高。
5)實驗過程:
啟動程序,內存占用384K,頁面錯誤是1498個。
先試小數組,局部性好的遍歷方式,耗時0.018秒。頁面錯誤變成7672,內存占用增加為24932K。猜測①得證。
由于剛才訪問時數組的內容尚未調入內存,為保證變量唯一,再次運行(小數組,局部性好)。雖然時間減少了,但是由于數組較小,隨機性較大,不能證明猜測④。
接著再運行(小數組,局部性差)。
可見局部性差時時間增加,猜測②得證。
并且不論局部性好壞,頁面錯誤和活動的內存均未發生改變,因此局部性好壞不影響頁面錯誤。猜測③得證。由于小數組的時間的偶然性較大,使用大數組重復運行,可見調入內存后,再次訪問的速度明顯加快。下圖1是未調入時,下圖2、3是調入時,猜測④得證。
任務2 Windows/Linux模擬實現OPT和LRU淘汰算法。
1. 提示
[以下模擬過程僅供參考,不是唯一方案!百度參考其他方案!]
提示1:程序指令執行過程采用遍歷數組的操作來模擬;
提示2:用1個較大數組A(例如2400個元素)模擬進程,數組里面放的是隨機數,每個元素被訪問時就使用printf將其打印出來,模仿指令的執行。數組A的大小必須是設定的頁大小(例如10個元素或16個元素等等)的整數倍。
提示3:用3-8個小數組(例如數組B,數組C,數組D等)模擬分得的頁框。小數組的大小等于頁大小(例如10條指令的大小,即10的元素)。小數組里面復制的是大數組里面的相應頁面的內容(自己另外構建頁表,描述大數組的頁與小數組序號的關系)。
提示4:利用不同的次序訪問數組A,次序可以是:順序,跳轉,分支,循環,或隨機,自己構建訪問次序。不同的次序也一定程度反映程序局部性。
提示5:大數組的訪問次序可以用 rand( )函數定義,模擬產生指令訪問序列,對應到大數組A的訪問次序。然后將指令序列變換成相應的頁地址流,并針對不同的頁面淘汰算法統計“缺頁”情況。缺頁即對應的“頁面”沒有裝到小數組中(例如數組B,數組C,數組D等)。
提示6:實驗中頁面大小,頁框數,訪問次序,淘汰算法都應可調。
提示7:至少實現2個淘汰算法。
為了方便瀏覽提示,我做了一點顏色標記,如下。
每種淘汰模式:
- Start
- 遍歷查找是否在某一頁框中。
- 如果不在,則轉5。
- 如果在,則訪問它,然后轉8。
- 判斷頁表是否已滿。
- 如果未滿,則直接添加,然后訪問它,然后轉8。
- 如果已滿,則應用淘汰算法確定需要淘汰的一頁,然后添加新元素,再訪問它,轉8。
- END
淘汰算法:
- FIFO(First Input First Output):先進先出。算法實現思路:淘汰頁表中的第一頁。
- OPT(OPTimal Replacement):其所選擇的被淘汰的頁面將是以后永不使用的,或是在最長(未來)時間內不再被訪問的頁面。算法實現思路:遍歷剩下的元素,當遇到在當前頁的元素時停止遍歷,找到停止最晚的頁用于淘汰(如果始終沒有遇到則說明該頁永不使用,可直接被淘汰)。
- LRU(Least Recently Used):根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是“如果數據最近被訪問過,那么將來被訪問的幾率也更高”。算法實現思路:每一頁都設置一個int類型的訪問記錄,當被訪問時清零,沒訪問時++。
- LFU(Least Frequently Used):根據數據的歷史訪問頻率來淘汰數據,其核心思想是“如果數據過去被訪問多次,那么將來被訪問的頻率也更高”。算法實現思路:每一頁都設置一個int類型的訪問頻率記錄,當被訪問時++,沒訪問時不做處理。
2. 任務代碼
很長,不貼了
#include<iostream>
#include<list>
#include<map>
#include<vector>
#pragma warning(disable:4996)
using namespace std;
#define pageTotalSize 2400
#define seqLength 800
int pageSize = 10; //頁面大小
int pageFrameNum = 3; //頁框數
int visitOrder = 0; //訪問次序
int eliminateAlgorithm = 0; //淘汰算法
string eliminateStr[4] = {"FIFO","OPT","LRU","LFU"};
string visitStr[4] = { "順序","跳轉","循環","隨機"};
int page[pageTotalSize];
vector<int>pageFrame[20]; //頁框數最大20
int visitSeq[seqLength];
void FIFO(int seq[seqLength], bool showDetail); /*其所選擇的被淘汰的頁面是最早的一頁*/
void OPT(int seq[seqLength], bool showDetail); /*其所選擇的被淘汰的頁面將是以后永不使用的,或是在最長(未來)時間內不再被訪問的頁面*/
void LRU(int seq[seqLength], bool showDetail); /*根據數據的歷史訪問記錄來進行淘汰數據*/
void LFU(int seq[seqLength], bool showDetail); /*根據數據的歷史訪問頻率來進行淘汰數據*/
void Order(int visitOrder); /*生成指定次序的訪問數組*/
3. 結果及說明
測試了很多次,數據少有時候跑出來的結果LRU比OPT還好,它真的是一個很優秀的算法。
自選模式的:
任務3 Linux下利用/proc/pid/pagemap技術計算某個變量或函數虛擬地址對應的物理地址等信息。
1. 提示
提示1:Linux的/proc/pid/pagemap文件允許用戶查看當前進程虛擬頁的物理地址等相關信息。
- 每個虛擬頁包含一個64位的值
- 注意分析64位的信息
提示2:獲取當前進程的pagemap文件的全名
提示3:可以輸出進程中某個或多個全局變量或自定義函數的虛擬地址,所在頁號,所在物理頁框號,物理地址等信息。
思考:(1)如何通過擴充實驗展示不同進程的同一虛擬地址對應不同的物理地址。(2)如何通過擴充實驗驗證不同進程的共享庫具有同一的物理地址。
2. 任務代碼
我覺得這篇博客的代碼寫得比我寫的好,分析也很好,但是因為寫得太好了不像我能寫出來的代碼,所以我主要沒有借鑒它的代碼。利用/proc/pid/pagemap將虛擬地址轉換為物理地址
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
//注意用sudo運行!!!
char buf[200];
//計算虛擬地址對應的地址,傳入虛擬地址vaddr
void mem_addr(char* str, unsigned long pid, unsigned long vaddr, unsigned long* paddr)
{
int pageSize = getpagesize();//調用此函數獲取系統設定的頁面大小
unsigned long v_pageIndex = vaddr / pageSize;//計算此虛擬地址相對于0x0的經過的頁面數
unsigned long v_offset = v_pageIndex * sizeof(uint64_t);//計算在/proc/pid/page_map文件中的偏移量
unsigned long page_offset = vaddr % pageSize;//計算虛擬地址在頁面中的偏移量
uint64_t item = 0;//存儲對應項的值
sprintf(buf, "%s%lu%s", "/proc/", pid, "/pagemap");
//printf("%s\n",buf);
int fd = open(buf, O_RDONLY);//以只讀方式打開/proc/pid/page_map
lseek(fd, v_offset, SEEK_SET);//將游標移動到相應位置
read(fd, &item, sizeof(uint64_t));//讀取對應項的值,并存入item中
//printf("%lu\n",v_offset);
uint64_t phy_pageIndex = (((uint64_t)1 << 55) - 1) & item;//計算物理頁號,即取item的bit0-54
*paddr = (phy_pageIndex * pageSize) + page_offset;//再加上頁內偏移量就得到了物理地址
printf("【%s】pid = %lu, 虛擬地址 = 0x%lx, 所在頁號 = %lu, 物理地址 = 0x%lx, 所在物理頁框號 = %lu\n", str, pid, vaddr, v_pageIndex, *paddr, phy_pageIndex);
sleep(1);
}
const int a = 100;//全局常量
int main()
{
int b = 100;//局部變量
static int c = 100;//局部靜態變量
const int d = 100;//局部常量
unsigned long phy = 0;//物理地址
char *p = (char*)malloc(100);//動態內存
int pid = fork();//創建子進程
mem_addr("全局常量", getpid(), (unsigned long)&a, &phy);
mem_addr("局部變量", getpid(), (unsigned long)&b, &phy);
mem_addr("局部靜態變量", getpid(), (unsigned long)&c, &phy);
mem_addr("局部常量", getpid(), (unsigned long)&d, &phy);
sleep(1);
free(p);
waitpid();
return 0;
}
3. 結果及說明
注意,運行時一定要加上sudo。
如圖,輸出了進程中多個變量的虛擬地址、所在頁號、物理地址、所在物理頁框號。
(1)如何通過擴充實驗展示不同進程的同一虛擬地址對應不同的物理地址。
答:通過fork創建不同的進程,如圖所示,pid為5022的為父進程,pid為5023的為子進程。其中全局變量的虛擬地址和物理地址,在父子進程中一致。局部變量、局部常量、局部靜態變量則均不一致。
(2)如何通過擴充實驗驗證不同進程的共享庫具有同一的物理地址。
這篇博客里有提到共享庫怎么判斷,我借鑒了一下,發現子進程的會顯示沒有present,不過其他進程是正常的。利用/proc/pid/pagemap將虛擬地址轉換為物理地址
下圖中包括三個進程,進程6944創建了子進程6945(右下角),以及進程6883(左下角)。
查看它們的/proc/pid/maps可見,它們都調用了同一個動態庫/usr/lib/x86_64-linux-gnu/libc-2.31.so,并可見在不同進程中這個庫的虛擬地址。
基于/proc/pid/pagemap獲取物理地址的思路,我們修改之前的程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
//注意用sudo運行!!!
char buf[200];
void mem_addr(unsigned long pid, unsigned long vaddr, unsigned long* paddr)
{
int pageSize = getpagesize();
unsigned long v_pageIndex = vaddr / pageSize;
unsigned long v_offset = v_pageIndex * sizeof(uint64_t);
unsigned long page_offset = vaddr % pageSize;
uint64_t item = 0;
sprintf(buf, "%s%lu%s", "/proc/", pid, "/pagemap");
int fd = open(buf, O_RDONLY);
lseek(fd, v_offset, SEEK_SET);
read(fd, &item, sizeof(uint64_t));
uint64_t phy_pageIndex = (((uint64_t)1 << 55) - 1) & item;
*paddr = (phy_pageIndex * pageSize) + page_offset;
printf("pid = %lu, 虛擬地址 = 0x%lx, 所在頁號 = %lu, 物理地址 = 0x%lx, 所在物理頁框號 = %lu\n", pid, vaddr, v_pageIndex, *paddr, phy_pageIndex);
sleep(1);
}
int main(int argc , char* argv[])
{
unsigned long phy = 0;//物理地址
printf("pid = %lu",getpid());
mem_addr(atoi(argv[1]), (unsigned long)strtol(argv[2],NULL,16), &phy);
sleep(1000);
return 0;
}
使其允許接受命令行參數,然后用它檢測不同進程的虛擬地址對應的物理地址。如下圖所示。
可見,不同進程的共享庫具有同一的物理地址。
而子進程的共享庫好像不見了(bushi)。
總結
以上是生活随笔為你收集整理的【HUST】网安|操作系统实验|实验三 内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2.3K star!5分钟搭建专属网课平
- 下一篇: ElementUI默认样式修改