哈工大计算机系统实验七——微壳

 

 

实验报告

验(七)

题     目      TinyShell        

     微壳           

专       业       计算机类           

学     号       xxxx          

班     级       xxxx             

学       生       xxxx              

指 导 教 师       xxxx               

实 验 地 点       xxxx               

实 验 日 期        2019.12.4         

计算机科学与技术学院

 

第1章 实验基本信息............................................................................................. - 4 -

1.1 实验目的......................................................................................................... - 4 -

1.2 实验环境与工具............................................................................................. - 4 -

1.2.1 硬件环境................................................................................................. - 4 -

1.2.2 软件环境................................................................................................. - 4 -

1.2.3 开发工具................................................................................................. - 4 -

1.3 实验预习......................................................................................................... - 4 -

第2章 实验预习..................................................................................................... - 6 -

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

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

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

2.4 什么是shell,功能和处理流程(5分)................................................... - 6 -

第3章 TinyShell的设计与实现.................................................................... - 7 -

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

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

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

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

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

第4章 TinyShell测试.................................................................................... - 9 -

4.1 测试方法......................................................................................................... - 9 -

4.2 测试结果评价................................................................................................. - 9 -

4.3 自测试结果..................................................................................................... - 9 -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4.4 自测试评分................................................................................................... - 14 -

第4章 总结........................................................................................................... - 15 -

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

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

参考文献................................................................................................................. - 16 -

第1章 实验基本信息

 

1.1 实验目的

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

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

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

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

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

1.2 实验环境与工具

1.2.1 硬件环境

Windows7 64 位以上;VirtualBox/Vmware 11 以上;Ubuntu 16.04 LTS 64 位/ 优麒麟 64 位

1.2.2 软件环境

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

1.2.3 开发工具

填写……

1.3 实验预习

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

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

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

了解 shell 的基本原理

熟知进程创建、回收的方法和相关系统函数 熟知信号机制和信号处理相关的系统函数

第2章 实验预习

总分20

 

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

进程:一个执行程序中的实例,提供给我吗一种错觉:我们的程序好像是系统中当前运行的唯一程序,我们的程序独占使用处理器和内存,处理器好像是无间断的执行我们程序中的指令,我们程序的代码和数据好像是系统中内存唯一的对象。

创建进程:使用fork函数,父进程调用fork函数创建一个新的运行的子进程,子进程得到和父进程用户及虚拟地址空间完全相同的一个副本。

回收方法:(1)当一个进程由于某种原因终止时,进程保持在一种已经终止的状态,直到被他的父进程回收,当父进程回收他的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已经终止的进程,从此开始,该进程就不存在了。(2)如果一个父进程终止了,内核会安排init进程成为它的孤儿进程的养分,init进程将会回收这些孤儿进程。

 

 

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

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

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

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

 

信号的类型:

 

 

 

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

1.信号的发送方法:

  1. 用/bin/kill程序发送信号,/bin/kill程序可以向另外的进程发送任意的信号
  2. 从键盘发送信号,在键盘上输入Ctrl+C会导致内核发送一个SIGINT信号到前台进程中的每个进程,默认情况下是终止前台作业
  3. 用kill函数发送信号给其他进程(包括他们自己)
  4. 使用alarm函数发送信号,进程可以通过调用 alarm 函数在指定 secs 秒后发送一个 SIGALRM 信号给调用进程。
  1. 信号的阻塞方法:

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

显示阻塞进制:应用程序可以调用 sigprocmask 函数和它的辅助函 数,明确地阻塞和解除阻塞选定的信号。调用要求如下:

 

 

3.处理程序的设置方法:

 

  1. 调用 signal 函数,调用 signal(SIG,handler),SIG 代表信号类型,handler 代表接收到 SIG 信号之后对应的处理程序。
  2. 因为 signal 的语义各有不同,所以我们需要一个可移植的信号处理函 数设置方法,Posix 标准定义了 sigaction 函数,它允许用户在设置信号

