return -1; }
private int findEmpty() {for(int i=0;i<16;i++) {if(openfile[i]==null) return i;
}
return -1; }
2 readVirtualMemory() writeVirtualMemory()
实现多进程并发执行。提出一种内存分配方法使每个用户进程能够使用属于自己的内存。没必要是动态内存分配,每个程序在执行之前就知道自己需要多少进程。建议使用一个全局的内存页链表,而且在分配内存时应该有同步。应该才有内存池,使用一页一页的内存而不是一大块。进程退出时应该释放所有的内存。应该使用页表来将物理地址与逻辑地址对应起来,而且页表中应该指出文件是否为只读。
(a) 设计思想 在loadSection中,在导入coff之前应该创建一个页表,进行物理地址和逻辑地址的关联,然后把程序的每一块按照顺序对应于物理地址导入到内存中。 读内存时,要先利用页表将逻辑地址转换为物理地址然后再将内存数据复制到数组中
写内存时,要先利用页表将逻辑地址转换为物理地址然后再将内存数据复制到数组中 (b)源代码
protected boolean loadSections() {
UserKernel.allocateMemoryLock.acquire();
if (numPages > UserKernel.memoryLinkedList.size())
{ coff.close();
Lib.debug(dbgProcess, \physical memory\); UserKernel.allocateMemoryLock.release(); return false; }
pageTable = new TranslationEntry[numPages];// 创建页表 for (int i = 0; i < numPages; i++)
{ int nextPage=UserKernel.memoryLinkedList.remove();
pageTable[i] = new TranslationEntry(i, nextPage, true, false, false, false); }
UserKernel.allocateMemoryLock.release();
for (int s = 0; s < coff.getNumSections(); s++)
{ CoffSection section = coff.getSection(s);
Lib.debug(dbgProcess, \ +
section.getName()+ \ + section.getLength() + \);
for (int i = 0; i < section.getLength(); i++) {
int vpn = section.getFirstVPN() + i;
pageTable[vpn].readOnly=section.isReadOnly();
section.loadPage(i, pageTable[vpn].ppn); } }
return true; }
public int readVirtualMemory(int vaddr, byte[] data, int offset, int length) {
Lib.assertTrue(offset >= 0 && length >= 0 && offset + length <= data.length);
byte[] memory = Machine.processor().getMemory();
if(length>(pageSize*numPages-vaddr)) length=pageSize*numPages-vaddr;
if(data.length-offset length=data.length-offset; int transferredbyte=0; do{int pageNum=Processor.pageFromAddress(vaddr+transferredbyte); if(pageNum<0||pageNum>=pageTable.length) return 0; int pageOffset=Processor.offsetFromAddress(vaddr+transferredbyte); int leftByte=pageSize-pageOffset; int amount=Math.min(leftByte, length-transferredbyte); int realAddress=pageTable[pageNum].ppn*pageSize+pageOffset; System.arraycopy(memory, realAddress, data, offset+transferredbyte, amount); transferredbyte=transferredbyte+amount;} while(transferredbyte public int writeVirtualMemory(int vaddr, byte[] data, int offset, int length) { Lib.assertTrue(offset >= 0 && length >= 0 && offset + length <= data.length); byte[] memory = Machine.processor().getMemory(); if(length>(pageSize*numPages-vaddr)) length=pageSize*numPages-vaddr; if(data.length-offset length=data.length-offset; int transferredbyte=0; do{ int pageNum=Processor.pageFromAddress(vaddr+transferredbyte); if(pageNum<0||pageNum>=pageTable.length) return 0; int pageOffset=Processor.offsetFromAddress(vaddr+transferredbyte); int leftByte=pageSize-pageOffset; int amount=Math.min(leftByte, length-transferredbyte); int realAddress=pageTable[pageNum].ppn*pageSize+pageOffset; System.arraycopy(data, offset+transferredbyte, memory, realAddress, amount); transferredbyte=transferredbyte+amount; } while(transferredbyte 3 exec join exit系统调用 寄存器传递的所有的参数都是内存的地址,所以需要读写内存来获得真正参数。孩子进程的状态是私有的,所以不能使用共享内存的方式。进程号可以做为参数传递给join方法用来指明父进程将要join哪一个孩子进程。最后一个执行exit的方法将关闭系统,但是直接调用Machine.halt(). (a)设计思想 exec: 先从内存中将文件名读出,然后将参数表从内存中读出,创建一个新的用户进程,将文件和参数表加载到子进程,运行子进程,同时将这个子进程的父进程置为这个进程,再将子进程加入子进程列表。 join:利用进程号确定join的是哪一个进程,先遍历子进程链表,确定join的进程是子进程,获得join锁,让该进程休眠,直到子进程唤醒,子进程唤醒之后将子进程的状态存入自己的内存中 exit:首先关闭coff,然后将所有的打开文件关闭,把退出的状态置入,如果该进程有父进程,看是否执行了join方法,如果执行就将其唤醒,同时将本进程从子进程链表中删除,释放内存,结束底层线程,如果是最后一个结束的进程则将系统关闭 (b) 源代码 private int handleExec(int fileAddress, int argc, int argvAddress) { String filename=readVirtualMemoryString(fileAddress,256); if(filename==null||argc<0||argvAddress<0||argvAddress>numPages*pageSize) return -1; String[] args=new String[argc]; for(int i=0;i {byte[] argsAddress=new byte[4]; if(readVirtualMemory(argvAddress+i*4,argsAddress)>0) args[i] = readVirtualMemoryString(Lib.bytesToInt(argsAddress, 0),256); } UserProcess process=UserProcess.newUserProcess(); if(!process.execute(filename, args)) return -1; process.parentProcess=this; childProcess.add(process); return process.pid; } private int handleJoin(int pid, int statusAddress) { UserProcess process=null; for(int i=0;i {if(pid==childProcess.get(i).pid) {process=childProcess.get(i); break;} } if(process==null||process.thread==null) return -1; process.joinLock.acquire(); process.joinCondition.sleep(); process.joinLock.release(); byte[] childstat = new byte[4]; Lib.bytesFromInt(childstat, 0, process.status); int numWriteByte = writeVirtualMemory(statusAddress, childstat); if(process.normalExit&&numWriteByte==4) return 1; return 0; } private int handleExit(int status) { coff.close(); for(int i=0;i<16;i++) {if(openfile[i]!=null) {openfile[i].close(); openfile[i]=null;} } this.status=status; normalExit=true; if(parentProcess!=null) {joinLock.acquire(); joinCondition.wake(); joinLock.release(); parentProcess.childProcess.remove(this); } unloadSections(); KThread.finish(); if(numOfRunningProcess==1) Machine.halt(); numOfRunningProcess--; return 0; } 1 2 3的测试 要测试系统调用和内存分配,必须创建用户程序进行测试。首先先写一个C语言的程序,然后交叉编译,编译成.coff格式的文件,这样就能在nachos下运行。运行nachos,在nachos的shell中输入测试文件名,即可执行测试程序。 这个测试程序在通过调用写好的系统调用来检测nachos系统调用的可靠性 #include \#include \#include \ #define BUFSIZE 1024 #define BIG 4096 char buf[BUFSIZE], buf2[BUFSIZE], buf3[BUFSIZE], bigBuf[BIG], bigBuf1[BIG]; int main(void) { //test create,close and unlink int i, fileDescr, status, stdClose; for (i = 0; i < 2; i++) { fileDescr = creat(\ if (fileDescr == -1) { printf(\ return 1; } close(fileDescr); unlink(\ } //test read and write fileDescr = creat(\ if (fileDescr == -1) { printf(\ return 1; } for (i = 0; i < 26; i++) { buf[i] = '1' + i;