实现几个函数,使得该shell能处理前后台运行程序、能够处理ctrl+z、ctrl+c等信号。
实现这几个函数:
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] do bgfg: 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: 主要功能是解析cmdline,并且运行. [70 lines]
builtin_cmd: 辨识和解析出bulidin命令: quit, fg, bg, and jobs. [25lines]
do_bgfg: 实现bg和fg命令. [50 lines]
waitfg: 实现等待前台程序运行结束. [20 lines]
sigchld_handler: 响应SIGCHLD. 80 lines]
sigint_handler: 响应 SIGINT (ctrl-c) 信号. [15 lines]
sigtstp_handler: 响应 SIGTSTP (ctrl-z) 信号. [15 lines]
注意:
使用跟踪文件来指导shell的开发。从trace01开始,确保shell生成与参考shell相同的输出。然后转到文件trace02.txt,这样继续往下。
waitpid、kill、fork、execve、setpgid和sigprocmask函数非常有用。
wuntracking和WNOHANG选项对于waitpid也很有用。
在实现信号处理程序时,确保将SIGINT和SIGTSTP信号发送到整个前台进程组,在kill函数的参数中使用“-pid”而不是“pid”。
sdriver.pl程序测试这个错误。
任务中比较棘手的部分之一是决定waitfg之间的工作分配和sigchld处理函数。采用以下方法:
-在waitfg中,使用sleep函数的一个繁忙循环。
-在sigchld处理程序中,只使用一个对waitpid的调用。
虽然可以使用其他解决方案,例如在waitfg和sigchld处理程序中调用waitpid。
在eval中,父进程必须先使用sigprocmask来阻止SIGCHLD信号,然后再派生子进程,
然后解压这些信号,再次使用sigprocmask,在它将子节点添加到作业列表by之后
调用addjob。由于孩子继承了他们父母的阻塞向量,孩子必须在SIGCHLD执行新程序之前解除它的信号阻塞。父进程需要以这种方式阻塞SIGCHLD信号,以避免其中的竞态条件
sigchld_handler在父进程调用addjob之前捕获子进程(因此从作业列表中删除)。
诸如more、less、vi和emacs之类的程序,不考虑从shell中运行这些程序。专注简单的基于文本的程序,如/bin/ls,/bin/ps,/bin/echo.
当从标准Unix shell运行你的shell时,您的shell正在前台进程组中运行。如果你的shell随后创建了一个子进程,默认情况下,该子进程也是前台进程组。
因为按ctrl-c会向前台组的每个进程发送一个SIGINT,键入ctrl-c将向你的shell以及你的shell的每个子进程发送一个SIGINT。这显然是不正确的。
下面是解决方案:
在fork之后,但在execve之前,子进程应该调用setpgid(0,0),它将子进程放入一个新的进程组中,该进程组的组ID与孩子的PID相同。
这确保前台进程组中只有一个进程,即你的shell。
当您键入ctrl-c时,shell应该捕获生成的SIGINT,然后转发它到适当的前台作业。
1 eval()函数的原型可以在P503找到
主要功能是解析cmdline,并且运行。
1 void eval(char *cmdline) 2 { 3 char *argv[MAXLINE]; /*argument list of execve()*/ 4 char buf[MAXLINE]; /*hold modified commend line*/ 5 int bg; /*should the job run in bg or fg?*/ 6 pid_t pid; 7 sigset_t mask; /*mask for signal*/ 8 9 stpcpy(buf,cmdline); 10 bg = parseline(buf,argv); 11 12 if(argv[0]==NULL){ 13 return; /*ignore empty line*/ 14 } 15 16 if(!builtin_cmd(argv)){ /*not a build in cmd*/ 17 Sigemptyset(&mask); 18 Sigaddset(&mask,SIGCHLD); 19 Sigprocmask(SIG_BLOCK,&mask,NULL); /*block the SIGCHLD signal*/ 20 21 if((pid = Fork())==0) 22 { 23 Sigprocmask(SIG_UNBLOCK,&mask,NULL); /*unblock the SIGCHLD signal in child*/ 24 Setpgid(0,0); /*puts the child in a new process group*/ 25 26 if(execve(argv[0],argv,environ)<0){ 27 printf("%s: Command not found ",argv[0]); 28 exit(0); 29 } 30 } 31 32 addjob(jobs, pid, bg?BG:FG,cmdline); /*add job into jobs*/ 33 Sigprocmask(SIG_UNBLOCK,&mask,NULL); /*unblock the SIGCHLD signal in parent*/ 34 35 bg ? printf("[%d] (%d) %s", pid2jid(pid), pid,cmdline):waitfg(pid); /*do in background or foreground*/ 36 } 37 return; 38 }
2 builtin_cmd: 辨识和解析出bulidin命令: quit, fg, bg, and jobs.
辨别是否是builtin命令,是就执行;否则就返回。
参考p503
int builtin_cmd(char **argv) { if(!strcmp(argv[0],"quit")){ exit(0); } if(!strcmp(argv[0],"&")){ return 1; } if(!strcmp(argv[0],"bg")||!strcmp(argv[0],"fg"){ do_bgfg(argv); return 1; } if(!strcmp(argv[0],"jobs")){ listjobs(jobs); return 1; } return 0; //非builtin命令 }
3 do_bgfg: 实现bg和fg命令.
void do_bgfg(char **argv) { pid_t pid; struct job_t *job; char *id = argv[1]; if(id==NULL){ //没有参数 printf("%s command requires PID or %%jobid argument ",argv[0]); return; } if(id[0]=='%'){ //%number,number表示任务id int jid = atoi(&id[1]); job = getjobjid(jobs,jid);//取到job if(job==NULL){ printf("%%%d: No such job ",jid); return; } }else if(isdigit(id[0])){ //number,表示进程id pid = atoi(id); job = getjobpid(jobs,pid);//取到job if(job==NULL){ printf("(%d): No such process ",pid); return ; } }else{ printf("%s: argument must be a PID or %%jobid ", argv[0]); return; } Kill(-(job->pid),SIGCONT); //发送SIGCONT 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; }
4 waitfg: 实现等待前台程序运行结束.
void waitfg(pid_t pid){ while(pid == fgpid(jobs)){//只要当前进程在前台就等待 sleep(0); } return ; }
5 sigchld_handler: 响应SIGCHLD. 80 lines]
当子进程停止或终止时向父进程发送信号的处理函数,要循环进行waitpid操作,尽量多回收子进程
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)){//僵尸进程,删掉 printf("Job [%d] (%d) terminated by signal %d ",pid2jid(pid),pid,WTERMSIG(status)); deletejob(jobs,pid); } if(WIFSTOPPED(status)){//是个停止进程,不需要回收 printf("Job [%d] (%d) stopped by signal %d ",pid2jid(pid),pid,WSTOPSIG(status)); struct job_t *job = getjobpid(jobs,pid); if(job !=NULL )job->state = ST;//设置状态为ST } } return; }
6 sigint_handler: 响应 SIGINT (ctrl-c) 信号.
接收到应强行终止前台的进程。
void sigint_handler(int sig) { pid_t pid = fgpid(jobs);//获取前台进程pid if(pid != 0){ Kill(-pid,SIGINT);//删掉前台进程组 } return; }
7 sigtstp_handler: 响应 SIGTSTP (ctrl-z) 信号.
void sigtstp_handler(int sig) { pid_t pid = fgpid(jobs);//获取前台进程pid if(pid != 0 ){ struct job_t *job = getjobpid(jobs,pid);//得到作业 if(job->state == ST){ //已经终止状态,返回 return; }else{ Kill(-pid,SIGTSTP); } } return; }