CSAPP(CMU 15-213):Lab5 Shlab详解

# 前言

本系列文章意在记录答主学习CSAPP Lab的过程,也旨在可以帮助后人一二,欢迎大家指正!


tips:本lab主要是为了更加熟悉进程控制和信号的概念!!要完成一个支持简易作业控制的shell程序。
Shell这样的交互型程序主要负责管控用户所运行程序进程,并负责给各个子进程分配信号以完成要求!!!

handout

实现函数【参考代码行数】

所给的资料已经拥有一个shell程序的功能框架,我们只需完成有关作业控制的几个关键函数即可。(tsh.c

  • eval:分析和解释命令行的主例程【70行】
  • builtin_cmd:识别并执行对内建命令的操作(quitfgbgjobs)【25行】
  • do_bgfg:完成bgfg的操作【50行】
  • waitfg:等待前台作业的完成【20行】
  • signchld_handler:捕获SIGCHILD信号,对相关子进程 job 的状态进行管理【80行】
  • sigint_handler:捕获SIGINTctrl-C)信号,发送给前台进程【15行】
  • sigtstp_handler:捕获SIGTSTPctrl-z)信号,发送给前台进程【15行】

helper function

  • int parseline(const char *cmdline, char **argv);:解析命令行
  • void sigquit_handler(int sig);:quit 信号处理函数
  • void clearjob(struct job_t *job);: 清除 jobs 列表
  • void initjobs(struct job_t *jobs);:初绐化 jobs 列表
  • int maxjid(struct job_t *jobs);:返回最大已分配的 job ID
  • int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);:添加 job 到 job 列表
  • int deletejob(struct job_t *jobs, pid_t pid);:从 job 列表中删除 PID=pid 的 job
  • pid_t fgpid(struct job_t *jobs);:返回当前在前台的 job PID
  • struct job_t *getjobpid(struct job_t *jobs, pid_t pid);:根据 job pid 返回该 job
  • struct job_t *getjobjid(struct job_t *jobs, int jid);:根据 job jid 返回该 job
  • int pid2jid(pid_t pid);:由 process ID 得到 job ID
  • void listjobs(struct job_t *jobs);:打印 job 列表
  • void usage(void);:打印帮助信息
  • void unix_error(char *msg);:Unix 风格的 error 例程
  • void app_error(char *msg);:Application 风格的 error 例程
  • handler_t *Signal(int signum, handler_t *handler);:sigaction 的包装函数

driver commands

# Driver commands:
#     TSTP        Send a SIGTSTP signal to the child
#     INT         Send a SIGINT signal to the child 
#     QUIT        Send a SIGQUIT signal to the child
#     KILL        Send a SIGKILL signal to the child
#     CLOSE       Close Writer (sends EOF signal to child)
#     WAIT        Wait() for child to terminate
#     SLEEP <n>   Sleep for <n> seconds

作业控制(job control)

进程的作业状态
  • running
    • FG: running in foreground
    • BG: running in background
  • stopped
    • ST: stopped
  • terminated
作业控制函数
  • jobs:列出后台中运行的暂停的作业
  • bg <job>:将一个后台停止作业改变为后台运行的作业(通过接收SIGCONT信号)
  • fg <job>:将一个后台运行或停止的作业改变为前台运行的作业(通过接收SIGCONT信号)
  • kill <job>:终止一个作业
作业状态转变
  • FG -> ST : ctrl-Z
  • ST -> FG : fg command
  • ST -> BG : bg command
  • BG -> FG : fg command

myShell

​ handout中推荐使用trace文件来指导shell的开发,故我们的描述也是从几个trace文件逐步深入。

trace01

# trace01.txt - Properly terminate on EOF.

​ 作为第一个trace,我将其理解为命令行为空时返回,即修改如下代码。

//首先在解析命令行后判断命令是否为空                    ??feof?kkkkkkkkkkkkj
//eval(char *cmdline)
if (argv[0] == NULL) {   /* ignore empty line */
    return;
}

trace02

# trace02.txt - Process builtin quit command.
#
quit
WAIT

​ 若是内建命令,则先执行其命令。

//eval(char *cmdline)
if (!builtin_cmd(argv)) {   //这样处理非常巧妙,直接执行builtin_command命令,若是执行内命令后直接退出if,若不是则执行条件内部的语句
    // not builtin_command
}

trace03

