|
期中之后的第一个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 routine
102 */
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 output
109 * 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 in
176 *
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 and
179 * run the job in the context of the child. If the job is running in
180 * the foreground, wait for it to terminate and then return. Note:
181 * each child process must have a unique process group ID so that our
182 * background children don't receive SIGINT (SIGTSTP) from the kernel
183 * 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);
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 else
263 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 this
294 * structure will be populated with the parsed tokens. Characters
295 * enclosed in single or double quotes are treated as a single
296 * argument.
297 * Returns:
298 * 1: if the user has requested a BG job
299 * 0: if the user has requested a FG job
300 * -1: if cmdline is incorrectly formatted
301 *
302 * Note: The string elements of tok (e.g., argv[], infile, outfile)
303 * are statically allocated inside parseline() and will be
304 * 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 the
316 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 closing
370 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 handlers
435 *****************/
436
437 /*
438 * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
439 * a child job terminates (becomes a zombie), or stops because it
440 * received a SIGSTOP, SIGTSTP, SIGTTIN or SIGTTOU signal. The
441 * handler reaps all available zombie children, but doesn't wait
442 * 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 else
463 deletejob(job_list, pid);
464 } else
465 deletejob(job_list, pid);
466 }
467 return;
468 }
469
470 /*
471 * sigint_handler - The kernel sends a SIGINT to the shell whenver the
472 * user types ctrl-c at the keyboard. Catch it and send it along
473 * 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 whenever
485 * the user types ctrl-z at the keyboard. Catch it and suspend the
486 * 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 handlers
498 *********************/
499
500 /***********************************************
501 * Helper routines that manipulate the job list
502 **********************************************/
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);
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.jid > max)
526 max = job_list.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.pid == 0) {
539 job_list.pid = pid;
540 job_list.state = state;
541 job_list.jid = nextjid++;
542 if (nextjid > MAXJOBS)
543 nextjid = 1;
544 strcpy(job_list.cmdline, cmdline);
545 if (verbose) {
546 printf("Added job [%d] %d %s\n", job_list.jid,
547 job_list.pid, job_list.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.pid == pid) {
565 clearjob(&job_list);
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.state == FG)
579 return job_list.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.pid == pid)
591 return &job_list;
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.jid == jid)
603 return &job_list;
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.pid == pid) {
615 return job_list.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.pid != 0) {
628 sprintf(buf, "[%d] (%d) ", job_list.jid, job_list.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.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.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.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 routines
665 ******************************/
666
667 /***********************
668 * Other helper routines
669 ***********************/
670
671 /*
672 * usage - print a help message
673 */
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 routine
684 */
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 routine
692 */
693 void app_error(char *msg) {
694 fprintf(stdout, "%s\n", msg);
695 exit(1);
696 }
697
698 /*
699 * Signal - wrapper for the sigaction function
700 */
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 the
715 * 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 }
|
|