diff --git a/samples/subsys/demand_paging/CMakeLists.txt b/samples/subsys/demand_paging/CMakeLists.txt new file mode 100644 index 00000000000..aa7f7dbe2f8 --- /dev/null +++ b/samples/subsys/demand_paging/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(demand_paging) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/kernel/include) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/subsys/demand_paging/README.rst b/samples/subsys/demand_paging/README.rst new file mode 100644 index 00000000000..2addcbdd237 --- /dev/null +++ b/samples/subsys/demand_paging/README.rst @@ -0,0 +1,51 @@ +.. zephyr:code-sample:: demand-paging + :name: Demand paging + :relevant-api: mem-demand-paging + + Leverage demand paging to deal with code bigger than available RAM. + +Overview +******** + +This sample demonstrates how demand paging can be leveraged to deal with +firmware bigger than the available RAM if XIP is not possible. Select code +can be tagged to be loaded into memory on demand, and also be automatically +evicted for more code to be executed when memory is exhausted. + +Requirements +************ + +This demo requires the presence of a supported hardware MMU and a backing +store implementation with access to the compiled Zephyr binary. +For demonstration purposes, the ondemand_semihost backing store is used on +a QEMU ARM64 target with a hardcoded small RAM configuration. + +Building and Running +******************** + +This application can be built and executed on QEMU as follows: + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/demand_paging + :host-os: unix + :board: qemu_cortex_a53 + :goals: run + :compact: + +Sample Output +============= + +.. code-block:: console + + *** Booting Zephyr OS build v3.7.0-2108-g5975c3785356 *** + Calling huge body of code that doesn't fit in memory + free memory pages: from 37 to 0, 987 page faults + Calling it a second time + free memory pages: from 0 to 0, 987 page faults + Done. + +Exit QEMU by pressing :kbd:`CTRL+A` :kbd:`x`. + +To actually view the underlying demand paging subsystem at work, debug +prints can be enabled using :kconfig:option:`CONFIG_LOG` and +:kconfig:option:`CONFIG_KERNEL_LOG_LEVEL_DBG` in the config file. diff --git a/samples/subsys/demand_paging/prj.conf b/samples/subsys/demand_paging/prj.conf new file mode 100644 index 00000000000..c1111f3b834 --- /dev/null +++ b/samples/subsys/demand_paging/prj.conf @@ -0,0 +1,8 @@ +CONFIG_SRAM_SIZE=256 +CONFIG_DEMAND_PAGING=y +CONFIG_DEMAND_PAGING_ALLOW_IRQ=y +CONFIG_DEMAND_PAGING_STATS=y +CONFIG_DEMAND_MAPPING=y +CONFIG_LINKER_USE_ONDEMAND_SECTION=y +CONFIG_SEMIHOST=y +CONFIG_BACKING_STORE_ONDEMAND_SEMIHOST=y diff --git a/samples/subsys/demand_paging/sample.yaml b/samples/subsys/demand_paging/sample.yaml new file mode 100644 index 00000000000..9baed624edb --- /dev/null +++ b/samples/subsys/demand_paging/sample.yaml @@ -0,0 +1,25 @@ +sample: + description: On-Demand paging of firmware larger than available memory + name: demand-paging +common: + tags: + - kernel + - mmu + - demand_paging + integration_platforms: + - qemu_cortex_a53 + harness: console + harness_config: + type: multi_line + ordered: true + regex: + - "Calling huge body of code that doesn't fit in memory" + - "free memory pages: from (.*) to 0, (.*) page faults" + - "Calling it a second time" + - "free memory pages: from 0 to 0, (.*) page faults" + - "Done." +tests: + sample.demand_paging.ondemand_section: + platform_allow: + - qemu_cortex_a53 + - qemu_cortex_a53/qemu_cortex_a53/smp diff --git a/samples/subsys/demand_paging/src/main.c b/samples/subsys/demand_paging/src/main.c new file mode 100644 index 00000000000..8e672257a5a --- /dev/null +++ b/samples/subsys/demand_paging/src/main.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 BayLibre SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include /* for k_mem_num_pagefaults_get() */ + +/* + * We want to artificially create a huge body of code hence the volatile + * to prevent the compiler from optimizing everything down. + */ +volatile long foo_val1; +volatile long foo_val2; + +/* + * This function is tagged with __ondemand_func to be placed in a special + * "ondemand" segment by the linker. This means it is not loaded into memory + * at boot time but rather page-by-page on demand when actually executed. + * It is also subject to being evicted when memory reclamation occurs. + */ +static void __ondemand_func huge_evictable_function(void) +{ + foo_val1 = 13131313; + foo_val2 = 45454545; + +#define CODE \ + foo_val1 *= foo_val2; \ + foo_val2 += 1234567; \ + foo_val1 -= 9876543210; \ + __asm__ __volatile__ (".rep 1000; nop; .endr"); + +#define REPEAT_10(x) x x x x x x x x x x +#define REPEAT_1000(x) REPEAT_10(REPEAT_10(REPEAT_10(x))) + + REPEAT_1000(CODE) + +#undef REPEAT_1000 +#undef REPEAT_10 +#undef CODE +} + +int main(void) +{ + size_t free_pages_before, free_pages_after; + unsigned long faults_before, faults_after; + + printk("Calling huge body of code that doesn't fit in memory\n"); + free_pages_before = k_mem_free_get() / CONFIG_MMU_PAGE_SIZE; + faults_before = k_mem_num_pagefaults_get(); + + huge_evictable_function(); + + free_pages_after = k_mem_free_get() / CONFIG_MMU_PAGE_SIZE; + faults_after = k_mem_num_pagefaults_get(); + printk("free memory pages: from %zd to %zd, %ld page faults\n", + free_pages_before, free_pages_after, faults_after - faults_before); + + printk("Calling it a second time\n"); + free_pages_before = k_mem_free_get() / CONFIG_MMU_PAGE_SIZE; + faults_before = k_mem_num_pagefaults_get(); + + huge_evictable_function(); + + free_pages_after = k_mem_free_get() / CONFIG_MMU_PAGE_SIZE; + faults_after = k_mem_num_pagefaults_get(); + printk("free memory pages: from %zd to %zd, %ld page faults\n", + free_pages_before, free_pages_after, faults_after - faults_before); + + printk("Done.\n"); + return 0; +}