【Nachos】山东大学操作系统实验六 系统调用

完整源码见本人博客下载资源

一、完成情况一览

  • 完成class AddrSpace中的print函数
  • 扩展现有的class AddrSpace的实现,使得Nachos可以实现多用户程序
  • 实现Nachos 系统调用:Exec()
  • 熟悉了解了Nachos运行用户进程,内存管理机制

二、Nachos用户进程和内存管理机制解读

1、用户进程相关
  • 通过StartProcess函数(progtest.cc),内核线程转化成用户进程,线程控制块拥有了addr的信息,成为描述用户进程的进程控制块,最关键的信息就是AddrSpace对象,它存了属于该进程的页表TranslationEntry *pageTable
  • 关于用户进程的执行,由machine类负责,machine模拟了硬件部分,有cpu(靠mipssim.cc中OneInstruction函数的一长串switch语句解释命令),用户寄存器组(数组),主存(char数组)。machine类还实现了一套关键函数,最重要的是Run(运行进程,被StartProcess函数首次调用),和OneInstruction(执行一条指令)。值得一提的是,每OneInstruction一次,就会调一次OneTicks函数,推动时钟前进,此时,用户态时钟才是准确的。
  • 操作系统是靠中断驱动的,靠系统调用服务用户进程的。Nachos也不例外。machine.h中定义了所有异常类型,syscall.h中定义了所有系统调用接口。machine类里有void RaiseException(ExceptionType which, int badVAddr);函数,抛出异常后交由exception.cc中的void ExceptionHandler(ExceptionType which);处理,在这个异常处理函数中,根据异常种类which,调用interrupt类中的处理函数完成中断或系统调用的处理。题目中要求的Exec()系统调用也是这个流程。这中间需要注意,参数,返回值等是存在预先设定好的某寄存器中的。
2、内存管理机制
  • Nachos是采用页式内存分配方式管理用户内存空间的,最主要的两部分,一是AddrSpace类中存了属于该进程的页表TranslationEntry pageTable,并完成了页表初始化,内存空间分配;二是machine类中,ExceptionType Translate(int virtAddr, int physAddr, int size,bool writing);函数模拟了mmu的工作,每次读写内存时,通过页表找到物理地址。

  • 原始的Nachos,物理地址等于虚拟地址,内存只能为一个用户进程服务,必须将用户进程所需空间全部分配内存。

在这里插入图片描述

三、多道程序和Exec()实现步骤

1、添加AddrSpace::Print()函数

在nachos/userprog/addrspace.cc文件末尾添加如下代码

void
AddrSpace::Print() 
{ 
	printf("spaceID: %d\n",spaceID);
	printf("page table dump: %d pages in total\n", numPages); 
	printf("============================================\n"); 
	printf("\tVirtPage, \tPhysPage,\tvalid,\tdirty,\tuse\n"); 
	for (int i=0; i < numPages; i++) { 
	printf("\t%d,\t%d,\t%d,\t%d,\t%d\n", pageTable[i].virtualPage,pageTable[i].physicalPage,pageTable[i].valid,pageTable[i].dirty,pageTable[i].use); 
	} 
	printf("============================================\n\n"); 
}
2、用BitMap管理主存

要实现多道程序,就需要有一个全局管理,来组织空闲主存,分配主存。之前在fileSystem中用过的BitMap很适合。

  • 在system.cc中做适当声明和初始化。

    其中bool ProgMap[NumPhysPages]数组,是为了分配给每个用户进程唯一标识的spacID(后面会提到)。

#ifdef USER_PROGRAM	// requires either FILESYS or FILESYS_STUB
Machine *machine;	// user program memory and registers
//**********************
BitMap *freeMM_map;
bool ProgMap[NumPhysPages];       //to set pid
#endif
#ifdef USER_PROGRAM
    machine = new Machine(debugUserProg);	// this must come first
//*******************************************************************************
    freeMM_map = new BitMap(NumPhysPages);
    
    bzero(ProgMap,NumPhysPages);    //0
#endif
3、初始化用户空间AddrSpace::AddrSpace(OpenFile *executable)函数的修改
  • 给类AddrSpace增加int spaceID;属性

    初始化时,分配SpacID,如下

