Answer the following questions about the lecture for this week.
Suppose we build a dynamically linked library and a program that uses it as follows
clang -c utility1.c
clang -c utility2.c
clang -shared -o libutility.so utility1.o utility2.o
clang -c main.c
clang -o main.exe main.o -L. -l utility -Wl,-rpath .
If we modify utility2.c
, then we should rerun which commands
in order to make sure main.exe
uses the most recent version of
that the code in utility2.c
?
Select all that apply.
Suppose a C project has a header file constants.h
that is generated by processing a CSV constants.csv
file with C program called csvtoconstantsh
.
Then constants.h is used with main.c to generate a main
executable.
When run by hand the following sequence of commands are used:
clang -c csvtoconstantsh.c
clang -o csvtoconstantsh csvtoconstants.o
./csvtoconstantsh constants.csv constants.h
clang -c main.c
clang -o main main.o
Suppose the above commands are run, and then
constants.csv
is modified. Which commands should be rerun
to handle these changes?
Select all that apply.
When writing a Makefile to handle building this project,
one might have a rule for constants.h
:
constants.h: ...
(tab)./csvtoconstantsh constants.csv constants.h
where ...
is is a list that includes ___ (and (tab)
represents
a tab character). Select all that apply.
Consider a Makefile with the following contents:
foo.exe: foo.c
(tab)clang -c foo.c
(tab)clang -o foo.exe foo.o
compared to a Makefile with the following contents:
foo.exe: foo.o
(tab)clang -o foo.exe foo.o
foo.o: foo.c
(tab)clang -c foo.c
((tab)
represents a tab character.)
It is possible to set the modifications times of foo.exe
, foo.o
and foo.c
such that running make foo.exe
will ____ with the
first Makefile, but ____ with the second.
Answer the following quesitons about the lecture material for this week.
Operating systems could provide a system call that provides the functionality
of printf
, but they usually do not. Instead, printf
is usually implemented
in a library function that may internally make some system calls.
What are typically advantages of this approach? Select all that apply.
Consider a Unix-like system with a single-core running three (single-threaded) processes, an SSH client, a graphical terminal (which is being used to display the SSH client), and a program performing a long computation
Initially process SSH client is active on the CPU, then the following events happen in the following order:
How many context switches must occur between steps 1 and 8 above?
A non-system-call exception is likely to occur as part of which steps above? Write the numbers of the relevant steps; for example, if you think steps 2 and 7 will have a a non-system-call exception occur, then write "27".
also accepted 1 and 8 on the theory that the network interface needs to inform the OS when more output can be accepted via exception, though I don't think this is especially likely except on very loaded network (and it wasn't my intention to test about this kind of issue)
also accepted 2 or 3 (but not both) [theory that SSH client is waiting in a loop rather than having a system call that puts the program to sleep, so we need an timer interrupt to switch to the long computation. I think this is unlikely (we don't like our programs hogging the processor while waiting for input), but not unreasonable.]
[to be handled in manual grading]: -1 point per disagreement
One or more system calls are likely to be started during which of the steps above? Write the numbers of the relevant steps; for example, if you think steps 2 and 7 will have a system call be started, then write "27".
Do not include steps for which a system call already in progress is resumed (but do include cases where a system call is both started and finished within the step).
(12568 is a better answer, but we have not taught enough about how processes communicate to discount number 6 happening via shared memory instead of a system call)
[to be handled in manual grading]: -1 point per disagreement
Suppose a program running on a Linux system contains the following line:
*pointer = 42;
When pointer
does not specify a valid memory location (and no setup
has been done to handle this situation specially), this line will
cause the program to crash (terminating with an error).
To make this happen, ____.
Consider the following C code:
int global = 0;
void handle_sigint(int id) {
global += 1;
printf("SIGINT: global=%d\n", global);
}
int main() {
Initialize();
struct sigaction sa;
sa.sa_handler = &handle_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGINT, &sa, NULL);
char line[128];
while (fgets(line, sizeof line, stdin)) {
Process(line);
}
}
Assume all necessary #include
s and an implementation of the
Initialize
and Process
functions are included,
that these functions do not do anything with signals,
that the system is setup so control-C always sends the signal SIGINT,
and the C code is compiled into an executable.
While the executable is waiting for input, a user presses
control-C, which results in the SIGINT signal being triggered
and the signal handler printing out a
message like SIGINT: global=1
.
When the signal handler runs in this scenario, the signal handler will ____. Select all that apply.
While running this program, a developer observes
that pressing control-C usually
results in it printing a message like SIGINT: global=1
and continuing
to look for input, but sometimes instead it makes the program terminate.
Which of the following situations will cause the program to terminate this way?
On a Unix-like system, generally two processes with the same user IDs and group IDs ____. Select all that apply.
Consider the following access control list:
user:aaa1a:r--
user:bbb1b:rw-
user:ccc1c:r--
other::---
(other::---
represents that users and groups not otherwise listed have no access.)
To allow us to replicate the effects of this access control list with
chmod
-style permissions, we could create a new group
and make it so programs run by ____ are assigned to that group.
Which of the following scenarios can we achieve on a shared machine like the portal servers with the POSIX access control lists we discussed in lecture? Select all that apply.
Answer the following questions about the lecture material for this week.
For the following questions, consider a system with 7-bit page offsets, 10-bit virtual addresses and 11-bit physical addresses and the following page table:
index (virtual page #, in binary) | valid? | physical page # (in binary) |
000 | 0 | — |
001 | 1 | 0110 |
010 | 1 | 1001 |
011 | 1 | 1010 |
100 | 1 | 1111 |
101 | 1 | 0010 |
110 | 1 | 0110 |
111 | 0 | — |
What is a physical address for virtual address (written in binary) 0100001110? Write your answer in binary; if there is no corresponding physical address write "none".
What is a virtual address for the physical address (written in binary) 01100001110? Write your answer in binary; if there is no corresponding virtual address write "none".
Consider a program executing the following instruction:
movq %rax, (%rcx)
whose machine code is located at address 0x300010
.
Suppose %rax
contains 0x999000
, and %rcx
contains 0x123450
,
and the system uses 4096-byte pages (so addresses
have 12-bit page offsets).
In the process of fetching and executing that instruction,
the processor will
access a page table entry for virtual pages ____.
List the virtual page numbers in hexadecimal separated by commas.
For example, if you think the answer is virtual pages 0x33,
0x43 and 0x45, write 0x33,0x44,0x45
.
half-credit for just one
Consider a system with page tables that are stored in memory as a single array.
Suppose the system uses 65536 byte pages (so addresses have 16-bit page offsets),
4-byte page entries,
and the page table base register is set to physical (byte) address 0x30000
(so the page table entry with index 0 is at 0x30000
, the
one with index 1 at 0x30004
, the one with index 2 at 0x30008
, etc.)
As part of translating a virtual address to a physical address, the system:
0x30110
, and0x999433
What was the physical page number contained in the page table entry that was found? Write your answer as a hexadecimal number.
What was the virtual address being translated? Write your answer as a hexadecimal number.
On a system with a page tables stored in memory as a single array, if one process can access twice as many addresses as another, then ____. Select all that apply.
intended reading of quesiton was that "would cause segfault" means "can't access". Under that interpration, only C is correct. Some students said that, yes, we can access an address if that always triggers an exception. Under that interpretation, only A and D are correct
Answer the following questions about the lecture material for this week.
For the following questions, consider a system with:
Consider an access to virtual address 0xABCD
,
where during the access:
0x03300
,0x0123
0x0456
Then, at what physical address is the first-level page table entry located? Write your answer as a hexadecimal number.
VA = [5 bit VPN part 1][5 bit VPN part 2][6 bit page offset]
first-level addr = PTBR + VPN part 1 * 2 = 0x3300 + (binary 10101) * 2
If a process's page tables allow it to access 6400 bytes worth of distinct physical addresses, then its page tables take up at least ____ bytes.
(Assume none of the 6400 bytes of addresses the process can access are used to store its page table entries.)
half-credit for 256
each 2nd-level table can point to up to 64 * 32 bytes of data (if all the entries are valid and point to distinct data pages), to total more than 6400 bytes we need at least 4 2nd-level tables, which take up 256 bytes. We also need a first-level table to point to the second-level table tthat will take up 64 bytes even though it is mostlyinvalid entries), giving 320 bytes total.
Consider the instruction
movq %rax, (%rcx)
As part of executing an instruction a last-level page table entry corresponding to the
value of %rcx
will be retrieved.
Assume in addition to a valid bit, the page table entry contains a "writeable" bit, an "executable" bit, and a "user-mode" bit, each of which indicate if that kind of memory access can be completed using that page table entry.
The instruction will trigger an exception as a result of the page table lookup if ____. Select all that apply.
Consider the following C program (which uses the POSIX API):
// some needed #includes not shown
int global;
int main() {
global = 0;
printf("1"); fflush(stdout);
pid_t p = fork();
printf("2"); fflush(stdout);
if (p == 0) {
global = 3;
} else {
global = 4;
}
printf("%d", global); fflush(stdout);
return 0;
}
For the following questions, assume:
fork
and printf
and fflush
do not fail;global
always has a valid page table entry pointing to its storage in both processes12423
and 12324
are possible outputs of the above program.
How many other possible outputs are there?
12234 and 12243; the parent outputs 124 and the child outputs 23.
To figure out the possible outputs, let's consider where the child can output its characters. The earliest it can do it is immediately after the parent outputs the 1 (because of where the fork() is) and the latest is after it outputs 4. So our output "template" looks like 1[_]2[_]4[_] where [_] represents blanks where the child runs. We can either put "23" in exactly one slot (the child runs entirely in between two parent outputs); or we can put "2" in one slot and "3" in a later one.
This gives possibilities of 1[23]24, 12[23]4, 124[23], 1[2]2[3]4, 1[2]24[3], 12[2]4[3]. After accounting for some these appearing identical (since we can't distinguish the child writing 2 from the parent writing 2 generally), we have 12324, 12234, 12423, 12243 --- four possible outputs.
Suppose the system the above program runs on uses copy-on-write to implement
fork. Immediately after the fork
, ____.
Select all that apply.
Suppose the system the above program runs on uses copy-on-write to implement
fork. Suppose both the parent a child process completed their assignment
to global
but have not yet started the following printf
call.
Then, ____.
Select all that apply.
Answer the following questions about the lecture material for this week.
Consider the following C snippet (which uses the POSIX API):
pid_t pids[3];
for (int i = 0; i < 3; i += 1) {
printf("A"); fflush(stdout);
pids[i] = fork();
if (pids[i] == 0) {
/* child process */
printf("%d", i); fflush(stdout);
exit(0);
}
if (i >= 1) {
waitpid(pids[i-1], NULL, 0);
}
}
printf("B"); fflush(stdout);
waitpid(pids[2], NULL, 0);
Assume that fork
and waitpid
do not fail (and that all needed header files
are included).
Some possible outputs of this snippet are AA01AB2
and AA0A12B
(where the A
s and B
s are outputted
by the parent process and the 0
, 1
, and 2
are outputted by one of the three child processes
created by the call to fork()
in the loop.)
Which of the following are other possible outputs? Select all that apply.
Consider the following incomplete C snippet (which uses the POSIX API):
int input_fd = open("input.txt", O_RDONLY);
if (input_fd < 0)
handle_error();
pid_t pid = fork();
____________ /* 1 */
if (pid == 0) {
____________ /* 2 */
const char *argv[] = {"./a.out", NULL};
execv("./a.out", argv);
} else if (pid > 0) {
____________ /* 3 */
int status;
if ((pid_t) -1 == waitpid(pid, &status, 0)) {
handle_error();
}
}
In the snippet, __________
represents a blank to be replaced with code to complete the snippet.
For the following questions, assume that this program containing this snippet is not called a.out, and that open
, fork
, dup2,
execv,
closeand
waitpid` do not fail.
This code is intended to run a program called a.out
, which normally would take input from the terminal using stdin
, and have it read its input from input.txt
In order to make a.out
read from input.txt
for its standard input, one could
add a call to dup2
(like dup2(input_fd, STDIN_FILENO)
) to one of the blanks above.
Where can it be added to achieve this? (Identify the blank by the number in the comment labeling it.)
Normally, we'd use a dup2 call like dup2(input_fd, STDIN_FILENO)
to
redirect a.out
's input. What would happen if we erroneously
used dup2(STDIN_FILENO, input_fd)
? Select all that apply.
In lecture, we mentioned that temporal locality is a property that a program that accesses a value is likely to access that same value again soon.
Consider the following C snippet:
for (int i = 0; i < 1024; ++i) {
printf("%s %s\n", A[i], B[i]);
}
The snippet would exhibit more temporal locality in its array accesses if ____. Select all that apply.
Consider a direct-mapped cache whose contents are as follows:
index (written in binary) | valid | tag (written in binary) | data (written as hexadecimal bytes, lowest address first) |
00 | 1 | 1100 | AB CD |
01 | 1 | 0110 | EF 01 |
10 | 1 | 1110 | 23 45 |
11 | 0 | — | — |
Based on the contents of the cache shown above, accessing which of the following addresses would be a cache hit? All the addresses are written in binary. Select all that apply.
If a processor used this cache to read the byte value 0x99
from an address with index 01, tag 0000, and offset 1, then the cache would ____. Select all that apply.
For the following questions, consider a 2-way set associative cache with an LRU (least recently used) replacement policy whose contents are as follows.
In the cache contents below, indices and tags are written in binary, and data bytes are written as a sequence of hexadecimal bytes with the lowest address first.
way 0 | way 1 | ||||||
index (written in binary) | valid | tag (written in binary) | data | valid | tag (written in binary) | data | LRU way |
00 | 1 | 1100 | AB CD | 1 | 1101 | DE EF | 0 |
01 | 1 | 0010 | 01 02 | 1 | 0011 | 03 04 | 1 |
10 | 1 | 1110 | 23 45 | 0 | — | — | 1 |
11 | 0 | — | — | 0 | — | — | 0 |
What is the address with index 00, tag 1101, and (block) offset 1 in the above cache? Write your answer as a binary number.
Using the above cache, reading a byte from an address with index 01, tag 0011, offset 0 will _____. (Ignore effects from any changes in the other questions.) Select all that apply.
Using the above cache, reading a byte from an address with index 11, tag 1110, offset 0 will _____. (Ignore effects from any changes in the other questions.) Select all that apply.
Suppose an array is declared as follows:
unsigned char array[16];
which is located at a memory address that is a multiple of 1024 (two to the tenth power).
and then we run the following C snippet:
int count = 0;
count += array[0];
count += array[3];
count += array[6];
count /= array[1];
count /= array[4];
count /= array[7];
Suppose the above code snippet is run with a two-set direct-mapped data cache with 2 byte blocks. [Note that unlike the examples in lecture, the array elements take up 1 byte and the cache blocks are smaller]
Accessing array[0]
will use set 0 of this cache. Access which of the following
other array elements would also use set 0? Select all that apply.
mapping to set is based on addresses; because array[0]'s address is a multiple of 1024 and therefore 2, it is at the start of a 2-byte block. This block includes {array[0], array[1]}. The next block in memory is {array[2], array[3]}, and must map to the next set, set 1. After that {array[4], array[5]}, which would map to the next set, but since the cache only has two sets, this is set 0 instead of set 2. Then the next set {array[6], array[7]} maps to set 1.
Suppose the above code snippet is run with a two-set direct-mapped
data cache with 2 byte blocks.
Assume the cache is initially empty when the snippet starts and only accesses
to array
use the cache.
How many cache misses will occur?
(showing {contents of set 0 in cache}{contents of set 1 in cache} after each miss is processed) array[0] M: {01}{--}; array[3] M: {01}{23}; array[6] M: {01}{67}; array[1] H; array [4] M: {45}{67}; array[7] H
Suppose the above code snippet is run with a two-block fully associative
data cache with 2 byte blocks and an LRU (least recently used) replacement policy.
This cache could also
be described as a 2-way set associative cache with 1 set and 2-byte blocks
and an LRU replacement policy.
Assume the cache is initially empty when the snippet starts and only accesses
to array
use the cache.
How many cache misses will occur?
(showing which blocks present in cache, most recently accessed block first) array[0] M: {01}{--}; array[3] M: {23}{01}; array[6] M: {67}{23}; array[1] M {01}{67}; array [4] M: {45}{01}; array[7] M {67}{45}
For the following questions, consider a 2-way set associative cache with an LRU (least recently used) replacement policy, a write-back policy, and a write-allocate policy, whose contents are as follows.
In the cache contents below, indices and tags are written in binary, and data bytes are written as a sequence of hexadecimal bytes with the lowest address first.
way 0 | way 1 | ||||||||
index (written in binary) | valid | tag (written in binary) | data | dirty | valid | tag (written in binary) | data | dirty | LRU way |
00 | 1 | 1100 | AB CD | 1 | 1 | 1101 | DE EF | 0 | 0 |
01 | 1 | 0010 | 01 02 | 0 | 1 | 0011 | 03 04 | 1 | 1 |
10 | 1 | 1110 | 23 45 | 0 | 0 | — | — | 0 | 1 |
11 | 0 | — | — | 0 | 0 | — | — | 0 | 0 |
Using the above cache, writing a byte to an address with index 00, tag 1101, offset 1 will _____. Select all that apply.
Using the above cache, writing a byte to an address with index 01, tag 1111, offset 0 will _____. (Ignore effects from any changes in the other questions.) Select all that apply.
A system with:
Suppose the virtual address 0x56123
translates to the physical address 0x43123
.
When a TLB lookup occurs as a part of accessing this virtual address,
which TLB index will be used?
Write your answer as a hexadecimal number.
TLB lookup uses virtual page 0x56; lower 5 bits are the TLB index
Consider the following C code which is run only by calling the example()
function:
struct thread_data {
int x;
int *p;
};
void *thread_func(void *raw_ptr) {
struct thread_data *td;
td = (struct thread_data *)raw_ptr;
*(td->p) += td->x; /* LINE A */
td->x = *(td->p); /* LINE B */
return &td->x;
}
int global_array[2] = {0, 0};
void example() {
pthread_t threads[2];
for (int x = 0; x < 2; ++x) {
struct thread_data *td;
td = malloc(sizeof(struct thread_data));
td->x = x;
td->p = &global_array[x];
int ret = pthread_create(&threads[x], NULL, thread_func, (void*) td);
/* LINE C */
}
/* LINE D */
for (int i = 0; i < 2; ++i) {
void *raw_ptr;
pthread_join(threads[i], &raw_ptr);
int *p;
p = (int*) raw_ptr;
printf("got result %d\n", *p); /* LINE E */
}
/* LINE F */
}
The comments of the form "LINE X" are label particular parts of the code for the questions below.
Assume all appropriate #include
s are used and the example()
function is run
and the example()
function is the only thing that causes thread_func
to be called
or global_array
to be accessed.
When example()
causes the function thread_func
to run, the line labeled LINE B
modifies ____.
In the function example()
, the line labeled LINE E prints out
an integer that is stored ____.
The example()
function in the above code
has a memory leak because the malloc call in the first for loop
in example() does not have any corresponding free calls.
Which of the following is the earliest that a free
call or calls could be
safely added? (Assume that extra code is added to track the malloc'd pointers
so they can be freed.)
Consider the following code that uses mutexes:
pthread_mutex_t int_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t ptr_lock = PTHREAD_MUTEX_INITIALIZER;
int global_int1 = 1;
int global_int2 = 2;
int global_int3 = 3;
int *global_ptr = &global_int1;
void thread1() {
int *old_pointer;
pthread_mutex_lock(&ptr_lock);
old_pointer = global_ptr;
global_ptr = &global_int3;
pthread_mutex_unlock(&ptr_lock); /* A */
pthread_mutex_lock(&int_lock);
global_int2 += global_int1;
pthread_mutex_unlock(&int_lock);
pthread_mutex_lock(&ptr_lock); /* A */
old_pointer = global_ptr;
global_ptr = old_pointer;
pthread_mutex_unlock(&ptr_lock);
}
void thread2() {
pthread_mutex_lock(&ptr_lock);
pthread_mutex_lock(&int_lock); /* B */
global_int1 += *global_ptr;
pthread_mutex_unlock(&int_lock); /* B */
/* C */
global_ptr = &global_int2;
pthread_mutex_unlock(&ptr_lock);
}
For the following questions, assume that thread1
and thread2
are started at the same time
from different threads and run exactly once, and no other
code manipulates the global variables above.
Removing the lock and unlock call marked with the comment /* B */
would ____. Select all that apply.
It's possible for global_ptr
's final value to be &global_int1
despite thread2
's code setting to &global_int2
.
Which of the following changes would prevent that? Select all that apply.
[Question dropped due to error in code above.]
Gave everyone full credit for this question, since it did not make sense due to an error. I intended to ask the question without the old_pointer = global_ptr
line repeated. With it repeated, global_ptr
cannot be &global_int1
.
Consider the following code that uses barriers and mutexes:
pthread_barrier_t barrier;
pthread_mutex_t lock;
int x = 1, y = 2, z = 3;
void *thread1_func(void *) {
pthread_mutex_lock(&lock); /* LOCK A */
y += z;
pthread_mutex_unlock(&lock); /* UNLOCK A */
pthread_barrier_wait(&barrier);
pthread_mutex_lock(&lock); /* LOCK B */
x += z;
pthread_mutex_unlock(&lock); /* UNLOCK B */
pthread_barrier_wait(&barrier);
}
void *thread2func(void *) {
pthread_mutex_lock(&lock); /* LOCK C */
y += z;
pthread_mutex_unlock(&lock); /* UNLOCK C */
pthread_barrier_wait(&barrier);
pthread_mutex_lock(&lock); /* LOCK D */
y += z;
pthread_mutex_unlock(&lock); /* UNLOCK D */
pthread_barrier_wait(&barrier);
pthread_mutex_lock(&lock); /* LOCK E */
x += z;
pthread_mutex_unlock(&lock); /* UNLOCK E */
}
int main() {
pthread_barrier_init(&barrier, 2);
pthread_mutex_init(&lock, NULL);
pthread_t thread1;
pthread_t thread2;
pthread_create(&thread1, NULL, thread1_func, NULL);
pthread_create(&thread2, NULL, thread2_func, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("%d %d %d\n", x, y, z);
return 0;
}
Each of the calls to pthread_mutex_lock
and pthread_mutex_unlock
above are labeled
LOCK X or UNLOCK X where X is a letter.
Which (if any) of them could be removed without causing a race condition?
(That is, without making the values of x
, y
, and z
vary depending on the relative timing
of threads.)
(Assume that only matching pairs of locks and unlocks would be removed.)
Select all that apply.
The printf
in main()
avoids race conditions and always outputs the final values
of x
, y
, and z
. This would also happen if the printf were placed ____. Select all that apply.
Suppose we have a system for booking plane flights. This system needs to support booking a single ticket that encompasses multiple plane flights. To support this, the system, implemented in C with pthreads, uses the following data structures:
struct flight {
pthread_mutex_t lock;
int flight_id;
int remaining_seats;
struct tickets *tickets[MAX_SEATS];
};
struct ticket {
int ticket_number;
int num_flights;
struct flight *flights[MAX_FLIGHTS];
};
The support booking and cancelling tickets, the system implements the following functions:
struct ticket *BookTicket(int num_flights, struct flight *flights)
--- books a ticket
for the specified flights, or returns NULL if this is not possible due to insufficient
remaining seats
int ChangeTicket(struct ticket *ticket, int index, struct flight *new_flight)
---
change the index
'th flight (counting starting with 0) on the ticket ticket
to
new_flight
. Return 0 if this is not possible due to insufficient remaining seats;
otherwise return 1.
Psuedocode for these functions are as follows. Assume the
AddTicketToFlight
and RemoveTicketFromFlight
functions handle adjusting remaining_seats
.
struct ticket *BookTicket(int num_flights, struct flight *flights) {
int can_book = 1;
for (int i = 0; i < num_flights; i += 1) {
pthread_mutex_lock(&flights[i]->lock);
if (flights[i]->remaining_seats == 0) {
can_book = 0;
}
}
struct ticket *result;
if (can_book) {
result = AllocateTicket(num_flights, flights);
for (int i = 0; i < num_flights; i += 1) {
AddTicketToFlight(result, flights[i]);
}
}
for (int i = 0; i < num_flights; i += 1) {
pthread_mutex_unlock(&flights[i]->lock);
}
return result;
}
int ChangeTicket(struct ticket *ticket, int index, struct flight *new_flight) {
int successful = 0;
pthread_mutex_lock(&new_flight->lock);
if (new_flight->remaining_seats > 0) {
pthread_mutex_lock(&ticket->flights[index]->lock);
RemoveTicketFromFlight(ticket->flights[index]);
ticket->flights[index] = new_flight;
AddTicketToFlight(ticket, new_flight);
pthread_mutex_unlock(&ticket->flights[index]->lock);
successful = 1;
}
pthread_mutex_unlock(&new_flight->lock);
return successful;
}
If precautions are not taken, multiple simultaneous calls to BookTicket can deadlock. Which of the following are sufficient adjustments to prevent the deadlock? Select all that apply.
For this question:
It's possible for a simulatenous call to ChangeTicket and BookTicket to deadlock (even when those calls would not deadlock if they occurred separately and when there are no other functions manipulating the tickets and flights). Which of the following are examples of calls that could trigger this deadlock? Select all that apply.
In the psuedocode below A
, B
, etc. represent distinct flights, {X, Y, Z}
represents
an array of flights X
, Y
, and Z
, and <X, Y>
represents a ticket whose
flights are X
and Y
.
Consider a server where users connect to the server and receive messages from a list of announcements. The server implementation uses one thread for each connected user, and in addition has another thread that adds messages Each user is part of a single "channel" that has a message history. These are represented using structs as follows:
const char *messages[MAX_MESSAGES];
int last_message_index = -1;
struct User users[NUM_USERS];
pthread_mutex_t message_lock;
pthread_cond_t new_message_cv;
struct User {
int last_read_message_index; /* initially -1 */
};
When adding a message to the list of announcements, the server calls
an AddMessage
function. The threads representing user connections
repeatedly calls a function called GetNextMessage
to wait for and return a next message.
Partial implementations of these functions is shown below (where a blank written
with underscores like __________
represents some omitted code):
void AddMessage(const char *message) {
pthread_mutex_lock(&message_lock);
last_message_index += 1;
messages[last_messsage_index] = message;
________________________________________________ /* BLANK 1 */
pthread_mutex_unlock(&message_lock);
}
const char *GetNextMessage(struct User *user) {
pthread_mutex_lock(&message_lock);
while (___________________________________________________) { /* BLANK 2 */
pthread_cond_wait(&new_message_cv, &message_lock);
}
user->last_read_message_index += 1;
const char *result = messages[user->last_read_message_index];
pthread_mutex_unlock(&message_lock);
return result;
}
What code would be most appropriate to place in the blank marked with the comment BLANK 1
?
What code would be most appropriate to place in the blank marked with the comment BLANK 2
?
Consider a scheme for sending a message from machine A to machine B using unreliable messages like the one we discussed in lecture:
Suppose we use this scheme to transmit a message and the network is very unreliable: specifically, it loses the every other message machine A sends, starting with the first; and also loses every third message machine B sends, starting with the first.
Assume, however, that the network does not corrupt messages and when messages are not lost they are received quickly.
How many messages will machine A send as part of its effort to transmit this data? If A will never finish sending messages, write "infinity".
A -> B: part 1 (lost); A -> B: part 1; B -> A: ACK part 1 (lost); A -> B: part 1 (lost); A -> B: part 1; B -> A: ACK part 1
As part of retrieving a web page from a remote web server, a machine running a web browser will send an acknowledgment to the remote web server.
To send this, the machine running the web browser will send an Ethernet frame on its local network, that contains a IP packet, which contains a TCP segment.
The frame that the machine running the web browser sends on its local network for this acknowledgment will contain as its destination MAC address ____.
The frame that the machine running the web browser sends on its local network for this acknowledgment will contain as its destination IP address ____.
The frame that the machine running the web browser sends on its local network for this acknowledgment will contain as its source port number ____.
In lecture we discussed how when contacting a webserver like kytos02.cs.virginia.edu
,
several DNS servers are involved --- typically one or more a local DNS server run by the local ISP,
DNS server(s) for cs.virginia.edu
,
server(s) for virginia.edu
, and server(s) for .edu
, and one for the root of the DNS hierarchy.
If all the DNS servers for virginia.edu
become inaccessible , then we would expect
accessing web pages on kytos02.cs.virginia.edu
from a machine outside UVa ___.
was accidentally set as select-all-that-apply, but not intended
Let's say two machines A and B connected to the same wifi network are erroneously configured to have the same IP address 10.1.2.3. When a third machine C on the same wifi network attempts to send a message to 10.1.2.3, the message is only received by machine A. What most likely happened?
For the following questions assume that:
Suppose A receives a packet on the network with a destination of A and a source of B, and the packet contains:
In this situation, A can be assured that ____. Select all that apply.
Suppose A receives a packet on the network with a destination of A and a source of B, and the packet contains message encrypted with A's public key, and having as its contents:
In this situation, A can be assured that ____. Select all that apply.
Suppose B manages a popular concert venue and trusts A to handle selling tickets for it. B receives information about what tickets are booked from A, and intends to use the shared encryption and MAC key to send this information over the network without the attacker being able to fraudalently book additional tickets.
B receives messages from A with a reports of sold ticket, containing:
B always recomputes that the message authentication code to verify that it matches the rest of the data in the message, and if it does not, B ignores the message and alerts A to the problem.
Which of the following can the machine-in-the-middle attacker do to disrupt the information B receives (without B detecting something is wrong)? Select all that apply.
Suppose A and B want to communicate over an insecure network to agree about what to order for lunch.
They arrange to share public encryption and public signing keys, and decide to use the following [broken] protocol:
An attacker with sufficient control over the network (but who cannot interfere with A and B sharing their public keys securely) can interfere with the protocol to do which of the following? Selcet all that apply.
Answer the following questions about the lecture material for this week.
One application of digital signatures is to support hardware "tokens" as an alternative or supplement to passwords. For example, users might be given a small device (called a "token") that plugs into a USB port that contains a low-power processor and a private key. To login to a computer, they would plug this device into that machine's USB port, Then, the computer would send a login request to the token, and the token would send back a digital signature of the request. The computer would verify the signature and permit the login accordingly.
In order to verify the signature, suppose the computer uses certificates.
Which of the following would be appropriate way to use certificates in this case? Select all that apply.
Consider the following insecure scheme for sending "secure" emails from A to B.
A and B share an (symmetric) encryption key. When A wants to send an email to B, A takes a cryptographic hash of their email's text. Then, they send an encrypted version of their email's text, followed by the cryptographic hash.
When B receives the email, B decrypts the text, and recomputes the hash value. If the hash value B recomputes does not match, B rejects the message as tampered with and discards it silently.
Which of the following attacks does this scheme allow, assuming the encryption algorithm and cryptographic hash themselves are secure? Select all that apply.
A typical TLS handshake includes signing a cryptographic hash of prior messages with a long-term public key from the server.
A client that fails to verify this signature may ____. Select all that apply.
This question's wording confused two separate operations in the handshake --- signing of the key share, and a Finished message which is created using a MAC over the prior handshake messages. I think the problem with either case is B --- but the reason why for the case where this is just the MAC is more that the handshake could have been part of some kind of replay attack (but that's hard to reason about).
When executing the instruction addq %r8, %r9
on a single-cycle processor,
the register file's inputs will include ___. Select all that apply.
When executing the instruction addq %r8, %r9
on a five-stage pipelined
processor uses the stages we discussed in lecture,
during the decode stage, the register file's inputs will include ___.
Select all that apply.
Suppose a pipelined processor has three stages and a cycle time of 200ps.
When this processor executes five instructions, the amount of time from when the processor starts executing the first stage of the first instruction until when it finishes executing the last stage of the last instruction is _____ ps.
Assume nothing prevents the processor from starting a new instruction each cycle.
If not enough information is provided, write "unknown" and explain in the comments.
Suppose a single-cycle processor takes 1350 ps to execute each instruction. If we can divide the processor into a pipelined processor with:
and use pipeline registers (used to transfer values from one stage to the next, and not included in the work described above) with a 50ps delay. What (as integer number of ps) is the minimum cycle time the pipelined processor can have?
Consider a seven-stage pipelined processor where the stages are:
Assume that the results of arithmetic instructions are not available until near the end of the execute, part 2 stage. The results of instructions that load values from memory are not available until near the end of the memory, part 2 stage. Instructions that perform arithmetic need the values for the arithmetic by near the beginning of the execute, part 1 stage.
The processor uses forwarding (when possible without dramatically increasing cycle time) in combination with stalling to resolve data hazards.
When executing the following assembly snippet:
movq 0x1234(%r8), %r9
addq %r9, %r10
The processor will complete the writeback stage of the addq instruction ___ cycle(s) after it completes the writeback stage of the movq instruction.
When executing the following assembly snippet:
addq %r8, %r9
xorq %r9, %r10
subq %r9, %r11
The processor will complete the xorq instruction ___ cycle(s) after it completes the addq instruction, and will be complete the subq instruction ___ cycle(s) after it completes the xorq instruction.
Consider a pipelined processor design with six pipeline stages with a cycle time of 500 ps.
When a benchmark program is run on this processor:
Suppose these are the only cause of the processor not running one instruction per cycle.
If the benchmark program has one billion instructions, how long, in milliseconds to the nearest millisecond, does it take to run on this processsor?
[1 billion picoseconds is 1 millisecond.]
meant to write "of stalling and forwarding" instead of "of stalling a forwarding" (I hope this didn't confuse people???)
(1 + .1 + .05 * 2 + .02 * 4) cycles/instruction * 500 ps/cycle = 640 ps/instruction
1B instructions * 640 ps/instruction = 640 ms
Suppose a processor runs the following assembly snippet:
movq $3, %r10
outer:
movq $2, %r11
inner:
call foo
subq $1, %r11
cmpq $0, %r11
jg inner
subq $1, %r10
cmpq $0, %r10
jg outer
which could have been generated from this C code:
for (int i = 3; i > 0; i--) {
for (int j = 2; j > 0; j--) {
foo();
}
}
Suppose this processor uses a branch prediction strategy where it predicts a branch (such as a conditional jump) as taken if and only if it ran before and was taken the last time it ran (and predicts it as not taken otherwise). (This means that if a conditional jump has never been run before, it is always predicted as not taken.)
Ignoring what happens in the function foo
, the first time the above snippet is run, how
many correct branch predictions for the jg
instructions will happen?
Consider the following assembly snippet:
addq %r9, %r10
movq 0(%r13), %r9
subq %r9, %r11
On an out-of-order processor, it's possible for the addq
's result to be computed after the subq
's result is,
depending on the speed of the instructions before the assembly snippet and the movq
in the assembly snippet.
Which of the following make this more likely to happen than otherwise?
(In this question, when an instruction is "unusually slow", it takes longer than normal for an instruction's
result to be available once it is fetched.)
Select all that apply.
Consider the following assembly snippet:
addq %r8, %r9
xorq %r9, %r10
imulq %r10, %r12
addq %r11, %r10
imulq %r10, %r13
subq %r9, %r14
When executed on an out-of-order processor,
which instructions from the snippet
could have their results computed at the same time as
imulq %r10, %r12
on this processor?
Select all that apply.
Suppose an out-of-order processor using a design like we discused in lecture has 3 ALUs that each can perform an addition in one cycle.
If the processor is running the following assembly snippet:
addq %r8, %r14
addq %r9, %r15
addq %r10, %r14
addq %r11, %r15
addq %r12, %r14
addq %r13, %r15
addq %r14, %r15
To perform the additions for the assembly snippet, the processor must use a minimum of ___ cycles. (Do not include the time needed to load the instructions into the instruction queue or needed to writeback values or commit instructions. Assume once a computation is performed, its results can be used in a computation during the next cycle.)
Consider the following C function that adds two arbitrary-precision integers represented by an array of digit values ranging from 0 to 9, starting with the 1's place.
void AddNumbers(int num_digits, const char *a_digits, const char *b_digits, char *result_digits) {
char carry = 0;
for (int i = 0; i < num_digits; ++i) {
result_digits[i] = a_digits[i] + b_digits[i] + carry;
carry = 0;
while (result_digits[i] > 10) {
result_digits[i] -= 10;
carry += 1;
}
}
}
Assume this is translated to assembly in a way which does not omit any arithmetic operations written in the C code above and that it runs on a processor where each arithmetic and comparison instruction takes a constant amount of time.
Suppose we find that running the function with num_digits
of 4
and a_digits
of {9, 8, 7, 0}
and b_digits
of {2, 2, 1, 1}
is consistently slower than running with an a_digits
value of {5, 8, 1, 1}
and an unknown value of b_digits
.
Which of the following would be plausible values for b_digits
?
Suppose we knew that the AddNumbers()
function above was called with
a_digits
being the address 0x1000000
, b_digits
the address 0x2000000
and result_digits
the address 0x3000000
.
Using a side-chanel we determine that running AddNumbers()
accesses
cache set indexes 0 through 2 inclusive of a 64KB direct-mapped cache with 32-byte blocks.
Assuming that only accesses to a_digits
, b_digits
, and result_digits
used this cache, give a possible value of num_digits
.
Consider the following C code:
struct message {
unsigned int message_type;
unsigned int message_length;
char *data;
};
struct message_type {
unsigned int seen_messages;
...
};
struct message pending_messages[MAX_NUM_MESSAGES];
struct message_type message_types[NUM_MESSAGE_TYPES];
void HandleMessage(int index) {
if (index < MAX_NUM_MESSAGES) {
unsigned int type = pending_messages[index].message_type;
if (type < NUM_MESSAGE_TYPES) {
message_types[type].seen_messages += 1;
... // additional message handling code
}
} else {
abort();
}
}
Assume that:
pending_messages
is located at address 0x101000,message_types
at address 0x108000,sizeof(struct message)
is 16,sizeof(struct message_type)
is 128,If an attacker can cause the above code to execute speculatively with a chosen value of the argument index
,
then they can use the above code to execute a Spectre-style attack and read an integer from memory
at an attacker-chosen address that the above code can access but the attacker cannot.
In particular, the
attacker would set index
such that pending_messages[index].message_type
ends up being located at the address.
The code that increments seen_messages
would access a memory location based on this value, which will
cause the cache to be modified (even if this code is only executed speculatively).
The attacker will use a side channel to determine which sets of the data cache were accesssed when the
increment of seen_messages
was speculatively executed, and therefore learn about the value of type
, which is
the same as the value in the memory location the attacker chose.
Suppose an attacker selects index
such that type
ends up being the value of an attacker-chosen address when the above code is run as part of a branch misprediction.
If the attacker discovers that cache set index 44 is modified as a result of message_types[type].seen_messages += 1
,
then a possible value of type
was ____.
Write your answer as a hexadecimal number.
messge_types[0].seen_messages is in set 512, message_types[1].seen_messages in set 514, message_types[2].seen_messages in set 516, message_types[3].seen_messages in set 519, message_types[4].seen_messages in set 520, etc.
44 - 512 + 4096 = 3628 blocks away, or 3628 + 4096, or 4628 + 4096 * 2, etc.
3628 or 3628 + 4096 or 3628 + 4096*2 blocks ~~ index 3628/2 = 1814 or 1814+2048 = 3862 or ...
or, converted to hex 0x716 or 0xf16 or 0x1716 or 0x1f16, etc.
Which of the following changes to the above code would make the attack infeasible? Select all that apply.