Principles of Computer Systems
Spring 2019
Stanford University
Computer Science Department
Instructors: Chris Gregg and
Phil Levis
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.)Consider the following program. Assume that each call to printf flushes its output to the console in full, and further assume that none of the system calls fail in any unpredictable way (e.g. fork never fails, and waitpid only returns -1 because there aren’t any child processes at the moment it decides on its return value).
static pid_t pid; // necessarily global so handler1 has
// access to it
static int counter = 0;
static void handler1(int unused) {
counter++;
printf("counter = %d\n", counter);
kill(pid, SIGUSR1);
}
static void handler2(int unused) {
counter += 10;
printf("counter = %d\n", counter);
exit(0);
}
int main(int argc, char *argv[]) {
signal(SIGUSR1, handler1);
if ((pid = fork()) == 0) {
signal(SIGUSR1, handler2);
kill(getppid(), SIGUSR1);
while (true) {}
}
if (waitpid(-1, NULL, 0) > 0) {
counter += 1000;
printf("counter = %d\n", counter);
}
return 0;
}
Now further assume the call to exit(0) has also been removed from the handler2 function . Are there any other potential program outputs? If not, explain why. If so, what are they?
Consider the following program. Assume that each call to printf flushes its output to the console in full, and further assume that none of the system calls fail in any unpredictable way (e.g. fork never fails, and waitpid only returns -1 because there aren’t any child processes at the moment it decides on its return value).
counter = 1
counter = 10
counter = 1001
static pid_t pid; // necessarily global so handler1 has
// access to it
static int counter = 0;
static void handler1(int unused) {
counter++;
printf("counter = %d\n", counter);
kill(pid, SIGUSR1);
}
static void handler2(int unused) {
counter += 10;
printf("counter = %d\n", counter);
exit(0);
}
int main(int argc, char *argv[]) {
signal(SIGUSR1, handler1);
if ((pid = fork()) == 0) {
signal(SIGUSR1, handler2);
kill(getppid(), SIGUSR1);
while (true) {}
}
if (waitpid(-1, NULL, 0) > 0) {
counter += 1000;
printf("counter = %d\n", counter);
}
return 0;
}
Consider the following program. Assume that each call to printf flushes its output to the console in full, and further assume that none of the system calls fail in any unpredictable way (e.g. fork never fails, and waitpid only returns -1 because there aren’t any child processes at the moment it decides on its return value).
static pid_t pid; // necessarily global so handler1 has
// access to it
static int counter = 0;
static void handler1(int unused) {
counter++;
printf("counter = %d\n", counter);
kill(pid, SIGUSR1);
}
static void handler2(int unused) {
counter += 10;
printf("counter = %d\n", counter);
exit(0);
}
int main(int argc, char *argv[]) {
signal(SIGUSR1, handler1);
if ((pid = fork()) == 0) {
signal(SIGUSR1, handler2);
kill(getppid(), SIGUSR1);
while (true) {}
}
if (waitpid(-1, NULL, 0) > 0) {
counter += 1000;
printf("counter = %d\n", counter);
}
return 0;
}
So, another possible output would be:
counter = 1
counter = 1001
Consider the following program. Assume that each call to printf flushes its output to the console in full, and further assume that none of the system calls fail in any unpredictable way (e.g. fork never fails, and waitpid only returns -1 because there aren’t any child processes at the moment it decides on its return value).
static pid_t pid; // necessarily global so handler1 has
// access to it
static int counter = 0;
static void handler1(int unused) {
counter++;
printf("counter = %d\n", counter);
kill(pid, SIGUSR1);
}
static void handler2(int unused) {
counter += 10;
printf("counter = %d\n", counter);
exit(0);
}
int main(int argc, char *argv[]) {
signal(SIGUSR1, handler1);
if ((pid = fork()) == 0) {
signal(SIGUSR1, handler2);
kill(getppid(), SIGUSR1);
while (true) {}
}
if (waitpid(-1, NULL, 0) > 0) {
counter += 1000;
printf("counter = %d\n", counter);
}
return 0;
}
pthreads, which comes with all standard UNIX and Linux installations of gcc, provides thread support, along with other related concurrency directives..static void *recharge(void *args) {
printf("I recharge by spending time alone.\n");
return NULL;
}
static const size_t kNumIntroverts = 6;
int main(int argc, char *argv[]) {
printf("Let's hear from %zu introverts.\n", kNumIntroverts);
pthread_t introverts[kNumIntroverts];
for (size_t i = 0; i < kNumIntroverts; i++)
pthread_create(&introverts[i], NULL, recharge, NULL);
for (size_t i = 0; i < kNumIntroverts; i++)
pthread_join(introverts[i], NULL);
printf("Everyone's recharged!\n");
return 0;
}pthread_t handles.pthread_t (via pthread_create) by installing recharge as the thread routine each pthread_t should execute.void * and return a void *. That's the best C can do to support generic programming.pthread_create is used to set a thread priority and other attributes. We can just pass in NULL if all threads should have the same priority. That's what we do here.NULL.recharge threads is eligible for processor time the instant the surrounding pthread_t has been initialized.pthread_join is to threads what waitpid is to processes.pthread_join can be used to catch a thread routine's return value. If we don't care to receive it, we can pass in NULL to ignore it.static const char *kFriends[] = {
"Langston", "Manan", "Edward", "Jordan", "Isabel", "Anne",
"Imaginary"
};
static const size_t kNumFriends = sizeof(kFriends)/sizeof(kFriends[0]) - 1; // count excludes imaginary friend!
static void *meetup(void *args) {
const char *name = kFriends[*(size_t *)args];
printf("Hey, I'm %s. Empowered to meet you.\n", name);
return NULL;
}
int main() {
printf("Let's hear from %zu friends.\n", kNumFriends);
pthread_t friends[kNumFriends];
for (size_t i = 0; i < kNumFriends; i++)
pthread_create(&friends[i], NULL, meetup, &i);
for (size_t j = 0; j < kNumFriends; j++)
pthread_join(friends[j], NULL);
printf("Is everyone accounted for?\n");
return 0;
}cgregg@myth63$ ./friends
Let's hear from 6 friends.
Hey, I'm Jordan. Empowered to meet you.
Hey, I'm Anne. Empowered to meet you.
Hey, I'm Anne. Empowered to meet you.
Hey, I'm Anne. Empowered to meet you.
Hey, I'm Anne. Empowered to meet you.
Hey, I'm Imaginary. Empowered to meet you.
Is everyone accounted for?
cgregg@myth63$ ./friends
Let's hear from 6 friends.
Hey, I'm Jordan. Empowered to meet you.
Hey, I'm Anne. Empowered to meet you.
Hey, I'm Imaginary. Empowered to meet you.
Hey, I'm Imaginary. Empowered to meet you.
Hey, I'm Imaginary. Empowered to meet you.
Hey, I'm Imaginary. Empowered to meet you.
Is everyone accounted for?
cgregg@myth63$ ./friendsLet's hear from 6 friends.
Hey, I'm Jordan. Empowered to meet you.
Hey, I'm Isabel. Empowered to meet you.
Hey, I'm Isabel. Empowered to meet you.
Hey, I'm Anne. Empowered to meet you.
Hey, I'm Anne. Empowered to meet you.
Hey, I'm Imaginary. Empowered to meet you.
Is everyone accounted for?meetup references its incoming parameter now, and that pthread_create accepts the address of the surrounding loop's index variable i via its fourth parameter. pthread_create's fourth argument is always passed verbatim as the single argument to the thread routine.i without regard for the fact that i's address was shared with six child threads.
i, but the value of i itself. That assumption of course, is incorrect, as it captures the address and nothing else.i (even after it goes out of scope) is constant, but its contents evolve in parallel with the execution of the six meetup threads. *(size_t *)args takes a snapshot of whatever i happens to contain at the time it's evaluated.meetup threads only execute after the main thread has worked through all of its first for loop. The space at &i is left with a 6, and that's why Imaginary is printed so often.const char * instead. Snapshots of the const char * pointers are passed verbatim to meetup. The strings themselves are constants.static const char *kFriends[] = {
"Langston", "Manan", "Edward", "Jordan", "Isabel", "Anne",
"Imaginary"
};
static const size_t kNumFriends = sizeof(kFriends)/sizeof(kFriends[0]) - 1; // count excludes imaginary friend!
static void *meetup(void *args) {
const char *name = args;
printf("Hey, I'm %s. Empowered to meet you.\n", name);
return NULL;
}
int main() {
printf("%zu friends meet.\n", kNumFriends);
pthread_t friends[kNumFriends];
for (size_t i = 0; i < kNumFriends; i++)
pthread_create(&friends[i], NULL, meetup, (void *) kFriends[i]); // this line is different than before, too
for (size_t i = 0; i < kNumFriends; i++)
pthread_join(friends[i], NULL);
printf("All friends are real!\n");
return 0;
}cgregg@myth63$ ./friends
Let's hear from 6 friends.
Hey, I'm Langston. Empowered to meet you.
Hey, I'm Manan. Empowered to meet you.
Hey, I'm Edward. Empowered to meet you.
Hey, I'm Jordan. Empowered to meet you.
Hey, I'm Isabel. Empowered to meet you.
Hey, I'm Anne. Empowered to meet you.
Is everyone accounted for?
cgregg@myth63$ ./friends
Let's hear from 6 friends.
Hey, I'm Langston. Empowered to meet you.
Hey, I'm Manan. Empowered to meet you.
Hey, I'm Edward. Empowered to meet you.
Hey, I'm Isabel. Empowered to meet you.
Hey, I'm Jordan. Empowered to meet you.
Hey, I'm Anne. Empowered to meet you.
Is everyone accounted for?
cgregg@myth63$ ./friends
Let's hear from 6 friends.
Hey, I'm Langston. Empowered to meet you.
Hey, I'm Edward. Empowered to meet you.
Hey, I'm Manan. Empowered to meet you.
Hey, I'm Anne. Empowered to meet you.
Hey, I'm Jordan. Empowered to meet you.
Hey, I'm Isabel. Empowered to meet you.
Is everyone accounted for?