【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()

实验目的

尝试实现系统调用Exec() 和 Exit()

实验步骤

需要注意,在前三个步骤不需要修改代码。

一、nachos中系统调用的实现机制
观察nachos/machine/machine,mipssim中的实现可以看出,每一条用户程序中的指令在虚拟机中被读取后,包装成一个OneInstruction对象,经过解码,在mipssim中的一串长SWITCH语句中分别执行不同的操作。
而对于系统调用,在用户程序中的表现是调用nachos/userprog/syscall.h中定义的一系列系统调用函数,而这些函数的实现是定义在nachos/test/start.s中的汇编代码。
【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第1张图片
可以看出,实际上start.s中,不同的系统调用实际上是把系统调用的类型(SC_***)放入二号寄存器,然后执行指令syscall。
在mipssim中的switch语句中,对syscall的处理如下:
这里写图片描述
可以看出,这里抛出了一个异常,交给异常处理函数去处理。要注意的是,这里不同于其他指令的处理方式,没有使用break而是使用了return,而一般指令在break之后会进行一些其他处理:
【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第2张图片
可以看出其中包括了对pc的推进操作。
所以,直接return造成的后果是没有对pc进行推进,那么程序就会再次读入这条系统调用操作,无限循环。所以我们要手动添加这部分对pc推进的代码,稍后会讲到。

而RaiseException函数实际上最终调用的是nachos/userprog/exception.cc中的函数ExceptionHandler(ExceptionType which)。在这里ExceptionType是int的别名,可以当做int处理。
在这个函数中,我们可以看到,它利用了条件判断语句来进行判断,当前错误是系统调用还是用户程序出错,并进一步判断是什么错误。
【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第3张图片
也不难看出,程序当前只处理了Halt这个系统调用。
所以要完成Exec和Exit,就要在这里添加实现。

二、对Exec和Exit系统调用的分析
对于Exec系统调用来说,在syscall.h中的定义如下:
【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第4张图片
该调用的功能是,从可执行文件name运行一个新的用户程序,并行执行,并返回新的程序的内存空间标识符SpaceId。

具体的实现思路:根据name指向的地址打开可执行文件,创建AddrSpace,再fork出一个新的线程,将其内存空间设为刚创建的AddrSpace对象,目标函数中,对AddrSpace对象调用InitRegister和RestoreState方法,设置好虚拟机寄存器、内存空间的状态,调用machine->Run()执行。而执行系统调用的线程在fork出新线程后,将SpaceID返回,然后调用AdvancePC使pc寄存器的值前进,避免循环。

对于Exit系统调用来说,在syscall.h中的定义如下:
这里写图片描述
该调用的功能是,结束用户程序的运行。传入0时正常退出。由于我们没有其他退出情况要处理,所以这个状态码暂时不利用。

具体实现思路:简单地调用currentThread->Finsh()终止当前线程即可。

三、系统调用中对参数、返回值的读取和传递
系统调用中的返回值不同于普通函数的参数传递,参照exception.cc中的注释可以知道,它们是通过寄存器来储存的。

【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第5张图片

可以看出,参数分别储存在r4到r7内。
而返回值需要放在r2寄存器中。

而我们的参数不能以直接转换成指针的形式从内存中读取,原因是这里的内存地址实际上是虚拟机的内存,而转换成指针形式的内存地址是真实机器中的地址。所以读取要用machine->ReadMem(int addr,int size,int *value)方法。而读取字符串,则需要循环至读取到\0为止。

【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第6张图片

还需要注意的是,c++中char是1个字节,int是四个字节,这里读取char要将size设置为1.

四、AdvancePC的实现
参照mipssim中的实现,我们在exception.cc中定义如下函数:
【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第7张图片

五、SpaceID的实现
现有的AddrSpace中并没有实现唯一标识。
考虑以下以下因素:标识符总数要小于最大线程数(128),且能够全局访问(例如join等操作),并且能够唯一标识。由此,我们需要在AddrSpace中添加一个属性来记录这个标识符,在初始化时对其赋值,同时,为了避免重复,我们在system.h中声明一个数组来标记当前在使用的标识符,这样全局都可以进行访问。在AddrSpace对象销毁时,将对应的标识符置空。

具体的实现为,在nachos/threads/system.h中,添加声明

extern bool ThreadMap[128];

在nachos/threads/system.cc中,将其全部置为0

bzero(ThreadMap,128);

在nachos/userprog/AddrSpace.h中
为AddrSpace类添加属性

int spaceID;

添加方法

int getSpaceID(){return spaceID;}

在 nachos/userprog/AddrSpace.cc中,在构造方法开头添加如下语句来获取并标识spaceID:
【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第8张图片
在析构方法中添加如下语句来释放空间
【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第9张图片

