哈工大计算机系统实验之TinyShell

计算学部

目  录

第1章 实验基本信息

1.1 实验目的

1.2 实验环境与工具

1.2.1 硬件环境

1.2.2 软件环境

1.2.3 开发工具

1.3 实验预习

第2章 实验预习

2.1 进程的概念、创建和回收方法(5分)

2.2信号的机制、种类(5分)

2.3 信号的发送方法、阻塞方法、处理程序的设置方法(5分)

2.4 什么是shell,简述其功能和处理流程(5分)

第3章 TinyShell的设计与实现

3.1 设计(35分)

3.1.1 void eval(char *cmdline)函数(10分)

3. 1.2 int builtin_cmd(char **argv)函数(5分)

3. 1.3 void do_bgfg(char **argv) 函数(5分)

3. 1.4 void waitfg(pid_t pid) 函数(5分)

3. 1.5 void sigchld_handler(int sig) 函数(10分)

3.2 程序实现(tsh.c的全部内容)(10分)

第4章 TinyShell测试

4.1 测试方法

4.2 测试结果评价

4.3 自测试结果(15分)

4.3.1测试用例trace01.txt的输出截图(1分)

4.3.2测试用例trace02.txt的输出截图(1分)

4.3.3测试用例trace03.txt的输出截图(1分)

4.3.4测试用例trace04.txt的输出截图(1分)

4.3.5测试用例trace05.txt的输出截图(1分)

4.3.6测试用例trace06.txt的输出截图(1分)

4.3.7测试用例trace07.txt的输出截图(1分)

4.3.8测试用例trace08.txt的输出截图(1分)

4.3.9测试用例trace09.txt的输出截图(1分)

4.3.10测试用例trace10.txt的输出截图(1分)

4.3.11测试用例trace11.txt的输出截图(1分)

4.3.12测试用例trace12.txt的输出截图(1分)

4.3.13测试用例trace13.txt的输出截图(1分)

4.3.14测试用例trace14.txt的输出截图(1分)

4.3.15测试用例trace15.txt的输出截图(1分)

4.4 自测试评分

第4章 总结

4.1 请总结本次实验的收获

4.2 请给出对本次实验内容的建议

参考文献

第1章 实验基本信息

1.1 实验目的

理解现代计算机系统进程与并发的基本知识

掌握linux 异常控制流和信号机制的基本原理和相关系统函数

掌握shell的基本原理和实现方法

深入理解Linux信号响应可能导致的并发冲突及解决方法

培养Linux下的软件系统开发与测试能力 

1.2 实验环境与工具

1.2.1 硬件环境

CPU:Intel Core i5 12500H;2GHz;2G RAM;256GHD Disk 以上

1.2.2 软件环境

Windows7 64 位以上;VirtualBox/Vmware 11 以上;1.2.3 开发工具

1.2.3 开发工具

 vim+gcc; gdb;

1.3 实验预习

上实验课前,必须认真预习实验指导书(PPT或PDF)

了解实验的目的、实验环境与软硬件工具、实验操作步骤,复习与实验有关的理论知识。

了解进程、作业、信号的基本概念和原理

了解shell的基本原理

熟知进程创建、回收的方法和相关系统函数

熟知信号机制和信号处理相关的系统函数

第2章 实验预习

总分20

2.1 进程的概念、创建和回收方法(5分)

概念:

进程是一个执行程序中的实例,提供给人一种错觉,即程序好像是系统当前运行的唯一程序,独占处理器和内存,处理器无间断地执行程序中的指令。用户通过shell输入一个可执行目标文件的名字,运行程序时,shell就会创建一个新的进程,然后在这个新进程的上下文中执行这个可执行目标文件。应用程序也能够创建新进程,并且在这个新进程的上下文中运行它们自己的代码或其他应用程序。进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。

创建:

  1. fork函数:父进程调用fork函数创建一个新的运行的子进程,子进程得到和父进程用户及虚拟地址空间完全相同的一个副本。
  2. exec系列:一个进程创建另一个进程,会直接用新进程覆盖原有进程的代码段。
  3. system函数:不常用。

回收:

  1. 当一个进程由于某种原因终止时,进程保持在一种已经终止的状态,直到被它的父进程回收,当父进程回收了它的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已经终止的进程,则该进程就不存在了。其中一个僵死了但未被回收的进程称为僵死进程。
  2. 如果一个父进程终止了,内核会安排init进程去回收它的孤儿进程。
  3. 使用waitpid函数来等待子进程终止或者停止。

2.2信号的机制、种类(5分)

一个信号就是一条消息,它通知进程系统中发生了一种类型的事件,每种信号类型都对应某种系统的事件,其中信号类型用整数ID来标识,每个信号对应唯一ID。