AddrSpace::AddrSpace(OpenFile *executable)
{
    bool flag = false;
    for(int i = 0; i < NumPhysPages; i++)
    {
        if(!ProgMap[i]){
            ProgMap[i] = 1;
            flag = true;
            spaceID=i;
            break;
        }
    }
    ASSERT(flag);
  • 由BitMap分配物理块,修改如下:
// first, set up the translation 
    pageTable = new TranslationEntry[numPages];
    for (i = 0; i < numPages; i++) {
	pageTable[i].virtualPage = i;	// for now, virtual page # = phys page #
//*************************************************
	pageTable[i].physicalPage = freeMM_map->Find();
	pageTable[i].valid = TRUE;
	pageTable[i].use = FALSE;
	pageTable[i].dirty = FALSE;
	pageTable[i].readOnly = FALSE;  // if the code segment was entirely on 
					// a separate page, we could set its 
					// pages to be read-only
    }
    
// zero out the entire address space, to zero the unitialized data segment 
// and the stack segment
    //bzero(machine->mainMemory, size);
  • 将code和initData载入内存的部分也要修改,(原先是直接用虚拟地址访问内存)

    并在AddrSpace(OpenFile *executable)的末尾调用Print()

/ then, copy in the code and data segments into memory
     if (noffH.code.size > 0) {
        DEBUG('a', "Initializing code segment, at 0x%x, size %d\n", 
            noffH.code.virtualAddr, noffH.code.size);

    //code start from this page
    int code_page = noffH.code.virtualAddr/PageSize;
    //calculate physical address using page and offset (0);
    int code_phy_addr = pageTable[code_page].physicalPage *PageSize;
    //read memory
        executable->ReadAt(&(machine->mainMemory[code_phy_addr]),
            noffH.code.size, noffH.code.inFileAddr);
    }
    if (noffH.initData.size > 0) {
        DEBUG('a', "Initializing data segment, at 0x%x, size %d\n", 
            noffH.initData.virtualAddr, noffH.initData.size);
    //data start from this page
    int data_page = noffH.initData.virtualAddr/PageSize;
    //first data's offset of this page
    int data_offset = noffH.initData.virtualAddr%PageSize;
    //calculate physical address using page and offset ;
    int data_phy_addr = pageTable[data_page].physicalPage *PageSize + data_offset;  
    //read memory
        executable->ReadAt(&(machine->mainMemory[data_phy_addr]),
            noffH.initData.size, noffH.initData.inFileAddr);
    }


    Print();

至此,nachos已经可以支持多道程序

4、完成Exec()系统调用

按照二、中的原理分析,Exec的系统调用接口已经定义好,我们主要工作是在exception.cc中完成对SC_exec的系统调用的处理工作。

  • syscall.cc中对Exec用户接口的定义,系统调用类型以及返回值通过寄存器$2传递,参数是通过寄存器$4,$5,$6,$7传递,最多四个参数。
SpaceId Exec(char *name);
  • exception.cc修改

    mipssim.cc的Machine::OneInstruction(Instruction *instr)对系统调用的软中断,处理后是return,所以系统调用处理完后需要完成“pc+1”的工作,否则用户程序无法继续执行。

    AdvancePC()完成“PC+1”

在这里插入图片描述

ExceptionHandler(ExceptionType which)中增加SC_Exec的选项,并将中断处理交给interrupt类。

//**********************************pc+1
void AdvancePC() { 

	machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg)); 
	machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg)); 
	machine->WriteRegister(NextPCReg, machine->ReadRegister(NextPCReg) + 4); 
}
//****************************************

void
ExceptionHandler(ExceptionType which)
{
    int type = machine->ReadRegister(2);

    if (which == SyscallException) {
	switch(type){	
	    case SC_Halt:
		DEBUG('a', "Shutdown, initiated by user program.\n");
   		interrupt->Halt();
		return;
	    case SC_Exec:

		interrupt->Exec();
		AdvancePC();
		return;
	
	}
    } else {
	printf("Unexpected user mode exception %d %d\n", which, type);
	ASSERT(FALSE);
    }
}
  • Interrupt增加void Interrupt::Exec()函数

    工作和StartProcess()一样,还需要将当前线程送入就绪队列,而且不能直接fork(StartProcess,1),因为Exec需要返回SpaceID,所以new AddrSpace的工作必须在Exec()中完成,如果用了fork,StartProcess()中会再new一次,这样会有两个SpacID,不唯一,所以不行。

    void
    Interrupt::Exec()
    {
        printf("Execute system call of Exec()\n");
        //read argument
        char filename[50];
        int addr=machine->ReadRegister(4);
        int i=0;
        do{
           machine->ReadMem(addr+i,1,(int *)&filename[i]);//read filename from mainMemory
        }while(filename[i++]!='\0');
    
        printf("Exec(%s):\n",filename); 
    
    //yunxing
        OpenFile *executable = fileSystem->Open(filename);
        AddrSpace *space;
    
        if (executable == NULL) {
    	printf("Unable to open file %s\n", filename);
    	return;
        }
        space = new AddrSpace(executable);
        Thread *thread = new Thread("forked thread");
    //把当前线程送入就绪队列
        IntStatus oldLevel = interrupt->SetLevel(IntOff);
        scheduler->ReadyToRun(currentThread);	// ReadyToRun assumes that interrupts 
    					// are disabled!
        (void) interrupt->SetLevel(oldLevel);  
    
        currentThread=thread;
    
        thread->space = space;
    
        delete executable;			// close file
    
        space->InitRegisters();		// set the initial register values
        space->RestoreState();		// load page table register
    
        //return spaceID
        machine->WriteRegister(2,space->getSpaceID());
    
        machine->Run();			// jump to the user progam
        ASSERT(FALSE);			// machine->Run never returns;
    					// the address space exits
    					// by doing the syscall "exit"
    
    }
    

四、结果展示

写一个exec.c用于测试

#include "syscall.h" 

int 
main() 
{ 
int pid;
pid = Exec("../test/halt.noff"); 

Halt();
}

在这里插入图片描述在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值