Nachos Project2思路、代码

Project_2 多道程序设计

首先需要修改 Machine.class下面的地址。要联系到你的测试文件夹的地址‘

else {
   
// use ../test
   
testDirectory = new File(baseDirectory.getParentFile(), "Nachos\\src\\nachos\\test");
}

其中,baseDierectory是基本的地址,可以Debug查看内容值,后面的参数再加上你的test文件的相对于base的地址。

 

task1中的问题:

openfile[0]=UserKernel.console.openForReading();
openfile[1]=UserKernel.console.openForWriting();

这两行代码必须要有,给出了openfile[0],openfile[1]是特定的东西,不然在Write中就会出现异常。即openfile[1]=0,进入错误处理。

Task1:

实现文件系统调用

1)   create系统调用

openfile[fileDescriptor] =ThreadedKernel.fileSystem.open(filename,false);

1.根据传入的fileAddress,从内存中读出文件名

2.判断文件名是否存在,即fileAddress是否为空

3.如果不存在则创建一个文件

先去找合适的位置,如果已经有大于16个,则退出;没有则创建。创建的时候调用filesystem中的open函数(传入boolean值create为true,创建文件)

4.将fileAddress对应的openfile数组的位置放入刚刚创建的文件描述符

PS:open函数()

private int handleCreate(int fileAddress) {
  
//处理create()系统调用,创建一个文件,返回文件描述符
  
//读出文件名
  
String filename= readVirtualMemoryString(fileAddress, 256);
   if
(filename == null)
     
return -1;    //文件名不存在,创建失败
  
//openfile中找空的位置
  
int fileDescriptor = findEmpty();
   if
(fileDescriptor==-1)
     
return -1;    //fileDescriptor=-1进程打开文件数已经达到上限,无法创建并打开
  
//执行到此处fileDescriptor=openfile为空位的下标
  
//创建
  
else{
     
//创建一个文件,并且将此时的openfile[fileDescriptor]中放入相应的描述符
     
openfile[fileDescriptor]=ThreadedKernel.fileSystem.open(filename, true);//文件不存在直接创建
  
}
  
//返回文件描述符
  
return fileDescriptor;
}

2)   open系统调用

1.根据月传入的fileAddress,从虚拟内存中读出文件名

2.fileAddress不能为空,需要检查

3.在openfile中找到合适的位置,如果大于16个表明打开已满,退出。否则打开对应的文件,调用filesystem的open(传入boolean值create为false,即只是打开文件)

4.将fileAddress对应的openfile数组的位置放入刚刚打开的文件描述符

private int handleOpen(int fileAddress){
  
//处理open()的系统调用,打开一个文件
  
Stringfilename=readVirtualMemoryString(fileAddress,256);
   if
(filename == null)
     
return -1;    //文件名不存在
  
int fileDescriptor = findEmpty();
   if
(fileDescriptor== -1)
     
return -1;    //打开文件数已经达到上限,无法打开
  
else {
     
openfile[fileDescriptor] = ThreadedKernel.fileSystem.open(filename,false);
      return
fileDescriptor;//打开成功返回文件描述符
  
}
}

3)   read系统调用

int readNumber=openfile[fileDescriptor].read(temp, 0, length);

read系统调用应该给出,read的文件,写入的内存地址,读取的字节个数。

1.如果打开文件数目大于16或小于0,对应fileDescriptor的openfile为空都为异常情况直接退出。

2.将文件内容读出到暂存数组,并返回读出字节的个数。

3.如果返回的字节个数小于0表明读出字节小于0,出错,返回。

4.将内容写到虚拟内存地址,返回写成功的字节数量

private int handleRead(int fileDescriptor,int bufferAddress,int length){
  
//处理read()的系统调用,从文件中读出数据写入指定虚拟地址
  
//检查给定的文件描述符
  
if(fileDescriptor>15||fileDescriptor<0||openfile[fileDescriptor]==null)
     
return -1;    //文件未打开,出错
  
byte temp[]=new byte[length];
  
//读文件
  
int readNumber=openfile[fileDescriptor].read(temp, 0, length);
   if
(readNumber<=0)
     
return 0//没有读出数据
  
int writeNumber=writeVirtualMemory(bufferAddress,temp);
   return
writeNumber;
}

 

4)   write系统调用

write系统调用应该给出,write的文件、读内存的地址、写入内存的字节个数

1.如果打开文件数目大于16或小于0,对应fileDescriptor的openfile为空都为异常情况直接退出。

2.将文件内容读出到暂存数组,并返回读出字节的个数。

3.如果返回的字节个数小于0表明读出字节小于0,出错,返回。

4.将内容写到磁盘,返回写成功的字节数

5.如果写成功的字节数小于要写的字节数,仍是错误情况。

