sp21 mt1 Solutions
sp21 mt1 Solutions
INSTRUCTIONS
This is your exam. Complete it either at exam.cs61a.org or, if that doesn’t work, by emailing course staff with your
solutions before the exam deadline.
This exam is intended for the student with email address <EMAILADDRESS>. If this is not your email address, notify
course staff immediately, as each exam is different. Do not distribute this exam PDF even after the exam ends, as
some students may be taking the exam in a different time zone.
For questions with circular bubbles, you should select exactly one choice.
# You must choose either this option
# Or this one, but not both!
For questions with square checkboxes, you may select multiple choices.
2 You could select this choice.
2 You could select this one too!
You may start your exam now. Your exam is due at <DEADLINE> Pacific Time. Go to the next page
to begin.
Exam generated for <EMAILADDRESS> 2
This is a proctored, closed-book exam. During the exam, you may not communicate with other people
regarding the exam questions or answers in any capacity. If there is something in a question that you believe
is open to interpretation, please use the “Clarifications” button to request a clarification. We will issue an
announcement if we believe your question merits one.
This exam has 6 questions of varying difficulty and length. Make sure you read through the exam completely
before starting to work on the exam.
We will overlook minor syntax errors in grading coding questions. You do not have to add necessary #include
statements.
(a)
Name
(b)
Student ID
(c)
Please read the following honor code: “I understand that this is a closed book exam. I hereby promise that
the answers that I give on the following exam are exclusively my own. I understand that I am allowed to
use one 8.5x11, double-sided, handwritten cheat-sheet of my own making, but otherwise promise not to
consult other people, physical resources (e.g. textbooks), or internet sources in constructing my answers.”
Type your full name below to acknowledge that you’ve read and agreed to this statement.
Exam generated for <EMAILADDRESS> 3
This is true because a syscall is a request for a service from the kernel. Since
these services are privileged and can only be executed by the kernel, there must
be a transfer from user mode to kernel mode.
Exam generated for <EMAILADDRESS> 4
ii.
Explain.
This is false because a syscall is a request for a service from the kernel. Since
these services are privileged and can only be executed by the kernel, there must
be a transfer from user mode to kernel mode.
Exam generated for <EMAILADDRESS> 5
ii.
Explain.
The run thread acquires the mutex before the main thread does, then the run
thread will exit (via func_name) without releasing the mutex, and the main
thread will hang.
Exam generated for <EMAILADDRESS> 6
ii.
Explain.
(f ) (2.5 points)
i.
Calling lseek() on an open socket file descriptor results in an error.
True
# False
ii.
Explain.
Socket FDs are abstractions of network connections. Even though you can interact
with them like files, they aren’t actually files on disk. As a result, lseek does not
work.
Exam generated for <EMAILADDRESS> 9
ii.
Explain.
Socket FDs are abstractions of network connections. Even though you can interact
with them like files, they aren’t actually files on disk. As a result, lseek does not
work.
Exam generated for <EMAILADDRESS> 10
int main() {
void* hmem = malloc(1);
for (int i = 0; i < 3; i++) {
pthread_t pid;
pthread_create(&pid, NULL, foo, hmem);
}
}
printf("%p\n", &foo)
printf("%p\n", &global)
2 printf("%p\n", &arg)
printf("%p\n", arg)
2 None of the above
Threads in the same process share the same address space, so foo and global are the same across all
threads. The same argument is given to each thread, so arg stays the same. Each thread has its own stack,
so the location of the argument (&arg) is different.
(f ) (2.0 pt)
Assume we have an empty file called red.txt. Consider the following code:
int main() {
int red = open("red.txt", O_RDWR);
if (fork() == 0) {
close(red);
} else {
int status;
char* str = "blue";
wait(&status);
write(red, str, 4);
}
}
What could be the contents of red.txt after we run this code?
blue
blu
The file is still empty
2 The program is guaranteed to error
2 None of the above
Closing a file descriptor in one process does not affect the file descriptor in another process, since the file
descriptor mapping was copied when we forked. Regardless, the write() call may write “blue”, “blu”, or
nothing, as low-level I/O does not guarantee full writes and can error.
Exam generated for <EMAILADDRESS> 13
Dual-mode operation is when the hardware provides (at least) two modes: user
mode and supervisor/kernel mode. Certain operations can only be executed
in supervisor mode whereas other operations can be executed in either user or
supervisor mode. Having dual-mode operation allows for protection between
different programs as well as operating system protection from malicious users.
Yes, putting a thread to sleep and waking it up could take more CPU cycles than
busy waiting.
fork() spawns a new process, and copies the entire address space and file descriptors
of the parent process. exec() spawns a new process, but loads an entirely new
program/process image.
Process A can invoke the write syscall and pass in the kernel address 0xC0A1E5CE
as the buffer. Without memory access checks, the kernel will blindly copy data
from kernel memory into a file that the user can then access and read.
Yes. File descriptors are separate per process. For example, we can fork, then
open different files in the parent and child.
Exam generated for <EMAILADDRESS> 15
(f ) (4.0 pt)
In general, why is it faster to have inter-process communication using a pipe over using a file?
Pipes exist in memory, whereas files exist on disk. Pipes have less overheard so
would be better in situations where efficiency is important.
If the lock is released between test_and_set and FUTEX_WAIT, then the acquiring
thread will be put to sleep but there will be no thread to wake it up.
Exam generated for <EMAILADDRESS> 16
void koopaling_stomp(void) {
pthread_mutex_lock(&lock);
waitingKoopalings++;
if (activeKoopalings + activeGoombas > 0) {
pthread_cond_wait(&koopaling_cv, &lock);
}
waitingKoopalings--;
activeKoopalings++;
pthread_mutex_unlock(&lock);
STOMP_MARIO();
pthread_mutex_lock(&lock);
activeKoopalings--;
if (waitingGoombas == 0 && waitingKoopalings > 0) {
pthread_cond_signal(&koopaling_cv);
} else if (waitingKoopalings > 0) {
pthread_cond_signal(&koopaling_cv);
} else if (waitingGoombas > 0) {
pthread_cond_broadcast(&goomba_cv);
}
pthread_mutex_unlock(&lock);
}
void goomba_party(void) {
pthread_mutex_lock(&lock);
waitingGoombas++;
if (activeKoopalings > 0 || activeGoombas == LIMIT) {
pthread_cond_wait(&goomba_cv, &lock);
}
waitingGoombas--;
activeGoombas++;
pthread_mutex_unlock(&lock);
SUPER_GOOMBA_PARTY();
pthread_mutex_lock(&lock);
activeGoombas--;
if (activeGoombas == 0 && waitingKoopalings > 0) {
pthread_cond_signal(&koopaling_cv);
Exam generated for <EMAILADDRESS> 17
No. The pthread_cond_wait calls are wrapped in if blocks, not while loops, so the
code does not handle spurious wakeups.
No. The above code does not enforce the constraint that there is a max of LIMIT
Goombas in the room at one time. If using Hoare Semantics, when all the Goombas
on the queue are broadcasted by the Koopaling, there is no second check after the
Goombas wake up that ensures the room capacity stays <= LIMIT. Thus, it’s
possible for more than LIMIT Goombas to be sleeping on the condition variable
queue and for all of them to enter the room during a broadcast.
The castle is currently empty. At the same time, a Koopaling and a Goomba
decide they want to enter the castle. Because each grab their own lock, it is
possible both check their respective ‘if ’ conditions at the same time, see there are
no activeGoombas or activeKoopalings, and enter the castle. This violates the
constraint that both parties cannot be in the castle at the same time. Other race
conditions involve potential interleavings of one thread trying to read the values
for waitingX/activeX while another thread is trying to increment / decrement
those values.
Exam generated for <EMAILADDRESS> 18
pipe(log_fds[log_index].fds)
break
total += bytes_written
Exam generated for <EMAILADDRESS> 20
log_fds[log_index].fds[0]
(f ) (2.0 pt)
[F]
total += bytes_written
The child processes will hang. Specifically, they will block on the read call, since
the write FD of the pipe has not been closed in each of the child processes.
Exam generated for <EMAILADDRESS> 21
/* Obtains the next `yield`ed value from the coroutine, if there is one, and
* stores it in `dest`. If and only if there is no value to be obtained
* (because the coroutine has finished executing), the return value will be
* `false`. */
bool next(coroutine_t* coroutine, void** dest);
An example of using this API is shown below:
// Finite coroutine iterating over the characters of a string
static void iterate(coroutine_t* coroutine, void* _string) {
char* string = (void*)_string;
while (*string) {
yield(coroutine, (void*)(*string++));
}
}
int main() {
char* alphabet = "ABCDEFG";
coroutine_t* iterate_coroutine = launch_coroutine(iterate, alphabet);
void* value;
while (next(iterate_coroutine, &value)) {
printf("%c ", (char)value);
}
printf("\n");
coroutine_t* say_hello_coroutine = launch_coroutine(say_hello, NULL);
}
The output from the above example should be:
A B C D E F G
Notice that Hello world! is not part of the output. This is because as mentioned before, our coroutines are
lazy, and there was no call to next made with say_hello_coroutine, therefore the function say_hello never
began running.
Exam generated for <EMAILADDRESS> 22
Fill in the blanks below to complete the implementation of coroutines. You can assume that all syscalls with not
error, and you can use as many lines as necessary. Your implementation must not be subject to race-conditions,
must not busy-wait, and must not leak resources (namely memory). Furthermore, we emphasize that this is a
coding question: all solutions must be given in the C programming language. No credit will be awarded for
pseudocode, the contents of comments, code which calls “helper functions” which do not exist, etc.
coroutine.c
struct coroutine {
void* value;
bool finished;
void (*function)(coroutine_t*, void*);
void* aux;
sem_t has_yielded;
sem_t next_requested;
pthread_t thread;
};
7. Reference Sheet
/*********************************** Threads ***********************************/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
int pthread_join(pthread_t thread, void **retval);
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_post(sem_t *sem); // up
int sem_wait(sem_t *sem); // down
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
No more questions.