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?