Principles of Computer Systems
Autumn 2019
Stanford University
Computer Science Department
Lecturers: Chris Gregg and
Philip Levis
sigprocmask
lets us define define critical sections
if
statement is executing
counter_1
, it's 1counter_2
, it's 0printf
, read counter_1
, it's 0, read counter_1
, it's 0printf
it means they weren't equal at some point...sigprocmask(SIG_BLOCK, &mask, NULL);
counter_1++;
gettimeofday(&now, NULL);
counter_2++;
sigprocmask(SIG_UNBLOCK, &mask, NULL);
if (counter_1 != counter_2) {
printf("%li.%06li counter_1: %d, counter_2: %d\n",
now.tv_sec, now.tv_usec,
counter_1, counter_2);
return -1;
}
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 designwaitpid
to be in the SIGCHLD handlerpause(2)
allows us to sleep until a signal handler executes, but this is too coarse: we need to go back to sleep if the signal 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);
}
It's possible the foreground process finishes and reapProcesses
is invoked on its behalf before
normal execution flow updates fgpid
. If that happens, the shell will pause forever
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 set
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
fgpid
fgpid
static void waitForForegroundProcess(pid_t pid) {
fgpid = pid;
unblockSIGCHLD();
while (fgpid == pid) {
pause();
}
}
sigprocmask
: there is always a small window between when the signal is unblocked and pause is calledstatic 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 atomically changes the process signal mask and has it sleep until it handles a signal. 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;
}
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
Questions, please