shlab finished

This commit is contained in:
2025-05-20 22:09:48 +08:00
parent 421a8ac1a4
commit 1b0b28f910
33 changed files with 1933 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
# Makefile for the CS:APP Shell Lab
TEAM = NOBODY
VERSION = 1
HANDINDIR = /afs/cs/academic/class/15213-f02/L5/handin
DRIVER = ./sdriver.pl
TSH = ./tsh
TSHREF = ./tshref
TSHARGS = "-p"
CC = clang
CFLAGS = -Wall -O3
FILES = $(TSH) ./myspin ./mysplit ./mystop ./myint
all: $(FILES)
##################
# Handin your work
##################
handin:
cp tsh.c $(HANDINDIR)/$(TEAM)-$(VERSION)-tsh.c
##################
# Regression tests
##################
# Run tests using the student's shell program
test01:
$(DRIVER) -t trace01.txt -s $(TSH) -a $(TSHARGS)
test02:
$(DRIVER) -t trace02.txt -s $(TSH) -a $(TSHARGS)
test03:
$(DRIVER) -t trace03.txt -s $(TSH) -a $(TSHARGS)
test04:
$(DRIVER) -t trace04.txt -s $(TSH) -a $(TSHARGS)
test05:
$(DRIVER) -t trace05.txt -s $(TSH) -a $(TSHARGS)
test06:
$(DRIVER) -t trace06.txt -s $(TSH) -a $(TSHARGS)
test07:
$(DRIVER) -t trace07.txt -s $(TSH) -a $(TSHARGS)
test08:
$(DRIVER) -t trace08.txt -s $(TSH) -a $(TSHARGS)
test09:
$(DRIVER) -t trace09.txt -s $(TSH) -a $(TSHARGS)
test10:
$(DRIVER) -t trace10.txt -s $(TSH) -a $(TSHARGS)
test11:
$(DRIVER) -t trace11.txt -s $(TSH) -a $(TSHARGS)
test12:
$(DRIVER) -t trace12.txt -s $(TSH) -a $(TSHARGS)
test13:
$(DRIVER) -t trace13.txt -s $(TSH) -a $(TSHARGS)
test14:
$(DRIVER) -t trace14.txt -s $(TSH) -a $(TSHARGS)
test15:
$(DRIVER) -t trace15.txt -s $(TSH) -a $(TSHARGS)
test16:
$(DRIVER) -t trace16.txt -s $(TSH) -a $(TSHARGS)
# Run the tests using the reference shell program
rtest01:
$(DRIVER) -t trace01.txt -s $(TSHREF) -a $(TSHARGS)
rtest02:
$(DRIVER) -t trace02.txt -s $(TSHREF) -a $(TSHARGS)
rtest03:
$(DRIVER) -t trace03.txt -s $(TSHREF) -a $(TSHARGS)
rtest04:
$(DRIVER) -t trace04.txt -s $(TSHREF) -a $(TSHARGS)
rtest05:
$(DRIVER) -t trace05.txt -s $(TSHREF) -a $(TSHARGS)
rtest06:
$(DRIVER) -t trace06.txt -s $(TSHREF) -a $(TSHARGS)
rtest07:
$(DRIVER) -t trace07.txt -s $(TSHREF) -a $(TSHARGS)
rtest08:
$(DRIVER) -t trace08.txt -s $(TSHREF) -a $(TSHARGS)
rtest09:
$(DRIVER) -t trace09.txt -s $(TSHREF) -a $(TSHARGS)
rtest10:
$(DRIVER) -t trace10.txt -s $(TSHREF) -a $(TSHARGS)
rtest11:
$(DRIVER) -t trace11.txt -s $(TSHREF) -a $(TSHARGS)
rtest12:
$(DRIVER) -t trace12.txt -s $(TSHREF) -a $(TSHARGS)
rtest13:
$(DRIVER) -t trace13.txt -s $(TSHREF) -a $(TSHARGS)
rtest14:
$(DRIVER) -t trace14.txt -s $(TSHREF) -a $(TSHARGS)
rtest15:
$(DRIVER) -t trace15.txt -s $(TSHREF) -a $(TSHARGS)
rtest16:
$(DRIVER) -t trace16.txt -s $(TSHREF) -a $(TSHARGS)
# clean up
clean:
rm -f $(FILES) *.o *~

View File

@@ -0,0 +1,22 @@
################
CS:APP Shell Lab
################
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

BIN
shlab/shlab-handout/myint Executable file

Binary file not shown.

View File

@@ -0,0 +1,36 @@
/*
* myint.c - Another handy routine for testing your tiny shell
*
* usage: myint <n>
* Sleeps for <n> seconds and sends SIGINT to itself.
*
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
int main(int argc, char **argv)
{
int i, secs;
pid_t pid;
if (argc != 2) {
fprintf(stderr, "Usage: %s <n>\n", argv[0]);
exit(0);
}
secs = atoi(argv[1]);
for (i=0; i < secs; i++)
sleep(1);
pid = getpid();
if (kill(pid, SIGINT) < 0)
fprintf(stderr, "kill (int) error");
exit(0);
}

BIN
shlab/shlab-handout/myspin Executable file

Binary file not shown.

View File

@@ -0,0 +1,24 @@
/*
* myspin.c - A handy program for testing your tiny shell
*
* usage: myspin <n>
* Sleeps for <n> seconds in 1-second chunks.
*
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int i, secs;
if (argc != 2) {
fprintf(stderr, "Usage: %s <n>\n", argv[0]);
exit(0);
}
secs = atoi(argv[1]);
for (i=0; i < secs; i++)
sleep(1);
exit(0);
}

BIN
shlab/shlab-handout/mysplit Executable file

Binary file not shown.

View File

@@ -0,0 +1,35 @@
/*
* mysplit.c - Another handy routine for testing your tiny shell
*
* usage: mysplit <n>
* Fork a child that spins for <n> seconds in 1-second chunks.
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
int main(int argc, char **argv)
{
int i, secs;
if (argc != 2) {
fprintf(stderr, "Usage: %s <n>\n", argv[0]);
exit(0);
}
secs = atoi(argv[1]);
if (fork() == 0) { /* child */
for (i=0; i < secs; i++)
sleep(1);
exit(0);
}
/* parent waits for child to terminate */
wait(NULL);
exit(0);
}