处理时明确指定他们想要的信号处理语义,调用如下:

 

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

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

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

处理流程: 1)从终端读入输入的命令。

2)将输入字符串切分获得所有的参数

 3)如果是内置命令则立即执行

 4)否则调用相应的程序执行

 5)shell 应该接受键盘输入信号,并对这些信号进行相应处理

 

第3章 TinyShell的设计与实现

总分45

3.1 设计

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

函数功能对用户输入的参数进行解析并运行计算。如果用户输入内建的命令行(quit,bg,fg,jobs)那么立即执行。 否则,fork一个新的子进程并且将该任务在子进程的上下文中运行。如果该任务是前台任务那么需要等到它运行结束才返回。

参    :Char* cmdline

处理流程

  1. 首先调用 parseline 函数。将 cmdline 字符串切分为参数数组 argv,另外 parseline 函数返回 bg,可以得知该操作是否需要后台运行。
  2. 调用 buildin_cmd 判断该函数是否为内置函数。如果是内置函数则立即执 行。
  3. 如果不是内置函数,创建一个子进程,然后在子进程的上下文中运行命令 行要求运行的函数(调用 execve 函数)。

要点分析

  1. 如何创建子进程并运行命令:首先将 SIGCHLD、SIGINT、SIGSTP 信号 阻塞,因为在这些信号的信号处理函数中我们将使用到 job 相关的函数, 这些函数会对全局变量工作列表 jobs 进行操作,而我们接下来在父进程中 还要 addjob 将 job 加入的 jobs 中,所以为了避免子进程与信号处理函数竞 争 jobs,我们需要使用 sigprocmask 阻塞以上信号。

 2) 父进程创建完成子进程并用 addjob 记录后,需要用 sigprocmask 解除阻塞。

 3) 子进程从父进程继承了信号阻塞向量,所以子进程必须确保在执行新程序 之前接触对信号的阻塞。

4) Ctr-c 会给所有前台进程组中的进程发送 SIGINT 信号,包括 tsh 和 tsh 创建 的进程,但是这样使不正确的。所以我们需要在子进程中,execve 之前, 调用函数 setpgid(0,0)将子进程加入到一个 pgid=pid 的进程组之中。

5).整个过程中,需要仔细考虑显式阻塞的安排和设计,什么地方需要阻塞哪些信号。     

原则1:在访问全局变量(jobs)必须阻塞所有信号,包括调用老师给的那些函数。由于这些函数基本都是使用for循环遍历完成功能的,所以,务必保证函数执行中不能被中断。

原则2:在一些函数或者指令有必须的前后执行顺序时,请阻塞,保证前一个函数调用完成后(比如必须先addjob,再deletejob)

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

函数功能:判断命令是否是内置指令,是的话立即执行,不是则返回,对单独的‘&’无视

参    数:char **argv

处理流程依次判断用户输入的命令名称 argv 是否是

  1. 内置函数”quit” ,如果是则调用 exit(0)退出 tsh。

2) 内置函数”bg” | “fg”,如果是则调用函数 do_fgbg。返回值 1,tsh 继续。

3)内置函数”jobs”,如果是则调用 listjobs 列出所有的 job 信息。返回值 1, tsh 继续。

4) 如果以上都不是,则不是内置函数,返回 0,tsh 会将它作为一个 job 处理。

要点分析

分别对三种不同的情况调用相应的处理函数。填对参数。了解三种内 置函数的意义,注意只有 quit 是需要 exit(0)的,其他两种情况都需要返回 1,这是 tsh 会继续自己的处理流程。

 

 

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

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

参    数:char **argv

处理流程:

  1. 首先对传入的命令行输入进行解析。首先 bg fg的调用有两种格式,对 这两种格式分别判断然后读入输入的 job pid,通过 pid 调用 getjobpid 获得 job。
  2. 如果是 bg 命令,向 job 所在的进程组发送 SIGCONT 信号,更改 job 的 state 为 BG。

 3) 如果是 fg 命令, 向 job 所在的进程组发送 SIGCONT 信号,更改 job 的 state 为 FG,然后等待当前的程序运行直到当前的 job 不再是前台程 序。

