CS110: Principles of Computer Systems
Winter 2021-2022
Stanford University
Instructors: Nick Troccoli and Jerry Cain
Creating processes and running other programs
Inter-process communication and Pipes
Signals
Virtual Memory
assign3: implement multiprocessing programs like "trace" (to trace another program's behavior) and "farm" (parallelize tasks)
assign4: implement your own shell!
We will designate times in our program where we stop doing other work and handle any pending signals.
We will designate times in our program where we stop doing other work and handle any pending signals.
sigwait()
sigwait() can be used to wait (block) on a signal to come in:
int sigwait(const sigset_t *set, int *sig);
Cannot wait on SIGKILL or SIGSTOP, nor synchronous signals like SIGSEGV or SIGFPE.
sigwait()
Here's a program that overrides the behavior for Ctl-z to print a message instead:
int main(int argc, char *argv[]) {
sigset_t monitoredSignals;
sigemptyset(&monitoredSignals);
sigaddset(&monitoredSignals, SIGTSTP);
printf("Just try to Ctl-z me!\n");
while (true) {
int delivered;
sigwait(&monitoredSignals, &delivered);
printf("\nReceived signal %d: %s\n", delivered, strsignal(delivered));
}
return 0;
}
sigwait()
Problem: what if the user hits Ctl-z before we reach line 9, or between sigwait calls? It won't be handled by our code!
int main(int argc, char *argv[]) {
sigset_t monitoredSignals;
sigemptyset(&monitoredSignals);
sigaddset(&monitoredSignals, SIGTSTP);
printf("Just try to Ctl-z me!\n");
while (true) {
int delivered;
sigwait(&monitoredSignals, &delivered);
printf("\nReceived signal %d: %s\n", delivered, strsignal(delivered));
}
return 0;
}
sigwait()
int main(int argc, char *argv[]) {
sigset_t monitoredSignals;
sigemptyset(&monitoredSignals);
sigaddset(&monitoredSignals, SIGTSTP);
sleep(2);
printf("Just try to Ctl-z me!\n");
while (true) {
int delivered;
sigwait(&monitoredSignals, &delivered);
printf("\nReceived signal %d: %s\n", delivered, strsignal(delivered));
sleep(2);
}
return 0;
}
Problem: what if the user hits Ctl-z before we reach line 9, or between sigwait calls? It won't be handled by our code!
We will designate times in our program where we stop doing other work and handle any pending signals.
The sigprocmask function lets us temporarily block signals of the specified types. Instead, they will be queued up and delivered when the block is removed.
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
Side note: forked children inherit blocked signals! We may wish to remove a block in the child.
Here's the same program from before, but blocking SIGTSTP as soon as possible:
int main(int argc, char *argv[]) {
sigset_t monitoredSignals;
sigemptyset(&monitoredSignals);
sigaddset(&monitoredSignals, SIGTSTP);
sigprocmask(SIG_BLOCK, &monitoredSignals, NULL);
printf("Just try to Ctl-z me!\n");
while (true) {
int delivered;
sigwait(&monitoredSignals, &delivered);
printf("\nReceived signal %d: %s\n", delivered, strsignal(delivered));
}
return 0;
}
Wait - if we call sigwait while signals are blocked, what happens?
Key insight: sigwait() doesn't care about blocked signals when it is called.
Let's rewrite our five-children.c program using sigwait instead of signal handlers.
Key Idea: signal blocks are inherited by child processes. Therefore we must unblock in the child - otherwise, e.g. if we are execvp'ing, that program won't receive SIGCHLD!
static void spawnChildren(size_t numChildren, sigset_t signalsToUnblock) {
for (size_t kid = 1; kid <= numChildren; kid++) {
if (fork() == 0) {
sigprocmask(SIG_UNBLOCK, &signalsToUnblock, NULL);
sleep(3 * kid); // sleep emulates "play" time
printf("Child #%zu tired... returns to parent.\n", kid);
exit(0);
}
}
}
Let's rewrite our five-children.c program using sigwait instead of signal handlers.
Problem: if we have code like this, the parent will not wake up unless a child returns:
while (numChildrenParentSees < kNumChildren) {
int delivered;
sigwait(&monitoredSignals, &delivered);
// doesn't work - parent may sleep for longer including sigwait,
// and parent won't hear signals while snoozing.
snooze(5);
}
Let's rewrite our five-children.c program using sigwait instead of signal handlers.
Idea: we can't sleep ourselves anymore. Let's use another signal to keep track of our "sleep time" instead; like an alarm clock.
Solution: there is a signal SIGALRM we can send to ourselves X seconds later.
// This function schedules a SIGALRM signal to be sent to us in 'duration' seconds.
static void setAlarm(double duration) {
int seconds = (int)duration;
int microseconds = 1000000 * (duration - seconds);
struct itimerval next = {{0, 0}, {seconds, microseconds}};
setitimer(ITIMER_REAL, &next, NULL);
}
Let's rewrite our five-children.c program using sigwait instead of signal handlers.
Challenges with multiple processes running:
Challenges with multiple processes running:
We need an extremely fast way to convert virtual addresses to physical addresses.
Challenges with multiple processes running:
How do we map virtual addresses to physical addresses for each process?
One idea: map each process's virtual address space to physical memory starting at some offset. Fast translation!
Problems:
Problems:
How do we map virtual addresses to physical addresses for each process?
Another idea: just map each process's segments to physical memory. That way we only map what is allocated.
How do we map virtual addresses to physical addresses for each process?
A third idea: map memory only as needed, in units of pages (a page is 4096 bytes or some other power of 2).
How do we map a virtual page to a physical one?
How do we map a virtual page to a physical one?
Here's an example of a page table for a single process:
7FFFFFFF80234 maps to 835432
0000DDEE65111 maps to 45D834
many additional entries
0000A9CF9AAAF maps to 12387B
0x7FFFFFFF80234230 & 0xFFF = 0x230
0x230 | (0x835432 << 12) = 0x83543223
Challenges with multiple processes running:
What if one process accesses the memory of another? -> Virtual address spaces are separate and monitored by OS
Challenges with multiple processes running:
What if we run out of physical memory? -> OS can play tricks to swap memory to disk when needed, and map addresses only on demand.
Next time: Introduction to multithreading