这次的实验要求我们实现一个简化版的shell,需要能够实现子程序的前台与后台运行以及切换,同时实现几条内建命令。
需要我们实现的内建命令包括以下4个:

  • quit: 退出shell
  • jobs: 打印子进程列表与运行状态
  • fg: 切换进程到前台运行
  • bg: 切换进程到后台运行

eval()

  1. void eval(char *cmdline)
  2. {
  3. char *argv[MAXARGS];
  4. char buf[MAXLINE];
  5. int bg;
  6. pid_t pid;
  7. sigset_t mask_one, mask_all, pre_mask;
  8. sigfillset(&mask_all);
  9. sigemptyset(&mask_one);
  10. sigaddset(&mask_one, SIGCHLD);
  11. strcpy(buf, cmdline);
  12. bg = parseline(buf, argv);
  13. if (argv[0] == NULL)
  14. return;
  15. if (!builtin_cmd(argv))
  16. {
  17. sigprocmask(SIG_BLOCK, &mask_one, &pre_mask);//阻断SIGCHLD
  18. if ((pid = fork()) == 0)
  19. {
  20. setpgid(0, 0); //*******很重要
  21. sigprocmask(SIG_SETMASK, &pre_mask, NULL);
  22. if (execve(argv[0], argv, environ) < 0)
  23. {
  24. printf("%s: Command not found
  25. ", argv[0]);
  26. exit(0);
  27. }
  28. }
  29. sigprocmask(SIG_BLOCK, &mask_all, NULL);//addjob涉及全局变量访问
  30. if (bg)
  31. addjob(jobs, pid, BG, buf);
  32. else
  33. addjob(jobs, pid, FG, buf);
  34. if (!bg)
  35. waitfg(pid);
  36. else
  37. {
  38. struct job_t *curr_bgmask = getjobpid(jobs, pid);
  39. printf("[%d] (%d) %s", curr_bgmask->jid, curr_bgmask->pid, curr_bgmask->cmdline);
  40. }
  41. sigprocmask(SIG_SETMASK, &pre_mask, NULL);
  42. }
  43. return;
  44. }

这个函数本身比较简单,书上的代码拿来改改就能用,但是有一个特别重要的问题需要注意
CSAPP: shelllab - 图1
大体意思就是在Unix shell上运行我们自己的shell时,需要在创建出一个子进程之后调用setpgid(0, 0) 将他们放到一个新的进程组,否则我们的ctrl-c 与ctrl-z 会把信号发送到包括我们的shell在内的全部进程中,就是因为没注意这个使得我的shell产生的很多迷幻的错误,以至于调试很久却不知道原因(其实只能怪自己没认真看手册)。

waitfg()

对于waitfg()函数,我想过几个实行方案:

waitfg1()

  1. # This is a buggy code.
  2. void waitfg(pid_t pid)
  3. {
  4. int status;
  5. if (waitpid(pid,&status,0) < 0)
  6. unix_error("waitfg: waitpid error
  7. ");
  8. printf("wait %d ok
  9. ",pid);
  10. return;
  11. }
  12. void sigchld_handler(int sig)
  13. {
  14. pid_t pid;
  15. int old_errno = errno;
  16. while ((pid = waitpid(-1, NULL, 0)) > 0) //这里必须是while,书上讲的很明白了
  17. printf("Handler reaped child %d
  18. ", (int)pid);
  19. if (errno != ECHILD)
  20. unix_error("waitpid error");
  21. errno = old_errno;
  22. return;
  23. }

代码是之前的,省略了一些不相关的细节。
我们直接在waitfg里面调用 waitpid 函数,但是这样做有个很明显的问题,就是在回收后台进程时,如果在sigchld_handler 函数的执行阶段前台进程结束了,会被一并回收掉,从而导致waitfg 中的waitpid 回收不到前台进程,从而产生一个错误。

waitfg2()

  1. # This is a buggy code.
  2. void waitfg(pid_t pid)
  3. {
  4. int status;
  5. sigset_t mask_chld,pre_mask;
  6. sigemptyset(&mask_chld);
  7. sigaddset(&mask_chld,SIGCHLD);
  8. sigprocmask(SIG_SETMASK,&mask_chld,&pre_mask);
  9. if (waitpid(pid,&status,0) < 0)
  10. unix_error("waitfg: waitpid error
  11. ");
  12. printf("wait %d ok
  13. ",pid);
  14. sigprocmask(SIG_SETMASK,&pre_mask,NULL);
  15. return;
  16. }

加入我们再waitfg中加入对SIGCHLD信号的阻塞,这样可以保证前台进程能被正确回收,但是问题在于我们执行前台进程时不会处理SIGCHLD信号,这样子进程就不能被回收,如果前台进程一直运行,就会导致后台进程退出后全部变成僵死进程,浪费系统资源。
所以我们最终还是要按照书上的思路来实现

waitfg()

  1. void waitfg(pid_t pid)
  2. {
  3. sigset_t mask_empty, mask_all, mask_pre;
  4. sigemptyset(&mask_empty);
  5. sigfillset(&mask_all);
  6. sigprocmask(SIG_SETMASK, &mask_all, &mask_pre); //fgpid涉及全局变量的访问
  7. while (pid == fgpid(jobs))
  8. sigsuspend(&mask_empty);
  9. sigprocmask(SIG_SETMASK, &mask_pre, NULL);
  10. return;
  11. }

sigchld_handler()

  1. void sigchld_handler(int sig)
  2. {
  3. pid_t pid;
  4. int status;
  5. int old_errno = errno;
  6. sigset_t mask_empty, mask_all, mask_pre;
  7. sigemptyset(&mask_empty);
  8. sigfillset(&mask_all);
  9. while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
  10. {
  11. sigprocmask(SIG_BLOCK, &mask_all, &mask_pre);
  12. if (WIFSTOPPED(status)) //暂停
  13. {
  14. struct job_t *job = getjobpid(jobs, pid);
  15. job->state = ST;
  16. printf("Job [%d] (%d) stopped by signal %d
  17. ",job->jid, pid, WSTOPSIG(status));
  18. }
  19. else
  20. {
  21. if (WIFSIGNALED(status)) //被信号退出
  22. {
  23. struct job_t *job = getjobpid(jobs, pid);
  24. printf("Job [%d] (%d) terminated by signal %d
  25. ", job->jid, job->pid, WTERMSIG(status));
  26. }
  27. deletejob(jobs, pid);
  28. }
  29. sigprocmask(SIG_SETMASK, &mask_pre, NULL);
  30. }
  31. errno = old_errno;
  32. return;
  33. }

有了前面的基础,这个函数也很简单了。

sigint_handler() 与 sigtstp_handler()

  1. void sigint_handler(int sig)
  2. {
  3. pid_t pid;
  4. int old_errno = errno;
  5. if ((pid = fgpid(jobs)) != 0)
  6. {
  7. kill(-pid, SIGINT);
  8. }
  9. errno = old_errno;
  10. return;
  11. }
  12. void sigtstp_handler(int sig)
  13. {
  14. pid_t pid;
  15. int old_errno = errno;
  16. pid = fgpid(jobs);
  17. if (pid)
  18. {
  19. kill(-pid, sig);
  20. }
  21. errno = old_errno;
  22. return;
  23. }

这两个也很简单

builtin_cmd()

  1. int builtin_cmd(char **argv)
  2. {
  3. if (!strcmp(argv[0], "quit"))
  4. exit(0);
  5. else if (!strcmp(argv[0], "jobs"))
  6. {
  7. listjobs(jobs);
  8. return 1;
  9. }
  10. else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))
  11. {
  12. do_bgfg(argv);
  13. return 1;
  14. }
  15. return 0; /* not a builtin command */
  16. }

这个函数与书上的几乎无异。

do_bgfg()

  1. void do_bgfg(char **argv)
  2. {
  3. struct job_t *job;
  4. if(argv[1] == NULL){
  5. printf("%s command requires PID or %%jobid argument
  6. ",argv[0]);
  7. return;
  8. }
  9. char * idstring = argv[1];
  10. int isjid = 0;
  11. int id;
  12. if(idstring[0]=='%'){ //以jobid作为目标
  13. isjid = 1;
  14. id = atoi(idstring+1);
  15. if(!id){
  16. printf("%s: argument must be a PID or %%jobid
  17. ",argv[0]);
  18. return;
  19. }
  20. }
  21. else{ //以pid作为目标
  22. id = atoi(idstring);
  23. if(!id){
  24. printf("%s: argument must be a PID or %%jobid
  25. ",argv[0]);
  26. return;
  27. }
  28. }
  29. sigset_t mask_empty, mask_all, mask_pre;
  30. sigemptyset(&mask_empty);
  31. sigfillset(&mask_all);
  32. sigprocmask(SIG_SETMASK, &mask_all, &mask_pre); //与job相关操作涉及全局变量的访问
  33. if (isjid) //寻找目标job
  34. {
  35. job = getjobjid(jobs, id);
  36. if (!job){
  37. printf("%%%d: No such job
  38. ", id);
  39. sigprocmask(SIG_SETMASK, &mask_pre, NULL);
  40. return;
  41. }
  42. }
  43. else
  44. {
  45. job = getjobpid(jobs, id);
  46. if (!job){
  47. printf("(%d): No such process
  48. ", id);
  49. sigprocmask(SIG_SETMASK, &mask_pre, NULL);
  50. return;
  51. }
  52. }
  53. pid_t pid = job->pid;
  54. if (!strcmp(argv[0], "bg")) //bg fg切换
  55. {
  56. job->state = BG;
  57. printf("[%d] (%d) %s",job->jid,job->pid,job->cmdline);
  58. kill(-pid, SIGCONT);
  59. }
  60. else if (!strcmp(argv[0], "fg"))
  61. {
  62. job->state = FG;
  63. kill(-pid, SIGCONT);
  64. sigprocmask(SIG_SETMASK, &mask_pre, NULL);
  65. waitfg(pid);
  66. return;
  67. }
  68. sigprocmask(SIG_SETMASK, &mask_pre, NULL);
  69. return;
  70. }

主要是要对参数是否符合要求进行判断。

完整代码

tsh.c