# trace03.txt - Run a foreground job.
#
/bin/echo tsh> quit
quit

​ 创建内建命令quit。(直接终止 shell 进程)

// builtin_cmd(char **argv)
if (!strcmp(argv[0], "quit")) 
    exit(0);   //terminate shell process

trace04

输入
# trace04.txt - Run a background job.
#
/bin/echo -e tsh> ./myspin 1 \046
./myspin 1 &
思路与代码

​ 设置后台运行的选项&。(即设置添加job和删除Job).

​ 这个trace中添加了大部分处理 job 的逻辑。

//eval(char *cmdline)
int bg;
int state;
pid_t pid;
char buf[MAXLINE];     /* holds modified command lime */
char *argv[MAXARGS];   /* Argument list execve() */

strcpy(buf, cmdline);
bg = parseline(buf, argv);
if (argv[0] == NULL) {   /* ignore empty line */
    return;
}

sigset_t mask_all, mask_one, prev_one;
sigfillset(&mask_all);
sigemptyset(&mask_one);
sigaddset(&mask_one, SIGCHLD);

if (!builtin_cmd(argv)) {
    if((pid = fork()) < 0)
        unix_error("fork error");

    sigprocmask(SIG_BLOCK, &mask_one, &prev_one);     //block SIGCHLD
    if (pid == 0) {  //child process
        sigprocmask(SIG_BLOCK, &prev_one, NULL);      //unblock SIGCHLD
        setpgid(0, 0);     //注意要放入新的前台进程组中
        if (execve(argv[0], argv, environ) < 0) 
            unix_error("execve error");
    }

    state = bg ? BG : FG;
    sigprocmask(SIG_BLOCK, &mask_all, NULL);     //protect addjob()
    addjob(jobs, pid, state, cmdline);
    sigprocmask(SIG_SETMASK, &prev_one, NULL);     //unblock SIGCHLD

    if (!bg) {  //foreground job
        waitfg(pid);  //wait for a foreground job go complete
    } 
    else {  //background job
        printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
    }
}
return;

//sigchld_handler(int sig)
	//sigchld_handler 专注于处理子进程停止或终止时发出的信号SIGCHLD而对子进程的 job 进行管理
	//waitfg shell进程等待前台进程运行完毕
int olderrno = errno;
int statusp;
pid_t pid;
sigset_t mask_all, prev_all;
sigfillset(&mask_all);
while ((pid = waitpid(-1, &statusp, WNOHANG | WUNTRACED)) > 0) {
    // WNOHANG | WUNTRACED  立即返回
    if (WIFEXITED(statusp)) {  //子进程正常终止
        sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
        deletejob(jobs, pid);
        sigprocmask(SIG_SETMASK, &prev_all, NULL);
    } else if (WIFSIGNALED(statusp)) {   //子进程 信号退出 (因为一个未被捕获的信号终止的,eg.SIGINT)
    } else {  // 停止状态

    }
}
errno = olderrno;
return;
}
输出
./sdriver.pl -t trace04.txt -s ./tsh -a "-p"
#
# trace04.txt - Run a background job.
#
tsh> ./myspin 1 &
[1] (2764) ./myspin 1 &

trace05

输入
# trace05.txt - Process jobs builtin command.
#
/bin/echo -e tsh> ./myspin 2 \046
./myspin 2 &

/bin/echo -e tsh> ./myspin 3 \046
./myspin 3 &

/bin/echo tsh> jobs
jobs
思路与代码

​ 这个比较简单,只需添加内建命令 Jobs 即可,而打印 jobs 列表已经有提供(listjobs)。

//builtin_cmd(char **argv)
if (!strcmp(argv[0], "jobs")) {
    listjobs(jobs);
    return 1;
}
输出
./sdriver.pl -t trace05.txt -s ./tsh -a "-p"
#
# trace05.txt - Process jobs builtin command.
#
tsh> ./myspin 2 &
[1] (3305) ./myspin 2 &
tsh> ./myspin 3 &
[2] (3307) ./myspin 3 &
tsh> jobs
[1] (3305) Running ./myspin 2 &
[2] (3307) Running ./myspin 3 &

trace06

输入
# trace06.txt - Forward SIGINT to foreground job.
#
/bin/echo -e tsh> ./myspin 4
./myspin 4 

SLEEP 2
INT
思路与代码

​ 完成对前台 job 的处理以及 SIGINT 信号的处理。

