CSAPP Lab:Shell Lab——理解进程控制的秘密
本次实验目的是完成一个简单的shell程序,解析命令行参数,理解并使用(fork,execve,waitpid)常见的多进程函数,了解linux进程组,以及前台进程和后台进程的相关概念,理解linux的信号机制(包括发送信号,接受信号,阻塞信号等)。实验提示以及详情请阅读CMU的实验指导:http://csapp.cs.cmu.edu/public/labs.html?。 我们要完成的shell并不是从0开始,实验本身已经帮你完成了一部分内容,并且提供一些工具函数,我们要做的是实现一下这几个核心函数: eval: Main routine that parses and interprets the command line. [70 lines] builtin_cmd: Recognizes and interprets the built-in commands: quit,fg,bg,and jobs. [25 lines] dobgfg: Implements the bg and fg built-in commands. [50 lines] waitfg: Waits for a foreground job to complete. [20 lines] sigchld handler: Catches SIGCHILD signals. [80 lines] sigint handler: Catches SIGINT (ctrl-c) signals. [15 lines] sigtstp handler: Catches SIGTSTP (ctrl-z) signals. [15 lines] eval这是shell程序的核心函数,我们需要在这里完成以下几个事情: 1.调用parseline,生成argv以及判断是否是后台进程。 2.调用builtin_cmd判断是否是内建命令,如果是则已经在该方法中执行,shell直接返回,否则创建进程执行。 3.fork之前要注意屏蔽SIGHLD信号,否则有可能在addjob之前就调用deletejob造成竞争。 4.需要在fork后解锁SIGHLD的信号。 /* * eval - Evaluate the command line that the user has just typed in * * If the user has requested a built-in command (quit,jobs,bg or fg) * then execute it immediately. Otherwise,fork a child process and * run the job in the context of the child. If the job is running in * the foreground,wait for it to terminate and then return. Note: * each child process must have a unique process group ID so that our * background children don‘t receive SIGINT (SIGTSTP) from the kernel * when we type ctrl-c (ctrl-z) at the keyboard. */ void eval(char *cmdline) { char *argv[MAXARGS]; char buf[MAXLINE]; int isBg; pid_t pid; sigset_t mask; strcpy(buf,cmdline); isBg=parseline(buf,argv); if(argv[0]==NULL){ return; } if(!builtin_cmd(argv)){ //init mask sigemptyset(&mask); sigaddset(&mask,SIGCHLD); sigprocmask(SIG_BLOCK,&mask,NULL); //block SIGCHLD if((pid=fork())==0){ sigprocmask(SIG_UNBLOCK,NULL); //unblock SIGCHLD if(execve(argv[0],argv,environ)<0){ printf("%s:Command not found.n",argv[0]); exit(0); } //set own pid as group pid setpgid(0,0); } if(!isBg){ addjob(jobs,pid,FG,cmdline); waitfg(pid); } else{ addjob(jobs,BG,cmdline); printf("%d %s",cmdline); } sigprocmask(SIG_UNBLOCK,NULL); //unblock SIGCHLD } return; } ? builtin_cmdbuiltin_cmd做的事情比较简单,判断是否是内建命令,如果是,则直接执行并返回true,否则返回false。 /* * builtin_cmd - If the user has typed a built-in command then execute * it immediately. */ int builtin_cmd(char **argv) { if(!strcmp(argv[0],"quit")||!strcmp(argv[0],"q")){ exit(0); } if(!strcmp(argv[0],"jobs")){ listjobs(jobs); return 1; } if(!strcmp(argv[0],"bg")||!strcmp(argv[0],"fg")){ do_bgfg(argv); return 1; } return 0; } dobgfg这个方法还是比较复杂的,主要完成了bg和fg命令的操作,需要注意如下几点: 1.需要区分输入的是pid还是jid来调用不同函数。 2.通过发送SIGCONT来重启进程,发送对象需要为进程组。 3.不要忘记将后台进程改为前台进程后需要等待前台进程完成(调用waitfg)。 /* * do_bgfg - Execute the builtin bg and fg commands */ void do_bgfg(char **argv) { struct job_t *job; char *id=argv[1]; pid_t pid; if(id==NULL){ printf("%s command requireds pid or %%jobid argumentn",argv[0]); return; } //process by jobid if(id[0]==‘%‘) { int jid = atoi(&id[1]); job=getjobjid(jobs,jid); if(job==NULL) { printf("%s:No such jobn",id); return; } } //process by pid else if(isdigit(id[0])){ int pid = atoi(&id[1]); job = getjobpid(jobs,pid); if(job==NULL) { printf("%s:No such jobn",id); return; } } else{ printf("%s: argument must be a PID or %%jobidn",argv[0]); return; } //send SIGCONT to restart kill(-(job->pid),SIGCONT); //set job status if(!strcmp(argv[0],"bg")){ job->state = BG; printf("[%d] (%d) %s",job->jid,job->pid,job->cmdline); }else{ job->state = FG; waitfg(job->pid); } return; } ? waitfg没什么好说的,根据实验指导,这里直接使用忙等待来实现。 /* * waitfg - Block until process pid is no longer the foreground process */ void waitfg(pid_t pid) { while(pid == fgpid(jobs)){ sleep(0); } } ? sigchld handler回收僵尸进程的关键函数,需要注意如下几点: 1.理解waitpid的用法,这里使用WNOHANG|WUNTRACED的组合会更合适,表示立即返回,如果等待集合中没有进程被中止或停止返回0,否则返回进程的pid。 2.检验status的值来执行不同操作,status的含义有如下枚举:
所以三种情况都是需要delete job的,当进程为停止状态同时需要设置job的status。 void sigchld_handler(int sig) { pid_t pid; int status; while((pid = waitpid(-1,&status,WNOHANG|WUNTRACED))>0){ if(WIFEXITED(status)){ deletejob(jobs,pid); } if(WIFSIGNALED(status)){ deletejob(jobs,pid); } if(WIFSTOPPED(status)){ struct job_t *job = getjobpid(jobs,pid); if(job !=NULL ){ job->state = ST; } } } if(errno != ECHILD) unix_error("waitpid error"); return; } ? sigint handlerctrl-c的响应函数,直接调用kill函数给相关进程。 void sigint_handler(int sig) { pid_t pid = fgpid(jobs); if(pid!=0){ kill(-pid,sig); } return; } ? sigtstp handlerctrl-z的响应函数,直接调用kill函数给相关进程,需要注意kill前判断状态,不要重复发送信号。 /* * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever * the user types ctrl-z at the keyboard. Catch it and suspend the * foreground job by sending it a SIGTSTP. */ void sigtstp_handler(int sig) { pid_t pid = fgpid(jobs); if(pid!=0 ){ struct job_t *job = getjobpid(jobs,pid); if(job->state == ST){ return; }else{ Kill(-pid,SIGTSTP); } } return; } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- angularjs – 如何在角度js中设置默认时区
- angular – Ionic和Firebase – InvalidPipeArgument:管道
- 如何将引用的参数从变量传递给bash脚本
- 将Scala long转换为字符串打印为空白
- Bash自动完成不会转义SPACE,也不会在Ubuntu 11.4中标记目录
- scala – OpenCL是否适合基于代理的模拟?
- twitter-bootstrap – 尝试使引导模式更广泛
- bootstrap typeahead 3
- scala – spray Collection ToResponseMarshallable
- angular – 父变量不在ng-content内部工作