In this web-page I’m going to talk about how is OpenBSD virtual memory (UVM) system designed and implemented. I will include pointers to code that show where exactly things happen. More detailed information of UVM can be found in the original dissertation that presents UVM by Cranor: The UVM Virtual Memory System.
Virtual memory sub-system is required to control how pages are used by processes. In particular, virtual memory subsystem provides:
The purpose of the VM data structures is to give semantic information to pages listed in the process page table. This is used to handle pages properly depending on their purpose in the process address space.
We start from the proc structure. It holds all the information required for the process to run. In particular, it has the field p_vmspace that points to the structure that keeps information about thr process address space.
The vmspace structure holds statistics of the address space itself. Then, this structure points to the vm_map that is the central structure on the VM.
The _vmmap structure connects the machine-dependent code to the virtual memory. pmap points to the structure that keeps the MMU page table entries up-to-date (this is defined per architecture). rbhead index the entries by virtual address. This is searched to find the information of some page when a fault happens. Every entry in the VM represents a range of addresses whose pages hold the same semantic (FS or anonymous).
vm_map_entry first defines the range in which it is valid. It also points to vm_aref and uvm_object that link the entry with the right backing store.
UVM is composed of two layers. The first layer, amap, is reserved for pages private to processes. They are anonymous and backed up by the swap device. In case of a page fault, the handler first look into this layer for the page. If available, it is brought to memory.
The second layer is called object. This layer is shared among all the processes. Currently, it has three possible sources: file system, raw device, and anonymous. Pages can be promoted from this layer to amap.
A good walk-through of the fault handler can be found at the beginning of the uvm_fault.c file. In order to understand better the code, you can follow “the code is structured as follows” in these comments.
The fault handler is implemented in the uvm_fault function. It takes care of 2 cases with 2 sub-cases each (1A, 1B, 2A and, 2B). The first case corresponds to the page in the first layer, amap. In this case, there are two sub-cases. Case 1A, if the page has only one reference, we only need to bring the page from swap to memory. Case 1B, the page has more than one reference and a copy (COW) has to be made in case a write happened.
The second case happens when the page is not available in the amap. Then, the are two sub-cases. Case 2A, the page is found on the object layer. Case 2B, the page has to be zero-filled from the object layer and promoted to the amap layer.
Next I explained some variables that are not explained very well but are key to understand the code.