Csapp_lab4


 

 

 

实验报告

 

实 验(四)

 

 

题 目 TinyShell

微壳  

专 业 航院英才人工智能

学   号 7203610404

班   级 2036017

学 生 彭癸龙    

指 导 教 师 史先俊   

实 验 地 点 宿舍    

实 验 日 期 2022/5/14   

 

计算学部

 

目 录

 

1 实验基本信息    - 4 -

1.1 实验目的    - 4 -

1.2 实验环境与工具    - 4 -

1.2.1 硬件环境    - 4 -

1.2.2 软件环境    - 4 -

1.2.3 开发工具    - 4 -

1.3 实验预习    - 5 -

2 实验预习    - 7 -

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

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

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

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

3 TinyShell的设计与实现    - 10 -

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

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

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

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

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

4 TinyShell测试    - 25 -

4.1 测试方法    - 25 -

4.2 测试结果评价    - 25 -

4.3 自测试结果    - 25 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4.4 自测试评分    - 34 -

4 总结    - 35 -

4.1 请总结本次实验的收获    - 35 -

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

参考文献    - 36 -

 

第1章 实验基本信息

 

1.1 实验目的

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

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

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

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

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

 

1.2 实验环境与工具

1.2.1 硬件环境

CPUamd ryzen 5000 series 5800H

RAM: 16GB Samsung DDR4 3200MHz

Disk:SN750 512GB + SN550 1TB

1.2.2 软件环境

版本    Windows 11 专业版

版本    21H2

安装日期    ?2022/?3/?28

操作系统版本    22000.613

体验    Windows 功能体验包 1000.22000.613.0

 

Vmware 16 pro Ubuntu 16

1.2.3 开发工具

Visual studio 2022

CodeBlocks

vmware

1.3 实验预习

进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。它不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。

    作业是用户在一次算题过程中或一个事务处理中要求计算机系统所做的工作的集合。作业是一个比程序更为广泛的概念,它不仅包含了通常的程序和数据,而且还应配有一份作业说明书。系统通过作业说明书控制文件形式的程序和数据,使之执行和操作,并在系统中建立作业控制块的数据结构。在批处理系统中,是以作业为基本单位从外存调入内存的。

    在计算机科学中,信号是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。

    在计算机科学中,Shell俗称壳(用来区别于核),是指"为使用者提供操作界面"的软件(command interpreter,命令解析器)。它类似于DOS下的COMMAND.COM和后来的cmd.exe。它接收用户命令,然后调用相应的应用程序。

 

第2章 实验预习

总分20分

 

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

进程的概念:

一个特定的执行中程序的实例。(In computing, a process is the instance of a computer program that is being executed by one or many threads.)

创建进程:通过父进程调用fork()函数来创建一个子进程,fork函数返回两次,在父进程返回0,子进程返回pid

进程的回收方法:在父进程中可以调用waitpid来回收子进程。除此之外若父进程无法正常回收子进程,则内核安排init进程回收子进程。

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

信号的机制:Linux存在信号这一软件形式的异常控制流,它允许进程和内核终止其他进程。信号通知进程系统发生了一个类型的事件。每种信号对应一种系统事件,在头文件中定义了信号。比信号层次更低的异常控制流一般用户难以见到,信号提供了通知用户、让进程之间传递信息的机制。

信号的种类:

1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT

7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2

13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD

18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU

23) SIGURG 24) SIGXCPU25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF

28) SIGWINCH29) SIGIO 30) SIGPWR 31) SIGSYS34) SIGRTMIN

35) SIGRTMIN+1 36) SIGRTMIN+2 37)SIGRTMIN+3 38) SIGRTMIN+4

39) SIGRTMIN+5 40) SIGRTMIN+6 41)SIGRTMIN+7 42) SIGRTMIN+8

43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12

47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2

63) SIGRTMAX-1 64) SIGRTMAX

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

信号的发送方法:

1. /bin/kill 程序发送信号, /bin/kill 程序可以向另外的进程或进程组发送

任意的信号,负的 PID 会导致信号被发送到进程组 PID 中的每个进程;

2. 从键盘发送信号。例如,从键盘上输入 ctrl-c (ctrl-z) 会导致内核发送一个

SIGINT (SIGTSTP)信号到前台进程组中的每个作业

3. kill 函数发送信号。 int kill (pid_t pid, int sig);如果 pid 大于零,那么 kill函数发送信号号码 sig 给进程 pid;如果 pid 等于零, 那么 kill 发送信号 sig 给调用进程所在进程组中的每个进程,包括调用进程自己;如果pid 小于零, kill 发送信号 sig 给进程组|pid|pid 的绝对值)中的每个进程。

4. alarm 函数发送信号。 Unsigned int alarm(unsigned int secs);alarm 函数安排内核在 secs 秒后发送一个 SIGALRM 信号给调用进程。如果 secs 为零,

那么不会调度安排新的闹钟(alarm)。在任何情况下,对 alarm 的调用都将取消任何待处理的闹钟,并且返回任何待处理的闹钟在被发送前还剩下的秒数如果没有任何待处理的闹钟就返回零。

 

信号的阻塞方法:

1. 隐式阻塞机制。内核默认阻塞任何当前处理程序正在处理信号类型的待处

理的信号。