//waitfg(pid_t pid)
void waitfg(pid_t pid)
{
	sigset_t mask_empty;
	sigemptyset(&mask_empty);
	while (fgpid(jobs) > 0)              ///访问全局数据结构要阻塞信号的!!这边没有阻塞
		sigsuspend(&mask_empty);
    return;
}
//sigchld_handler(int sig)
else if (WIFSIGNALED(statusp)) {   //子进程 信号退出 (因为一个未被捕获的信号终止的,eg.SIGINT)
    sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
    printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, SIGINT);
    deletejob(jobs, pid);
    sigprocmask(SIG_SETMASK, &prev_all, NULL);
//sigint_handler(int sig)
void sigint_handler(int sig) 
{
	//shell收到SIGINT(ctrl-c)并向前台子进程发送其信号
	int olderrno = errno;
	int rc;
	sigset_t mask_all, prev_all;
	sigfillset(&mask_all);
	sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
	pid_t pid = fgpid(jobs);              //访问共享数据结构时要阻塞信号
	sigprocmask(SIG_SETMASK, &prev_all, NULL);
	if (pid) {   //若pid == 0 则无前台进程
		if ((rc = kill(-pid, sig)) < 0)   //sig == SIGINT
		unix_error("SIGINT error");
	}
	errno = olderrno;
    return;
}
输出
./sdriver.pl -t trace06.txt -s ./tsh -a "-p"
#
# trace06.txt - Forward SIGINT to foreground job.
#
tsh> ./myspin 4
Job [1] (3336) terminated by signal 2

trace07

输入
# trace07.txt - Forward SIGINT only to foreground job.
#
/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &

/bin/echo -e tsh> ./myspin 5
./myspin 5 

SLEEP 2
INT

/bin/echo tsh> jobs
jobs
#### 思路与代码

​ 在有后台 job 工作的情况下,向前台 job 发送 SIGINT,此功能前述已实现。

输出
./sdriver.pl -t trace07.txt -s ./tsh -a "-p"
#
# trace07.txt - Forward SIGINT only to foreground job.
#
tsh> ./myspin 4 &
[1] (3375) ./myspin 4 &
tsh> ./myspin 5
Job [2] (3377) terminated by signal 2
tsh> jobs
[1] (3375) Running ./myspin 4 &

trace08

输入
./sdriver.pl -t trace08.txt -s ./tshref -a "-p"
#
# trace08.txt - Forward SIGTSTP only to foreground job.
#
tsh> ./myspin 4 &
[1] (3409) ./myspin 4 &
tsh> ./myspin 5
Job [2] (3411) stopped by signal 20
tsh> jobs
[1] (3409) Running ./myspin 4 &
[2] (3411) Stopped ./myspin 5 
思路与代码

​ 向前台进程发送SIGTSTP信号。

//sigchld_handler(int sig)
else {  // 停止状态
     sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
     struct job_t* job = getjobpid(jobs, pid);
     job->state = ST;
     printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, SIGTSTP);
     sigprocmask(SIG_SETMASK, &prev_all, NULL);
	}
//sigstp_handler(int sig)    基本上与sigint相同
void sigtstp_handler(int sig) 
{
	//shell收到SIGTSTP(ctrl-z)并向前台子进程发送其信号
	int olderrno = errno;
	int rc;
	sigset_t mask_all, prev_all;
	sigfillset(&mask_all);
	sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
	pid_t pid = fgpid(jobs);              //访问共享数据结构时要阻塞信号
	sigprocmask(SIG_SETMASK, &prev_all, NULL);
	if (pid) {   //若pid == 0 则无前台进程
		if ((rc = kill(-pid, sig)) < 0)   //sig == SIGINT
		unix_error("SIGTSTP error");
	}
	errno = olderrno;
    return;
}
输出
./sdriver.pl -t trace08.txt -s ./tsh -a "-p"
#
# trace08.txt - Forward SIGTSTP only to foreground job.
#
tsh> ./myspin 4 &
[1] (3461) ./myspin 4 &
tsh> ./myspin 5
Job [2] (3463) terminated by signal 20
tsh> jobs
[1] (3461) Running ./myspin 4 &
[2] (3463) Stopped ./myspin 5 

trace09

输入
# trace09.txt - Process bg builtin command
#
/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &

/bin/echo -e tsh> ./myspin 5
./myspin 5 