发送信号:内核通过更新目的进程上下文的某种状态,发送一个信号给目的进程。

接收信号:内核强迫目的进程以某种方式对信号的发送做出反应,即接收。

信号种类:

2.3 信号的发送方法、阻塞方法、处理程序的设置方法(5分)

发送:

  1. 用/bin/kill程序可以向另外的进程发送任意的信号。
  2. 通过键盘发送信号,如输入Ctrl+C会导致内核发送一个SIGINT信号到前台进程中的每个进程,默认终止前台作业。而输入Ctrl+Z会导致内核发送一个SIGTSTP信号到前台进程中的每个进程,默认挂起前台作业。
  3. 用kill函数发送信号给其他进程,包括它们自己。
  4. 使用alarm函数在指定secs秒后发送一个SIGALRM信号给调用进程。

阻塞:

  1. 隐式阻塞机制:内核默认阻塞任何当前处理程序正在处理信号和待处理信号。
  2. 显式阻塞机制:应用程序可以调用sigprocmask函数及其辅助函数来明确地阻塞和解除阻塞选定信号。

设置:

进程可以通过signal函数来修改和信号相关联的默认行为,但SIGSTOP和SIGKILL的默认行为是不能修改的。

signal函数可以通过下列方法来改变和信号signum相关联的行为

  1. handler为SIG_IGN,那么忽略类型为signum的信号
  2. handler为SIG_DFL,那么类型为signum的信号行为恢复为默认行为
  3. 否则handler就是用户定义的函数地址,这个函数称为信号处理程序,只要进程接收到一个类型为signum的信号,就会调动这个程序,通过把该函数地址传递到signal函数来改变默认行为,即设置信号处理程序。调用信号处理程序被称为捕获信号,而执行信号处理程序被称为处理程序。

当一个进程捕获一个类型为k的信号时,会调用信号k所设置的处理程序,一个整数参数被设置为k,这个参数允许同一个处理函数去捕获不同类型的信号。

而因为signal的语义各有不同,系统调用可以被中断,定义了sigaction函数,允许用户在设置信号处理时,可以明确地指定想要的信号处理语义。

2.4 什么是shell,简述其功能和处理流程(5分)

shell:是用户使用linux的桥梁,shell既是一种命令语言,又是一种程序设计语言,它是一种应用程序。

功能:提供了一个界面,用户可以通过访问这个界面来访问操作系统内核的服务。

处理流程:

  1. 从终端读入输入的命令
  2. 将输入字符串切分获得所有的参数、
  3. 如果是内置命令,则立即执行
  4. 否则调用相应的程序执行
  5. shell应该接收键盘输入信号,并对其进行相应处理

第3章 TinyShell的设计与实现

总分45

3.1 设计(35分)

3.1.1 void eval(char *cmdline)函数(10分)

函数功能:解析和解释命令行的主例程

参    数:char *cmdline

处理流程:

  1. 调用parseline函数来将cmdline字符串切分为参数数组argv并返回整数bg来得知该操作是否要在后台运行
  2. 调用builtin_cmd函数来判断该命令是否为内置命令,如果是内置命令则立即执行,否则返回
  3. 如果不是内置命令则先设置阻塞信号集来阻塞预先识别的信号,防止shell去捕获并处理信号
  4. 调用fork函数来创建一个子进程并解锁子进程的阻塞,为子进程设置新的进程组ID来避免父进程接收到的控制信号影响到子进程,在子进程中调用execve函数来执行命令行指定的程序
  5. 父进程将新的子进程添加到任务列表中,并解除之前阻塞的信号
  6. 判断任务如果不是后台任务,则等待其完成,否则打印相关信息

要点分析:

  1. 将命令行的处理进行封装
  2. 阻塞shell的预识别信号,防止shell捕获并处理,避免竞争
  3. 父进程记录了addjob后解除阻塞,在阻塞期间未处理的SIGCHLD信号在此时被处理,同时在任务列表中被删除,避免了竞争
  4. Ctrl+C会给shell下的前台进程组中所有进程发送SIGINT信号,这将包括tsh和tsh所创建的进程,这是需要解决的问题

可以考虑如下方法:

①在fork和execve之间,子进程通过setpgid(0,0)来将自己放到一个新的进程组中,即把自己所在的进程组ID设置为自己的ID。这样在前台进程组只有一个进程tsh

②当输入Ctrl+C后,shell将捕获SIGINT并转发给tsh,当tsh收到SIGINT后

会转发给特定的前台作业的进程组

3. 1.2 int builtin_cmd(char **argv)函数(5分)

