E2 Answers
E2 Answers
This exam is closed book, closed notes. All cell phones must be turned off. No calculators may be
used.
These exam questions must be returned at the end of the exam, but we will not grade anything in
this booklet.
For all questions except question 71, pick the one best answer.
Good luck!
Part 1: True/False from Virtualization [1 point each]
Designate if the statement is True (a) or False (b).
21) The clock frequency of CPUs has been increasing exponentially each year since 1985.
False; clock frequency has not been increasing exponentially in more recent years, requiring multiple cores
to achieve the same type of speed-up.
22) Threads that are part of the same process share the same stack pointer.
False; each thread has its own stack and stack pointer so it can have own call stack and local
23) Threads that are part of the same process share the same page table base register.
True; the threads have the same address space.
24) Threads that are part of the same process share the same general-purpose registers.
False; each thread must have own registers (saved and restored when that thread is not executing).
25) Context-switching between threads of the same process requires flushing the TLB or tracking an ASID
in the TLB.
False; since the threads share the same address space, they share the same VPN->PPN mappings.
26) With kernel-level threads, if one thread of a process blocks, all the threads of that process will also be
blocked.
False; with kernel-level threads, the OS is aware of the state of each thread and just that thread will be
blocked.
27) The hardware atomic exchange instruction requires that interrupts are disabled during that instruction.
False; this instruction has nothing to do with disabling interrupts.
28) A lock that performs spin-waiting can provide fairness across threads (i.e., threads receive the lock in
the order they requested the lock).
True; the only question that most people answered incorrectly. Spin-waiting has poor efficiency, but it can
still provide fairness. Think about the implementation we described for ticket locks uses spin-waiting but
is still fair.
29) A lock implementation should always block instead of spin if it will always be used only on a
uniprocessor.
True; must block to relinquish processor to thread currently holding lock so that thread will relinquish lock.
30) On a multiprocessor, a lock implementation should spin instead of block if it is known that the lock will
be available before the time of required for a context-switch.
True; the amount of wasted time is then less than the amount of time wasted for a context switch.
31) Periodically yielding the processor while spin-waiting reduces the amount of wasted time to be
proportional to the duration of a time-slice.
False; the wasted time is proportional to the cost of a context-switch.
32) A semaphore can be used to provide mutual exclusion.
True; initialize the semaphore to 1.
33) When a thread returns from a call to cond_wait() it can safely assume that it holds the corresponding
mutex.
True; the thread relinquishes the mutex while in cond_wait() but acquires it again before it returns.
34) A thread calling cond_signal() will block until the corresponding mutex is available.
False; cond_signal doesnt need to do anything with the mutex.
35) With producer/consumer relationships and a finite-sized circular shared buffer, consuming threads
must wait until there is an empty element of the buffer.
False; consumers must wait until there is a full element.
36) The performance of broadcasting to a condition variable decreases as the number of waiting threads
increases.
True; with more waiting threads, more threads are awoken which must be scheduled (all incurring a context-
switch).
37) To implement a thread_join() operation with a condition variable, the thread_exit() code will call
cond_wait().
False; thread_exit() calls cond_signal().
38) A goal of a reader/writer lock is to ensure that either multiple readers hold the lock or just one writer
holds the lock.
True.
39) Building a condition variable on top of a semaphore is easier than building a semaphore over condition
variables and locks.
False; this is difficult
40) As the amount of code a mutex protects increases, the amount of concurrency in the application
increases.
False; with more code within a mutex, concurrency decreases (imagine one big lock around all the code).
41) Ordering problems in multi-threaded applications can be most easily fixed with locks.
False; ordering is fixed with condition variables or semaphores.
42) A wait-free algorithm relies on condition variables instead of locks.
False; wait-free algorithms use atomic hardware instructions.
43) Deadlock can be avoided if threads acquire all potentially needed locks atomically.
True; this breaks the hold-and-wait condition which is necessary for deadlock.
44) Deadlock can be avoided by having only a single lock within a multi-threaded application.
True; this breaks the hold-and-wait condition which is necessary for deadlock.
Part 3. Fork Processes vs. Creating Threads [20 points]
The
first
set
of
questions
looks
at
creating
new
processes
with
fork().
For
the
next
two
questions,
assume
the
following
code
is
compiled
and
run
on
a
modern
Linux
machine.
Assume
any
irrelevant
details
have
been
omitted
and
that
no
routines,
such
as
fork(),
ever
fail.
int a = 0;
int main() {
fork();
a++;
fork();
a++;
if (fork() == 0) {
printf(Hello!\n);
} else {
printf(Goodbye!\n);
}
a++;
printf(a is %d\n, a);
}
46) What
will
be
the
final
value
of
a
as
displayed
by
the
final
line
of
the
program?
a) The
value
for
a
may
be
printed
multiple
times
in
different
orders
with
different
values.
b) 3
c) 4
d) 8
e) None
of
the
above
Answer:
3
!
b.
Each
process
has
its
own
copy
of
the
variable
a,
with
no
sharing
across
processes.
Each
process
will
increment
athree
times,
resulting
in
an
identical
value
of
a=3
for
all
8
processes.
The next set of questions looks at creating new threads. For
the
next
questions,
assume
the
following
code
is
compiled
and
run
on
a
modern
Linux
machine.
Assume
any
irrelevant
details
have
been
omitted
and
that
no
routines,
such
as
pthread_create()
or
pthread_join(),
ever
fail.
halt
This
code
is
incrementing
a
variable
(e.g.,
a
shared
balance)
many
times
in
a
loop.
Assume
that
the
%bx
register
begins
with
the
value
3,
so
that
each
thread
performs
the
loop
3
times.
Assume
the
code
is
loaded
at
address
1000
and
that
the
memory
address
2000
originally
contains
the
value
0.
Assume
that
the
scheduler
runs
the
two
threads
producing
the
following
order
of
instructions
(the
first
column
shows
the
address
of
the
executed
instruction).
The
code
continues
on
the
next
page.
Answer:
We
find
it
useful
to
track
the
value
of
ax,
which
is
separate
for
each
thread,
along
with
addr
2000
which
is
shared
across
the
two
threads.
Instructions
involving
register
bx
and
jumping
can
be
ignored.
Thread 0 Thread 1
1000 mov 2000, %ax Contents of ax = 0
1001 add $1, %ax Contents of ax = 1
------ Interrupt ------ ------ Interrupt ------
1000 mov 2000, %ax Contents of ax = 0
------ Interrupt ------ ------ Interrupt ------
1002 mov %ax, 2000 50) Contents of addr = 1
------ Interrupt ------ ------ Interrupt ------
1001 add $1, %ax Contents of ax = 1
1002 mov %ax, 2000 51) Contents of addr = 1
1003 sub $1, %bx
------ Interrupt ------ ------ Interrupt ------
1003 sub $1, %bx
------ Interrupt ------ ------ Interrupt ------
1004 test $0, %bx
------ Interrupt ------ ------ Interrupt ------
1004 test $0, %bx
------ Interrupt ------ ------ Interrupt ------
1005 jgt .top
1000 mov 2000, %ax Contents of ax = 1
------ Interrupt ------ ------ Interrupt ------
1005 jgt .top
1000 mov 2000, %ax Contents of ax = 1
1001 add $1, %ax Contents of ax = 2
------ Interrupt ------ ------ Interrupt ------
1001 add $1, %ax Contents of ax = 2
1002 mov %ax, 2000 52) Contents of addr = 2
------ Interrupt ------ ------ Interrupt ------
1002 mov %ax, 2000 53) Contents of addr = 2
------ Interrupt ------ ------ Interrupt ------
1003 sub $1, %bx
1004 test $0, %bx
------ Interrupt ------ ------ Interrupt ------
1003 sub $1, %bx
1004 test $0, %bx
------ Interrupt ------ ------ Interrupt ------
1005 jgt .top
1000 mov 2000, %ax Contents of ax = 2
1001 add $1, %ax Contents of ax = 3
------ Interrupt ------ ------ Interrupt ------
1005 jgt .top
1000 mov 2000, %ax Contents of ax = 2
------ Interrupt ------ ------ Interrupt ------
1002 mov %ax, 2000 54) Contents of addr = 3
1003 sub $1, %bx
1004 test $0, %bx
------ Interrupt ------ ------ Interrupt ------
1001 add $1, %ax Contents of ax = 3
1002 mov %ax, 2000 55) Contents of addr = 3
1003 sub $1, %bx
------ Interrupt ------ ------ Interrupt ------
1005 jgt .top
------ Interrupt ------ ------ Interrupt ------
1004 test $0, %bx
------ Interrupt ------ ------ Interrupt ------
1006 halt
----- Halt;Switch ----- ----- Halt;Switch -----
1005 jgt .top
1006 halt
For
each
of
the
lines
designated
above
with
a
question
numbered
50-55,
determine
the
contents
of
the
memory
address
2000
AFTER
that
assembly
instruction
executes.
Use
the
following
options
for
questions
50-55.
a) 1
b) 2
c) 3
d) 4
e) None
of
the
above
56)
What
would
be
the
expected
value
for
the
final
contents
of
address
2000
if
there
had
been
no
race
conditions
between
the
two
threads?
a) 3
b) 4
c) 5
d) 6
e) None
of
the
above
Answer:
6->d.
Each
of
the
two
threads
would
add
3
to
address
2000
(which
started
at
0).
End
of
Part
4.
Part
5.
Spin-Locking
with
Atomic
Hardware
Instructions
[10
points]
Consider
the
following
incomplete
implementation
of
a
spin-lock.
Examine
it
carefully
because
it
is
not
identical
to
what
was
shown
in
lecture.
typedef struct __lock_t {
int flag;
} lock_t;
void thread_join() {
Mutex_lock(&m); // p1
while (done == 0) // p2
Cond_wait(&c, &m); // p3
Mutex_unlock(&m); // p4
}
void thread_exit() {
Mutex_lock(&m); // c1
done = 1; // c2
Cond_signal(&c); // c3
Mutex_unlock(&m); // c4
}
59)
After
the
instruction
stream
P
(i.e.,
after
the
scheduler
runs
one
line
from
the
parent),
which
line
of
the
parents
will
execute
when
it
is
scheduled
again?
a)
p1
(Hint
to
get
you
started:
this
line
if
the
lock
is
already
acquired
and
P
must
wait
here)
b) p2
(Hint:
here
if
the
lock
was
not
previously
acquired
and
thus
P
continues
to
this
line)
c) p3
(Hint:
no
idea
how
this
could
happen)
d) p4
(Hint:
no
idea
how
this
could
happen)
e) Code
beyond
p4
(Hint:
no
idea
how
this
could
happen)
Answer:
p2
(b).
P
will
have
acquired
the
lock,
which
means
it
will
have
finished
mutex_lock().
60)
Assume
the
scheduler
continues
on
with
C
(the
full
instruction
stream
is
PC).
Which
line
will
the
child
execute
when
it
is
scheduled
again?
a) c1
b)
c2
c)
c3
d)
c4
e)
Code
beyond
c4
Answer:
c1
(a).
C
must
wait
to
acquire
the
lock
since
it
is
currently
held
by
P.
61)
Assume
the
scheduler
continues
on
with
PPP
(the
full
instruction
stream
is
PCPPP).
Which
line
will
the
parent
execute
when
it
is
scheduled
again?
a) p1
b) p2
c) p3
d) p4
e) Code
beyond
p4
Answer:
p3(c).
Since
done
=
0,
P
will
execute
while
p2
and
p3;
it
is
stuck
in
p3
until
signaled.
Note
that
P
relinquishes
the
lock
while
in
p3.
62)
Assume
the
scheduler
continues
on
with
CCC
(the
full
instruction
stream
is
PCPPPCCC).
Which
line
will
the
child
execute
when
it
is
scheduled
again?
a) c1
b)
c2
c)
c3
d)
c4
e)
Code
beyond
c4
Answer:
c4
(d).
C
finishes
c1,
executes
c2,
and
then
c3.
The
next
time
C
runs,
it
will
execute
c4.
At
this
point,
the
condition
variable
has
been
signaled,
but
the
mutex
is
still
locked.
63)
Assume
the
scheduler
continues
on
with
PP
(the
full
instruction
stream
is
PCPPPCCCPP).
Which
line
will
the
parent
execute
when
it
is
scheduled
again?
a) p1
b) p2
c) p3
d) p4
e) Code
beyond
p4
Answer:
p3
(c).
P
cannot
return
from
cond_wait()
until
it
is
able
to
acquire
the
lock;
since
the
lock
is
held
by
C,
P
stays
in
cond_wait().
64)
Assume
the
scheduler
continues
on
with
CC
(the
full
instruction
stream
is
PCPPPCCCPPCC).
Which
line
will
the
child
execute
when
it
is
scheduled
again?
a) c1
b)
c2
c)
c3
d)
c4
e)
Code
beyond
c4
Answer:
(e).
C
executes
c4
and
then
code
beyond
c4.
65)
Assume
the
scheduler
continues
on
with
PPP
(the
full
instruction
stream
is
PCPPPCCCPPCCPPP).
Which
line
will
the
parent
execute
when
it
is
scheduled
again?
a) p1
b) p2
c) p3
d) p4
e) Code
beyond
p4
Answer:
(e).
P
finishes
p3,
then
rechecks
p2
(the
while
loop),
and
then
executes
p4.
Next
time,
it
will
execute
code
beyond
p4.
66)
If
the
above
code
has
a
problem,
how
could
it
be
fixed?
Choose
one
option
below.
a) Code
does
not
have
a
problem
b) Change
how
the
done
variable
is
initialized
c) Remove
the
mutex_lock()
and
mutex_unlock()
calls
d) Change
the
while()
loop
to
an
if()
statement
e) None
of
the
above
Answer:
(a)
No
problems
with
this
code
End
of
Part
6a.
Part
6b.
Inserting
into
a
linked
list
[15
points]
Assume
the
following
code
for
inserting
keys
into
a
shared
linked
list
(identical
to
code
in
class):
typedef struct __node_t {
int key;
struct __node_t *next;
} node_t;
Typedef struct __list_t {
node_t *head;
} list_t;
Void List_Insert(list_t *L, int key) {
node_t *new = malloc(sizeof(node_t));
new->key = key;
new->next = L->head;
L->head = new;
}
Assume
a
list
L
originally
contains
three
nodes
with
keys
3, 4, and 5.
Assume
thread
T
calls
List_Insert(L, 2)
and
thread
S
calls
List_Insert(L, 6).
Assume
malloc()
does
not
fail.
Given
the
following
schedules
of
C-statements
in
the
list_insert()
routines
for
threads
T
and
S,
what
will
be
the
final
contents
of
the
list?
67)
Schedule:
TTTTSSSS
a) 2,
6,
3,
4,
5
b) 6,
2,
3,
4,
5
c) 2,
3,
4,
5
d) 6,
3,
4,
5
e) Unknown
results
or
None
of
the
above
Answer:
(b).
Each
call
to
List_Insert()
runs
to
completion;
key
2
is
inserted
first
at
the
head
of
the
list,
then
6,
which
gives
the
list:
6,
2,
3,
4,
5
68)
Schedule:
SSSSTTTT
a) 2,
6,
3,
4,
5
b) 6,
2,
3,
4,
5
c) 2,
3,
4,
5
d) 6,
3,
4,
5
e) Unknown
results
or
None
of
the
above
Answer:
(a).
Each
call
to
List_Insert()
runs
to
completion;
key
6
is
inserted
first,
then
2,
which
gives
the
list:
2,
6,
3,
4,
5
69)
Schedule:
SSTTTTSS
a) 2,
6,
3,
4,
5
b) 6,
2,
3,
4,
5
c) 2,
3,
4,
5
d) 6,
3,
4,
5
e) Unknown
results
or
None
of
the
above
Answer:
(b)
A
context
switch
away
from
S
occurs
after
new->key
has
been
set
to
6;
then
key
2
is
completely
and
correctly
added
to
the
list;
then
key
6
is
added
to
the
list.
70)
Schedule:
SSSTTTTS
a) 2,
6,
3,
4,
5
b) 6,
2,
3,
4,
5
c) 2,
3,
4,
5
d) 6,
3,
4,
5
e) Unknown
results
or
None
of
the
above
Answer:
d.
A
context
switch
occurs
away
from
S
after
new->next = L->head (without
key
2);
Key
2
is
then
added
to
the
head
of
the
list.
However,
when
S
is
resumed,
it
executes
L->head = new; this
causes
the
list
to
point
to
6
(which
points
to
3,
4,
5);
thus
key
2
is
lost
from
the
list.
71)
If
the
above
code
has
a
race
condition,
how
could
it
be
fixed?
Indicate
all
that
apply.
a) Code
does
not
have
a
race
condition
b) Lock
c) Condition
variable
d) Semaphore
e) Atomic
compare
and
swap
instruction
Official
Answer:
Need
the
two
instruction
assignment
of
new->next
and
L->head
to
be
atomic.
Code
can
be
fixed
with
a
lock,
a
semaphores
(which
can
be
used
as
mutex
by
initializing
to
1),
AND
with
an
atomic
compare
and
swap
instruction.
Correct
answers
should
have
marked
all
3,
but
everyone
was
given
credit
for
this
question.
End
of
Part
6b
Part
6c.
Lock
implementation
with
blocked
threads
[21
points]
Assume
you
have
the
following
code
for
acquiring
and
releasing
a
lock
(this
is
identical
to
code
shown
in
class):
Typedef struct {
bool lock = false;
bool guard = false;
queue_t q; // assume managed FIFO
} LockT;