















Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
Instructions for implementing a simplified version of a socket lock object and a reader/writer lock in c. The socket lock is used to manage connections in a web browser, preventing multiple threads from attempting to use a broken connection. The reader/writer lock is a synchronization mechanism for managing concurrent access to shared data, allowing multiple readers to access the data simultaneously while ensuring that only one writer can modify it at a time.
Typology: Exams
1 / 23
This page cannot be seen from the preview
Don't miss anything!
















think clearly about a problem, you will probably have time to write your answer legibly.
I have not received advance information on the content of this 15-410 mid-term exam by dis- cussing it with anybody who took part in the main exam session or via any other avenue.
Signature: Date
You have been asked to write a simplified version of a specialized locking object which might be used in a web browser. As you may know, it is somewhat expensive to establish a connection to a web server, so many browsers “cache” connections so that once a request for some web object (HTML page or image) is complete a later request can re-use an existing connection without the overhead of establishing a new one. (For the purposes of this exam, we will assume that our browser never opens more than one connection at a time to any single server—maybe it’s running on an embedded device and needs to conserve resources).
This locking object is special for two reasons. First, once a thread acquires a lock on a socket, it will perform a potentially long sequence of socket I/O requests, each one of which may require many milliseconds, before releasing the lock (and thus the socket). Your design should be appropriate for this usage pattern. Second, it is a sad fact of life that networks fail and browsers become disconnected from servers. When this happens, it would be silly for many threads in turn to “acquire” a broken connection socket and fruitlessly issue system calls against it. Therefore, when a browser thread determines that a server connection has failed, it invokes a special operation, called “broken(),” on the socket lock, before unlocking it. This allows the socket-lock code to inform all relevant threads to give up and invoke higher-level policy code to figure out what to do next. For exam purposes we will assume that slock init(), slock broken(), slock unlock(), and slock destroy() cannot fail, but slock lock() is explicitly allowed (i.e., expected) to return -1 to indicate that the underlying socket has not been locked because it was declared to be broken.
It is strongly recommended that you rough out an implementation on the scrap paper provided at the end of the exam, or on the back of some other page, before you start to write your solution on the next page. If we cannot understand the solution you provide, your grade will suffer!
The remainder of this page is intentionally blank.
Please declare a struct slock and implement slock init(), slock lock(), slock broken(), and slock unlock() (you do not need to implement slock destroy()). You may use standard thread- library synchronization, such as mutexes, condition variables, semaphores, and/or reader/writer locks. You may use deschedule()/make runnable() if you must—it’s not recommended, but may not use atomic instructions (XCHG, LL/SC, etc.). For the purposes of the exam you should assume an error-free environment (invocations of slock functions are always legal; memory allocation will always succeed; thread-library primitives will not detect internal inconsistencies or otherwise “fail,” etc.). If you wish, you may assume that the mutexes provided by the underlying thread library provide bounded waiting; you may also assume, if you wish, that the underlying condition variables are “as FIFO as possible.” You may wish to refer to the “cheat sheets” at the end of the exam.
typedef struct slock {
} slock_t;
You may use this page as extra space for your slock solution if you wish.
typedef struct rwlock { int read_count; /* #readers holding lock (or waiting on a writer) / mutex_t read_count_lock; / protects read_count / int mode; / mode that lock is in: RWLOCK_READ or RWLOCK_WRITE / mutex_t write_lock; / taken by writer XOR first reader; protects mode */ } rwlock_t;
void rwlock_init(rwlock_t *rwlock) { mutex_init(&rwlock->read_count_lock); mutex_init(&rwlock->write_lock); rwlock->read_count = 0; rwlock->mode = RWLOCK_READ; }
int rwlock_lock(rwlock_t rwlock, int mode) { switch (mode) { case RWLOCK_READ: mutex_lock(&rwlock->read_count_lock); if (rwlock->read_count == 0) { / Are we the first reader? */ mutex_lock(&rwlock->write_lock); rwlock->mode = RWLOCK_READ; } rwlock->read_count++; mutex_unlock(&rwlock->read_count_lock); return 0; case RWLOCK_WRITE: mutex_lock(&rwlock->write_lock); rwlock->mode = RWLOCK_WRITE; return 0; } }
int rwlock_unlock(rwlock_t rwlock) { / write_lock is always held on entry / switch (rwlock->mode) { case RWLOCK_READ: mutex_lock(&rwlock->read_count_lock); rwlock->read_count--; if (rwlock->read_count == 0) { / Were we the last reader? */ mutex_unlock(&rwlock->write_lock); } mutex_unlock(&rwlock->read_count_lock); return 0; case RWLOCK_WRITE: mutex_unlock(&rwlock->write_lock); return 0; } }
As you know, one approach to the deadlock problem is to impose a total ordering on resources such as locks. When analyzing unknown code, it can be useful to compute a “lock dependency graph” in which a directed edge is drawn from node A to node B if resource B is acquired by a thread at a time when that thread already owns resource A (we say that the acquisition of B “depends on” the acquisition of A having previously happened). If the lock dependency graph of a body of code is a directed acyclic graph (DAG), this constitutes a partial ordering on the resources, which can easily be “flattened” to form some total ordering. However, if a lock dependency graph contains a cycle, there cannot be a total ordering of locks. Note that a lock dependency graph reflects static properties of the code, and is different from the process/resource graph notation used in class, which shows the state of a system at some point in time.
(b) 5 points Draw the lock dependency graph for the code shown above. You must “com- ment” each edge by detailing the condition or situation in which the dependency occurs.
(c) 10 points Can the implementation shown above deadlock? If so, provide an execution sequence using the tabular form shown below. If a deadlock is not possible, provide a clear and concise argument that it cannot happen; your reasoning should be convincing enough to be included with the code as documentation. Trace format: Thread 0 Thread 1 rwlock(WRITE) ... rwlock(READ) ... return; return; You may introduce temporary variables or other obvious notation as necessary to improve the clarity of your answer. Be sure that any execution trace or argument you provide us with is easy to read and conclusively demonstrates the claim you are making.
Consider the following Pebbles system calls (listed in alphabetical order):
Assign each system call on the list above to one of three categories: “likely to block the invoking thread,” “may or may not block the invoking thread,” or “unlikely to block the invoking thread.” Briefly (one to four sentences) justify your assignment of each system call to the category you selected. Note that “block the invoking thread” means “changes the thread from ‘running’ to ‘blocked.”’ It is up to you to decide whether one (or more!) of the three categories is empty.
You may use this page as extra space for the blocking question if you wish.
In short, alloca() provides a C-language interface to allocating a variable amount of memory on the stack. In some senses, it is much like C99’s variable array allocation support—these two code sequences accomplish the same thing:
void fun1(void) { int n = rand() % 15410; int foo[n];
foo[0] = 15412; printf("%d\n", foo[0]); /* foo[] is not disturbed by functions fun1() calls / return; / foo[] is automatically deallocated when fun1() returns */ }
void fun2(void) { int n = rand() % 15410; int *foo = alloca(n * sizeof (int));
foo[0] = 15412; printf("%d\n", foo[0]); /* foo[] is not disturbed by functions fun2() calls / return; / foo[] is automatically deallocated when fun2() returns */ }
It turns out that compiling code that uses alloca() is not straightforward. To make this clear, we will ask you to hand-compile into assembly language a short C function that would be a good candidate for using alloca().
The following function receives a “length/value”-coded message from a network connection and appends the message onto the end of a log. Because each message can have a different size, the function first reads the length from the network connection, then allocates a buffer, then uses that buffer to receive and store the message. Note that for exam purposes critically-important input validation and error checking have been omitted.
void fetchlog(int socket) { int len; unsigned char *value;
read(socket, &len, sizeof (len)); value = alloca(len); /* value[] is allocated on stack / read(socket, value, len); logstore(value, len); return; / value[] is automatically deallocated */ }
It is probably beneficial for you to read all parts of the question before answering any part.
It is probably beneficial for you to read all parts of the question before answering any part.
(a) 8 points To start off with, please write the assembly code that a compiler might generate for this function.
The alloca() specification shown above is copied from Mac OS X. The corresponding Linux man page contains the following statement in the BUGS section: “On many systems alloca() cannot be used inside the list of arguments of a function call.” What this means is that read(fd, alloca(32), 32)
won’t work right.
(c) 2 points Explain briefly (again, two or three sentences should be plenty; you may show assembly code if you wish) why that kind of invocation won’t work or what might go wrong if code like that were compiled by a na¨ıve compiler.
/* Life cycle */ int fork(void); int exec(char *execname, char *argvec[]); void set_status(int status); void vanish(void) NORETURN; int wait(int *status_ptr); void task_vanish(int status) NORETURN;
/* Thread management / int thread_fork(void); / Prototype for exam reference, not for C calling!!! */ int gettid(void); int yield(int pid); int deschedule(int flag); int make_runnable(int pid); int get_ticks(); int sleep(int ticks); / 100 ticks/sec / typedef void (swexn_handler_t)(void *arg, ureg_t *ureg); int swexn(void *esp3, swexn_handler_t eip, void *arg, ureg_t *newureg):
/* Memory management */ int new_pages(void * addr, int len); int remove_pages(void * addr);
/* Console I/O */ char getchar(void); int readline(int size, char *buf); int print(int size, char *buf); int set_term_color(int color); int set_cursor_pos(int row, int col); int get_cursor_pos(int *row, int *col);
/* Miscellaneous */ void halt(); int ls(int size, char *buf);
/* "Special" */ void misbehave(int mode);
If a particular exam question forbids the use of a system call or class of system calls, the presence of a particular call on this list does not mean it is “always ok to use.”