1、系统调用实习报告善良的大姐姐2015.5.32目录一:总体概述 .3二:任务完成情况 .3任务完成列表(Y/N) .3具体 Exercise 的完成情况 3三:遇到的困难以及解决方法 .18四:收获及感想 .19五:对课程的意见和建议 .19六:参考文献 .193一:总体概述自 lab4 我们完成了虚拟内存的实习,可以运行用户程序之后,我们就考虑加入系统调用。即,用户程序可以通过特定的系统调用,陷入 Nachos 内核,从而完成特定的目标。本次 lab 一共要求完成 10 个系统调用,包括两大部分,文件系统相关Create, Open,Close ,Read,Write;用户程序相关Exec,
2、Fork,Yield,Join,Exit 。需要在阅读和理解源码的基础上,知道系统调用的执行流程,进一步修增源代码,实现新增的系统调用功能。二:任务完成情况任务完成列表(Y/N )Exercise1 Exercise2 Exercise3 Exercise4 Exercise5Yes Yes Yes Yes Yes具体 Exercise 的完成情况Exercise 1:源代码阅读任务:阅读与系统调用相关的源代码,理解系统调用的实现原理。完成情况:1. Syscall.h概述:1) 定义了每个系统调用对应的系统调用号2) 声明了每个系统调用 2. Exception.cc概述:对系统陷入进行处理
3、。41) 从machine的2号寄存器读入系统调用号 2) 执行对应的操作代码(需要自己完成)3) (如果需要)将返回值写回Machine的2号寄存器3. Start.s概述:当用户程序执行一个系统调用的时候,将参数放入2号寄存器,然后跳转到exception.cc执行。以Halt为例:4. 总结当用户希望执行一条系统调用的时候:1) 在用户程序中调用2) 当这条语句被OneInstruction函数解析执行时,会判断出这是一条系统调用,转入start.s3) 在start.s中找到系统调用对应的入口(可能需要自己增加),将系统调用号放入machine的2号寄存器,并转入exception.c
4、c4) 在exception.cc中,读出2号寄存器中的系统调用号,执行对应操作5) 必要时,将返回值写回2号寄存器,并注意,将PC前进。6) 指令回到用户程序系统调用的下一条继续执行。系统调用完成。为了执行一条系统调用,我们需要完成的部分:1) 自己写一个用于测试的用户程序的.c文件,并修改test的Makefile,使得用户程序能够被Nachos系统执行。2) 因为本次lab需要写的系统调用,在start.s中都已经写好了,因此我们不需要修改。同样,syscall.h中,系统调用号和函数声明也都写好了。(但如果希望自己新增系统调用,这两个文件是需要修改的,修改方式可以参照别的系统调用)3)
5、 补充exception.cc,执行对应的系统调用操作。将系统调用号放入 2 号寄存器第一步:增加需要编译的文件名称。这两个是我新增的第二步,增加如何编译的信息。5Exercise 2: 系统调用实现任务:类比 Halt 的实现,完成与文件系统相关的系统调用:Create, Open,Close,Write,Read。Syscall.h 文件中有这些系统调用基本说明。Exercise 3: 编写用户程序任务:编写并运行用户程序,调用 练习 2 中所写系统调用,测试其正误。EX2+EX3 完成情况:首先,在完成了文件系统的 lab 之后,为了能够让 userprog 编译通过,需要修改syste
6、m.h 的 makefile,多 include 几个头文件以及声明 extern 变量。否则,会出现:undefined reference * 这种情况。其次,我们需要在 exception.cc 中,加入系统调用入口:1. void Create(char *name)概述:61) 从 4 号寄存器读入文件名指针2) 利用指针,从 Machine 的 mainmemory(内存)中,读出文件名3) 用文件名为参数,调用 filesystem 的 create 函数,创建文件(默认长度 256byte)4) 将 PC 前移: (machine.cc 中定义)代码:(从内存中读出文件名部分)
7、测试截图:之后在 userprog 文件夹下,就出现了 a.txt 这个文件(因为我是用 UNIX 的文件系统来做这次 lab 的)2. OpenFileId Open(char *name)概述:1) 从 4 号寄存器读入文件名指针2) 利用指针,从 Machine 的 mainmemory(内存)中,读出文件名73) 用文件名为参数,调用 filesystem 的 open 函数,打开文件,返回 OpenFile 指针(在 Nachos 系统中, OpenFile 相当于是文件描述符)4) 将 OpenFile 指针写回 machine 的 2 号寄存器(machine-WriteRegi
8、ster(2,(int)openfile))5) PC 前移代码:(Openfile 写回部分)测试截图:(打开失败) (Open 成功会配合之后的系统调用再进行测试截图)3. void Close(OpenFileId id)概述:1) 从 4 号寄存器中读出 OpenFile 的指针2) 调用 filesystem 的 close 方法,将 openfile 关闭(自己编写的 close 方法,其中将传入的 OpenFile 指针 delete 掉)3) PC 前移代码:(从寄存器中读出 Openfile 指针+关闭 部分)测试截图:(Close 配合 open 系统调用测试截图)84.
9、int Read(char *buffer, int size, OpenFileId id)概述:1) 从 4 号寄存器读出来装载字符串的指针2) 从 5 号寄存器读出需要读取的字符串长度3) 从 6 号寄存器读出 OpenFile 的指针4) 调用 openfile 的 read 函数,从光标位置读出一定长度的字符串,存入临时数组当中5) 将读出来的内容,写回 machine 的 mainmemory 中,写入的位置就是字符串指针依次往后。6) 将 openfile 的 read 函数的返回值,写入 2 号寄存器当中。代码:创建文件打开文件关闭文件注意:打开文件返回的文件指针,和关闭文件时
10、读取的文件指针相同。9测试截图:(见 write 部分,二者配合测试。 )5. void Write(char *buffer, int size, OpenFileId id)概述:1) 从 4 号寄存器读出来装载字符串的指针2) 从 5 号寄存器读出需要读取的字符串长度3) 从 6 号寄存器读出 OpenFile 的指针4) 从 machine 的 mainmemory 中,从字符串指针位置开始,连续读出字符串长度个字符5) 将读出来的内容,调用 OpenFile 的 write 方法,写入文件当中。代码:10测试截图:a) 单独测试 write成功写入希望写入的内容11b) 配合 rea
11、d 一起测试Exercise 4: 系统调用实现任务:实现如下系统调用:Exec,Fork,Yield,Join ,Exit 。Syscall.h 文件中有这些系统调用基本说明。Exercise 5: 编写用户程序任务:编写并运行用户程序,调用 练习 4 中所写系统调用,测试其正确性。EX4+EX5 完成情况:首先,为了能够使得 Join 和 Exec 的系统调用,能够在父子进程之间发生联系,我们需要在 thread.h 中加入 Thread *childThreadMaxThreadSize,用于记录当前线程的子线程的thread 指针。这个数组在 thread 的构造函数中初始化,初始值均
12、为 NULL。还需要加入Thread *fatherThread 变量,用于记录当前线程的父线程(如果存在) 。同样在构造函数中进行初始化,初始赋值为自己。1. SpaceId Exec(char *name)概述:1) 从 4 号寄存器中读出名字指针,并仿照 EX2 中的做法,从 mainmemory 中获得名打开文件 a,读取文件 a(指针相同)打开文件 b,将 a 读取的内容写入 b(指针也相同)字符的起始位置都是一样的。12字。2) 新创建一个线程3) 在当前线程的 childThread 数组中,寻找是否还有空位,如果有,赋值给新创建的线程,标明父子关系,并且将新线程的指针号写回 2
13、 号寄存器。接着,将新创建线程的 fatherThread 指针赋值为 currentThread。新线程这时可以调用 Fork,执行一段代码。最后,PC 前移4) 如果没有空位,输出错误信息,将 PC 前移。5) Fork 执行的代码:参照 progtest 中,startprocess 函数,先是打开这个用户程序文件,然后新创建一个地址空间,接着初始化 machine,最后让 machine-run。代码:a) Fork 之后调用的函数:b) 处理系统调用的代码:13测试截图:(和之后的 join+exit 一起测)2. int Join(SpaceId id)概述:1) 从 4 号寄存器
14、中读出要等待的子线程的 OpenFile 指针2) 找到自己的 childThread 数组中,这个 OpenFile 指针对应的位置3) While 循环,当这个位置不为 NULL 时,调用 currentThread-Yield(),等待子线程结束4) 跳出 while 循环后,将子线程的结束状态写入 2 号寄存器,PC 前移代码:14测试截图:(配合 Exec 和 Exit 一起测)3. void Exit(int status)概述:1) 由于 lab4 虚拟内存中,需要在 exit 系统调用中释放 page 和清空 tlb,因此此处继续保留这两个功能。2) 判断:如果当前 exit
15、的线程不是主线程:如果当前线程有父线程,将父线程中对应的自己设置为 NULL(表明自己已经要退出啦!) ,并且调用 currentThread-Finish()3) 如果是主线程:PC 前移测试截图:(配合之前的 Exec 和 Exit)a) 测试函数:Mytest 函数:15b) 测试截图:主线程执行 Exec系统调用新 Fork 出来的线程(设置优先级更高,于是立即抢占)切换新线程创建自己的用户空间准备在新线程的新的用户空间中运行 Mytest由于时间片到了,此时切换到主线程主线程此时执行完了 Exec,开始执行 Join 系统调用。由于在等待的子线程还未结束,于是让位CPU164. vo
16、id Fork(void (*func)()概述:1) 从 4 号寄存器中读出要执行的用户函数的起始 PC2) 新创建一个线程,并参照 Exec 中的步骤,完成父子关系的建立3) PC 前移。4) 在 Fork 的系统函数中,完成准备工作:将父进程的用户空间( Pagetable)拷贝到子进程中;将 machine 当前的 PC 值修改为 1)中的 PC 值,NextPC 值修改为 1)中的 PC 值 +4;将修改完成的这套寄存器值存放到新线程的寄存器当中(其中,除了两个 PC 值,其余的都和父进程相同!)5) Machine-Run(),就可以跳转到用户函数执行了。注意:1) 开始时刻,子进
17、程和父进程的寄存器值,除了 PC 不同,别的都相同。2) 子进程和父进程使用同样的 pagetable。3) 子进程和父进程共享 machine 的栈空间(因此子进程不必重设栈空间)代码:新线程执行 mytest 中的内容:Create 一个文件,然后 Exit退出(EXIT 中会释放物理页)新线程执行完毕了,切换到主线程。主线程由于上次 PC 没前移,于是再次调用 Join。此次成功了。主线程调用Exit 退出。1718测试截图:5. void Yield()概述:1) PC 前移2) 调用 currentThread-Yield()(注意,此处二者不能调换顺序,因为一旦当前线程 yield
18、 了,下一次再上 CPU,是不会从没执行完的系统调用处开始,而是会从 PC 当前对应的指令开始的。如果没有提前将 PC 前移,那么下一次,还会执行 Yield 系统调用,就陷入了死循环)代码:测试截图:a) 测试函数b) 测试结果Mytest 函数Fork_func 部分(见上) ,准备工作。用户函数执行。 (与左侧图片中显示的代码对应)19三:遇到的困难以及解决方法困难 1:Userprog 编译不通过编译 userprog 一直提示 undefined reference: thread/synchdisk 啦,bitmap 啦感觉很疑惑。原来是在 system.h 中, IFDEF US
19、ER_PROGRAM 中,需要 include 这几项。详见EX2+EX3 完成情况中对此的说明及截图。这一块和之前的截图分析的相同新线程执行 Yield,于是切换到主线程。主线程在等待子线程完成,于是又切换回子线程切换回子线程之后,子线程继续执行打开操作(这里的乱码我也不知道为何。 )20困难 2:当对同个文件执行 read 和 write 时,如果先 write 再 read,会出现读出来的东西都是乱码的情况这是由于二者共享了同个文件的文件指针(OpenFile 指针) ,如果先 write,此时文件指针已经移到了文件的末尾,再执行 read,就是从未知的位置读出东西,自然是乱码。困难 3
20、:在写 Exec 部分的时候,当从子线程切换到主线程的时候,主线程反复执行 Exec 系统调用一开始以为是寄存器没更新之类的问题。原来是忘记在 Exec 的系统调用末尾让PC+4,由于再次切换回主线程的时候,主线程会从当前 PC 位置开始执行,如果 PC 没有+4,就会一直反复执行 Exec 这条系统调用。四:收获及感想这是最后一个必做 Lab,一开始觉得不就是系统调用嘛,应该很简单!但事实上,两句话就能说明白的一个系统调用,真正写起来,还是要面对许多细节问题。譬如陷入系统调用之后,参数的传递,这里就涉及是否要从 machine 的 mainmemory 中读出东西,还是寄存器里的值直接可以使用,都是需要仔细考虑的。包括之后的 Fork 和 Exec 系统调用,要考虑如何共享地址空间之类的问题,感觉这次 lab 细节很重要。五:对课程的意见和建议不知道为什么,测试譬如 Open 或者 Create 系统调用的时候,文件名经常会出现一串乱码(如 EX4+EX5 完成情况中, yield 系统调用中的截图) ,但是我检查过,指针什么的都是正确的,我怀疑是 ReadMem 或者 WriteMem 写的哪里有问题,这是原本 Nachos 代码的问题。六:参考文献博客:http:/