## 一些小话
前段时间因为一些杂七杂八的事情 被逼无奈 赶各种作业 再加上某个姓张的贱人 欺骗我说计网课设得交了 就一直把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算法 简单