首页 > 代码库 > CSAPP2e:Shell lab 解答

CSAPP2e:Shell lab 解答

        期中之后的第一个lab 就是实现一个简单的Shell 程序,程序的大部分已经写好,只需要实现 eval 函数和处理信号的sigchld_handle, sigint_handle, sigtstp_handle这三个函数。 这个lab 主要要求处理好各个信号,因为上课的时候一直听得很糊涂,就拖着没有写,直到这两天deadline逼近才动手。同样是时间紧迫,debug的时候出了很多问题,在网上搜了很多解答,但是因为题目版本不一样,并不完全适用,比如之前的不需要重定向。因此把自己写的代码也贴出来,最后是一些自己的心得。

        这里还有shell lab的所有文件压缩包提供下载
 

        一些心得:

  • 测试的时候的一些奇葩函数, mytstpp 向shell 发出一个SIGTSTP,而mytstps 向自己的进程发出SIGTSTP信号。前者shell 会调用sigtstp_handle 函数,而后者会使子进程stop,然后向shell 发出SIGCHLD信号,shell调用sigchld_handle 函数。这就要求我们分清楚每一个信号会由哪个进程(函数)处理。
  • shell收到的每个SIGTDTP,SIGINT信号都要发给前台进程,而这个前台进程是由自己的job_list 列表维护的,而实际上每个子进程的停止,终止都是由信号操作。
  • 调试的时候可以用GDB,但是涉及到信号和线程,可以在网上搜一下有很好的教程。
  • 通过运行tshref 可以找到每个命令的标准输出,这个稍微注意一下就可以了。
就写到这里了,如果有错误,烦请指正,同时欢迎交流!
 