函数功能:识别并解释内置命令:quit,fg,bg,jobs

参    数:char **argv

处理流程:

通过一些if判断语句来确定是否为内置命令,如果是内置命令则运行相应函数并返回1,特殊的是quit则不用返回,如果非内置命令则返回0

要点分析:

使用函数模块化的思想

3. 1.3 void do_bgfg(char **argv) 函数(5分)

函数功能:实现内置命令bg和fg

参    数:char **argv

处理流程:

首先判断是否有参数输入,没有的话直接输出提示信息并返回。有参数的话接着判断输入的是PID还是%JID,无论哪种都要在jobs中查询,在过程中出现问题则输出提示信息并返回。接下来匹配是后台运行还是前台运行,如果是后台运行,则向job所在的进程组发送SIGCONT信号,更改job的state为bg,如果是前台运行,则向job所在进程组发送SIGCONT信号,更改job的state为fg,然后等待当前的程序运行,直到当前的job不再是前台程序

要点分析:

区分传入的是PID还是%JID,按照bg和fg的特性来发送信号使得程序持续运行,fg需要将目标程序在前台运行,调用waitfg来等待程序终止

3. 1.4 void waitfg(pid_t pid) 函数(5分)

函数功能:等待一个前台作业结束

参    数:pid_t pid

处理流程:

调用fgpid函数来判断所给进程是否结束,使用sigsuspend函数,不阻塞任何信号。

要点分析:

跳出循环的方式为通过fgpid来检测是否进程结束

3. 1.5 void sigchld_handler(int sig) 函数(10分)

函数功能:捕获SIGCHLD信号

参    数:int sig

处理流程:

保存errno来防止过程中的改变影响进程上下文的运行,使用循环回收所有的子进程,防止僵死,对waitpid返回的情况匹配,如果正常退出则删除子任务,如果是未被捕获的信号终止,则输出提示信息并删除,最后恢复errno

要点分析:

使用循环来确保所有子进程回收,在删除子任务时时要阻塞所有信号,防止对进程的影响,防止调用去修改errno,在开始时保存,在结束时恢复

3.2 程序实现(tsh.c的全部内容)(10分)

