Skip to content

Commit 1f4b939

Browse files
committed
Shell 4
1 parent cc7ffe8 commit 1f4b939

17 files changed

+709
-0
lines changed

shell4/.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
infinikill
2+
waitdemo
3+
waitlimit-block
4+
waitlimit-block2
5+
waitlimit-block3
6+
waitlimit-block4
7+
waitlimit-block5
8+
waitlimit-poll

shell4/GNUmakefile

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
PROGRAMS = waitdemo waitlimit-poll waitlimit-block waitlimit-block2 \
2+
waitlimit-block3 waitlimit-block4 waitlimit-block5 \
3+
infinikill
4+
all: $(PROGRAMS)
5+
6+
O ?= 2
7+
include ../common/rules.mk
8+
9+
%.o: %.c $(BUILDSTAMP)
10+
$(CC) $(CPPFLAGS) $(CFLAGS) $(DEPCFLAGS) $(O) -o $@ -c $<
11+
12+
$(PROGRAMS): %: %.o
13+
$(CC) $(CFLAGS) $(O) -o $@ $^
14+
15+
16+
clean:
17+
rm -f *.o *.core $(PROGRAMS)
18+
rm -rf $(DEPSDIR) *.dSYM
19+
20+
.PHONY: all clean

shell4/README.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Wait and race conditions
2+
3+
* waitdemo: wait for a process to die
4+
5+
# Waiting for multiple events
6+
7+
New problem: Let's wait for the process to die, OR 0.75 sec, whichever
8+
comes first. This relates to the functionality of the `timeout`
9+
command.
10+
11+
* waitlimit-poll
12+
13+
This polling solution works, but uses a lot of CPU.
14+
15+
* waitlimit-block
16+
17+
This blocking solution works without wasting CPU, relying on the fact
18+
that signals—such as `SIGCHLD`, which is delivered to a parent when a
19+
child exits—interrupt long-running system calls.
20+
21+
But does it really work? Remove the usleep() in the child and replace
22+
fork() with nfork(), then run it multiple times. You should eventually
23+
see a problem!
24+
25+
* waitlimit-block2
26+
27+
This blocking solution appears to solve the problem by using signals
28+
in a different way. This time, `SIGALRM` interrupts `waitpid`.
29+
30+
This seems more reliable in practice. But does *it* really work in
31+
*all* situations?
32+
33+
* waitlimit-block3–waitlimit-block5
34+
35+
These blocking solutions really do work.

shell4/helpers.h

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#ifndef HELPERS_H
2+
#define HELPERS_H
3+
#include <unistd.h>
4+
#include <stdio.h>
5+
#include <assert.h>
6+
#include <stdlib.h>
7+
#include <string.h>
8+
#include <ctype.h>
9+
#include <signal.h>
10+
#include <sys/wait.h>
11+
#include <sys/time.h>
12+
#include <sys/select.h>
13+
#include <sched.h>
14+
#include <errno.h>
15+
#include <fcntl.h>
16+
17+
// timestamp()
18+
// Return the current time as a double.
19+
20+
static inline double timestamp(void) {
21+
struct timeval tv;
22+
gettimeofday(&tv, NULL);
23+
return tv.tv_sec + tv.tv_usec / 1000000.0;
24+
}
25+
26+
27+
// nfork()
28+
// Like `fork()`, but nondeterministically runs the child first.
29+
//
30+
// This is conceptually the same as `fork`, since the OS is allowed to
31+
// run either process first (or to run them in parallel on multiple
32+
// cores), but in practice it is rare that the child runs first. This
33+
// function is useful for shaking out race conditions.
34+
35+
static inline pid_t nfork(void) {
36+
pid_t p = fork();
37+
if (p > 0) {
38+
struct timeval tv;
39+
gettimeofday(&tv, NULL);
40+
if (tv.tv_usec % 7 >= 4)
41+
usleep(tv.tv_usec % 7);
42+
}
43+
return p;
44+
}
45+
46+
47+
// handle_signal(signo, handler)
48+
// Install `handler` as the signal handler for `signo`.
49+
// The `handler` is automatically re-installed after signal delivery.
50+
// Has the same interface as `signal()` (`man 2 signal`), but is portable.
51+
52+
static inline int handle_signal(int signo, void (*handler)(int)) {
53+
struct sigaction sa;
54+
sa.sa_handler = handler; // call `handler` on signal
55+
sigemptyset(&sa.sa_mask); // don't block other signals in handler
56+
sa.sa_flags = 0; // don't restart system calls
57+
return sigaction(signo, &sa, NULL);
58+
}
59+
60+
61+
// make_nonblocking(fd)
62+
// Make file descriptor `fd` nonblocking: attempts to read
63+
// from `fd` will fail with errno EWOULDBLOCK if no data is
64+
// available, and attempts to write to `fd` will fail with
65+
// errno EWOULDBLOCK if no space is available. Not all file
66+
// descriptors can be made nonblocking, but pipes and network
67+
// sockets can.
68+
69+
static inline int make_nonblocking(int fd) {
70+
return fcntl(fd, F_SETFL, O_NONBLOCK);
71+
}
72+
73+
#endif