2. 显式阻塞机制。应用程序可以使用 sigprocmask 函数和它的辅助函数,明

确地阻塞和解除阻塞选定的信号。

 

处理程序的设置方法:

通过把处理程序的地址传递给 signal 函数从而改变默认行为,这叫做设置信

号处理程序。

进程可以使用 signal 函数修改和信号 signum 相关联的默认行为:

handler_t *signal(int signum, handler_t *handler)

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

1. 如果 handler SIG_IGN,那么忽略类型为 signum 的信号;

2. 如果 handler SIG_DFL,那么类型为 signum 的信号行为恢复为默认

行为;

3. 否则, handler 就是用户定义的函数的地址,这个函数被称为信号处理程

序, 只要接收到一个类型为 signum 的信号,就会调用这个程序。

 

 

 

 

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

Shell 是一种交互型的应用级程序,它可以代表用户执行程序。(In computing, a shell is a computer program which exposes an operating system's services to a human user or other programs.

功能:

执行读命令然后执行命令然后终止。Shell读取来自用户的命令行,代表用户执行程序。

处理流程:

命令行是一串ASCII字符串。第一个词是可执行程序,或者是shell的内置命令,剩余部分是命令的参数。如果是内置命令shell会在该进程中执行,否则shell会创建子进程然后执行。新建的子进程又叫做作业。通常,作业可以由 Unix 管道连接的多个子进程组成。 如果命令行以 &符号结尾,那么作业将在后台运行,这意味着在打印提示符并等待下一个命令之前, shell 不会等待作业终止。否则,作业在前台运行,这意味着 shell 在作业终止前不会执行下一条命令行。 因此,在任何时候,最多可以在一个作业中运行在前台。 但是,任意数量的作业可以在后台运行。Unix shell 支持作业控制的概念,允许用户在前台和后台之间来回移动作业,并更改进程的状态(运行,停止或终止)。Unixshell 还提供支持作业控制的各种内置命令。例如:

jobs:列出运行和停止的后台作业。

bg :将停止的后台作业更改为正在运行的后台作业。

fg :将停止或运行的后台作业更改为在前台运行。

kill :终止作业。

 

 

第3章 TinyShell的设计与实现

总分45分

3.1 设计

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

函数功能:解析用户输入的命令行

参 数:char* cmdline

处理流程:

  1. 首先通过调用parseline函数解析用户输入的用空格分割的命令行参数,通过参数构造argv向量。Parseline函数返回int值bg,用于标志是否后台运行。
  2. 调用builtin_cmd函数,其参数为argv,判断命令手否为quit\bg\fg\jobs这四条内置命令之一,若是内置命令则执行命令并且返回1,否则返回0. 若该函数返回0则需要创建子进程并在子进程中执行命令。

要点分析:

  1. 每个子进程需要设置单独的pgid,否则键入ctrl+c或ctrl+z时子进程会同tsh处于同一进程组中一起被终止。设置pgdi需要调用setpdig函数。
  2. 在fork子进程之前需要阻塞SIGCCHLD、SIGINT、SIGSTP信号来防止子进程与信号处理程序竞争jobs。
  3. 在父进程创建子进程并通过addjob记录后,再次调用sigprocmask解除阻塞。

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

函数功能:检查命令行参数是否为四条内置命令之一,若是则返回1并执行,若不是则返回0。

参 数:char** argv

处理流程:用if和strcmp判断命令行参数是否为内置命令,若是则执行且返回1,否则返回0.

要点分析:

  1. 需要返回1来表示内置命令。

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

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

参 数:char** argv

处理流程:

  1. 检查命令行是否携带正确的参数,区分pid和jid,最终将id存于jobp中。
  2. 若为bg命令,则需要恢复后台进程,将state改为BG,发送信号SIGCONT;若为fg。需要恢复前台进程,将state改为FG,然后调用waitfg,等待前台进程结束。

要点分析:

  1. 需要区分参数pid和jid
  2. 修改进程状态需要发送SIGCONT信号。

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

函数功能:等待前台子进程完成

参 数:pid_t pid

处理流程:当前台进程pid等于输入参数时,程序休眠直到前台进程pid不再是输入的参数。

要点分析:在while循环中调入sleep函数达到一直检查并等待的效果。

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

函数功能:捕获SIGCHILD信号,回收子进程中的僵尸子进程,改变被终止状态标志,改变被挂起进程状态标志。

参 数:int sig

处理流程:

  1. 设置变量olderrno保存原始errno。
  2. 循环判断,若子进程通过调用exit或return终止,则在阻塞信号的情况下deletejobs,完成后接触信号阻塞。若子进程因为一个未捕获的信号终止,则打印提示信息,并在阻塞信号的情况下deletejobs,完成后解除信号阻塞。若引起返回的子进程当前是停止的,则打印提示信息。
  3. 还原errno并退出

要点分析:

  1. 需要对errno进行保护
  2. 阻塞信号和还原信号通过sigprocmask实现.

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

重点检查代码风格:

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

 

第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 自测试结果

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 请总结本次实验的收获

1. 对异常控制流理解更深刻

2. 对进程的概念更加熟悉

3. 掌握了shell的原理和使用方法实现方法

 

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

 

注:本章为酌情加分项。

参考文献

 

相关