重点检查代码风格:

  1. 用较好的代码注释说明——5
  2. 检查每个系统调用的返回值——5
  1. /*
  2.  * tsh - A tiny shell program with job control
  3.  * <Put your name and login ID here>
  4.  */
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <unistd.h>
  8. #include <string.h>
  9. #include <ctype.h>
  10. #include <signal.h>
  11. #include <sys/types.h>
  12. #include <sys/wait.h>
  13. #include <errno.h>
  14. /* Misc manifest constants */
  15. #define MAXLINE 1024   /* max line size */
  16. #define MAXARGS 128    /* max args on a command line */
  17. #define MAXJOBS 16     /* max jobs at any point in time */
  18. #define MAXJID 1 << 16 /* max job ID */
  19. /* Job states */
  20. #define UNDEF 0 /* undefined */
  21. #define FG 1    /* running in foreground */
  22. #define BG 2    /* running in background */
  23. #define ST 3    /* stopped */
  24. /*
  25.  * Jobs states: FG (foreground), BG (background), ST (stopped)
  26.  * Job state transitions and enabling actions:
  27.  *     FG -> ST  : ctrl-z
  28.  *     ST -> FG  : fg command
  29.  *     ST -> BG  : bg command
  30.  *     BG -> FG  : fg command
  31.  * At most 1 job can be in the FG state.
  32.  */
  33. /* Global variables */
  34. extern char **environ;   /* defined in libc */
  35. char prompt[] = "tsh> "/* command line prompt (DO NOT CHANGE) */
  36. int verbose = 0;         /* if true, print additional output */
  37. int nextjid = 1;         /* next job ID to allocate */
  38. char sbuf[MAXLINE];      /* for composing sprintf messages */
  39. struct job_t
  40. {                          /* The job struct */
  41.     pid_t pid;             /* job PID */
  42.     int jid;               /* job ID [1, 2, ...] */
  43.     int state;             /* UNDEF, BG, FG, or ST */
  44.     char cmdline[MAXLINE]; /* command line */
  45. };
  46. struct job_t jobs[MAXJOBS]; /* The job list */
  47. /* End global variables */
  48. /* Function prototypes */
  49. /* Here are the functions that you will implement */
  50. void eval(char *cmdline);
  51. int builtin_cmd(char **argv);
  52. void do_bgfg(char **argv);
  53. void waitfg(pid_t pid);
  54. void sigchld_handler(int sig);
  55. void sigtstp_handler(int sig);
  56. void sigint_handler(int sig);
  57. /* Here are helper routines that we've provided for you */
  58. int parseline(const char *cmdline, char **argv);
  59. void sigquit_handler(int sig);
  60. void clearjob(struct job_t *job);
  61. void initjobs(struct job_t *jobs);
  62. int maxjid(struct job_t *jobs);
  63. int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);
  64. int deletejob(struct job_t *jobs, pid_t pid);
  65. pid_t fgpid(struct job_t *jobs);
  66. struct job_t *getjobpid(struct job_t *jobs, pid_t pid);
  67. struct job_t *getjobjid(struct job_t *jobs, int jid);
  68. int pid2jid(pid_t pid);
  69. void listjobs(struct job_t *jobs);
  70. void usage(void);
  71. void unix_error(char *msg);
  72. void app_error(char *msg);
  73. typedef void handler_t(int);
  74. handler_t *Signal(int signum, handler_t *handler);
  75. /*
  76.  * main - The shell's main routine
  77.  */
  78. int main(int argc, char **argv)
  79. {
  80.     char c;
  81.     char cmdline[MAXLINE];
  82.     int emit_prompt = 1/* emit prompt (default) */
  83.     /* Redirect stderr to stdout (so that driver will get all output
  84.      * on the pipe connected to stdout) */
  85.     dup2(12);
  86.     /* Parse the command line */
  87.     while ((c = getopt(argc, argv, "hvp")) != EOF)
  88.     {
  89.         switch (c)
  90.         {
  91.         case 'h'/* print help message */
  92.             usage();
  93.             break;
  94.         case 'v'/* emit additional diagnostic info */
  95.             verbose = 1;
  96.             break;
  97.         case 'p':            /* don't print a prompt */
  98.             emit_prompt = 0/* handy for automatic testing */
  99.             break;
  100.         default:
  101.             usage();
  102.         }
  103.     }
  104.     /* Install the signal handlers */
  105.     /* These are the ones you will need to implement */
  106.     Signal(SIGINT, sigint_handler);   /* ctrl-c */
  107.     Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */
  108.     Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */
  109.     /* This one provides a clean way to kill the shell */
  110.     Signal(SIGQUIT, sigquit_handler);
  111.     /* Initialize the job list */
  112.     initjobs(jobs);
  113.     /* Execute the shell's read/eval loop */
  114.     while (1)
  115.     {
  116.         /* Read command line */
  117.         if (emit_prompt)
  118.         {
  119.             printf("%s", prompt);
  120.             fflush(stdout);
  121.         }
  122.         if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
  123.             app_error("fgets error");
  124.         if (feof(stdin))
  125.         { /* End of file (ctrl-d) */
  126.             fflush(stdout);
  127.             exit(0);
  128.         }
  129.         /* Evaluate the command line */
  130.         eval(cmdline);
  131.         fflush(stdout);
  132.         fflush(stdout);
  133.     }
  134.     exit(0); /* control never reaches here */
  135. }
  136. /*
  137.  * eval - Evaluate the command line that the user has just typed in
  138.  *
  139.  * If the user has requested a built-in command (quit, jobs, bg or fg)
  140.  * then execute it immediately. Otherwise, fork a child process and
  141.  * run the job in the context of the child. If the job is running in
  142.  * the foreground, wait for it to terminate and then return.  Note:
  143.  * each child process must have a unique process group ID so that our
  144.  * background children don't receive SIGINT (SIGTSTP) from the kernel
  145.  * when we type ctrl-c (ctrl-z) at the keyboard.
  146.  */
  147. void eval(char *cmdline)
  148. {
  149.     /* $begin handout */
  150.     char *argv[MAXARGS]; /* argv for execve() */
  151.     int bg;              /* should the job run in bg or fg? */
  152.     pid_t pid;           /* process id */
  153.     sigset_t mask;       /* signal mask */
  154.     /* Parse command line */
  155.     bg = parseline(cmdline, argv);
  156.     if (argv[0] == NULL)
  157.         return/* ignore empty lines */
  158.     if (!builtin_cmd(argv))
  159.     {
  160.         /*
  161.          * This is a little tricky. Block SIGCHLD, SIGINT, and SIGTSTP
  162.          * signals until we can add the job to the job list. This
  163.          * eliminates some nasty races between adding a job to the job
  164.          * list and the arrival of SIGCHLD, SIGINT, and SIGTSTP signals.
  165.          */
  166.         if (sigemptyset(&mask) < 0)
  167.             unix_error("sigemptyset error");
  168.         if (sigaddset(&mask, SIGCHLD))
  169.             unix_error("sigaddset error");
  170.         if (sigaddset(&mask, SIGINT))
  171.             unix_error("sigaddset error");
  172.         if (sigaddset(&mask, SIGTSTP))
  173.             unix_error("sigaddset error");
  174.         if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)
  175.             unix_error("sigprocmask error");
  176.         /* Create a child process */
  177.         if ((pid = fork()) < 0)
  178.             unix_error("fork error");
  179.         /*
  180.          * Child  process
  181.          */
  182.         if (pid == 0)
  183.         {
  184.             /* Child unblocks signals */
  185.             sigprocmask(SIG_UNBLOCK, &mask, NULL);
  186.             /* Each new job must get a new process group ID
  187.                so that the kernel doesn't send ctrl-c and ctrl-z
  188.                signals to all of the shell's jobs */
  189.             if (setpgid(00) < 0)
  190.                 unix_error("setpgid error");
  191.             /* Now load and run the program in the new job */
  192.             if (execve(argv[0], argv, environ) < 0)
  193.             {
  194.                 printf("%s: Command not found\n", argv[0]);
  195.                 exit(0);
  196.             }
  197.         }
  198.         /*
  199.          * Parent process
  200.          */
  201.         /* Parent adds the job, and then unblocks signals so that
  202.            the signals handlers can run again */
  203.         addjob(jobs, pid, (bg == 1 ? BG : FG), cmdline);
  204.         sigprocmask(SIG_UNBLOCK, &mask, NULL);
  205.         if (!bg)
  206.             waitfg(pid);
  207.         else
  208.             printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
  209.     }
  210.     /* $end handout */
  211.     return;
  212. }
  213. /*
  214.  * parseline - Parse the command line and build the argv array.
  215.  *
  216.  * Characters enclosed in single quotes are treated as a single
  217.  * argument.  Return true if the user has requested a BG job, false if
  218.  * the user has requested a FG job.
  219.  */
  220. int parseline(const char *cmdline, char **argv)
  221. {
  222.     static char array[MAXLINE]; /* holds local copy of command line */
  223.     char *buf = array;          /* ptr that traverses command line */
  224.     char *delim;                /* points to first space delimiter */
  225.     int argc;                   /* number of args */
  226.     int bg;                     /* background job? */
  227.     strcpy(buf, cmdline);
  228.     buf[strlen(buf) - 1] = ' ';   /* replace trailing '\n' with space */
  229.     while (*buf && (*buf == ' ')) /* ignore leading spaces */
  230.         buf++;
  231.     /* Build the argv list */
  232.     argc = 0;
  233.     if (*buf == '\'')
  234.     {
  235.         buf++;
  236.         delim = strchr(buf, '\'');
  237.     }
  238.     else
  239.     {
  240.         delim = strchr(buf, ' ');
  241.     }
  242.     while (delim)
  243.     {
  244.         argv[argc++] = buf;
  245.         *delim = '\0';
  246.         buf = delim + 1;
  247.         while (*buf && (*buf == ' ')) /* ignore spaces */
  248.             buf++;
  249.         if (*buf == '\'')
  250.         {
  251.             buf++;
  252.             delim = strchr(buf, '\'');
  253.         }
  254.         else
  255.         {
  256.             delim = strchr(buf, ' ');
  257.         }
  258.     }
  259.     argv[argc] = NULL;
  260.     if (argc == 0/* ignore blank line */
  261.         return 1;
  262.     /* should the job run in the background? */
  263.     if ((bg = (*argv[argc - 1] == '&')) != 0)
  264.     {
  265.         argv[--argc] = NULL;
  266.     }
  267.     return bg;
  268. }
  269. /*
  270.  * builtin_cmd - If the user has typed a built-in command then execute
  271.  *    it immediately.
  272.  */
  273. int builtin_cmd(char **argv)
  274. {
  275.     /**
  276.      *判断是否是内置命令,如果是,执行并返回1;如果不是,返回0。
  277.      */
  278.     if (!strcmp(argv[0], "quit"))
  279.         exit(0);
  280.     if (!strcmp(argv[0], "&"))
  281.         return 1;
  282.     if (!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg"))
  283.     {
  284.         do_bgfg(argv);
  285.         return 1;
  286.     }
  287.     if (!strcmp(argv[0], "jobs"))
  288.     {
  289.         listjobs(jobs);
  290.         return 1;
  291.     }
  292.     return 0/* not a builtin command */
  293. }
  294. /*
  295.  * do_bgfg - Execute the builtin bg and fg commands
  296.  */
  297. void do_bgfg(char **argv)
  298. {
  299.     /* $begin handout */
  300.     struct job_t *jobp = NULL;
  301.     /* Ignore command if no argument */
  302.     if (argv[1] == NULL)
  303.     {
  304.         printf("%s command requires PID or %%jobid argument\n", argv[0]);
  305.         return;
  306.     }
  307.     /* Parse the required PID or %JID arg */
  308.     if (isdigit(argv[1][0]))
  309.     {
  310.         pid_t pid = atoi(argv[1]);
  311.         if (!(jobp = getjobpid(jobs, pid)))
  312.         {
  313.             printf("(%d): No such process\n", pid);
  314.             return;
  315.         }
  316.     }
  317.     else if (argv[1][0] == '%')
  318.     {
  319.         int jid = atoi(&argv[1][1]);
  320.         if (!(jobp = getjobjid(jobs, jid)))
  321.         {
  322.             printf("%s: No such job\n", argv[1]);
  323.             return;
  324.         }
  325.     }
  326.     else
  327.     {
  328.         printf("%s: argument must be a PID or %%jobid\n", argv[0]);
  329.         return;
  330.     }
  331.     /* bg command */
  332.     if (!strcmp(argv[0], "bg"))
  333.     {
  334.         if (kill(-(jobp->pid), SIGCONT) < 0)
  335.             unix_error("kill (bg) error");
  336.         jobp->state = BG;
  337.         printf("[%d] (%d) %s", jobp->jid, jobp->pid, jobp->cmdline);
  338.     }
  339.     /* fg command */
  340.     else if (!strcmp(argv[0], "fg"))
  341.     {
  342.         if (kill(-(jobp->pid), SIGCONT) < 0)
  343.             unix_error("kill (fg) error");
  344.         jobp->state = FG;
  345.         waitfg(jobp->pid);
  346.     }
  347.     else
  348.     {
  349.         printf("do_bgfg: Internal error\n");
  350.         exit(0);
  351.     }
  352.     /* $end handout */
  353.     return;
  354. }
  355. /*
  356.  * waitfg - Block until process pid is no longer the foreground process
  357.  */
  358. void waitfg(pid_t pid)
  359. {
  360.     sigset_t mask, prev;
  361.     sigemptyset(&mask);
  362.     sigaddset(&mask, SIGCHLD);
  363.     sigprocmask(SIG_SETMASK, &mask, &prev);
  364.     while (fgpid(jobs) == pid)
  365.         sigsuspend(&prev);
  366.     sigprocmask(SIG_SETMASK, &prev, NULL);
  367.     return;
  368. }
  369. /*****************
  370.  * Signal handlers
  371.  *****************/
  372. /*
  373.  * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
  374.  *     a child job terminates (becomes a zombie), or stops because it
  375.  *     received a SIGSTOP or SIGTSTP signal. The handler reaps all
  376.  *     available zombie children, but doesn't wait for any other
  377.  *     currently running children to terminate.
  378.  */
  379. void sigchld_handler(int sig)
  380. {
  381.     //记录下errno 防止在信号处理过程中调用函数出错而产生errno变化
  382.     int olderrno = errno;
  383.     int status;
  384.     sigset_t mask_all, prev_all;
  385.     pid_t pid;
  386.     sigfillset(&mask_all);
  387.     /*
  388.     循环回收所有僵死进程并显示导致退出的情况
  389.     其中WNOHANG | WUNTRACED表示立即返回,如果等待集合中的子进程都没有停止则返回0,否则返回PID
  390.     */
  391.     while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
  392.     {
  393.         if (WIFEXITED(status))
  394.         {                                                 //子进程通过调用exit或者一个返回正常终止
  395.             sigprocmask(SIG_BLOCK, &mask_all, &prev_all); //阻塞所有的信号
  396.             deletejob(jobs, pid);                         //在任务列表中删除任务
  397.             sigprocmask(SIG_SETMASK, &prev_all, NULL);    //解除阻塞
  398.         }
  399.         //子进程是因为一个未被捕获的信号终止的
  400.         if (WIFSIGNALED(status))
  401.         {
  402.             printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
  403.             sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
  404.             deletejob(jobs, pid); //在任务列表中删除任务
  405.             sigprocmask(SIG_SETMASK, &prev_all, NULL);
  406.         }
  407.     }
  408.     errno = olderrno;
  409.     return;
  410. }
  411. /*
  412.  * sigint_handler - The kernel sends a SIGINT to the shell whenver the
  413.  *    user types ctrl-c at the keyboard.  Catch it and send it along
  414.  *    to the foreground job.
  415.  */
  416. void sigint_handler(int sig)
  417. {
  418.     int olderrno = errno; //保存errno
  419.     pid_t pid = fgpid(jobs);
  420.     if (pid == 0)
  421.     {
  422.         return;
  423.     }
  424.     //向处于foreground正在运行的进程所处的进程组发送SIGINT
  425.     kill(-pid, SIGINT);
  426.     errno = olderrno;
  427.     return;
  428. }
  429. /*
  430.  * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
  431.  *     the user types ctrl-z at the keyboard. Catch it and suspend the
  432.  *     foreground job by sending it a SIGTSTP.
  433.  */
  434. void sigtstp_handler(int sig)
  435. {
  436.     int olderrno = errno; //保存errno
  437.     pid_t pid = fgpid(jobs);
  438.     if (pid == 0)
  439.         return;
  440.     //设置state为ST
  441.     getjobpid(jobs, pid)->state = ST;
  442.     kill(-pid, SIGTSTP);
  443.     printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, sig);
  444.     errno = olderrno;
  445.     return;
  446. }
  447. /*********************
  448.  * End signal handlers
  449.  *********************/
  450. /***********************************************
  451.  * Helper routines that manipulate the job list
  452.  **********************************************/
  453. /* clearjob - Clear the entries in a job struct */
  454. void clearjob(struct job_t *job)
  455. {
  456.     job->pid = 0;
  457.     job->jid = 0;
  458.     job->state = UNDEF;
  459.     job->cmdline[0] = '\0';
  460. }
  461. /* initjobs - Initialize the job list */
  462. void initjobs(struct job_t *jobs)
  463. {
  464.     int i;
  465.     for (i = 0; i < MAXJOBS; i++)
  466.         clearjob(&jobs[i]);
  467. }
  468. /* maxjid - Returns largest allocated job ID */
  469. int maxjid(struct job_t *jobs)
  470. {
  471.     int i, max = 0;
  472.     for (i = 0; i < MAXJOBS; i++)
  473.         if (jobs[i].jid > max)
  474.             max = jobs[i].jid;
  475.     return max;
  476. }
  477. /* addjob - Add a job to the job list */
  478. int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline)
  479. {
  480.     int i;
  481.     if (pid < 1)
  482.         return 0;
  483.     for (i = 0; i < MAXJOBS; i++)
  484.     {
  485.         if (jobs[i].pid == 0)
  486.         {
  487.             jobs[i].pid = pid;
  488.             jobs[i].state = state;
  489.             jobs[i].jid = nextjid++;
  490.             if (nextjid > MAXJOBS)
  491.                 nextjid = 1;
  492.             strcpy(jobs[i].cmdline, cmdline);
  493.             if (verbose)
  494.             {
  495.                 printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
  496.             }
  497.             return 1;
  498.         }
  499.     }
  500.     printf("Tried to create too many jobs\n");
  501.     return 0;
  502. }
  503. /* deletejob - Delete a job whose PID=pid from the job list */
  504. int deletejob(struct job_t *jobs, pid_t pid)
  505. {
  506.     int i;
  507.     if (pid < 1)
  508.         return 0;
  509.     for (i = 0; i < MAXJOBS; i++)
  510.     {
  511.         if (jobs[i].pid == pid)
  512.         {
  513.             clearjob(&jobs[i]);
  514.             nextjid = maxjid(jobs) + 1;
  515.             return 1;
  516.         }
  517.     }
  518.     return 0;
  519. }
  520. /* fgpid - Return PID of current foreground job, 0 if no such job */
  521. pid_t fgpid(struct job_t *jobs)
  522. {
  523.     int i;
  524.     for (i = 0; i < MAXJOBS; i++)
  525.         if (jobs[i].state == FG)
  526.             return jobs[i].pid;
  527.     return 0;
  528. }
  529. /* getjobpid  - Find a job (by PID) on the job list */
  530. struct job_t *getjobpid(struct job_t *jobs, pid_t pid)
  531. {
  532.     int i;
  533.     if (pid < 1)
  534.         return NULL;
  535.     for (i = 0; i < MAXJOBS; i++)
  536.         if (jobs[i].pid == pid)
  537.             return &jobs[i];
  538.     return NULL;
  539. }
  540. /* getjobjid  - Find a job (by JID) on the job list */
  541. struct job_t *getjobjid(struct job_t *jobs, int jid)
  542. {
  543.     int i;
  544.     if (jid < 1)
  545.         return NULL;
  546.     for (i = 0; i < MAXJOBS; i++)
  547.         if (jobs[i].jid == jid)
  548.             return &jobs[i];
  549.     return NULL;
  550. }
  551. /* pid2jid - Map process ID to job ID */
  552. int pid2jid(pid_t pid)
  553. {
  554.     int i;
  555.     if (pid < 1)
  556.         return 0;
  557.     for (i = 0; i < MAXJOBS; i++)
  558.         if (jobs[i].pid == pid)
  559.         {
  560.             return jobs[i].jid;
  561.         }
  562.     return 0;
  563. }
  564. /* listjobs - Print the job list */
  565. void listjobs(struct job_t *jobs)
  566. {
  567.     int i;
  568.     for (i = 0; i < MAXJOBS; i++)
  569.     {
  570.         if (jobs[i].pid != 0)
  571.         {
  572.             printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);
  573.             switch (jobs[i].state)
  574.             {
  575.             case BG:
  576.                 printf("Running ");
  577.                 break;
  578.             case FG:
  579.                 printf("Foreground ");
  580.                 break;
  581.             case ST:
  582.                 printf("Stopped ");
  583.                 break;
  584.             default:
  585.                 printf("listjobs: Internal error: job[%d].state=%d ",
  586.                        i, jobs[i].state);
  587.             }
  588.             printf("%s", jobs[i].cmdline);
  589.         }
  590.     }
  591. }
  592. /******************************
  593.  * end job list helper routines
  594.  ******************************/
  595. /***********************
  596.  * Other helper routines
  597.  ***********************/
  598. /*
  599.  * usage - print a help message
  600.  */
  601. void usage(void)
  602. {
  603.     printf("Usage: shell [-hvp]\n");
  604.     printf("   -h   print this message\n");
  605.     printf("   -v   print additional diagnostic information\n");
  606.     printf("   -p   do not emit a command prompt\n");
  607.     exit(1);
  608. }
  609. /*
  610.  * unix_error - unix-style error routine
  611.  */
  612. void unix_error(char *msg)
  613. {
  614.     fprintf(stdout"%s: %s\n", msg, strerror(errno));
  615.     exit(1);
  616. }
  617. /*
  618.  * app_error - application-style error routine
  619.  */
  620. void app_error(char *msg)
  621. {
  622.     fprintf(stdout"%s\n", msg);
  623.     exit(1);
  624. }
  625. /*
  626.  * Signal - wrapper for the sigaction function
  627.  */
  628. handler_t *Signal(int signum, handler_t *handler)
  629. {
  630.     struct sigaction actionold_action;
  631.     action.sa_handler = handler;
  632.     sigemptyset(&action.sa_mask); /* block sigs of type being handled */
  633.     action.sa_flags = SA_RESTART; /* restart syscalls if possible */
  634.     if (sigaction(signum, &action, &old_action) < 0)
  635.         unix_error("Signal error");
  636.     return (old_action.sa_handler);
  637. }
  638. /*
  639.  * sigquit_handler - The driver program can gracefully terminate the
  640.  *    child shell by sending it a SIGQUIT signal.
  641.  */
  642. void sigquit_handler(int sig)
  643. {
  644.     printf("Terminating after receipt of SIGQUIT signal\n");
  645.     exit(1);
  646. }

