shlab finished
This commit is contained in:
68
shlab/README-shlab
Normal file
68
shlab/README-shlab
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#####################################################################
|
||||||
|
# CS:APP Shell Lab
|
||||||
|
# Directions to Instructors
|
||||||
|
#
|
||||||
|
# Copyright (c) 2002, R. Bryant and D. O'Hallaron, All rights reserved.
|
||||||
|
# May not be used, modified, or copied without permission.
|
||||||
|
#
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
This directory contains the files that you will need to run the CS:APP
|
||||||
|
shell lab. The lab is the student's first experience with systems-level
|
||||||
|
programming, and teaches them about processes, process control, and
|
||||||
|
signals.
|
||||||
|
|
||||||
|
************
|
||||||
|
1. Overview
|
||||||
|
************
|
||||||
|
|
||||||
|
In this lab, students build a simple Unix shell with job control.
|
||||||
|
They are given a skeleton called tsh.c (Tiny Shell) that implements
|
||||||
|
some of the less interesting functions, and are then asked to
|
||||||
|
implement the functions and signal handlers that provide job control.
|
||||||
|
|
||||||
|
Students evaluate the functionality of their shells using a
|
||||||
|
trace-driven driver program called sdriver.pl.
|
||||||
|
|
||||||
|
The reference solution is in ./src/tsh.c
|
||||||
|
|
||||||
|
********
|
||||||
|
2. Files
|
||||||
|
********
|
||||||
|
|
||||||
|
README
|
||||||
|
grade/ Autograding scripts
|
||||||
|
shlab-handout/ Handout directory that is given to the students
|
||||||
|
src/ Trace-driven driver program
|
||||||
|
writeup/ Sample Latex lab writeup
|
||||||
|
|
||||||
|
*******************
|
||||||
|
3. Building the Lab
|
||||||
|
*******************
|
||||||
|
|
||||||
|
To build the lab, modify the Latex lab writeup in ./writeup/shlab.tex
|
||||||
|
for your environment. Then type the following in the current
|
||||||
|
directory:
|
||||||
|
|
||||||
|
unix> make clean
|
||||||
|
unix> make
|
||||||
|
|
||||||
|
The Makefile generates the driver code, formats the lab writeup, and
|
||||||
|
then copies the driver code to the shlab-handout directory. Finally,
|
||||||
|
it builds a tarfile of the shlab-handout directory (in
|
||||||
|
shlab-handout.tar) which you can distribute to students. The command:
|
||||||
|
|
||||||
|
unix> make dist DEST=<DIR>
|
||||||
|
|
||||||
|
will copy the tarfile and copies of the writeup to directory <DIR>,
|
||||||
|
where the students can access it.
|
||||||
|
|
||||||
|
|
||||||
|
**********************
|
||||||
|
4. Autograding the Lab
|
||||||
|
**********************
|
||||||
|
|
||||||
|
There is an autograding script that automatically grades the lab. See
|
||||||
|
./grade/README for instructions.
|
||||||
|
|
||||||
|
|
||||||
100
shlab/shlab-handout/Makefile
Normal file
100
shlab/shlab-handout/Makefile
Normal 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 *~
|
||||||
|
|
||||||
|
|
||||||
22
shlab/shlab-handout/README
Normal file
22
shlab/shlab-handout/README
Normal 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
BIN
shlab/shlab-handout/myint
Executable file
Binary file not shown.
36
shlab/shlab-handout/myint.c
Normal file
36
shlab/shlab-handout/myint.c
Normal 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
BIN
shlab/shlab-handout/myspin
Executable file
Binary file not shown.
24
shlab/shlab-handout/myspin.c
Normal file
24
shlab/shlab-handout/myspin.c
Normal 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
BIN
shlab/shlab-handout/mysplit
Executable file
Binary file not shown.
35
shlab/shlab-handout/mysplit.c
Normal file
35
shlab/shlab-handout/mysplit.c
Normal 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
BIN
shlab/shlab-handout/mystop
Executable file
Binary file not shown.
36
shlab/shlab-handout/mystop.c
Normal file
36
shlab/shlab-handout/mystop.c
Normal 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
210
shlab/shlab-handout/sdriver.pl
Executable 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;
|
||||||
5
shlab/shlab-handout/trace01.txt
Normal file
5
shlab/shlab-handout/trace01.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
# trace01.txt - Properly terminate on EOF.
|
||||||
|
#
|
||||||
|
CLOSE
|
||||||
|
WAIT
|
||||||
5
shlab/shlab-handout/trace02.txt
Normal file
5
shlab/shlab-handout/trace02.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
# trace02.txt - Process builtin quit command.
|
||||||
|
#
|
||||||
|
quit
|
||||||
|
WAIT
|
||||||
5
shlab/shlab-handout/trace03.txt
Normal file
5
shlab/shlab-handout/trace03.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
# trace03.txt - Run a foreground job.
|
||||||
|
#
|
||||||
|
/bin/echo tsh> quit
|
||||||
|
quit
|
||||||
5
shlab/shlab-handout/trace04.txt
Normal file
5
shlab/shlab-handout/trace04.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#
|
||||||
|
# trace04.txt - Run a background job.
|
||||||
|
#
|
||||||
|
/bin/echo -e tsh> ./myspin 1 \046
|
||||||
|
./myspin 1 &
|
||||||
11
shlab/shlab-handout/trace05.txt
Normal file
11
shlab/shlab-handout/trace05.txt
Normal 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
|
||||||
8
shlab/shlab-handout/trace06.txt
Normal file
8
shlab/shlab-handout/trace06.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#
|
||||||
|
# trace06.txt - Forward SIGINT to foreground job.
|
||||||
|
#
|
||||||
|
/bin/echo -e tsh> ./myspin 4
|
||||||
|
./myspin 4
|
||||||
|
|
||||||
|
SLEEP 2
|
||||||
|
INT
|
||||||
14
shlab/shlab-handout/trace07.txt
Normal file
14
shlab/shlab-handout/trace07.txt
Normal 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
|
||||||
14
shlab/shlab-handout/trace08.txt
Normal file
14
shlab/shlab-handout/trace08.txt
Normal 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
|
||||||
20
shlab/shlab-handout/trace09.txt
Normal file
20
shlab/shlab-handout/trace09.txt
Normal 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
|
||||||
22
shlab/shlab-handout/trace10.txt
Normal file
22
shlab/shlab-handout/trace10.txt
Normal 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
|
||||||
|
|
||||||
12
shlab/shlab-handout/trace11.txt
Normal file
12
shlab/shlab-handout/trace11.txt
Normal 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
|
||||||
|
|
||||||
17
shlab/shlab-handout/trace12.txt
Normal file
17
shlab/shlab-handout/trace12.txt
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
23
shlab/shlab-handout/trace13.txt
Normal file
23
shlab/shlab-handout/trace13.txt
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
47
shlab/shlab-handout/trace14.txt
Normal file
47
shlab/shlab-handout/trace14.txt
Normal 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
46
shlab/shlab-handout/trace15.txt
Normal file
46
shlab/shlab-handout/trace15.txt
Normal 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
|
||||||
|
|
||||||
16
shlab/shlab-handout/trace16.txt
Normal file
16
shlab/shlab-handout/trace16.txt
Normal 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
BIN
shlab/shlab-handout/tsh
Executable file
Binary file not shown.
912
shlab/shlab-handout/tsh.c
Normal file
912
shlab/shlab-handout/tsh.c
Normal 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
BIN
shlab/shlab-handout/tshref
Executable file
Binary file not shown.
220
shlab/shlab-handout/tshref.out
Normal file
220
shlab/shlab-handout/tshref.out
Normal 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'
|
||||||
BIN
shlab/shlab.pdf
Normal file
BIN
shlab/shlab.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user