nachos-虚拟内存管理
nachos虛擬內存實驗
文章目錄
- nachos虛擬內存實驗
- 內容一、總體概述
- 內容二、任務完成情況
- 具體Exercise的完成情況
- 1.Exercise1 源代碼閱讀
- 2.Exercise2 TLB MISS 異常處理
- Exercise 3 置換算法
- Exercise 4 內存全局管理數據結構
- Exercise 5 多線程支持
- Exercise 6 缺頁中斷處理
- Exercise 7 lazy-loading
- Challenge2 多線程實現基本思路:
- 內容三:遇到的困難及解決方法
- 內容四:收獲及感想
- 內容五:對課程的意見和建議
內容一、總體概述
本次實驗主要是通過閱讀相關代碼,了解 nachos用戶程序的執行過程,之后完成TLB,頁表和虛擬內存等的實現。。其中第一部分主要內容是實現TLB相關異常處理和置換算法,當前的 nachos只支持單個用戶程序,沒有用到TLB。第二部分的主要內容是實現全局內存管理機制,使得 nachos內存可以同時存在多個線程。第三部分的主要內容是實現程序運行過程中發生缺頁中斷時,才會將所需的頁面從磁盤調入內存。Challenge部分是增加線程掛起狀態以及實現倒排頁表。
內容二、任務完成情況
任務完成列表(Y/N)
| Y | Y | Y | Y | Y | Y | Y | Y |
具體Exercise的完成情況
1.Exercise1 源代碼閱讀
- 閱讀code/userprog/progtest.cc,著重理解nachos執行用戶程序的過程,以及該過程中與內存管理相關的要點。
- 閱讀code/machine目錄下的machine.h(cc),translate.h(cc)文件和code/userprog目錄下的exception.h(cc),理解當前Nachos系統所采用的TLB機制和地址轉換機制。
(1)用戶程序執行過程
userprog/progtest.cc定義函數
StartProcess主要功能是實現用戶程序啟動,
如果我們希望執行test中的用戶程序,
那么我們進入userprog,執行./nachos -x …/test/
(用戶程序),通過識別-x 參數,nachos 調用
StartProcess 執行用戶程序
(具體實現在 threads/main.cc)
StartProcess 的基本流程是:
通過文件系統定義的 OpenFile 打開相關文件
通過 AddrSpace的構造函數建立用戶空間,裝載文件
通過 AddrSpace 的InitRegisters 函數初始化用戶寄存器
通過 AddrSpace 的RestoreState 函數裝載頁表
通過machine的Run 函數運行用戶程序
AddrSpace 的構造函數實現在 userprog/addrspace.cc,主要流程是:
獲取文件頭,大小端做適宜轉換
通過文件頭計算文件所需空間,包括代碼段,初始化數據段,未初始化數據段,棧空間 4 個部分
通過文件所需空間計算出文件所需的虛擬頁面數量,創建用戶空間頁表,指示了第 i 個虛擬頁對應第 i 個物理頁,將用戶程序的正文段和相關數據依次調入內存。
AddrSpace 的 InitRegisters 函數實現在 userprog/addrspace.cc,主要流程是:
初始化普通寄存器(初始化為 0)
初始化當前指令指針(PC,初始化為 0)
初始化下一條指令指針(初始化為 4)
初始化棧指針(地址空間尾部適當前移)
AddrSpace 的 RestoreState 函數實現在 userprog/addrspace.cc,主要流程是:
將頁表裝載到machine 類中
準備執行用戶程序machine 的Run函數實現在machine/mipssim.cc,基本流程是:
通過 OneInstruction 函數完成指令譯碼和執行
通過interrupt 的 OneTick 函數使得時鐘前進
machine 的Run 函數通過 machine 的ReadMem 函數讀內存數據,通過 machine的WriteMem 函數寫內存數據,兩個函數的實現在 machine/translate.cc,核心是 translate 函數
translate 函數實現在machine/translate.cc,主要功能是實現虛擬地址到物理地址的轉換,translate 函數可能返回相應的錯誤,在這樣的情況下,ReadMem 函數/WriteMem 函數調用 RaiseException 函數進行處理,
RaiseException 函數定義在 machine/machine.cc,基本流程是將錯誤信息存儲在特定位置,調用 ExceptionHandler 函數處理不同的錯誤,
ExceptionHandler函數實現在userprog/exception.cc,主要流程是根據錯誤信息處理不同錯誤。
目前支持的錯誤:
寄存器支持:
(2)TLB 機制和地址轉換機制
表項維護的位置是machine/translate.h 中 TranslationEntry 數據結構
Class TranslationEntry { public: int virtualPage; // 虛擬頁號 int physicalPage; // 物理頁號 boot valid; // 該Entry 是否使用,TRUE 表示使用 bool readOnly; // 對應頁的訪問屬性,TRUE 示只讀,否則為讀寫 bool use; // 該Entry 是否被使用過,每次訪問后置為TRUE bool dirty; // 對應的物理頁使用情況,TRUE 表示被寫過 }相關參數
以下參數定義在filesys/disk.h #define SectorSize 128 以下參數定義在machine/machine.h #define PageSize SectorSize #define NumPhysPages 32 #define MemorySize (NumPhysPages * PageSize) #define TLBSize 4TLB 初始化的位置是machine/machine.cc 中machine 的構造函數
TLB 的使用的位置是machine/translate.cc中的translate函數,基本流程是遍歷TLB 數組,查找是否有對應映射,如果有,那么 TLB 命中,直接進行物理地址轉換,否則,TLB 沒有命中,標志 PageFaultException,進入Exception處理。(目前還沒有對應的處理函數)
地址轉換機制的位置是machine/translate.cc中的translate 函數,基本流程是:通過虛擬地址得到vpn和offset,通過TLB或是 Pagetable得到vpn對應的ppn,(否則拋出異常,在異常處理函數中做處理,但目前這部分沒有實現),通過ppn 和offset 得到物理地址,返回物理地址。
需要說明的是,在處理完TLB的miss或者Pagefault 之后,不需要將PC+4,因為異常處理函數結束后,返回的最終位置會是OneInstruction函數的取指階段。取指失敗后,OneInstruction函數會退出,然后再用相同的PC取指。而這次就能夠TLB hit或者pagetable hit了。
machine/machine.cc 的 Machine 類模擬內存,重要函數包括
2.Exercise2 TLB MISS 異常處理
修改code/userprog目錄下exception.cc中的ExceptionHandler函數,使得Nachos系統可以對TLB異常進行處理(TLB異常時,Nachos系統會拋出PageFaultException,詳見code/machine/machine.cc)
1、設計思路
首先Translate()方法,啟動TLB,讓用戶程序在運行的時候先訪問 TLB,如果出現 TLB MISS,會立刻拋出一個 RaiseException(),然后通過 ExceptionHandler()處理這個缺頁異常,處理的動作就是讓系統從pageTable 頁表中查找要找的頁表項。
2、userprog/Makefile
我們需要使用TLB,但是TLB并沒有啟用(在Exercise1里面解釋過),所以我們需要先在userprog/Makefile添加宏。
然后在machine/machine.h中對USE_TLB進行宏定義,只有這樣宏才能真正起作用。
3、machine/translate.cc
因為系統是默認沒有啟用TLB的,但是我們現在啟用了TLB,所以我們應該注釋掉ASSERT(tlb == NULL || pageTable == NULL); ,否則會報錯Assertion failed: line 203, file “…/machine/translate.cc”
4、userprog/exception.cc
修改ExceptionHandler函數,因為之前系統是沒有PageFaultException異常的(因為之前系統默認是把物理頁面全部裝入內存的,也沒有啟用TLB所以不會出現PageFaultException),所以我們需要添加PageFaultException,并進行處理。
else if(which == PageFaultException){//發生缺頁中斷則讓TLBMissCount++TLBMissCount++;if(machine->tlb == NULL){//頁表失效,因為默認不會出現所以直接用ASSERT(FALSE);ASSERT(FALSE);}else{//快表失效,處理流程首先調用machine的ReadRegister函數,從BadVAddrReg寄存器中取出發生異常的虛擬地址,并算出vpn//DEBUG('m',"=> TLB miss (no TLB entry)\n");int BadVAddr = machine->ReadRegister(BadVAddrReg);TLBMissHandler(BadVAddr);//TLBAlgoFIFO(BadVAddr); //FIFO算法測試//TLBAlgoClock(BadVAddr); //CLOCK時鐘算法測試}} int position = 0; void TLBMissHandler(int virtAddr) //頁表失效處理函數 {unsigned int vpn;vpn = (unsigned) virtAddr / PageSize;TranslationEntry page = machine->pageTable[vpn];if(!page.valid){DEBUG('m',"\t=> Page miss\n");page = PageFaultHandler(vpn);}TLBAlgoClock(virtAddr); //處理快表失效 }5、測試結果
Exercise 3 置換算法
為TLB機制實現至少兩種置換算法,通過比較不同算法的置換次數可比較算法的優劣。
1、FIFO算法
算法的思想是每次淘汰最先進入TLB的頁面。具體實現方式則是每次移除塊表數組的第一項,然后一次將后面的往前移,新的表項放在快表數組的尾項。
userprog/exception.cc
2、CLOCK時鐘置換算法
Nachos系統已經定義了TLB的use和valid,所以我們可以很方便的實現時鐘算法。具體實現是首先判斷valid的值,看該位置是否被訪問過,如果為false則直接進行替換;如果為true,則進一步判斷use的值,來看是否被修改過,如果修改過則將其值置為false,然后判斷下一位。如果為use為false則直接進行替換。
void TLBAlgoClock(int virtAddr) {unsigned int vpn;vpn = (unsigned) virtAddr / PageSize;while(1){position3 %= TLBSize;if(machine->tlb[position3].valid == FALSE){break;}else{if(machine->tlb[position3].use){//更新use的值machine->tlb[position3].use = FALSE;position3++;}else{break;}}}machine->tlb[position3] = machine->pageTable[vpn];machine->tlb[position3].use = TRUE; }3、測試兩個算法,并打印出TLB相關信息
首先在translate.cc中設置兩個全局變量, TLBMissCount = 0;(記錄TLB MISS);TranslateCount = 0(記錄進程頁面訪問次數)。并在machine.h中進行擴展聲明。然后在每次發生PageFaultException讓TLBMissCount+1;在每次執行TranslateCount函數時,讓TranslateCount+1。分別調用兩個算法最終在程序執行結束退出后調用debug函數打印出TLB缺頁次數,缺頁率等信息。
4、測試結果
測試說明:我試用了系統提供的sort排序進行測試,在最開始的時候報錯Assertion failed: line 81, file "…/userprog/addrspace.cc,仔細閱讀代碼發現是因為系統給的sort排序超出了內存限制,同時原來sort在最后接觸進行的系統調用EXIT 尚未在本系統中實現,所以我在原來的基礎上進行了修改,并重新make。
#include "syscall.h"int A[20]; /* size of physical memory; with code, we'll run out of space!*/int main() {int i, j, tmp;/* first initialize the array, in reverse sorted order */for (i = 0; i < 20; i++) A[i] = 20 - i;/* then sort! */for (i = 0; i < 19; i++)for (j = i; j < (19 - i); j++)if (A[j] > A[j + 1]) { /* out of order -> need to swap ! */tmp = A[j];A[j] = A[j + 1];A[j + 1] = tmp;}//Exit(A[0]); /* and then we're done -- should be 0! */Halt(); }我將原來的數組縮小為20,同時結束之后進行halt系統調用.
FIFO算法測試結果:
CLOCK 時鐘算法測試結果:
對比之后發現,時鐘算法的效率明顯優于FIFO算法。
分頁式內存管理
目前Nachos系統中,類Class Thread的成員變量AddrSpace* space中使用TranslationEntry* pageTable來管理內存。應用程序的啟動過程中,對其進行初始化;而在線程的切換過程中,亦會對該變量進行保存和恢復的操作(使得類Class Machine中定義的Class Machine::TranslationEntry* pageTable始終指向當前正在運行的線程的頁表)。
Exercise 4 內存全局管理數據結構
設計并實現一個全局性的數據結構(如空閑鏈表、位圖等)來進行內存的分配和回收,并記錄當前內存的使用狀態。
1、基本思路
這里我選擇使用位圖(bitMap)來管理空閑的內存。在machine類中增加成員變量bitmap,類型為數組。因為Nachos系統擁有32位的物理頁面,所以設置了一個大小為32的數組,初始值都為0,分配之后設置為1。每次申請物理內存的時候調用allocateMemory函數來尋找一塊空閑的頁面 ,如果沒有空閑的頁面則返回-1。freeMemory函數則是負責回收內存的。具體就是將當前頁表對應的所有位圖位置設為0。
2、machine/machine.h
unsigned int bitmap[32];int allocateMemory(void);void freeMemory(void);3、machine/machine.cc
實現上述函數
int Machine::allocateMemory() {for(int i=0;i<32;i++){if(bitmap[i]==0){bitmap[i]=1;printf("allocate memory %d\n",i);return i;}}return -1; }void Machine::freeMemory(void) {for(int i=0;i<NumPhysPages;i++){//int current=pageTable[i].physicalPage;if(pageTable[i].threadId == currentThread->getTid()){if(bitmap[i]==1){printf("free Memory %d\n",i);bitmap[i]=0;}}} }4、添加內存分配回收機制
分配內存的allocateMemory函數主要是在內存初始化的時候調用的,主要通過修改userprog/addrspace.cc文件。而內存的回收則是在程序結束之后通過Exit進行調用,而系統還未實現Exit系統調用,所以在這個部分我們還需要實現Exit,具體實現是修改userprog/exception.cc文件。
addrspace.cc
5、測試結果
為了方便截圖,我們直接運行一個空的程序,將原有的halt.c進行修改,注釋掉Halt()(后面的測試基本上都使用修改后的halt.c文件進行測試)。
Exercise 5 多線程支持
1、基本思想
目前因為系統的內存中同時只能存在一個線程,所以規定系統將程序的內容調入內存時是根據虛擬地址來確定的,并且規定了這個虛擬地址和物理地址相同。基于上一個exercise修改之后,我們將實現掉入內存的位置根據物理地址來確定。同時之前在程序退出之后因為默認系統同時只存在一個線程,所以系統就運行結束了,現在我們因為有多個線程,所以在程序結束之后我們需要切換到下一個程序。
2、實現程序運行結束之后切換
//exception.cc中的ExceptionHandler(ExceptionType which)函數中做的修改 else if(which == PageFaultException){//發生缺頁中斷則讓TLBMissCount++TLBMissCount++;if(machine->tlb == NULL){//頁表失效,因為默認不會出現所以直接用ASSERT(FALSE);ASSERT(FALSE);}else{//快表失效,處理流程首先調用machine的ReadRegister函數,從BadVAddrReg寄存器中取出發生異常的虛擬地址,并算出vpn//DEBUG('m',"=> TLB miss (no TLB entry)\n");int BadVAddr = machine->ReadRegister(BadVAddrReg);TLBMissHandler(BadVAddr);//TLBAlgoFIFO(BadVAddr); //FIFO算法測試//TLBAlgoClock(BadVAddr); //CLOCK時鐘算法測試}}3、修改地址空間的上下文交換
在進上下文交換的時候因為切換了程序,所以TLB原有的內容則失效了,所以我們應該清楚TLB的內容,否則會頻繁出現頁面替換,降低效率。
void AddrSpace::SaveState() {for(int i=0;i<TLBSize;i++){machine->tlb[i].valid = FALSE;} }4、測試函數
Nachos執行用戶程序的函數入口是通過StartProcess函數,但是這個函數只能執行一個用戶程序。因此我們仿照StartProcess的思想,重新構造了一個新的函數入口StartTwoThread用于執行兩個程序。同時為了方便,我們將兩個線程載入同一個程序(就是之前的halt.c)。
Thread* CreateSingleThread(OpenFile *executable,int number) {printf("Creating user program thread %d\n",number);char ThreadName[20];sprintf(ThreadName,"User program %d",number);Thread *thread = new Thread(strdup(ThreadName),0);//注意這里設置的新線程的優先級必須高于main的優先級,否則線程不能主動放棄處理機需要手動實現AddrSpace *space;space = new AddrSpace(executable);thread->space = space;return thread; } void UserProgThread(int number) {printf("Running user program thread %d\n",number);currentThread->space->InitRegisters();currentThread->space->RestoreState();currentThread->space->PrintState();machine->Run();ASSERT(FALSE); } void StartTwoThread(char *filename) {OpenFile *executable = fileSystem->Open(filename);if(executable == NULL){printf("Unable to open file %s\n",filename);return ;}//CreateSingleThread函數主要實現了原來StartProcess函數的//space = new AddrSpace(executable,5);和//currentThread->space = space;部分Thread *thread1 = CreateSingleThread(executable,1);Thread *thread2 = CreateSingleThread(executable,2);delete executable;//UserProgThread函數的目的是進行相關寄存器的初始化以及加載頁表thread1->Fork(UserProgThread,1);thread2->Fork(UserProgThread,2); }實現一個在類AddrSpace中實現一個PrintState函數,打印出Address Space的相關信息。
void AddrSpace::PrintState() {printf("=== %s ===\n","Address Space Information");printf("numPages = %d\n",numPages);printf("VPN\tPPN\tvalid\tR0\tuse\tdirty\n");for(int i=0;i<numPages;i++){printf("%d\t",pageTable[i].virtualPage);printf("%d\t",pageTable[i].physicalPage);printf("%d\t",pageTable[i].valid);printf("%d\t",pageTable[i].use);printf("%d\t",pageTable[i].dirty);printf("%d\t",pageTable[i].readOnly);printf("\n");}printf("========================================\n"); }5、修改main函數
原來的程序執行函數入口通過-x調用StartProcess,但是現在我們修改了程序入口,所以我們應該新增參數-X調用StartTwoThread
#ifdef USER_PROGRAMif (!strcmp(*argv, "-x")) { // run a user programASSERT(argc > 1);StartTwoThread(*(argv + 1));argCount = 2;} else if (!strcmp(*argv, "-c")) { // test the consoleif (argc == 1)ConsoleTest(NULL, NULL);else {ASSERT(argc > 2);ConsoleTest(*(argv + 1), *(argv + 2));argCount = 3;}interrupt->Halt(); // once we start the console, then // Nachos will loop forever waiting // for console input} #endif // USER_PROGRAM6、測試結果
由于Exercise6和Exercise7是密切關聯的,所以我把兩個實驗放在一起寫。
Exercise 6 缺頁中斷處理
基于TLB機制的異常處理和頁面替換算法的實踐,實現缺頁中斷處理(注意!TLB機制的異常處理是將內存中已有的頁面調入TLB,而此處的缺頁中斷處理則是從磁盤中調入新的頁面到內存)、頁面替換算法等。
Exercise 7 lazy-loading
我們已經知道,Nachos系統為用戶程序分配內存必須在用戶程序載入內存時一次性完成,故此,系統能夠運行的用戶程序的大小被嚴格限制在4KB以下。請實現Lazy-loading的內存分配算法,使得當且僅當程序運行過程中缺頁中斷發生時,才會將所需的頁面從磁盤調入內存。
1、基本思想
Exercise 6缺頁中斷處理的思想是將發生缺頁中斷的虛擬頁面從磁盤調入物理頁面,也就是虛擬內存的概念,在這里的虛擬內存通過filesystem創建了一個virtual_memory模擬。當發生PageFaultException時(分為兩種情況,頁表失效和快表失效)通過ExceptionHandler函數處理,前面實現了快表失效,在這里需要實現頁表失效處理。而之前Nachos系統本身是不會發生缺頁中斷的,因為系統直接將程序一次性裝載進入內存 ,所以不存在頁表失效,這就需要結合Exercise 7 的內存分配機制Lazy-loading才可能發生頁表失效。
對于Lazy-loading即按需請求調頁而不是一次性全部裝載,這就需要修改Addspace的構造函數,將用戶程序的內容先裝載到虛擬內存,等需要的時候再從virtual_memory中調入。
2、創建虛擬內存
在基本思想中已經描述過,通過fileSystem創建一個virtual_memory文件來模擬虛擬內存,同時將數據裝載到virtual_memory中。為了盡可能的使每個實驗代碼能夠運行,我這里重構了addspace的構造函數,首先在addspace.h中聲明新的構造函數。
AddrSpace(OpenFile *executable); // Create an address space,// initializing it with the program// stored in the file "executable"AddrSpace(OpenFile *executable,int lab); //虛擬內存實驗所重構的構造函數,lab參數沒有意義~AddrSpace(); // De-allocate an address space AddrSpace::AddrSpace(OpenFile *executable,int lab) {NoffHeader noffH;unsigned int i, size;executable->ReadAt((char *)&noffH, sizeof(noffH), 0);if ((noffH.noffMagic != NOFFMAGIC) && (WordToHost(noffH.noffMagic) == NOFFMAGIC))SwapHeader(&noffH);ASSERT(noffH.noffMagic == NOFFMAGIC);// how big is address space?size = noffH.code.size + noffH.initData.size + noffH.uninitData.size + UserStackSize; // we need to increase the size// to leave room for the stacknumPages = divRoundUp(size, PageSize);size = numPages * PageSize;//上面的內容與原來的一樣//用filesystem創建VirtualMemory文件,運行nachos之后,會在userprog目錄下面生成該文件bool success_create_vm = fileSystem->Create("VirtualMemory",size);ASSERT(numPages <= NumPhysPages);//pageTable = new TranslationEntry[numPages];for(i=0;i<numPages;i++){machine->pageTable[i].virtualPage = i;machine->pageTable[i].physicalPage = machine->allocateMemory(); //因為我們沒有將用戶程序內容裝載進內存,所以physicalPage的值可以為0machine->pageTable[i].valid = TRUE; //表示沒有從磁盤裝載任何頁面進內存machine->pageTable[i].use = FALSE;machine->pageTable[i].dirty = FALSE;machine->pageTable[i].readOnly = FALSE;machine->pageTable[i].threadId = currentThread->getTid();}//初始化整個物理內存bzero(machine->mainMemory,MemorySize);OpenFile *vm = fileSystem->Open("VirtualMemory");char *virtualMemory_temp;virtualMemory_temp = new char[size]; //該數組主要是用于將用戶程序的內容寫入磁盤的中間過度for(i=0;i<size;i++)virtualMemory_temp[i] = 0;if(noffH.code.size>0){DEBUG('a',"\tCopying code segment, at 0x%x, size %d\n",noffH.code.virtualAddr,noffH.code.size);executable->ReadAt(&(virtualMemory_temp[noffH.code.virtualAddr]),noffH.code.size,noffH.code.inFileAddr);vm->WriteAt(&(virtualMemory_temp[noffH.code.virtualAddr]),noffH.code.size,noffH.code.virtualAddr*PageSize);}if(noffH.initData.size >0){DEBUG('a',"\tCopying data segment, at 0x%x, size %d\n",noffH.initData.virtualAddr,noffH.initData.size);executable->ReadAt(&(virtualMemory_temp[noffH.initData.virtualAddr]),noffH.initData.size,noffH.initData.inFileAddr);vm->WriteAt(&(virtualMemory_temp[noffH.initData.virtualAddr]),noffH.initData.size,noffH.initData.virtualAddr*PageSize);}//上面的內容仿照原始的addspace構造函數編寫delete vm; }3、缺頁處理
當發生缺頁異常時,通過ExceptionHandler函數調用TLBMissHandler函數,TLBMissHandler將將缺頁異常的地址裝換成虛擬地址,然后調用PageFaultHandler函數,該函數首先通過machine->allocateMemory()尋找一個空閑頁,如果返回-1則調用NaivePageReplacement函數,該函數的作用是進行頁面替換。首先尋找一個未被修改過的頁面進行替換,如果找到則結束;否則將第一個(1,1)的頁面進行替換,并將頁表內容寫會磁盤。
int NaivePageReplacement(int vpn) {int pageFrame = -1;for(int temp_vpn = 0;temp_vpn < machine->pageTableSize,temp_vpn != vpn;temp_vpn++){if(machine->pageTable[temp_vpn].valid){if(!machine->pageTable[temp_vpn].dirty){pageFrame = machine->pageTable[temp_vpn].physicalPage;break;}}}if(pageFrame == -1){for(int replaced_vpn = 0; replaced_vpn < machine->pageTableSize, replaced_vpn != vpn;replaced_vpn++){if(machine->pageTable[replaced_vpn].valid){machine->pageTable[replaced_vpn].valid = FALSE;pageFrame = machine->pageTable[replaced_vpn].physicalPage;//將頁表寫回磁盤OpenFile *vm = fileSystem->Open("VirtualMemory");vm->WriteAt(&(machine->mainMemory[pageFrame*PageSize]),PageSize,replaced_vpn*PageSize);delete vm;break;}}}return pageFrame; } TranslationEntry PageFaultHandler(int vpn) {int pageFrame = machine->allocateMemory();if(pageFrame == -1){pageFrame == NaivePageReplacement(vpn);}machine->pageTable[vpn].physicalPage = pageFrame;OpenFile *vm = fileSystem->Open("VirtualMemory");vm->ReadAt(&(machine->mainMemory[pageFrame*PageSize]),PageSize,vpn*PageSize);delete vm;machine->pageTable[vpn].valid = TRUE;machine->pageTable[vpn].use = FALSE;machine->pageTable[vpn].dirty = FALSE;machine->pageTable[vpn].readOnly = FALSE;currentThread->space->PrintState(); //打印地址空間信息 } int position = 0; void TLBMissHandler(int virtAddr) //頁表失效處理函數 {unsigned int vpn;vpn = (unsigned) virtAddr / PageSize;TranslationEntry page = machine->pageTable[vpn];if(!page.valid){DEBUG('m',"\t=> Page miss\n");page = PageFaultHandler(vpn);}TLBAlgoClock(virtAddr); //處理快表失效 } void ExceptionHandler(ExceptionType which) {int type = machine->ReadRegister(2);if ((which == SyscallException)) {if((type == SC_Halt)){DEBUG('T', "TLB Miss: %d, TLB Hit: %d, Total Translate: %d, TLB Miss Rate: %.2lf%%\n",TLBMissCount,TranslateCount-TLBMissCount,TranslateCount,(double)(TLBMissCount*100)/(TranslateCount));interrupt->Halt();}else if(type == SC_Exit){printf("program exit\n");if(currentThread->space != NULL){machine->freeMemory();delete currentThread->space;currentThread->space = NULL;currentThread->Finish();int nextPc=machine->ReadRegister(NextPCReg);machine->WriteRegister(PCReg,nextPc);}}}else if(which == PageFaultException){//發生缺頁中斷則讓TLBMissCount++TLBMissCount++;if(machine->tlb == NULL){//頁表失效,因為默認不會出現所以直接用ASSERT(FALSE);ASSERT(FALSE);}else{//快表失效,處理流程首先調用machine的ReadRegister函數,從BadVAddrReg寄存器中取出發生異常的虛擬地址,并算出vpn//DEBUG('m',"=> TLB miss (no TLB entry)\n");int BadVAddr = machine->ReadRegister(BadVAddrReg);TLBMissHandler(BadVAddr);//TLBAlgoFIFO(BadVAddr); //FIFO算法測試//TLBAlgoClock(BadVAddr); //CLOCK時鐘算法測試}} else {printf("Unexpected user mode exception %d %d\n", which, type);ASSERT(FALSE);} }4、測試結果
Challenge2 多線程實現基本思路:
前面實現的倒排頁表并不是嚴格意思的倒排頁表,實際上,倒排頁表與進程/線程數量無關,所以,我們需要在 machine 類維護倒排頁表,而不是在 addrspace類維護倒排頁表。
考慮到多線程共用倒排頁表的實際情況,我們需要在頁表項記錄線程 ID
在mahine/machine.cc 的machine 類的構造函數完成倒排頁表的初始化
#ifdef USE_TLBpageTable = new TranslationEntry[NumPhysPages];//初始化倒排頁表for (i = 0; i < NumPhysPages; i++)pageTable[i].physicalPage = i;pageTable[i].virtualPage = i;pageTable[i].valid = FALSE;pageTable[i].use= FALSE;pageTable[i].dirty = FALSE;pageTable[i].readOnly = FALSE;pageTable[i].threadId = -1; #else // use linear page tabletlb = NULL;pageTable = NULL; #endif考慮到所有線程共用相同倒排頁表,不需要從線程內存空間載入頁表,這樣的修改同時意味著我們在addrspace 定義的頁表失去了作用
void AddrSpace::RestoreState() {// machine->pageTable = pageTable;//machine->pageTableSize = numPages; } //由于引入了線程ID,在表項判斷時增加判斷內容 pageTable[i].threadID == currentThread->getThreadID() //在表項修改時增加修改內容 machine->pageTable[pos].threadID = currentThread->getThreadID();同時改寫釋放空間函數,根據線程ID 釋放相關空間
void Machine::freeMemory(void) {for(int i=0;i<NumPhysPages;i++){//int current=pageTable[i].physicalPage;if(pageTable[i].threadId == currentThread->getTid()){if(bitmap[i]==1){printf("free Memory %d\n",i);bitmap[i]=0;}}} }測試結果:
內容三:遇到的困難及解決方法
困難 1:理解#ifdef
根據nachos 的默認設置,TLB 沒有開啟,查看相關代碼發現#ifdef 相關命令,查詢相關資料得知,#ifdef 是條件編譯的重要命令,所謂條件編譯,就是對程序中的部分內容只在滿足特定條件的情況下進行編譯,條件編譯包括許多指令,其中#ifdef 的常見形式為:
#ifdef 標識符
//程序段 1
#else
//程序段 2
#endif
這段語句的作用是當標識符已經被定義過(一般是用#define 命令定義),則對程序段1 進行編譯,否則編譯程序段2。進一步查詢相關資料,得知我們需要修改userprog/Makefile,開啟 USE_TLB
DEFINES = … -DUSE_TLB
困難2:理解nachos文件系統
如果希望利用文件而不是內存的額外空間實現 Exercise6和 Exercise7,那么需要涉及 nahcos 文件系統。主要涉及的部分包括文件系統數據結構FileSystem和打開文件數據結構Openfile。
文件系統數據結構FileSystem 的重要函數包括
Create函數:基本功能是文件系統中創建固定大小的文件,如果創建成功,那么返回TRUE,否則返回 FALSE
Open函數:基本功能是打開已經存在的文件,返回的是打開文件數據結構
Remove函數:基本功能是刪除已經存在的文件,如果刪除成功,那么返回TRUE,否則返回FALSE
需要說明的是,如果我們希望關閉已經打開的文件,那么需要調用相應打開文件數據結構的析構函數
打開文件數據結構Openfile 的重要函數包括
Length函數:基本功能是返回文件長度
Seek函數:基本功能是移動當前文件位置指針
ReadAt函數:基本功能是從文件特定位置讀取特定長度的數據到特定位置,返回值是實際讀出的字節數
WriteAt函數:基本功能是從特定位置向文件特定位置寫入特定長度的數據,返回值是實際寫入的字節數
Read函數:基本功能是從文件讀取特定長度的數據到特定位置,返回值是實際讀出的字節數,基于ReadAt 函數實現
Write函數:基本功能是從特定位置向文件寫入特定長度的數據,返回值是實際寫入的字節數,基于WriteAt 函數實現
困難3 理解DEBUG
對DEBUG函數不熟悉,剛開始在對程序進行調試的時候總是使用printf函數,然而nachos系統定義的DEBUG函數功能更加強大。
內容四:收獲及感想
實習課程和理論課程相得益彰。通過本次實習,我強化了對存儲模型相關知識的理解,鍛煉了編程能力。經過一段時間的接觸,感覺對 nachos 系統有了一定的了解,對課程有了一定的興趣,期待在以后的實習中能夠了解nachos 更多的功能并且在此基礎上進行更多的修改。
內容五:對課程的意見和建議
我覺得課程方式很好,老師講課非常認真,對同學們也很寬容,布置給我們的實驗雖然讓我們在做的過程中一直懷疑人生,但是在實驗完成之后回想整個過程,我們獲得的提高非常巨大!由于實驗逐漸變難,所以在實驗過程中大家出現了很多分歧,也無法確定誰的想法是正確的,希望老師能在課上對實驗進行一定的梳理。
總結
以上是生活随笔為你收集整理的nachos-虚拟内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android recycleview使
- 下一篇: AsyncTask使用详解