BIN
shlab/shlab-handout/mystop Executable file

Binary file not shown.

View File

@@ -0,0 +1,36 @@
/*
* mystop.c - Another handy routine for testing your tiny shell
*
* usage: mystop <n>
* Sleeps for <n> seconds and sends SIGTSTP to itself.
*
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
int main(int argc, char **argv)
{
int i, secs;
pid_t pid;
if (argc != 2) {
fprintf(stderr, "Usage: %s <n>\n", argv[0]);
exit(0);
}
secs = atoi(argv[1]);
for (i=0; i < secs; i++)
sleep(1);
pid = getpid();
if (kill(-pid, SIGTSTP) < 0)
fprintf(stderr, "kill (tstp) error");
exit(0);
}

210
shlab/shlab-handout/sdriver.pl Executable file
View File

@@ -0,0 +1,210 @@
#!/usr/bin/perl
#!/usr/local/bin/perl
use Getopt::Std;
use FileHandle;
use IPC::Open2;
#######################################################################
# sdriver.pl - Shell driver
#
# Copyright (c) 2002, R. Bryant and D. O'Hallaron, All rights reserved.
# May not be used, modified, or copied without permission.
#
# The driver runs a student's shell program as a child, sends
# commands and signals to the child as directed by a trace file,
# and captures and displays the output produced by the child.
#
# Tracefile format:
#
# The tracefile consists of text lines that are either blank lines,
# comment lines, driver commands, or shell commands. Blank lines are
# ignored. Comment lines begin with "#" and are echo'd without change
# to stdout. Driver commands are intepreted by the driver and are not
# passed to the child shell. All other lines are shell commands and
# are passed without modification to the shell, which reads them on
# stdin. Output produced by the child on stdout/stderr is read by
# the parent and printed on its stdout.
#
# 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
#
######################################################################
#
# usage - print help message and terminate
#
sub usage
{
printf STDERR "$_[0]\n";
printf STDERR "Usage: $0 [-hv] -t <trace> -s <shellprog> -a <args>\n";
printf STDERR "Options:\n";
printf STDERR " -h Print this message\n";
printf STDERR " -v Be more verbose\n";
printf STDERR " -t <trace> Trace file\n";
printf STDERR " -s <shell> Shell program to test\n";
printf STDERR " -a <args> Shell arguments\n";
printf STDERR " -g Generate output for autograder\n";
die "\n" ;
}
# Parse the command line arguments
getopts('hgvt:s:a:');
if ($opt_h) {
usage();
}
if (!$opt_t) {
usage("Missing required -t argument");
}
if (!$opt_s) {
usage("Missing required -s argument");
}
$verbose = $opt_v;
$infile = $opt_t;
$shellprog = $opt_s;
$shellargs = $opt_a;
$grade = $opt_g;
# Make sure the input script exists and is readable
-e $infile
or die "$0: ERROR: $infile not found\n";
-r $infile
or die "$0: ERROR: $infile is not readable\n";
# Make sure the shell program exists and is executable
-e $shellprog
or die "$0: ERROR: $shellprog not found\n";
-x $shellprog
or die "$0: ERROR: $shellprog is not executable\n";
# Open the input script
open INFILE, $infile
or die "$0: ERROR: Couldn't open input file $infile: $!\n";
#
# Fork a child, run the shell in it, and connect the parent
# and child with a pair of unidirectional pipes:
# parent:Writer -> child:stdin
# child:stdout -> parent:Reader
#
$pid = open2(\*Reader, \*Writer, "$shellprog $shellargs");
Writer->autoflush();
# The autograder will want to know the child shell's pid
if ($grade) {
print ("pid=$pid\n");
}
#
# Parent reads a trace file, sends commands to the child shell.
#
while (<INFILE>) {
$line = $_;
chomp($line);
# Comment line
if ($line =~ /^#/) {
print "$line\n";
}
# Blank line
elsif ($line =~ /^\s*$/) {
if ($verbose) {
print "$0: Ignoring blank line\n";
}
}
# Send SIGTSTP (ctrl-z)
elsif ($line =~ /TSTP/) {
if ($verbose) {
print "$0: Sending SIGTSTP signal to process $pid\n";
}
kill 'TSTP', $pid;
}
# Send SIGINT (ctrl-c)
elsif ($line =~ /INT/) {
if ($verbose) {
print "$0: Sending SIGINT signal to process $pid\n";
}
kill 'INT', $pid;
}
# Send SIGQUIT (whenever we need graceful termination)
elsif ($line =~ /QUIT/) {
if ($verbose) {
print "$0: Sending SIGQUIT signal to process $pid\n";
}
kill 'QUIT', $pid;
}
# Send SIGKILL
elsif ($line =~ /KILL/) {
if ($verbose) {
print "$0: Sending SIGKILL signal to process $pid\n";
}
kill 'KILL', $pid;
}
# Close pipe (sends EOF notification to child)
elsif ($line =~ /CLOSE/) {
if ($verbose) {
print "$0: Closing output end of pipe to child $pid\n";
}
close Writer;
}
# Wait for child to terminate
elsif ($line =~ /WAIT/) {
if ($verbose) {
print "$0: Waiting for child $pid\n";
}
wait;
if ($verbose) {
print "$0: Child $pid reaped\n";
}
}
# Sleep
elsif ($line =~ /SLEEP (\d+)/) {
if ($verbose) {
print "$0: Sleeping $1 secs\n";
}
sleep $1;
}
# Unknown input
else {
if ($verbose) {
print "$0: Sending :$line: to child $pid\n";
}
print Writer "$line\n";
}
}
#
# Parent echoes the output produced by the child.
#
close Writer;
if ($verbose) {
print "$0: Reading data from child $pid\n";
}
while ($line = <Reader>) {
print $line;
}
close Reader;
# Finally, parent reaps child
wait;
if ($verbose) {
print "$0: Shell terminated\n";
}
exit;

View File

@@ -0,0 +1,5 @@
#
# trace01.txt - Properly terminate on EOF.
#
CLOSE
WAIT

View File

@@ -0,0 +1,5 @@
#
# trace02.txt - Process builtin quit command.
#
quit
WAIT

View File

@@ -0,0 +1,5 @@
#
# trace03.txt - Run a foreground job.
#
/bin/echo tsh> quit
quit

View File

@@ -0,0 +1,5 @@
#
# trace04.txt - Run a background job.
#
/bin/echo -e tsh> ./myspin 1 \046
./myspin 1 &

View File

@@ -0,0 +1,11 @@
#
# 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

View File

@@ -0,0 +1,8 @@
#
# trace06.txt - Forward SIGINT to foreground job.
#
/bin/echo -e tsh> ./myspin 4
./myspin 4
SLEEP 2
INT

View File

@@ -0,0 +1,14 @@
#
# 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

View File

@@ -0,0 +1,14 @@
#
# trace08.txt - Forward SIGTSTP only to foreground job.
#
/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

View File

@@ -0,0 +1,20 @@
#
# 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

View File

@@ -0,0 +1,22 @@
#
# 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

View File

@@ -0,0 +1,12 @@
#
# 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

View File

@@ -0,0 +1,17 @@
#
# 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

View File

@@ -0,0 +1,23 @@
#
# 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

View File

@@ -0,0 +1,47 @@
#
# 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

View File

@@ -0,0 +1,46 @@
#
# 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

View File

@@ -0,0 +1,16 @@
#
# 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

BIN
shlab/shlab-handout/tsh Executable file

Binary file not shown.

912
shlab/shlab-handout/tsh.c Normal file
View File

@@ -0,0 +1,912 @@
/*
* tsh - 一个支持作业控制的微型shell程序
*
* 功能特性:
* - 支持内置命令jobs, fg, bg
* - 支持前台/后台作业控制
* - 支持作业状态管理(运行中/停止)
* - 处理Ctrl-C(中断)、Ctrl-Z(停止)等信号
* - 支持最多16个并发作业
*
* 主要数据结构:
* - job_t: 表示一个作业包含PID、作业ID、状态和命令行
* - jobs数组: 保存所有作业的列表
*
* 作业状态说明:
* - UNDEF: 未定义
* - FG: 前台运行
* - BG: 后台运行
* - ST: 已停止
*
* ChengJingyu 202302723005
*/
/* 最大命令行长度 */
#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 */
/* 最大作业ID值 */
#define MAXJID 1 << 16 /* max job ID */
/* 作业状态常量定义 */
#define UNDEF 0 /* 未定义状态 */
#define FG 1 /* 前台运行状态 */
#define BG 2 /* 后台运行状态 */
#define ST 3 /* 停止状态 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// #include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.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.
*/
/* 全局变量定义 */
extern char **environ; /* 环境变量数组定义在libc中 */
char prompt[] = "tsh> "; /* 命令行提示符(请勿修改) */
int verbose = 0; /* 调试模式标志,为真时打印额外信息 */
int nextjid = 1; /* 下一个要分配的作业ID */
char sbuf[MAXLINE]; /* 用于构造sprintf消息的缓冲区 */
/* 作业结构体定义 */
struct job_t {
pid_t pid; /* 进程ID */
int jid; /* 作业ID [1, 2, ...] */
int state; /* 状态: UNDEF(未定义), BG(后台), FG(前台), ST(停止) */
char cmdline[MAXLINE]; /* 命令行字符串 */
};
struct job_t jobs[MAXJOBS]; /* 作业列表数组 */
/* 全局变量定义结束 */
/* 函数原型声明 */
/* 需要实现的核心功能函数 */
void eval(char *cmdline); /* 评估并执行命令行 */
int builtin_cmd(char **argv); /* 执行内置命令 */
void do_bgfg(char **argv); /* 处理fg/bg命令 */
void waitfg(pid_t pid); /* 等待前台作业完成 */
/* 信号处理函数 */
void sigchld_handler(int sig); /* 子进程状态改变处理 */
void sigtstp_handler(int sig); /* Ctrl-Z处理 */
void sigint_handler(int sig); /* Ctrl-C处理 */
/* 提供的辅助函数 */
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); /* 获取最大作业ID */
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); /* 获取前台作业PID */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid); /* 通过PID获取作业 */
struct job_t *getjobjid(struct job_t *jobs, int jid); /* 通过JID获取作业 */
int pid2jid(pid_t pid); /* PID转JID */
void listjobs(struct job_t *jobs); /* 列出所有作业 */
void listjob(struct job_t *job); /* 列出单个作业 */
/* 工具函数 */
void usage(void); /* 打印用法信息 */
void unix_error(char *msg); /* Unix风格错误处理 */
void app_error(char *msg); /* 应用风格错误处理 */
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler); /* 信号处理包装函数 */
/*
* main - shell的主循环函数
*
* 功能:
* 1. 解析命令行参数
* 2. 安装信号处理程序
* 3. 初始化作业列表
* 4. 进入读取-评估循环
*
* 参数:
* argc - 参数个数
* argv - 参数数组
*
* 返回值:
* 正常情况不会返回若返回则退出码为0
*/
int main(int argc, char **argv) {
char c;
char cmdline[MAXLINE];
int emit_prompt = 1; /* 是否显示提示符默认为1(显示) */
/* 将标准错误重定向到标准输出,确保所有输出都通过管道传递 */
dup2(1, 2);
/* 解析命令行参数 */
while ((c = getopt(argc, argv, "hvp")) != EOF) {
switch (c) {
case 'h': /* 打印帮助信息 */
usage();
break;
case 'v': /* 启用详细输出模式 */
verbose = 1;
break;
case 'p': /* 不显示提示符(用于自动化测试) */
emit_prompt = 0;
break;
default:
usage();
}
}
/* 安装信号处理程序 */
/* 需要实现的信号处理函数 */
Signal(SIGINT, sigint_handler); /* 处理Ctrl-C中断信号 */
Signal(SIGTSTP, sigtstp_handler); /* 处理Ctrl-Z停止信号 */
Signal(SIGCHLD, sigchld_handler); /* 处理子进程状态变化 */
/* 提供一个干净终止shell的方式 */
Signal(SIGQUIT, sigquit_handler);
/* 初始化作业列表 */
initjobs(jobs);
/* shell主循环读取-评估-执行 */
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)) /* 处理文件结束(Ctrl-D) */
{
fflush(stdout);
exit(0);
}
/* 评估并执行命令行 */
eval(cmdline);
fflush(stdout);
fflush(stdout);
}
exit(0); /* 控制流不会到达这里 */
}
/*
* eval - 评估并执行用户输入的命令行
*
* 功能:
* 1. 如果是内置命令(quit/jobs/bg/fg),直接执行
* 2. 否则fork子进程执行命令
* 3. 如果是前台作业,等待其完成
* 4. 设置独立的进程组,防止后台作业接收终端信号
*
* 参数:
* cmdline - 用户输入的命令行字符串
*/
void eval(char *cmdline) {
char *argv[MAXARGS]; /* execve()的参数列表 */
char buf[MAXLINE]; /* 存储修改后的命令行 */
int bg; /* 作业是否应该在后台运行 */
pid_t pid; /* 子进程ID */
sigset_t mask_all, mask_one, pre;
/* 初始化信号屏蔽集合 */
sigfillset(&mask_all); /* 屏蔽所有信号 */
sigemptyset(&mask_one); /* 初始化空信号集 */
sigaddset(&mask_one, SIGCHLD); /* 添加SIGCHLD信号 */
/* 解析命令行 */
strcpy(buf, cmdline);
bg = parseline(buf, argv); /* 解析命令行并判断是否后台运行 */
if (argv[0] == NULL) /* 忽略空行 */
return;
/* 处理非内置命令 */
if (!builtin_cmd(argv)) {
/* 阻塞SIGCHLD信号防止addjob前收到SIGCHLD */
sigprocmask(SIG_BLOCK, &mask_one, &pre);
/* 创建子进程执行命令 */
if ((pid = fork()) == 0) { /* 子进程执行用户作业 */
setpgid(0, 0); /* 设置新的进程组,防止后台作业接收终端信号 */
sigprocmask(SIG_SETMASK, &pre, NULL); /* 恢复信号屏蔽 */
/* 执行命令 */
if (execve(argv[0], argv, environ) < 0) {
printf("%s: Command not found.\n", argv[0]);
exit(0);
}
}
/* 父进程添加作业到作业列表 */
addjob(jobs, pid, bg ? BG : FG, cmdline); /* 添加作业到作业列表 */
sigprocmask(SIG_SETMASK, &pre, NULL); /* 恢复信号屏蔽 */
/* 处理前台/后台作业 */
if (!bg) {
waitfg(pid); /* 等待前台作业完成 */
} else {
listjob(getjobpid(jobs, pid)); /* 列出后台作业信息 */
}
}
return;
}
/*
* parseline - 解析命令行并构建argv数组
*
* 功能:
* 1. 解析命令行参数,处理单引号内的内容作为一个参数
* 2. 判断是否为后台作业(&结尾)
*
* 参数:
* cmdline - 输入的命令行字符串
* argv - 输出的参数数组
*
* 返回值:
* 如果是后台作业返回1否则返回0
*/
int parseline(const char *cmdline, char **argv) {
static char array[MAXLINE]; /* 存储命令行副本 */
char *buf = array; /* 遍历命令行的指针 */
char *delim; /* 参数分隔符位置 */
int argc; /* 参数个数 */
int bg; /* 是否为后台作业 */
/* 初始化处理 */
strcpy(buf, cmdline);
buf[strlen(buf) - 1] = ' '; /* 替换末尾的换行符为空格 */
while (*buf && (*buf == ' ')) /* 跳过前导空格 */
buf++;
/* 构建argv参数列表 */
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 == ' ')) /* 跳过空格 */
buf++;
/* 查找下一个参数分隔符 */
if (*buf == '\'') {
buf++;
delim = strchr(buf, '\'');
} else {
delim = strchr(buf, ' ');
}
}
argv[argc] = NULL; /* 参数列表以NULL结尾 */
if (argc == 0) /* 忽略空行 */
return 1;
/* 检查是否为后台作业 */
if ((bg = (*argv[argc - 1] == '&')) != 0) {
argv[--argc] = NULL; /* 移除&符号 */
}
return bg;
}
/*
* builtin_cmd - 执行内置命令
*
* 功能:
* 1. 处理quit命令: 退出shell
* 2. 处理jobs命令: 列出所有作业
* 3. 处理fg/bg命令: 转发给do_bgfg处理
* 4. 忽略单独的&符号
*
* 参数:
* argv - 命令参数数组
*
* 返回值:
* 如果是内置命令返回1否则返回0
*/
int builtin_cmd(char **argv) {
if (!strcmp(argv[0], "quit")) /* 退出命令 */
exit(0);
if (!strcmp(argv[0], "&")) /* 忽略单独的& */
return 1;
if (!strcmp(argv[0], "jobs")) { /* 列出作业 */
listjobs(jobs);
return 1;
}
if (!strcmp(argv[0], "fg") || !strcmp(argv[0], "bg")) { /* 前台/后台命令 */
do_bgfg(argv);
return 1;
}
return 0; /* 不是内置命令 */
}
/*
* do_bgfg - 执行bg和fg内置命令
*
* 功能:
* 1. 解析PID或%JID参数
* 2. 将作业转为前台或后台运行
* 3. 发送CONT信号继续作业
*
* 参数:
* argv - 命令参数数组
*/
void do_bgfg(char **argv) {
struct job_t *job;
char *name = argv[0]; /* 命令名(fg/bg) */
char *num = argv[1]; /* PID或%JID参数 */
/* 参数验证 */
if (num == NULL) {
printf("%s command requires PID or %%jobid argument\n", name);
return;
}
/* 处理%JID格式 */
if (*num == '%') {
num = num + 1; /* 跳过% */
if (strspn(num, "0123456789") != strlen(num)) { /* 检查数字 */
printf("%s: argument must be a PID or %%jobid\n", name);
return;
}
job = getjobjid(jobs, atoi(num)); /* 通过JID获取作业 */
if (job == NULL) {
printf("%%%s: No such job\n", num);
return;
}
}
/* 处理PID格式 */
else {
if (strspn(num, "0123456789") != strlen(num)) { /* 检查数字 */
printf("%s: argument must be a PID or %%jobid\n", name);
return;
}
job = getjobpid(jobs, (pid_t)atoi(num)); /* 通过PID获取作业 */
if (job == NULL) {
printf("(%s): No such process\n", num);
return;
}
}
/* 执行fg/bg命令 */
if (!strcmp(argv[0], "fg")) { /* 前台命令 */
kill(job->pid, SIGCONT); /* 发送继续信号 */
job->state = FG; /* 设为前台作业 */
waitfg(job->pid); /* 等待前台作业完成 */
} else if (!strcmp(argv[0], "bg")) { /* 后台命令 */
kill(job->pid, SIGCONT); /* 发送继续信号 */
job->state = BG; /* 设为后台作业 */
listjob(job); /* 列出作业信息 */
}
}
/*
* waitfg - 等待前台作业完成
*
* 功能:
* 1. 阻塞当前进程,直到指定的前台作业完成
* 2. 使用sigsuspend安全地等待信号避免竞争条件
* 3. 处理SIGCHLD信号确保前台作业结束时能及时检测到
*
* 参数:
* pid - 要等待的前台作业的进程ID
*
* 实现说明:
* 1. 使用信号屏蔽确保原子性操作
* 2. 通过检查作业列表中的前台作业状态来判断是否继续等待
* 3. 使用sigsuspend替代pause避免竞争条件
*/
void waitfg(pid_t pid) {
/*
* 示例说明(trace06.txt):
* ./myspin 4 # 子进程前台执行主进程进入pause
* INT #
* (1)
* CTRL_C发送INT信号给主进程中断pause状态,进入INT处理函数,
* 该函数把INT信号转发给前台子进程处理完成后返回上次中断的位置(2)。
* (2)
* INT中断处理函数返回后继续执行下一条指令,
* 发现while循环条件依然成立又进入pause.
* (3)
* 前台子进程被INT信号终止结束生命发送CHLD信号给主进程
* 中断pause状态进入CHLD处理函数
* 该函数回收僵尸进程删除对应的job,处理完成后返回上次中断的位置。
* (4)
* CHLD中断返回后继续执行下一条指令
* 发现while循环条件不再成立跳出while循环。
*
* 注意:如果(3)的中断位置在while条件执行后pause执行前
* 那么使用pause会导致中断处理函数返回后一直pause
* 而sigsuspend可以避免这种情况。
*/
sigset_t mask, prev;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD); /* 屏蔽SIGCHLD信号 */
sigprocmask(SIG_BLOCK, &mask, &prev); /* 设置信号屏蔽 */
/* 循环等待前台作业完成 */
while (fgpid(jobs)) {
if (verbose)
printf("%ld pausing...\n", time(NULL));
sigsuspend(&prev); /* 原子地解除屏蔽并等待信号 */
if (verbose)
printf("%ld interrupt return\n", time(NULL));
}
sigprocmask(SIG_SETMASK, &prev, NULL); /* 恢复原始信号屏蔽 */
}
/*****************
* Signal handlers
*****************/
/*
* sigchld_handler - 处理子进程状态变化的信号处理函数
*
* 功能:
* 1. 当子进程终止(成为僵尸进程)或停止时内核会发送SIGCHLD信号
* 2. 回收所有可用的僵尸子进程
* 3. 不等待其他正在运行的子进程终止
*
* 参数:
* sig - 信号编号
*/
void sigchld_handler(int sig) {
if (verbose)
printf("%ld interrupt by SIGCHLD(%d)\n", time(NULL), sig);
int olderrno = errno;
pid_t pid;
int status;
/* 循环处理所有终止或停止的子进程 */
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED | WCONTINUED)) > 0) {
if (WIFEXITED(status)) { /* 子进程正常退出 */
deletejob(jobs, pid); /* 从作业列表中删除作业 */
} else if (WIFSIGNALED(status)) { /* 子进程被信号终止 */
printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid,
WTERMSIG(status));
deletejob(jobs, pid); /* 从作业列表中删除作业 */
} else if (WIFSTOPPED(status)) { /* 子进程被信号停止 */
printf("Job [%d] (%d) stopted by signal %d\n", pid2jid(pid), pid,
WSTOPSIG(status));
getjobpid(jobs, pid)->state = ST; /* 更新作业状态为停止 */
} else if (WIFCONTINUED(status)) { /* 子进程被继续 */
/* 目前不做特殊处理 */
}
}
/* 错误处理 */
if (pid == -1 && errno != ECHILD) {
unix_error("sigchld_handler error");
}
errno = olderrno; /* 恢复errno */
}
/*
* sigint_handler - 处理Ctrl-C中断信号的信号处理函数
*
* 功能:
* 1. 当用户在键盘输入Ctrl-C时内核会发送SIGINT信号
* 2. 将信号转发给前台作业
*
* 参数:
* sig - 信号编号
*/
void sigint_handler(int sig) {
if (verbose)
printf("%ld interrupt by SIGINT(%d)\n", time(NULL), sig);
int olderrno = errno;
pid_t pid = fgpid(jobs); /* 获取前台作业的PID */
if (kill(pid, SIGINT) == -1) { /* 向前台作业发送SIGINT信号 */
unix_error("sigint_handler");
}
errno = olderrno; /* 恢复errno */
}
/*
* sigtstp_handler - 处理Ctrl-Z停止信号的信号处理函数
*
* 功能:
* 1. 当用户在键盘输入Ctrl-Z时内核会发送SIGTSTP信号
* 2. 将信号转发给前台作业使其停止
*
* 参数:
* sig - 信号编号
*/
void sigtstp_handler(int sig) {
if (verbose) {
printf("%ld interrupt by SIGSTP(%d)\n", time(NULL), sig);
}
int olderrno = errno;
pid_t pid = fgpid(jobs); /* 获取前台作业的PID */
if (kill(pid, SIGTSTP) == -1) /* 向前台作业发送SIGTSTP信号 */
unix_error("sigtstp_handler");
errno = olderrno; /* 恢复errno */
}
/*********************
* 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 - 添加作业到作业列表
*
* 功能:
* 1. 将新作业添加到第一个空闲位置
* 2. 分配作业ID并自动递增
* 3. 设置作业状态和命令行
*
* 参数:
* jobs - 作业列表数组
* pid - 进程ID
* state - 作业状态(UNDEF/FG/BG/ST)
* cmdline - 命令行字符串
*
* 返回值:
* 成功返回1失败返回0
*/
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) {
int i;
if (pid < 1) /* 检查PID有效性 */
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++; /* 分配作业ID并递增 */
if (nextjid > MAXJOBS) /* 作业ID循环使用 */
nextjid = 1;
strcpy(jobs[i].cmdline, cmdline); /* 保存命令行 */
if (verbose) { /* 调试输出 */
printf("%ld Added job [%d] %d %s\n", time(NULL), jobs[i].jid,
jobs[i].pid, jobs[i].cmdline);
}
return 1; /* 添加成功 */
}
}
printf("Tried to create too many jobs\n"); /* 作业列表已满 */
return 0;
}
/*
* deletejob - 从作业列表删除指定PID的作业
*
* 功能:
* 1. 根据PID查找并删除作业
* 2. 更新下一个可用的作业ID
*
* 参数:
* jobs - 作业列表数组
* pid - 要删除的进程ID
*
* 返回值:
* 成功返回1失败返回0
*/
int deletejob(struct job_t *jobs, pid_t pid) {
int i;
if (pid < 1) /* 检查PID有效性 */
return 0;
/* 查找并删除作业 */
for (i = 0; i < MAXJOBS; i++) {
if (jobs[i].pid == pid) { /* 找到匹配的作业 */
if (verbose) { /* 调试输出 */
printf("%ld deleted job [%d] %d %s\n", time(NULL), jobs[i].jid,
jobs[i].pid, jobs[i].cmdline);
}
clearjob(&jobs[i]); /* 清除作业条目 */
nextjid = maxjid(jobs) + 1; /* 更新下一个可用的作业ID */
return 1; /* 删除成功 */
}
}
return 0; /* 未找到匹配的作业 */
}
/*
* fgpid - 获取当前前台作业的PID
*
* 功能:
* 遍历作业列表查找状态为FG的作业
*
* 参数:
* jobs - 作业列表数组
*
* 返回值:
* 找到返回PID否则返回0
*/
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 - 通过PID查找作业
*
* 功能:
* 在作业列表中查找指定PID的作业
*
* 参数:
* jobs - 作业列表数组
* pid - 要查找的进程ID
*
* 返回值:
* 找到返回作业指针否则返回NULL
*/
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 - 通过JID查找作业
*
* 功能:
* 在作业列表中查找指定JID的作业
*
* 参数:
* jobs - 作业列表数组
* jid - 要查找的作业ID
*
* 返回值:
* 找到返回作业指针否则返回NULL
*/
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 - 将PID转换为JID
*
* 功能:
* 查找指定PID对应的作业ID
*
* 参数:
* pid - 进程ID
*
* 返回值:
* 找到返回JID否则返回0
*/
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 - 打印所有作业信息
*
* 功能:
* 遍历作业列表,打印所有非空作业的信息
* 包括JID、PID、状态和命令行
*
* 参数:
* jobs - 作业列表数组
*/
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);
}
}
}
/*
* listjob - 打印单个作业信息
*
* 功能:
* 打印指定作业的信息
* 包括JID、PID和命令行
*
* 参数:
* job - 要打印的作业指针
*/
void listjob(struct job_t *job) {
if (job->pid != 0) {
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
}
}
/******************************
* end job list helper routines
******************************/
/***********************
* Other helper routines
***********************/
/*
* usage - 打印帮助信息
*
* 功能:
* 打印shell的使用说明和命令行选项
*/
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风格错误处理
*
* 功能:
* 打印Unix系统调用错误信息并退出程序
*
* 参数:
* msg - 自定义错误消息
*/
void unix_error(char *msg) {
fprintf(stdout, "%s: %s\n", msg, strerror(errno));
exit(1);
}
/*
* app_error - 应用风格错误处理
*
* 功能:
* 打印应用程序错误信息并退出程序
*
* 参数:
* msg - 错误消息
*/
void app_error(char *msg) {
fprintf(stdout, "%s\n", msg);
exit(1);
}
/*
* Signal - 信号处理包装函数
*
* 功能:
* 1. 为指定信号安装信号处理程序
* 2. 设置SA_RESTART标志使被信号中断的系统调用自动重启
* 3. 返回旧的处理程序
*
* 参数:
* signum - 信号编号
* handler - 信号处理函数
*
* 返回值:
* 旧的信号处理程序
*/
handler_t *Signal(int signum, handler_t *handler) {
struct sigaction action, old_action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask); /* 屏蔽正在处理的信号类型 */
action.sa_flags = SA_RESTART; /* 如果可能,重启被中断的系统调用 */
if (sigaction(signum, &action, &old_action) < 0)
unix_error("Signal error");
return (old_action.sa_handler);
}
/*
* sigquit_handler - SIGQUIT信号处理函数
*
* 功能:
* 1. 处理SIGQUIT信号优雅终止shell
* 2. 打印终止信息并退出
*
* 参数:
* sig - 信号编号
*/
void sigquit_handler(int sig) {
printf("Terminating after receipt of SIGQUIT signal\n");
exit(1);
}