private int handleWrite(int fileDescriptor,int bufferAddress,int length){
  
//处理write()的系统调用,将指定虚拟内存地址的数据写入文件
  
if(fileDescriptor>15||fileDescriptor<0||openfile[fileDescriptor]==null)
     
return -1;    //文件未打开,出错
  
byte temp[]=new byte[length];
  
//读出虚拟内存地址中的数据到temp
  
int readNumber=readVirtualMemory(bufferAddress,temp);
  
//数据读出后保存在temp
  
if(readNumber<=0)
     
return 0//未读出数据
  
//Temp中的数据 写入文件
  
int writeNumber=openfile[fileDescriptor].write(temp, 0, length);
   if
(writeNumber<length)
     
return -1;//未完全写入,出错
  
return writeNumber;
}

 

 

5)   close系统调用

close系统调用应该给出,需要关闭的文件的文件描述符

1.如果打开文件数目大于16或小于0,对应fileDescriptor的openfile为空都为异常情况直接退出。

2.关闭openfile数组中的文件,并把此位置置为null

private int handleClose(int fileDescriptor){
  
//处理close()的系统调用,用于关闭打开的文件
  
if(fileDescriptor>15||fileDescriptor<0||openfile[fileDescriptor]==null)
     
return -1;          //文件不存在,关闭出错
  
openfile[fileDescriptor].close();
  
openfile[fileDescriptor]=null;
   return
0;
}

 

6)   unlink系统调用

用于删除文件

根据传入的文件地址参数,从fileSystem中移除file

private int handleUnlink(int fileAddress){
  
//处理unlink的系统调用,用于删除文件
  
//获得文件名
  
Stringfilename=readVirtualMemoryString(fileAddress,256);
   if
(filename==null)
     
return 0;     //文件不存在,不必删除
  
if(ThreadedKernel.fileSystem.remove(filename))//删除磁盘中实际的文件
     
return 0;
   else
      return
-1;
}

 

Task2

完成对多道程序的支持

UserKernel:memoryLinkedList 存放当前空闲物理页号

allocateMemoryLock 保护内存互斥访问

声明:

//保护内存互斥访问锁
public static Lock allocateMemoryLock;
//全局队列,存放当前空闲的物理页号
public static LinkedList<Integer> memoryLinkedList;

 

实例化:

allocateMemoryLock=new Lock();
//初始化的时候,使memoryLinkedList包含所有的页号
memoryLinkedList=new LinkedList<Integer>();
for(int i=0;i<1024;i++){
   memoryLinkedList.add(i);
}

 

1)   loadSections

@function:为进程分配内存

numPages中保存了进场执行所需要的页的数量(在load中确定)。

1.判断需要的页的数量 与 物理内存页的数量的大小关系,只有物理页数量大于需要的页的数量才能进行内存分配,否则内存分配失败,返回false。

实例化一个页表pageTable数组(TranslationEntry对象类型)

从空闲物理页表号链表(memoryLinkedList)中取出一个空闲物理页号。

在页表数组中实例化一个TranslationEntry对象,保存联系的 虚拟页号与物理页号及相关信息

加载coffSection对于每个 section对象由很多的页,内层for循环得到每一页页号,将它标记为只读,然后装入页。(根据物理地址来装入页)

protected boolean loadSections() {
      UserKernel.allocateMemoryLock.acquire();//获取分配的内存的锁
      //进程所需要的页面数已知,保存在numPages中
      //判断能否分配内存
      //numPages中    已经确定了需要页的数量
      //页数量如果大于实际物理内存的页数量,则不能加载
   if (numPages > Machine.processor().getNumPhysPages()) {
      //判断能否装载
      coff.close();
      //缺少物理内存(如果程序的页数大于处理器的空闲物理页数就会失败)
      Lib.debug(dbgProcess, "\t insufficient(缺少物理地址) 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();
   // load sections(段),一个段是由很多页组成
   for (int s = 0; s < coff.getNumSections(); s++) {
      CoffSection section = coff.getSection(s);     //coff获得Section
      Lib.debug(dbgProcess,
            "\tinitializing " + section.getName() + " section (" + section.getLength() + " pages)");
      for (int i = 0; i < section.getLength(); i++) {
         int vpn = section.getFirstVPN() + i;//得到每一段虚拟页号(有页号偏移i),section不同
         pageTable[vpn].readOnly=section.isReadOnly();//标记为只读
         // for now, just assume virtual addresses=physical addresses
         //装入物理页
         section.loadPage(i,pageTable[vpn].ppn);
      }
   }
   return true;
}

2)   readVirtualMemory

从物理内存地址 读到data数组中

计算从该首地址开始的页表剩余字节的个数

如果读出的个数比剩余的数据长度大,则只能读出剩余数据长度

计算data数组中能否存下 length长度的数据,如果存不下则将 数据传输大小设置为可以存的大小

因为可能涉及到翻页所以需要用do-while循环

1.计算给定 首地址和已复制的数据的字节长度对应的页号。判断页号的合法性。

2.计算首地址和已复制的数据的字节长度对应的页偏移量。

3.根据页偏移量计算该页编号剩余的页的容量。

4.计算将要复制的数据的长度:页剩余量和剩余传输量中的较小值。

5.计算物理内存的地址,虚拟页号对应的物理页号*页大小+页偏移量

从物理内存数组中 从计算的地址开始 复制 得到的数据传输长度的数据传入到 指定的数组 从初始偏移开始+已复制成功的字节数。

增加复制成功的字节数

 
   public int readVirtualMemory(int vaddr, byte[] data, int offset, int length) {
      //从虚拟内存地址读出到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>=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<length);
      return transferredbyte;
   }

 

3)   writeVirtualMemory

