Pintosproject2作者:西安电子科技大学王永刚QQ:357543420这个项目将使pintos可以加载并执行用户程序,并且为用户程序提供系统调用。Project2需要完成的的任务有四个:Task1ProcessTerminationMessages进程终止信息Task2ArgumentPassing参数传递Task3SystemCalls系统调用Task4DenyingWritestoExecutables不能写入可执行文件Task1:ProcessTerminationMessages进程终止信息要求:1.在进程结束时输出退出代码(就是main函数的返回值,或者异常退出代码。注意:用户进程结束时输入退出代码,核心线程返回时不输入。输出格式被规定如下:printf(“%s:exit(%d)\n”,..);实现方法:1.既然要打印返回值,就得用一个变量保存返回值,于是在structthread结构中加入一个变量回保存返回值:intret;在init_thread()函数中初始化为0(这里可以不用初始化)。2.在线程退出里要保存其返回值到ret中,这个将在系统调用里的exit函数中保存,这里先不考虑。在什么地方加入printf()呢?每个线程结束后,都要调用thread_exit()函数,如果是加载了用户进程,在thread_exit()函数中还会调用process_exit()函数,在process_exit()函数中,如果是用户进程,那么其页表一定不为NULL,而核心进程页表一定为NULL,即只有用户进程退出时if(pd!=NULL){}就会成立,所以在大括号中加入:printf(“%s:exit(%d)\n”,cur-name,cur-ret);其中cur=thread_current();即当前线程的structthread指针。TASK1OK…TASK2ArgumentPassing参数传递要求:1.分离从命令行传入的文件名和各个参数。2.按照C函数调用约定,把参数放入栈中。实现方法:1.分离参数的方法:用string.h中的strtok_r()函数,在string.c中有详细的说明。2.在process_execute()函数中,因为thread_create()需要一个线程名,此时应该传递给它文件名(不带参数)。可如下处理:char*real_name,*save_ptr;real_name=strtok_r(file_name,,&save_ptr);tid=thread_create(real_name,PRI_DEFAULT,start_process,fn_copy);(3)在start_process()函数中,再次分离参数,放入栈中。由于在process_execute()对file_name作了复制,文件名并未丢失,但是要注意,无论加载用户程序成功还是失败,都得释放file_name所占用的一个页的空间(Debughere3weeks)。注意:传给Load()函数的参数也只能有文件名,所以在load()函数前要分离出文件名:char*token=NULL,*save_ptr=NULL;token=strtok_r(file_name,,&save_ptr);success=load(token,&if_.eip,&if_.esp);参数放置的一种方法:(1)找到用户栈指针:在start_process()函数中有structintr_frameif_;这样一个结构,其中有一个成员if_.esp,这就是用户栈指针,在load()函数中为其赋值,分配了栈空间。(2)调用strtok_r分离出一个个参数(就是一个个字符串了),把每个字符串都复制到用户栈中,并把他在栈中的位置记录到一个数组中,以备下一步使用。注意:栈是向下增长,而字符串是向上增长。char*esp=(char*)if_.esp;char*arg[256];//assumenumbersofargumentbelow256inti,n=0;for(;token!=NULL;token=strtok_r(NULL,,&save_ptr)){esp-=strlen(token)+1;//becauseuserstackincreasetolowaddr.strlcpy(esp,token,strlen(token)+2);//copyparamtouserstackarg[n++]=esp;}(3)要加入一个双字的对齐,因为是32位的,所以就是四字节对齐。while((int)esp%4make)//wordalignesp--;//注意:栈是向下增长,所以这里是—而不是++;(4)要将第(2)步保存下的指针逆序放入栈中。按照C约定,先要放入一个0,以防没有参数。int*p=esp-4;*p--=0;然后依次放入参数n的地址,参数n-1的地址…参数0的地址。for(i=n-1;i=0;i--)//placethearguments'pointerstostack*p--=(int*)arg[i];(5)放入argc,argv*p--=p+1;*p--=n;*p--=0;esp=p+1;(6)让用户栈指针指向新的栈顶if_.esp=esp如下图摆放。如果命令行是:/bin/ls–lfoobar完整代码见附录!TASK3systemcall系统调用要求:(1)实现以下系统调用:pfn[SYS_WRITE]=IWrite;//printf和写文件需要。pfn[SYS_EXIT]=IExit;//退出时return后调用pfn[SYS_CREATE]=ICreate;//创建文件pfn[SYS_OPEN]=IOpen;//打开文件pfn[SYS_CLOSE]=IClose;//关闭文件pfn[SYS_READ]=IRead;//读文件pfn[SYS_FILESIZE]=IFileSize;//返回文件大小pfn[SYS_EXEC]=IExec;//加载用户程序pfn[SYS_WAIT]=IWait;//等待子进程结束pfn[SYS_SEEK]=ISeek;//移动文件指针pfn[SYS_REMOVE]=IRemove;//删除文件pfn[SYS_TELL]=ITell;//返回文件指针位置pfn[SYS_HALT]=IHalt;//关机要想完成以上系统调用,还要明白系统调用的机制,见后边。参考文件有:src/lib/user/syscall.c了解每个系统调用的形式。src/lib/syscall-nr.h了解每个系统调用号。实现方法:(1)搭建框架用一个数组保存各函数名,数组下标就是系统调用号。在syscall_init()函数中初始化数组pfn[]为NULL在syscall_handler()函数中依据系统调用号调用相函数。typedefvoid(*CALL_PROC)(structintr_frame*);CALL_PROCpfn[MAXCALL];voidsyscall_init(void){intr_register_int(0x30,3,INTR_ON,syscall_handler,syscall);inti;for(i=0;iMAXCALL;i++)pfn[i]=NULL;}staticvoidsyscall_handler(structintr_frame*f/*UNUSED*/){if(!is_user_vaddr(f-esp))ExitStatus(-1);intNo=*((int*)(f-esp));if(No=MAXCALL||MAXCALL0){printf(Wedon'thavethisSystemCall!\n);ExitStatus(-1);}if(pfn[No]==NULL){printf(thisSystemCall%dnotImplement!\n,No);ExitStatus(-1);}pfn[No](f);}(2)每一个系统调用的实现。完整代码见附录○1SYS_WRITE-------------voidIWrite(structintr_frame*f)printf函数会调用这个系统调用向屏幕输出,所以不实现这个系统调用,用户程序将无法输出任何字符。写文件也要用这个系统调用。所以要使用pintos自带的一个简单的文件系统。首先从用户栈中取出三个参数---fd,buffer,size如果fd是文件句柄,先要从进程打开文件表中找到该句柄对应的文件指针再调用pintos提供的file_write()函数向文件写入数据。打开文件表将在打开文件时建立,到SYS_OPEN系统调用实现时再讲其具体实现。如果fd是标准输出stdout句柄则调用putbuf函数向终端输出。○2SYS_EXIT------------voidIExit(structintr_frame*f);用户程序正常退出会调用这个系统调用。取出返回值,保存到进程控制块的ret变量中。调用thread_exit()函数退出进程用户程序非正常退出(如越界访问等原因)需要另加一个函数来实现。voidExitStatus(intstatus)//非正常退出时使用{structthread*cur=thread_current();cur-ret=status;//保存返回值。thread_exit();}○3SYS_CREATE-创建文件voidICreate(structintr_frame*f)取出仅有的一个参数—文件名。调用filesys_create()函数。保存返回值。○4SYS_OPEN---打开文件voidIOpen(structintr_frame*f)取出文件名。调用filesys_open()函数打开文件。这里需要为每个进程维护一个打开文件表。打开文件后要为这个文件分配一个句柄号。在structthread结构中加入:intFileNum;//打开文件数限制进程打开文件数structlistfile_list;//打开文件列表intmaxfd;//句柄分配使用每打开一个文件就让maxfd加1,关闭文件可以不减小。关联文件句柄与文件指针的结构:(被链入file_list)structfile_node{intfd;structlist_elemelem;structfile*f;};有了以上准备,每打开一个文件都要新创建一个file_node结构,分配句柄,并把file_node加入file_list;最后返回文件句柄就OK.○5SYS_CLOSE—关闭文件voidIClose(structintr_frame*f)一种是关闭一个文件。一种是进程退出时关闭所有文件。从用户栈中获取要关闭文件的句柄。在用户打开文件列表中找到对应文件,以得到文件指针。调用file_close()函数关闭文件,释放structfile_node。关闭所有文件自然是每一个都要关闭,释放了。Debughere3weeks○6SYS_READ—读文件IRead()从用户栈中获得fdbuffersize三个参数如果fd是标准输入设备,则调用input_getc()如果fd是文件句柄由fd从进程打开文件表中得到文件指针调用file_read()函数从文件中读数据。○7SYS_FILESIZE–获取文件大小IFileSize()从用户栈中获得fd由fd从进程打开文件表中得到文件指针调用file_length得到文件大小○8SYS_EXEC---加载用户程序IExec()用户程序通过SYS_EXEC这个系统调用创建子进程。在IExec()函数中,分配一个页,复制一份用户提供的用户名。否则在后来分离参数时,加入’\0’时出现核心线程写入用户内存空间的页错误。还要注意线程同步问题。在IExec()中调用process_execute()函数创建子进程,但是从process_execute()得到了用户进程pid