sp24-mt1-solutions (1)
sp24-mt1-solutions (1)
College of Engineering
Computer Science Division EECS
Spring 2024 John Kubiatowicz
Midterm I
SOLUTION
February 15th, 2024
CS162: Operating Systems and Systems Programming
Your Name:
TA Name:
Discussion Section
Time:
General Information:
This is a closed book exam. You are allowed 1 page of notes (both sides). You have 110 minutes to
complete as much of the exam as possible. Make sure to read all of the questions first, as some of the
questions are substantially more time consuming.
Write all of your answers directly on this paper. Make your answers as concise as possible. On
programming questions, we will be looking for performance as well as correctness, so think through
your answers carefully. If there is something about the questions that you believe is open to
interpretation, please ask us about it!
1 20
2 18
3 22
4 24
5 16
Total 100
Page 1/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
3.14159265358979323846264338327950288419716939937510582097494459230781640628620899
Page 2/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 1a[2pts]: Because the “monitor” pattern of synchronization involves sleeping inside a critical
section, it requires a special hardware support to avoid deadlock (i.e. a permanent lack of forward progress).
⬜ True False
Explain: No special hardware is required beyond the hardware used for lock
implementation. All that is necessary is for the implementation of cond_wait() release the
lock when putting the thread on the sleep queue and to reaquire the lock before returning.
Problem 1b[2pts]: In UNIX, Pipes are implemented with a buffer in user space.
⬜ True False
Explain: UNIX Pipes are implemented in the kernel (they are constructed with the
pipe() system call). Consequently, the buffer is in kernel space.
Problem 1c[2pts]: Any two processes in a running system have a common ancestor process.
Clarification during exam: a process is considered an ancestor of itself.
True ⬜ False
Explain: All processes can ultimately be traced to the “init” process that starts up at
boot time. Init starts shells and kernel daemon processes, which launch other processes.
Problem 1d[2pts]: After the main thread in a process calls the exit() system call (either implicitly
or explicitly), the other threads are allowed to run to completion before the process terminates.
⬜ True False
Explain: The exit() system call frees up all process resources (except for memory
storing the exit status), including all of the process’ threads.
Problem 1e[2pts]: A user-level library implements each system call by executing a “transition to
kernel mode” instruction followed by a procedure call to an appropriate system call handler in the
kernel. To make this work, the user-level library must be built with access to a symbol table that
contains the kernel addresses of each system call.
⬜ True False
Explain: For security reasons, we can never allow the user to run arbitrary code with
the kernel-mode bit set or even know the addresses of kernel handlers. So, a system call has
to atomically, in hardware, (1) set the kernel mode bit while (2) switching to a kernel stack
and (3) jumping to a numbered handler in the kernel.
Page 3/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 1f[2pts]: Not every call to fread() must make a call the kernel using the read()
system call to retrieve data from the underlying file.
True ⬜ False
Explain: This is true because fread() makes use of a data buffer in user memory (i.e.
in the FILE structure). It only makes a call to read() when the buffer is empty, but otherwise
serves fread() requests from the user-level buffer.
Problem 1g[2pts]: The test&set instruction consists of two independent operations: (1) read the
value from memory and (2) replace it with the value “1”. Thus, it must be used in a loop to protect
against the case in which multiple threads execute test&set simultaneously and end up
interleaving these two operations from different threads.
⬜ True False
Explain: The test&set instruction must atomically read from memory and set the
value of the memory to 1. Otherwise it would be no better than a non-atomic combination of
a load and a store (and thus suffer from the limitations of the “got milk” solution #3).
Problem 1h[2pts]: It is possible for a client machine (with IP address X) and server machine (with
IP address Y) to have multiple simultaneous but independent communication channels between
them – despite the fact that network packets get intermixed in the middle.
True ⬜ False
Explain: A socket-to-socket connection over the Internet is uniquely identified by a
5-tuple of values: [ Source IP, Source Port, Dest IP, Dest Port, Protocol]. Multiple unique
channels between X and Y can be distinguished with different source and/or destination ports
which are included in the message headers, thereby allowing messages to be sorted out at
source or destination.
Problem 1i[2pts]: When a process issues a system call in Pintos, the kernel must change the active
page table (i.e., update the page table base register) so that the system call handler can
access kernel memory.
⬜ True False
Explain: PintOS achieves two separate address spaces in a single page table by
marking some entries as “kernel only.” Consequently, during a system call, the additional
mappings for the kernel become immediately available as soon as the processor mode
changes to kernel mode – without having the change the page table.
Problem 1j[2pts]: A user-level application can build an implementation of lock acquire() and
release() by using the interrupt enable and disable functions of the pthread library.
⬜ True False
Explain: For security reasons, user code is never allowed to enable or disable
interrupts. So, there is no way, even in principle, that they could build a lock using interrupt
enable/disable, and there is no such functionality in the pthread library.
Page 4/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 2b[2pts]: What are some things that can cause a transfer from user mode to kernel mode?
(choose all that apply):
A: User code divides by zero.
B: The user executes a system call.
C: A packet is received from the network.
D: The application uses malloc() to allocate memory from the heap.
E: The timer goes off.
A: TRUE. Integer divide by zero causes a divide by zero trap and enters the kernel through
the appropriate entry in the interrupt vector.
B: TRUE. A system call is a software trap instruction that transfers into the kernel.
C: TRUE. Reception of packets from the network can generate interrupts, which will force a
transition from user mode to kernel mode to handle the incoming packets..
D: TRUE. Calls to malloc() allocate data on the heap, which can enter the kernel to ask
for more memory (via the sbrk() system call) to be assigned to the part of the address
space assigned to the heap..
E: TRUE. The timer generates an interrupt which causes a transition from user mode to
kernel mode.
Page 5/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
A: FALSE. If we reenable interrupts just before sleeping, then it is possible that the lock would
be freed and thread taken back off the wait queue – only for use to go immediately to sleep,
possibly to not wake again.
B: FALSE. If we reenable interrupts just before putting thread on wait queue, then the
releaser would notice us (and thus try to wake us up) – and we would proceed to put
ourselves on the wait queue and go to sleep, possibly never to wake up again.
C: TRUE. This solution is correct because we maintain the atomicity of the lock while we put
ourselves on the wait queue and context switch to another thread. The other thread will
then get loaded and reenable interrupts cleanly.
D: FALSE. This code cannot run in user mode because it enables and disables interrupts.
E: FALSE. For obvious reasons.
Problem 2d[2pts]: Which of the following are true about semaphores (choose all that apply):
A: ⬜ Semaphores can be initialized to any 32-bit value in the range -231 to 231-1
B: If there is at least one thread sleeping on a given semaphore, then the result of performing a
Semaphore.V() will be to wake up one of the sleeping threads rather than incrementing
the value of the semaphore.
C: ⬜ Semaphores cannot be used for locking.
D: ⬜ The interface for Semaphore.P() is specified in a way that prevents its implementation
from busy-waiting, even for a brief period of time.
E: The pure semaphore interface does not allow querying for the current value of the
semaphore.
Page 6/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
A: TRUE. This is the whole point of having a separate kernel stack for every thread. It means
that all of the state of the thread – including its sequence of calls in the kernel – are stored
on the kernel stack. By pushing the remaining state on the stack, we can just put this stack
(and probably the TCB as well) on the given wait queue because it represents everything
we need to restart thread from its previous position.
B: FALSE. There is no “kernel thread” that is independent of the “user thread.” There is
only one thread that is either executing in kernel mode or user mode.
C: FALSE. Typically, the kernel stack is of limited size, since the kernel does not do arbitrarily
deep recursive computation. In fact, the PintOS kernel combines both the TCB and kernel
stack into a 4KB page. Linux combines the TCB and kernel stack into a single 8KB chunk.
D: TRUE. The contents of the user’s segment register and stack pointer cannot be trusted to
be useable or large enough to handle kernel code. Further, the kernel cannot use a stack
that is in user space without risking having it corrupted by other threads.
E: FALSE. For obvious reasons.
Page 7/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 2f[2pts]: Which of the following statements about processes are true? (choose all that
apply):
A: If a process calls execv(), it does not need to free any of its allocated heap variables.
B: ⬜ Using IPC (such as a pipe), a child process is able to share the address of a stack-allocated
variable directly with its parent process so that the two processes can communicate directly
using load and store instructions.
C: ⬜ Each process has its own instance of user and kernel memory.
D: ⬜ If a parent process has multiple running threads at the time of fork(), then the child process
will have multiple running threads immediately after fork().
E: ⬜ Immediately after fork(), a child has a duplicate file descriptor table with pointers to
duplicated file description structures for files that are open in the parent.
A: TRUE. The execv() system call replaces the complete contents of the address space,
including the heap. Consequently, there is no reason to free heap contents.
B: FALSE. Since the parent and child process have separate, isolated memory spaces, simply
sharing an address between parent and child is insufficient to allow sharing. You would
also have to also setup shared memory (which would not normally involve the stack).
C: FALSE. There is only one kernel, shared among all processes.
D: FALSE. The fork() system call only launches a single thread in the child, namely a
duplicate of the thread that ran fork() in the parent.
E: FALSE. Because the file descriptor table of the parent is copied in the child, all the file
descriptors in the child point at the same file descriptions that they did in the parent.
Consequently, this is false because the file descriptions are not duplicated.
Problem 2g[2pts]: Which of the following statements about files are true? (choose all that apply):
A: The same file descriptor number can correspond to different files for different processes.
B: ⬜ The same file descriptor number can correspond to different files for different threads in the
same process.
C: ⬜ Reserved 0, 1, and 2 (stdin, stdout, stderr) file descriptors cannot be overwritten by a
user program.
D: File descriptions keep track of the file offset.
E: An lseek() within one process may be able to affect the writing position for another
process.
A: TRUE. Each process has a unique file descriptor table whose contents depends on the
history of open() and close() operations executed by threads in that process.
B: FALSE. File descriptors are a process-level resources, so different threads in a process
use the same (identical) file descriptor table.
C: FALSE. These file descriptors can be closed and reassigned easily using dup2().
D: TRUE. The kernel tracks the file offset so that a sequential set of read() operations will
walk through the contents of the file automatically.
E: TRUE. Since a parent and child share open file descriptions immediately after fork(),
an lseek() in one of these will change the file offset in the underlying file description and
hence the writing position for the other process.
Page 8/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 2h[2pts]: Select all true statements about the x86 Calling sequence (choose all that apply):
A: ⬜ Parameters are pushed onto the stack in the order that they are declared in the function.
B: ⬜ Right before the Caller jumps to the Callee function, the ESP must be 16-byte alligned.
C: ⬜ If padding is necessary for alignment, it should be added at a lower address than the
parameters.
D: In the Callee, space is allocated for local variables via subtracting from the ESP.
E: ⬜ None of the above.
A: FALSE. Parameters are pushed on the stack in reverse order.
B: FALSE. While it is true that the ESP should be 16-byte aligned during the prologue, it
should already be at this alignment prior to executing the call instruction.
C: FALSE. Padding should be added at a higher address than the parameters.
D: TRUE. The compiler is able to calculate how much of the stack is used by local variables
and subsequently subtracts the right amount from ESP.
E: FALSE. Obviously
Problem 2i[2pts]: Which of the following are true about condition variables? (choose all that apply):
A: cond_wait() can only be used when holding the lock associated with the condition
variable.
B: ⬜ In practice, Hoare semantics are used more often than Mesa semantics.
C: ⬜ Mesa semantics will lead to busy waiting in cond_wait().
D: Each condition variable has its own wait queue for sleeping threads.
E: ⬜ All of the above.
Page 9/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 3a[3pts]: Give a concise proof why x≠1 when both threads have completed. Hint: consider
the fact that each store is one greater than the value it loaded, even when threads interleave.
Incidentally, although we didn’t ask you this, there is actually an interleaving that produces x = 2!
Problem 3b[2pts]: What is a critical section and what is the role of locking in enforcing correct
behavior for a multithreaded program?
A critical section is a sequence of operations which must be executed as a unit (without interleaving by
multiple threads) in order to avoid incorrect behavior of the program. To enforce this correctness criteria,
we can use a lock to prevent multiple threads from entering the critical section at the same time, i.e. we
make threads acquire() the lock on entry to the critical section and release() the lock on exit of the
critical section.
Page 10/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
In class, we discussed a number of atomic hardware primitives that are available on modern
architectures. In particular, we discussed “test and set” (TSET), SWAP, and “compare and swap”
(CAS). They can be defined as follows (let “expr” be an expression, “&addr” be an address of a
memory location, and “M[addr]” be the actual memory location at address addr):
Test and Set (TSET) Atomic Swap (SWAP) Compare and Swap (CAS)
int TSET(&addr) { int SWAP(&addr,expr) { bool CAS(&addr,expr1,expr2) {
int result = M[addr]; int result = M[addr]; if (M[addr] == expr1) {
M[addr] = 1; M[addr] = expr; M[addr] = expr2;
return(result); return (result); return true;
} } } else {
return false;
}
}
Both TSET and SWAP return values from memory, whereas CAS returns either true or false. Note
that our &addr notation is similar to a reference in C, and means that the &addr argument must be
something that can be stored into. For instance, TSET could implement a spin-lock acquire as follows:
int lock = 0; // lock is free
while (TSET(&lock)); // Later: acquire lock
Problem 3c[2pts]: Show how to implement a spinlock acquire() with a single while loop using
CAS instead of TSET. Fill in the arguments to CAS below. Hint: need to wait until can grab lock.
void acquire(int *mylock) {
Page 11/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
The issue with constructing locks using only user-level atomic instructions such as TSET or CAS is
that we cannot block a thread (i.e. put it to sleep) when it is unable to acquire a lock. As discussed in
class, Futex() is a system call that allows a thread to put itself to sleep, under certain circumstances,
on a queue associated with a user-level address. It also allows a thread to ask the kernel to wake up
threads that might be sleeping on the same queue. Recall that the function signature for Futex is:
int futex(int *uaddr, int futex_op, int val);
In this problem, we focus on two futex_op values:
1. For FUTEX_WAIT, the kernel checks atomically as follows:
if (*uaddr == val): the calling thread will sleep and another thread will start running
if (*uaddr != val):the calling thread will keep running, i.e. futex() returns immediately
2. For FUTEX_WAKE, this function will wake up to val waiting threads. You can assume in this
problem that val will always be <= the actual number of waiting threads.
Problem 3f[2pts]: Fill out the missing blanks, in the acquire() function, below, to make a
version of a lock that does not busy wait. The corresponding release() function is given for you:
void acquire(int *thelock) { // Acquire a lock
while (TSET(thelock)) {
Problem 3i[3pts]: Fill in the missing blanks, below, for the three-state solution, using constants
from the Lock enum. Hint: You are optimizing your lock based on the answer to Problem 3h:
// Acquire a lock
void acquire(Lock *thelock) {
// Fast case acquire:
// Contested acquire:
// Release a lock
void release(Lock *thelock) {
Page 13/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 4a[3pts]: What happens when an interrupt occurs? What does the interrupt controller do?
An interrupt is an asynchronous signal that comes from outside the processor pipeline. Assuming that a
particular interrupt is enabled, the interrupt will cause the processor to stop what it is doing and
atomically: swap in the kernel stack, save essential user registers on the new stack, change to kernel
mode, and start executing a handler that is dependent on the interrupt ID. The interrupt controller is a
piece of hardware that permits the OS to individually enable and disable interrupts and that will
prioritize the set of enabled interrupts when there is more than one asserted interrupt.
Problem 4b[3pts]: The linked list nodes you have seen before (in 61B) stored each value alongside
the previous and next pointers. However, this is not the case in PintOS. Below are the definition of
the list_elem and list structures for PintOS:
struct list_elem
{
struct list_elem *prev;
struct list_elem *next;
};
struct list
{
struct list_elem head;
struct list_elem tail;
};
Provide justification for why PintOS lists are implemented this way. In an actual list built using
these definitions, where are the related values stored?
Lists are implemented this way to provide a type independent, object-oriented (or “generic”) list
mechanism in C (which is decidedly not object-oriented). Lists and list elements can be manipulated by
functions that do not need to know anything about the nodes that are being linked in the list. In an
actual list built this way, the “list_elem” structure is embedded in an enclosing structure, which actually
contains the values.
Problem 4c[3pts]: What needs to be saved and restored on a context switch between two threads in
the same process? What if the two threads are in different processes? Be explicit.
All of the execution state – registers, program counters, stack pointers, etc – must be saved and restored
on a context switch between threads in the same process. In addition, when switching between threads in
different processes, the memory protection state must be switched (i.e. change the active page tables).
Process-specific state such as the PCB, file-descriptor table, etc, is switched automatically as a side-
effect of changing out the address space and registers, since they stay in the same place in memory and
do not actually have to be saved or restored.
Page 14/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 4d[2pts]: What was the problem with the Therac-25 radiation therapy machine? Your
answer should involve one of the topics of the class.
The Therac-25 radiation therapy machine was responsible for the deaths multiple cancer patients when
it delivered excessive doses of radiation to these patients. Among other things, investigators discovered
a race-condition/synchronization problem that caused this machine to malfunction when operators typed
too quickly at the console (thereby interrupting the movement of internal mechanical components that
controlled the radiation type and dosage).
Problem 4f[2pts]: When a process thread executes fork(), this creates a new child process with an
address space that is separate from the parent and that has identical contents to the parent process.
Does an implementation of fork() require the operating system to copy all of the data of the parent
process into the child process? Explain.
No, the OS does not have to copy all of the parent’s data as long as it provides the same application
behavior as if the contents of the parent’s address space were fully copied. One possible way to do this
is to use copy-on-write (COW): at the time of fork, we set all of the parent’s page table entries to read-
only before copying the page table for the child. To the child, this looks like a complete copy. And, if
either the parent or child tries to write a page, we get a page fault and make two copies of that
particular page – one for the parent and one for the child, maintaining the illusion that the child got an
independent copy of the parent’s address space.
Problem 4g[2pts]: When handling PintOS syscalls in userprog/syscall.c, how can we tell
what syscall the user called, since there is only one syscall_handler function?
The first argument to the syscall_handler (pushed to the user stack in the interrupt stack frame) is an
enum that specifies which syscall is needed. This is passed in from the lib/user/syscall.c file for each
syscall.
Page 15/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 4h[4pts]: Your friend tells you that they can open a file once but read it twice. Although
you are skeptical, you decide to give it a try. Without utilizing another call to open, finish the
following double_read() function. This function should read the specified file twice, storing the
appended result as a duplicated, null-terminated string in the given buffer ‘buffer’. Note: You
must actually read() the file contents twice – do not just copy the data after reading the file once!
In the following, assume that all system calls succeed and that the buffer is big enough to hold the
result. Read the file in a maximum of CHUNK_SIZE bytes at a time. While you do not necessarily
need to use every line here, you are also not allowed to add semicolons to existing lines.
int offset = 0 ;
int bytesread ;
if (bytesread == 0 ) break;
offset += bytesread ;
}
lseek(fd, 0, SEEK_SET) ;
}
*(buffer+offset) = ‘\0’ ;
close(fd);
}
Problem 4i[3pts]: The “high-level” file I/O interface (i.e fopen(), fclose(), fread(),
fwrite(), and other associated functions) is often called the “streaming I/O interface” in contrast
to the “low-level” interface (i.e. open(), close(), read(), write()) . Why is this
terminology/distinction appropriate? (Hint: start by explaining what a streaming communication
pattern might is).
A streaming communication pattern is one that writes or reads a large sequential stream of bytes to or
from a file using a (continuous) series of operations. It is distinguished from a random pattern of reads
or writes. The key difference between the “high-level” and “low-level” interfaces is the presence of a
user-level buffer in the high-level versions. Assuming a streaming I/O pattern, the high-level I/O
operations can use the buffer gather together the data from a bunch of requests (from fread() or
fwrite() ) into a much smaller number of read() or write() operations to the kernel, thereby
gaining in performance. However, this advantage is only present if the user-level buffer can be properly
utilized, namely through a sequential “stream” of reads or writes that can lead to larger blocks of
writes.
Page 16/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Page 17/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 5a[2pts]: We will represent a sell request with a sell_request_t structure and the
queue of pending sell requests with a match_queue_t structure. Complete the following
definitions. Assume that we will use a PintOS list to link sell requests into the match_queue_t
structure. Further assume that buyers will sleep on one condition variable and sellers will sleep on
a separate one, both of which are stored within the match_queue. Do not add any semicolons!
typedef struct sell_request {
int waiting_sell; // Remaining # shares for this seller
struct list_elem mylink; // Link for PintOS list
} sell_request_t;
typedef struct match_queue {
char *stock_symbol; // String describing stock
int waiting_buy; // Number waiting buyers
pthread_mutex_t mlock ;
pthread_cond_t sellwait ;
pthread_cond_t buywait ;
} match_queue_t;
Problem 5b[2pts]: Complete the following match_queue allocator, assuming calloc succeeds).
Please do not add any semicolons. Error codes for syscalls and pthread functions do not need to be
checked.
match_queue_t *match_queue_alloc(char *stock_symbol) {
match_queue_t *new_match_queue =
(match_queue_t *)calloc(1,sizeof(match_queue_t));
new_match_queue‐>stock_symbol = stock_symbol;
list_init(&(new_match_queue‐>mlist)) ;
pthread_mutex_init(&(new_match_queue‐>mlock)) ;
pthread_cond_init(&(new_match_queue‐>sellwait)) ;
pthread_cond_init(&(new_match_queue‐>buywait)) ;
return new_match_queue;
}
Page 18/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 5c[6pts]: Fill in the missing blanks for the buyer’s routine, below. Assume that
buy_stock_match() should not return until the total requested shares have been matched with a
seller. However, busy-waiting is strictly forbidden. Make sure that sellers (represented by
sell_request_t structures with a non-zero sell_waiting value are handled strictly in order. Error
codes for syscalls and pthread functions do not need to be checked. Note: The matchq argument
represents a pointer that has gone through your match_queue_alloc() routine, so should be properly
initialized. While you do not necessarily need to use every line here, you are also not allowed to
add semicolons to existing lines.
Hint: The buyers and sellers work together. Thus, as you will handle in Problem 3d, sellers will
sleep waiting for their shares to be completely matched with buyers. And buyers will sleep waiting
for enough shares to become available.
pthread_mutex_lock(&(matchq‐>mlock)) ;
while(num_shares) {
if (list_empty(&(matchq‐>mlist) )) {
matchq‐>waiting_buy += 1;
pthread_cond_wait(&(matchq‐>buywait), &(matchq‐>mlock)) ;
matchq‐>waiting_buy ‐= 1;
} else {
struct list_elem *fe = list_pop_front(&(matchq‐>mlist) );
sell_request_t *first =
list_entry(fe , sell_request_t , mylink );
if (first‐>waiting_sell > num_shares) {
first‐>waiting_sell ‐= num_shares ;
num_shares = 0 ;
list_push_front(&(matchq‐>mlist), fe) ;
} else {
num_shares ‐= first‐>waiting_sell ;
first‐>waiting_sell = 0 ;
pthread_cond_broadcast(&(matchq‐>sellwait) ;
}
}
}
pthread_mutex_unlock(&(matchq‐>mlock) ;
________________________________________________________________________;
}
Page 19/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Problem 5d[4pts]: Fill in the missing blanks for the seller’s routine. Assume that calloc() always
succeeds and that the seller should be put to sleep until all of their stock shares have been matched
to buyers. Make sure that new sellers are not allowed to jump in front of existing sellers. Error
codes for syscalls and pthread functions do not need to be checked. Note: The matchq argument
represents a pointer that has gone through your match_queue_alloc() routine, so should be properly
initialized. Busy-waiting is not allowed. Further, you should make sure that there are no memory
leaks (it is up to the seller to free any sell structures they have allocated). While you do not
necessarily need to use every line here, you are also not allowed to add semicolons to existing lines.
// Sell num_shares of a stock through the given match_queue
// Assume num_shares > 0 and matchq is valid (from match_queue_alloc())
void sell_stock_match(match_queue_t *matchq, int num_shares) {
sell_request_t *req =
(sell_request_t *)calloc(1, sizeof(sell_request_t));
req‐>waiting_sell = num_shares;
pthread_mutex_lock(&(matchq‐>mlock)) ;
list_push_back(&(matchq‐>mlist), &(req‐>mylink) );
if (matchq‐>waiting_buy) {
pthread_cond_broadcast(&(matchq‐>buywait) ;
}
while (req‐>waiting_sell ) {
pthread_cond_wait(&(matchq‐>sellwait), &(matchq‐>mlock)) ;
}
free(req) ;
pthread_mutex_unlock(&(matchq‐>mlock) ;
}
Problem 5e[2pts]: Suppose that many sellers show up all at once, before buyers arrive. Then,
suppose buyers arrive one at a time. Although still correct, the above solution will incur a lot of
scheduler overhead. Explain the problem in three sentences or less and how you would fix it.
The problem here is that all sellers are treated identically – regardless of whether or not their shares
have been all sold. So, each time that a sell_request_t is emptied (because its num_shares 0), we
wake up all sellers (using a broadcast), all but one of which will check their sell structure, notice that it
still contains shares, and go to sleep. We have to use broadcast because there are no guarantees of the
order which threads will be woken up from a condition variable.
We fix this by putting a condition variable inside each sell_request_t. As a result, we can directly wake
up only the buyer whose shares have been freed.
Page 20/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Page 21/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Page 22/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Page 23/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Page 24/25
CS 162 Spring 2024 Midterm I SOLUTION February 15th, 2024
Page 25/25