要点分析:

1. 区分bg和fg命令,以及传入pid或者jid参数对应的进程的状态。前者if,后者switch就可以包括所用的情况

2. 注意用户输入错误处理,比如参数数量不够或者参数传入错误的情况

3. 对例外进行报错,分别有:命令为空,找不到 PID 的 job,不符合 bgfg 的格式,命令不是 bgfg。

4.按照 bgfg 命令的特性,我们需要向目标 job 所在的进程组发送 SIGCONT 信号,代表如果该进程组中的进程停止,则需要重新进行。

5.在 fg 命中,我们是要将目标 job 放到前台运行,因此我们需要调用 waitfg 阻塞 tsh 进行,使前台一直都是该 job,直到该 job 不再是前台进 程。触发 job 退出前台的条件是 job 结束运行,从而退出子进程使父进 程调用 SIGCHLD 的处理函数,处理函数中对 job 进行了回收,此时前 台程序不再是 job

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

函数功能:阻塞直到指定 pid 的进程不再是前台进程。

参    数:pid_t pid

处理流程:

进行 while 循环,每次 循环 sleep 1 秒,while 的终止条件是前台程序的 PID 不再是 pid。

要点分析:

   调用 fgpid 函数向 jobs 查询当前 state 是 FG 的 job 的 PID。

 

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

函数功能:父进程中接收到 SIGCHLD 信号的处理函数。

参    数:int sig  处

处理流程:

1) 保存 errno

  1. 处理所有子进程集合中已经停止或终止的子进程。       
  2. 3) 如果该子进程通过调用 exit 或者一个返回正常终止,则阻塞信号,删 除 job,恢复信号。
  3. 如果该子进程当前已经停止,向屏幕打印信息。
  4. 如果该当前子进程是因为一个未被捕获的信号终止的,则向屏幕打印 信息,阻塞信号,删除 job,恢复信号。

6) 恢复 errno

要点分析:

(1)如何处理所有的子进程:利用 while 循环重复判断,判断条件是:      while ((child_pid = waitpid(-1, &status, WNOHANG|WUNTRACED)) > 0) ,其 中waitpid中的option设置为WNOHANG|WUNTRACED的       含义是立即 返回,如果等待集合中的子进程都没有停止或终止的则返  回为 0,如果 有一个,则返回该进程的 PID。

2) 如何判断子进程不同的返回状态:通过 waitpid 传入的 status,分别调 用 WIFEXITED,WIFSTOPPED,WIFSIGNALED,如果为真,则分别 代表的返回情况是:子进程通过调用 exit 或者一个返回正常终止、该 子进程当前已经停止、该当前子进程是因为一个未被捕获的信号终止 的。之所以判断是因为我们需要对这三种不同的情况进行不同的处理。

 3) 因为在信号处理函数中可能会调用修改 errno 的函数,所以我们   需要保 存恢复 errono。

 

 

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

重点检查代码风格:

  1. 用较好的代码注释说明——5
  2. 检查每个系统调用的返回值——5

tsh.c(如下所示)

/*

 * tsh - A tiny shell program with job control

 *1180800811张瑞豪

 * <Put your name and login ID here>

 */

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>

#include <ctype.h>

#include <signal.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <errno.h>



/* Misc manifest constants */

#define MAXLINE    1024   /* max line size */

#define MAXARGS     128   /* max args on a command line */

#define MAXJOBS      16   /* max jobs at any point in time */

#define MAXJID    1<<16   /* max job ID */



/* Job states */

#define UNDEF 0 /* undefined */

#define FG 1    /* running in foreground */

#define BG 2    /* running in background */

#define ST 3    /* stopped */



