In operating systems, memory management provides ways to dynamically allocate portions of memory to processes and free it for reuse when no longer needed. Modern operating systems use address spaces to isolate process memory.
An address space represents the set of virtual addresses that a process uses to reference memory. Virtual addresses map directly to physical addresses. Fuchsia uses VMARs (Virtual Memory Address Regions) to represent address spaces.
In Fuchsia, every process has a root VMAR, and can partition into a hierarchy of VMARs and mappings. Mappings point to an underlying VMO (Virtual Memory Object):
The VMAR is a contiguous range of virtual addresses in a particular process' address space:
VM mappings represent “mapped” ranges in the address space, i.e., virtual addresses which are backed by physical pages:
The following are available VMAR and VM mapping syscalls:
zx_vmar_allocate()
- create a new child VMARzx_vmar_map()
- map a VMO into a processzx_vmar_unmap()
- unmap a memory region from a processzx_vmar_protect()
- adjust memory access permissionszx_vmar_destroy()
- destroy a VMAR and all of its childrenSee also the Virtual Memory Address Region reference.
VMOs are containers of bytes of memory. They hold physical pages that can be mapped into address spaces through VM mappings. The following are VMO syscalls:
zx_vmo_create()
- create a new VMOzx_vmo_create_child()
- create a new child VMOzx_vmo_create_physical()
- create a new physical VMOzx_vmo_get_size()
- obtain the size of a VMOzx_vmo_op_range()
- perform an operation on a range of a VMOzx_vmo_read()
- read from a VMOzx_vmo_replace_as_executable()
- make an executable version of a VMOzx_vmo_set_cache_policy()
- set the caching policy for pages held by a VMOzx_vmo_set_size()
- adjust the size of a VMOzx_vmo_write()
- write to a VMOSee also the Virtual Memory Object reference.
The Virtual Memory Manager (VMM) is responsible for maintaining process address spaces, including:
It does this by managing relationships amongst VMARs, VM Mappings, VMOs, and hardware page tables. See also the VMM source code.
When a process starts off, its entire address space is represented as one VMAR. As different portions of the address space are mapped, the VMAR hierarchy tree gets populated. Nodes in the tree get created and destroyed as VMARs and VM Mappings are created and destroyed.
A node represents either a VMAR or a VM Mapping:
Learn more about visualizing memory usage from the root VMAR.
The VMM uses Address space layout randomization to control the address ranges where new VMARs are created within an address space. The aslr.entropy_bits
kernel command line option can be used to control the number of bits of entropy in the randomization. Higher entropy results in a sparser address space with more spread out VMARs and VM Mappings.
The Physical Memory Manager (PMM) carves up all available physical memory (RAM) on the system into pages and manages what happens to them. It is responsible for providing free physical pages to VMOs when they need them.
VMOs are demand paged, i.e. their pages are populated (committed) on demand. Pages get committed as they are written to. Until then, uncommitted pages are represented by a singleton physical zero page on the system. This avoids needlessly allocating memory for pages that have not been accessed, or are only ever read from.
VMOs can also be backed by a userspace pager, which populates specific contents in the VMO on demand, for example, contents read from a file on disk. Learn how to write a user pager.
To map a VMO:
Use zx_vmar_root_self()
to obtain a handle to the root VMAR of the process, for example:
zx_handle_t vmar = zx_vmar_root_self();
Use zx_vmo_create(...)
to create a VMO. This returns the VMO handle, for example:
const size_t map_size = zx_system_get_page_size(); zx_vmo_create(map_size, 0, &vmo);
Use zx_vmar_allocate(...)
to create a child VMAR within the parent VMAR. This returns a VMAR handle and its start address, for example:
const size_t region_size = zx_system_get_page_size() * 10; zx_vmar_allocate(vmar, ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE, 0, region_size, ®ion, ®ion_addr);
This step is optional; you can also map the VMO directly to the parent.
Use zx_vmar_map(...)
to map the VMO within a VMAR. This returns the start address of the VMO now mapped to the VMAR, for example:
zx_vmar_map(region, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo, 0, map_size, &map_addr);
Note: See VMAR unit testing code for additional examples.
In the previous example, let's say zx_vmar_map()
returned the map_addr
0x4000. To access the mapped address 0x4000, the code might look something like below:
zx_vmar_map(...&addr); // addr = 0x4000 auto arr = reinterpret_cast<int*>(addr); arr[0] = 1; // page fault
The pointer dereference would result in a page fault. To resolve the page fault, the kernel would do the following:
root_vmar
, until a VM mapping containing that address is found.ArchVmAspace
, which represents the architecture specific page tables that track the virtual to physical address mappings used by the MMU.