Memory Management in Xous

Memory is allocated with the MapMemory syscall. This call accepts four arguments:

  • physical: Option<NonZeroUsize>: The physical address you would like to allocate. Specify None if you don't need a particular address.
  • virtual: Option<NonZeroUsize>: The virtual address you would like to allocate. Specify None if you don't need a particular virtual address.
  • size: NonZeroUsize: The size of the region to allocate. This must be page-aligned.
  • flags: MemoryFlags: A list of platform-specific flags to apply to this region.

The memory will return a MemoryRange that encompasses the given region.

You can free memory with UnmapMemory, though be very careful not to free memory that is currently in use. UnmapMemory simply takes the MemoryRange returned by MapMemory.

Physical Addresses

A program rarely needs to access physical addresses, and in most operating systems it's not the kind of thing you can actually do. However, Xous is designed to be embedded, so it's entirely legal to request a physical address.

The trick is that you can only request physical addresses that actually exist. For example, you cannot request a physical address for a mirrored region of a peripheral because that is not a valid address.

If you request a physical address from main RAM, the memory will be zeroed when you receive it. Peripherals and ares that are not in main RAM will not be zeroed. It is for this reason that system services are recommended to claim all peripherals before running user programs.

Virtual Addresses

All Xous programs run with virtual memory enabled. Attempting to perform an illegal operation will result in an exception. If you have an exception handler installed, illegal memory accesses will run this exception handler which may fix up the exception.

Demand Paging

When you allocate memory using MapMemory(None, None, ..., ...), you will be handed memory from the DEFAULT_BASE. This memory will not be backed by a real page, and will only be allocated by the kernel once you access the page. This allows threads to allocate large stacks without running out of memory immediately.

Pages that are mapped-but-unallocated are visible in a process' page table view. As an example, consider the following excerpt from a page table view:

    38 60026000 -> 400a3000 (flags: VALID | R | W | USER | A | D)
    41 60029000 -> 40108000 (flags: VALID | R | W | A | D)
    42 6002a000 -> 40109000 (flags: VALID | R | W | A | D)
    43 6002b000 -> 00000000 (flags: R | W)
    44 6002c000 -> 00000000 (flags: R | W)

Addresses 0x60026000 (#38), 0x60029000 (#41), and 0x6002a000 (#42) are all allocated. The rest of the pages are valid-but-unallocated.

Address 0x60026000 (#38) is mapped to the process and has a valid physical address. Reads and writes to this page are backed by physical address 0x400a3000.

Addresses 0x60029000 (#41) and 0x6002a000 (#42) are still owned by the kernel, likely because they were being cleared.

Addresses 0x6002b000 (#43) and 0x6003c000 (#44) are on-demand allocated. They have no physical backing, and attempting to access them will result in a kernel fault where they will be allocated. When the page is allocated, it will be given the flags R | W in addition to default kernel flags.

The Heap

When we talk about "The Heap" we mean data that is managed by functions such as malloc. Xous has a pair of syscalls that behave vaguely like the Unix brk command.

IncreaseHeap(usize, MemoryFlags) will increase a program's heap by the given amount. This returns the new heap as a MemoryRange.

To decrease the heap by a given amount, call DecreaseHeap(usize).

Note that you must adjust the heap in units of PAGE_SIZE.

You can avoid using these syscalls by manually allocating regions using MapMemory, however they are a convenient abstraction with their own memory range.

liballoc as bundled by Xous uses these syscalls as a backing for memory.

Virtual Memory Regions

There are different memory regions in virtual address space:

AddressNameVariableDescription
0x0001_0000text-Start of .text with the default riscv linker script (riscv64-unknown-elf-ld -verbose)
0x2000_0000heapDEFAULT_HEAP_BASEStart of the heap section returned by IncreaseHeap
0x4000_0000messageDEFAULT_MESSAGE_BASEBase address where MemoryMessage messages are mapped inside of a server
0x6000_0000defaultDEFAULT_BASEDefault region when calling `MapMemory(..., None, ..., ...) -- most threads have their stack here
0x7fff_ffffstack-The default stack for the first thread - grows downwards from 0x8000_0000 not inclusive
0xa000_0000swhalSWAP_HAL_VADDRHardware-specific pages for the swapper. For configs that use memory-mapped swap, contains the memory mapping (and thus constrains total swap size). For configs that use register-mapped swap, contains the HAL structures for the register driver. These configurations could potentially have effectively unlimited swap.
0xe000_0000swptSWAP_PT_VADDRSwap page table roots. One page per process, contains virtual addresses (meant to be walked with code)
0xe100_0000swcfgSWAP_CFG_VADDRSwap configuration page. Contains all the arguments necessary to set up the swapper.
0xe100_1000swrptSWAP_RPT_VADDRLocation where the memory allocation tracker (runtime page tracker) is mapped when it is shared into userspace.
0xe110_0000swcountSWAP_COUNT_VADDRLocation of the block swap count table. This is statically allocated by the loader before the kernel starts.
0xff00_0000kernelUSER_AREA_ENDThe end of user area and the start of kernel area
0xff40_0000pgtablePAGE_TABLE_OFFSETA process' page table is located at this offset, accessible only to the kernel
0xff80_0000pgrootPAGE_TABLE_ROOT_OFFSETThe root page table is located at this offset, accessible only to the kernel
0xff80_1000processPROCESSThe process context descriptor page
0xffc0_0000kargsKERNEL_ARGUMENT_OFFSETLocation of kernel arguments
0xffd0_0000ktext-Kernel .text area. Mapped into all processes.
0xfff7_ffffkstack-Kernel stack top, grows down from 0xFFF8_0000 not inclusive
0xfffe_ffffexstack-Stack area for exception handlers, grows down from 0xFFFF_0000 not inclusive

In addition, there are special addresses that indicate the end of a function. The kernel will set these as the return address for various situations, and they are documented here for completeness:

AddressNameVariableDescription
0xff80_2000retisrRETURN_FROM_ISRIndicates the return from an interrupt service routine
0xff80_3000exitthrEXIT_THREADIndicates a thread should exit
0xff80_4000retexRETURN_FROM_EXCEPTION_HANDLERIndicates the return from an exception handler
0xff80_8000retswapRETURN_FROM_SWAPPERIndicates the return from the userspace swapper code. Only available when swap feature is selected.