/*

 * Jobs states: FG (foreground), BG (background), ST (stopped)

 * Job state transitions and enabling actions:

 *     FG -> ST  : ctrl-z

 *     ST -> FG  : fg command

 *     ST -> BG  : bg command

 *     BG -> FG  : fg command

 * At most 1 job can be in the FG state.

 */



/* Global variables */

extern char **environ;      /* defined in libc */

char prompt[] = "tsh> ";    /* command line prompt (DO NOT CHANGE) */

int verbose = 0;            /* if true, print additional output */

int nextjid = 1;            /* next job ID to allocate */

char sbuf[MAXLINE];         /* for composing sprintf messages */



struct job_t {              /* The job struct */

    pid_t pid;              /* job PID */

    int jid;                /* job ID [1, 2, ...] */

    int state;              /* UNDEF, BG, FG, or ST */

    char cmdline[MAXLINE];  /* command line */

};

struct job_t jobs[MAXJOBS]; /* The job list */

/* End global variables */





/* Function prototypes */



/* Here are the functions that you will implement */

void eval(char *cmdline);

int builtin_cmd(char **argv);

void do_bgfg(char **argv);

void waitfg(pid_t pid);



void sigchld_handler(int sig);

void sigtstp_handler(int sig);

void sigint_handler(int sig);



/* Here are helper routines that we've provided for you */

int parseline(const char *cmdline, char **argv);

void sigquit_handler(int sig);



void clearjob(struct job_t *job);

void initjobs(struct job_t *jobs);

int maxjid(struct job_t *jobs);

int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);

int deletejob(struct job_t *jobs, pid_t pid);

pid_t fgpid(struct job_t *jobs);

struct job_t *getjobpid(struct job_t *jobs, pid_t pid);

struct job_t *getjobjid(struct job_t *jobs, int jid);

int pid2jid(pid_t pid);

void listjobs(struct job_t *jobs);



void usage(void);

void unix_error(char *msg);

void app_error(char *msg);

typedef void handler_t(int);

handler_t *Signal(int signum, handler_t *handler);



/*

 * main - The shell's main routine

 */

/*

 * shell的执行main函数

 */

int main(int argc, char **argv)

{

    char c;

    char cmdline[MAXLINE];

    int emit_prompt = 1; /* emit prompt (default) */



    /*将stderr重定位到stdout*/

    dup2(1, 2);



    /* 解析命令行 讨论命令参数*/

    while ((c = getopt(argc, argv, "hvp")) != EOF) {

        switch (c) {

        case 'h':             /*打印帮助信息*/

            usage();

	    break;

        case 'v':             /* 打印格外调试信息*/

            verbose = 1;

	    break;

        case 'p':             /* 不打印prompt */

            emit_prompt = 0;

	    break;

	    default:

            usage();

	    }

    }



    /* 注册自己编写的信号处理函数 */



    Signal(SIGINT,  sigint_handler);   /* ctrl-c */

    Signal(SIGTSTP, sigtstp_handler);  /* ctrl-z */

    Signal(SIGCHLD, sigchld_handler);  /* 终止 或者 子进程返回 */



    /*干净地杀死terminal*/

    Signal(SIGQUIT, sigquit_handler);



    /*初始化工作列表*/

    initjobs(jobs);



    /* 执行死循环流程*/

    while (1) {



	/* 读入命令行 */

	if (emit_prompt) {

	    printf("%s", prompt);

	    fflush(stdout);

	}

	if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))

	    app_error("fgets error");

	if (feof(stdin)) { /* End of file (ctrl-d) */

	    fflush(stdout);

	    exit(0);

	}



	/* 处理命令行 */

	eval(cmdline);

	fflush(stdout);

	fflush(stdout);

    }



    exit(0); /* control never reaches here */

}



