Locking memory

Updated: October 26, 2022

The QNX Neutrino RTOS supports POSIX memory locking, so that a process can avoid the latency of fetching a page of memory, by locking the memory so that the page is memory-resident (i.e., it remains in physical memory). Locked memory is also called wired memory.

Note: It's the association between virtual and physical pages of memory that gets locked.

The levels of locking are as follows:

Unlocked
Unlocked memory can be paged in and out. Memory is allocated when it's mapped, but page table entries aren't created. The first attempt to access the memory fails, and the thread stays in the WAITPAGE state while the memory manager initializes the memory and creates the page table entries.

Failure to initialize the page results in the receipt of a SIGBUS signal.

Locked
Locked memory may not be paged in or out. Page faults can still occur on access or reference, to maintain usage and modification statistics. Pages that you think are PROT_WRITE are still actually PROT_READ. This is so that, on the first write, the kernel may be alerted that a MAP_PRIVATE page now is different from the shared backing store, and must be privatized.

Memory that's mapped from a typed-memory file descriptor is implicitly locked. Shared memory objects that are populated with shm_ctl() are implicitly locked, unless you use the SHMCTL_LAZY flag.

To lock and unlock a portion of a process's memory, call mlock() and munlock(); to lock and unlock all of a process's memory, call mlockall() and munlockall(). The memory remains locked until the process unlocks it, exits, or calls an exec*() function. If the process calls fork(), a posix_spawn*() function, or a spawn*() function, the memory locks are released in the child process.

More than one process can lock the same (or overlapping) region; the memory remains locked until all the processes have unlocked it. Memory locks don't stack; if a process locks the same region more than once, unlocking it once undoes all of the process's locks on that region.

To lock all memory for all applications, specify the -ml option for procnto. Thus all pages are at least initialized (if still set only to PROT_READ).

Superlocked
(A QNX Neutrino extension) No faulting is allowed at all; all memory must be initialized and privatized, and the permissions set, as soon as the memory is mapped. Superlocking covers the process's whole address space.

A process's memory is superlocked if at least one of these conditions is met:

  • The process calls mlockall() specifying MCL_CURRENT | MCL_FUTURE for the flags.
  • The process installs an interrupt service routine (ISR).
  • One of its threads successfully requests I/O privileges by doing the following:
    1. Enabling the PROCMGR_AID_IO ability for the process. For more information, see procmgr_ability().
    2. Calling ThreadCtl(), specifying one of the _NTO_TCTL_IO* flags. For example:
      ThreadCtl( _NTO_TCTL_IO_PRIV, 0 );
          
  • The system as a whole is superlocked with the -mL option for procnto.

For MAP_LAZY mappings, memory isn't allocated or mapped until the memory is first referenced for any of the above types. Once it's been referenced, it obeys the above rules—it's a programmer error to touch a MAP_LAZY area in a critical region (where interrupts are disabled or in an ISR) that hasn't already been referenced.