Problem set 3b: WeensyOS Fork

This assignment builds on problem set 3a to add fork support.

Get the code

The p-fork.cc and p-exit.cc programs that exercise pset 3b functionality are new. Get them with

$ git pull; git pull --no-rebase handout main

or, alternately

$ git pull; git pull --no-rebase https://github.com/cs61/cs61-f26-psets.git main

You should have no conflicts, but if you do the usual advice applies.

Phase 6: Fork

The fork system call starts a new process by copying an existing process. The process that calls fork is called the parent process, and the newly created copy is called the child process. The system call appears to return twice: it returns 0 to the child, and returns the child’s process ID to the parent.

Run WeensyOS with make run-fork. Alternately, at any time, press the ‘f’ key; this will soft-reboot WeensyOS and ask it to run a single p-fork process, rather than the gang of allocators. You should see something like this:

Initial fork state

This is because you haven’t implemented fork yet.

Implement fork.

When you’re done, you should see something like this after make run-fork (or pressing ‘f’).

Fork works

An image like this means you forgot to copy the data for some pages, so the processes are actually sharing stack and/or data pages:

Incorrect fork

Phase 7: Freeing memory

So far, none of your test programs have ever freed memory or exited. Memory allocation’s pretty easy when there’s no free! So let’s add free by allowing applications to exit.

In this phase, you’ll implement the sys_exit system call, which exits the current process. This is a capstone since freeing memory will tend to expose weaknesses and problems in your other code.

To test your work, use make run-exit. Alternately, you can press ‘e’ at any time, which reboots WeensyOS to run the p-exit program. (Initially it’ll crash because sys_exit() isn’t implemented yet.) p-exit processes alternate among forking new children, allocating memory, and exiting. The result is that once your code is correct, p-exit makes crazy patterns forever, like this:

Fork with exit

A fully correct OS can run p-exit indefinitely. In an OS with a memory leak, the physical memory map will collect L pages (L for leak), persistent blank spots, or both, and if run long enough, these pages will take over the screen. Here’s an example; note the Ls accumulating at the end of the physical memory map:

Fork with exit and slow leak

Reducing ALLOC_SLOWDOWN in p-exit may encourage errors to manifest, but you may need to be patient.

Here’s your task.

  1. Complete kfree so that kfree frees memory. Make sure that kalloc can re-use freed memory.

  2. sys_exit should mark a process as free and free all of its memory. This includes the process’s code, data, heap, and stack pages, as well as the pages used for its page directory and page table pages. The memory should become available again for future allocations.

    Use vmiter and ptiter to enumerate the relevant pages. But be careful not to free the console.

  3. You may also need to change fork and perhaps other places. In p-exit, unlike in previous parts of the pset, sys_fork and sys_page_alloc might be called when there’s not enough memory to create a new process or allocate or map a page. Your code should handle this cleanly, without leaking memory in any situation.

    If a sys_page_alloc or sys_fork system call runs out of memory during its operation:

    • The system call must clean up after itself. Any kernel memory or other resources (e.g., process slots) allocated during the system call must be freed.

    • The system call must return -1 to the caller.

    Note that out-of-memory events in system calls must not panic the kernel, and they must not cause the calling process to exit. It is not an error when a system call fails! Instead the error return value is passed to the process to handle as it prefers.

There should be no memory leaks!

About physical memory iterators (ptiter)

Phase 8: Shared read-only memory

It’s wasteful for fork() to copy all of a process’s memory. For example, most processes, including p-fork, never change their code. So what if we shared the memory containing the code? That’d be fine for process isolation, as long as neither process could write the code.

Change process_setup to map read-only program segments using read-only memory. Use the seg.writable() function to detect whether a program segment can use read-only memory. Then, in fork, share read-only pages between processes rather than copying them; and, in exit, ensure that shared pages are correctly accounted for when a process exits.

Use vmiter::writable() to detect whether a page is read-only. Do not use an equality test like it.perm() == (PTE_P | PTE_U)! vmiter::perm() values often include bits like PTE_A and PTE_D that are automatically set by processor hardware, so equality tests on it.perm() will usually fail.

When you’re done, running p-exit should look like this:

Shared read-only memory

Each process’s virtual address space begins with an “S”, indicating that the corresponding physical page is Shared by multiple processes.

Turnin

You will turn in your code by pushing your git repository to github.com/cs61/YOUR-PSET_REPO.git and updating the grading server.