ShellLab
一、 Lab介绍
CSAPP的ShellLab是实现一个自己的shell程序,完成之后可以熟练掌握UNIX关于进程的系统调用(例如fork、waitpid等),熟悉Linux的信号机制,而且这个Lab非常有趣。
二、 Lab实现
实现这个Lab主要是完成以下几个函数的编写:
eval实现
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;
}
buildin_cmd实现
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 */
}
do_bgfg实现
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;
}
}
waitfg实现
void waitfg(pid_t pid)
{
struct job_t* job = getjobpid(jobs,pid);
if( job == NULL ){
return;
}
while( pid == fgpid(jobs) ){ // spin
sleep(1);
}
}
sigchld_handler实现
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;
}
sigint_handler实现
void sigint_handler(int sig)
{
int old_errno = errno;
pid_t pid = fgpid(jobs);
if( pid == 0 ){
return;
}
kill(-pid,sig);
errno = old_errno;
}
sigstp_handler实现
void sigtstp_handler(int sig)
{
int old_errno = errno;
pid_t pid = fgpid(jobs);
if( pid == 0 ){
return;
}
kill(-pid,sig);
errno = old_errno;
}