## 一些小話
? ? ? ? ? 前段時間因為一些雜七雜八的事情 被逼無奈 趕各種作業 再加上某個姓張的賤人 欺騙我說計網課設得交了 就一直把nachos的實驗放在一邊 然后今天聽朋友說操作系統沒做完 所以就想著按照自己的理解寫一個盡量傻瓜版的教程 試一試 看下能不能嘗試著寫的清晰一點 當作操作系統實驗報告了。。。(雖然每次都是做完實驗不寫報告。。期末再趕吧 希望不要被打死)
## 開始
寫實驗之前我們的看看我們要寫什么以及我們擁有什么 我們要做的其實就是用我們擁有技能去做我們要寫的東西 就ok了 所以先捋一捋
#### 我們要寫什么
我們要在一個叫做nachos的實驗代碼中去添加一段代碼 完成
訪問TLB
TLB調度算法
#### 我們擁有什么
有一份叫做nachos的實驗源碼
順手有了助教哥哥的ppt
會一點點c和c++(c++這一部分其實不太重要 如果后續的過程中需要用到的地方 我會給出教詳細的解釋 以及根據c的語法猜c++的語法)
會一點操作系統的理論(虛擬內存和TLB) ==> 這一部分我不太自信 下一節會給出說明 先忽略此條
會瞎猜東西 可以從搖擺人三個字猜出籃球上的搖擺人估計跑得賊快 得到處跑(雖然是玩笑 但我覺得很重要的一個技能 nachos的實驗源碼看完估計得一段時間 所以很多實現我們得根據函數名去猜)
一顆被其他代碼折磨過已經對世界絕望的心 反正我耐性好 賊好
好了 如果你也和我一樣無聊 擁有以上大多數 這篇教程大概能夠看懂了 哦 對了 還得忍受我逼逼 我給別人將東西習慣把別人當白癡 講的賊細 別無他法的時候 就先選我的試試吧。。
## go
兩個部分講解 理論+實驗 理論部分比較少 賊少 重點實驗
#### 理論
講理論有點糾結 因為我當時聽助教哥哥講了一大堆理論之后 有點方 這都啥啥啥 我的代碼在哪 但是不講理論部分 直接做實驗 又感覺大多數人ctrl+c 然后 ctrl+v就把實驗做完了 這是一件挺無聊的事 所以我想講下理論 不過會很短 這部分的理論可能還沒我現在講的這段話的字數多 這些理論可能是錯的 可能是模糊的 但能夠支撐這次實驗完成(原諒我 我也只是個學生) 具體的理論知識依靠 剛哥講解 + 操作系統教材 + google
理論:
我們先回到我們要寫什么 TLB
TLB的概念是因為什么^_^ 虛擬內存
虛擬內存是因為什么^_^ ? 物理內存
所以我們得到下面的公式:
TLB-->虛擬內存-->物理內存
我們只要搞懂這三個就ok
物理內存: 計算機你想一下 根據能量守恒原則(高中的時候我們學過 這只是一個類比 別當真)
有一天你要在計算機上面跑一個程序
你得放點能量讓程序運行
你運行的程序需要能量從哪里來
==> 操作系統提供了能量 wjl官方術語翻譯一下就是
操作系統讓程序跑起來
好了 仔細想想這句話 操作系統讓程序跑起來 emmm 那我們在哪里跑 是不是要占一個空間 在這臺電腦里面(電腦包含操作系統 操作系統包含程序) 這個程序具體運行在電腦的哪個ka ka guo guo? 就叫物理地址
虛擬內存:有一天我們相當不幸 選了一個禿頂的專業 學了c語言 開始寫程序 你的程序也得在某個地方跑起來 程序的數據得放在某一個位置 那你放的位置要是和另外一個程序放的位置一樣 不就很尷尬(別人有個b=1放在2處 你有一個a=3也想放在2這個地方 就覆蓋了 所以你破壞了別人辛辛苦苦的變量b 就很尷尬 ok) 假設每個程序員寫程序的時候都得去考慮其他人寫的程序的數據放哪 這事兒賊惡心了 估計得忙死一大波人 作為一位優秀的程序員 我們都是優秀的甩鍋俠 所以程序數據的位置的尷尬我們交給操作系統的設計者背鍋 設想一種幸福的情況
我有一個程序A 別人有一個程序B 假設我們可以幸福的認為
A占領位置:0-30
B占領位置:0-30
A::0-30和B::0-30? ? 這兩個是不同的空間 A永遠影響不了B B也不關A鳥事 這是一件多么幸福的事呀 這就叫做虛擬內存
至于他們為什么看起來數值相同 但空間不同 就得看操作系統設計者那群背鍋俠的實現了 不過可以假設操作系統(為了方便 用C代替)總共擁有0-30的空間 定義A::2 = C::6, B::2 = C::7(這個定義其實就是虛擬地址轉物理地址) 這樣就可以實現數據位置的看起來相同 但實際上物理地址不同 至于為什么30的空間可以裝下60(A:30 + B:30 = 60)這就是操作系統的調度 一段時間內放AB的片段((A的片段 < 30 + B的片段<30) <= 30 滿足這個條件即可) 這是另外一個問題了
那操作系統怎么來轉換A::2 = C::6 呢 有一種叫做頁表的結構 片段這玩意太不專業了 所以我們用頁的概念來代替 根據上面 假設A有10個片段 即為A有10頁 每頁的起始地址不一樣 定義前三頁 我們記作A1, A2, A3 stAddr:代表頁起始地址 A1_stAddr = 1000, A2_stAddr = 2000, A3_stAddr = 3000, 數據項物理地址A_data_1=1001 A_data_2=2001 A_data_3=3001,? ? 我們現在先規定一個數組virTable={11, 21,31} 這里面放的A_data_1, A_data_2, A_data_3的虛擬地址? 有一個數組叫做pageTable={10,20,30} 叫做頁表 我們怎么根據virTable, pageTable來找數據的物理地址呢
開始我們的表演
先找virTable 獲取11 規定第一位為頁表項 1 第二位為offset(偏移地址) 1 根據頁表項找到起始地址 對應pageTable[0] 為10 執行加法10 * 100(100代表pageFrame)+1 = 1001 還是有丟丟神奇 我們找到他了 1001 就是物理地址 11是虛擬地址
TLB: 那pageTable放哪呀 哥 這個呢 放一個寄存器里面就太好了 但是你一想 寄存器可賊貴了 而且前面我是比較樂觀 A賊小 假設你用java(好吧 我在黑java)寫了一個程序 那程序又臭又長 還賊慢 一共1000000MB 那你得分100000頁( 假設) 我們的pageTable得記錄100000個數據 是不是發現寄存器不夠了 所以我們把他放在內存里(內存 賊大 真的 放個頁表那絕對夠)
stop
停下來之后想一想 我們有100000個頁表項 假設每個訪問一次 執行cpu->內存100000次 這個過程呢 就有點慢 因為內存便宜嘛 你什么時候見過便宜的東西跑得賊快的 (比如java女裝程序員就賊便宜 那程序速度..) 所以我們能不呢讓他快一點呢 這個時候TLB就上場了 我們放一部分頁表項在TLB里面 這樣TLB里面有頁表項的時候 直接訪問TLB獲取起始地址就可以了
TLB這種東西 總結來說 就是訪問TLB比寄存器慢 但比內存快 價格比寄存器便宜 但比內存貴
所以這種人和稀泥就賊合適
速度(cpu->TLB->get起始地址*100000) > 速度(cpu->內存->get起始地址* 100000)==>重要公式A
記住這個公式A 我們程序的實現就是這個公式A
TLB是一個中轉(理解這個)
#### 和nachos相關的理論總結
程序由頁表進行虛擬地址向物理地址的轉換(cpu->mem) 源代碼已經實現
程序為了提高 放了一個緩沖器TLB(此處存放一部分頁表項) 分為以下兩種情況
if(訪問頁表項在TLB中)
get起始地址
else
進行cpu->內存->get起始地址的訪問
程序原來未實現TLB進行的訪問是
進行cpu->內存->get起始地址的訪問
####實驗
很抱歉我BB了那么久 我們來講實驗吧 怎么轉換代碼
注意看我們理論總結 TLB else的地方 與第三點程序原來未實現TLB的訪問 你會發現他們簡直一摸一樣 都是? ? ? ? ? 進行cpu->內存->get起始地址的訪問
原來的程序是可以運行的 所以我們可以推測 "進行cpu->內存->get起始地址的訪問"
這個機制在源代碼中已經實現了 那就意味著 我們只要把原有的代碼復制粘貼到想要的地方就可以了(請記住這個觀點)
所以 原有的代碼段在哪找 所以我們得找到mem的實現 在這個地方插入TLB 那怎么找呢?
不用擔心 我們已經幫你找好了
先介紹這個 nachos的文件夾結構 從code目錄開始
在寫程序的過程中 我們得時刻查閱自己寫的對不對 就得把有關虛擬內存的部分顯示出來 如何顯示呢
好了 現在假設我們的實驗文件已經完成 我們所需要的命令是nachos -x ../test/add.noff -d a(在build.linux 下輸入這個)
那么 -x -d a 這三個參數是怎么回事
-x: 在/threads/main.cc 里面的main函數里面 實現了-x的解析 代表如果-x出現 加載-x后的參數 ../test/add.noff 賦值給userProgName 作為為用戶程序 讓用戶程序在nachos里面運行
-d | -a 在lib/debug.h 里面 實現了一個加做DEBUG的宏 在調試模式下 會輸出各種調試信息
-d(main.cc 解析) 開啟調試模式
-a代表輸出有關虛擬內存的分配(建議查看注釋 都有詳細的解釋 22到26行)
好了 我們開始正題 尋找"原有的代碼段" 首先發現 -x獲取之后 得到userProgName 然后在后續的過程中 調用space->Execute() 程序將會一直執行下去(main.cc)
space從哪里來 打開userProg/Addrspace.h? 發現定義了AddrSpace這個類 在.cc文件里面實現了Execute();? ==>AddrSpace.cc
為了方便 我直接寫出函數代碼調用流程
space->Execute() ==> kernel->machine->Run() ==>OneInstruction(instr); ==>/
stop
OneInstruction(instr); 這個東西是 每次執行一條指令 假設程序有50條指令 一條一條執行就可以把程序跑完 這就叫關鍵函數1
繼續
OneInstruction(instr);==>ReadMem(registers[PCReg], 4, &raw)
進入關鍵函數1細看 發現先經過ReadMem讀取用戶指令的值 獲取指令的值放入raw中 根據raw的值不同 執行不同的操作 比如raw = 123(1 23)計算2+3 raw=223(2 23) 計算2-3 2叫做操作碼(opcode) 23叫做操作數(這里的定義十分不準確 先這樣理解著)
我們的指令放到一個地方 根據前面 這個地方我們現在擁有的是虛擬地址 4看起來應該是地址長度 那么register[PCReg]應該就是虛擬地址存放地點了 ReadMem記作關鍵函數2
繼續
Machine::ReadMem(int addr, int size, int *value)
這是ReadMem原型 addr為虛擬地址 value為取值
ReadMem==>Translate() 將虛擬地址轉化為物理地址 獲取raw的值 放入value當中
注意這里的代碼段
exception = Translate(addr, &physicalAddress, size, FALSE);
? if (exception != NoException)
{
RaiseException(exception, addr);
return FALSE;
? }
exception檢驗取值過程是否出錯 若沒有出錯 程序繼續運行 如果錯了 返回異常 退出程序
后面的switch語句? 根據size的值? 獲取value的值? 然后給raw 比如地址為1234 放了值11 00 11 00 如果size = 1 取得 value = 11(11為8-bit 1byte) 若size = 3 value = 11 00 11(忽視小端序大端序的問題)
所以Translate執行的就是由虛擬地址轉換物理地址 起始從參數名也可以看出(請查看Translate的參數名 函數原型)
addr: 虛擬地址
physvalue 代表物理地址
這是very關鍵函數3 我們的原有的代碼段就是在這里進行的
繼續
進入Translate函數查看流程 根據我們以前的公式
此處標記為過程AAAA
1001
獲取頁表項序號(1): vpn = (unsigned) virtAddr / PageSize;
翻譯一下 1 = 11 / 10
? ? 獲取 offset = (unsigned) virtAddr % PageSize;
翻譯一下 1 = 11 % 10
獲取起始地址: pageFrame = entry->physicalPage;
翻譯一下: 100 = PageTable[0]
獲取物理地址 *physAddr = pageFrame * PageSize + offset;
翻譯一下? 1001 = 100 * 10 + 1(這里和我前面講的理論有點出入 湊合著理解吧 我才20歲 我想睡覺)
從PageFrame那里開始的地方 就是頁表轉換的過程
代碼如下
pageFrame = entry->physicalPage;==>從這里開始一直到Translate結束 就是 "原程序代碼"
我們要做的 根據前面所說 起始就是把這段代碼復制粘貼到合適的地方
那么 哪兒是合適的地方呢
讓我們來看看我們的TLB TLB中的 if else指出 如果PageTable[i]在TLB里面 通過TLB訪問PageTable[i] 如果不存在 就通過PageTable的表直接訪問 對應代碼下
for (entry = NULL, i = 0; i < TLBSize; i++)
? ? ? ? if (tlb[i].valid && (tlb[i].virtualPage == ((int)vpn))) {
entry = &tlb[i]; // FOUND!
break;
? }
if (entry == NULL) { // not found
? ? ? ? DEBUG(dbgAddr, "Invalid TLB entry for this virtual page!");
? ? ? ? return PageFaultException; // really, this is a TLB fault,
for循環里面進行了TLB的遍歷 如果存在 就直接獲取頁表項 然后獲取起始地址 進行轉換 如果不存在 拋出PageFaultException異常 (根據我們前面if else的說法 叫做繼續通過頁表訪問 頁表訪問原來是由過程AAAA進行的處理 但是現在我們已經return PageFaultException;出函數體了 過程AAAA無法執行 所以程序在打開TLB選項如果這樣運行 沒人幫忙擦屁股 就無法處理頁表項不在TLB中的情況 就會出錯) 所以 我們的初級任務達成
任務1:當頁表項不在TLB中 模仿原有的程序代碼 實現一個新的CPU->TLB->get起始地址(在return pageFaultException;之后實現)
小貼士:
我們要寫的代碼其實不多 既然叫模仿 其實復制粘貼就可以了
程序這里寫參數bug 活用ASSERTNOTREACED()調試程序
#### 開始模仿
wait 模仿啥 仔細想想 我們應該是模仿在 TLB 調用 在TLB中找不到表項之后 模仿擦屁股的過程 那么 關鍵字 TLB
emmmm TLB 這玩意 存在么
這個問題等同于
原有的關鍵機制 PagetTable在源代碼中 對應的是變量名PageTable
TLB在源碼用對應一個變量名叫做tlb的結構體
在Translate函數當中中 有一段
if tlb == null
do something
else
do tlb something
do something這一段使用原有的PageTable的調用 而程序為了能夠運行 在未實現TLB的時候 程序保證tlb永遠為null 所以我們得先弄明白
為什么在原來給出的源碼當中 TLB會默認被保證為null呢
這一段的代碼體現在
#ifdef USE_TLB (/machine/machine.cc中可見這段代碼)
? ? tlb = new TranslationEntry[TLBSize];
? ? for (i = 0; i < TLBSize; i++)
tlb[i].valid = FALSE;
? ? pageTable = NULL;
#else // use linear page table
? ? tlb = NULL;
? ? pageTable = NULL;
#endif
至于這一段 得先理解ifdef USE_TLB語句 這句話擴充一下就是
if USE_TLB define
等效于
如果USE_TLB定義過 則編譯ifdef代碼塊 否則編譯else處代碼塊 所以我們得先讓tlb != null定 那么我們得定義USE_TLB
#define USE_TLB
放一下這個語句在machine.h 的第一行 然后對nachos進行make 進行我們要輸入的命令 得到結果如圖
程序出錯了 謝天謝地 出錯是好事 你想 我們把TLB選項打開了 擦屁股的事沒有干 不出錯才有鬼 那么 為什么會出錯
看一下201 行 得到如圖
ASSERT 什么鬼
這個的定義在lib/debug.h里面 總結來說 ASSERT是這樣一個函數
ASSERT(條件A): 如果條件A為真 程序啥事都沒有 如果條件A為假 程序退出 并且打印出ASSERT在程序的哪一行 當前在201(translate.cc)行
// we must have either a TLB or a page table, but not both!
? ? ASSERT(tlb == NULL || pageTable == NULL);
? ASSERT(tlb != NULL || pageTable != NULL);
再來看下程序注釋 我的詞匯量為500的翻譯就是? TLB和PageTable有且只有一個(有的意思是指不為NULL)
舉例 TLB = NULL PageTable == NULL 程序 202不滿足 出錯
TLB != NULL PageTable != NULL 201不滿足 出錯
我們打印一下TLB和PageTable的值 添加以下代碼
221? ? #ifdef USE_TLB
222? ? ASSERT(tlb != NULL && pageTable != NULL);
223? ? #else
224? ? std::cout << "TLB " << tlb << "PageTable " << pageTable << std::endl;
225? ? ASSERT(tlb == NULL || pageTable == NULL);
226? ? ASSERT(tlb != NULL || pageTable != NULL);
227? ? #endif
make 然后繼續運行 效果如圖
程序繼續出錯 做到這里我覺得我們可以哭了 Invalid TLB emmm 說明我們的TLB的已經見效了 只是我們的算法沒有實現 沒有在TLB里面找到表項而已 我們處理完就萬事OK了
wait 沒有在TLB里面找到表項。。。啥意思 我們根據前面講的
TLB比較貴 放不下所有頁表 所以我們只是放了一部分
舉個例子 程序A有 1 2 3 4 5 TLB含有 1 2 表項 當查找2 的時候 在for循環里面遍歷能找到表項 直接獲取值 對應代碼(查看translate函數)在這
for (entry = NULL, i = 0; i < TLBSize; i++)
if (tlb[i].valid && (tlb[i].virtualPage == ((int)vpn))) {
entry = &tlb[i]; // FOUND!
break;
}
entry獲取值 然后進行虛擬地址到物理地址的運算
然后我們查看源代碼 如果不在tlb中 發現拋出異常 實現如下
return pageFaultException
還記得translate是由ReadMem調用 所以這個pageFaultException的值會返回給ReadMem
查看ReadMem 發現如下(查看ReadMem函數)
if (exception != NoException) {
RaiseException(exception, addr);
return FALSE;
}
顯然我們返回的是pageFaultException 不等于noException 程序轉而調用RaiseException 查看RaiseException 發現程序繼續調用ExceptionHandler()
在userprog/exception.cc 找到ExceptionHandler函數 結合錯誤信息 找到出錯的那一行 ASSERTNOTREACHED()? ==> 108 行 這里程序終止
對ASSERTTHREAD進行小小的解釋 大概可以理解為 程序只要運行到這就會報錯退出(見debug.h查看實現) 那為什么要有這個函數呢 因為程序有些地方按正常的流程絕對不應該運行到這 這樣的話會打印行數 方便我們調試 解釋這么多是因為后面我們調試bug的時候需要用到
為什么會運行到108行 因為沒有對pageFaultException進行處理(查看switch語句可以發現 根本沒有 case pageFaultException:)
那我們對這進行一下美化(在switch(which)下面一行添加) 代碼如下:
case PageFaultException:
60? ? ? ? kernel->machine->ourHandleTLB();
61? ? ? ? return ;
62? ? ? ? ASSERTNOTREACHED();
return ;==>
保證程序運行不會經過ASSERTNOTREACHED()
重新編譯 運行 可以得到一個無限循環 如圖
這是因為總有拋出異常后我們并沒有對異常進行處理 沒有去取物理地址之類的 我們并沒有對這進行處理
分析一下 程序執行完return ;返回RaiseException中 然后返回到ReadMem 返回false 程序其實啥都沒做 所以我們對程序進行一下處理 即對TLB不在的情況進行處理
在return;前面加上 kernnel->machine->ourHandleTLB(); kernel對象會調用==>machine對象然后去調用屬于==>machine對象的一個叫做ourHanleTLB()的函數 然后我們在ourHandleTLB對象里面進行擦屁股就行了
ourHanleTLB是我們自己定義的 所以我們得自己實現 現在machine.h里面加入
void ourHanleTLB() --> 放在public下面
然后在translate里面實現 代碼如圖
void Machine::ourHandleTLB()
259 {
260
261? ? entry = &pageTable[vpn];
262
263? ? if (entry->readOnly && writing) {? // trying to write to a read-only page
264? ? ? ? DEBUG(dbgAddr, "Write to read-only page at " << virtAddr);
265? ? ? ? return ReadOnlyException;
266? ? }
267? ? pageFrame = entry->physicalPage;
268
269? ? // if the pageFrame is too big, there is something really wrong!
270? ? // An invalid translation was loaded into the page table or TLB.
271? ? if (pageFrame >= NumPhysPages) {
272? ? ? ? DEBUG(dbgAddr, "Illegal pageframe " << pageFrame);
273? ? ? ? return BusErrorException;
274? ? }
275? ? entry->use = TRUE;? ? ? ? ? // set the use, dirty bits
276? ? if (writing)
277? ? ? ? entry->dirty = TRUE;
278? ? *physAddr = pageFrame * PageSize + offset;
279? ? ASSERT((*physAddr >= 0) && ((*physAddr + size) <= MemorySize));
280? ? DEBUG(dbgAddr, "phys addr = " << *physAddr);
281? ? return NoException;
282 }
對比一下translate函數 你會發現簡直一摸一樣 因為我一早就說 他們做的事差不多 然后我們運行一下 得到結果如下
開始報錯 size等等沒有定義 為什么沒有定義 因為我們在這個函數體里面根本沒有實現size的聲明 就像憑空出現的一樣 所以我們得定義他
wait size這些 原來的值是和虛擬內存關聯的 現在我們需要虛擬內存的值 但是ourHandleTLB的函數體里面 參數部分未傳入這些必要的值 那我們怎么獲取呢
采用全局變量 把size等等設置為全局變量 如下 這就是我們要保存的變量的類型聲明 namespace稍后解釋
namespace USE_TLB_NAME
67 {
68? ? int i;
69? ? unsigned int vpn, offset;
70? ? TranslationEntry *entry;
71? ? unsigned int pageFrame;
72
74? ? int virtAddr, * physAddr,? size;
75? ? bool writing;
78 }
接下來是賦值(在if(tlb == NULL 添加USE_TLB_NAME代碼)) 把原來的值保存下來 如圖 修改后的結果如圖
USE_TLB_NAME::vpn = vpn;
235? ? USE_TLB_NAME::offset = offset;
236? ? USE_TLB_NAME::physAddr = physAddr;
237? ? USE_TLB_NAME::writing = writing;
238? ? USE_TLB_NAME::size = size;
239? ? USE_TLB_NAME::virtAddr = virtAddr;
240
241? ? if (tlb == NULL) {? ? ? ? ? // => page table => vpn is index into table
對不起 剛才有一段關鍵代碼未貼 更改ourHandleException()如下
void Machine::ourHandleTLB()
286 {
287
288? ? TranslationEntry *entry;
289? ? unsigned int pageFrame;
290
291? ? entry = &pageTable[USE_TLB_NAME::vpn];
292
293? ? if (entry->readOnly && USE_TLB_NAME::writing) {? ? // trying to write to a read-only page
294? ? ? ? DEBUG(dbgAddr, "Write to read-only page at " << USE_TLB_NAME::virtAddr);
295? ? ? ? return ReadOnlyException;
296? ? }
297? ? pageFrame = entry->physicalPage;
298
299? ? // if the pageFrame is too big, there is something really wrong!
300? ? // An invalid translation was loaded into the page table or TLB.
301? ? if (pageFrame >= NumPhysPages) {
302? ? ? ? DEBUG(dbgAddr, "Illegal pageframe " << pageFrame);
303? ? ? ? return BusErrorException;
304? ? }
305? ? entry->use = TRUE;? ? ? ? ? // set the use, dirty bits
306? ? if (USE_TLB_NAME::writing)
307? ? ? ? entry->dirty = TRUE;
308? ? *USE_TLB_NAME::physAddr = pageFrame * PageSize + USE_TLB_NAME::offset;
309? ? ASSERT((*USE_TLB_NAME::physAddr >= 0) && ((*USE_TLB_NAME::physAddr + USE_TLB_NAME::size) <= MemorySize));
310? ? DEBUG(dbgAddr, "phys addr = " << *USE_TLB_NAME::physAddr);
311? ? flag = TRUE;
312? ? return NoException;
313 }
編譯運行程序
程序繼續報錯 讓我們來分析一下 程序陷入無限循環 處理完這個TLB的情況的時候 并沒有進行下一頁的處理 然后我們查看ReadMem函數處的調用? PageTableException返回的時候 程序已經正確處理了 但是返回的還是FALSE 所以報錯 讓我們處理一下這里
更改代碼 代碼如圖
exception = Translate(addr, &physicalAddress, size, TRUE);
163? ? if (exception != NoException) {
164? ? ? ? RaiseException(exception, addr);
165? ? ? ? if(flag == FALSE) // ok
166? ? ? ? ? ? ? ? return FALSE;
flag = FALSE;
167? ? }
程序加了if語句 flag是什么?
flag是我們定義的一個全局變量(在.translate.cc定義 bool flag = FALSE) 看下我們的TLB處理成功沒
如果成功 為TRUE
否則為默認值FALSE?
在ourHandler結尾粗 return Noexception之前一行處加上
flag = TRUE
所以如果返回TRUE 說明TLB處理完畢 不用管 繼續執行下面代碼 并把flag 重置為 FALSE 方便下一次檢驗
如果返回FALSE 程序return 程序失敗
實驗結果運行 我們發現 誒嘿 還是無限循環 不過這次要好多了 觀察value處 發現我們的實驗代碼可以取值了 只是取到某個值的時候 程序就崩了 那么。。我們來調試一下 gdb ? 抱歉哎完全不會 我們換個玩意 比如前面扯了一大隊的ASSERTNOTREACHED()-->讓程序終止 首先聲明一個全局變量int count = 0(取10是因為取10頁的次數 方便觀察)
在readMem加入如下代碼
? if(++count == 10) ASSERTNOTREACHED();
同flag一樣 count是一個全局變量 定義在文件開頭 10次之后停止
發現程序在負數
Invalid TLB entry for this virtual page!
phys addr = 388
value read = -1346437104
那里停下來 看一下正常流程應該是啥樣 去掉USE_TLB宏(注釋掉USE_TLB的定義 在Machine.h里面 ) 比對正常運行程序 發現是因為指令分為兩種模式 read write 沒有處理write函數 于是我們在writeMem里面加入和ReadMem相同的邏輯(加入前面的flag判斷) 去掉我們的調試程序 恢復USE_TLB宏定義 如圖
bingo 程序跑通了 完美 (不完美的地方還有很多 其實 比如pageTable在哪里賦予的值 我懶得寫了。。 tlb里面的頁表項是怎么放進去的 而且發現跑出來的結果全是INvalid tlb里里面根本沒有值 怎么賦值。。。 懶。。。)
stop at here
自己根據這個思路實現置換吧 反正都一樣 推薦FIFO算法 簡單