六、实现Exec调用
将ExceptionHandler函数中的逻辑判定改为如下形式(当然,这样只是看起来舒服一些,如果喜欢也可以使用原版else if的形式)

【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第10张图片

添加case SC_Exec的代码以处理Exec系统调用,

【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第11张图片

而StartProcess函数、space,thread定义如下(实际上,必须定义到ExceptionHandler之前的位置)

【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第12张图片

可以看出大体流程与上面的分析一致。实际上,如果读过nachos/userprog/progtest.cc的代码,会发现大体流程与progtest文件中的StartProcess函数基本一致,除了新建线程和读取参数,返回spaceID的部分。
而且,由于需要返回内存空间AddrSpace的唯一标志符,这里需要在StartProcess之外初始化AddrSpace对象,所以把StartProcess函数拆分了。

七、实现Exit系统调用
添加case SC_Exit的处理如下

【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第13张图片

简单地终结了当前线程,其中的资源会自动释放掉。

八、编写测试文件
在nachos/test下新建文件exec.c,

【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第14张图片

实现非常简单,只是调用了exit调用来退出。
修改同目录下halt.c文件

【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第15张图片

这里halt.c在执行时会调用Exec系统调用启动exec.noff
在test/MakeFile中的targets一句末尾加上exec

这里写图片描述

在nachos/test目录和nachos/userprog目录分别make编译。

九、测试
在nachos/userprog输入命令
./nachos -x ../test/halt.noff

【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第16张图片

可以看出实现了多程序并行
为了验证程序正常执行,可以使用以下命令
./nachos -d m -x ../test/halt.noff
其中-d m是为了打印汇编的调试输出。

【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第17张图片
【nachos】山东大学操作系统课设实验nachos系统(6)系统调用Exec()和Exit()_第18张图片

可以看出,程序正常执行。

附代码:

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();
                break;
            }
            case SC_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);

                //open file
                OpenFile *executable = fileSystem->Open(filename);

                if (executable == NULL) {
                    printf("Unable to open file %s\n", filename);
                    return;
                }

                //new address space
                space = new AddrSpace(executable);  
                delete executable;          // close file

                //new and fork thread
                thread = new Thread("forked thread");
                thread->Fork(StartProcess, 1);

                //run the new thread
                currentThread->Yield();

                //return spaceID
                machine->WriteRegister(2,space->getSpaceID());

                //advance PC
                AdvancePC();    
                break;
            }
            case SC_Exit:{
                printf("Execute system call of Exit()\n");

                //machine->clear();
                AdvancePC();
                currentThread->Finish();
                break;
           }
            default:{
                printf("Unexpected syscall %d %d\n", which, type);
                ASSERT(FALSE);
            }
       }           

    } else {
        printf("Unexpected user mode exception %d %d\n", which, type);
        ASSERT(FALSE);
    }


}
AddrSpace::AddrSpace(OpenFile *executable)
{
    bool flag = false;
    for(int i = 0; i < 128; i++)
    {
        if(!ThreadMap[i]){
            ThreadMap[i] = 1;
            flag = true;
            spaceID=i;
            break;
        }
    }
    ASSERT(flag);

    if(bitmap == 0)
        bitmap = new BitMap(NumPhysPages);
    NoffHeader noffH;
    unsigned int i, size;

    executable->ReadAt((char *)&noffH, sizeof(noffH), 0);
    if ((noffH.noffMagic != NOFFMAGIC) && 
        (WordToHost(noffH.noffMagic) == NOFFMAGIC))
        SwapHeader(&noffH);
    ASSERT(noffH.noffMagic == NOFFMAGIC);

// how big is address space?
    size = noffH.code.size + noffH.initData.size + noffH.uninitData.size 
            + UserStackSize;    // we need to increase the size
                        // to leave room for the stack
    numPages = divRoundUp(size, PageSize);
    size = numPages * PageSize;

    ASSERT(numPages <= NumPhysPages);       // check we're not trying
                        // to run anything too big --
                        // at least until we have
                        // virtual memory

    DEBUG('a', "Initializing address space, num pages %d, size %d\n", 
                    numPages, size);
// 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 = bitmap->Find();
    ASSERT(pageTable[i].physicalPage!=-1);
    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);

// 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();

}

//----------------------------------------------------------------------
// AddrSpace::~AddrSpace
//  Dealloate an address space.  Nothing for now!
//----------------------------------------------------------------------

AddrSpace::~AddrSpace()
{
    ThreadMap[spaceID] = 0;
    for (int i = 0; i < numPages; i++) {
         bitmap->Clear(pageTable[i].physicalPage);

    }
   delete [] pageTable;
}

你可能感兴趣的:(nachos)