Show all solutions
1. Lonely isolated kernels (15 points)
QUESTION 1A. How do timer interrupts help an operating system kernel enforce process isolation? List all that apply.
- Timer interrupts stop processes from writing to kernel-only memory.
- Timer interrupts ensure that processes exit if they run for too long.
- Timer interrupts ensure that processes yield if they run for too long.
- Timer interrupts ensure that the kernel gets many chances per second to make scheduling decisions.
- None of the above.
QUESTION 1B. How do page tables help an operating system kernel enforce process isolation? List all that apply.
- Page tables give each process its own view of primary memory.
- Page tables ensure that processes cannot access kernel-only memory.
- Every page table corresponds to a process, and the pages mapped in that page table are all owned by that process.
- Page tables ensure that a process exits if it accesses an address for which the process has no permission.
- Page tables ensure that the kernel gets control if a process accesses an address for which the process has no permission.
- None of the above.
QUESTION 1C. How does protected control transfer help an operating system kernel enforce process isolation? List all that apply.
- Protected control transfer lets a kernel control the environment which is active when the machine is running with full privilege.
- Protected control transfer lets a process communicate with the kernel by causing the kernel to start executing at an arbitrary location.
- Protected control transfer lets a process communicate with the kernel by causing the kernel to start executing at a preconfigured location.
- Protected control transfer ensures that a process can be restarted exactly where it left off.
- None of the above.
2. Water through a sieve (35 points)
Memory leaks are the bane of WeensyOS. In this problem, you are asked to find and describe leaks from fragments of a WeensyOS kernel written by Sherron Watkins. There are more general questions about kernels too.
QUESTION 2A. First consider Watkins’s implementation of syscall_page_alloc.
int syscall_page_alloc(uintptr_t addr) {
if (addr < PROC_START_ADDR || addr >= MEMSIZE_VIRTUAL || (addr % PAGESIZE) != 0) {
return -1;
}
void* ptr = kalloc(PAGESIZE);
if (!ptr || vmiter(current, addr).try_map(ptr, PTE_PWU) < 0) {
return -1;
}
memset(ptr, 0, PAGESIZE);
return 0;
}
This function is defined in kernel.cc and called (from the kernel’s syscall
function) as return syscall_page_alloc(current->regs.reg_rdi).
What statements are true about syscall_page_alloc and its addr parameter?
List all that apply.
addrwas originally supplied by a WeensyOS process as a system call argument.addrwas passed in the%rdiregister, as required by WeensyOS’s system call calling convention.- System calls like
page_allocinvolve voluntary protected control transfer from processes into the kernel. - The WeensyOS kernel’s system call handler stores process registers like
%rdiinto the relevantstruct proc. - None of the above.
QUESTION 2B. Which page table is active on the processor during line 9 of
syscall_page_alloc, when the contents of the newly-allocated page are
initialized to 0?
QUESTION 2C. List the statements about ptr that are true whenever
syscall_page_alloc returns 0.
reinterpret_cast<uintptr_t>(ptr)is page-aligned (a multiple ofPAGESIZE).reinterpret_cast<uintptr_t>(ptr) == addr.reinterpret_cast<uintptr_t>(ptr) != addr.reinterpret_cast<uintptr_t>(ptr) >= PROC_START_ADDR.- None of the above.
QUESTION 2D. Describe a situation that might cause Watkins’s
syscall_page_alloc to leak at least one page of memory.
QUESTION 2E. Now consider Watkins’s implementation of syscall_exit. (Her
implementation takes the process to exit as an argument.)
void syscall_exit(proc* p) {
for (uintptr_t addr = PROC_START_ADDR; addr < MEMSIZE_VIRTUAL; addr += PAGESIZE) {
vmiter it(p, addr);
if (it.present()) {
kfree(it.kptr());
}
}
kfree(reinterpret_cast<void*>(p->pagetable));
p->state = P_FREE;
}
Watkins’s syscall_exit has no special case for CONSOLE_ADDR. Does it need
one? Explain briefly.
QUESTION 2F. What kind of memory does Watkins’s syscall_exit leak, and how
would you fix this leak? Explain briefly.
QUESTION 2G. If you remove one line of code from Watkins’s syscall_exit, it
will start to leak process descriptors, eventually causing syscall_fork to
fail regardless of memory availability. Which line?
QUESTION 2H. Now consider Watkins’s implementation of syscall_fork.
int syscall_fork() {
pid_t pid = free_pid(); // finds a free ptable slot; returns -1 if none
if (pid < 0 || pid >= MAXNPROC) {
return -1;
}
ptable[pid].pagetable = kalloc_pagetable();
if (!ptable[pid].pagetable) {
return -1;
}
for (uintptr_t addr = 0; addr < PROC_START_ADDR; addr += PAGESIZE) {
vmiter parit(current, addr); // parent iterator
vmiter chit(&ptable[pid], addr); // child iterator
if (chit.try_map(parit.pa(), parit.perm()) < 0) {
return -1;
}
}
for (uintptr_t addr = PROC_START_ADDR; addr < MEMSIZE_VIRTUAL; addr += PAGESIZE) {
vmiter parit(current, addr);
if (!parit.present()) {
continue;
}
void* ptr = kalloc(PAGESIZE);
if (!ptr) {
return -1;
}
// ... rest of function elided ...
What kinds of memory might be leaked if Watkins’s syscall_fork returns on line 4? List all that apply.
- Page table pages
- The console
- Kernel memory
- Memory containing a process code segment
- Memory containing a process data segment
- Memory containing process heap pages
- Memory containing a process stack
- None of the above
QUESTION 2I. What kinds of memory listed in Question 2H might be leaked if
Watkins’s syscall_fork returns on line 9? List all that apply.
QUESTION 2J. What kinds of memory listed in Question 2H might be leaked if
Watkins’s syscall_fork returns on line 16? List all that apply.
QUESTION 2K. What kinds of memory listed in Question 2H might be leaked if
Watkins’s syscall_fork returns on line 27? List all that apply.
3. A garland of caches (20 points)
QUESTION 3A. Match each type of cache (1–4) with a read request that cache might use if an access causes a cache miss (A–F). You will use each read request at most once.
-
- Processor cache
- Translation lookaside buffer
- Buffer cache
- Stdio cache
-
- System call
- Drive READ command
- Instruction fetch
- Page table walk
- Cache line load
- None of the above
QUESTION 3B. Write a brief narrative explaining how executing a single
line of C++ code in a process, such as int ch = fgetc(stdin), might activate
all of these caches (i.e., (1) processor cache, (2) translation lookaside
buffer, (3) buffer cache, and (4) stdio cache).
QUESTION 3C. You’re powering through pset 4 with the aid of a secret snack drawer that can hold up to 3 types of snack at a time. This snack drawer operates as a 3-slot snack cache for the dining hall. You hunger for these snacks in this order:
- Apple slice
- Blueberry
- Chip
- Apple slice
- Dosa bite
- Blueberry
- Egg (hard-boiled)
- Apple slice
- Blueberry
- Chip
(So the reference string is ABCADBEABC.)
If you use the optimal eviction policy to manage your snack drawer, which snacks will be evicted from your drawer (i.e., thrown out and replaced with a different snack)? List the evicted snacks in order by time; you may use letters instead of names.
QUESTION 3D. If you eat the same snacks in the same order but manage your snack drawer with the least-recently-used (LRU) policy, which snacks will be evicted from your drawer? List the evicted snacks in order by time; you may use letters instead of names.
4. Wrestling with stdio (30 points)
Linda McMahon is working on problem set 4, specifically on the combination of single-character writes and seeks. Here’s the relevant part of her code, which is based on the single-slot cache we presented in section:
struct io61_file {
int fd;
static constexpr off_t bufsize = 4096;
unsigned char cbuf[bufsize];
off_t tag;
off_t end_tag;
off_t pos_tag;
};
int io61_writec(io61_file* f, int ch) {
// flush to make room if character doesn’t fit
if ((f->pos_tag < f->tag || f->pos_tag >= f->tag + f->bufsize)
&& io61_flush(f) < 0) {
return -1;
}
f->cbuf[f->pos_tag - f->tag] = ch;
++f->pos_tag;
if (f->end_tag < f->pos_tag) {
f->end_tag = f->pos_tag;
}
return 0;
}
int io61_flush(io61_file* f) {
if (f->tag < f->end_tag) { // flush cache buffer
ssize_t nw = write(f->fd, f->cbuf, f->end_tag - f->tag);
if (nw != f->end_tag - f->tag) {
return -1;
}
f->tag = f->end_tag;
}
if (f->tag != f->pos_tag) { // move to requested file position
off_t off = lseek(f->fd, f->pos_tag, SEEK_SET);
if (off == -1) {
return -1;
}
f->tag = f->end_tag = f->pos_tag;
}
return 0;
}
int io61_seek(io61_file* f, off_t off) {
f->pos_tag = off; // delay actual seek to next flush or write
return 0;
}
(Recall that io61_writec writes the character to the cache, returning -1 on
error; and io61_flush writes all cached data to the underlying file
descriptor, returning -1 if not all cached data could be written.)
QUESTION 4A. Parts A through C ask you what system calls McMahon’s library
would make for different test programs. In each case, f->fd == 1 and
f->tag, f->end_tag, and f->pos_tag are all initially 0. You may assume
that every system call returns the value expected by the library (so
io61_writec and io61_flush never return -1). As an example, this test
program:
io61_writec(f, 'A');
io61_writec(f, 'B');
io61_flush(f);
would make one system call, write(1, "AB", 2).
What system calls would McMahon’s library make for this test program?
for (int i = 0; i != 5096; ++i) {
io61_writec(f, '6');
}
io61_flush(f);
QUESTION 4B. Under the same assumptions, what system calls would McMahon’s library make for this test program?
io61_writec(f, 'H');
io61_flush(f);
io61_seek(f, 1);
io61_writec(f, 'i');
io61_flush(f);
QUESTION 4C. Under the same assumptions, what system calls would McMahon’s library make for this test program?
for (int i = 0; i != 3; ++i) {
io61_writec(f, 'X');
}
io61_seek(f, 5000);
for (int i = 0; i != 3; ++i) {
io61_writec(f, 'Y');
}
io61_flush(f);
QUESTION 4D. Does McMahon’s library handle short writes correctly? Explain briefly.
QUESTION 4E. True or false: McMahon’s cache is correct for sequential writes (with the possible exception of short writes). Explain briefly if unsure.
QUESTION 4F. True or false: McMahon’s cache is efficient for sequential writes (with the possible exception of short writes). Explain briefly if unsure.
QUESTION 4G. Which of these non-sequential access patterns will McMahon’s
cache handle correctly (assuming no short writes)? List all that apply.
All block writes are executed with repeated io61_writec calls.
- Repeatedly writing the same byte (
writec(ch);seek(0);writec(ch); etc.) - Strided access with block size 1 and stride 5000
- Strided access with block size 1 and stride 1000
- Strided access with block size 4096 and stride 1000000
- Reverse sequential access
- Random access
- None of the above
QUESTION 4H. Which of the non-sequential access patterns from the previous question offer good opportunities for write coalescing (whether or not McMahon’s cache implements that optimization)? List all that apply.
QUESTION 4I. Change one line of code so that McMahon’s cache is correct for all listed non-sequential access patterns (with the possible exception of short writes).
5. The end (5 points)
QUESTION 5A. How should this class adapt to AI coding assistants? Any answer but no answer will receive full credit.