第4章 TinyShell测试

总分15

4.1 测试方法

针对tsh和参考shell程序tshref,完成测试项目4.1-4.15的对比测试,并将测试结果截图或者通过重定向保存到文本文件(例如:./sdriver.pl -t trace01.txt -s ./tsh -a "-p" > tshresult01.txt)。

4.2 测试结果评价

tsh与tshref的输出在一下两个方面可以不同:

(1)PID

(2)测试文件trace11.txt, trace12.txt和trace13.txt中的/bin/ps命令,每次运行的输出都会不同,但每个mysplit进程的运行状态应该相同。

除了上述两方面允许的差异,tsh与tshref的输出相同则判为正确,如不同则给出原因分析。

4.3 自测试结果(15分)

4.3.1测试用例trace01.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.2测试用例trace02.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.3测试用例trace03.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.4测试用例trace04.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.5测试用例trace05.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.6测试用例trace06.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.7测试用例trace07.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.8测试用例trace08.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.9测试用例trace09.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.10测试用例trace10.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.11测试用例trace11.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.12测试用例trace12.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.13测试用例trace13.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.14测试用例trace14.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.3.15测试用例trace15.txt的输出截图(1分)

tsh测试结果

tshref测试结果

测试结论

相同

4.4 自测试评分

根据节4.3的自测试结果,程序的测试评分为:    15   

第4章 总结

4.1 请总结本次实验的收获

在本次实验中,我可以体验和利用一些之前只在课堂讲过的函数,这使得我对进程有了更明确的了解,我认为这门课设计的非常完美,每次实验课都会之前学过的理论知识加以应用。

4.2 请给出对本次实验内容的建议

暂无

注:本章为酌情加分项。

参考文献

为完成本次实验你翻阅的书籍与网站等

[1]  林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.

[2]  辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.

[3]  赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).

[4]  谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.

[5]  KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.

[6]  CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值