pintospro2project2UserProgram.docx
- 文档编号:29675811
- 上传时间:2023-07-26
- 格式:DOCX
- 页数:35
- 大小:105.51KB
pintospro2project2UserProgram.docx
《pintospro2project2UserProgram.docx》由会员分享,可在线阅读,更多相关《pintospro2project2UserProgram.docx(35页珍藏版)》请在冰豆网上搜索。
pintospro2project2UserProgram
Pintosproject2
作者:
电子科技大学
这个项目将使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];//assumenumbersofargumentbelow256
inti,n=0;
for(;token!
=NULL;token=strtok_r(NULL,"",&save_ptr))
{
esp-=strlen(token)+1;//becauseuserstackincreasetolowaddr.
strlcpy(esp,token,strlen(token)+2);//copyparamtouserstack
arg[n++]=esp;
}
(3)要加入一个双字的对齐,因为是32位的,所以就是四字节对齐。
while((int)esp%4make)//wordalign
esp--;//注意:
栈是向下增长,所以这里是—而不是++;
(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];
void
syscall_init(void)
{
intr_register_int(0x30,3,INTR_ON,syscall_handler,"syscall");
inti;
for(i=0;i pfn[i]=NULL; } staticvoid syscall_handler(structintr_frame*f/*UNUSED*/) { if(! is_user_vaddr(f->esp)) ExitStatus(-1); intNo=*((int*)(f->esp)); if(No>=MAXCALL||MAXCALL<0) { printf("Wedon'thavethisSystemCall! \n"); ExitStatus(-1); } if(pfn[No]==NULL) { printf("thisSystemCall%dnotImplement! \n",No); ExitStatus(-1); } pfn[No](f); } (2)每一个系统调用的实现。 完整代码见附录 SYS_WRITE-------------voidIWrite(structintr_frame*f) printf函数会调用这个系统调用向屏幕输出,所以不实现这个系统调用,用户程序将无法输出任何字符。 写文件也要用这个系统调用。 所以要使用pintos自带的一个简单的文件系统。 首先从用户栈中取出三个参数---fd,buffer,size 如果fd是文件句柄,先要从进程打开文件表中找到该句柄对应的文件指针再调用pintos提供的file_write()函数向文件写入数据。 打开文件表将在打开文件时建立,到SYS_OPEN系统调用实现时再讲其具体实现。 如果fd是标准输出stdout句柄则调用putbuf函数向终端输出。 SYS_EXIT------------voidIExit(structintr_frame*f); 用户程序正常退出会调用这个系统调用。 取出返回值,保存到进程控制块的ret变量中。 调用thread_exit()函数退出进程 用户程序非正常退出(如越界访问等原因)需要另加一个函数来实现。 voidExitStatus(intstatus)//非正常退出时使用 { structthread*cur=thread_current(); cur->ret=status;//保存返回值。 thread_exit(); } SYS_CREATE-创建文件voidICreate(structintr_frame*f) 取出仅有的一个参数—文件名。 调用filesys_create()函数。 保存返回值。 SYS_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. SYS_CLOSE—关闭文件voidIClose(structintr_frame*f) 一种是关闭一个文件。 一种是进程退出时关闭所有文件。 从用户栈中获取要关闭文件的句柄。 在用户打开文件列表中找到对应文件,以得到文件指针。 调用file_close()函数关闭文件,释放structfile_node。 关闭所有文件自然是每一个都要关闭,释放了。 Debughere3weeks SYS_READ—读文件IRead() 从用户栈中获得fdbuffersize三个参数 如果fd是标准输入设备,则调用input_getc() 如果fd是文件句柄 由fd从进程打开文件表中得到文件指针 调用file_read()函数从文件中读数据。 SYS_FILESIZE–获取文件大小IFileSize() 从用户栈中获得fd 由fd从进程打开文件表中得到文件指针 调用file_length得到文件大小 SYS_EXEC---加载用户程序IExec() 用户程序通过SYS_EXEC这个系统调用创建子进程。 在IExec()函数中, 分配一个页,复制一份用户提供的用户名。 否则在后来分离参数时,加入’\0’时出现核心线程写入用户存空间的页错误。 还要注意线程同步问题。 在IExec()中调用process_execute()函数创建子进程,但是从process_execute()得到了用户进程pid后,用户程序并没用加载。 所以要等待用户进程被调度后—调用了start_process()函数才能知道。 Start_process()函数真正加载用户程序,可能会因为找不到程序文件或存不足等原因导致加载失败。 所以父进程调用process_execute()后不能立即返回,要在一个信号量上等待sema_down(sema),直到start_process()函数中加载用户程序成功后再semp_up(sema)激活父进程,激活父进程后应该立即挂起自己—sema_down(sema),这里父进程获取子进程状态信息后,再出父进程sema_up()激活子进程。 如果父进程创建了一个优先级比自己高的子进程,如果不这样坐,start_process()函数每一次执行sema_up(sema)后,父进程还是不会被调度,而子进程可以已经运行完毕,这样父进程就得不到子进程的状态了。 在struct_thread结构中加入semaphoreSemaWaitSuccess; 可以在父进程的的这个信号量上等,也可是子进程的 SemaWaitSuccess上等。 如果子进程创建成功则返回pid,失败返回-1。 SYS_WAIT—等待函数IWait() 主线程创建子进程后,出于他与子进程优先级一样,所以,二者交替执行,这样主线程就有可能先结束,这导致了一开始的test失败。 起初可以通过创建子进程时提高子进程优先级或者在process_wait()中加入while(true)这样的死循环来解决。 后期要通过信号量同步。 这个系统调用的需求: 父进程创建子进程后可能要等子进程结束。 Process_wait()要返回子进程的返回值。 情况有如下: 父进程调用process_wait()时子进程还未结束,此进父进程将被挂起,等子进程结束后再唤醒父进程,父进程再取得返回值。 父进程调用process_wait()时子进程已经结束,这就要求子进程结束后应该把返回值保存到父进程的进程控控制块中。 于是在structthread要加入一个链表,structlistsons_ret; 结构: structret_data { intpid; intret; structlist_elemelem; }; 这样就能保存子进程的返回值了。 在structthread结构中加入boolbWait;表示进程本身有没有被父进程等待。 在structthread结构中加入boolSaveData;如果子进程已经把返回值保存到父进程里了就设SaveData为true;SaveData应该被初始化为false; 在structthread结构中加入structthread*father;表示父线程。 每创建一个子线程,都要在子线程中记录父线程。 信号量同步方法: 在structthread结构中加入semaphoreSemaWait; 这里选择在父进程的SemaWait上等。 这个等待会把父进程的structthread进程控制块插入到SemaWait的list中去。 要想同时等待多个进程则不可能把父进程插入到多个子进程中去。 当然,这里的测试只能等一个子进程,所以在父进程和子进程上等都可以。 父进程执行process_wait(child_pid)后,可以由child_pid得到子进程structthread指针t。 通过遍历all_list比较pid实现. 如果在all_list没有发现子进程的进程控制块或者发现t->SaveData==true||t->status==THREAD_DYING;表示子进程已经结束,直接从自己的sons_ret链表中找到子进程的返回值返回就OK. 如果子进程还在运行,则执行sema_down(t->father->SemaWait)把自己挂起,子进程执行完毕后,发现在bWait==true,自己被等待了,再释放父进程sema_up(SemaWait);如果bWait==fale,则不用唤醒父进程。 父进程被唤醒后,再从sons_ret链表中得到子进程的返回值。 每个子进程只能等一次,第二次等同一个子进程只能返回-1. 一个进程结束时,在process_exit()函数中,要释放自己打开的所有文件,保存返回值到父进程,输出退出信息,如果有父进程在等他就唤醒父进程,释放子进程链表。 SYS_SEEK---移动文件指针ISeek() 从用户栈中取出文件句fd柄要移动的距离, 把fd转为文件指针, 调用file_seek()函数移动文件指针即可。 SYS_REMOVE删除文件IRemove 从用户栈中取出要删除文件的文件名。 调用filesys_remove()删除文件。 SYS_TELL返回文件指针当前位置ITell() 从用户栈中取出文件句fd柄要移动的距离, 把fd转为文件指针, 调用file_tell()函数得到指针位置。 SYS_HALT关机IHALT 调用shutdown_power_off()函数关机 用户程序导致页错误时,会进入page_fault()函数,在exception.c中。 在page_fault()中加入 if(not_present||(is_kernel_vaddr(fault_addr)&&user)) ExitStatus(-1); 来处理页错误。 Task4DenyingWritestoExecutables不能写入可执行文件 在start_process函数中加入 t->FileSelf=filesys_open(token); file_deny_write(t->FileSelf); 其中FileSelf变量是要在structthread结构中添加的。 进程退出时就解除: 在process_exit()中加入 if(cur->FileSelf! =NULL)//撤销对自己人deny_write { file_allow_write(cur->FileSelf); file_close(cur->FileSelf); } 注意: 所有系统调用的返回值都放到用户的eax寄存器中。 取出参数时要对用户栈指针作详细的检查,是否越界,越界则直接调用Exit_Status(-1)中止用户进程。 用户程序加载过程: (1)核心线程通过调用process_execute(char*file_name); 函数来创建用户进程。 File_name为要加载的文件名。 这个函数中还调用了thread_create()函数为用户进程创建了一个线程。 File_name和一个叫start_process的函数被传递给了thread_create(),thread_create()函数创建线程后就把线程放入ready_list()等待被调度; (2)得到CPU后就开始start_process(void*file_name)函数。 这个函数做了以下几件事儿: 根据file_name把用户程序从硬盘调入存,还为其分配了虚拟存。 注意这里要完成task2,不然文件名不正确,就没法打开文件。 给用户分配了栈空间3GB开始。 向低字节增长。 在这里要实现参数分离,并且把参数按规定放入用户栈中。 (3)通过嵌汇编asmvolatile(………….)调用了用户程序中的main()函数。 (4)main()函数从栈中取出传给他的参数,执行完毕后会调用系统调用exit(),Exit()函数又调用thread_exit()函数,thread_exit()函数又调用process_exit()函数,最后在thread_exit()函数中把即将退出的函数的进程控制块structthread从all_list中remove掉,调用了进程调度器schedule()函数,调用下一下进程执行。 系统调用过程: 在用户程序使用一个系统调用,如printf();在必然会触发一个30号中断,正如src/lib/user/syscall.c文件中所述。 可见参数个数不同,系统调用不同。 这个30号中断调用之前,把系统调用号、用户参数(0到3个不等)压入栈中。 然后开始执行中断程序,中断程序又调用了syscall_handler(structintr_frame*f)函数,其中f是一个指向了用户程序当前运行信息的的指针,其中就有用户栈指针esp,所以在我们添加的系统调用中,就可以根据这个指针取出系统调用号和各个参数。 系统调用结束后,要把返回值入如f->eax中. 注意: 用户栈中的各个参数并不连续存放: •三个参数write(fd,buffer,size); intfd=*(esp+2); char*buffer=(char*)*(esp+6); unsignedsize=*(esp+3); •两个参数create(pFileName,size); boolret=filesys_create((constchar*)*((unsignedint *)f->esp+4),*((unsignedint*)f->esp+5)); •一个参数exit(-1); cur->ret=*((int*)f->esp+1); 附录: Task2参数传递代码(红色) Task3系统调用(蓝色) Task4denywrite(绿色) tid_t process_execute(constchar*file_name) { char*fn_copy; tid_ttid; /*MakeacopyofFILE_NAME. Otherwisethere'saracebetweenthecallerandload().*/ fn_copy=pall
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- pintospro2project2UserProgram