从指定数组中将数据写入到物理内存中

与read很相似,先判断各种的参数。计算写入字符的长度。

传数据长度应该比段中页剩余量小。

数据传输长度如果超过了数组的剩余长度表明不能传输那么多数据,则将传输长度减少至数组剩余长度的量。

因为可能涉及到翻页所以需要用do-while循环

1.计算给定 首地址和已复制的数据的字节长度对应的页号。判断页号的合法性。

2.计算首地址和已复制的数据的字节长度对应的页偏移量。

3.根据页偏移量计算该页编号剩余的页的容量。

4.计算将要复制的数据的长度:页剩余量和剩余传输量中的较小值。

5.计算物理内存的地址,虚拟页号对应的物理页号*页大小+页偏移量

每次从指定数组中的偏移地址开始 写到 物理地址中 的指定地址,长度由前面计算得到

增加写成功的字节数量

    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;
      //如果数组中要写的长度比给定的小,则给length减为数组剩余的长度
      if(data.length-offset<length)
         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<length);
      return transferredbyte;
      // for now, just assume that virtual addresses equal physical addresses
//    if (vaddr < 0 || vaddr >= memory.length)
//       return 0;
//
//    int amount = Math.min(length, memory.length - vaddr);
//    System.arraycopy(data, offset, memory, vaddr, amount);
//
//    return amount;
   }

 

Task3:

1)   Exce系统调用

第一个参数为文件名地址,第二个参数为参数个数,第三个参数为参数指针表。

1.读虚拟内存获得文件名,判断合法性:得到的文件名不能为空,参数个数不能小于0,地址不能小于0,地址不能超过总的容量(numPages*pageSize)

2.将文件内容读入虚拟内存,先从argvAddress及其后续偏移地址分别取出参数的地址放入argsAddress(byte类型数组中),再从该地址数组中拿地址取得相应参数得string值

3.创建、执行子进程

4.设置子线程的参数信息:父线程设置为当前线程,将子线程加入到当前线程的子线程链表中

返回子线程的线程号

private int handleExec(int fileAddress,int argc,int argvAddress){
  
//读虚拟内存获得文件名
  
Stringfilename=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<argc;i++)
   {
//将文件内容读入虚拟内存
     
byte[] argsAddress=new byte[4];
     
//??*4的原因
     
//argvAddress作为参数表数组的首址,读取虚拟内存地址
     
if(readVirtualMemory(argvAddress+i*4,argsAddress)>0)
        
//argsAddress中保存了读出的参数
        
//依次读出每个参数
        
args[i]=readVirtualMemoryString(Lib.bytesToInt(argsAddress, 0), 256);//?
  
}
  
//创建子进程,将文件和参数标加载到子进程
  
UserProcessprocess=UserProcess.newUserProcess();
  
//创建并执行子进程
  
if(!process.execute(filename, args))//文件打开失败,退出
        
return -1;
  
//将这个子进程的父进程置为该进程
  
process.parentProcess=this;//将当前父进程信息赋予子进程
  
//将子进程加入到子进程表中(是此父进程的子进程表)
  
childProcess.add(process);
   return
process.pid;
}

 

2)   Join系统调用

第一个参数是join线程的pid,第二个参数是一个地址用来保存子进程的返回值

1. 利用进程号pid确定join的是哪一个进程,检查该线程的childProcesses,遍历子进程链表。确定join的进程是子进程。如果子进程编号不在其中则返回,不能执行join

2. 获得join的锁,拿到锁后使该进程在子进程的joinCondition条件变量中睡眠直到被子进程唤醒

3. 唤醒后释放锁,将子进程的运行状态存入父进程的内存中。

4. 判断是否正常退出

