Working with Memory

Updated: April 19, 2023

(or The Persistence of Memory, with a nod to Salvador Dali)

In the Process Manager chapter of the System Architecture, we looked at how the OS manages memory. This chapter describes how you can work with memory.

Let's start with some definitions. By memory, we mean anything that can be accessed via physical addresses, but there are various classifications of memory:

RAM vs non-RAM
RAM refers to what we typically consider to be memory; you can read from it or write to it.

Non-RAM refers to memory-mapped I/O or memory-mapped devices. For example, some hardware exposes its registers in this way. You can treat non-RAM as RAM, and it behaves somewhat as RAM, but it really isn't.

This is a QNX Neutrino distinction. SYSRAM is any RAM that's available for general-purpose allocation by the system. Once the system has booted, this is the memory that you can allocate by using malloc(). If you call mmap() to map a file, the backing memory comes from SYSRAM.

Non-SYSRAM is whatever RAM isn't included in SYSRAM, including memory that's reserved by startup for memory pools to be used for specific purposes such as USB, graphics, or cameras.

Pageable vs wired
Pageable memory is reserved when you allocate it, but might not yet be backed; that is, it might not yet be associated with physical memory. The physical memory association can change under your feet, so you need to be careful. Pageable memory is fine for malloc() or memory-backed files.

Wired memory is backed as soon as it's allocated.

There's a range of specificity in the memory chosen, from allowing the OS to select whatever memory is most convenient for it, to accessing memory at a particular physical address:
  • If you use malloc(), you usually don't care where the backing memory really is, and there isn't a way for you to specify where it is.
  • If you're memory-mapping a file, you want the content of the memory to reflect what's in the file, but you usually don't care where the backing memory is—the content might not even be in RAM yet; it might still be on the disk.
  • You might need memory in a given range, such as below 4 GB.
  • If you're mapping a memory-mapped device, the memory needs to be at the exact physical address identified.
Non-contiguous, contiguous, and mostly contiguous
  • If you call malloc() or map a file, you usually don't care if the memory is contiguous. Note that if you don't ask for contiguous memory, you'll likely get the most scattered memory available; the memory manager does this in order to reduce the fragmentation of contiguous regions in the system.
  • Contiguous memory must be in one block.
  • If you ask for mostly contiguous memory, the physical memory allocator tries to get contiguous memory, but without taking “too long” to look for it. This is handy if you want to minimize the number of page table entries, but you don't want to wait while the allocator scans all the physical memory looking for a contiguous block.
Shared vs private
Changes that you make to a shared memory mapping are propagated back to the backing object; private memory is your own copy.
Memory attributes
These include the caching policy and memory ordering.
Object-backed vs direct mapping
Is there an object underneath, or are you directly mapping RAM?

Let's look at an example. The pidin utility displays (among other things) information about sections of the system page. If you run pidin syspage=asinfo, the output might look like this:

Header size=0x00000108, Total Size=0x00000cd0, #Cpu=2, Type=256
Section:asinfo offset:0x00000710 size:0x00000340 elsize:0x00000020
  0000) 0000000000000000-000000000000ffff o:ffff a:0000 p:100 c:0 n:/io
  0020) 0000000000000000-000fffffffffffff o:ffff a:0010 p:100 c:0 n:/memory
  0040) 0000000000000000-00000000ffffffff o:0020 a:0010 p:100 c:0 n:/memory/below4G
  0060) 0000000000000000-0000000000ffffff o:0020 a:0010 p:100 c:0 n:/memory/isa
  0080) 0000000006000000-00000000ffefffff o:0020 a:0013 p:100 c:0 n:/memory/device
  00a0) 00000000fff00000-00000000ffffffff o:0020 a:0005 p:100 c:0 n:/memory/rom
  00c0) 0000000000000000-000000000009f7ff o:0060 a:0017 p:100 c:0 n:/memory/isa/ram
  00e0) 0000000000100000-0000000000ffffff o:0060 a:0037 p:100 c:0 n:/memory/isa/ram
  0100) 0000000001000000-0000000005ffffff o:0040 a:0037 p:100 c:0 n:/memory/below4G/ram
  0120) 0000000006000000-000000003fedffff o:0080 a:0017 p:100 c:0 n:/memory/device/ram
  0140) 000000003ff00000-000000003fffffff o:0080 a:0017 p:100 c:0 n:/memory/device/ram
  0160) 00000000000f6a00-00000000000f6a23 o:0020 a:0007 p:100 c:0 n:/memory/acpi_rsdp
  0180) 00000000fee00000-00000000fee003ef o:0020 a:0003 p:100 c:0 n:/memory/lapic
  01a0) 000000000142c038-0000000001c9bb7b o:0020 a:0005 p:100 c:0 n:/memory/imagefs
  01c0) 0000000001400f30-000000000142c037 o:0020 a:0007 p:100 c:0 n:/memory/startup
  01e0) 000000000142c038-0000000001c9bb7b o:0020 a:0007 p:100 c:0 n:/memory/bootram
  0200) 0000000000000000-00000000ffffffff o:ffff a:0010 p:100 c:0 n:/virtual
  0220) ffff800000001000-ffff8000000f25d0 o:0200 a:0000 p:100 c:0 n:/virtual/vboot
  0240) 0000000000001000-000000000009efff o:00c0 a:0007 p:100 c:0 n:/memory/isa/ram/sysram
  0260) 0000000000106000-0000000000108fff o:00e0 a:0007 p:100 c:0 n:/memory/isa/ram/sysram
  0280) 000000000010f000-00000000004f6fff o:00e0 a:0007 p:100 c:0 n:/memory/isa/ram/sysram
  02a0) 00000000004f8000-0000000000ffffff o:00e0 a:0027 p:100 c:0 n:/memory/isa/ram/sysram
  02c0) 0000000001000000-000000000142bfff o:0100 a:0007 p:100 c:0 n:/memory/below4G/ram/sysram
  02e0) 0000000001c9c000-0000000005ffffff o:0100 a:0027 p:100 c:0 n:/memory/below4G/ram/sysram
  0300) 0000000006000000-000000003f2d4fff o:0120 a:0007 p:100 c:0 n:/memory/device/ram/sysram
  0320) 000000003ff00000-000000003fffffff o:0140 a:0007 p:100 c:0 n:/memory/device/ram/sysram

For details about the above information, see the section on asinfo in the “System Page” chapter of Building Embedded Systems.

The memory regions in this example include the following:

The /virtual and /virtual/vboot regions aren't currently used.

Note: QNX Neutrino supports the use of IOMMU/SMMU hardware through the smmuman manager, which you can use to ensure that DMA devices can't access physical memory that you haven't explicitly granted them access to. For more information, see the SMMUMAN User's Guide.