documentation: implement on-demand paging for RISC-V devices

It documents how the on-demand paging was implemented for RISC-V
devices and how it can be extended to other architectures.
This commit is contained in:
Tiago Medicci Serrano 2024-03-01 14:45:07 -03:00 committed by Xiang Xiao
parent c67502d9b4
commit e287ed9090
2 changed files with 95 additions and 19 deletions

View file

@ -4,17 +4,78 @@
On-Demand Paging
================
Introduction
============
Kernel Build Implementation
===========================
Overview
--------
On-demand paging and lazy loading are techniques used to manage physical
memory. The basic idea is to allow a program to execute even though the
entire program is not resident in memory. The program is loaded into
memory on demand. This is a technique that is used in many operating
systems to allow large programs to execute on small memory systems.
Commonly, a Memory Management Unit (MMU) is used to map virtual memory
into physical memory. Applications are then loaded into virtual memory
address spaces and access to physical memory is managed by the MMU. If
the virtual memory is not resident in physical memory, then a page fault
occurs. The operating system then loads the missing page into memory and
resumes execution.
This document summarizes the design of NuttX on-demand paging. This
feature permits embedded MCUs with some limited RAM space to execute
large programs from some non-random access media.
Requirements and Assumptions
----------------------------
What kind of platforms can support NuttX on-demang paging?
On-demand paging requires *Kernel Build* (``CONFIG_BUILD_KERNEL=y``) mode.
In this mode, no applications are built within the NuttX kernel. Instead,
the applications are built as separate programs that are loaded into memory
(``CONFIG_ELF=y`` and ``CONFIG_BINFMT_LOADABLE=y``). In this mode, each
process has its own address environment (``CONFIG_ARCH_ADDRENV=y``).
Logic Design Description
------------------------
When an application is being loaded ``up_addrenv_create`` is called to create
the process's address environment. This includes mapping the commonly used
``text``, ``data`` and ``heap`` sections withing the virtual memory space.
Without on-demand paging, the physical memory is then allocated and mapped
accordingly, before the process is started. When on-demand paging is enabled,
usually only one single page for each section is allocated and mapped.
The process starts executing within its address environment, accessing the
virtual memory. Whenever it tries to access a virtual memory address that is
not mapped in the MMU, a page fault occurs. The MMU then triggers an
exception that is handled by the kernel. The kernel then checks if there are
enough free physical pages available and maps the virtual memory address to
it. Finally, execution is resumed from the same point where the page fault
first occurred.
Example: RISC-V
^^^^^^^^^^^^^^^
RISC-V's ``up_addrenv_create`` calls ``create_region`` (both defined in
``arch/risc-v/src/common/riscv_addrenv.c``). ``create_region`` maps a single
region to MMU by allocating physical memory for the page tables. When
``CONFIG_PAGING=y`` is not selected, all the physical page tables are
allocated from the physical memory space and then mapped to the virtual
memory space. When ``CONFIG_PAGING=y`` is selected, only the first page of
each section is mapped to the virtual memory space. The rest of the pages are
mapped to the virtual memory space only when a page fault occurs.
The page fault is handled by the ``riscv_fillpage`` function in the exception
handler (defined in ``arch/risc-v/src/common/riscv_exception.c``). Whenever
a page fault occurs, the ``riscv_fillpage`` function is called. This function
allocates a physical page and maps it to the virtual memory space that
triggered the page fault exception and then resumes execution from the same
point where the page fault first occurred.
:ref:`knsh32_paging` simulates a device with 4MiB physical memory with 8MiB
of virtual heap memory allocated for each process. This is possible by
enabling on-demand paging.
Legacy Implementation
=====================
This legacy implementation runs on *Flat Build* (*Kernel Build* did not
even exist at that time).
What kind of platforms can support NuttX legacy on-demand paging?
#. The MCU should have some large, probably low-cost non-volatile
storage such as serial FLASH or an SD card. This storage probably
@ -29,7 +90,7 @@ What kind of platforms can support NuttX on-demang paging?
LPC3131) would be sufficient for many applications.
#. The MCU has an MMU (again like the NXP LPC3131).
If the platform meets these requirement, then NuttX can provide
If the platform meets these requirements, then NuttX can provide
on-demand paging: It can copy .text from the large program in
non-volatile media into RAM as needed to execute a huge program from the
small RAM.
@ -55,10 +116,10 @@ Terminology
Task Control Block
NuttX Common Logic Design Description
=====================================
-------------------------------------
Initialization
--------------
^^^^^^^^^^^^^^
The following declarations will be added.
@ -89,7 +150,7 @@ logic, those architecture specific functions are instead declared in
``include/nuttx/page.h``.
Page Faults
-----------
^^^^^^^^^^^
**Page fault exception handling**. Page fault handling is performed by
the function ``pg_miss()``. This function is called from
@ -146,7 +207,7 @@ inputs are required.
page fill.
Fill Initiation
---------------
^^^^^^^^^^^^^^^
The page fill worker thread will be awakened on one of three conditions:
@ -217,7 +278,7 @@ The architecture-specific functions, ``up_checkmapping()``,
will be prototyped in ``include/nuttx/arch.h``
Fill Complete
-------------
^^^^^^^^^^^^^
For the blocking ``up_fillpage()``, the result of the fill will be
returned directly from the call to ``up_fillpage``.
@ -246,7 +307,7 @@ completed:
- Signal the page fill worker thread.
Task Resumption
---------------
^^^^^^^^^^^^^^^
For the non-blocking ``up_fillpage()``, the page fill worker thread will
detect that the page fill is complete when it is awakened with
@ -279,10 +340,10 @@ In this either, the page fill worker thread will:
- Wait for the next fill related event (a new page fault).
Architecture-Specific Support Requirements
==========================================
------------------------------------------
Memory Organization
-------------------
^^^^^^^^^^^^^^^^^^^
**Memory Regions**. Chip specific logic will map the virtual and
physical address spaces into three general regions:
@ -367,7 +428,7 @@ would be a two phase link:
"non-swappable" region.
Architecture-Specific Functions
-------------------------------
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Most standard, architecture-specific functions are declared in
``include/nuttx/arch.h``. However, for the case of this paging logic,
@ -406,4 +467,3 @@ implemented just for on-demand paging support are:
will be called when the page fill is finished (or an error occurs). This
callback is assumed to occur from an interrupt level when the device
driver completes the fill operation.

View file

@ -154,6 +154,22 @@ In `nsh`, applications can be run from the `/system/bin` directory::
nsh> /system/bin/hello
.. _knsh32_paging:
knsh32_paging
-------------
Similar to ``knsh32_romfs``, but enabling on-demand paging: this
configuration simulates a 4MiB device (using QEMU), but sets the number of
heap pages equal to ``CONFIG_ARCH_HEAP_NPAGES=2048``. This means that each
process's heap is 8MiB, whereas ``CONFIG_POSIX_SPAWN_DEFAULT_STACKSIZE`` is
``1048576`` (1MiB) represents the stack size of the processes (which is
allocated from the process's heap). This configuration is used for 32-bit
RISC-V which implements the Sv32 MMU specification and enables processes
to have their own address space larger than the available physical memory.
This is particularly useful for implementing a set of programming language
interpreters.
knsh32_romfs
------------