Memory management

Updated: April 19, 2023

The virtual memory system implements the POSIX model for memory management along with some QNX Neutrino extensions.

These extensions include:

The memory manager's task is to manage virtual memory, not physical memory. Virtual memory is an abstraction of physical memory that allows the content of memory to come from anywhere in RAM, as well as from files in a filesystem, mapped device registers, and so on. Virtual memory gives a per-process view of memory, which helps with safety and security, but also allows for shared memory objects and shared libraries.



Figure 1. Virtual memory is an abstraction of physical memory.

The hardware that you run QNX Neutrino on must have a Memory Management Unit (MMU). The MMU is turned on early in the startup sequence, before the kernel itself starts. Once the MMU has started, there are no direct references to physical memory; the MMU translates virtual addresses into physical ones. It's up to the memory manager to create the information for managing physical memory, including the mappings of virtual to physical addresses.

The MMU divides physical memory into fixed-size pieces called pages that are usually (but not always) 4 KB. Some physical memory is used for the image filesystem, and the memory manager has a boot heap that it uses to allocate the data structures that it needs. System RAM (or “sysram”) is the physical memory that the memory manager can allocate in response to requests for memory.



Figure 2. Managing physical memory.

A zone is a fixed-size, contiguous range of physical memory (currently 16 MB). The division of RAM into zones is intended to reduce fragmentation by having zones dedicated to specific allocation types.

The memory manager creates enough virtual pages to service all available memory, including sysram, files, and so on, but there isn't a fixed association between virtual and physical pages. If a virtual page is associated with a physical page, we say that the virtual page is backed by physical memory.



Figure 3. Virtual pages.

Each process has its own virtual address space, which is a per-process view of virtual memory. This protects process address spaces from each other, preventing coding errors in a thread in one process from damaging memory used by threads in other processes or even in the OS. This protection is useful both for development and for the installed runtime system, because it makes postmortem analysis possible. Almost all OS services and drivers are independent processes.

A process's virtual address space is divided into regions, which are ranges of contiguous virtual addresses that refer to various pieces of memory. Maps associate virtual addresses with virtual memory, or with a physical device, and include permissions. The mmap() function creates regions and the maps. Initially no memory backs the regions.



Figure 4. Two virtual address spaces illustrating four types of faults.

The default behavior is faulting and paging on demand; faults can occur in various situations corresponding to the numbers in the above diagram:

  1. The first time you access a region created with mmap(), a fault occurs, and the memory manager associates the map with a virtual page and a physical page.
  2. Physical pages back virtual pages when needed, but aren't necessarily always assigned. When you access a virtual page that isn't backed, the memory manager gets a physical page for it.
  3. If there's no region, a SIGSEGV occurs.
  4. Two address spaces have regions that refer to the same virtual page. This can occur after a fork(), where the child has a copy of the parent's regions. Shared memory is similar. In this case, the maps are made read-only; on a write (if permitted), a fault occurs, and the memory manager allocates a new virtual page associated with a physical page for the faulting process.

The memory manager keeps track of how much memory is needed to back all allocated memory. The mmap() function fails if there isn't enough memory for it to reserve.

The fact that memory is lazily backed (that is, reserved and then backed when it's needed) can cause some confusion about the MAP_LAZY flag for mmap(). If you set this flag, then the memory isn't even reserved; a fault then causes a SIGBUS.

The exception to using faulting to back memory is mapping something that's implicitly or explicitly backed by a physical object. Examples include mappings using MAP_PHYS, shared memory objects created with shm_ctl(), and typed memory. For memory mapped with MAP_ANON or MAP_FILE, a fault occurs when a page is initially accessed, and may also occur on later accesses.