5.  private int handleJoin(int pid,int statusAddress)
{
   UserProcess process=
null;
  
//遍历子进程链表,确定join的进程是子进程
  
for(int i=0;i<childProcess.size();i++){
     
if(pid==childProcess.get(i).pid)//如果子进程是当前运行程序则返回
     
{
         process=
childProcess.get(i);
         break;
     
}
   }
  
if(process==null)
     
return -1;
  
//获得join锁,让该进程休眠,直到子进程唤醒
  
//得到锁,保持互斥
  
process.joinLock.acquire();//得到锁
  
//在该线程
  
process.joinCondition.sleep();//进程睡眠,加入等待队列等待被唤醒
  
process.joinLock.release();
   byte
[] childstat = new byte[4];
  
//取出子进程的运行状态
  
childstat=Lib.bytesFromInt(process.status);
  
//将子进程的状态存入内存中,判断当前进程是否正常结束
  
int numWriteByte=writeVirtualMemory(statusAddress,childstat);
   if
(process.normalExit&&numWriteByte==4)
     
return 1;
   return
0;
}

 

3)   Exit系统调用

先关闭coff文件。

因为即将退出该进程

1. 判断是否在该子进程openfile中有打开的文件,如果有则关闭,并且把位置内容置为null。保存status状态(打开或关闭)。

2. 如果该进程的父进程不为空,就唤醒父进程。并从父进程的childProcess中删除这个子进程,再释放内存

3. 判断当前正在运行的进程数量是否为1,如果只有一个线程运行表示只有main线程了,执行停机指令

4.  private int handleExit(int status){
  
coff.close();//关闭coff
  
for(int i=0;i<16;i++){
     
if(openfile[i]!=null
      {
        
//无内容可以写,关闭
        
openfile[i].close();
        
openfile[i]=null;
     
}
   }
  
this.status=status;//把状态置入(打开或关闭)
  
normalExit=true;
  
//如果有父进程,就从父进程的子进程链表删除,而且如果父进程中join子进程,则唤醒父进程
  
if(parentProcess!=null){
      
joinLock.acquire();//实现互斥
     
joinCondition.wake();
     
joinLock.release();
     
parentProcess.childProcess.remove(this);
  
}
  
//释放内存
  
unloadSections();
   if
(numOfRunningProcess==1)
      Machine.halt()
;
  
numOfRunningProcess--;
  
KThread.currentThread().finish();
   return
0;
}

 

Task4 实现彩票调度程序

彩票调度(LotteryScheduler)与优先级调度(PriorityScheduler)的大体实现方式相同,不过对于彩排调度是根据彩票落在那个优先级范围内来判断执行哪一个进程。

主要实现方式

1)   getEffectivePriority

首先得到自己线程的彩票数,再将所有自己的等待队列中的所有线程的彩票加起来作为自己的彩票数量。即自己的有效优先级为所有等待队列线程的彩票+自己持有彩票数

public int getEffectivePriority()
{
  
effectivePriority=priority;//得到自己的彩票数
  
for(int i=0;i<waitQueue.link.size();i++)
   {
     
//得到自己waitQueue上的线程的彩票号的总号码
     
effectivePriority+=waitQueue.link.get(i).getEffectivePriority();
  
}
  
return effectivePriority;
}

 

判断下一个执行的线程的方法

2)   nextThread

计算这个等待队列中所有的线程持有的彩票的数量。在这个持有的彩票数量中,随机生成一个彩票号,该彩票号即表明该执行的线程的彩票号。

将等待队列中的线程的彩票数依次累计(彩票数由getEffectivePriority()方法给出),如果彩票号落在某个线程的彩票号范围内则该线程即将可以执行,从等待队列中移除该线程,返回该线程。

public KThread nextThread() {
  
// TODO Auto-generated method stub
  
Lib.assertTrue(Machine.interrupt().disabled());
  
//彩票数量
  
int lottery=0;
  
KThread thread=null;
   for
(int i=0;i<link.size();i++)
   {
     
//得到这个队列里的所有线程的彩票
     
lottery+=link.get(i).getEffectivePriority();
  
}
  
//随机指定运行线程的彩票号
  
int run=Lib.random(lottery+1);
  
   int
rank=0;
  
   for
(int i=0;i<link.size();i++)
   {
     
//将队列中的线程垒起来,加一个判断是否超出彩票号
     
rank+=link.get(i).getEffectivePriority();
      if
(rank>=run)
      {
         thread=
link.get(i).thread;//该线程可以执行
        
break;
     
}
   }
  
if(thread!=null)
   {
     
//如果下一个线程不为空,则从队列中移除线程
     
link.remove(thread.schedulingState);//移除该线程
     
return thread;
  
}
  
else
  
{
     
return null;
  
}
     
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值