CSAPP的ShellLab是实现一个自己的shell程序,完成之后可以熟练掌握UNIX关于进程的系统调用(例如fork、waitpid等),熟悉Linux的信号机制,而且这个Lab非常有趣。
实现这个Lab主要是完成以下几个函数的编写:
void eval(char *cmdline) { char *argv[MAXARGS]; char buf[MAXLINE]; int bg; // Should the job run in bg or fg pid_t pid; sigset_t mask_all,mask_one,prev_mask; strcpy(buf,cmdline); bg = parseline(buf,argv); if( argv[0] == NULL ){ return; // Ignore empty lines } if( !builtin_cmd(argv) ){ sigemptyset(&mask_one); sigfillset(&mask_all); sigaddset(&mask_one,SIGCHLD); // 父进程在创建子进程时,不要响应子进程结束中断,故添加SIGCHLD到阻塞队列 sigprocmask(SIG_BLOCK,&mask_one,&prev_mask); if( (pid = fork()) == 0 ){ // Child process // 将子进程单独放入一个进程组里面,否则在删除job的时候可能删除shell进程的别的子进程 setpgid(0, 0); // 取消阻塞,这一步非常关键,因为子进程继承了父进程的阻塞队列,故应该将子进程的阻塞队列清空 sigprocmask(SIG_SETMASK,&prev_mask,NULL); if( execve(argv[0],argv,environ) < 0 ){ printf("%s: Command not found.\n",argv[0]); exit(1); } } // 给 addjob 加锁,保证原子操作(因为jobs是一个全局变量,而有些中断的处理器也会对jobs进行操作,这样就不安全了) sigprocmask(SIG_BLOCK,&mask_all,NULL); addjob(jobs,pid,bg ? BG : FG,cmdline); sigprocmask(SIG_SETMASK,&prev_mask,NULL); // Parent process waits for foreground job to terminate if( !bg ){ waitfg(pid); }else { printf("[%d] (%d) %s",pid2jid(pid),pid,cmdline); } } return; }
int builtin_cmd(char **argv) { if( !strcmp(argv[0],"quit") ){ kill(getpid(),SIGQUIT); return 1; }else if( !strcmp(argv[0],"jobs") ){ listjobs(jobs); return 1; }else if( !strcmp(argv[0],"&") ){ return 1; }else if( !strcmp(argv[0],"bg") || !strcmp(argv[0],"fg") ){ do_bgfg(argv); return 1; } return 0; /* not a builtin command */ }
void do_bgfg(char **argv) { struct job_t* job; char* flag; int jid; pid_t pid; flag = argv[1]; if( flag == NULL ){ printf("%s command requires PID or %%jobid argument",argv[0]); return; } // if it is a jid if( flag[0] == '%' ){ jid = atoi(&flag[1]); job = getjobjid(jobs,jid); if( job == NULL ){ printf("(%s)No such job\n",flag); return; }else{ // get the pid if a valid job for later to kill pid = job->pid; } }else if(isdigit(flag[0])){ // if it is a pid pid = atoi(flag); // get jid jid = pid2jid(pid); // get job job = getjobjid(jobs,jid); if( job == NULL ){ printf("(%d)No such process\n",pid); return; } }else{ printf("%s: argument must be a PID or %%jobid\n",argv[0]); return; } // kill for each time kill(-pid,SIGCONT); if( !strcmp("fg",argv[0]) ){ // wait for fg job->state = FG; waitfg(job->pid); }else{ // print for bg printf("[%d] (%d) %s\n",job->jid,job->pid,job->cmdline); job->state = BG; } }
void waitfg(pid_t pid) { struct job_t* job = getjobpid(jobs,pid); if( job == NULL ){ return; } while( pid == fgpid(jobs) ){ // spin sleep(1); } }
void sigchld_handler(int sig) { int old_errno = errno; pid_t pid; int status; while( (pid = waitpid(-1,&status,WNOHANG | WUNTRACED)) > 0 ){ // 因为上面提示信息说明不要等待任何子进程终止,所以这里 options 使用 WNOHANG | WUNTRACED // enter here means that one of child has changed status if( WIFEXITED(status) ){ // exit normally or call exit(0) or call exit(1) // delete terminated child deletejob(jobs,pid); }else if( WIFSIGNALED(status) ){ // terminated by received a signal int jid = pid2jid(pid); printf("Job [%d] (%d) terminated by signal %d\n",jid,pid,WTERMSIG(status)); deletejob(jobs,pid); }else if( WIFSTOPPED(status) ){ // stop struct job_t* job = getjobpid(jobs,pid); job->state = ST; int jid = pid2jid(pid); printf("Job [%d] (%d) stopped by signal %d\n",jid,pid,WSTOPSIG(status)); } } errno = old_errno; return; }
void sigint_handler(int sig) { int old_errno = errno; pid_t pid = fgpid(jobs); if( pid == 0 ){ return; } kill(-pid,sig); errno = old_errno; }
void sigtstp_handler(int sig) { int old_errno = errno; pid_t pid = fgpid(jobs); if( pid == 0 ){ return; } kill(-pid,sig); errno = old_errno; }