/*

 * eval - Evaluate the command line that the user has just typed in

 *

 * If the user has requested a built-in command (quit, jobs, bg or fg)

 * then execute it immediately. Otherwise, fork a child process and

 * run the job in the context of the child. If the job is running in

 * the foreground, wait for it to terminate and then return.  Note:

 * each child process must have a unique process group ID so that our

 * background children don't receive SIGINT (SIGTSTP) from the kernel

 * when we type ctrl-c (ctrl-z) at the keyboard.

*/

/*

 * eval函数是用来处理命令行输入的主要逻辑,所有的处理执行逻辑在这里实现

 */

void eval(char *cmdline)

{

    /* $begin handout */

    char *argv[MAXARGS]; /* argv for execve() */

    int bg;              /* should the job run in bg or fg? */

    pid_t pid;           /* process id */

    sigset_t mask;       /* signal mask */



    /*解析命令行*/

    bg = parseline(cmdline, argv);

    if (argv[0] == NULL)

	return;   /* ignore empty lines */



    if (!builtin_cmd(argv)) {



    /* 将SIGCHLD SIGINT SIGSTP的信号阻塞 因为在这些处理程序中都是用了job相关的函数

     * 也就是同时调用了全局变量jobs 为了避免竞争所以对这些信号进行阻断

     * 同时需要注意 需要输出错误信息

     */

	if (sigemptyset(&mask) < 0)

	    unix_error("sigemptyset error");

	if (sigaddset(&mask, SIGCHLD))

	    unix_error("sigaddset error");

	if (sigaddset(&mask, SIGINT))

	    unix_error("sigaddset error");

	if (sigaddset(&mask, SIGTSTP))

	    unix_error("sigaddset error");

	if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)

	    unix_error("sigprocmask error");



	/* 创建一个子进程 */

	if ((pid = fork()) < 0)

	    unix_error("fork error");



	/*

	 * 子进程中的执行逻辑 用pid=0来区分

	 */

	if (pid == 0) {

	    /* 子进程 解锁信号 */

	    sigprocmask(SIG_UNBLOCK, &mask, NULL);



	    /* 对每一个子进程开一个单独的进程组 用setpid(0,0)来实现 */

	    if (setpgid(0, 0) < 0)

		  unix_error("setpgid error");



	    /* 执行新的程序 */

	    if (execve(argv[0], argv, environ) < 0) {

            printf("%s: Command not found\n", argv[0]);

            exit(0);

	    }

	}



	/*

	 * 父进程

	 */



	/* 为了解决父进程与子进程之前存在的race问题,上面设置了信号的封锁,

	 * 这里在添加了job之后需要对上面封锁的信号进行解锁*/

	addjob(jobs, pid, (bg == 1 ? BG : FG), cmdline);

	sigprocmask(SIG_UNBLOCK, &mask, NULL);



	if (!bg)

	    waitfg(pid);

	else

	    printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);

    }

    /* $end handout */

    return;

}



/*

 * parseline - Parse the command line and build the argv array.

 *

 * Characters enclosed in single quotes are treated as a single

 * argument.  Return true if the user has requested a BG job, false if

 * the user has requested a FG job.

 */

/* 解析命令行 */

int parseline(const char *cmdline, char **argv)

{

    static char array[MAXLINE]; /* holds local copy of command line */

    char *buf = array;          /* ptr that traverses command line */

    char *delim;                /* points to first space delimiter */

    int argc;                   /* number of args */

    int bg;                     /* background job? */



    strcpy(buf, cmdline);

    buf[strlen(buf)-1] = ' ';  /* replace trailing '\n' with space */

    while (*buf && (*buf == ' ')) /* ignore leading spaces */

	buf++;



    /* Build the argv list */

    argc = 0;

    if (*buf == '\'') {

	buf++;

	delim = strchr(buf, '\'');

    }

    else {

	delim = strchr(buf, ' ');

    }



    while (delim) {

	argv[argc++] = buf;

	*delim = '\0';

	buf = delim + 1;

	while (*buf && (*buf == ' ')) /* ignore spaces */

	       buf++;



	if (*buf == '\'') {

	    buf++;

	    delim = strchr(buf, '\'');

	}

	else {

	    delim = strchr(buf, ' ');

	}

    }

    argv[argc] = NULL;



    if (argc == 0)  /* ignore blank line */

	return 1;



    /* should the job run in the background? */

    if ((bg = (*argv[argc-1] == '&')) != 0) {

	argv[--argc] = NULL;

    }

    return bg;

}



