Principles of Computer Systems
Autumn 2019
Stanford University
Computer Science Department
Lecturers: Chris Gregg and
Philip Levis
sigprocmask
lets us define define critical sections
Signal | SIGALRM | SIGCHLD | SIGUSR1 | SIGUSR2 | SIGINT |
---|---|---|---|---|---|
Pending | 0 | 1 | 0 | 0 | 0 |
Enabled | 1 | 0 | 1 | 0 | 1 |
int x, y;
x = 5;
y = 7;
printf("%d %d\n", x, y)
int x, y;
x = 5;
y = 7;
print_ints(&x, &y);
Your compiler doesn't have to allocate space for x and y -- it can just pass constants to printf.
Your compiler has to allocate space for x and y -- it doesn't know what print_ints will do.
int x, y;
...
x++;
y = x + 1;
print_ints(&x, &y);
Your compiler could have x in a register r, then store r + 1 in x and r + 2 in y. If x were modified between lines 3 and 4, y will still be stored as r + 2.
simplesh
example from last week. The full program is right here.
simplesh
, we had no choice, because we hadn't learned about signals or signal handlers yet.// simplesh.c
int main(int argc, char *argv[]) {
while (true) {
// code to initialize command, argv, and isbg omitted for brevity
pid_t pid = fork();
if (pid == 0) execvp(argv[0], argv);
if (isbg) {
printf("%d %s\n", pid, command);
} else {
waitpid(pid, NULL, 0);
}
}
printf("\n");
return 0;
}
waitpid
to halt the shell until its foreground process has exited.
waitpid
executes in the main loopSIGCHLD
handler will run too, it calls waitpid
pid
, one will return an errorwaitpid
expect to return an error (e.g., suppress error messages), but this is hacking around a poor designpause(2)
allows us to sleep until a signal handler executes, but this is too coarse: we need to go back to sleep if it wasn't for the foreground processpause()
to sleep until a signal handler executeswaitpid to
rule them all, one waitpid
to find them
static void reapProcesses(int sig) {
while (true) {
pid_t pid = waitpid(-1, NULL, WNOHANG);
if (pid <= 0) {
break;
} else if (pid == fgpid) {
fgpid = 0;
}
}
}
static void waitForForegroundProcess(pid_t pid) {
fgpid = pid;
while (fgpid == pid) {
pause();
}
}
pid_t pid = forkProcess();
if (pid == 0) {...}
if (isbg) {
printf("%d %s\n", pid, command);
} else {
waitForForegroundProcess(pid);
}
reapProcesses
is invoked on its behalf before
normal execution flow updates fgpid
. If that happens, the shell will spin forever and never advance up to the shell prompt.fgpid
, such that reapProcesses
does not execute before we set fgpid
sigprocmask
to block SIGCHLD before fork
, then unblock in child and in parent after fgpid
is setfgpid
fgpid
static void waitForForegroundProcess(pid_t pid) {
fgpid = pid;
unblockSIGCHLD();
while (fgpid == pid) {
pause();
}
}
static void waitForForegroundProcess(pid_t pid) {
fgpid = pid;
unblockSIGCHLD();
while (fgpid == pid) {
pause();
}
}
waitForForegroundProcess
on the prior slide is that each lifts the block on SIGCHLD
before going to sleep via pause
.SIGCHLD
you're relying on to notify the parent that the child has finished could very well arrive in the narrow space between lift and sleep. That would inspire deadlock.pause
called sigsuspend
, which asks that the OS change the blocked set to the one provided, but only after the caller has been forced off the CPU. When some unblocked signal arrives, the process gets the CPU, the signal is handled, the original blocked set is restored, and sigsuspend
returns.farm
and your Assignment 4 stsh
.sigsuspend
to the rescue// simplesh-all-better.c
static void waitForForegroundProcess(pid_t pid) {
fgpid = pid;
sigset_t empty;
sigemptyset(&empty);
while (fgpid == pid) {
sigsuspend(&empty);
}
unblockSIGCHLD();
}
printf
calls succeed, and that all calls to printf
are atomic. Assume nothing about scheduling or time slice durations.static void bat(int unused) {
printf("pirate\n");
exit(0);
}
int main(int argc, char *argv[]) {
signal(SIGUSR1, bat);
pid_t pid = fork();
if (pid == 0) {
printf("ghost\n");
return 0;
}
kill(pid, SIGUSR1);
printf("ninja\n"); return 0;
}
printf
calls succeed, and that all calls to printf
are atomic. Assume nothing about scheduling or time slice durations.static void bat(int unused) {
printf("pirate\n");
exit(0);
}
int main(int argc, char *argv[]) {
signal(SIGUSR1, bat);
pid_t pid = fork();
if (pid == 0) {
printf("ghost\n");
return 0;
}
kill(pid, SIGUSR1);
printf("ninja\n"); return 0;
}
printf
calls succeed, and that all calls to printf
are atomic. Assume nothing about scheduling or time slice durations.int main(int argc, char *argv[]) {
pid_t pid;
int counter = 0;
while (counter < 2) {
pid = fork();
if (pid > 0) break;
counter++;
printf("%d", counter);
}
if (counter > 0) printf("%d", counter);
if (pid > 0) {
waitpid(pid, NULL, 0);
counter += 5;
printf("%d", counter);
}
return 0;
}
printf
calls succeed, and that all calls to printf
are atomic. Assume nothing about scheduling or time slice durations.int main(int argc, char *argv[]) {
pid_t pid;
int counter = 0;
while (counter < 2) {
pid = fork();
if (pid > 0) break;
counter++;
printf("%d", counter);
}
if (counter > 0) printf("%d", counter);
if (pid > 0) {
waitpid(pid, NULL, 0);
counter += 5;
printf("%d", counter);
}
return 0;
}
>
of the counter
> 0
test is changed to a >=
, then counter
values of zeroes would be included in each possible output. How many different outputs are now possible? (No need to list the outputs—just present the number.)printf
calls succeed, and that all calls to printf
are atomic. Assume nothing about scheduling or time slice durations.int main(int argc, char *argv[]) {
pid_t pid;
int counter = 0;
while (counter < 2) {
pid = fork();
if (pid > 0) break;
counter++;
printf("%d", counter);
}
if (counter > 0) printf("%d", counter);
if (pid > 0) {
waitpid(pid, NULL, 0);
counter += 5;
printf("%d", counter);
}
return 0;
}
>
of the counter
> 0
test is changed to a >=
, then counter
values of zeroes would be included in each possible output. How many different outputs are now possible? (No need to list the outputs—just present the number.)If we have time...
signal 1570987083.612345 counter_1: 0, counter_2: 1 1570987083.612345 counter_1: 0, counter_2: 1 signal
??????
user
stack
kernel
kernel
stack
user CPU context
signal
user
stack
kernel
kernel
stack
signal
printf
printf
$ man signal-safety