Virtual Memory
Virtual memory abstracts physical memory, providing each process with its own separate address space. This enables the isolation and protection of each task’s memory, and can provide efficiency improvements by mapping a single set of pages into multiple tasks.
Address Space Model
Rotary uses a higher-half model, in which the lowest 1GB of physical memory is mapped into the highest 1GB of virtual memory. On x86, the kernel’s address space begins at 0xC0000000.
The kernel is mapped into all user-mode tasks, like all modern operating systems.
The start of the kernel’s virtual address range is defined by KERNEL_START_VIRT, and the start of its kmap() range is defined by KMAP_START_VIRT.
Data Structures
Rotary uses two primary data structures to manage the virtual address space and mappings of tasks, struct vm_space and struct vm_map.
struct vm_space contains a pointer to the task’s page global directory (struct pgd), a list of mappings (struct vm_map), and a counter for how many users are currently using that virtual address space.
struct vm_map represents an individual region of physical memory mapped into the task’s virtual memory. It contains a pointer to its parent vm_space, a start address, an end address, and some flags.
Together, these two data structures provide authoritative information on the virtual address mappings of a task. These data structures are consulted by the page fault handler during a page fault to assess whether the fault occurred at an address that should be mapped into the task’s memory.
Demand Paging
As mentioned in a previous section, Rotary does not immediately apply mappings to the task’s page table. Instead, it makes use of demand paging, in which the page table is only updated upon a page fault.
The page fault handler is expected to invoke vm_space_page_fault(), passing the faulting task’s address space and faulting address as arguments.
The virtual memory page fault handler will then iterate through the vm_map entries in the task’s address space to assess whether the faulting address is located in any of these mapped regions.
If the region is mapped, vm_space_map_page() is invoked to allocate a physical page for the mapping, and update the task’s page table to reference the newly allocated page.
Creating and Destroying an Address Space
A new address space can be created in the following manner:
struct vm_space * space = vm_space_new();
This function allocates memory from the kernel heap, initialises the space’s mappings list, and creates a new page global directory via ptable_pgd_new(). By default, this will copy the kernel mappings into the new page table, as these mappings must be present immediately.
Destroying an address space is just as simple,
Creating and Adding Mappings to an Address Space
Relevant Source
kernel/mm/vm.c- Contains functions to create, manipulate and destroy address spaces. Handles page faults.