/*

 * builtin_cmd - If the user has typed a built-in command then execute

 *    it immediately.

 */

int builtin_cmd(char **argv)

{

    /*

     * 判断是否是内置函数

     * 如果是内置函数 则执行 返回1

     * 如果不是内置函数 返回0

     */

    if(!strcmp(argv[0],"quit")){    //quit 退出tsh

        exit(0);

    }
	if(!strcmp(argv[0],"&")){//忽略单独的&
		return 1;
	}
    if(!strcmp(argv[0],"bg") || (!strcmp(argv[0],"fg"))) {  //fg bg任务显示,调用相应函数即可

        do_bgfg(argv);

        return 1;

    }

    if(!strcmp(argv[0],"jobs")) {     //列出所有任务,调用 listjobs 即可

        listjobs(jobs);

        return 1;

    }



    return 0;     /* not a builtin command */

}



/*

 * do_bgfg - Execute the builtin bg and fg commands

 */

void do_bgfg(char **argv)

{

    /* 执行内置命令bg fg */

    /* $begin handout */

    struct job_t *jobp=NULL;



    /* Ignore command if no argument */

    if (argv[1] == NULL) {

        printf("%s command requires PID or %%jobid argument\n", argv[0]);

        return;

    }



    /* Parse the required PID or %JID arg */

    if (isdigit(argv[1][0])) {  //开头是数字的格式

        pid_t pid = atoi(argv[1]);      //数字转换函数 可以跳过前面的空格,转换负数

        if (!(jobp = getjobpid(jobs, pid))) {

            printf("(%d): No such process\n", pid);

            return;

        }

    }

    else if (argv[1][0] == '%') {   //开头是%的格式

        int jid = atoi(&argv[1][1]);    //传入的是地址 直接取地址

        if (!(jobp = getjobjid(jobs, jid))) {

            printf("%s: No such job\n", argv[1]);

            return;

        }

    }

    else {

        printf("%s: argument must be a PID or %%jobid\n", argv[0]);

        return;

    }



    /* bg command */

    if (!strcmp(argv[0], "bg")) {

        if (kill(-(jobp->pid), SIGCONT) < 0)        //向job所在的进程组发送SIGCONT信号

            unix_error("kill (bg) error");

        jobp->state = BG;                           //更改state为BG

        printf("[%d] (%d) %s", jobp->jid, jobp->pid, jobp->cmdline);

    }

    /* fg command */

    else if (!strcmp(argv[0], "fg")) {

        if (kill(-(jobp->pid), SIGCONT) < 0)        //向job所在的进程组发送SIGCONT信号

            unix_error("kill (fg) error");

        jobp->state = FG;                           //更改state为FG

        waitfg(jobp->pid);                          //调用waitfg等待job,直到前台进程不是job

    }

    else {

        printf("do_bgfg: Internal error\n");

        exit(0);

    }

    /* $end handout */

    return;

}



/*

 * waitfg - Block until process pid is no longer the foreground process

 */

void waitfg(pid_t pid)

{

    while(fgpid(jobs)==pid) {       //busy_loop 等待前台程序不再是pid

        sleep(1);

    }

}



/*****************

 * Signal handlers

 *****************/



/*

 * sigchld_handler - The kernel sends a SIGCHLD to the shell whenever

 *     a child job terminates (becomes a zombie), or stops because it

 *     received a SIGSTOP or SIGTSTP signal. The handler reaps all

 *     available zombie children, but doesn't wait for any other

 *     currently running children to terminate.

 */

void sigchld_handler(int sig)