BIN
shlab/shlab-handout/tshref Executable file

Binary file not shown.

View File

@@ -0,0 +1,220 @@
make[1]: Entering directory `/afs/cs.cmu.edu/project/ics/im/labs/shlab/src'
./sdriver.pl -t trace01.txt -s ./tsh -a "-p"
#
# trace01.txt - Properly terminate on EOF.
#
./sdriver.pl -t trace02.txt -s ./tsh -a "-p"
#
# trace02.txt - Process builtin quit command.
#
./sdriver.pl -t trace03.txt -s ./tsh -a "-p"
#
# trace03.txt - Run a foreground job.
#
tsh> quit
./sdriver.pl -t trace04.txt -s ./tsh -a "-p"
#
# trace04.txt - Run a background job.
#
tsh> ./myspin 1 &
[1] (26252) ./myspin 1 &
./sdriver.pl -t trace05.txt -s ./tsh -a "-p"
#
# trace05.txt - Process jobs builtin command.
#
tsh> ./myspin 2 &
[1] (26256) ./myspin 2 &
tsh> ./myspin 3 &
[2] (26258) ./myspin 3 &
tsh> jobs
[1] (26256) Running ./myspin 2 &
[2] (26258) Running ./myspin 3 &
./sdriver.pl -t trace06.txt -s ./tsh -a "-p"
#
# trace06.txt - Forward SIGINT to foreground job.
#
tsh> ./myspin 4
Job [1] (26263) terminated by signal 2
./sdriver.pl -t trace07.txt -s ./tsh -a "-p"
#
# trace07.txt - Forward SIGINT only to foreground job.
#
tsh> ./myspin 4 &
[1] (26267) ./myspin 4 &
tsh> ./myspin 5
Job [2] (26269) terminated by signal 2
tsh> jobs
[1] (26267) Running ./myspin 4 &
./sdriver.pl -t trace08.txt -s ./tsh -a "-p"
#
# trace08.txt - Forward SIGTSTP only to foreground job.
#
tsh> ./myspin 4 &
[1] (26274) ./myspin 4 &
tsh> ./myspin 5
Job [2] (26276) stopped by signal 20
tsh> jobs
[1] (26274) Running ./myspin 4 &
[2] (26276) Stopped ./myspin 5
./sdriver.pl -t trace09.txt -s ./tsh -a "-p"
#
# trace09.txt - Process bg builtin command
#
tsh> ./myspin 4 &
[1] (26281) ./myspin 4 &
tsh> ./myspin 5
Job [2] (26283) stopped by signal 20
tsh> jobs
[1] (26281) Running ./myspin 4 &
[2] (26283) Stopped ./myspin 5
tsh> bg %2
[2] (26283) ./myspin 5
tsh> jobs
[1] (26281) Running ./myspin 4 &
[2] (26283) Running ./myspin 5
./sdriver.pl -t trace10.txt -s ./tsh -a "-p"
#
# trace10.txt - Process fg builtin command.
#
tsh> ./myspin 4 &
[1] (26290) ./myspin 4 &
tsh> fg %1
Job [1] (26290) stopped by signal 20
tsh> jobs
[1] (26290) Stopped ./myspin 4 &
tsh> fg %1
tsh> jobs
./sdriver.pl -t trace11.txt -s ./tsh -a "-p"
#
# trace11.txt - Forward SIGINT to every process in foreground process group
#
tsh> ./mysplit 4
Job [1] (26298) terminated by signal 2
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
25181 pts/3 S 0:00 -usr/local/bin/tcsh -i
26239 pts/3 S 0:00 make tshrefout
26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1
26241 pts/3 S 0:00 make tests
26295 pts/3 S 0:00 perl ./sdriver.pl -t trace11.txt -s ./tsh -a -p
26296 pts/3 S 0:00 ./tsh -p
26301 pts/3 R 0:00 /bin/ps a
./sdriver.pl -t trace12.txt -s ./tsh -a "-p"
#
# trace12.txt - Forward SIGTSTP to every process in foreground process group
#
tsh> ./mysplit 4
Job [1] (26305) stopped by signal 20
tsh> jobs
[1] (26305) Stopped ./mysplit 4
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
25181 pts/3 S 0:00 -usr/local/bin/tcsh -i
26239 pts/3 S 0:00 make tshrefout
26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1
26241 pts/3 S 0:00 make tests
26302 pts/3 S 0:00 perl ./sdriver.pl -t trace12.txt -s ./tsh -a -p
26303 pts/3 S 0:00 ./tsh -p
26305 pts/3 T 0:00 ./mysplit 4
26306 pts/3 T 0:00 ./mysplit 4
26309 pts/3 R 0:00 /bin/ps a
./sdriver.pl -t trace13.txt -s ./tsh -a "-p"
#
# trace13.txt - Restart every stopped process in process group
#
tsh> ./mysplit 4
Job [1] (26313) stopped by signal 20
tsh> jobs
[1] (26313) Stopped ./mysplit 4
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
25181 pts/3 S 0:00 -usr/local/bin/tcsh -i
26239 pts/3 S 0:00 make tshrefout
26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1
26241 pts/3 S 0:00 make tests
26310 pts/3 S 0:00 perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
26311 pts/3 S 0:00 ./tsh -p
26313 pts/3 T 0:00 ./mysplit 4
26314 pts/3 T 0:00 ./mysplit 4
26317 pts/3 R 0:00 /bin/ps a
tsh> fg %1
tsh> /bin/ps a
PID TTY STAT TIME COMMAND
25181 pts/3 S 0:00 -usr/local/bin/tcsh -i
26239 pts/3 S 0:00 make tshrefout
26240 pts/3 S 0:00 /bin/sh -c make tests > tshref.out 2>&1
26241 pts/3 S 0:00 make tests
26310 pts/3 S 0:00 perl ./sdriver.pl -t trace13.txt -s ./tsh -a -p
26311 pts/3 S 0:00 ./tsh -p
26320 pts/3 R 0:00 /bin/ps a
./sdriver.pl -t trace14.txt -s ./tsh -a "-p"
#
# trace14.txt - Simple error handling
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 4 &
[1] (26326) ./myspin 4 &
tsh> fg
fg command requires PID or %jobid argument
tsh> bg
bg command requires PID or %jobid argument
tsh> fg a
fg: argument must be a PID or %jobid
tsh> bg a
bg: argument must be a PID or %jobid
tsh> fg 9999999
(9999999): No such process
tsh> bg 9999999
(9999999): No such process
tsh> fg %2
%2: No such job
tsh> fg %1
Job [1] (26326) stopped by signal 20
tsh> bg %2
%2: No such job
tsh> bg %1
[1] (26326) ./myspin 4 &
tsh> jobs
[1] (26326) Running ./myspin 4 &
./sdriver.pl -t trace15.txt -s ./tsh -a "-p"
#
# trace15.txt - Putting it all together
#
tsh> ./bogus
./bogus: Command not found
tsh> ./myspin 10
Job [1] (26343) terminated by signal 2
tsh> ./myspin 3 &
[1] (26345) ./myspin 3 &
tsh> ./myspin 4 &
[2] (26347) ./myspin 4 &
tsh> jobs
[1] (26345) Running ./myspin 3 &
[2] (26347) Running ./myspin 4 &
tsh> fg %1
Job [1] (26345) stopped by signal 20
tsh> jobs
[1] (26345) Stopped ./myspin 3 &
[2] (26347) Running ./myspin 4 &
tsh> bg %3
%3: No such job
tsh> bg %1
[1] (26345) ./myspin 3 &
tsh> jobs
[1] (26345) Running ./myspin 3 &
[2] (26347) Running ./myspin 4 &
tsh> fg %1
tsh> quit
./sdriver.pl -t trace16.txt -s ./tsh -a "-p"
#
# 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] (26359) stopped by signal 20
tsh> jobs
[1] (26359) Stopped ./mystop 2
tsh> ./myint 2
Job [2] (26362) terminated by signal 2
make[1]: Leaving directory `/afs/cs.cmu.edu/project/ics/im/labs/shlab/src'