SLEEP 2
TSTP

/bin/echo tsh> jobs
jobs

/bin/echo tsh> bg %2
bg %2

/bin/echo tsh> jobs
jobs
思路与代码

​ 完成内建函数bg

void do_bgfg(char **argv) 
{
    if (!strcmp(argv[0], "bg")) {
		sigset_t mask_all, prev_all;
		sigfillset(&mask_all);
		struct job_t* job;
		if (argv[1][0] == '%') {
			int jid = atoi(&argv[1][1]);
			sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
			job = getjobjid(jobs, jid);
			sigprocmask(SIG_SETMASK, &prev_all, NULL);
		} else {
			pid_t pid = atoi(argv[1]);
			sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
			job = getjobpid(jobs, pid);
			sigprocmask(SIG_SETMASK, &prev_all, NULL);
		}
		if (kill(-job->pid, SIGCONT) < 0) 
			unix_error("SIGCONT error");
		job->state = BG;
		printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
	}
	return;
}
输出
./sdriver.pl -t trace09.txt -s ./tsh -a "-p"
#
# trace09.txt - Process bg builtin command
#
tsh> ./myspin 4 &
[1] (3712) ./myspin 4 &
tsh> ./myspin 5
Job [2] (3714) terminated by signal 20
tsh> jobs
[1] (3712) Running ./myspin 4 &
[2] (3714) Stopped ./myspin 5 
tsh> bg %2
[2] (3714) ./myspin 5 
tsh> jobs
[1] (3712) Running ./myspin 4 &
[2] (3714) Running ./myspin 5 

trace10

输入
# trace10.txt - Process fg builtin command. 
#
/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &

SLEEP 1
/bin/echo tsh> fg %1
fg %1

SLEEP 1
TSTP

/bin/echo tsh> jobs
jobs

/bin/echo tsh> fg %1
fg %1

/bin/echo tsh> jobs
jobs
思路与代码

​ 完成内建函数fg

void do_bgfg(char **argv) 
{
	sigset_t mask_all, prev_all;
	sigfillset(&mask_all);
	struct job_t* job;
	sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
	if (argv[1][0] == '%') {
		int jid = atoi(&argv[1][1]);
		job = getjobjid(jobs, jid);
	} else {
		pid_t pid = atoi(argv[1]);
		job = getjobpid(jobs, pid);
	}
	sigprocmask(SIG_SETMASK, &prev_all, NULL);

    if (!strcmp(argv[0], "bg")) {  //bg command
		if (kill(-job->pid, SIGCONT) < 0) 
			unix_error("SIGCONT error");
		job->state = BG;
		printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);

	} else {   //fg command
		if (job->state == ST) 
			if (kill(-job->pid, SIGCONT) < 0) 
				unix_error("SIGCONT error");
		job->state = FG;
		waitfg(job->pid);
	}
	return;
}
输出
./sdriver.pl -t trace10.txt -s ./tsh -a "-p"
#
# trace10.txt - Process fg builtin command. 
#
tsh> ./myspin 4 &
[1] (3826) ./myspin 4 &
tsh> fg %1
Job [1] (3826) stopped by signal 20
tsh> jobs
[1] (3826) Stopped ./myspin 4 &
tsh> fg %1
tsh> jobs

基本功能已完成,以下都是对细节的测试。

trace11

输入
# trace11.txt - Forward SIGINT to every process in foreground process group
#
/bin/echo -e tsh> ./mysplit 4
./mysplit 4 

SLEEP 2
INT

/bin/echo tsh> /bin/ps a
/bin/ps a
思路与代码

​ 保证是对前台作业组(不是前台作业)发送信号 SIGINT。这个功能我们前面已经实现过了。

输出

​ 因为输出的是/bin/ps a 所有的进程,结果就不在这里放了。

trace12

输入
# trace12.txt - Forward SIGTSTP to every process in foreground process group
#
/bin/echo -e tsh> ./mysplit 4
./mysplit 4 

SLEEP 2
TSTP

/bin/echo tsh> jobs
jobs

/bin/echo tsh> /bin/ps a
/bin/ps a
思路与代码

​ 保证是对前台作业组(不是前台作业)发送信号 SIGTSTP。这个功能我们前面已经实现过了。

输出

​ 同样输出太多,不在此处放了。

trace13

输入
# trace13.txt - Restart every stopped process in process group
#
/bin/echo -e tsh> ./mysplit 4
./mysplit 4 