shell4/infinikill.c

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "helpers.h"
2+
#include <sched.h>
3+
4+
int main(int argc, char** argv) {
5+
if (argc != 2) {
6+
fprintf(stderr, "Usage: infinikill PID\n");
7+
exit(1);
8+
}
9+
10+
pid_t p = strtol(argv[1], NULL, 0);
11+
double start_time = timestamp();
12+
13+
size_t n = 0;
14+
while (1) {
15+
int r = kill(p, SIGUSR1);
16+
if (r != 0) {
17+
fprintf(stderr, "%.6f: %s after %zu iterations\n",
18+
timestamp() - start_time, strerror(errno), n);
19+
exit(1);
20+
}
21+
++n;
22+
sched_yield();
23+
}
24+
}

shell4/waitdemo.c

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include "helpers.h"
2+
3+
int main(void) {
4+
fprintf(stderr, "%.6f: parent: Hello from pid %d\n", 0.0, getpid());
5+
6+
// Start a child
7+
double start_time = timestamp();
8+
pid_t p1 = fork();
9+
assert(p1 >= 0);
10+
if (p1 == 0) {
11+
usleep(500000);
12+
fprintf(stderr, "%.6f: child: Goodbye from pid %d\n",
13+
timestamp() - start_time, getpid());
14+
exit(0);
15+
}
16+
17+
// Wait for the child and print its status
18+
int status;
19+
pid_t exited_pid = waitpid(p1, &status, 0);
20+
assert(exited_pid == p1);
21+
22+
double elapsed = timestamp() - start_time;
23+
if (WIFEXITED(status))
24+
fprintf(stderr, "%.6f: parent: Child exited with status %d\n",
25+
elapsed, WEXITSTATUS(status));
26+
else
27+
fprintf(stderr, "%.6f: parent: Child exited abnormally [%x]\n",
28+
elapsed, status);
29+
}

shell4/waitlimit-block.c

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include "helpers.h"
2+
3+
void signal_handler(int signal) {
4+
(void) signal;
5+
}
6+
7+
int main(void) {
8+
fprintf(stderr, "%.6f: parent: Hello from pid %d\n", 0.0, getpid());
9+
10+
// Demand that SIGCHLD interrupt system calls
11+
int r = handle_signal(SIGCHLD, signal_handler);
12+
assert(r >= 0);
13+
14+
// Start a child
15+
double start_time = timestamp();
16+
pid_t p1 = fork();
17+
assert(p1 >= 0);
18+
if (p1 == 0) {
19+
usleep(500000);
20+
fprintf(stderr, "%.6f: child: Goodbye from pid %d\n",
21+
timestamp() - start_time, getpid());
22+
exit(0);
23+
}
24+
25+
// Wait for the child and print its status
26+
r = usleep(750000);
27+
if (r == -1 && errno == EINTR)
28+
fprintf(stderr, "%.6f: parent: usleep interrupted by signal\n",
29+
timestamp() - start_time);
30+
31+
int status;
32+
pid_t exited_pid = waitpid(p1, &status, WNOHANG);
33+
assert(exited_pid == 0 || exited_pid == p1);
34+
35+
double elapsed = timestamp() - start_time;
36+
if (exited_pid == 0)
37+
fprintf(stderr, "%.6f: parent: Child timed out\n", elapsed);
38+
else if (WIFEXITED(status))
39+
fprintf(stderr, "%.6f: parent: Child exited with status %d\n",
40+
elapsed, WEXITSTATUS(status));
41+
else
42+
fprintf(stderr, "%.6f: parent: Child exited abnormally [%x]\n",
43+
elapsed, status);
44+
}

