首页 > 代码库 > 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 解答
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。