SLEEP 2
TSTP

/bin/echo tsh> jobs
jobs

/bin/echo tsh> /bin/ps a
/bin/ps a

/bin/echo tsh> fg %1
fg %1

/bin/echo tsh> /bin/ps a
/bin/ps a
思路与代码

​ 同样是对进程组中的每个进程进行重启操作。

输出

​ 太多,不放。

trace14

输入
# trace14.txt - Simple error handling
#
/bin/echo tsh> ./bogus
./bogus

/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &

/bin/echo tsh> fg
fg

/bin/echo tsh> bg
bg

/bin/echo tsh> fg a
fg a

/bin/echo tsh> bg a
bg a

/bin/echo tsh> fg 9999999
fg 9999999

/bin/echo tsh> bg 9999999
bg 9999999

/bin/echo tsh> fg %2
fg %2

/bin/echo tsh> fg %1
fg %1

SLEEP 2
TSTP

/bin/echo tsh> bg %2
bg %2

/bin/echo tsh> bg %1
bg %1

/bin/echo tsh> jobs
jobs
思路与代码

​ 对系统调用(fgbg)的错误返回进行处理。

输出

​ 此处不放,最终代码中见真章。

trace15

输入
# trace15.txt - Putting it all together
#

/bin/echo tsh> ./bogus
./bogus

/bin/echo tsh> ./myspin 10
./myspin 10

SLEEP 2
INT

/bin/echo -e tsh> ./myspin 3 \046
./myspin 3 &

/bin/echo -e tsh> ./myspin 4 \046
./myspin 4 &

/bin/echo tsh> jobs
jobs

/bin/echo tsh> fg %1
fg %1

SLEEP 2
TSTP

/bin/echo tsh> jobs
jobs

/bin/echo tsh> bg %3
bg %3

/bin/echo tsh> bg %1
bg %1

/bin/echo tsh> jobs
jobs

/bin/echo tsh> fg %1
fg %1

/bin/echo tsh> quit
quit
思路与代码

​ 将前面的功能需求综合起来测试。无新代码。

输出
# trace15.txt - Putting it all together
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 10
Job [1] (4715) terminated by signal 2
tsh> ./myspin 3 &
[1] (4717) ./myspin 3 &
tsh> ./myspin 4 &
[2] (4719) ./myspin 4 &
tsh> jobs
[1] (4717) Running ./myspin 3 &
[2] (4719) Running ./myspin 4 &
tsh> fg %1
Job [1] (4717) stopped by signal 20
tsh> jobs
[1] (4717) Stopped ./myspin 3 &
[2] (4719) Running ./myspin 4 &
tsh> bg %3
%3: No such job
tsh> bg %1
[1] (4717) ./myspin 3 &
tsh> jobs
[1] (4717) Running ./myspin 3 &
[2] (4719) Running ./myspin 4 &
tsh> fg %1
tsh> quit

trace16

输入
# trace16.txt - Tests whether the shell can handle SIGTSTP and SIGINT
#     signals that come from other processes instead of the terminal.
#

/bin/echo tsh> ./mystop 2 
./mystop 2

SLEEP 3

/bin/echo tsh> jobs
jobs

/bin/echo tsh> ./myint 2 
./myint 2
思路与代码

​ 之前是从键盘发送信号(SIGTSTP、SIGINT),由shell接收并发送给子进程,现在测试 shell 下的运行进程之间是否可以发送信号。

输出
# trace16.txt - Tests whether the shell can handle SIGTSTP and SIGINT
#     signals that come from other processes instead of the terminal.
#
tsh> ./mystop 2
Job [1] (4760) stopped by signal 20
tsh> jobs
[1] (4760) Stopped ./mystop 2
tsh> ./myint 2
Job [2] (4763) terminated by signal 2

总结逐步的实现流程

功能实现

  • trace01:无
  • trace02:设置内建命令
  • trace03:创建内建命令quit
  • trace04:设置后台执行逻辑&以及作业控制
  • trace05:创建内建命令jobs,即打印作业列表
  • trace06:设置前台作业处理逻辑SIGINT 信号的处理
  • trace07:测试在有后台 job 工作的情况下,向前台 job 发送 SIGINT
  • trace08:向前台进程发送SIGTSTP信号
  • trace09:创建内建命令bg
  • trace10:创建内建命令fg