shell4/waitlimit-block2.c

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include "helpers.h"
2+
3+
void signal_handler(int signal) {
4+
(void) signal;
5+
}
6+
7+
int main(void) {
8+
fprintf(stderr, "%.6f: parent: Hello from pid %d\n", 0.0, getpid());
9+
10+
// Start a child
11+
double start_time = timestamp();
12+
pid_t p1 = fork();
13+
assert(p1 >= 0);
14+
if (p1 == 0) {
15+
usleep(500000);
16+
fprintf(stderr, "%.6f: child: Goodbye from pid %d\n",
17+
timestamp() - start_time, getpid());
18+
exit(0);
19+
}
20+
21+
// Handle SIGALRM; no action is required
22+
int r = handle_signal(SIGALRM, signal_handler);
23+
assert(r >= 0);
24+
25+
// Set alarm for 0.75 seconds
26+
struct itimerval itimer;
27+
timerclear(&itimer.it_interval);
28+
itimer.it_value.tv_sec = 0;
29+
itimer.it_value.tv_usec = 750000;
30+
r = setitimer(ITIMER_REAL, &itimer, NULL);
31+
assert(r >= 0);
32+
33+
// Wait for the child and print its status
34+
int status;
35+
pid_t exited_pid = waitpid(p1, &status, 0);
36+
assert(exited_pid == p1 || (exited_pid == -1 && errno == EINTR));
37+
38+
double elapsed = timestamp() - start_time;
39+
if (exited_pid == -1)
40+
fprintf(stderr, "%.6f: parent: Child timed out\n", elapsed);
41+
else if (WIFEXITED(status))
42+
fprintf(stderr, "%.6f: parent: Child exited with status %d\n",
43+
elapsed, WEXITSTATUS(status));
44+
else
45+
fprintf(stderr, "%.6f: parent: Child exited abnormally [%x]\n",
46+
elapsed, status);
47+
}

shell4/waitlimit-block3.c

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include "helpers.h"
2+
int signalpipe[2];
3+
4+
void signal_handler(int signal) {
5+
(void) signal;
6+
ssize_t r = write(signalpipe[1], "!", 1);
7+
assert(r == 1);
8+
}
9+
10+
int main(void) {
11+
fprintf(stderr, "%.6f: parent: Hello from pid %d\n", 0.0, getpid());
12+
13+
int r = pipe(signalpipe);
14+
assert(r >= 0);
15+
r = handle_signal(SIGCHLD, signal_handler);
16+
assert(r >= 0);
17+
18+
// Start a child
19+
double start_time = timestamp();
20+
pid_t p1 = fork();
21+
assert(p1 >= 0);
22+
if (p1 == 0) {
23+
usleep(500000);
24+
fprintf(stderr, "%.6f: child: Goodbye from pid %d\n",
25+
timestamp() - start_time, getpid());
26+
exit(0);
27+
}
28+
29+
// Wait for 0.75 sec, or until a byte is written to `signalpipe`,
30+
// whichever happens first
31+
struct timeval timeout = { 0, 750000 };
32+
fd_set fds;
33+
FD_SET(signalpipe[0], &fds);
34+
r = select(signalpipe[0] + 1, &fds, NULL, NULL, &timeout);
35+
36+
int status;
37+
pid_t exited_pid = waitpid(p1, &status, WNOHANG);
38+
assert(exited_pid == 0 || exited_pid == p1);
39+
40+
double elapsed = timestamp() - start_time;
41+
if (exited_pid == 0)
42+
fprintf(stderr, "%.6f: parent: Child timed out\n", elapsed);
43+
else if (WIFEXITED(status))
44+
fprintf(stderr, "%.6f: parent: Child exited with status %d\n",
45+
elapsed, WEXITSTATUS(status));
46+
else
47+
fprintf(stderr, "%.6f: parent: Child exited abnormally [%x]\n",
48+
elapsed, status);
49+
}

0 commit comments

Comments
 (0)