Introduction

The purpose of this assignment is to become more familiar with the concepts of process control and signalling. You’ll do this by writing a simple Unix shell program that supports job control.

本次作业的目的是更加熟悉过程控制和信号的概念。 您将通过编写一个支持作业控制的简单 Unix shell 程序来做到这一点。

Files

Makefile	# Compiles your shell program and runs the tests
README		# This file
tsh.c		# The shell program that you will write and hand in
tshref		# The reference shell binary.

# The remaining files are used to test your shell
sdriver.pl	# The trace-driven shell driver
trace*.txt	# The 15 trace files that control the shell driver
tshref.out 	# Example output of the reference shell on all 15 traces

# Little C programs that are called by the trace files
myspin.c	# Takes argument <n> and spins for <n> seconds
mysplit.c	# Forks a child that spins for <n> seconds
mystop.c        # Spins for <n> seconds and sends SIGTSTP to itself
myint.c         # Spins for <n> seconds and sends SIGINT to itself

Ready

Start by copying the file shlab-handout.tar to the protected directory (the lab directory) in which
you plan to do your work. Then do the following:
• Type the command tar xvf shlab-handout.tar to expand the tarfile.
• Type the command make to compile and link some test routines.
• Type your team member names and Andrew IDs in the header comment at the top of tsh.c.

Hand Out Instructions

Looking at the tsh.c (tiny shell) file, you will see that it contains a functional skeleton of a simple Unix shell. To help you get started, we have already implemented the less interesting functions. Your assignment is to complete the remaining empty functions listed below. As a sanity check for you, we’ve listed the approximate number of lines of code for each of these functions in our reference solution (which includes lots of comments)

查看 tsh.c (tiny shell) 文件,您会看到它包含一个简单的 Unix shell 的功能框架。 为了帮助您入门,我们已经实现了不太有趣的功能。 你的任务是完成下面列出的剩余的空函数。 作为对您的健全性检查,我们在参考解决方案中列出了每个函数的大致代码行数(其中包括大量注释)

• eval: Main routine that parses and interprets the command line. [70 lines]
• builtin cmd: Recognizes and interprets the built-in commands: quit, fg, bg, and jobs. [25
lines]
• do bgfg: Implements the bg and fg built-in commands. [50 lines]
• waitfg: Waits for a foreground job to complete. [20 lines]
• sigchld handler: Catches SIGCHILD signals. 80 lines]
• sigint handler: Catches SIGINT (ctrl-c) signals. [15 lines]
• sigtstp handler: Catches SIGTSTP (ctrl-z) signals. [15 lines]
• eval 解析和解释命令行的主要例程。[70行]
• builtin_cmd :识别和解释内置命令:退出、FG、BG和jobs.line [25行]
• do_bgfg :实现bgandfg内置命令。[50行]
• waitfg:等待前台工作完成。[20行]
• sigchld handler:捕获SIG_CHILD信号。[80行]
• sigint handler:捕获SIG_INT(ctrl-c)信号。[15行]
• sigtstp handler:捕获SIGTSTP(ctrl-z)信号。[15行]

Each time you modify your tsh.c file, type make to recompile it. To run your shell, type tsh to the command line:

unix> ./tsh
tsh> [type commands to your shell here]

Solve

1. eval函数

对命令行进行求值的代码,首要任务是调用parseline函数,这个函数解析了以空格为分隔符的命令行参数,并构造最终会传递给execve的argv向量,第一个参数被假设为要么是一个内置的shell命令名,马上就会解释这个命令,要么是一个可执行目标文件,会在一个新的子进程的上下文中加载并运行这个文件。

如果命令行最后一个参数是’&’字符,那么parseline返回1,表示应该在后台执行该程序(shell不会等待它完成),否则它返回0,表示应该在前台执行这个程序(shell会等待它完成)。

void eval(char *cmdline)
{
    char *argv[MAXARGS];
    int bg = parseline(cmdline, argv) + 1;
    pid_t pid;
    sigset_t mask_all, mask_one, prev_one;
    if (argv[0] == NULL)
        return;
    if (!builtin_cmd(argv))
    {
        sigfillset(&mask_all);
        sigemptyset(&mask_one);
        sigaddset(&mask_one, SIGCHLD);
        sigprocmask(SIG_BLOCK, &mask_one, &prev_one); //添加阻塞SIGCHLD信号, 防止子进程先被调度, 让add先于delete
        pid = fork();
        if (pid < 0)
        {
            printf("fork error\\n");
        }
        if (!pid)
        {
            sigprocmask(SIG_SETMASK, &prev_one, NULL);
            if (setpgid(0, 0) < 0)
            { //创建新的进程组, 进程组ID就为子进程的pid
                printf("SETPGID error\\n");
                exit(0);
            }
            if (execve(argv[0], argv, environ) < 0)
            {
                printf("%s: command not found\\n", argv[0]);
                exit(0);
            }
        }
        else
        {
            sigprocmask(SIG_BLOCK, &mask_all, NULL); //阻塞全部信号, 保护对共享全局数据的结构的访问
            addjob(jobs, pid, bg, cmdline);
            sigprocmask(SIG_SETMASK, &mask_one, NULL); //恢复接收SIGCHLD信号
            if (bg == FG)
            { //前台运行
                waitfg(pid);
            }
            else
            {
                printf("[%d] (%d) %s", nextjid - 1, pid, cmdline); //后台运行 则输出
            }
        }
        sigprocmask(SIG_SETMASK, &prev_one, NULL);
    }
    return;
}