测试

  • trace11:测试是对前台作业组(不是前台作业)发送信号 SIGINT
  • trace12:测试是对前台作业组(不是前台作业)发送信号SIGTSTP
  • trace13:测试对进程组中的每个进程进行重启操作
  • trace14:对系统调用(fgbg)的错误返回进行处理
  • trace15:对前述功能综合测试
  • trace16:测试 shell 下的运行进程之间是否可以发送信号(SIGTSTP、SIGINT)

最终代码tsh.c

//tsh.c
/* 
 * tsh - A tiny shell program with job control
 * 
 * <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 
 */
int main(int argc, char **argv) 
{
    char c;
    char cmdline[MAXLINE];
    int emit_prompt = 1; /* emit prompt (default) */

    /* Redirect stderr to stdout (so that driver will get all output
     * on the pipe connected to stdout) */
    dup2(1, 2);

    /* Parse the command line */
    while ((c = getopt(argc, argv, "hvp")) != EOF) {
        switch (c) {
        case 'h':             /* print help message */
            usage();
	    break;
        case 'v':             /* emit additional diagnostic info */
            verbose = 1;
	    break;
        case 'p':             /* don't print a prompt */
            emit_prompt = 0;  /* handy for automatic testing */
	    break;
	default:
            usage();
	}
    }

    /* Install the signal handlers */

    /* These are the ones you will need to implement */
    Signal(SIGINT,  sigint_handler);   /* ctrl-c */
    Signal(SIGTSTP, sigtstp_handler);  /* ctrl-z */
    Signal(SIGCHLD, sigchld_handler);  /* Terminated or stopped child */

    /* This one provides a clean way to kill the shell */
    Signal(SIGQUIT, sigquit_handler); 

    /* Initialize the job list */
    initjobs(jobs);

    /* Execute the shell's read/eval loop */
    while (1) {

	/* Read command line */
	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);
	}

	/* Evaluate the command line */
	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.  
*/
void eval(char *cmdline) 
{
	int bg;
	int state;
	pid_t pid;
	char buf[MAXLINE];     /* holds modified command lime */
	char *argv[MAXARGS];   /* Argument list execve() */
	
	strcpy(buf, cmdline);
	bg = parseline(buf, argv);
	if (argv[0] == NULL) {   /* ignore empty line */
		return;
	}

	sigset_t mask_all, mask_one, prev_one;
	sigfillset(&mask_all);
	sigemptyset(&mask_one);
	sigaddset(&mask_one, SIGCHLD);
	
	if (!builtin_cmd(argv)) {
		if((pid = fork()) < 0)
			unix_error("fork error");
		
		sigprocmask(SIG_BLOCK, &mask_one, &prev_one);     //block SIGCHLD
		if (pid == 0) {  //child process
			sigprocmask(SIG_BLOCK, &prev_one, NULL);      //unblock SIGCHLD
			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(1);
			}
		}

		state = bg ? BG : FG;
		sigprocmask(SIG_BLOCK, &mask_all, NULL);     //protect addjob()
		addjob(jobs, pid, state, cmdline);
		sigprocmask(SIG_SETMASK, &prev_one, NULL);     //unblock SIGCHLD

		if (!bg) {  //foreground job
			waitfg(pid);  //wait for a foreground job go complete
		} 
		else {  //background job
			printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
		}
	}

    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) 
{
	if (!strcmp(argv[0], "quit")) 
		exit(0);   //terminate shell process
	if (!strcmp(argv[0], "jobs")) {
		listjobs(jobs);
		return 1;
	}
	if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) {
		do_bgfg(argv);
		return 1;
	}
    return 0;     /* not a builtin command */
}

/* 
 * do_bgfg - Execute the builtin bg and fg commands
 */