{

    /*

     * SIGCHLD 信号处理程序

     */

    int olderrno = errno;       //记录下errno 防止在信号处理过程中 调用函数 出错 产生errno变化

    int status;

    sigset_t mask_all, prev_all;

    pid_t pid;



    sigfillset(&mask_all);

    while((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {  //while循环 回收所有僵死进程

        //对导致退出的情况进行屏幕回显

        // WHOANG|WUNTRACED 代表立即返回,如果等待集合中的子进程都没有停止或终止则返回为0,如果有一个,则返回他的PID

        if (WIFEXITED(status)) {        //子进程通过调用exit或者一个返回正常终止

            sigprocmask(SIG_BLOCK, &mask_all, &prev_all);       //阻塞所有的信号

            deletejob(jobs, pid);                             //在任务列表中删除任务

            sigprocmask(SIG_SETMASK, &prev_all, NULL);          //解除阻塞

        }



        if (WIFSTOPPED(status)) {      //引起返回的子进程当前是停止的

            printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));

        }



        if (WIFSIGNALED(status)) {      //子进程是因为一个未被捕获的信号终止的

            printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));

            sigprocmask(SIG_BLOCK, &mask_all, &prev_all);     //在任务列表中删除任务

            deletejob(jobs, pid);

            sigprocmask(SIG_SETMASK, &prev_all, NULL);

        }

    }



    errno = olderrno;       //恢复errno

}



/*

 * sigint_handler - The kernel sends a SIGINT to the shell whenver the

 *    user types ctrl-c at the keyboard.  Catch it and send it along

 *    to the foreground job.

 */

void sigint_handler(int sig)

{

    /*中断信号SIGINT处理函数*/

    int olderrno = errno;       //保存errno

    pid_t pid = fgpid(jobs);



    if (pid == 0) {

        return;

    }



    kill(-pid, SIGINT);       //向处于foreground正在运行的进程所处的进程组发送SIGINT

    errno = olderrno;

}



/*

 * sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever

 *     the user types ctrl-z at the keyboard. Catch it and suspend the

 *     foreground job by sending it a SIGTSTP.

 */

void sigtstp_handler(int sig) {

//    puts("handle sigstop");

    /*SIGSTP信号的处理函数*/

    pid_t pid = fgpid(jobs);

    if(pid == 0) return ;

    getjobpid(jobs, pid)->state = ST;       //设置state为ST

//  Job [2] (29481) stopped by signal 20

    kill(-pid,SIGTSTP);

    printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, sig);

    return;

}



/*********************

 * End signal handlers

 *********************/



/***********************************************

 * Helper routines that manipulate the job list

 **********************************************/



/* clearjob - Clear the entries in a job struct */

void clearjob(struct job_t *job) {

    job->pid = 0;

    job->jid = 0;

    job->state = UNDEF;

    job->cmdline[0] = '\0';

}



/* initjobs - Initialize the job list */

void initjobs(struct job_t *jobs) {

    int i;



    for (i = 0; i < MAXJOBS; i++)

	clearjob(&jobs[i]);

}



/* maxjid - Returns largest allocated job ID */

int maxjid(struct job_t *jobs)

{

    int i, max=0;



    for (i = 0; i < MAXJOBS; i++)

	if (jobs[i].jid > max)

	    max = jobs[i].jid;

    return max;

}



/* addjob - Add a job to the job list */

int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline)

{

    int i;



    if (pid < 1)

	return 0;



    for (i = 0; i < MAXJOBS; i++) {

	if (jobs[i].pid == 0) {

	    jobs[i].pid = pid;

	    jobs[i].state = state;

	    jobs[i].jid = nextjid++;

	    if (nextjid > MAXJOBS)

		nextjid = 1;

	    strcpy(jobs[i].cmdline, cmdline);

  	    if(verbose){

	        printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);

            }

            return 1;

	}

    }

    printf("Tried to create too many jobs\n");

    return 0;

}



/* deletejob - Delete a job whose PID=pid from the job list */

