Compare commits
12 Commits
646ecc0931
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b3246ebd9 | |||
| 575d48efa6 | |||
| 1518950242 | |||
| 0a652a3214 | |||
| 17257c2d9f | |||
| e1da8a7292 | |||
| 1c50fbd315 | |||
| 3e87f2c37d | |||
| 4f21b2e69f | |||
|
|
e2a5e6750a | ||
| ab6ccd8cf6 | |||
| 1122a9c541 |
93
labs/03_shlab/Makefile
Normal file
93
labs/03_shlab/Makefile
Normal file
@@ -0,0 +1,93 @@
|
||||
# Makefile for the Shell Lab
|
||||
|
||||
TEAM = INDIVIDUAL
|
||||
VERSION = 1
|
||||
DRIVER = ./sdriver.pl
|
||||
TSH = ./tsh
|
||||
TSHREF = ./tshref
|
||||
TSHARGS = "-p"
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -O2
|
||||
FILES = $(TSH) ./myspin ./mysplit ./mystop ./myint
|
||||
|
||||
all: $(FILES)
|
||||
|
||||
##################
|
||||
# Regression tests
|
||||
##################
|
||||
|
||||
tests: test01 test02 test03 test04 test05 test06 test07 test08 test09 test10 test11 test12 test13 test14 test15 test16
|
||||
# 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)
|
||||
|
||||
rtests: rtest01 rtest02 rtest03 rtest04 rtest05 rtest06 rtest07 rtest08 rtest09 rtest10 rtest11 rtest12 rtest13 rtest14 rtest15 rtest16
|
||||
# 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
labs/03_shlab/README
Normal file
22
labs/03_shlab/README
Normal file
@@ -0,0 +1,22 @@
|
||||
************************************
|
||||
* CSE4009 Assignment 3 - 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 16 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
|
||||
|
||||
36
labs/03_shlab/myint.c
Normal file
36
labs/03_shlab/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);
|
||||
|
||||
}
|
||||
24
labs/03_shlab/myspin.c
Normal file
24
labs/03_shlab/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);
|
||||
}
|
||||
35
labs/03_shlab/mysplit.c
Normal file
35
labs/03_shlab/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);
|
||||
}
|
||||
36
labs/03_shlab/mystop.c
Normal file
36
labs/03_shlab/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
labs/03_shlab/sdriver.pl
Executable file
210
labs/03_shlab/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
labs/03_shlab/trace01.txt
Normal file
5
labs/03_shlab/trace01.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# trace01.txt - Properly terminate on EOF.
|
||||
#
|
||||
CLOSE
|
||||
WAIT
|
||||
5
labs/03_shlab/trace02.txt
Normal file
5
labs/03_shlab/trace02.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# trace02.txt - Process builtin quit command.
|
||||
#
|
||||
quit
|
||||
WAIT
|
||||
5
labs/03_shlab/trace03.txt
Normal file
5
labs/03_shlab/trace03.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# trace03.txt - Run a foreground job.
|
||||
#
|
||||
/bin/echo tsh> quit
|
||||
quit
|
||||
5
labs/03_shlab/trace04.txt
Normal file
5
labs/03_shlab/trace04.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# trace04.txt - Run a background job.
|
||||
#
|
||||
/bin/echo -e tsh> ./myspin 1 \046
|
||||
./myspin 1 &
|
||||
11
labs/03_shlab/trace05.txt
Normal file
11
labs/03_shlab/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
labs/03_shlab/trace06.txt
Normal file
8
labs/03_shlab/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
labs/03_shlab/trace07.txt
Normal file
14
labs/03_shlab/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
labs/03_shlab/trace08.txt
Normal file
14
labs/03_shlab/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
labs/03_shlab/trace09.txt
Normal file
20
labs/03_shlab/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
labs/03_shlab/trace10.txt
Normal file
22
labs/03_shlab/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
labs/03_shlab/trace11.txt
Normal file
12
labs/03_shlab/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
labs/03_shlab/trace12.txt
Normal file
17
labs/03_shlab/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
labs/03_shlab/trace13.txt
Normal file
23
labs/03_shlab/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
labs/03_shlab/trace14.txt
Normal file
47
labs/03_shlab/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
labs/03_shlab/trace15.txt
Normal file
46
labs/03_shlab/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
labs/03_shlab/trace16.txt
Normal file
16
labs/03_shlab/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
|
||||
|
||||
606
labs/03_shlab/tsh.c
Normal file
606
labs/03_shlab/tsh.c
Normal file
@@ -0,0 +1,606 @@
|
||||
/*
|
||||
* tsh - A tiny shell program with job control
|
||||
*
|
||||
* Name: Hajin Ju
|
||||
* Student id: 2024062806
|
||||
*/
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Misc manifest constants */
|
||||
#define MAXLINE 1024 /* max line size */
|
||||
#define MAXARGS 128 /* max args on a command line */
|
||||
#define MAXJOBS 16 /* max jobs at any point in time */
|
||||
#define MAXJID 1 << 16 /* max job ID */
|
||||
|
||||
/* Job states */
|
||||
#define UNDEF 0 /* undefined */
|
||||
#define FG 1 /* running in foreground */
|
||||
#define BG 2 /* running in background */
|
||||
#define ST 3 /* stopped */
|
||||
|
||||
/*
|
||||
* Jobs states: FG (foreground), BG (background), ST (stopped)
|
||||
* Job state transitions and enabling actions:
|
||||
* FG -> ST : ctrl-z
|
||||
* ST -> FG : fg command
|
||||
* ST -> BG : bg command
|
||||
* BG -> FG : fg command
|
||||
* At most 1 job can be in the FG state.
|
||||
*/
|
||||
|
||||
/* Global variables */
|
||||
extern char **environ; /* defined in libc */
|
||||
char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */
|
||||
int verbose = 0; /* if true, print additional output */
|
||||
int nextjid = 1; /* next job ID to allocate */
|
||||
char sbuf[MAXLINE]; /* for composing sprintf messages */
|
||||
|
||||
struct job_t { /* The job struct */
|
||||
pid_t pid; /* job PID */
|
||||
int jid; /* job ID [1, 2, ...] */
|
||||
int state; /* UNDEF, BG, FG, or ST */
|
||||
char cmdline[MAXLINE]; /* command line */
|
||||
};
|
||||
struct job_t jobs[MAXJOBS]; /* The job list */
|
||||
/* End global variables */
|
||||
|
||||
/* Function prototypes */
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Functions that you will implement
|
||||
*/
|
||||
|
||||
void eval(char *cmdline);
|
||||
int builtin_cmd(char **argv);
|
||||
void do_bgfg(char **argv);
|
||||
void waitfg(pid_t pid);
|
||||
|
||||
void sigchld_handler(int sig);
|
||||
void sigint_handler(int sig);
|
||||
void sigtstp_handler(int sig);
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
/* These functions are already implemented for your convenience */
|
||||
int parseline(const char *cmdline, char **argv);
|
||||
void sigquit_handler(int sig);
|
||||
|
||||
void clearjob(struct job_t *job);
|
||||
void initjobs(struct job_t *jobs);
|
||||
int maxjid(struct job_t *jobs);
|
||||
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);
|
||||
int deletejob(struct job_t *jobs, pid_t pid);
|
||||
pid_t fgpid(struct job_t *jobs);
|
||||
struct job_t *getjobpid(struct job_t *jobs, pid_t pid);
|
||||
struct job_t *getjobjid(struct job_t *jobs, int jid);
|
||||
int pid2jid(pid_t pid);
|
||||
void listjobs(struct job_t *jobs);
|
||||
|
||||
void usage(void);
|
||||
void unix_error(char *msg);
|
||||
void app_error(char *msg);
|
||||
typedef void handler_t(int);
|
||||
handler_t *Signal(int signum, handler_t *handler);
|
||||
|
||||
/*
|
||||
* main - The shell's main routine
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
char c;
|
||||
char cmdline[MAXLINE];
|
||||
int emit_prompt = 1; /* emit prompt (default) */
|
||||
|
||||
/* Redirect stderr to stdout (so that driver will get all output
|
||||
* on the pipe connected to stdout) */
|
||||
dup2(1, 2);
|
||||
|
||||
/* Parse the command line */
|
||||
while ((c = getopt(argc, argv, "hvp")) != EOF) {
|
||||
switch (c) {
|
||||
case 'h': /* print help message */
|
||||
usage();
|
||||
break;
|
||||
case 'v': /* emit additional diagnostic info */
|
||||
verbose = 1;
|
||||
break;
|
||||
case 'p': /* don't print a prompt */
|
||||
emit_prompt = 0; /* handy for automatic testing */
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
/* Install the signal handlers */
|
||||
|
||||
/* These are the ones you will need to implement */
|
||||
Signal(SIGINT, sigint_handler); /* ctrl-c */
|
||||
Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */
|
||||
Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */
|
||||
|
||||
/* This one provides a clean way to kill the shell */
|
||||
Signal(SIGQUIT, sigquit_handler);
|
||||
|
||||
/* Initialize the job list */
|
||||
initjobs(jobs);
|
||||
|
||||
/* Execute the shell's read/eval loop */
|
||||
while (1) {
|
||||
|
||||
/* Read command line */
|
||||
if (emit_prompt) {
|
||||
printf("%s", prompt);
|
||||
fflush(stdout);
|
||||
}
|
||||
if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
|
||||
app_error("fgets error");
|
||||
if (feof(stdin)) { /* End of file (ctrl-d) */
|
||||
fflush(stdout);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* Evaluate the command line */
|
||||
eval(cmdline);
|
||||
fflush(stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
exit(0); /* control never reaches here */
|
||||
}
|
||||
|
||||
/*
|
||||
* eval - Evaluate the command line that the user has just typed in
|
||||
*
|
||||
* If the user has requested a built-in command (quit, jobs, bg or fg)
|
||||
* then execute it immediately. Otherwise, fork a child process and
|
||||
* run the job in the context of the child. If the job is running in
|
||||
* the foreground, wait for it to terminate and then return. Note:
|
||||
* each child process must have a unique process group ID so that our
|
||||
* background children don't receive SIGINT (SIGTSTP) from the kernel
|
||||
* when we type ctrl-c (ctrl-z) at the keyboard.
|
||||
*/
|
||||
void eval(char *cmdline) {
|
||||
pid_t pid;
|
||||
|
||||
sigset_t mask, prev_mask;
|
||||
|
||||
char *argv[MAXARGS];
|
||||
|
||||
int bg = parseline(cmdline, argv);
|
||||
|
||||
if (argv[0] && !builtin_cmd(argv)) {
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGCHLD);
|
||||
sigprocmask(SIG_BLOCK, &mask, &prev_mask);
|
||||
|
||||
if ((pid = fork()) == 0) {// Child process
|
||||
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
|
||||
setpgid(0, 0);// Set new process group
|
||||
|
||||
if (execve(argv[0], argv, environ) < 0) {
|
||||
printf("%s: Command not found\n", argv[0]);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bg) {// Foreground job
|
||||
addjob(jobs, pid, FG, cmdline);
|
||||
|
||||
} else {// Background job
|
||||
addjob(jobs, pid, BG, cmdline);
|
||||
fprintf(stderr, "[%d] (%d) %s", pid2jid(pid), pid, cmdline);
|
||||
}
|
||||
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
|
||||
|
||||
if (!bg) waitfg(pid);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* parseline - Parse the command line and build the argv array.
|
||||
*
|
||||
* Characters enclosed in single quotes are treated as a single
|
||||
* argument. Return true if the user has requested a BG job, false if
|
||||
* the user has requested a FG job.
|
||||
*/
|
||||
int parseline(const char *cmdline, char **argv) {
|
||||
static char array[MAXLINE]; /* holds local copy of command line */
|
||||
char *buf = array; /* ptr that traverses command line */
|
||||
char *delim; /* points to first space delimiter */
|
||||
int argc; /* number of args */
|
||||
int bg; /* background job? */
|
||||
|
||||
strcpy(buf, cmdline);
|
||||
buf[strlen(buf) - 1] = ' '; /* replace trailing '\n' with space */
|
||||
while (*buf && (*buf == ' ')) /* ignore leading spaces */
|
||||
buf++;
|
||||
|
||||
/* Build the argv list */
|
||||
argc = 0;
|
||||
if (*buf == '\'') {
|
||||
buf++;
|
||||
delim = strchr(buf, '\'');
|
||||
} else {
|
||||
delim = strchr(buf, ' ');
|
||||
}
|
||||
|
||||
while (delim) {
|
||||
argv[argc++] = buf;
|
||||
*delim = '\0';
|
||||
buf = delim + 1;
|
||||
while (*buf && (*buf == ' ')) /* ignore spaces */
|
||||
buf++;
|
||||
|
||||
if (*buf == '\'') {
|
||||
buf++;
|
||||
delim = strchr(buf, '\'');
|
||||
} else {
|
||||
delim = strchr(buf, ' ');
|
||||
}
|
||||
}
|
||||
argv[argc] = NULL;
|
||||
|
||||
if (argc == 0) /* ignore blank line */
|
||||
return 1;
|
||||
|
||||
/* should the job run in the background? */
|
||||
if ((bg = (*argv[argc - 1] == '&')) != 0) {
|
||||
argv[--argc] = NULL;
|
||||
}
|
||||
return bg;
|
||||
}
|
||||
|
||||
/*
|
||||
* builtin_cmd - If the user has typed a built-in command then execute
|
||||
* it immediately.
|
||||
*/
|
||||
int builtin_cmd(char **argv) {
|
||||
// Built-in-commands: jobs, bg, fg, quit
|
||||
|
||||
if (!strcmp(argv[0], "quit")) {
|
||||
exit(0);
|
||||
} else if (!strcmp(argv[0], "jobs")) {
|
||||
listjobs(jobs);
|
||||
return 1;
|
||||
} else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg")) {
|
||||
do_bgfg(argv);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* do_bgfg - Execute the builtin bg and fg commands
|
||||
*/
|
||||
void do_bgfg(char **argv) {
|
||||
struct job_t *job = NULL;
|
||||
pid_t pid = 0;
|
||||
int jid = 0;
|
||||
|
||||
if (argv[1] == NULL) {
|
||||
printf("%s command requires PID or %%jobid argument\n", argv[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (argv[1][0] == '%') {
|
||||
if (sscanf(&argv[1][1], "%d", &jid) != 1) {
|
||||
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
|
||||
return;
|
||||
}
|
||||
job = getjobjid(jobs, jid);
|
||||
if (job == NULL) {
|
||||
printf("%s: No such job\n", argv[1]);
|
||||
return;
|
||||
}
|
||||
} else if (isdigit((unsigned char) argv[1][0])) {
|
||||
if (sscanf(argv[1], "%d", &pid) != 1) {
|
||||
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
|
||||
return;
|
||||
}
|
||||
job = getjobpid(jobs, pid);
|
||||
if (job == NULL) {
|
||||
printf("(%s): No such process\n", argv[1]);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
printf("%s: argument must be a PID or %%jobid\n", argv[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
pid = job->pid;
|
||||
|
||||
if (!strcmp(argv[0], "bg")) {
|
||||
job->state = BG;
|
||||
if (kill(-pid, SIGCONT) < 0)
|
||||
unix_error("kill (SIGCONT) error");
|
||||
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
|
||||
} else { /* fg */
|
||||
job->state = FG;
|
||||
if (kill(-pid, SIGCONT) < 0)
|
||||
unix_error("kill (SIGCONT) error");
|
||||
waitfg(pid);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* waitfg - Block until process pid is no longer the foreground process
|
||||
*/
|
||||
void waitfg(pid_t pid) {
|
||||
while (pid == fgpid(jobs)) {
|
||||
sleep(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*****************
|
||||
* Signal handlers
|
||||
*****************/
|
||||
|
||||
/*
|
||||
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
|
||||
* a child job terminates (becomes a zombie), or stops because it
|
||||
* received a SIGSTOP or SIGTSTP signal. The handler reaps all
|
||||
* available zombie children, but doesn't wait for any other
|
||||
* currently running children to terminate.
|
||||
*/
|
||||
void sigchld_handler(int sig) {
|
||||
int olderrno = errno;
|
||||
pid_t pid;
|
||||
int status;
|
||||
|
||||
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) {
|
||||
if (WIFEXITED(status)) {
|
||||
deletejob(jobs, pid);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
fprintf(stderr, "Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
|
||||
deletejob(jobs, pid);
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
struct job_t *job = getjobpid(jobs, pid);
|
||||
if (job != NULL) {
|
||||
job->state = ST;
|
||||
fprintf(stderr, "Job [%d] (%d) stopped by signal %d\n", job->jid, pid, WSTOPSIG(status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errno = olderrno;
|
||||
}
|
||||
|
||||
/*
|
||||
* sigint_handler - The kernel sends a SIGINT to the shell whenver the
|
||||
* user types ctrl-c at the keyboard. Catch it and send it along
|
||||
* to the foreground job.
|
||||
*/
|
||||
void sigint_handler(int sig) {
|
||||
pid_t pid;
|
||||
if ((pid = fgpid(jobs)) > 0) {
|
||||
kill(-pid, SIGINT);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
|
||||
* the user types ctrl-z at the keyboard. Catch it and suspend the
|
||||
* foreground job by sending it a SIGTSTP.
|
||||
*/
|
||||
void sigtstp_handler(int sig) {
|
||||
pid_t pid;
|
||||
if ((pid = fgpid(jobs)) > 0) {
|
||||
kill(-pid, SIGTSTP);
|
||||
}
|
||||
}
|
||||
|
||||
/*********************
|
||||
* End signal handlers
|
||||
*********************/
|
||||
|
||||
/***********************************************
|
||||
* Helper routines that manipulate the job list
|
||||
**********************************************/
|
||||
|
||||
/* clearjob - Clear the entries in a job struct */
|
||||
void clearjob(struct job_t *job) {
|
||||
job->pid = 0;
|
||||
job->jid = 0;
|
||||
job->state = UNDEF;
|
||||
job->cmdline[0] = '\0';
|
||||
}
|
||||
|
||||
/* initjobs - Initialize the job list */
|
||||
void initjobs(struct job_t *jobs) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAXJOBS; i++)
|
||||
clearjob(&jobs[i]);
|
||||
}
|
||||
|
||||
/* maxjid - Returns largest allocated job ID */
|
||||
int maxjid(struct job_t *jobs) {
|
||||
int i, max = 0;
|
||||
|
||||
for (i = 0; i < MAXJOBS; i++)
|
||||
if (jobs[i].jid > max)
|
||||
max = jobs[i].jid;
|
||||
return max;
|
||||
}
|
||||
|
||||
/* addjob - Add a job to the job list */
|
||||
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline) {
|
||||
int i;
|
||||
|
||||
if (pid < 1)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < MAXJOBS; i++) {
|
||||
if (jobs[i].pid == 0) {
|
||||
jobs[i].pid = pid;
|
||||
jobs[i].state = state;
|
||||
jobs[i].jid = nextjid++;
|
||||
if (nextjid > MAXJOBS)
|
||||
nextjid = 1;
|
||||
strcpy(jobs[i].cmdline, cmdline);
|
||||
if (verbose) {
|
||||
printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
printf("Tried to create too many jobs\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* deletejob - Delete a job whose PID=pid from the job list */
|
||||
int deletejob(struct job_t *jobs, pid_t pid) {
|
||||
int i;
|
||||
|
||||
if (pid < 1)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < MAXJOBS; i++) {
|
||||
if (jobs[i].pid == pid) {
|
||||
clearjob(&jobs[i]);
|
||||
nextjid = maxjid(jobs) + 1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* fgpid - Return PID of current foreground job, 0 if no such job */
|
||||
pid_t fgpid(struct job_t *jobs) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAXJOBS; i++)
|
||||
if (jobs[i].state == FG)
|
||||
return jobs[i].pid;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* getjobpid - Find a job (by PID) on the job list */
|
||||
struct job_t *getjobpid(struct job_t *jobs, pid_t pid) {
|
||||
int i;
|
||||
|
||||
if (pid < 1)
|
||||
return NULL;
|
||||
for (i = 0; i < MAXJOBS; i++)
|
||||
if (jobs[i].pid == pid)
|
||||
return &jobs[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* getjobjid - Find a job (by JID) on the job list */
|
||||
struct job_t *getjobjid(struct job_t *jobs, int jid) {
|
||||
int i;
|
||||
|
||||
if (jid < 1)
|
||||
return NULL;
|
||||
for (i = 0; i < MAXJOBS; i++)
|
||||
if (jobs[i].jid == jid)
|
||||
return &jobs[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* pid2jid - Map process ID to job ID */
|
||||
int pid2jid(pid_t pid) {
|
||||
int i;
|
||||
|
||||
if (pid < 1)
|
||||
return 0;
|
||||
for (i = 0; i < MAXJOBS; i++)
|
||||
if (jobs[i].pid == pid) {
|
||||
return jobs[i].jid;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* listjobs - Print the job list */
|
||||
void listjobs(struct job_t *jobs) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAXJOBS; i++) {
|
||||
if (jobs[i].pid != 0) {
|
||||
printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);
|
||||
switch (jobs[i].state) {
|
||||
case BG:
|
||||
printf("Running ");
|
||||
break;
|
||||
case FG:
|
||||
printf("Foreground ");
|
||||
break;
|
||||
case ST:
|
||||
printf("Stopped ");
|
||||
break;
|
||||
default:
|
||||
printf("listjobs: Internal error: job[%d].state=%d ",
|
||||
i, jobs[i].state);
|
||||
}
|
||||
printf("%s", jobs[i].cmdline);
|
||||
}
|
||||
}
|
||||
}
|
||||
/******************************
|
||||
* end job list helper routines
|
||||
******************************/
|
||||
|
||||
/***********************
|
||||
* Other helper routines
|
||||
***********************/
|
||||
|
||||
/*
|
||||
* usage - print a help message
|
||||
*/
|
||||
void usage(void) {
|
||||
printf("Usage: shell [-hvp]\n");
|
||||
printf(" -h print this message\n");
|
||||
printf(" -v print additional diagnostic information\n");
|
||||
printf(" -p do not emit a command prompt\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* unix_error - unix-style error routine
|
||||
*/
|
||||
void unix_error(char *msg) {
|
||||
fprintf(stdout, "%s: %s\n", msg, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* app_error - application-style error routine
|
||||
*/
|
||||
void app_error(char *msg) {
|
||||
fprintf(stdout, "%s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Signal - wrapper for the sigaction function
|
||||
*/
|
||||
handler_t *Signal(int signum, handler_t *handler) {
|
||||
struct sigaction action, old_action;
|
||||
|
||||
action.sa_handler = handler;
|
||||
sigemptyset(&action.sa_mask); /* block sigs of type being handled */
|
||||
action.sa_flags = SA_RESTART; /* restart syscalls if possible */
|
||||
|
||||
if (sigaction(signum, &action, &old_action) < 0)
|
||||
unix_error("Signal error");
|
||||
return (old_action.sa_handler);
|
||||
}
|
||||
|
||||
/*
|
||||
* sigquit_handler - The driver program can gracefully terminate the
|
||||
* child shell by sending it a SIGQUIT signal.
|
||||
*/
|
||||
void sigquit_handler(int sig) {
|
||||
printf("Terminating after receipt of SIGQUIT signal\n");
|
||||
exit(1);
|
||||
}
|
||||
BIN
labs/03_shlab/tshref
Executable file
BIN
labs/03_shlab/tshref
Executable file
Binary file not shown.
218
labs/03_shlab/tshref.out
Normal file
218
labs/03_shlab/tshref.out
Normal file
@@ -0,0 +1,218 @@
|
||||
./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
|
||||
218
notes/10.md
Normal file
218
notes/10.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# Signals and Nonlocal Jumps
|
||||
|
||||
## Shell
|
||||
|
||||
Linux Process Hierachy: all is rooted from `init` or `systemd` (pid = 1).
|
||||
|
||||
A shell is an application that runs programs on behalf of the user.
|
||||
|
||||
* `sh` Original Unix shell
|
||||
* `csh` BSD Unix C Shell
|
||||
* `bash` GNU Bourne-Again Shell (default Linux shell)
|
||||
|
||||
|
||||
## Signals
|
||||
|
||||
All signal is a small message that notifies a process that an event has occured in the system.
|
||||
|
||||
Signal is sent by kernel.
|
||||
|
||||
Signal is identified by small integer ID (1-30).
|
||||
|
||||
Only information in a signal is **its ID** and **the fact that it arrived**.
|
||||
|
||||
| ID | name | Default Action | Corresponding Event |
|
||||
| --- | ------- | -------------- | ----------------------------------- |
|
||||
| 2 | SIGINT | terminate | user types `Ctrl+C` |
|
||||
| 9 | SIGKILL | terminate | kill program |
|
||||
| 11 | SIGSEGV | terminate | segmentation violation |
|
||||
| 14 | SIGALRM | terminate | timer expired |
|
||||
| 17 | SIGCHLD | ignore | child process stopped or terminated |
|
||||
|
||||
Sending a signal is essentially **updating some state** in the context of the destination process.
|
||||
Kernel sends a signal for one of the following reasons:
|
||||
|
||||
A destination process receives a signal when it is forced by the kernel and reacts some way to the signal.
|
||||
* Ignore
|
||||
* Terminate (with optional core-dump)
|
||||
* Catch
|
||||
* executing a user-level handler function called **signal handler**.
|
||||
|
||||
### Pending & Blocking
|
||||
|
||||
A signal is **pending** if sent but not yet received. There can be at most one pending signal of any particular type because signals are **not queued**. Signal is managed by bitmask. So subsequent signals of same type is discarded.
|
||||
* sets `k` bit when delivered
|
||||
* clears `k` bit when received
|
||||
|
||||
Blocking signals: a process can block the receipt of certain signals. It is not received until unblocked. It is also managed by bitmask.
|
||||
* can be set and cleared by using `sigprocmask`
|
||||
|
||||
### Receiving
|
||||
|
||||
Suppose kernel is returning from an exception handler and is ready to pass control to process `p`
|
||||
Kernel computes `pnb = pending(p) & ~blocked(p)`.
|
||||
* if `pnb == 0`, no unblocked pending signals for `p`, so just return to `p` normally.
|
||||
* otherwise, choose least nonzero bit `k` in `pnb` and force process `p` to receive signal `k`
|
||||
* The receipt of the signal triggers action by `p`
|
||||
* Repeat for all nonzero `k`
|
||||
|
||||
### Default Action & Signal Handler
|
||||
|
||||
Each signal type has a predefined default action:
|
||||
* terminates
|
||||
* stops until restarted by a SIGCONT signal
|
||||
* ignores
|
||||
|
||||
```c
|
||||
handler_t * signal(int signum, handler_t *handler);
|
||||
```
|
||||
|
||||
* `SIG_IGN` ignore
|
||||
* `SIG_DFL` revert to default action
|
||||
* handler function pointer
|
||||
|
||||
### Blocking and Unblocking
|
||||
|
||||
* Implicit blocking
|
||||
Kernel blocks any pending signals of type currently being handled.
|
||||
* Explicit blocking `sigprocmask()`
|
||||
|
||||
Supporting functions:
|
||||
* `sigemptyset()` create empty set
|
||||
* `sigfillset()` add every signal number to set
|
||||
* `sigaddset()` add signal number to set
|
||||
* `sigdelset()` delete signal number from set
|
||||
|
||||
```c
|
||||
sigset_t mask, prev_mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGINT);
|
||||
|
||||
sigprocmaksk(SIG_BLOCK, &mask, &prev_mask);
|
||||
// critical section
|
||||
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
|
||||
```
|
||||
|
||||
### Safe Signal Handling
|
||||
|
||||
Handlers are concurrent with main program and share global data structures. This can lead to troubles.
|
||||
|
||||
Some guidlines help to avoid troubles:
|
||||
|
||||
0. Keep you handlers as simple as possible
|
||||
1. Call only **async-signal-safe** functions in your handlers
|
||||
2. Save and restore `errno` on entry and exit
|
||||
3. Protect accesses to shared data structure by temporarily blocking all signals
|
||||
4. Declare global variables as `volatile`
|
||||
5. Declare global flags as `volatile sig_atomic_t`
|
||||
|
||||
#### Async-Signal-Safety
|
||||
|
||||
A function satisfying either two conditions(below) is **Async-Signal-Safety function**:
|
||||
* **reentrant**: not modifying global data (or static data), only use local data(stack frame).
|
||||
* **non-interruptible**: blocking singnals during modifying global data.
|
||||
|
||||
POSIX guarantees that the 117 functions to be async-signal-safe, including:
|
||||
* `_exit`, `write`, `wait`, `waitpid`, `sleep`, `kill`
|
||||
|
||||
**IMPORTANT**: `printf`, `malloc`, `sprintf`, `eixt` are **NOT** async-signal-safe.
|
||||
|
||||
### Correct Signal Handling Example for reaping multiple childs
|
||||
|
||||
```c
|
||||
void child_handler(int sig) {
|
||||
int olderrno = errno; pid_t pid;
|
||||
while ((pid = wait(NULL)) > 0) {
|
||||
ccount --;
|
||||
sio_puts("Handler reaped child ");
|
||||
sio_putl((long) pid);
|
||||
sio_puts(" \n");
|
||||
}
|
||||
if (errno != ECHILD) {
|
||||
sio_error("wait error");
|
||||
}
|
||||
errno = olderrno;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Portable Signal Handling
|
||||
|
||||
Different UNIX systems have different signal handling sematnics.
|
||||
|
||||
So `sigaction` is introduced to provide a portable interface for signal handling.
|
||||
|
||||
```c
|
||||
handler_t * Signal(int signum, handler_t *handler) {
|
||||
struct sigaction action, old_action;
|
||||
action.sa_handler = handler;
|
||||
sigemptyset(&action.sa_mask); // block sigs of type being handled
|
||||
action.sa_flags = SA_RESTART; // restart syscalls if possible
|
||||
if (sigaction(signum, &action, &old_action) < 0)
|
||||
unix_error("Signal error");
|
||||
return (old_action.sa_handler);
|
||||
}
|
||||
```
|
||||
|
||||
### Concurrent Flows to lead Races
|
||||
|
||||
```c
|
||||
void handler(int sig) {
|
||||
int olderrno = errno;
|
||||
pid_t pid;
|
||||
while ((pid = waitpid(-1, NULL, 0)) > 0) {
|
||||
deletejob(pid);
|
||||
}
|
||||
if (errno != ECHILD) sio_error("waitpid error");
|
||||
errno = olderrno;
|
||||
}
|
||||
|
||||
int main() {
|
||||
while (1) {
|
||||
if ((pid = Fork()) == 0) {
|
||||
execve("/bin/date", argv, NULL);
|
||||
}
|
||||
}
|
||||
addjob(pid);
|
||||
}
|
||||
```
|
||||
|
||||
In above example, the ecf flow can be executed before the deletejob; thus it cannot be deleted.
|
||||
Therefore, we need to synchronize the two concurrent flows.
|
||||
|
||||
```c
|
||||
void handler(int sig) {
|
||||
int olderrno = errno;
|
||||
|
||||
sigset_t mask_all, prev_all;
|
||||
pid_t pid;
|
||||
|
||||
sigfillset(&mask_all);
|
||||
while ((pid = waitpid(-1, NULL, 0)) > 0) {
|
||||
sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
|
||||
deletejob(pid);
|
||||
sigprocmask(SIG_SETMASK, &prev_all, NULL);
|
||||
}
|
||||
if (errno != ECHILD) sio_error("waitpid error");
|
||||
errno = olderrno;
|
||||
}
|
||||
|
||||
int main() {
|
||||
pid_t pid;
|
||||
sigset_t mask_all, prev_one;
|
||||
|
||||
sigfillset(&mask_all);
|
||||
signal(SIGCHLD, handler);
|
||||
|
||||
while (1) {
|
||||
sigprocmask(SIG_BLOCK, &mask_all, &prev_one); // Block SIGCHLD
|
||||
if ((pid = Fork()) == 0) {
|
||||
sigprocmask(SIG_SETMASK, &prev_one, NULL); // Unblock SIGCHLD
|
||||
execve("/bin/date", argv, NULL);
|
||||
}
|
||||
sigprocmask(SIG_BLOCK, &mask_one, NULL);
|
||||
addjob(pid);
|
||||
sigprocmask(SIG_SETMASK, &prev_one, NULL);
|
||||
}
|
||||
}
|
||||
```
|
||||
223
notes/11.md
Normal file
223
notes/11.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# System-Level I/O
|
||||
|
||||
IO is the process of coping data between the main memory and external devices.
|
||||
|
||||
In a Linux, **file** is a sequence of $m$ bytes.
|
||||
|
||||
All I/O devices are represented as files. Even the kernel is represented as a file.
|
||||
|
||||
## Unix IO
|
||||
|
||||
* `open` and `close`
|
||||
* `read` and `write`
|
||||
* `lseek` changing **current file position**
|
||||
|
||||
### File Types
|
||||
|
||||
* Regular files
|
||||
* Directory
|
||||
* Socket
|
||||
* ...
|
||||
|
||||
#### Regular Files
|
||||
|
||||
A regular file contains arbitary data.
|
||||
|
||||
For example **text file** is a sequence of text lines. EOL is different in different OS: (`\n` in Unix, `\r\n` in Windows & Internet).
|
||||
|
||||
#### Directories
|
||||
|
||||
Directory contains an array of links. Least two links are `.`(itself) and `..`(parent dir).
|
||||
|
||||
* `ls`
|
||||
* `mkdir`
|
||||
* `rmdir`
|
||||
|
||||
All files are orgnaized as a hierarchy anchored by root dir named `/`.
|
||||
|
||||
Kernel maintains curr working dir (cwd) for each process that modified using the `cd` command.
|
||||
|
||||
Path names
|
||||
* Absolute `/home/yenru0/workspace`
|
||||
* Relative `../workspace`
|
||||
|
||||
### Open & Close & Read & Write
|
||||
|
||||
```c
|
||||
int fd;
|
||||
|
||||
if ((fd = open("file.txt", O_RDONLY)) < 0) {
|
||||
perror("open");
|
||||
exit(1);
|
||||
}
|
||||
```
|
||||
|
||||
* `open` returns a non-negative integer called **file descriptor** (fd).
|
||||
* `fd == -1` indicates an error.
|
||||
* `0`: stdin, `1`: stdout, `2`: stderr
|
||||
|
||||
```c
|
||||
int fd; int ret;
|
||||
if ((ret = close(fd)) < 0) {
|
||||
perror("close");
|
||||
exit(1);
|
||||
}
|
||||
```
|
||||
|
||||
Closing an already closed can lead to a disastrous situation in threaded programs. So always check the return code.
|
||||
|
||||
```c
|
||||
char buf[512];
|
||||
|
||||
nbytes = read(fd, buf, sizeof(buf));
|
||||
|
||||
```
|
||||
|
||||
```c
|
||||
ssize_t read(int fd, void *usrbuf, size_t n);
|
||||
```
|
||||
|
||||
read returns the number of bytes read from the `fd` into `buf`.
|
||||
`ssize_t` is signed version of `size_t`.
|
||||
|
||||
If `read` returns negative value, an error occurred.
|
||||
|
||||
```c
|
||||
ssize_t write(int fd, const void *usrbuf, size_t n);
|
||||
```
|
||||
|
||||
If `write` returns negative value, an error occurred.
|
||||
|
||||
### Short Counts
|
||||
|
||||
It means that `read` or `write` transfers fewer bytes than requested. It can occur in these situations:
|
||||
|
||||
* `EOF` on reads
|
||||
* Reading text lines from an terminal
|
||||
* Reading from a network socket
|
||||
|
||||
Never occurs:
|
||||
* Reading from disk files (except for `EOF`)
|
||||
* Writing to disk files
|
||||
|
||||
## RIO pakcage
|
||||
|
||||
RIO is a set of wrappers efficient and robust I/O functions subject to **short couunts**.
|
||||
|
||||
* unbuffered RIO functions `rio_readn`, `rio_writen`
|
||||
* buffered RIO functions `rio_readnb`, `rio_readlineb`
|
||||
* buffered RIO functions are thread-safe and can be interleaved arbitrarily on the same descriptor.
|
||||
|
||||
|
||||
### Buffered RIO
|
||||
|
||||
To read efficiently from a file, RIO uses partially cached in an interal memory buffer. (`rio_t` structure)
|
||||
|
||||
For reading from file, Buffer has buffered portion of already read and unread data. It is refilled automatically by `rio_readnb` and `rio_readlineb` as needed. This is **partially cached**.
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
int rio_fd; // Descriptor for this internal buf
|
||||
int rio_cnt; // Unread bytes in internal buf
|
||||
char *rio_bufptr; // Next unread byte in internal buf
|
||||
char rio_buf[RIO_BUFSIZE]; // Internal buffer
|
||||
} rio_t;
|
||||
```
|
||||
|
||||
example:
|
||||
|
||||
```c
|
||||
int main(int argc, char **argv) {
|
||||
int n; rio_t rio; char buf[MAXLINE];
|
||||
rio_readinitb(&rio, STDIN_FILENO);
|
||||
while ((n = rio_readlineb(&rio, buf, MAXLINE)) != 0) {
|
||||
rio_writen(STDOUT_FILENO, buf, n);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Metadata
|
||||
|
||||
Metadata is data about data. (file access, file size, file type)
|
||||
|
||||
* Per-process metadata
|
||||
* when a process opens a file, the kernel creates an entry in a per-process table called the **file descriptor table**
|
||||
* Per-file metadata
|
||||
* can be accessed using `stat` system call
|
||||
|
||||
|
||||
```c
|
||||
struct stat {
|
||||
dev_t st_dev; // ID of device containing file
|
||||
ino_t st_ino; // inode number
|
||||
mode_t st_mode; // protection
|
||||
nlink_t st_nlink; // number of hard links
|
||||
uid_t st_uid; // user ID of owner
|
||||
gid_t st_gid; // group ID of owner
|
||||
dev_t st_rdev; // device ID (if special file)
|
||||
off_t st_size; // total size, in bytes
|
||||
blksize_t st_blksize; // blocksize for filesystem I/O
|
||||
blkcnt_t st_blocks; // number of 512B blocks allocated
|
||||
time_t st_atime; // time of last access
|
||||
time_t st_mtime; // time of last modification
|
||||
time_t st_ctime; // time of last status change
|
||||
};
|
||||
```
|
||||
|
||||
### How to Kernel represents Open Files
|
||||
|
||||
* Descriptor table(per-process)
|
||||
* Open file table(shared by all processes)
|
||||
* v-node table(shared by all processes)
|
||||
|
||||
When a process opens a file, the kernel creates an entry in the per-process file descriptor table. Each entry contains a pointer to an entry in the open file table. Each entry in the open file table contains a pointer to an entry in the v-node table.
|
||||
|
||||
When a `fork` calls: the child process inherits copies of the parent's file descriptors. And the entry points to open file table's entry increasing `refcnt`.
|
||||
|
||||
### IO redirection
|
||||
|
||||
for example: `ls > foo.txt`
|
||||
|
||||
Answer: `dup2(oldfd, newfd)` it means copies descriptor table entry `oldfd` to `newfd`
|
||||
so `dup2(4, 1)` makes `stdout` point to the same open file as descriptor 4.
|
||||
|
||||
## stdio
|
||||
|
||||
The C standard library (`libc.so`) provides a collection of higher-level standard I/O functions.
|
||||
|
||||
* `fopen`, `fclose`, `fread`, `fwrite`, `fgets`, `fputs`, `fscanf`, `fprintf`
|
||||
|
||||
`stdio` models open files as **streams**, which are abstraction for a file descriptor and a buffer in memory.
|
||||
|
||||
```c
|
||||
extern FILE * stdin;
|
||||
extern FILE * stdout;
|
||||
extern FILE * stderr;
|
||||
```
|
||||
|
||||
### Buffered I/O
|
||||
|
||||
Application often read and write one char at a time. However, UNIX System calls `read` and `write` calls expensive. So we need buffered read & write; use unix `read` & `write` to **get a block of data into a buffer**. And then user application reads/writes **one char at a time from/to the buffer**; it is efficient because it is simple memory access.
|
||||
|
||||
`stdio` uses buffer. `printf` is not write immediately to the `stdout` file; it is stored in a buffer. And then when `fflush(stdout)`, `exit`, or return from `main`, the buffer is flushed to the file using `write` syscall.
|
||||
|
||||
## Remark
|
||||
|
||||
* UNIX IO
|
||||
* RIO package
|
||||
* stdio
|
||||
|
||||
|
||||
|
||||
When to use
|
||||
* stdio: disk or terminal files
|
||||
* unix io: signal handlers, or when you need absolute high performance
|
||||
* RIO: networking
|
||||
|
||||
### Binary
|
||||
|
||||
DO NOT USE:
|
||||
* text oriented I/O: `fgets`, `scanf`, `rio_readlineb`
|
||||
* string functions: `strlen`, `strcpy`, `strcat`, `strcmp`
|
||||
14
notes/12.md
Normal file
14
notes/12.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Memory
|
||||
|
||||
## Memory Hierachies
|
||||
|
||||
Storage media typically trades off speed with capacity
|
||||
|
||||
|
||||
## Locality
|
||||
|
||||
* **Temporal Locality**: recently referenced items are likely to be referenced again soon
|
||||
* **Spaitial Locality**: items with nearby addresses are tend to be referenced close together in time
|
||||
|
||||
## Cache Concepts
|
||||
|
||||
25
notes/8.md
Normal file
25
notes/8.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Linking
|
||||
|
||||
When Calling Functions in Other Files, We Need to Link Them Together. Because Caller do know how to pass data by calling convention, but do know where the callee is located in memory.
|
||||
|
||||
## Why Linker Needed?
|
||||
|
||||
* Modularity
|
||||
* Efficiency
|
||||
* Time: Seperate Compiliation
|
||||
* Space: Libraries
|
||||
|
||||
## What do Linker do?
|
||||
|
||||
1. Symbol Resolution
|
||||
2. Reloacation
|
||||
|
||||
## 3 Types of Object
|
||||
|
||||
1. Relocatable Object File (`*.o`)
|
||||
2. Executable File (`a.out`)
|
||||
3. Shared Object File (`*.so`)
|
||||
|
||||
|
||||
## ELF* Executable and Linkable Format
|
||||
|
||||
254
notes/9.md
Normal file
254
notes/9.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# Exception
|
||||
|
||||
An Exception is a **transfer of control** to the OS kernel in response to some event(like div by 0, overflow, ctrl+C). that is change in processor state.
|
||||
|
||||
**Exception Tables**
|
||||
|
||||
Each type of event has an unique exc number `k`: `k` is index into **exception table(interrupt vector table)**
|
||||
|
||||
Handler `k` is called each time exception `k` occurs.
|
||||
|
||||
## Asyncronous exceptions
|
||||
|
||||
It is caused by external events. Handler returns to next instruction.
|
||||
|
||||
for example: Timer interrupt, I/O interrupt.
|
||||
|
||||
## Syncronous exceptions
|
||||
|
||||
It is caused by events that occur as a result of exxecuting an current instruction.
|
||||
|
||||
* Traps
|
||||
* Intentional like procedure calls (e.g. system calls)
|
||||
* Returns to next instruction
|
||||
* Faults
|
||||
* Unintentional but possibly recoverable (e.g. page fault(recoverable), protection fault(not recoverable), floating point exception)
|
||||
* re-executes by kernel or aborts
|
||||
* Aborts
|
||||
* Unintentional and not recoverable (e.g. illegal instruction, parity error, machine check)
|
||||
* Aborts the program
|
||||
|
||||
## System Calls
|
||||
|
||||
Each x86-64 system call has a unique syscall number.
|
||||
|
||||
| Num | Name | Desc |
|
||||
| --- | ------ | --------------- |
|
||||
| 0 | read | read file |
|
||||
| 1 | write | write file |
|
||||
| 2 | open | open file |
|
||||
| 3 | close | close file |
|
||||
| 4 | stat | get file status |
|
||||
| 57 | fork | create process |
|
||||
| 59 | execve | execute program |
|
||||
| 62 | kill | send signal |
|
||||
|
||||
## Fault Example
|
||||
|
||||
### Page Fault
|
||||
```c
|
||||
int a[1000];
|
||||
main() {
|
||||
a[500] = 13;
|
||||
}
|
||||
```
|
||||
In this situation, a page containing `a[500]` is currently on disk, so page fault occurs, CPU cannot find the data in physical RAM. So kernel copy page from disk to memory, and return and re-executes the instruction `movl`
|
||||
|
||||
### Invalid Memory Ref
|
||||
|
||||
```c
|
||||
int a[1000];
|
||||
main() {
|
||||
a[5000] = 13;
|
||||
}
|
||||
```
|
||||
|
||||
In this situation, address `a[5000]` is invalid, so protection fault occurs, kernel terminates the program by sending `SIGSEGV` signal to the user process. Then user process exits with error code `Segmentation Fault`.
|
||||
|
||||
|
||||
## Process
|
||||
|
||||
An instance of a running program.
|
||||
|
||||
Process provides each program with two key abstractions:
|
||||
* Logical control flow
|
||||
* Each program seems to have **exclusive use of the CPU** provided by **context switching** of the kernel
|
||||
* Private address space
|
||||
* Each program seems to have **exclusive use of main memory** provided by **virtual memory** of the kernel
|
||||
|
||||
But in reality, computer runs multiple processes simultaneously by time-sharing CPU and multiplexing memory.
|
||||
|
||||
### Multiprocessing
|
||||
|
||||
Single processor executes multiple processes concurrently. process execution interleaved by time-slicing. Address spaces managed by virtual memory system. And register values for non-executing processes saved in memory.
|
||||
|
||||
Multicore processor share main memory each can execute a separate process. scheduling of processors onto cores done by kernel.
|
||||
|
||||
#### Concurrent Processes
|
||||
|
||||
Concurrency is **not at the exact same time**.
|
||||
|
||||
Two processes are **concurrent** if their flows **overlap in time**. Otherwise, they are **sequential**.
|
||||
|
||||
Control flows for concurrent processes are pysically disjoint in time. But user think that they are logically running in parallel.
|
||||
|
||||
* Execution time of instruction may vary because of the Nondeterminism of the System: OS scheduling, Interrupts, Cache miss or Page fault, I/O device delays.
|
||||
|
||||
#### Context Switching
|
||||
|
||||
Prcess are managed by a shared chunk of memory-resident OS code called the **kernel**.
|
||||
|
||||
What is important is that the kernel is **not a seprate process**. It is invoked by processes when they need OS services, or when exceptions occur. That is Part of the processor.
|
||||
|
||||
Control flow passes via a context switching.
|
||||
|
||||
## Syscall Error Handling
|
||||
|
||||
On error, Linux sys level function typically returns `-1` and sets the global variable `errno` to indicate the specific error.
|
||||
|
||||
Hard and fast rule:
|
||||
* You must check the return status of every system-level function
|
||||
* Only exception is the handful of functions that return void
|
||||
|
||||
Error reporting functions:
|
||||
```c
|
||||
void unix_error(char *msg) {
|
||||
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
Error handling wrappers:
|
||||
```c
|
||||
pid_t Fork(void) {
|
||||
pid_t pid;
|
||||
if ((pid = fork()) < 0) unix_error("Fork error");
|
||||
return pid;
|
||||
}
|
||||
```
|
||||
|
||||
## Creating and Terminating Processes
|
||||
|
||||
* `pid_t getpid(void)` returnes pid of current process
|
||||
* `pid_t getppid(void)` returns pid of parent process
|
||||
|
||||
We can think of a process as being in one of three states:
|
||||
* Running
|
||||
* Executing or waiting to be executed
|
||||
* Stopped
|
||||
* Process execution is suspended and will not be scheduled until futher notice
|
||||
* Terminated
|
||||
* Stopped permanently
|
||||
|
||||
### Terminating Processes
|
||||
|
||||
1. `return` from `main`
|
||||
2. call `exit(status)` function
|
||||
3. Receive a termination signal
|
||||
|
||||
```c
|
||||
void exit(int status);
|
||||
```
|
||||
|
||||
### Creating Process
|
||||
|
||||
Parent process can creates a new running child process by calling `fork()` system call.
|
||||
|
||||
```c
|
||||
int fork(void);
|
||||
```
|
||||
|
||||
it returns `0` to the newly created child, and returns **child's pid** to the parent.
|
||||
Child is almost identical to parent: child get an identical copy of the parent's virtual address space, file descriptors, and process state.
|
||||
But child has its own unique pid.
|
||||
|
||||
```c {cmd=gcc, args=[-O2 -x c $input_file -o 9_1.out]}
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
int main() {
|
||||
pid_t pid;
|
||||
int x = 1;
|
||||
pid = fork();
|
||||
if (pid == 0) { /* Child */
|
||||
printf("child: x=%d\n", ++x);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
printf("parent: x=%d\n", --x);
|
||||
exit(0);
|
||||
}
|
||||
```
|
||||
|
||||
```sh {cmd hide}
|
||||
while ! [ -r 9_1.out ]; do sleep .1; done; ./9_1.out
|
||||
```
|
||||
|
||||
Concurrent execution of parent and child processes. `fork` duplicates but separates address space.
|
||||
File descriptors are shared between parent and child like `stdout`, `stderr`.
|
||||
|
||||
Modeling fork with Process Graphs
|
||||
* Each vertex represents a process state
|
||||
* Directive Edges represent is ordering of execution.
|
||||
* Edge can be labeled with current value of variables
|
||||
|
||||
Any topological sort of the graph corresponds to a feasible total ordering.
|
||||
|
||||
|
||||
### Reaping Child Processes
|
||||
|
||||
When process terminates, it still consumes system resources. It is called a "zombie".
|
||||
|
||||
**"Reaping"** is performed by the parent by using `wait` or `waitpid`. And then parent is given exit status information. Finally kernel then deletes zombie child process.
|
||||
|
||||
If any parent terminates without reaping a child, then the orphaned child will be reaped by the `init` process (pid 1).
|
||||
However, long-running processes should reap their children to avoid accumulating zombies.
|
||||
|
||||
```c {cmd=gcc, args=[-O2 -x c $input_file -o 9_2.out]}
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main() {
|
||||
if(fork() == 0) {
|
||||
printf("Terminating child process\n");
|
||||
exit(0);
|
||||
} else {
|
||||
printf("Running Parent\n");
|
||||
sleep(1);
|
||||
printf("Parent exiting\n");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```sh {cmd hide}
|
||||
while ! [ -r 9_2.out ]; do sleep .1; done; ./9_2.out & ps -ef | grep "9_2.out" | grep -v grep;
|
||||
```
|
||||
|
||||
|
||||
```c
|
||||
pid_t wait(int * child_status);
|
||||
```
|
||||
|
||||
`wait` suspends current process until **one of its children** terminates.
|
||||
|
||||
* **return value**: pid of terminated child
|
||||
* **child_status**: if not `NULL`, the integer points will be set to the termination status(reason and exit status) of the child.
|
||||
* It can be checked by using macros defined in `wait.h`
|
||||
|
||||
## execve
|
||||
|
||||
```c
|
||||
int execve(const char *filename, char *const argv[], char *envp[]);
|
||||
```
|
||||
|
||||
Load and runs in the current process.
|
||||
|
||||
* `filename`: path of the executable file
|
||||
* `argv`: argument list
|
||||
* `envp`: environment variables "name=value"
|
||||
* `getenv`, `putenv`, `printenv`
|
||||
|
||||
It does **overwrite code, data, stack**. retain only **pid, file descriptors, signal context**.
|
||||
|
||||
Called **once and never returns on success**.
|
||||
BIN
pdf/L10.pdf
(Stored with Git LFS)
Normal file
BIN
pdf/L10.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
pdf/L11.pdf
(Stored with Git LFS)
Normal file
BIN
pdf/L11.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
pdf/L12.pdf
(Stored with Git LFS)
Normal file
BIN
pdf/L12.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
pdf/L13.pdf
(Stored with Git LFS)
Normal file
BIN
pdf/L13.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
pdf/L14.pdf
(Stored with Git LFS)
Normal file
BIN
pdf/L14.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
pdf/P3.pdf
(Stored with Git LFS)
Normal file
BIN
pdf/P3.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
Reference in New Issue
Block a user