实验三:缓存与虚拟内存
一.题目要求:
第一点:题目要求我们实现TLB管理和全局的反向页表的管理和维护。
第二点:题目要求我们实现按需调页的功能。
第三点:题目要求我们实现懒加载机制。
二.问题解决思路
此处我们将按照正常的进程执行流程来展开本实验的思路,涉及之前的实验的细节将跳过,只展开与本实验相关的流程细节,过程中会展示部分代码的细节以方便理解,此外,本实验涉及几个需要另外实现的类,我们会在下一个环节(变量说明)详细介绍他们。
在正式开始之前,我们需要明确一个非常关键的点:整个执行过程中,要确保硬件始终只能看到TLB这一层,即所有的底层操作都只能围绕TLB。另外,我们还要确定TLB、反向页表等的结构,TLB沿用上一个实验中的页表项的结构,他将具有以下几个状态位:valid,used,dirty。反向页表将使用java提供的hashmap来实现
当发生缺页时,我们将使用二次机会来选择受害者,这意味着我们需要一个关于当前内存的信息的副本(不用担心一致性问题,这个副本所看到的内容是实时存在于内存和反向页表的)
受害者将存储在以下的结构(交换区)中:
以上是需要补充的部分结构,现在,让我们正式进入流程:
当一个进程执行前,系统会获取进程的coff文件内容为其构建分段分页信息,与之前不同的是,由于使用了TLB的思想,我们并不会为进程构建一个页表,而是先将各个页先存储到交换区中,并在反向页表中注册该进程的虚拟页信息(暂时不分配物理帧),不过这里为了区分,我们将coff页存储到专门的区域,堆栈页和参数页存储到交换区
存储堆栈页和参数页:
存储coff页:
之后需要调用这些页时,我们将从这些结构里面去获取,这也就实现了懒加载机制。
然后进程正常取指令执行指令,它通过访问TLB来获取该页
如果TLB未命中,就抛出一个TLBMiss异常,再通过handleTLBFault()去处理异常,首先看反向页表里面该页是否分配了物理帧
如果分配了物理帧,就随机选择一个TLB作为受害者,将所需的页信息换入(这里同样不必担心一致性问题,TLB、反向页表、内存副本所看到的都是同样的内容,无需在换入换出时去刻意维护)
如果还没有分配物理帧,就从内存中选择空闲帧,
选择空闲帧会遇到以下几种情况:
内存还有空闲帧,则正常的分配,如果内存没有空闲帧,则通过二次机会选择一个帧作为受害者
该受害者将会被移出内存至交换区,这里会在交换区为该页分配一个索引,方便之后我们需要时可以顺利找到他,反向页表里面该页的valid变为false,分配的物理帧变为-1。
然后我们获得了一个可用的物理帧,接下来会遇到以下几种情况:
1.需要调用的是coff页,且第一次加载,则在反向页表里面更新信息,注意第一次加载时我们要获取该coff的只读信息,同时将dirty和used变为false,之后在硬件访问TLB时会对这两个位进行操作。
2.需要调用的是堆栈页或参数页,且第一次加载,则在反向页表里面更新信息,注意第一次加载时我们要将dirty和used变为false,之后在硬件访问TLB时会对这两个位进行操作。
3.需要调用的页已经加载过,则该页在交换区里面,我们通过交换区获得该页的索引从而取出该页,分配至内存,在反向页表里面更新信息。
通过以上流程我们便完成了一个简单的按需调页。
值得注意的是,当内存过小时,这种方案会显得吃力,即出现抖动现象(目前的测试是1个页时执行echo程序出现明显的抖动,大于1页顺利执行)
当进程被抢占而需要交出cpu时,他要将现场保留,之后换回自己时,再恢复现场
保存现场:
恢复现场:
当进程执行完正常结束时,需要释放资源,需要释放的包括:反向页表里面关于该进程的信息,交换区里面关于该进程的信息
至此,我们已经顺利的在实验要求下完成了一个进程的执行
三.变量说明:
CoffSectionAddress类:存储了关于coff段的信息,包括偏移,段号等信息,用于懒加载的信息获取
InvertedPageTable类:反向页表的相关操作,包括进程信息的注册,为进程虚拟页与物理帧建立一个链接,为操作系统提供服务。详细的包括插入一个进程的页信息、删除进程的页信息、设置页表项信息、换出时解除该页的物理帧绑定,换入时恢复该页的物理帧绑定、二次机会获取受害页
SecondChanceReplacement类:二次机会的算法提供者,InvertedPageTable类通过该类通过的服务选择下一个受害页
SwapperController类:交换区信息,为每个进程提供了堆栈页和参数页的懒加载的暂存场所,为按需调页提供了换出的暂存场所,会为进程的每个虚拟页维护一个索引信息,需要时将该页存到该索引对应的区域,通过该索引可以获取之前存储的页内容
TranslationEntryWithPid类:内存的一个副本,存储了每个物理帧被哪个进程的哪个页占用
VirtualPageFlag类:为进程与虚拟页提供了一个绑定,因为反向页表中是以进程加虚拟页号来搜索物理帧,可以通过该类来提供索引信息
VMKernel类:继承了UserKernel
VMProcess类:继承了UserProcess,在其基础上进行升级,取消了进程页表的设置,用TLB取缔,可以处理TLBMiss异常,确保完成按需调页,在初始化时采用懒加载模式,优化执行效率
四.源代码:
CoffSectionAddress类代码:
public class CoffSectionAddress {
private int sectionNumber;
private int pageOffset;
public int getSectionNumber() {
return sectionNumber;
}
public CoffSectionAddress(int sectionNumber, int pageOffset) {
this.sectionNumber = sectionNumber;
this.pageOffset = pageOffset;
}
public void setSectionNumber(int sectionNumber) {
this.sectionNumber = sectionNumber;
}
public int getPageOffset() {
return pageOffset;
}
public void setPageOffset(int pageOffset) {
this.pageOffset = pageOffset;
}
}
InvertedPageTable类代码:
//全局的反向页表 保存在真正内存位置的页的虚拟地址以及拥有该页的进程的信息
public class InvertedPageTable {
//全局的反向页表
private static HashMap<VirtualPageFlag, TranslationEntry> GlobalInvertedPageTable = new HashMap<VirtualPageFlag, TranslationEntry>();
//全局的一个 页表 存放所有的物理页(对应 进程) 对应进程的物理页 的一个副本
public static TranslationEntryWithPid[] PhysicalPageCopy = new TranslationEntryWithPid[Machine.processor().getNumPhysPages()];
//二次机会
private static SecondChanceReplacement secondChanceReplacement = new SecondChanceReplacement();
private InvertedPageTable() {
}
//向反向页表中插入一页
public static boolean insertEntry(int pid, TranslationEntry entry) {
VirtualPageFlag virtualPageFlag = new VirtualPageFlag(pid, entry.vpn);
if (GlobalInvertedPageTable.containsKey(virtualPageFlag)) {
return false;
}
GlobalInvertedPageTable.put(virtualPageFlag, entry);
//如果则虚拟页在物理页中,而tlb可以直接由处理器使用。
if (entry.valid) {
PhysicalPageCopy[entry.ppn] = new TranslationEntryWithPid(GlobalInvertedPageTable.get(virtualPageFlag), pid);
PhysicalPageCopy[entry.ppn].getTranslationEntry().valid = true;
}
return true;
}
//将某进程的某虚拟页 从反向页表中删除
public static TranslationEntry deleteEntry(int pid, int vpn) {
VirtualPageFlag virtualPageFlag = new VirtualPageFlag(pid, vpn);
TranslationEntry entry = GlobalInvertedPageTable.get(virtualPageFlag);
if (entry == null) {
return null;
}
//如果则虚拟页在内存中,则将其清空
if (entry.valid) {
PhysicalPageCopy[entry.ppn] = null;
}
return entry;
}
//设置某进程的某一个TranslationEntry
public static void setEntry(int pid, TranslationEntry newEntry) {
VirtualPageFlag virtualPageFlag = new VirtualPageFlag(pid, newEntry.vpn);
//如果此TranslationEntry本身就不在反向页表中
if (!GlobalInvertedPageTable.containsKey(virtualPageFlag)) {
return;
}
//从反向页表中取出旧TranslationEntry
TranslationEntry oldEntry = GlobalInvertedPageTable.get(virtualPageFlag);
if (oldEntry.valid) {
if (PhysicalPageCopy[oldEntry.ppn] == null) {
return;
}
PhysicalPageCopy[oldEntry.ppn] = null;
}
GlobalInvertedPageTable.put(virtualPageFlag, newEntry);
if (newEntry.valid) {
if (PhysicalPageCopy[newEntry.ppn] != null) {
return;
}
PhysicalPageCopy[newEntry.ppn] = new TranslationEntryWithPid(GlobalInvertedPageTable.get(virtualPageFlag), pid);
PhysicalPageCopy[newEntry.ppn].getTranslationEntry().valid = true;
}
}
//激活
public static void liveEntry(int pID, TranslationEntry entry) {
VirtualPageFlag key = new VirtualPageFlag(pID, entry.vpn);
if (!GlobalInvertedPageTable.containsKey(key)) {
return;
}
TranslationEntry oldEntry = GlobalInvertedPageTable.get(key);
oldEntry.valid = true;
oldEntry.ppn = entry.ppn;
PhysicalPageCopy[oldEntry.ppn] = new TranslationEntryWithPid(GlobalInvertedPageTable.get(key), pID);
PhysicalPageCopy[oldEntry.ppn].getTranslationEntry().valid = true;
}
//禁用
public static void invalidEntry(int pID, TranslationEntry entry) {
VirtualPageFlag key = new VirtualPageFlag(pID, entry.vpn);
if (!GlobalInvertedPageTable.containsKey(key)) {
return;
}
TranslationEntry oldEntry = GlobalInvertedPageTable.get(key);
PhysicalPageCopy[oldEntry.ppn] = null;
oldEntry.valid = false;
oldEntry.ppn = -1;
}
public static void updateEntry(int pID, TranslationEntry entry) {
VirtualPageFlag key = new VirtualPageFlag(pID, entry.vpn);
if (GlobalInvertedPageTable.containsKey(key)) {
return;
}
TranslationEntry oldEntry = GlobalInvertedPageTable.get(key);
TranslationEntry newEntry = mix(entry, oldEntry);
System.out.println(newEntry.valid);
if (oldEntry.valid) {
if (PhysicalPageCopy[oldEntry.ppn] == null) {
return;
}
PhysicalPageCopy[oldEntry.ppn] = null;
}
GlobalInvertedPageTable.put(key, newEntry);
if (newEntry.valid) {
if (PhysicalPageCopy[newEntry.ppn] != null) {
return;
}
PhysicalPageCopy[newEntry.ppn] = new TranslationEntryWithPid(GlobalInvertedPageTable.get(key), pID);
}
}
private static TranslationEntry mix(TranslationEntry neww, TranslationEntry old) {
if (neww.dirty || old.dirty) {
neww.dirty = true;
}
if (neww.used || neww.used) {
neww.used = true;
}
return neww;
}
//获取 某进程 某虚拟页下对应的TranslationEntry
public static TranslationEntry getEntry(int pid, int vpn) {
VirtualPageFlag virtualPageFlag = new VirtualPageFlag(pid, vpn);
if (GlobalInvertedPageTable.containsKey(virtualPageFlag)) {
return GlobalInvertedPageTable.get(virtualPageFlag);
}else {
return null;
}
}
//选取被置换的页
public static TranslationEntryWithPid getVictimPage() {
TranslationEntryWithPid entry = null;
int i = secondChanceReplacement.findSwappedPage();
entry = PhysicalPageCopy[i];
PhysicalPageCopy[i] = null;
return entry;
}
public static void print() {
for(int i = 0; i<PhysicalPageCopy.length;i++) {
if(PhysicalPageCopy[i]!=null) {
VirtualPageFlag virtualPageFlag = new VirtualPageFlag(PhysicalPageCopy[i].getPid(), PhysicalPageCopy[i].getTranslationEntry().vpn);
System.out.println("进程"+PhysicalPageCopy[i].getPid()+" 拥有 "+PhysicalPageCopy[i].getTranslationEntry().vpn+" 在 "+PhysicalPageCopy[i].getTranslationEntry().ppn);
System.out.println(GlobalInvertedPageTable.get(virtualPageFlag).valid+" "+GlobalInvertedPageTable.get(virtualPageFlag).used+" "+GlobalInvertedPageTable.get(virtualPageFlag).dirty);
}
}
}
public static void print(int i) {
if(PhysicalPageCopy[i]!=null)
System.out.println("进程"+PhysicalPageCopy[i].getPid()+" 拥有 "+PhysicalPageCopy[i].getTranslationEntry().vpn+" 在 "+PhysicalPageCopy[i].getTranslationEntry().ppn);
}
}
SecondChanceReplacement类代码:
/**
* 二次机会算法
*/
public class SecondChanceReplacement {
private int currentPhysicalPage;
private int replacePhysicalPage;
public SecondChanceReplacement() {
currentPhysicalPage = 0;
replacePhysicalPage = 0;
}
public int findSwappedPage() {
if (!UserKernel.memoryLinkedList.isEmpty()) {
replacePhysicalPage = UserKernel.memoryLinkedList.removeFirst();
return replacePhysicalPage;
} else {
InvertedPageTable.print();
//System.out.println(currentPhysicalPage);
while (InvertedPageTable.PhysicalPageCopy[currentPhysicalPage].getTranslationEntry().used) {
System.out.println(UserKernel.currentProcess().sectionlength +" "+ InvertedPageTable.PhysicalPageCopy[currentPhysicalPage].getTranslationEntry().vpn);
//if(UserKernel.currentProcess().processID == InvertedPageTable.PhysicalPageCopy[currentPhysicalPage].getPid() && UserKernel.currentProcess().sectionlength > InvertedPageTable.PhysicalPageCopy[currentPhysicalPage].getTranslationEntry().vpn) {
InvertedPageTable.PhysicalPageCopy[currentPhysicalPage].getTranslationEntry().used = false;
InvertedPageTable.updateEntry( InvertedPageTable.PhysicalPageCopy[currentPhysicalPage].getPid(), InvertedPageTable.PhysicalPageCopy[currentPhysicalPage].getTranslationEntry());
//}
currentPhysicalPage = ++currentPhysicalPage % Machine.processor().getNumPhysPages();
}
replacePhysicalPage = currentPhysicalPage;
currentPhysicalPage++;
currentPhysicalPage %= Machine.processor().getNumPhysPages();
System.out.println("拿出 "+replacePhysicalPage);
return replacePhysicalPage;
}
}
}
SwapperController类代码:
//将物理页中 不需要的置换到 磁盘上 将需要的 置换到内存中 物理内存一共16页
public class SwapperController {
private int pageSize;
//要交换的文件
private HashMap<Integer, byte[]> swapFile;
//内存中的文件名
private String swapFileName;
private HashMap<VirtualPageFlag, Integer> swapTable;
private HashMap<VirtualPageFlag, Boolean> swapReadonly;
private HashSet<VirtualPageFlag> unallocated;//未分配列表
private LinkedList<Integer> availableLocations;//可用位置
private static SwapperController instance = null;
private SwapperController(String swapFileName) {
pageSize = Processor.pageSize;
this.swapFileName = swapFileName;
swapTable = new HashMap<VirtualPageFlag, Integer>();
swapReadonly = new HashMap<VirtualPageFlag, Boolean>();
unallocated = new HashSet<VirtualPageFlag>();
availableLocations = new LinkedList<Integer>();
swapFile = new HashMap<Integer, byte[]>();
//swapFile = ThreadedKernel.fileSystem.open(swapFileName, true);
if (swapFile == null) {
System.out.println("无法打开此文件");
}
//重制交换空间 将空间置0
//byte[] zeroBuffer = new byte[Processor.pageSize * Machine.processor().getNumPhysPages()*100];
//swapFile.write(zeroBuffer, 0, zeroBuffer.length);
}
public void deletePosition(int pid, int vpn) {
VirtualPageFlag key = new VirtualPageFlag(pid, vpn);
if (!swapTable.containsKey(key)) return;
int availableLocation = swapTable.remove(key);
if(swapReadonly.containsKey(key))
swapReadonly.remove(key);
availableLocations.add(availableLocation);
}
public static SwapperController getInstance(String swapFileName) {
if (instance == null) {
instance = new SwapperController(swapFileName);
}
return instance;
}
//读取交换文件
public byte[] readFromSwapFile(int pid, int vpn) {
int position = findEntry(pid, vpn);
System.out.println("从读出 "+position);
if (position == -1) {
return new byte[pageSize];
}
byte[] memcopy = swapFile.get(position).clone();
if(memcopy == null) {
return new byte[pageSize];
}
//byte[] memcopy = new byte[pageSize];
//int length = swapFile.read(position * pageSize, memcopy, 0, pageSize);
//if (length == -1) {
//System.out.println("wrong");
//return new byte[pageSize];
//}
System.out.println("读出Swap: "+memcopy);
return memcopy;
}
public boolean RD(int pid, int vpn) {
VirtualPageFlag key = new VirtualPageFlag(pid, vpn);
return swapReadonly.remove(key);
}
//写入交换文件
public int writeToSwapFile(int pid, int vpn, byte[] page, int offset, boolean readOnly) {
//先 为进程在swapTable中分配位置 表示已经交换
int position = allocatePosition(pid, vpn);
System.out.println("写入到"+position);
if (position == -1) {
return -1;
}
//写入交换文件
System.out.println("写入Swap: "+page);
byte[] copy = page.clone();
swapFile.put(position, copy);
VirtualPageFlag key = new VirtualPageFlag(pid, vpn);
swapReadonly.put(key, readOnly);
for(int i = 0; i < swapFile.size(); i++) {
System.out.println(swapFile.get(i));
}
//System.out.println(position);
//int i = swapFile.write(position * pageSize, page, offset, pageSize);
//if (i == -1) {
//System.out.println("写入文件失败");
//}
return position;
}
public void insertUnallocatedPage(int pid, int vpn) {
VirtualPageFlag key = new VirtualPageFlag(pid, vpn);
unallocated.add(key);
System.out.println("分配"+key.getPid()+"的"+key.getVirtualPageNum());
}
//在swapTable中为进程分配位置
public int allocatePosition(int pid, int vpn) {
VirtualPageFlag virtualPageFlag = new VirtualPageFlag(pid, vpn);
if (unallocated.contains(virtualPageFlag)) {
unallocated.remove(virtualPageFlag);
if (availableLocations.isEmpty()) {
availableLocations.add(swapTable.size());
}
//分配位置
int index = availableLocations.removeFirst();
swapTable.put(virtualPageFlag, index);
System.out.println("Swap分配"+pid+"的"+vpn+"到自己的"+index);
System.out.println(swapTable.size());
return index;
} else {
//位置已经分配 直接返回
int index = -1;
index = swapTable.get(virtualPageFlag);
System.out.println("Swap已经分配"+pid+"的"+vpn+"到自己的"+index);
if (index == -1) {
return -1;
}
return index;
}
}
//获取进程虚拟页的 位置
private int findEntry(int pid, int vpn) {
Integer position = swapTable.get(new VirtualPageFlag(pid, vpn));
if (position == null)
return -1;
else
return position.intValue();
}
}
TranslationEntryWithPid类代码:
//pid与 TranslationEntry的对应
public class TranslationEntryWithPid {
private TranslationEntry translationEntry;
private int pid;
public TranslationEntryWithPid(TranslationEntry translationEntry, int pid) {
this.translationEntry = translationEntry;
this.pid = pid;
}
public TranslationEntry getTranslationEntry() {
return translationEntry;
}
public void setTranslationEntry(TranslationEntry translationEntry) {
this.translationEntry = translationEntry;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
}
VirtualPageFlag类代码:
//管理 进程号 以及 虚拟页数 (反向页表 )
public class VirtualPageFlag {
private int pid;
private int VirtualPageNum;
public VirtualPageFlag(int pid, int virtualPageNum) {
this.pid = pid;
VirtualPageNum = virtualPageNum;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public int getVirtualPageNum() {
return VirtualPageNum;
}
public void setVirtualPageNum(int virtualPageNum) {
VirtualPageNum = virtualPageNum;
}
@Override
public boolean equals(Object that) {
if (that == null) return false;
return this.pid == ((VirtualPageFlag) that).pid && this.VirtualPageNum == ((VirtualPageFlag) that).VirtualPageNum;
}
@Override
public int hashCode() {
return (new Integer(pid).toString() + new Integer(VirtualPageNum).toString()).hashCode();
}
}
VMKernel类代码:
/**
* A kernel that can support multiple demand-paging user processes.
*/
public class VMKernel extends UserKernel {
/**
* Allocate a new VM kernel.
*/
public VMKernel() {
super();
}
/**
* Initialize this kernel.
*/
public void initialize(String[] args) {
super.initialize(args);
}
/**
* Test this kernel.
*/
public void selfTest() {
super.selfTest();
}
/**
* Start running user programs.
*/
public void run() {
super.run();
}
/**
* Terminate this kernel. Never returns.
*/
public void terminate() {
super.terminate();
}
// dummy variables to make javac smarter
private static VMProcess dummy1 = null;
private static final char dbgVM = 'v';
//空页
//保证访问内存互斥的锁
}
VMProcess类代码:
/**
* A <tt>UserProcess</tt> that supports demand-paging.
*/
public class VMProcess extends UserProcess {
/**
* Allocate a new process.
*/
public VMProcess() {
super();
allocatedPages = new LinkedList<Integer>();
lazyLoadPages = new HashMap<Integer, CoffSectionAddress>();
tlbBackUp = new TranslationEntry[Machine.processor().getTLBSize()];
for (int i = 0; i < tlbBackUp.length; i++) {
tlbBackUp[i] = new TranslationEntry(-1, -1, false, false, false, false);
}
pagelength = Machine.processor().getNumPhysPages();
}
protected void swapOut(int pid, int vpn) {
TranslationEntry entry = InvertedPageTable.getEntry(pid, vpn);
if (entry == null) {
return;
}
if (!entry.valid) {
return;
}
for (int i = 0; i < Machine.processor().getTLBSize(); i++) {
TranslationEntry tlbEntry = Machine.processor().readTLBEntry(i);
//遍历tbl 置换出对应的页
if (tlbEntry.vpn == entry.vpn && tlbEntry.ppn == entry.ppn && tlbEntry.valid) {
byte[] memory = Machine.processor().getMemory();
int bufferOffset = Processor.makeAddress(entry.ppn, 0);
byte[] data = new byte[pageSize];
System.arraycopy(memory, entry.ppn * pageSize, data, 0, pageSize);
System.out.println("从"+entry.ppn * pageSize+"开始为"+data);
SwapperController.getInstance(SwapFileName).writeToSwapFile(pid, vpn, data, bufferOffset, tlbEntry.readOnly);
//将反向页表中旧的页换掉
//表示 不在内存中
InvertedPageTable.invalidEntry(pid, tlbEntry);
tlbEntry.valid = false;
//读取反向页表中对应的新页
entry = InvertedPageTable.getEntry(pid, vpn);
//写入tlb
Machine.processor().writeTLBEntry(i, tlbEntry);
break;
}
}
System.out.println("换出成功");
}
//取得一个物理页 然后将 发生页错误的虚拟页 装到对应的物理页中
protected void swapIn(int ppn, int vpn) {
TranslationEntry entry = InvertedPageTable.getEntry(processID, vpn);
if (entry == null) {
return;
}
if (entry.valid) {
return;
}
boolean dirty, used;
boolean readOnly = false;
if (lazyLoadPages.containsKey(vpn)) {
System.out.println("第一次get"+vpn);
readOnly = lazyLoad(vpn, ppn).isReadOnly();
dirty = false;
used = false;
} else {
System.out.println("不是第一次/不是coff");
//如果不是首次加载此coff 则将此物理页 从交换文件 复制到主存中
byte[] memory = Machine.processor().getMemory();
if(vpn<sectionlength)
readOnly = SwapperController.getInstance(SwapFileName).RD(processID, vpn);
byte[] page = SwapperController.getInstance(SwapFileName).readFromSwapFile(processID, vpn);
//SwapperController.getInstance(SwapFileName).deletePosition(processID, vpn);
//src:源数组 srcPos:源数组要复制的起始位置 dest:目标数组 destPos:目标数组复制的起始位置 length:复制的长度
Machine.processor().setMemory(page, 0, ppn * pageSize);
//System.arraycopy(page, 0, memory, ppn * pageSize, pageSize);
//memory = Machine.processor().getMemory();
dirty = false;
used = false;
}
TranslationEntry newEntry = new TranslationEntry(vpn, ppn, true, readOnly, used, dirty);
//更改反向页表中此页的状态
//InvertedPageTable.setEntry(processID, newEntry);
InvertedPageTable.setEntry(processID, newEntry);
//InvertedPageTable.print(ppn);
}
protected int swap(int vpn) {
TranslationEntry entry = InvertedPageTable.getEntry(processID, vpn);
if (entry.valid)
return entry.ppn;
int ppn = getFreePage();
System.out.println(processID+"想访问"+vpn+" 将装载到"+ppn);
swapIn(ppn, vpn);
return ppn;
}
protected CoffSection lazyLoad(int vpn, int ppn) {
CoffSectionAddress coffSectionAddress = lazyLoadPages.remove(vpn);
if (coffSectionAddress == null) {
return null;
}
CoffSection section = coff.getSection(coffSectionAddress.getSectionNumber());
section.loadPage(coffSectionAddress.getPageOffset(), ppn);
return section;
}
protected int getFreePage() {
//获取一个物理页
int ppn = VMKernel.getFreePage();
if (ppn == -1) {
//如果没有物理页了 需要选择一页牺牲掉
TranslationEntryWithPid victim = InvertedPageTable.getVictimPage();
ppn = victim.getTranslationEntry().ppn;
System.out.println("缺页 换"+ppn+"的"+victim.getTranslationEntry().vpn);
swapOut(victim.getPid(), victim.getTranslationEntry().vpn);
}else {
}
return ppn;
}
//重写readVirtualMemory方法
public int readVirtualMemory(int vaddr, byte[] data, int offset, int length) {
pageLock.acquire();
int vpn = Machine.processor().pageFromAddress(vaddr);
//从反向页表读出对应的TranslationEntry
//swap(vpn);
TranslationEntry entry = InvertedPageTable.getEntry(processID, vpn);
System.out.println("想读取"+vpn+"在"+entry.ppn);
for(int i = 0; i < 4; i++) {
System.out.println(Machine.processor().readTLBEntry(i).vpn+" "+Machine.processor().readTLBEntry(i).ppn);
}
if (!entry.valid) {
//表示 此页还没有被加载到物理页中 需要重新分配物理页
int ppn = getFreePage();
//System.out.println("选择了"+ppn);
System.out.println("读时"+processID+"将"+vpn+" 将装载到"+ppn);
swapIn(ppn, vpn);
}
//entry.used = true;
//在反向页表中 更新对应的页
InvertedPageTable.setEntry(processID, entry);
pageLock.release();
//从虚拟内存地址读出到data中
//偏移量与长度都要为正数,偏移量+长度<=总的数据长度
Lib.assertTrue(offset >= 0 && length >= 0 && offset + length <= data.length);
//getMemory会返回主程序的数组
byte[] memory = Machine.processor().getMemory(); //获得物理数组
//计算剩下的页表字节个数
if(length>(pageSize*numPages-vaddr))
length=pageSize*numPages-vaddr;
//计算能够传输的数据的大小,如果data数组中存不下length,则减小length(传输字节数)
if(data.length-offset<length)
length=data.length-offset;
//转换成功的字节数
int transferredbyte=0;
do{
//计算页号
int pageNum=Processor.pageFromAddress(vaddr+transferredbyte);
//页号大于 页表的长度 或者 为负 是异常情况
if(pageNum<0||pageNum>=numPages)
return 0;
//计算页偏移量
int pageOffset=Processor.offsetFromAddress(vaddr+transferredbyte);
//计算剩余页的容量
int leftByte=pageSize-pageOffset;
//计算下一次传送的数量:剩余页容量和需要转移的字节数中较小者
int amount=Math.min(leftByte, length-transferredbyte);
//计算物理内存的地址
int realAddress=entry.ppn*pageSize+pageOffset;
//将物理内存的东西传输到虚拟内存
System.arraycopy(memory, realAddress, data,offset+transferredbyte, amount);
System.out.println(new String(data));
//修改传输成功的字节数
transferredbyte=transferredbyte+amount;
}while(transferredbyte<length);
return transferredbyte;
//return super.readVirtualMemory(vaddr, data, offset, length);
}
public int writeVirtualMemory(int vaddr, byte[] data, int offset, int length) {
pageLock.acquire();
int vpn = Processor.pageFromAddress(vaddr);
//由于进程只能看到TLB 所以需要将需要的页换入
swap(vpn);
TranslationEntry entry = InvertedPageTable.getEntry(processID, vpn);
System.out.println("想写入"+vpn+"在"+entry.ppn);
//entry.dirty = true;
//entry.used = true;
InvertedPageTable.setEntry(processID, entry);
pageLock.release();
Lib.assertTrue(offset >= 0 && length >= 0 && offset + length <= data.length);
//物理内存
byte[] memory = Machine.processor().getMemory();
//写内存的长度如果超过页剩余量
if(length>(pageSize*numPages-vaddr))
length=pageSize*numPages-vaddr;
//如果数组中要写的长度比给定的小,则给length减为数组剩余的长度
if(data.length-offset<length)
length=data.length-offset;
int transferredbyte=0;
do{
//此函数返回给定地址的页号
int pageNum=Processor.pageFromAddress(vaddr+transferredbyte);
if(pageNum<0||pageNum>=numPages)
return 0;
//此函数返回给定地址的页偏移量
int pageOffset=Processor.offsetFromAddress(vaddr+transferredbyte);
//页剩余的字节数
int leftByte=pageSize-pageOffset;
//设置本次转移的数量
int amount=Math.min(leftByte, length-transferredbyte);
int realAddress=entry.ppn*pageSize+pageOffset;
//从虚拟内存写入到物理内存(判断是否只读)
if(!entry.readOnly) {
System.arraycopy(data, offset+transferredbyte, memory, realAddress, amount);
}
//改变写成功的字节数
transferredbyte=transferredbyte+amount;
}while(transferredbyte<length);
return transferredbyte;
//return super.writeVirtualMemory(vaddr, data, offset, length);
}
protected TranslationEntry AllocatePageTable(int vpn) {
return InvertedPageTable.getEntry(processID, vpn);
}
/**
* Save the state of this process in preparation for a context switch.
* Called by <tt>UThread.saveState()</tt>.
*/
/**
* 为了解决这个问题,您必须保证至少一个进程在上下文切换之前保证有2个TLB翻译以及内存中的2个对应页
* 。这样,至少有一个进程能够在双误指令上取得进展。通过在上下文切换中保存和恢复TLB的状态,可以减少Live锁的效果,
* 但是同样的问题可能发生在很少的物理内存页上。在这种情况下,在加载指令页和数据页之前,2个进程可能会以类似的方式被卡住。
*/
public void saveState() {
//super.saveState();
for (int i = 0; i < Machine.processor().getTLBSize(); i++) {
//保存此进程当前的TLB信息
tlbBackUp[i] = Machine.processor().readTLBEntry(i);
//保存此时TLB的状态到 反向页表中
if (tlbBackUp[i].valid) {
InvertedPageTable.updateEntry(processID, tlbBackUp[i]);
}
}
}
/**
* Restore the state of this process after a context switch. Called by
* <tt>UThread.restoreState()</tt>.
*/
public void restoreState() {
//super.restoreState();
for (int i = 0; i < tlbBackUp.length; i++) {
//如果此进程之前的TLB中有页在内存中 则
if (tlbBackUp[i].valid) {
//还原TLB信息
Machine.processor().writeTLBEntry(i, tlbBackUp[i]);
TranslationEntry entry = InvertedPageTable.getEntry(processID, tlbBackUp[i].vpn);
if (entry != null && entry.valid) {
Machine.processor().writeTLBEntry(i, entry);
} else {
Machine.processor().writeTLBEntry(i, new TranslationEntry(0, 0, false, false, false, false));
}
} else {
Machine.processor().writeTLBEntry(i, new TranslationEntry(0, 0, false, false, false, false));
}
}
}
/**
* Initializes page tables for this process so that the executable can be
* demand-paged.
*
* @return <tt>true</tt> if successful.
*/
protected boolean loadSections() {
//return super.loadSections();
// load sections
//加载coff的 section
for (int s = 0; s < coff.getNumSections(); s++) {
CoffSection section = coff.getSection(s);
for (int i = 0; i < section.getLength(); i++) {
int virtualPageNum = section.getFirstVPN() + i;
//将coffsection的加载变为懒加载
CoffSectionAddress coffSectionAddress = new CoffSectionAddress(s, i);
lazyLoadPages.put(virtualPageNum, coffSectionAddress);
System.out.println("section: "+virtualPageNum);
}
}
return true;
}
/**
* Release any resources allocated by <tt>loadSections()</tt>.
*/
protected void unloadSections() {
InvertedPageTable.print();
releaseResource();
super.unloadSections();
}
/**
* Handle a user exception. Called by
* <tt>UserKernel.exceptionHandler()</tt>. The
* <i>cause</i> argument identifies which exception occurred; see the
* <tt>Processor.exceptionZZZ</tt> constants.
*
* @param cause the user exception that occurred.
*/
public void handleException(int cause) {
Processor processor = Machine.processor();
switch (cause) {
case Processor.exceptionTLBMiss:
//导致TLB缺失的虚拟地址是通过调用processor.readRegister(processor.regBadVAddr);获得的
int address = processor.readRegister(Processor.regBadVAddr);
pageLock.acquire();
//处理TLB缺页
boolean isSuccessful = handleTLBFault(address);
if (!isSuccessful) {
UThread.finish();
}
pageLock.release();
break;
default:
if(cause == 7) {
InvertedPageTable.print();
}
super.handleException(cause);
break;
}
}
//TLB错误 说明没有在TLB中找到对应的 虚拟页
protected boolean handleTLBFault(int vaddr) {
//虚拟页数
int vpn = Processor.pageFromAddress(vaddr);
//获取到 页错误发生的 反向页表中对应的TranslationEntry
TranslationEntry entry = InvertedPageTable.getEntry(processID, vpn);
System.out.println(processID+"想访问"+vpn);
if (entry == null) {
return false;
}
//如果对应的页不在内存中(valid为false) 需要取一个物理页 分配物理页 (将页装入内存中) 然后装入tlb
if (!entry.valid) {
System.out.println("miss");
int ppn = getFreePage();
System.out.println(processID+"将"+vpn+" 将装载到"+ppn);
//Machine.processor().print();
swapIn(ppn, vpn);
entry = InvertedPageTable.getEntry(processID, vpn);
}
//否则直接牺牲TLB中的页 然后 置换
int victim = getTLBVictim();
System.out.println(processID+"受害者"+victim);
replaceTLBEntry(victim, InvertedPageTable.getEntry(processID, vpn));
System.out.println("-----------------全局页表-----------------------");
InvertedPageTable.print();
System.out.println("--------------------TLB--------------------");
Machine.processor().print();
System.out.println("----------------------------------------");
System.out.println();
return true;
}
protected void replaceTLBEntry(int index, TranslationEntry newEntry) {
TranslationEntry oldEntry = Machine.processor().readTLBEntry(index);
if (oldEntry.valid) {
//InvertedPageTable.updateEntry(processID, newEntry);
}
Machine.processor().writeTLBEntry(index, newEntry);
}
//选择一个TLB中的页牺牲掉
protected int getTLBVictim() {
//如果
for (int i = 0; i < Machine.processor().getTLBSize(); i++) {
//如果此页现在已经不在主存中 则可以将它直接置换掉
if (!Machine.processor().readTLBEntry(i).valid) {
return i;
}
}
//否则 随机置换一个
int vic = Lib.random(Machine.processor().getTLBSize());
return vic;
}
//给进程分配逻辑页 先设置页表条目 但不分配物理页
protected boolean allocate(int vpn, int acquirePagesNum, boolean readOnly) {
for (int i = 0; i < acquirePagesNum; ++i) {
InvertedPageTable.insertEntry(processID, new TranslationEntry(vpn + i, -1, false, readOnly, false, false));
SwapperController.getInstance(SwapFileName).insertUnallocatedPage(processID, vpn + i);
allocatedPages.add(vpn + i);
System.out.println("other: "+(vpn+i));
}
numPages += acquirePagesNum;
return true;
}
protected void releaseResource() {
for (int vpn : allocatedPages) {
pageLock.acquire();
TranslationEntry entry = InvertedPageTable.deleteEntry(processID, vpn);
if (entry.valid)
VMKernel.addFreePage(entry.ppn);
SwapperController.getInstance(SwapFileName).deletePosition(processID, vpn);
pageLock.release();
}
}
protected boolean load(String name, String[] args) {
Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")");
OpenFile executable = ThreadedKernel.fileSystem.open(name, false);
if (executable == null) {
Lib.debug(dbgProcess, "\topen failed");
return false;
}
try {
coff = new Coff(executable);
} catch (EOFException e) {
executable.close();
Lib.debug(dbgProcess, "\tcoff load failed");
return false;
}
// make sure the sections are contiguous and start at page 0
numPages = 0;
for (int s = 0; s < coff.getNumSections(); s++) {
CoffSection section = coff.getSection(s);
if (section.getFirstVPN() != numPages) {
coff.close();
Lib.debug(dbgProcess, "\tfragmented executable");
return false;
}
//如果 所有的物理页都被分配 则 释放在主存的的那些页
if (!allocate(numPages, section.getLength(), section.isReadOnly())) {
releaseResource();
return false;
}
}
sectionlength = numPages;
// make sure the argv array will fit in one page
byte[][] argv = new byte[args.length][];
int argsSize = 0;
for (int i = 0; i < args.length; i++) {
argv[i] = args[i].getBytes();
// 4 bytes for argv[] pointer; then string plus one for null byte
argsSize += 4 + argv[i].length + 1;
}
if (argsSize > pageSize) {
coff.close();
Lib.debug(dbgProcess, "\targuments too long");
return false;
}
// program counter initially points at the program entry point
initialPC = coff.getEntryPoint();
// 分配堆栈页;堆栈指针最初指向它的顶部
if (!allocate(numPages, stackPages, false)) {
releaseResource();
return false;
}
initialSP = numPages * pageSize;
// 最后保留1页作为参数
if (!allocate(numPages, 1, false)) {
releaseResource();
return false;
}
if (!loadSections())
return false;
int entryOffset = (numPages - 1) * pageSize;
int stringOffset = entryOffset + args.length * 4;
this.argc = args.length;
this.argv = entryOffset;
for (int i = 0; i < argv.length; i++) {
byte[] stringOffsetBytes = Lib.bytesFromInt(stringOffset);
Lib.assertTrue(writeVirtualMemory(entryOffset, stringOffsetBytes) == 4);
entryOffset += 4;
Lib.assertTrue(writeVirtualMemory(stringOffset, argv[i]) == argv[i].length);
stringOffset += argv[i].length;
Lib.assertTrue(writeVirtualMemory(stringOffset, new byte[]{0}) == 1);
stringOffset += 1;
}
return true;
}
public boolean execute(String name, String[] args) {
if (!load(name, args)) {
return false;
}
UThread t = (UThread) new UThread(this).setName(name);
pidThreadMap.put(this.processID, t);
t.fork();
return true;
}
private static final int pageSize = Processor.pageSize;
private static final char dbgProcess = 'a';
private static final char dbgVM = 'v';
//处理器只能看到TLB
protected TranslationEntry[] tlbBackUp;
protected static Lock pageLock = new Lock();
//实现coffsection的懒加载
protected HashMap<Integer, CoffSectionAddress> lazyLoadPages;//还没有加载的coff
private static String SwapFileName = "Proj3SwapFile";
}