代码附下:
  1 /*  2  * tsh - A tiny shell program with job control  3  *  4  * 2014.12.1  5  *   6  */  7 #include <assert.h>  8 #include <stdio.h>  9 #include <stdlib.h> 10 #include <unistd.h> 11 #include <string.h> 12 #include <ctype.h> 13 #include <signal.h> 14 #include <sys/types.h> 15 #include <fcntl.h> 16 #include <sys/wait.h> 17 #include <errno.h> 18  19 /* Misc manifest constants */ 20 #define MAXLINE    1024   /* max line size */ 21 #define MAXARGS     128   /* max args on a command line */ 22 #define MAXJOBS      16   /* max jobs at any point in time */ 23 #define MAXJID    1<<16   /* max job ID */ 24  25 /* Job states */ 26 #define UNDEF         0   /* undefined */ 27 #define FG            1   /* running in foreground */ 28 #define BG            2   /* running in background */ 29 #define ST            3   /* stopped */ 30  31 /* 32  * Jobs states: FG (foreground), BG (background), ST (stopped) 33  * Job state transitions and enabling actions: 34  *     FG -> ST  : ctrl-z 35  *     ST -> FG  : fg command 36  *     ST -> BG  : bg command 37  *     BG -> FG  : fg command 38  * At most 1 job can be in the FG state. 39  */ 40  41 /* Parsing states */ 42 #define ST_NORMAL   0x0   /* next token is an argument */ 43 #define ST_INFILE   0x1   /* next token is the input file */ 44 #define ST_OUTFILE  0x2   /* next token is the output file */ 45  46 /* Global variables */ 47 extern char **environ; /* defined in libc */ 48 char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */ 49 int verbose = 0; /* if true, print additional output */ 50 int nextjid = 1; /* next job ID to allocate */ 51 char sbuf[MAXLINE]; /* for composing sprintf messages */ 52  53 struct job_t { /* The job struct */ 54     pid_t pid; /* job PID */ 55     int jid; /* job ID [1, 2, ...] */ 56     int state; /* UNDEF, BG, FG, or ST */ 57     char cmdline[MAXLINE]; /* command line */ 58 }; 59 struct job_t job_list[MAXJOBS]; /* The job list */ 60  61 struct cmdline_tokens { 62     int argc; /* Number of arguments */ 63     char *argv[MAXARGS]; /* The arguments list */ 64     char *infile; /* The input file */ 65     char *outfile; /* The output file */ 66     enum builtins_t { /* Indicates if argv[0] is a builtin command */ 67         BUILTIN_NONE, BUILTIN_QUIT, BUILTIN_JOBS, BUILTIN_BG, BUILTIN_FG 68     } builtins; 69 }; 70 /* End global variables */ 71  72 /* Function prototypes */ 73 void eval(char *cmdline); 74  75 void sigchld_handler(int sig); 76 void sigtstp_handler(int sig); 77 void sigint_handler(int sig); 78  79 /* Here are helper routines that we‘ve provided for you */ 80 int parseline(const char *cmdline, struct cmdline_tokens *tok); 81 void sigquit_handler(int sig); 82  83 void clearjob(struct job_t *job); 84 void initjobs(struct job_t *job_list); 85 int maxjid(struct job_t *job_list); 86 int addjob(struct job_t *job_list, pid_t pid, int state, char *cmdline); 87 int deletejob(struct job_t *job_list, pid_t pid); 88 pid_t fgpid(struct job_t *job_list); 89 struct job_t *getjobpid(struct job_t *job_list, pid_t pid); 90 struct job_t *getjobjid(struct job_t *job_list, int jid); 91 int pid2jid(pid_t pid); 92 void listjobs(struct job_t *job_list, int output_fd); 93  94 void usage(void); 95 void unix_error(char *msg); 96 void app_error(char *msg); 97 typedef void handler_t(int); 98 handler_t *Signal(int signum, handler_t *handler); 99 100 /*101  * main - The shell‘s main routine102  */103 int main(int argc, char **argv) {104     char c;105     char cmdline[MAXLINE]; /* cmdline for fgets */106     int emit_prompt = 1; /* emit prompt (default) */107 108     /* Redirect stderr to stdout (so that driver will get all output109      * on the pipe connected to stdout) */110     dup2(1, 2);111 112     /* Parse the command line */113     while ((c = getopt(argc, argv, "hvp")) != EOF) {114         switch (c) {115         case h: /* print help message */116             usage();117             break;118         case v: /* emit additional diagnostic info */119             verbose = 1;120             break;121         case p: /* don‘t print a prompt */122             emit_prompt = 0; /* handy for automatic testing */123             break;124         default:125             usage();126         }127     }128 129     /* Install the signal handlers */130 131     /* These are the ones you will need to implement */132     Signal(SIGINT, sigint_handler); /* ctrl-c */133     Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */134     Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */135     Signal(SIGTTIN, SIG_IGN);136     Signal(SIGTTOU, SIG_IGN);137 138     /* This one provides a clean way to kill the shell */139     Signal(SIGQUIT, sigquit_handler);140 141     /* Initialize the job list */142     initjobs(job_list);143 144     /* Execute the shell‘s read/eval loop */145     while (1) {146 147         if (emit_prompt) {148             printf("%s", prompt);149             fflush(stdout);150         }151         if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))152             app_error("fgets error");153         if (feof(stdin)) {154             /* End of file (ctrl-d) */155             printf("\n");156             fflush(stdout);157             fflush(stderr);158             exit(0);159         }160 161         /* Remove the trailing newline */162         cmdline[strlen(cmdline) - 1] = \0;163 164         /* Evaluate the command line */165         eval(cmdline);166 167         fflush(stdout);168         fflush(stdout);169     }170 171     exit(0); /* control never reaches here */172 }173 174 /*175  * eval - Evaluate the command line that the user has just typed in176  *177  * If the user has requested a built-in command (quit, jobs, bg or fg)178  * then execute it immediately. Otherwise, fork a child process and179  * run the job in the context of the child. If the job is running in180  * the foreground, wait for it to terminate and then return.  Note:181  * each child process must have a unique process group ID so that our182  * background children don‘t receive SIGINT (SIGTSTP) from the kernel183  * when we type ctrl-c (ctrl-z) at the keyboard.184  */185 void eval(char *cmdline) {186     int bg; /* should the job run in bg or fg? */187     struct cmdline_tokens tok;188 189     /* Parse command line */190     bg = parseline(cmdline, &tok);191 192     if (bg == -1)193         return; /* parsing error */194     if (tok.argv[0] == NULL)195         return; /* ignore empty lines */196 197     int stdi,stdo;198     stdi=dup(STDIN_FILENO);199     stdo=dup(STDOUT_FILENO);200 201     int infg, outfg;202     infg = -1;203     outfg = -1;204     if (tok.infile != NULL) {205         infg = open(tok.infile, O_RDONLY, 0);206         dup2(infg, STDIN_FILENO);207     }208     if (tok.outfile != NULL) {209         outfg = open(tok.outfile, O_RDWR, 0);210         dup2(outfg, STDOUT_FILENO);211     }212 213     pid_t pid;214     struct job_t *job;215     sigset_t mask;216     sigemptyset(&mask);217     sigaddset(&mask, SIGCHLD);218     sigaddset(&mask, SIGINT);219     sigaddset(&mask, SIGTSTP);220     if (tok.builtins == BUILTIN_NONE) {221         sigprocmask(SIG_BLOCK, &mask, NULL);222 223         if ((pid = fork()) == 0) {224             sigprocmask(SIG_UNBLOCK, &mask, NULL);225             setpgid(0, 0);226 227             //Signal(SIGTTIN,SIG_DFL);228            //Signal(SIGTTOU,SIG_DFL);229 230             execve(tok.argv[0], tok.argv, environ);231 232             if(infg!=-1)233                 close(infg);234             if(outfg!=-1)235                 close(outfg);236 237         } else {238 239             addjob(job_list, pid, bg + 1, cmdline);240             job = getjobpid(job_list, pid);241             sigprocmask(SIG_UNBLOCK, &mask, NULL);242 243             sigemptyset(&mask);244             if (!bg) {245                 while(pid==fgpid(job_list))246                     sleep(0);247             } else {248                 printf("[%d] (%d) %s\n", job->jid, pid, job->cmdline);249             }250         }251 252     } else {253         if(tok.builtins==BUILTIN_QUIT)254             exit(0);255         else if(tok.builtins==BUILTIN_JOBS) {256             listjobs(job_list,STDOUT_FILENO);257         }258         else{259             int jid;260             if(tok.argv[1][0]==%)261                 jid=atoi((tok.argv[1])+sizeof(char));262             else263                 jid=pid2jid(atoi(tok.argv[1]));264             job=getjobjid(job_list,jid);265             if(tok.builtins==BUILTIN_BG) {266                 printf("[%d] (%d) %s\n",job->jid,job->pid,job->cmdline);267                 job->state=BG;268                 kill(-(job->pid),SIGCONT);269             } else {270                 job->state=FG;271                 kill(-(job->pid),SIGCONT);272             }273         }274     }275 276     dup2(stdi, STDIN_FILENO);277     dup2(stdo, STDOUT_FILENO);278     if(infg!=-1)279         close(infg);280     if(outfg!=-1)281         close(outfg);282     return;283 }284 285 /*286  * parseline - Parse the command line and build the argv array.287  *288  * Parameters:289  *   cmdline:  The command line, in the form:290  *291  *                command [arguments...] [< infile] [> oufile] [&]292  *293  *   tok:      Pointer to a cmdline_tokens structure. The elements of this294  *             structure will be populated with the parsed tokens. Characters295  *             enclosed in single or double quotes are treated as a single296  *             argument.297  * Returns:298  *   1:        if the user has requested a BG job299  *   0:        if the user has requested a FG job300  *  -1:        if cmdline is incorrectly formatted301  *302  * Note:       The string elements of tok (e.g., argv[], infile, outfile)303  *             are statically allocated inside parseline() and will be304  *             overwritten the next time this function is invoked.305  */306 int parseline(const char *cmdline, struct cmdline_tokens *tok) {307 308     static char array[MAXLINE]; /* holds local copy of command line */309     const char delims[10] = " \t\r\n"; /* argument delimiters (white-space) */310     char *buf = array; /* ptr that traverses command line */311     char *next; /* ptr to the end of the current arg */312     char *endbuf; /* ptr to the end of the cmdline string */313     int is_bg; /* background job? */314 315     int parsing_state; /* indicates if the next token is the316      input or output file */317 318     if (cmdline == NULL) {319         (void) fprintf(stderr, "Error: command line is NULL\n");320         return -1;321     }322 323     (void) strncpy(buf, cmdline, MAXLINE);324     endbuf = buf + strlen(buf);325 326     tok->infile = NULL;327     tok->outfile = NULL;328 329     /* Build the argv list */330     parsing_state = ST_NORMAL;331     tok->argc = 0;332 333     while (buf < endbuf) {334         /* Skip the white-spaces */335         buf += strspn(buf, delims);336         if (buf >= endbuf)337             break;338 339         /* Check for I/O redirection specifiers */340         if (*buf == <) {341             if (tok->infile) {342                 (void) fprintf(stderr, "Error: Ambiguous I/O redirection\n");343                 return -1;344             }345             parsing_state |= ST_INFILE;346             buf++;347             continue;348         }349         if (*buf == >) {350             if (tok->outfile) {351                 (void) fprintf(stderr, "Error: Ambiguous I/O redirection\n");352                 return -1;353             }354             parsing_state |= ST_OUTFILE;355             buf++;356             continue;357         }358 359         if (*buf == \‘ || *buf == \") {360             /* Detect quoted tokens */361             buf++;362             next = strchr(buf, *(buf - 1));363         } else {364             /* Find next delimiter */365             next = buf + strcspn(buf, delims);366         }367 368         if (next == NULL) {369             /* Returned by strchr(); this means that the closing370              quote was not found. */371             (void) fprintf(stderr, "Error: unmatched %c.\n", *(buf - 1));372             return -1;373         }374 375         /* Terminate the token */376         *next = \0;377 378         /* Record the token as either the next argument or the input/output file */379         switch (parsing_state) {380         case ST_NORMAL:381             tok->argv[tok->argc++] = buf;382             break;383         case ST_INFILE:384             tok->infile = buf;385             break;386         case ST_OUTFILE:387             tok->outfile = buf;388             break;389         default:390             (void) fprintf(stderr, "Error: Ambiguous I/O redirection\n");391             return -1;392         }393         parsing_state = ST_NORMAL;394 395         /* Check if argv is full */396         if (tok->argc >= MAXARGS - 1)397             break;398 399         buf = next + 1;400     }401 402     if (parsing_state != ST_NORMAL) {403         (void) fprintf(stderr,404                 "Error: must provide file name for redirection\n");405         return -1;406     }407 408     /* The argument list must end with a NULL pointer */409     tok->argv[tok->argc] = NULL;410 411     if (tok->argc == 0) /* ignore blank line */412         return 1;413 414     if (!strcmp(tok->argv[0], "quit")) { /* quit command */415         tok->builtins = BUILTIN_QUIT;416     } else if (!strcmp(tok->argv[0], "jobs")) { /* jobs command */417         tok->builtins = BUILTIN_JOBS;418     } else if (!strcmp(tok->argv[0], "bg")) { /* bg command */419         tok->builtins = BUILTIN_BG;420     } else if (!strcmp(tok->argv[0], "fg")) { /* fg command */421         tok->builtins = BUILTIN_FG;422     } else {423         tok->builtins = BUILTIN_NONE;424     }425 426     /* Should the job run in the background? */427     if ((is_bg = (*tok->argv[tok->argc - 1] == &)) != 0)428         tok->argv[--tok->argc] = NULL;429 430     return is_bg;431 }432 433 /*****************434  * Signal handlers435  *****************/436 437 /*438  * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever439  *     a child job terminates (becomes a zombie), or stops because it440  *     received a SIGSTOP, SIGTSTP, SIGTTIN or SIGTTOU signal. The441  *     handler reaps all available zombie children, but doesn‘t wait442  *     for any other currently running children to terminate.443  */444 void sigchld_handler(int sig) {445     pid_t pid;446     int status;447     while ((pid = waitpid(-1, &status, WUNTRACED | WNOHANG)) > 0) {448         if (WIFSTOPPED(status)) {449             int jid=pid2jid(pid);450             if(jid!=0) {451                 printf("Job [%d] (%d) stopped by signal %d\n",jid,pid,WSTOPSIG(status));452                 (getjobpid(job_list,pid))->state=ST;453             }454         } else if (WIFSIGNALED(status)) {455             if (WTERMSIG(status) == SIGINT) {456                 int jid=pid2jid(pid);457                 if(jid!=0) {458                     printf("Job [%d] (%d) terminated by signal %d\n",jid,pid,SIGINT);459                     deletejob(job_list,pid);460                 }461             }462             else463                 deletejob(job_list, pid);464         } else465             deletejob(job_list, pid);466     }467     return;468 }469 470 /*471  * sigint_handler - The kernel sends a SIGINT to the shell whenver the472  *    user types ctrl-c at the keyboard.  Catch it and send it along473  *    to the foreground job.474  */475 void sigint_handler(int sig) {476     pid_t pid = fgpid(job_list);477     if (pid != 0) {478         kill(-pid, sig);479     }480     return;481 }482 483 /*484  * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever485  *     the user types ctrl-z at the keyboard. Catch it and suspend the486  *     foreground job by sending it a SIGTSTP.487  */488 void sigtstp_handler(int sig) {489     pid_t pid = fgpid(job_list);490     if (pid != 0) {491         kill(-pid, sig);492     }493     return;494 }495 496 /*********************497  * End signal handlers498  *********************/499 500 /***********************************************501  * Helper routines that manipulate the job list502  **********************************************/503 504 /* clearjob - Clear the entries in a job struct */505 void clearjob(struct job_t *job) {506     job->pid = 0;507     job->jid = 0;508     job->state = UNDEF;509     job->cmdline[0] = \0;510 }511 512 /* initjobs - Initialize the job list */513 void initjobs(struct job_t *job_list) {514     int i;515 516     for (i = 0; i < MAXJOBS; i++)517         clearjob(&job_list[i]);518 }519 520 /* maxjid - Returns largest allocated job ID */521 int maxjid(struct job_t *job_list) {522     int i, max = 0;523 524     for (i = 0; i < MAXJOBS; i++)525         if (job_list[i].jid > max)526             max = job_list[i].jid;527     return max;528 }529 530 /* addjob - Add a job to the job list */531 int addjob(struct job_t *job_list, pid_t pid, int state, char *cmdline) {532     int i;533 534     if (pid < 1)535         return 0;536 537     for (i = 0; i < MAXJOBS; i++) {538         if (job_list[i].pid == 0) {539             job_list[i].pid = pid;540             job_list[i].state = state;541             job_list[i].jid = nextjid++;542             if (nextjid > MAXJOBS)543                 nextjid = 1;544             strcpy(job_list[i].cmdline, cmdline);545             if (verbose) {546                 printf("Added job [%d] %d %s\n", job_list[i].jid,547                         job_list[i].pid, job_list[i].cmdline);548             }549             return 1;550         }551     }552     printf("Tried to create too many jobs\n");553     return 0;554 }555 556 /* deletejob - Delete a job whose PID=pid from the job list */557 int deletejob(struct job_t *job_list, pid_t pid) {558     int i;559 560     if (pid < 1)561         return 0;562 563     for (i = 0; i < MAXJOBS; i++) {564         if (job_list[i].pid == pid) {565             clearjob(&job_list[i]);566             nextjid = maxjid(job_list) + 1;567             return 1;568         }569     }570     return 0;571 }572 573 /* fgpid - Return PID of current foreground job, 0 if no such job */574 pid_t fgpid(struct job_t *job_list) {575     int i;576 577     for (i = 0; i < MAXJOBS; i++)578         if (job_list[i].state == FG)579             return job_list[i].pid;580     return 0;581 }582 583 /* getjobpid  - Find a job (by PID) on the job list */584 struct job_t *getjobpid(struct job_t *job_list, pid_t pid) {585     int i;586 587     if (pid < 1)588         return NULL;589     for (i = 0; i < MAXJOBS; i++)590         if (job_list[i].pid == pid)591             return &job_list[i];592     return NULL;593 }594 595 /* getjobjid  - Find a job (by JID) on the job list */596 struct job_t *getjobjid(struct job_t *job_list, int jid) {597     int i;598 599     if (jid < 1)600         return NULL;601     for (i = 0; i < MAXJOBS; i++)602         if (job_list[i].jid == jid)603             return &job_list[i];604     return NULL;605 }606 607 /* pid2jid - Map process ID to job ID */608 int pid2jid(pid_t pid) {609     int i;610 611     if (pid < 1)612         return 0;613     for (i = 0; i < MAXJOBS; i++)614         if (job_list[i].pid == pid) {615             return job_list[i].jid;616         }617     return 0;618 }619 620 /* listjobs - Print the job list */621 void listjobs(struct job_t *job_list, int output_fd) {622     int i;623     char buf[MAXLINE];624 625     for (i = 0; i < MAXJOBS; i++) {626         memset(buf, \0, MAXLINE);627         if (job_list[i].pid != 0) {628             sprintf(buf, "[%d] (%d) ", job_list[i].jid, job_list[i].pid);629             if (write(output_fd, buf, strlen(buf)) < 0) {630                 fprintf(stderr, "Error writing to output file\n");631                 exit(1);632             }633             memset(buf, \0, MAXLINE);634             switch (job_list[i].state) {635             case BG:636                 sprintf(buf, "Running    ");637                 break;638             case FG:639                 sprintf(buf, "Foreground ");640                 break;641             case ST:642                 sprintf(buf, "Stopped    ");643                 break;644             default:645                 sprintf(buf, "listjobs: Internal error: job[%d].state=%d ", i,646                         job_list[i].state);647             }648             if (write(output_fd, buf, strlen(buf)) < 0) {649                 fprintf(stderr, "Error writing to output file\n");650                 exit(1);651             }652             memset(buf, \0, MAXLINE);653             sprintf(buf, "%s\n", job_list[i].cmdline);654             if (write(output_fd, buf, strlen(buf)) < 0) {655                 fprintf(stderr, "Error writing to output file\n");656                 exit(1);657             }658         }659     }660     if (output_fd != STDOUT_FILENO)661         close(output_fd);662 }663 /******************************664  * end job list helper routines665  ******************************/666 667 /***********************668  * Other helper routines669  ***********************/670 671 /*672  * usage - print a help message673  */674 void usage(void) {675     printf("Usage: shell [-hvp]\n");676     printf("   -h   print this message\n");677     printf("   -v   print additional diagnostic information\n");678     printf("   -p   do not emit a command prompt\n");679     exit(1);680 }681 682 /*683  * unix_error - unix-style error routine684  */685 void unix_error(char *msg) {686     fprintf(stdout, "%s: %s\n", msg, strerror(errno));687     exit(1);688 }689 690 /*691  * app_error - application-style error routine692  */693 void app_error(char *msg) {694     fprintf(stdout, "%s\n", msg);695     exit(1);696 }697 698 /*699  * Signal - wrapper for the sigaction function700  */701 handler_t *Signal(int signum, handler_t *handler) {702     struct sigaction action, old_action;703 704     action.sa_handler = handler;705     sigemptyset(&action.sa_mask); /* block sigs of type being handled */706     action.sa_flags = SA_RESTART; /* restart syscalls if possible */707 708     if (sigaction(signum, &action, &old_action) < 0)709         unix_error("Signal error");710     return (old_action.sa_handler);711 }712 713 /*714  * sigquit_handler - The driver program can gracefully terminate the715  *    child shell by sending it a SIGQUIT signal.716  */717 void sigquit_handler(int sig) {718     printf("Terminating after receipt of SIGQUIT signal\n");719     exit(1);720 }

 

CSAPP2e:Shell lab 解答