void do_bgfg(char **argv) 
{
	//错误处理
	int isBg = !strcmp(argv[0], "bg");
	if (argv[1] == NULL) {
		printf("%s command requires PID or %%jobid argument\n", isBg ? "bg" : "fg");
		return;
	}

	sigset_t mask_all, prev_all;
	sigfillset(&mask_all);
	struct job_t* job = NULL;
	int jid;
	pid_t pid;
	sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
	if (sscanf(argv[1], "%%%d", &jid) > 0) {       //jid  格式输入
		if ((job = getjobjid(jobs, jid)) == NULL) {
			printf("%%%d: No such job\n", jid);
			return;
		}
	} else if (sscanf(argv[1], "%d", &pid) > 0) {  //pid  格式输入
		pid_t pid = atoi(argv[1]);
		if ((job = getjobpid(jobs, pid)) == NULL) {
			printf("(%d): No such process\n", pid);
			return;
		}
	} else {  //错误格式
		printf("%s: argument must be a PID or %%jobid\n", isBg ? "bg" : "fg");
		return;
	}
	sigprocmask(SIG_SETMASK, &prev_all, NULL);

    if (isBg) {  //bg command
		if (kill(-job->pid, SIGCONT) < 0) 
			unix_error("SIGCONT error");
		job->state = BG;
		printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);

	} else {   //fg command
		if (job->state == ST) 
			if (kill(-job->pid, SIGCONT) < 0) 
				unix_error("SIGCONT error");
		job->state = FG;
		waitfg(job->pid);
	}
	return;
}

/* 
 * waitfg - Block until process pid is no longer the foreground process
 */
void waitfg(pid_t pid)
{
	sigset_t mask_empty;
	sigemptyset(&mask_empty);
	while (fgpid(jobs) > 0)              ///访问全局数据结构要阻塞信号的!!这边没有阻塞
		sigsuspend(&mask_empty);
    return;
}

/*****************
 * 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_handler 专注于处理子进程停止或终止时发出的信号SIGCHLD而对子进程的 job 进行管理
	//waitfg shell进程等待前台进程运行完毕
	int olderrno = errno;
	int statusp;
	pid_t pid;
	sigset_t mask_all, prev_all;
	sigfillset(&mask_all);
	while ((pid = waitpid(-1, &statusp, WNOHANG | WUNTRACED)) > 0) {
		// WNOHANG | WUNTRACED  立即返回, 使用此参数是为了与waitfg的功能分开
		if (WIFEXITED(statusp)) {  //子进程正常终止
			sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
			deletejob(jobs, pid);
			sigprocmask(SIG_SETMASK, &prev_all, NULL);
		} else if (WIFSIGNALED(statusp)) {   //子进程 信号退出 (因为一个未被捕获的信号终止的,eg.SIGINT)
			sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
			printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, SIGINT);
			deletejob(jobs, pid);
			sigprocmask(SIG_SETMASK, &prev_all, NULL);
		} else {  // 停止状态
			sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
			struct job_t* job = getjobpid(jobs, pid);
			job->state = ST;
			printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, SIGTSTP);
			sigprocmask(SIG_SETMASK, &prev_all, NULL);
		}
	}
	errno = olderrno;
    return;
}

/* 
 * 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) 
{
	//shell收到SIGINT(ctrl-c)并向前台子进程发送其信号
	int olderrno = errno;
	int rc;
	sigset_t mask_all, prev_all;
	sigfillset(&mask_all);
	sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
	pid_t pid = fgpid(jobs);              //访问共享数据结构时要阻塞信号
	sigprocmask(SIG_SETMASK, &prev_all, NULL);
	if (pid) {   //若pid == 0 则无前台进程
		if ((rc = kill(-pid, sig)) < 0)   //sig == SIGINT
		unix_error("SIGINT error");
	}
	errno = olderrno;
    return;
}

/*
 * 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) 
{
	//shell收到SIGTSTP(ctrl-z)并向前台子进程发送其信号
	int olderrno = errno;
	int rc;
	sigset_t mask_all, prev_all;
	sigfillset(&mask_all);
	sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
	pid_t pid = fgpid(jobs);              //访问共享数据结构时要阻塞信号
	sigprocmask(SIG_SETMASK, &prev_all, NULL);
	if (pid) {   //若pid == 0 则无前台进程
		if ((rc = kill(-pid, sig)) < 0)   //sig == SIGINT
		unix_error("SIGTSTP error");
	}
	errno = olderrno;
    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
 */
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);
}

验证结果

脚本批量生成

​ 将16个 trace 的结果全部重定向至 tsh.out 文件中。

#!/bin/bash

list="test01 test02 test03 test04 test05 test06 test07 test08 test09 test10 test11 test12 test13 test14 test15 test16"

rm tsh.out

for task in $list
do
	make $task >> tsh.out
	echo "make $task done."
done

比对*.out文件

diff tshref.out tsh.out > diff.out

​ 在 diff.out 中查看,只有进程ID不同,验证完成。

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值