理解Nachos系统调用 Exercise 1 源代码阅读
阅读与系统调用相关的源代码,理解系统调用的实现原理。
code/userprog/syscall.h
code/userprog/exception.cc
code/test/start.s
code/userprog/syscall.h :这里定义了Nachos中的系统调用号,以及相应的系统调用接口。Nachos目前共有11种系统调用。
code/userprog/exception.cc :这里定义了异常处理函数。当异常发生的时候,ExceptionHandler 函数会被调用。系统调用也是异常的一种,即SyscallException
。然后根据从寄存器中读到的系统调用号,来进行相应的处理。
code/test/start.s :用于辅助Nachos中用户程序的执行。该文件共有两个作用,其一是定义程序执行时跳转到main 函数,以及程序执行结束时调用Exit
系统调用。另一个作用是,实现了用户程序的系统调用接口,该接口会将参数放在寄存器中,并跳转到异常处理函数执行相应的处理。
文件系统相关的系统调用 Exercise 2 系统调用实现
类比Halt的实现,完成与文件系统相关的系统调用:Create, Open,Close,Write,Read。Syscall.h文件中有这些系统调用基本说明。
前置工作 首先新增两个文件syscalldep.h和syscalldep.cc,然后在code/Makefile.common 中注册这两个文件,以及对应的目标文件。这两个文件的作用是,定义一些系统调用真实实现,让其在code/userprog/exception.cc 中被调用实现真正的处理。
由于上一次实习已经实现了Nachos中的文件系统,因此修改code/userprog/Makefile ,将文件系统完成后的宏换上。这里不要忘了把启用TLB加上,不然可能会出现一些问题。注:此时的用户程序是实现了交换分区之后的版本。
另外,如果使用Nachos内实现的文件系统,则不定长文件名也会存在问题。不定长文件名会存在的问题在Nachos Lab04 文件系统 Exercise 2 扩展文件属性中讨论过。为了方便测试,将不定长文件名改为定长文件名。 然后根据需要修改文件名的最大长度限制FileNameMaxLen
。
在Machine中实现一个PC前进的方法,用于增加PC。虽然该方法早先在Nachos Lab02 虚拟内存 中实现过,但是当时的实现方式存在一些问题,因此在此处更正。
1 2 3 4 5 void Machine::PCAdvance () { registers[PrevPCReg] = registers[PCReg]; registers[PCReg] = registers[NextPCReg]; registers[NextPCReg] = registers[NextPCReg] + sizeof (int ); }
Create 实现 首先从寄存器中读取字符串的起始地址。由于文件名是不定长的,因此读取文件名分成了两步。先循环一遍,获取字符串长度;再进行一次循环从内存中读取字符。最后调用文件系统的Create方法,由于文件支持动态长度,因此初始大小设为0。由于文件名改成了有最大长度限制,因此这里name字符数组直接初始化为定长大小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void SysCreate () { int base = machine->ReadRegister (4 ); int count = 0 ; int value; char name[FileNameMaxLen + 1 ]; do { while (!machine->ReadMem (base + count, 1 , &value)); name[count] = (char )value; count++; } while (value != '\0' && count <= FileNameMaxLen + 1 ); if (!fileSystem->Create (name, 0 )) { printf ("Create %s Failed\n" , name); } machine->PCAdvance (); }
然后在ExceptionHandler 注册该系统调用的处理。同时修改PC增加的规则,除SC_Halt之外,都需要将PC增加,来让用户程序能够继续执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void ExceptionHandler (ExceptionType which) { int type = machine->ReadRegister (2 ); if (which == SyscallException) { ... } else if (type == SC_Create) { DEBUG ('a' , "File Create!\n" ); SysCreate (); } if (type != SC_Halt) { int nextPc = machine->ReadRegister (NextPCReg); machine->WriteRegister (PCReg, nextPc); } } ... }
测试 修改code/test/halt.c 文件,让其调用Create系统调用。
1 2 3 4 5 6 7 8 #include "syscall.h" int main () { Create("aaa" ); Halt(); }
需要注意的是,由于修改了文件系统为Nachos真实实现的文件系统,使用StartProcess 打开的是Nachos内部的可执行程序,因此需要先将可执行程序拷贝到Nachos内。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 root@02487b68b87e:/nachos/nachos-3.4/code/userprog root@02487b68b87e:/nachos/nachos-3.4/code/userprog 0 VirtualMemory 0 test No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! Ticks: total 129060, idle 127010, system 2050, user 0 Disk I/O: reads 22, writes 9 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 Cleaning up...
然后执行test程序,可以看到文件”aaa”被成功创建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 root@02487b68b87e:/nachos/nachos-3.4/code/userprog Thread main, swap memory page 0 is allocated! Thread main, swap memory page 1 is allocated! Thread main, swap memory page 2 is allocated! Thread main, swap memory page 3 is allocated! Thread main, swap memory page 4 is allocated! Thread main, swap memory page 5 is allocated! Thread main, swap memory page 6 is allocated! Thread main, swap memory page 7 is allocated! Thread main, swap memory page 8 is allocated! Thread main, swap memory page 9 is allocated! Thread main, swap memory page 10 is allocated! Machine halting! Ticks: total 25584559, idle 25394330, system 190200, user 29 Disk I/O: reads 1497, writes 1188 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 Cleaning up... root@02487b68b87e:/nachos/nachos-3.4/code/userprog 0 VirtualMemory 0 test 0 aaa No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! Ticks: total 129060, idle 127010, system 2050, user 0 Disk I/O: reads 22, writes 9 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 Cleaning up...
Open 实现 Open 调用的实现由于有返回值,因此需要将Nachos中的文件描述符,即OpenFile指针写入到r2寄存器中。同样,在ExceptionHandler 中加入SC_Open
调用的处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void SysOpen () { int base = machine->ReadRegister (4 ); int value; int count = 0 ; char name[FileNameMaxLen + 1 ]; do { while (!machine->ReadMem (base + count, 1 , &value)); name[count] = (char )value; count++; } while (value != '\0' && count <= FileNameMaxLen + 1 ); OpenFile *openFile = fileSystem->Open (name); if (openFile == NULL ) { printf ("Open %s Failed\n" , name); } machine->WriteRegister (2 , (OpenFileId)openFile); printf ("File %s => OpenFileId %d\n" , name, (OpenFileId)openFile); }
测试 再次修改code/test/halt.c 的代码,使用Exit
来将获取到的文件描述符输出。
1 2 3 4 5 6 int main () { OpenFileId fd = Open ("aaa" ); Exit (fd); }
结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 root@02487b68b87e:/nachos/nachos-3.4/code/userprog Thread main, swap memory page 0 is allocated! Thread main, swap memory page 1 is allocated! Thread main, swap memory page 2 is allocated! Thread main, swap memory page 3 is allocated! Thread main, swap memory page 4 is allocated! Thread main, swap memory page 5 is allocated! Thread main, swap memory page 6 is allocated! Thread main, swap memory page 7 is allocated! Thread main, swap memory page 8 is allocated! Thread main, swap memory page 9 is allocated! Thread main, swap memory page 10 is allocated! File aaa => OpenFileId 136395216 Exit Code 136395216 No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! Ticks: total 21985080, idle 21786997, system 198050, user 33 Disk I/O: reads 1579, writes 1249 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 Cleaning up...
Close 实现 关闭文件,则从寄存器中解析出文件描述符指针的值,然后调用detele来关闭文件。由于文件指针的值是一个整数,因此直接从寄存器中将其取出即可,而不用去内存中寻找。
1 2 3 4 5 6 7 8 void SysClose () { int base = machine->ReadRegister (4 ); OpenFile *openFile = (OpenFile *)base; delete openFile; printf ("File id %d closed!\n" , (int )openFile); }
测试 先打开文件,再调用Close关闭文件。
1 2 3 4 5 6 7 int main () { OpenFileId fd = Open ("aaa" ); Close (fd); Halt (); }
结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 root@02487b68b87e:/nachos/nachos-3.4/code/userprog Thread main, swap memory page 0 is allocated! Thread main, swap memory page 1 is allocated! Thread main, swap memory page 2 is allocated! Thread main, swap memory page 3 is allocated! Thread main, swap memory page 4 is allocated! Thread main, swap memory page 5 is allocated! Thread main, swap memory page 6 is allocated! Thread main, swap memory page 7 is allocated! Thread main, swap memory page 8 is allocated! Thread main, swap memory page 9 is allocated! Thread main, swap memory page 10 is allocated! File aaa => OpenFileId 165435856 File id 165435856 closed! Machine halting! Ticks: total 22065080, idle 21866038, system 199000, user 42 Disk I/O: reads 1586, writes 1255 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 Cleaning up...
Write 实现 分别从r4,r5,r6三个寄存器中读取输入参数。由于输入的buffer是字符串,因此从内存中逐字节将其读到变量in中。再调用文件的Write 方法将其写入进文件。同时in的大小需要稍大一些,用于存储末尾的\0
字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void SysWrite () { int buf_base = machine->ReadRegister (4 ); int size = machine->ReadRegister (5 ); int openFileId = machine->ReadRegister (6 ); int value; char *in = new char [size + 1 ]; for (int i = 0 ; i < size; i++) { while (!machine->ReadMem (buf_base + i, 1 , &value)); in[i] = (char )value; } in[size] = '\0' ; printf ("Get instream string => %s\n" , in); OpenFile *openFile = (OpenFile *)openFileId; int r = openFile->Write (in, size); printf ("Writing in fileId %d with %d bytes\n" , (OpenFileId)openFile, r); delete [] in; }
测试 再次修改测试函数,调用Write系统调用。
1 2 3 4 5 6 7 8 int main () { OpenFileId fd = Open ("aaa" ); Write ("HelloWorld!" , 11 , fd); Close (fd); Halt (); }
结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 root@02487b68b87e:/nachos/nachos-3.4/code/userprog Thread main, swap memory page 0 is allocated! Thread main, swap memory page 1 is allocated! Thread main, swap memory page 2 is allocated! Thread main, swap memory page 3 is allocated! Thread main, swap memory page 4 is allocated! Thread main, swap memory page 5 is allocated! Thread main, swap memory page 6 is allocated! Thread main, swap memory page 7 is allocated! Thread main, swap memory page 8 is allocated! Thread main, swap memory page 9 is allocated! Thread main, swap memory page 10 is allocated! File aaa => OpenFileId 142665376 Get instream string => HelloWorld! Writing in fileId 142665376 with 11 bytes File id 142665376 closed! Machine halting! Ticks: total 24161070, idle 23941760, system 219250, user 60 Disk I/O: reads 1749, writes 1386 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 Cleaning up...
Read 实现 与写类似,从文件中读取数据,让后逐字节将其写入到内存当中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void SysRead () { int buf_base = machine->ReadRegister (4 ); int size = machine->ReadRegister (5 ); int openFileId = machine->ReadRegister (6 ); char *out = new char [size]; OpenFile *openFile = (OpenFile *)openFileId; int r = openFile->Read (out, size); printf ("Read %d bytes from FileId %d\n" , r, openFileId); for (int i = 0 ; i < r; i++) { while (!machine->WriteMem (buf_base + i, 1 , (int )out[i])); } machine->WriteRegister (2 , r); delete [] out; }
测试 由于Nachos内部没有实现标准输出相关的调用。因此将读取的结果写入到另一个文件中来查看读取的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int main () { char buf[100 ]; OpenFileId fd = Open ("aaa" ); Read (buf, 11 , fd); Close (fd); buf[11 ] = '\0' ; Create ("readRes" ); fd = Open ("readRes" ); Write (buf, 11 , fd); Close (fd); Halt (); }
结果如下,可以看到新文件中写入了从原文件中读到的字符串”HelloWorld!”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 root@02487b68b87e:/nachos/nachos-3.4/code/userprog Thread main, swap memory page 0 is allocated! Thread main, swap memory page 1 is allocated! Thread main, swap memory page 2 is allocated! Thread main, swap memory page 3 is allocated! Thread main, swap memory page 4 is allocated! Thread main, swap memory page 5 is allocated! Thread main, swap memory page 6 is allocated! Thread main, swap memory page 7 is allocated! Thread main, swap memory page 8 is allocated! Thread main, swap memory page 9 is allocated! Thread main, swap memory page 10 is allocated! File aaa => OpenFileId 135694800 Read 11 bytes from FileId 135694800 File id 135694800 closed! File readRes => OpenFileId 135693984 Get instream string => HelloWorld! Writing in fileId 135693984 with 11 bytes File id 135693984 closed! Machine halting! Ticks: total 27793070, idle 27539230, system 253730, user 110 Disk I/O: reads 2027, writes 1607 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 Cleaning up...
Exercise 3 编写用户程序
编写并运行用户程序,调用练习2中所写系统调用,测试其正确性。
这一部分与Exercise 2放在一起,见Exercise 2的测试部分。
执行用户程序相关的系统调用 Exercise 4 系统调用实现
实现如下系统调用:Exec,Fork,Yield,Join,Exit。Syscall.h文件中有这些系统调用基本说明。
前置条件 此处的系统调用涉及多线程,因此也就有了子线程和父线程的概念。在code/threads/thread.h 中定义一个宏MaxNumSubThread
来标记一个线程能拥有的最大子线程数。修改Thread类,新增一个线程指针数组,用于记录其所拥有的子线程;一个父线程指针,用于标识父线程;以及一个状态码整数,用于记录最后一个退出的子线程的状态码。以及三个方法分别用于创建子线程,删除子线程,以及确认子线程是否存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define MaxNumSubThread 10 class Thread {... public : Thread *subThreads[MaxNumSubThread]; Thread *parThread; int lastSubExitStatus; Thread * AddSubThread () ; void DelSubThread (Thread * st) ; bool CheckThread (Thread * st) ; };
然后在构造函数对新增的信息进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 Thread::Thread (char * threadName) { ... for (int i = 0 ; i < MaxNumSubThread; i++) { subThreads[i] = NULL ; } parThread = NULL ; lastSubExitStatus = 0 ; }
创建,销毁和确认子线程的方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 Thread * Thread::AddSubThread () { for (int i = 0 ; i < MaxNumSubThread; i++) { if (subThreads[i] == NULL ) { printf ("Thread %s add a subthread\n" , name); Thread *st = new Thread ("SubThread" ); subThreads[i] = st; st->parThread = this ; return st; } } return NULL ; } void Thread::DelSubThread (Thread *st) { for (int i = 0 ; i < MaxNumSubThread; i++) { if (subThreads[i] == st) { subThreads[i] = NULL ; break ; } } } bool Thread::CheckSubThread (Thread *st) { for (int i = 0 ; i < MaxNumSubThread; i++) { if (subThreads[i] == st) { return TRUE; } } return FALSE; }
状态码数组的相关方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void Thread::SetSubExitStatus (Thread *st, int status) { for (int i = 0 ; i < MaxNumSubThread; i++) { if (subThreads[i] == st) { subExitStatus[i] = status; break ; } } } int Thread::GetSubExitStatus (Thread *st) { for (int i = 0 ; i < MaxNumSubThread; i++) { if (subThreads[i] == st) { return subExitStatus[i]; } } }
除了父子进程之外,还需要考虑用户地址空间的拷贝。对AddrSpace类,新增一个空构造函数,以及一个复制方法Duplicate 。
1 2 3 4 5 6 7 8 9 10 11 class AddrSpace { public : ... AddrSpace () {}; ... AddrSpace *Duplicate () ; ... };
该复制方法Duplicate 会拷贝当前用户空间的所有页,然后返回一个新的地址空间。在设计上,出于简便,直接采取了拷贝所有地址空间页的方式 ,而非Linux上延迟拷贝的方式。
根据lazy-loading的设计,SetUpNewOneEntry 方法会向交换分区申请一页,然后将旧地址空间的对应页的信息全部复制到新页中。旧地址空间页需要分为仍在交换分区,以及被换入内存两类考虑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 void SetUpNewOneEntry (TranslationEntry &oldOne, TranslationEntry &newOne) { newOne.virtualPage = oldOne.virtualPage; newOne.valid = FALSE; newOne.use = FALSE; newOne.dirty = FALSE; newOne.readOnly = FALSE; int sp = machine->swapBitMap->Find (); ASSERT (sp != -1 ); newOne.swapPage = sp; printf ("Sub Thread %s, swap memory page %d is allocated!\n" , currentThread->getName (), sp); if (oldOne.swapPage != -1 ) { char *buf = new char [PageSize]; machine->swapFile->ReadAt (buf, PageSize, oldOne.swapPage * PageSize); machine->swapFile->WriteAt (buf, PageSize, sp * PageSize); delete [] buf; } else { int ppn = oldOne.physicalPage; int value; char ch; for (int i = 0 ; i < PageSize; i++) { while (!machine->ReadMem (ppn + i, 1 , &value)); ch = (char )value; machine->swapFile->WriteAt (&ch, 1 , sp * PageSize + i); } } } AddrSpace * AddrSpace::Duplicate () { AddrSpace * newAddrSpace = new AddrSpace (); newAddrSpace->numPages = numPages; newAddrSpace->pageTable = new TranslationEntry[numPages]; printf ("Duplicating User Space!\n" ); for (int i = 0 ; i < numPages; i++) { SetUpNewOneEntry (pageTable[i], newAddrSpace->pageTable[i]); } return newAddrSpace; }
Exec 实现 首先创建一个类似于code/userprog/progtest.cc 中的StartProcess 的函数ForkAndExec ,该函数被子线程调用Fork 执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void ForkAndExec (char *filename) { OpenFile *executable = fileSystem->Open (filename); AddrSpace *space2; if (executable == NULL ) { printf ("Unable to open file %s\n" , filename); delete [] filename; return ; } printf ("Start to executing file %s\n" , filename); delete [] filename; space2 = new AddrSpace (executable); currentThread->space = space2; delete executable; space2->InitRegisters (); space2->RestoreState (); machine->Run (); ASSERT (FALSE); }
然后实现Exec系统调用。在该调用中,首先创建一个子线程exec_t,让子线程调用Fork来执行新的用户程序。同时,将子线程指针的值(SpaceId)作为返回值写入到寄存器中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 void SysExec () { int base = machine->ReadRegister (4 ); int value; int count = 0 ; char *name = new char [FileNameMaxLen + 1 ]; do { while (!machine->ReadMem (base + count, 1 , &value)); name[count] = (char )value; count++; } while (value != '\0' && count <= FileNameMaxLen + 1 ); Thread *exec_t = currentThread->AddSubThread (); if (exec_t == NULL ) { printf ("Create a subthread failed!\n" ); } machine->WriteRegister (2 , (SpaceId)exec_t ); printf ("Space Id => %d\n" , (SpaceId)exec_t ); if (exec_t != NULL ) { exec_t ->Fork (ForkAndExec, (void *)name); } }
测试 被Exec执行的程序do_exec如下,创建一个文件sc_exec_f。
1 2 3 4 5 6 int main () { Create ("sc_exec_f" ); Exit (0 ); }
执行Exec系统调用的程序如下:
1 2 3 4 5 6 int main () { int ad = Exec ("do_exec" ); Exit (ad); }
其测试等到Join和Exit实现之后再进行。
Exit 实现 其实现是在Nachos Lab02 虚拟内存 中的基础上做了一些的修改。
Exit退出线程是还会判断,是否有父线程。如果有,则在父线程中设置自己的Exit状态码,然后删除自己。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void SysExit () { int status = machine->ReadRegister (4 ); DEBUG ('t' , "" , status); printf ("Exit Code %d\n" , status); if (currentThread->parThread != NULL ) { currentThread->parThread->lastSubExitStatus = status; currentThread->parThread->DelSubThread (currentThread); } if (currentThread->space != NULL ) { for (unsigned int i = 0 ; i < machine->pageTableSize; i++) { int phyPage = machine->pageTable[i].physicalPage; DEBUG ('m' , "Physical memory page %d is cleared!\n" , phyPage); machine->memBitMap->Clear (phyPage); } delete currentThread->space; currentThread->space = NULL ; } currentThread->Finish (); }
测试 其执行效果,部分已在其他测试中出现。新增的部分,则等到Join的实现再测试。
Join 实现 通过调用CheckSubThread 反复确认,子线程是否存在于子线程数组中。如果子线程运行结束则会调用Exit系统调用,将其在子线程数组中的位置设为NULL
,从而导致确认的结果为FALSE
;反之,则当前线程让出CPU,直至确认的结果为FALSE
。
1 2 3 4 5 6 7 8 9 10 void SysJoin () { int base = machine->ReadRegister (4 ); SpaceId id = (SpaceId)base; while (currentThread->CheckSubThread ((Thread *)id)) { currentThread->Yield (); } machine->WriteRegister (2 , currentThread->lastSubExitStatus); }
测试 首先创建一个名为exec_test的测试程序,该测试程序会调用Exec系统调用去执行程序”do_exec”,同时使用Join调用等待该子程序的执行结束。最后将子程序的状态码,用Exit返回。
1 2 3 4 5 6 7 int main () { SpaceId ad = Exec ("do_exec" ); int status = Join (ad); Exit (status); }
然后创建测试程序do_exec,该程序会创建一个名为”st_c”的文件,并返回状态码1。
1 2 3 4 5 6 int main () { Create ("st_c" ); Exit (1 ); }
测试结果如下。注:此处的测试使用的是Unix文件系统。之所以不使用Nachos内的文件系统是因为文件同步机制实现的有点问题,导致子线程的调用fileSystem->Open
时,执行不下去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 root@02487b68b87e:/nachos/nachos-3.4/code/userprog Thread main, swap memory page 0 is allocated! Thread main, swap memory page 1 is allocated! Thread main, swap memory page 2 is allocated! Thread main, swap memory page 3 is allocated! Thread main, swap memory page 4 is allocated! Thread main, swap memory page 5 is allocated! Thread main, swap memory page 6 is allocated! Thread main, swap memory page 7 is allocated! Thread main, swap memory page 8 is allocated! Thread main, swap memory page 9 is allocated! Thread main, swap memory page 10 is allocated! Thread main add a subthread Space Id => 159160096 Start to executing file do_exec Thread SubThread, swap memory page 0 is allocated! Thread SubThread, swap memory page 1 is allocated! Thread SubThread, swap memory page 2 is allocated! Thread SubThread, swap memory page 10 is allocated! Thread SubThread, swap memory page 11 is allocated! Thread SubThread, swap memory page 12 is allocated! Thread SubThread, swap memory page 13 is allocated! Thread SubThread, swap memory page 14 is allocated! Thread SubThread, swap memory page 15 is allocated! Thread SubThread, swap memory page 16 is allocated! Thread SubThread, swap memory page 17 is allocated! Exit Code 272 Exit Code 272 No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! Ticks: total 105, idle 8, system 30, user 67 Disk I/O: reads 0, writes 0 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 Cleaning up...
然后在本地可以看到st_c文件创建成功。
Fork 实现 由于Thread::Fork 方法仅能传递一个参数,因此创建一个数据类型UserPC类,来辅助传参。UserPC仅记录两个信息,uPC(PC值),space(地址空间)。
1 2 3 4 5 class UserPC {public : int uPC; AddrSpace * space; };
然后实现Fork系统调用。Fork系统调用和Exec系统调用的主要区别在于,Fork类似于Pyhton中的多线程,而Exec则类似于Python中的多进程的概念。Fork用于创建一个新线程来处理当前程序的某个部分(函数);而Exec会创建一个新线程来执行一个新的程序。
因此Fork的实现步骤如下:
从寄存器中获取任务的PC值,存到base中
调用AddSubThread 方法,创建当前线程的子线程exec_t
借助刚创建的UserPC类来记录,任务PC值 (存在base中)和父线程地址空间的拷贝 (调用Duplicate 方法复制)
子线程exec_t调用SaveUserState 方法,来为子线程设置寄存器状态。因为子线程相当于父线程的拷贝,并且子线程是从父线程中的某个点继续运行,所以子线程需要复制父线程的寄存器状态 。
子线程调用Thread::Fork 方法来执行任务ForkFunc ,并将UserPC对象传递进去。
在ForkFunc 中,首先设置子线程的地址空间(来自于父线程地址空间的拷贝)
在ForkFunc 中,然后修改当前PC寄存器的值,和NextPC寄存器的值。目的是为了让子线程从此处开始执行。
在ForkFunc 中,最后调用Machine::Run 来执行子线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void SysFork () { int base = machine->ReadRegister (4 ); printf ("Forking...\n" ); Thread *exec_t = currentThread->AddSubThread (); printf ("1 => Create sub thread id %d\n" , (SpaceId)exec_t ); UserPC *userPc = new UserPC; userPc->uPC = base; userPc->space = currentThread->space->Duplicate (); printf ("2 => Duplicate old space %d to new space %d\n" , (int )currentThread->space, (int )userPc->space); printf ("3 => Copy user's states\n" ); exec_t ->SaveUserState (); exec_t ->Fork (ForkFunc, (int )userPc); } void ForkFunc (int which) { UserPC *userPc = (UserPC *)which; currentThread->space = userPc->space; int uPC = userPc->uPC; printf ("4 => Setup PCReg to %d\n" , uPC); machine->WriteRegister (PCReg, uPC); machine->WriteRegister (NextPCReg, uPC + 4 ); printf ("5 => All setting down, start to run\n" ); machine->Run (); }
测试 编写测试函数如下,使用Fork来多线程执行调用函数Func。Func会创建文件”fork_t”。
1 2 3 4 5 6 7 8 9 void Func () { Create ("fork_t" ); } int main () { Fork (Func); }
测试结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 root@02487b68b87e:/nachos/nachos-3.4/code/userprog Thread main, swap memory page 0 is allocated! Thread main, swap memory page 1 is allocated! Thread main, swap memory page 2 is allocated! Thread main, swap memory page 3 is allocated! Thread main, swap memory page 4 is allocated! Thread main, swap memory page 5 is allocated! Thread main, swap memory page 6 is allocated! Thread main, swap memory page 7 is allocated! Thread main, swap memory page 8 is allocated! Thread main, swap memory page 9 is allocated! Thread main, swap memory page 10 is allocated! Forking... Thread main add a subthread 1 => Create sub thread id 148031264 Duplicating User Space! Sub Thread main, swap memory page 0 is allocated! Sub Thread main, swap memory page 1 is allocated! Sub Thread main, swap memory page 2 is allocated! Sub Thread main, swap memory page 10 is allocated! Sub Thread main, swap memory page 11 is allocated! Sub Thread main, swap memory page 12 is allocated! Sub Thread main, swap memory page 13 is allocated! Sub Thread main, swap memory page 14 is allocated! Sub Thread main, swap memory page 15 is allocated! Sub Thread main, swap memory page 16 is allocated! Sub Thread main, swap memory page 17 is allocated! 2 => Duplicate old space 148031000 to new space 148031600 3 => Copy user's states 4 => Setup PCReg to 208 5 => All setting down, start to run Exit Code 0 Exit Code 0 No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! Ticks: total 106, idle 17, system 30, user 59 Disk I/O: reads 0, writes 0 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 Cleaning up...
从截图中可以看到文件fork_t被成功创建。
Yield 实现 此处有一点需要注意,需要先将PC增加,再调用Yield。否则就会导致进程被唤醒时,仍处于执行Yield调用的指令位置。这会导致程序反复执行Yield。因此在ExceptionHandler 将其放在PC增加之后处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void ExceptionHandler (ExceptionType which) {... else if (type == SC_Fork) { DEBUG ('a' , "Program Fork!\n" ); SysFork (); } machine->PCAdvance (); if (type == SC_Yield) { DEBUG ('a' , "Program Yield!\n" ); SysYield (); } } ...
然后Yield调用实现比较简单,就是调用Thread::Yield 方法。
1 2 3 4 5 void SysYield () { printf ("Thread %s yield...\n" , currentThread->getName ()); currentThread->Yield (); }
测试 首先创建测试程序do_yield。该程序会创建文件”yield_t”,然后调用Yield。被唤醒之后会打开文件yield_t。
1 2 3 4 5 6 7 8 9 int main () { OpenFileId fd; Create ("yield_t" ); Yield (); fd = Open ("yield_t" ); Close (fd); Exit (fd); }
然后创建测试程序yield_test。该程序会执行do_yield程序。
1 2 3 4 5 6 7 int main () { SpaceId id; id = Exec ("do_yield" ); Join (id); Exit (0 ); }
测试结果如下。可以看到线程main执行Join调用,等待子线程SubThread的执行。子线程执行任务直到调用Yield调用,切换回主线程main,main继续在Join中等待,再次切换到子线程继续执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 root@02487b68b87e:/nachos/nachos-3.4/code/userprog Thread main, swap memory page 0 is allocated! Thread main, swap memory page 1 is allocated! Thread main, swap memory page 2 is allocated! Thread main, swap memory page 3 is allocated! Thread main, swap memory page 4 is allocated! Thread main, swap memory page 5 is allocated! Thread main, swap memory page 6 is allocated! Thread main, swap memory page 7 is allocated! Thread main, swap memory page 8 is allocated! Thread main, swap memory page 9 is allocated! Thread main, swap memory page 10 is allocated! Thread main add a subthread Space Id => 165242656 Thread main do waiting Start to executing file do_yield Thread SubThread, swap memory page 0 is allocated! Thread SubThread, swap memory page 1 is allocated! Thread SubThread, swap memory page 2 is allocated! Thread SubThread, swap memory page 10 is allocated! Thread SubThread, swap memory page 11 is allocated! Thread SubThread, swap memory page 12 is allocated! Thread SubThread, swap memory page 13 is allocated! Thread SubThread, swap memory page 14 is allocated! Thread SubThread, swap memory page 15 is allocated! Thread SubThread, swap memory page 16 is allocated! Thread SubThread, swap memory page 17 is allocated! Thread main do waiting Thread SubThread yield... Thread main do waiting File yield_t => OpenFileId 165267856 File id 165267856 closed! Exit Code 165267856 File id 165267856 closed! Exit Code 165267856 No threads ready or runnable, and no pending interrupts. Assuming the program completed. Machine halting! Ticks: total 193, idle 11, system 80, user 102 Disk I/O: reads 0, writes 0 Console I/O: reads 0, writes 0 Paging: faults 0 Network I/O: packets received 0, sent 0 Cleaning up...
可以看到子线程中的文件”yield_t”被成功创建。
Exercise 5 编写用户程序
编写并运行用户程序,调用练习4中所写系统调用,测试其正确性。
这一部分与Exercise 4放在一起,见Exercise 4的测试部分。
遇到的困难 无
参考资料 [1] 百度文库. Nachos系统调用实习报告[EB/OL]. https://wenku.baidu.com/view/cde42078aef8941ea66e0566.html