int deletejob(struct job_t *jobs, pid_t pid)

{

    int i;



    if (pid < 1)

	return 0;



    for (i = 0; i < MAXJOBS; i++) {

	if (jobs[i].pid == pid) {

	    clearjob(&jobs[i]);

	    nextjid = maxjid(jobs)+1;

	    return 1;

	}

    }

    return 0;

}



/* fgpid - Return PID of current foreground job, 0 if no such job */

pid_t fgpid(struct job_t *jobs) {

    int i;



    for (i = 0; i < MAXJOBS; i++)

	if (jobs[i].state == FG)

	    return jobs[i].pid;

    return 0;

}



/* getjobpid  - Find a job (by PID) on the job list */

struct job_t *getjobpid(struct job_t *jobs, pid_t pid) {

    int i;



    if (pid < 1)

	return NULL;

    for (i = 0; i < MAXJOBS; i++)

	if (jobs[i].pid == pid)

	    return &jobs[i];

    return NULL;

}



/* getjobjid  - Find a job (by JID) on the job list */

struct job_t *getjobjid(struct job_t *jobs, int jid)

{

    int i;



    if (jid < 1)

	return NULL;

    for (i = 0; i < MAXJOBS; i++)

	if (jobs[i].jid == jid)

	    return &jobs[i];

    return NULL;

}



/* pid2jid - Map process ID to job ID */

int pid2jid(pid_t pid)

{

    int i;



    if (pid < 1)

	return 0;

    for (i = 0; i < MAXJOBS; i++)

	if (jobs[i].pid == pid) {

            return jobs[i].jid;

        }

    return 0;

}



/* listjobs - Print the job list */

void listjobs(struct job_t *jobs)

{

    int i;



    for (i = 0; i < MAXJOBS; i++) {

	if (jobs[i].pid != 0) {

	    printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);

	    switch (jobs[i].state) {

		case BG:

		    printf("Running ");

		    break;

		case FG:

		    printf("Foreground ");

		    break;

		case ST:

		    printf("Stopped ");

		    break;

	    default:

		    printf("listjobs: Internal error: job[%d].state=%d ",

			   i, jobs[i].state);

	    }

	    printf("%s", jobs[i].cmdline);

	}

    }

}

/******************************

 * end job list helper routines

 ******************************/





/***********************

 * Other helper routines

 ***********************/



/*

 * usage - print a help message

 */

void usage(void)

{

    printf("Usage: shell [-hvp]\n");

    printf("   -h   print this message\n");

    printf("   -v   print additional diagnostic information\n");

    printf("   -p   do not emit a command prompt\n");

    exit(1);

}



/*

 * unix_error - unix-style error routine

 */

void unix_error(char *msg)

{

    fprintf(stdout, "%s: %s\n", msg, strerror(errno));

    exit(1);

}



/*

 * app_error - application-style error routine

 */

void app_error(char *msg)

{

    fprintf(stdout, "%s\n", msg);

    exit(1);

}



/*

 * Signal - wrapper for the sigaction function

 */

/* 给出的通过sigaction进行的包装 完成信号注册的功能 */

handler_t *Signal(int signum, handler_t *handler)

{

    struct sigaction action, old_action;



    action.sa_handler = handler;

    sigemptyset(&action.sa_mask); /* block sigs of type being handled */

    action.sa_flags = SA_RESTART; /* restart syscalls if possible */



    if (sigaction(signum, &action, &old_action) < 0)

	unix_error("Signal error");

    return (old_action.sa_handler);

}



/*

 * sigquit_handler - The driver program can gracefully terminate the

 *    child shell by sending it a SIGQUIT signal.

 */

void sigquit_handler(int sig)

{

    printf("Terminating after receipt of SIGQUIT signal\n");

    exit(1);

}

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

*了解了信号的处理机制,对一些信号的函数有更深的了解

*了解了shell对命令行的处理机制

  •  

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、付费专栏及课程。

余额充值