# Nachos OS Project 3 HackMD好讀版: https://hackmd.io/@He1sEnBerG/HyClb7j8p ## Part 1 ### What is your motivation, problem analysis and plan ? 由於這次跑得 code 會耗費大量記憶體,所以不像是 HW1 時可以把程式都放進 memory 裡,需要設計虛擬記憶體(Virtual Memory)的機制,所以我打算先看我 HW1 對 PageTable 的實做並照著助教的提示完成作業。 ### Explain the details of code snippet you added or modified. 首先我們在 kernel 裡加入一個 class 專門處理 PageFault #### Private members - phy2virPage *考慮到我們需要同時運行兩個 process,因此在進行 Page Swap 的過程中,我們需要確定目前正在使用 Physical Page 的 Page 的是誰(可能位於另一個 PageTable 中)。因此,我們使用 **phy2virPage** 來儲存與 Physical Page 對應的 Page (TranslationEntry)。* - swap *用 nachos 裡的 synchDisk 做硬碟讀寫* - vmPages *紀錄那些 Virtual Pages 已被使用,這裡會用 Virtual Page Num 來當作硬碟讀寫的 sector* - fifo *FIFO replacement會用到的 counter* #### Public members - getVmPageNum() *回傳尚未被使用的 Virtual Page Number* - trackPhyPage(int, TranslationEntry) *把使用 PhyPage 的 Page 記錄進 phy2virPage - write2Disk(int sector, char *data) *把 data 寫進 disk 裡的 sector - swapPage(int vpn, int algo) *把目前 PageTable[vpn] 裡的 Page swap 進 main memory,algo目前只有FIFO* **userprog/userkernel.h** ```cpp class VirtualMemory; class UserProgKernel : public ThreadedKernel { public: ... Machine *machine; FileSystem *fileSystem; // add here VirtualMemory *virtualMemory; ... } class VirtualMemory { public: VirtualMemory(); int getVmPageNum(); void trackPhyPage(int phyPageNum,TranslationEntry *entry); void write2Disk(int sector, char *data); void swapPage(int vpn, int algo); private: // Tracks the virtual page that uses physical page TranslationEntry *phy2virPage[NumPhysPages]; SynchDisk *swap; bool vmPages[1024]; // 1024 = NumSector int fifo; }; ``` **userprog/userkernel.cc** ```cpp VirtualMemory::VirtualMemory() { swap = new SynchDisk("Swap"); memset(vmPages, 0, 1024); fifo = 0; } int VirtualMemory::getVmPageNum() { int i; for (i = 0; i < 1024; i++) { if (vmPages[i] == 0) { vmPages[i] = 1; break; } } return i; } void VirtualMemory::write2Disk(int sector, char *data) { swap->WriteSector(sector, data); } void VirtualMemory::trackPhyPage(int phyPageNum, TranslationEntry *entry) { phy2virPage[phyPageNum] = entry; } void VirtualMemory::swapPage(int vpn, int algo) { int target; char *buf1; char *buf2; if (algo == 0) { // FIFO target = fifo; fifo = (fifo + 1) % NumPhysPages; } buf1 = new char[PageSize]; buf2 = new char[PageSize]; // Read target Page from main memory to buf1 memcpy(buf1, &kernel->machine->mainMemory[target * PageSize], PageSize); // Read virtual page data from disk to buf2 swap->ReadSector(kernel->machine->pageTable[vpn].virtualPage, buf2); // Load buf2 to main memory memcpy(&kernel->machine->mainMemory[target * PageSize], buf2, PageSize); // Write buf1 to disk swap->WriteSector(kernel->machine->pageTable[vpn].virtualPage, buf1); phy2virPage[target]->virtualPage = kernel->machine->pageTable[vpn].virtualPage; phy2virPage[target]->valid = FALSE; kernel->machine->pageTable[vpn].valid = TRUE; kernel->machine->pageTable[vpn].physicalPage = target; trackPhyPage(target,&kernel->machine->pageTable[vpn]); delete[] buf1; delete[] buf2; return; } void UserProgKernel::Initialize() { ThreadedKernel::Initialize(); // init multithreading machine = new Machine(debugUserProg); fileSystem = new FileSystem(); // add VM virtualMemory = new VirtualMemory(); ... } ``` 再來就是修改 addrspace 中把 code load 進記憶體的部份 **userprog/addrspace.h** 用 pageTable_loaded 紀錄 pageTable 是否 Load 完成,防止 AddrSpace::SaveState() 把 addrspace 內資料覆蓋掉 ```cpp class AddrSpace{ ... private: bool pageTable_loaded; ... }; ``` **userprog/addrspace.cc** 透過使用 gdb,發現 noffH.initData.size 的值都為 0,因此這次作業只需要處理 noffH.code。解決方式大致上是,如果 phyPage 仍然足夠,則採取與之前作業相同的處理方式。但如果 phyPage 不足,則將資料寫入 Disk,再將存儲的 disk sector 存放到 Page 資訊中,同時將 valid 設置為 false。這樣在程式執行時,讀取這些頁面將引發 PageFaultException ```cpp bool AddrSpace::Load(char *fileName) { OpenFile *executable = kernel->fileSystem->Open(fileName); NoffHeader noffH; unsigned int size; if (executable == NULL) { cerr << "Unable to open file " << fileName << "\n"; return FALSE; } 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; // to leave room for the stack numPages = divRoundUp(size, PageSize); // cout << "number of pages of " << fileName<< " is "<<numPages<<endl; pageTable = new TranslationEntry[numPages]; for (unsigned int i = 0; i < numPages; i++) { pageTable[i].virtualPage = 0; pageTable[i].physicalPage = 0; pageTable[i].valid = FALSE; pageTable[i].use = FALSE; pageTable[i].dirty = FALSE; pageTable[i].readOnly = FALSE; } size = numPages * PageSize; DEBUG(dbgAddr, "Initializing address space: " << numPages << ", " << size); // then, copy in the code and data segments into memory if (noffH.code.size > 0) { for (unsigned int i = 0; i < numPages; i++) { unsigned int j; for (j = 0; j < NumPhysPages; j++) { // find unused Physical pages if (!usedPhysicalPage[j]) { break; } } if (j < NumPhysPages) { // if there is unused Physical pages DEBUG(dbgAddr, "Phy page found: " << i); usedPhysicalPage[j] = true; pageTable[i].physicalPage = j; pageTable[i].valid = TRUE; kernel->virtualMemory->trackPhyPage(j,&pageTable[i]); executable->ReadAt(&(kernel->machine->mainMemory[j * PageSize])\ , PageSize, noffH.code.inFileAddr + (i * PageSize)); } else { // write in disk DEBUG(dbgAddr, "No enough physical pages for page " << i); char *buffer; buffer = new char[PageSize]; // Find empty Virtual Page pageTable[i].virtualPage = kernel->virtualMemory->getVmPageNum(); pageTable[i].valid = FALSE; executable->ReadAt(buffer, PageSize, \ noffH.code.inFileAddr + (i * PageSize)); kernel->virtualMemory->write2Disk(pageTable[i].virtualPage, buffer); delete[] buffer; } } } ... } void AddrSpace::SaveState() { if (pageTable_loaded) { pageTable = kernel->machine->pageTable; numPages = kernel->machine->pageTableSize; } } ``` 最終要處理的是當遇到 PageFault 時的 ExceptionHandler。計算完 vpn 就傳入上面寫好的**VirtualMemory::swapPage**進行處理。 **userprog/exception.cc** ```cpp void ExceptionHandler(ExceptionType which) { int type = kernel->machine->ReadRegister(2); int val, vaddr, vpn; switch (which) { case PageFaultException: kernel->stats->numPageFaults++; vaddr = kernel->machine->ReadRegister(39); vpn = vaddr / PageSize; // algo: 0:FIFO, 1:LFU, 2:LRU kernel->virtualMemory->swapPage(vpn,0); return; case SyscallException: ... } ``` ### Experiment result and some discussion, observation. 首先 sort.c 應該寫錯了,第 25 行 j 要從 0 開始而非 i,然後最小值 A[0] 應該為 1 而非 0 #### FIFO matmult ![matmult](https://hackmd.io/_uploads/rkf2zdnLT.png =70%x) sort ![sort](https://hackmd.io/_uploads/rJD2fun86.png =70%x) Running Both ![Running Both](https://hackmd.io/_uploads/Bys3zdn8p.png =70%x) ### What problem you face and how to tackle it? ### 問題一 在處理 addrspace.cc 的時候遇到了一個重要的問題。具體來說,當我在寫到 addrspace.cc 中的 Load() 函數時,想要測試在 phyPage 不足的情況下,能否成功將資料寫入硬碟。然而當我打開了 debug flag a 觀察時,發現 Load() 在儲存了 phyPage + 1 個頁面後就終止了。 通過使用 gdb 進行逐步執行,我發現在執行完 WriteSector 後,Addrspace 中的數據都會消失。進一步觀察 WriteSector 函數,我發現它設置了計時器中斷,模擬讀取硬碟的時間。這讓我誤以為在 HW2 中,我將計時器的 CallBack() 寫錯了。但是當我回到 HW1 時的 commit,仍然發現了相同的問題。 最終,我發現處理 timer interrupt 時 context switch 會執行 AddrSpace::SaveState(),這會導致機器中的空數據覆蓋我們正在寫入一半的 PageTable。解決這個問題的方法是在執行 Load 之前設置一個變數 pageTable_loaded,等到所有頁面都成功加載後再允許 SaveState 執行。 ### 問題二 第二個問題是在實作 Page Replacement Algorithm 遇到的問題,就是不管是 LFU 還是 LRU 都需要紀錄 Page 的讀取,我原本是在Machine::Translate裡統計讀取資訊,但由於作業規定不能更改 machine 裡的檔案,變成只有 PageFault Exception 發生時我們才有控制權,這會使其他演算法無法設計。