kernel/usermode/memory_domain.html · kernel/usermode/memory_domain.rst 공식 원문 ↗

Zephyr 3.7.0 · Kernel · Userspace

Memory Protection Design

User thread에 stack·text·ro-data 이외의 RAM 접근을 architecture 제약에 맞춘 partition 단위로 부여합니다.

Official pathkernel/usermode/memory_domain.html
Source filekernel/usermode/memory_domain.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

Part 1

요약·해설

Manual·automatic partition, library global routing, default domain과 thread inheritance를 boot-time MPU 설계부터 연결합니다.

Boot

background · text · ro-data

Partition

address · size · attribute

Domain

thread · inherit · shared

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 8114F248B63335288E20A736B55E2FF6C9236299D355DA6B306C5609BD90561B
.. _memory_domain:

Memory Protection Design
########################

Zephyr's memory protection design is geared towards microcontrollers with MPU
(Memory Protection Unit) hardware. We do support some architectures, such as x86,
which have a paged MMU (Memory Management Unit), but in that case the MMU is
used like an MPU with an identity page table.

All of the discussion below will be using MPU terminology; systems with MMUs
can be considered to have an MPU with an unlimited number of programmable
regions.

There are a few different levels on how memory access is configured when
Zephyr memory protection features are enabled, which we will describe here:

Boot Time Memory Configuration
******************************

This is the configuration of the MPU after the kernel has started up. It should
contain the following:

- Any configuration of memory regions which need to have special caching or
  write-back policies for basic hardware and driver function. Note that most
  MPUs have the concept of a default memory access policy map, which can be
  enabled as a "background" mapping for any area of memory that doesn't
  have an MPU region configuring it. It is strongly recommended to use this
  to maximize the number of available MPU regions for the end user. On
  ARMv7-M/ARMv8-M this is called the System Address Map, other CPUs may
  have similar capabilities.

- A read-only, executable region or regions for program text and ro-data, that
  is accessible to user mode. This could be further sub-divided into a
  read-only region for ro-data, and a read-only, executable region for text, but
  this will require an additional MPU region. This is required so that
  threads running in user mode can read ro-data and fetch instructions.

- Depending on configuration, user-accessible read-write regions to support
  extra features like GCOV, HEP, etc.

Assuming there is a background map which allows supervisor mode to access any
memory it needs, and regions are defined which grant user mode access to
text/ro-data, this is sufficient for the boot time configuration.

Hardware Stack Overflow
***********************

:kconfig:option:`CONFIG_HW_STACK_PROTECTION` is an optional feature which detects stack
buffer overflows when the system is running in supervisor mode. This
catches issues when the entire stack buffer has overflowed, and not
individual stack frames, use compiler-assisted :kconfig:option:`CONFIG_STACK_CANARIES`
for that.

Like any crash in supervisor mode, no guarantees can be made about the overall
health of the system after a supervisor mode stack overflow, and any instances
of this should be treated as a serious error. However it's still very useful to
know when these overflows happen, as without robust detection logic the system
will either crash in mysterious ways or behave in an undefined manner when the
stack buffer overflows.

Some systems implement this feature by creating at runtime a 'guard' MPU region
which is set to be read-only and is at either the beginning or immediately
preceding the supervisor mode stack buffer.  If the stack overflows an
exception will be generated.

This feature is optional and is not required to catch stack overflows in user
mode; disabling this may free 1-2 MPU regions depending on the MPU design.

Other systems may have dedicated CPU support for catching stack overflows
and no extra MPU regions will be required.

Thread Stack
************

Any thread running in user mode will need access to its own stack buffer.
On context switch into a user mode thread, a dedicated MPU region or MMU
page table entries will be programmed with the bounds of the stack buffer.
A thread exceeding its stack buffer will start pushing data onto memory
it doesn't have access to and a memory access violation exception will be
generated.

Note that user threads have access to the stacks of other user threads in
the same memory domain. This is the minimum required for architectures to
support memory domains. Architecture can further restrict access to stacks
so each user thread only has access to its own stack if such architecture
advertises this capability via
:kconfig:option:`CONFIG_ARCH_MEM_DOMAIN_SUPPORTS_ISOLATED_STACKS`.
This behavior is enabled by default if supported and can be selectively
disabled via :kconfig:option:`CONFIG_MEM_DOMAIN_ISOLATED_STACKS` if
architecture supports both operating modes. However, some architectures
may decide to enable this all the time, and thus this option cannot be
disabled. Regardless of these kconfigs, user threads cannot access
the stacks of other user threads outside of their memory domains.

Thread Resource Pools
*********************

A small subset of kernel APIs, invoked as system calls, require heap memory
allocations. This memory is used only by the kernel and is not accessible
directly by user mode. In order to use these system calls, invoking threads
must assign themselves to a resource pool, which is a :c:struct:`k_heap`
object. Memory is drawn from a thread's resource pool using
:c:func:`z_thread_malloc` and freed with :c:func:`k_free`.

The APIs which use resource pools are as follows, with any alternatives
noted for users who do not want heap allocations within their application:

 - :c:func:`k_stack_alloc_init` sets up a k_stack with its storage
   buffer allocated out of a resource pool instead of a buffer provided by the
   user. An alternative is to declare k_stacks that are automatically
   initialized at boot with :c:macro:`K_STACK_DEFINE()`, or to initialize the
   k_stack in supervisor mode with :c:func:`k_stack_init`.

 - :c:func:`k_pipe_alloc_init` sets up a k_pipe object with its
   storage buffer allocated out of a resource pool instead of a buffer provided
   by the user. An alternative is to declare k_pipes that are automatically
   initialized at boot with :c:macro:`K_PIPE_DEFINE()`, or to initialize the
   k_pipe in supervisor mode with :c:func:`k_pipe_init`.

 - :c:func:`k_msgq_alloc_init` sets up a k_msgq object with its
   storage buffer allocated out of a resource pool instead of a buffer provided
   by the user. An alternative is to declare a k_msgq that is automatically
   initialized at boot with :c:macro:`K_MSGQ_DEFINE()`, or to initialize the
   k_msgq in supervisor mode with :c:func:`k_msgq_init`.

 - :c:func:`k_poll` when invoked from user mode, needs to make a kernel-side
   copy of the provided events array while waiting for an event. This copy is
   freed when :c:func:`k_poll` returns for any reason.

 - :c:func:`k_queue_alloc_prepend` and :c:func:`k_queue_alloc_append`
   allocate a container structure to place the data in, since the internal
   bookkeeping information that defines the queue cannot be placed in the
   memory provided by the user.

 - :c:func:`k_object_alloc` allows for entire kernel objects to be
   dynamically allocated at runtime and a usable pointer to them returned to
   the caller.

The relevant API is :c:func:`k_thread_heap_assign` which assigns
a k_heap to draw these allocations from for the target thread.

If the system heap is enabled, then the system heap may be used with
:c:func:`k_thread_system_pool_assign`, but it is preferable for different
logical applications running on the system to have their own pools.

Memory Domains
**************

The kernel ensures that any user thread will have access to its own stack
buffer, plus program text and read-only data. The memory domain APIs are the
way to grant access to additional blocks of memory to a user thread.

Conceptually, a memory domain is a collection of some number of memory
partitions. The maximum number of memory partitions in a domain
is limited by the number of available MPU regions. This is why it is important
to minimize the number of boot-time MPU regions.

Memory domains are *not* intended to control access to memory from supervisor
mode. In some cases this may be unavoidable; for example some architectures do
not allow for the definition of regions which are read-only to user mode but
read-write to supervisor mode. A great deal of care must be taken when working
with such regions to not unintentionally cause the kernel to crash when
accessing such a region. Any attempt to use memory domain APIs to control
supervisor mode access is at best undefined behavior; supervisor mode access
policy is only intended to be controlled by boot-time memory regions.

Memory domain APIs are only available to supervisor mode. The only control
user mode has over memory domains is that any user thread's child threads
will automatically become members of the parent's domain.

All threads are members of a memory domain, including supervisor threads
(even though this has no implications on their memory access). There is a
default domain ``k_mem_domain_default`` which will be assigned to threads if
they have not been specifically assigned to a domain, or inherited a memory
domain membership from their parent thread. The main thread starts as a
member of the default domain.

Memory Partitions
=================

Each memory partition consists of a memory address, a size,
and access attributes. It is intended that memory partitions are used to
control access to system memory. Defining memory partitions are subject
to the following constraints:

- The partition must represent a memory region that can be programmed by
  the underlying memory management hardware, and needs to conform to any
  underlying hardware constraints. For example, many MPU-based systems require
  that partitions be sized to some power of two, and aligned to their own
  size. For MMU-based systems, the partition must be aligned to a page and
  the size some multiple of the page size.

- Partitions within the same memory domain may not overlap each other. There is
  no notion of precedence among partitions within a memory domain.  Partitions
  within a memory domain are assumed to have a higher precedence than any
  boot-time memory regions, however whether a memory domain partition can
  overlap a boot-time memory region is architecture specific.

- The same partition may be specified in multiple memory domains. For example
  there may be a shared memory area that multiple domains grant access to.

- Care must be taken in determining what memory to expose in a partition.
  It is not appropriate to provide direct user mode access to any memory
  containing private kernel data.

- Memory domain partitions are intended to control access to system RAM.
  Configuration of memory partitions which do not correspond to RAM
  may not be supported by the architecture; this is true for MMU-based systems.

There are two ways to define memory partitions: either manually or
automatically.

Manual Memory Partitions
------------------------

The following code declares a global array ``buf``, and then declares
a read-write partition for it which may be added to a domain:

.. code-block:: c

    uint8_t __aligned(32) buf[32];

    K_MEM_PARTITION_DEFINE(my_partition, buf, sizeof(buf),
                           K_MEM_PARTITION_P_RW_U_RW);

This does not scale particularly well when we are trying to contain multiple
objects spread out across several C files into a single partition.

Automatic Memory Partitions
---------------------------

Automatic memory partitions are created by the build system. All globals
which need to be placed inside a partition are tagged with their destination
partition. The build system will then coalesce all of these into a single
contiguous block of memory, zero any BSS variables at boot, and define
a memory partition of appropriate base address and size which contains all
the tagged data.

.. figure:: ../../../../kernel/usermode/auto_mem_domain.png
   :alt: Automatic Memory Domain build flow
   :align: center

   Automatic Memory Domain build flow

Automatic memory partitions are only configured as read-write
regions. They are defined with :c:macro:`K_APPMEM_PARTITION_DEFINE()`.
Global variables are then routed to this partition using
:c:macro:`K_APP_DMEM()` for initialized data and :c:macro:`K_APP_BMEM()` for
BSS.

.. code-block:: c

    #include <zephyr/app_memory/app_memdomain.h>

    /* Declare a k_mem_partition "my_partition" that is read-write to
     * user mode. Note that we do not specify a base address or size.
     */
    K_APPMEM_PARTITION_DEFINE(my_partition);

    /* The global variable var1 will be inside the bounds of my_partition
     * and be initialized with 37 at boot.
     */
    K_APP_DMEM(my_partition) int var1 = 37;

    /* The global variable var2 will be inside the bounds of my_partition
     * and be zeroed at boot size K_APP_BMEM() was used, indicating a BSS
     * variable.
     */
    K_APP_BMEM(my_partition) int var2;

The build system will ensure that the base address of ``my_partition`` will
be properly aligned, and the total size of the region conforms to the memory
management hardware requirements, adding padding if necessary.

If multiple partitions are being created, a variadic preprocessor macro can be
used as provided in ``app_macro_support.h``:

.. code-block:: c

    FOR_EACH(K_APPMEM_PARTITION_DEFINE, part0, part1, part2);

Automatic Partitions for Static Library Globals
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The build-time logic for setting up automatic memory partitions is in
``scripts/build/gen_app_partitions.py``. If a static library is linked into Zephyr,
it is possible to route all the globals in that library to a specific
memory partition with the ``--library`` argument.

For example, if the Newlib C library is enabled, the Newlib globals all need
to be placed in ``z_libc_partition``. The invocation of the script in the
top-level ``CMakeLists.txt`` adds the following:

.. code-block:: none

    gen_app_partitions.py ... --library libc.a z_libc_partition ..

For pre-compiled libraries there is no support for expressing this in the
project-level configuration or build files; the toplevel ``CMakeLists.txt`` must
be edited.

For Zephyr libraries created using ``zephyr_library`` or ``zephyr_library_named``
the ``zephyr_library_app_memory`` function can be used to specify the memory
partition where all globals in the library should be placed.

.. _memory_domain_predefined_partitions:

Pre-defined Memory Partitions
-----------------------------

There are a few memory partitions which are pre-defined by the system:

 - ``z_malloc_partition`` - This partition contains the system-wide pool of
   memory used by libc malloc(). Due to possible starvation issues, it is
   not recommended to draw heap memory from a global pool, instead
   it is better to define various sys_heap objects and assign them
   to specific memory domains.

 - ``z_libc_partition`` - Contains globals required by the C library and runtime.
   Required when using either the Minimal C library or the Newlib C Library.
   Required when :kconfig:option:`CONFIG_STACK_CANARIES` is enabled.

Library-specific partitions are listed in ``include/app_memory/partitions.h``.
For example, to use the MBEDTLS library from user mode, the
``k_mbedtls_partition`` must be added to the domain.

Memory Domain Usage
===================

Create a Memory Domain
----------------------

A memory domain is defined using a variable of type
:c:struct:`k_mem_domain`. It must then be initialized by calling
:c:func:`k_mem_domain_init`.

The following code defines and initializes an empty memory domain.

.. code-block:: c

    struct k_mem_domain app0_domain;

    k_mem_domain_init(&app0_domain, 0, NULL);

Add Memory Partitions into a Memory Domain
------------------------------------------

There are two ways to add memory partitions into a memory domain.

This first code sample shows how to add memory partitions while creating
a memory domain.

.. code-block:: c

    /* the start address of the MPU region needs to align with its size */
    uint8_t __aligned(32) app0_buf[32];
    uint8_t __aligned(32) app1_buf[32];

    K_MEM_PARTITION_DEFINE(app0_part0, app0_buf, sizeof(app0_buf),
                           K_MEM_PARTITION_P_RW_U_RW);

    K_MEM_PARTITION_DEFINE(app0_part1, app1_buf, sizeof(app1_buf),
                           K_MEM_PARTITION_P_RW_U_RO);

    struct k_mem_partition *app0_parts[] = {
        app0_part0,
        app0_part1
    };

    k_mem_domain_init(&app0_domain, ARRAY_SIZE(app0_parts), app0_parts);

This second code sample shows how to add memory partitions into an initialized
memory domain one by one.

.. code-block:: c

    /* the start address of the MPU region needs to align with its size */
    uint8_t __aligned(32) app0_buf[32];
    uint8_t __aligned(32) app1_buf[32];

    K_MEM_PARTITION_DEFINE(app0_part0, app0_buf, sizeof(app0_buf),
                           K_MEM_PARTITION_P_RW_U_RW);

    K_MEM_PARTITION_DEFINE(app0_part1, app1_buf, sizeof(app1_buf),
                           K_MEM_PARTITION_P_RW_U_RO);

    k_mem_domain_add_partition(&app0_domain, &app0_part0);
    k_mem_domain_add_partition(&app0_domain, &app0_part1);

.. note::
    The maximum number of memory partitions is limited by the maximum
    number of MPU regions or the maximum number of MMU tables.

Memory Domain Assignment
------------------------

Any thread may join a memory domain, and any memory domain may have multiple
threads assigned to it. Threads are assigned to memory domains with an API
call:

.. code-block:: c

    k_mem_domain_add_thread(&app0_domain, app_thread_id);

If the thread was already a member of some other domain (including the
default domain), it will be removed from it in favor of the new one.

In addition, if a thread is a member of a memory domain, and it creates a
child thread, that thread will belong to the domain as well.

Remove a Memory Partition from a Memory Domain
----------------------------------------------

The following code shows how to remove a memory partition from a memory
domain.

.. code-block:: c

    k_mem_domain_remove_partition(&app0_domain, &app0_part1);

The k_mem_domain_remove_partition() API finds the memory partition
that matches the given parameter and removes that partition from the
memory domain.

Available Partition Attributes
------------------------------

When defining a partition, we need to set access permission attributes
to the partition. Since the access control of memory partitions relies on
either an MPU or MMU, the available partition attributes would be architecture
dependent.

The complete list of available partition attributes for a specific architecture
is found in the architecture-specific include file
``include/zephyr/arch/<arch name>/arch.h``, (for example, ``include/zehpyr/arch/arm/arch.h``.)
Some examples of partition attributes are:

.. code-block:: c

    /* Denote partition is privileged read/write, unprivileged read/write */
    K_MEM_PARTITION_P_RW_U_RW
    /* Denote partition is privileged read/write, unprivileged read-only */
    K_MEM_PARTITION_P_RW_U_RO

In almost all cases ``K_MEM_PARTITION_P_RW_U_RW`` is the right choice.

Configuration Options
*********************

Related configuration options:

* :kconfig:option:`CONFIG_MAX_DOMAIN_PARTITIONS`

API Reference
*************

The following memory domain APIs are provided by :zephyr_file:`include/zephyr/kernel.h`:

.. doxygengroup:: mem_domain_apis

Part 3

한국어 전문 번역

Zephyr memory protection은 MPU(Memory Protection Unit) hardware가 있는 microcontroller를 중심으로 설계됐습니다. paged MMU를 가진 x86 같은 architecture도 지원하지만 identity page table로 MPU처럼 사용합니다. 아래 설명은 MPU 용어를 사용하며 MMU system은 programmable region 수가 무제한인 MPU로 생각할 수 있습니다.

Boot-time memory configuration

Kernel startup 뒤 MPU configuration에는 hardware·driver가 요구하는 cache/write-back policy region이 있어야 합니다. 대부분 MPU에는 별도 region이 없는 memory에 적용하는 default background map이 있으므로 end user에게 region을 많이 남기기 위해 적극 사용합니다. ARMv7-M/ARMv8-M에서는 System Address Map이라 부릅니다.

User mode가 instruction을 fetch하고 ro-data를 읽도록 program text와 ro-data에 read-only executable region을 제공합니다. Ro-data와 executable text를 분리하면 region 하나가 더 필요합니다. Configuration에 따라 GCOV·HEP 같은 기능용 user-accessible read-write region도 추가합니다.

Supervisor가 background map으로 필요한 memory에 접근하고 user text/ro-data region이 있으면 boot-time configuration으로 충분합니다.

Hardware stack overflow

CONFIG_HW_STACK_PROTECTION은 supervisor mode에서 stack buffer 전체 overflow를 검출하는 optional 기능입니다. 개별 stack frame corruption은 compiler-assisted CONFIG_STACK_CANARIES를 사용합니다.

일부 system은 supervisor stack 시작 또는 바로 앞에 read-only guard MPU region을 runtime 생성해 overflow access가 exception을 내게 합니다. User-mode overflow 검출에는 필수가 아니며 끄면 MPU design에 따라 region 1~2개를 확보할 수 있습니다. Dedicated CPU overflow detection이 있으면 별도 region이 필요 없습니다.

Thread stack

User thread context switch 때 자기 stack bounds로 MPU region 또는 MMU page-table entry를 program합니다. Stack을 넘으면 권한 없는 memory에 write해 memory access violation exception이 발생합니다.

최소 memory-domain 지원에서는 같은 domain의 user thread가 서로의 stack에 접근할 수 있습니다. CONFIG_ARCH_MEM_DOMAIN_SUPPORTS_ISOLATED_STACKS capability가 있으면 각 thread를 자기 stack에만 제한할 수 있고 기본 활성화됩니다. 두 mode를 지원하는 architecture는 CONFIG_MEM_DOMAIN_ISOLATED_STACKS로 선택 해제할 수 있지만 항상 isolation을 강제하는 architecture도 있습니다. 다른 domain의 stack에는 어떤 경우에도 접근할 수 없습니다.

Thread resource pools

일부 system call API는 kernel-only heap allocation이 필요합니다. User mode가 직접 접근할 수 없고 caller thread는 k_heap resource pool을 지정해야 합니다. z_thread_malloc()으로 pool에서 받고 k_free()로 해제합니다.

  • k_stack_alloc_init(): storage를 pool에서 allocation합니다. 대안은 K_STACK_DEFINE() 또는 supervisor의 k_stack_init().
  • k_pipe_alloc_init(): pipe buffer를 pool에서 allocation합니다. 대안은 K_PIPE_DEFINE() 또는 k_pipe_init().
  • k_msgq_alloc_init(): message queue buffer를 pool에서 allocation합니다. 대안은 K_MSGQ_DEFINE() 또는 k_msgq_init().
  • User-mode k_poll(): wait 동안 event array의 kernel-side copy를 만들고 return할 때 해제합니다.
  • k_queue_alloc_prepend()·k_queue_alloc_append(): user data에 internal bookkeeping을 넣을 수 없어 container를 allocation합니다.
  • k_object_alloc(): kernel object 전체를 runtime allocation합니다.

k_thread_heap_assign()으로 target thread의 k_heap을 지정합니다. System heap이 있으면 k_thread_system_pool_assign()을 쓸 수 있지만 logical application마다 별도 pool을 두는 편이 global starvation을 줄입니다.

Memory domains

Kernel은 user thread에 자기 stack, program text, read-only data 접근을 보장합니다. 추가 memory block은 memory domain API로 부여합니다. Domain은 memory partition collection이며 maximum partition 수는 available MPU region 수가 제한하므로 boot-time region 수를 줄이는 것이 중요합니다.

Domain API는 supervisor mode에서만 쓸 수 있습니다. User mode가 통제할 수 있는 유일한 부분은 child thread가 parent domain membership을 자동 상속하는 것입니다.

Supervisor를 포함해 모든 thread는 domain 하나에 속합니다. Explicit assignment나 parent inheritance가 없으면 k_mem_domain_default를 사용하며 main thread도 default domain에서 시작합니다.

Memory partitions

Partition은 address, size, access attribute로 구성됩니다. Hardware가 program할 수 있는 region이어야 하고 MPU는 흔히 power-of-two size와 self-size alignment, MMU는 page alignment와 page-size multiple을 요구합니다.

  • 같은 domain 안 partition은 overlap할 수 없고 precedence 개념이 없습니다. Domain partition은 boot-time region보다 우선한다고 가정하지만 overlap 허용 여부는 architecture-specific입니다.
  • 같은 partition을 여러 domain에 넣어 shared memory로 사용할 수 있습니다.
  • Private kernel data가 든 memory를 user에게 직접 노출하면 안 됩니다.
  • 주 목적은 system RAM이며 MMU architecture 등에서는 non-RAM region이 지원되지 않을 수 있습니다.

Manual memory partitions

Global buffer를 hardware alignment에 맞추고 read-write partition으로 선언합니다.

uint8_t __aligned(32) buf[32];

K_MEM_PARTITION_DEFINE(my_partition, buf, sizeof(buf),
                       K_MEM_PARTITION_P_RW_U_RW);

여러 C file에 흩어진 object를 한 partition에 모으려면 manual 방식은 잘 확장되지 않습니다.

Automatic memory partitions

Build system은 destination partition tag가 붙은 global을 contiguous memory block으로 합치고 BSS를 boot에서 zeroing하며 hardware constraint에 맞는 base·size partition을 자동 정의합니다.

Automatic Memory Domain build flow
Source globals

K_APP_DMEM · K_APP_BMEM tag

Link sections

partition별 initialized data · BSS 수집

Build script

contiguous block · alignment · padding

k_mem_partition

base · size · read-write attribute

Memory domain

partition을 추가해 user thread access 부여

Automatic partition은 read-write만 지원합니다. K_APPMEM_PARTITION_DEFINE()으로 partition을 만들고 initialized data는 K_APP_DMEM(), BSS는 K_APP_BMEM()으로 route합니다.

#include <zephyr/app_memory/app_memdomain.h>

/* Declare a k_mem_partition "my_partition" that is read-write to
 * user mode. Note that we do not specify a base address or size.
 */
K_APPMEM_PARTITION_DEFINE(my_partition);

/* The global variable var1 will be inside the bounds of my_partition
 * and be initialized with 37 at boot.
 */
K_APP_DMEM(my_partition) int var1 = 37;

/* The global variable var2 will be inside the bounds of my_partition
 * and be zeroed at boot size K_APP_BMEM() was used, indicating a BSS
 * variable.
 */
K_APP_BMEM(my_partition) int var2;

Build system이 base alignment와 total size를 맞추고 필요하면 padding을 추가합니다. 여러 partition은 variadic FOR_EACH macro로 정의할 수 있습니다.

FOR_EACH(K_APPMEM_PARTITION_DEFINE, part0, part1, part2);

Static library global의 automatic partition

scripts/build/gen_app_partitions.py의 --library argument로 static library의 모든 global을 특정 partition에 route할 수 있습니다. Newlib libc.a global을 z_libc_partition에 넣는 invocation은 다음과 같습니다.

gen_app_partitions.py ... --library libc.a z_libc_partition ..

Pre-compiled library는 project configuration에서 표현할 수 없어 top-level CMakeLists.txt를 수정해야 합니다. zephyr_library 또는 zephyr_library_named로 만든 Zephyr library는 zephyr_library_app_memory()로 target partition을 지정합니다.

Pre-defined memory partitions

z_malloc_partition은 libc malloc()의 system-wide pool입니다. Starvation 위험 때문에 global pool보다 여러 sys_heap을 logical domain별로 배정하는 것을 권장합니다. z_libc_partition은 Minimal C 또는 Newlib runtime global을 담고 CONFIG_STACK_CANARIES에서도 필요합니다.

Library-specific partition은 include/app_memory/partitions.h에 있습니다. User mode에서 MBEDTLS를 쓰려면 k_mbedtls_partition을 domain에 추가합니다.

Memory domain 사용법

Domain 생성

k_mem_domain variable을 선언하고 빈 partition list로 초기화합니다.

struct k_mem_domain app0_domain;

k_mem_domain_init(&app0_domain, 0, NULL);

Partition 추가

Domain initialization과 동시에 pointer array를 넘겨 partition을 추가할 수 있습니다.

/* the start address of the MPU region needs to align with its size */
uint8_t __aligned(32) app0_buf[32];
uint8_t __aligned(32) app1_buf[32];

K_MEM_PARTITION_DEFINE(app0_part0, app0_buf, sizeof(app0_buf),
                       K_MEM_PARTITION_P_RW_U_RW);

K_MEM_PARTITION_DEFINE(app0_part1, app1_buf, sizeof(app1_buf),
                       K_MEM_PARTITION_P_RW_U_RO);

struct k_mem_partition *app0_parts[] = {
    app0_part0,
    app0_part1
};

k_mem_domain_init(&app0_domain, ARRAY_SIZE(app0_parts), app0_parts);

이미 initialized domain에는 하나씩 추가할 수도 있습니다.

/* the start address of the MPU region needs to align with its size */
uint8_t __aligned(32) app0_buf[32];
uint8_t __aligned(32) app1_buf[32];

K_MEM_PARTITION_DEFINE(app0_part0, app0_buf, sizeof(app0_buf),
                       K_MEM_PARTITION_P_RW_U_RW);

K_MEM_PARTITION_DEFINE(app0_part1, app1_buf, sizeof(app1_buf),
                       K_MEM_PARTITION_P_RW_U_RO);

k_mem_domain_add_partition(&app0_domain, &app0_part0);
k_mem_domain_add_partition(&app0_domain, &app0_part1);

Thread assignment

Thread를 domain에 배정하면 default를 포함한 이전 domain에서 제거됩니다. Member thread가 child를 만들면 child도 같은 domain에 속합니다.

k_mem_domain_add_thread(&app0_domain, app_thread_id);

Partition 제거

Matching partition을 찾아 domain에서 제거합니다.

k_mem_domain_remove_partition(&app0_domain, &app0_part1);

Partition attribute

Attribute는 MPU/MMU access-control capability에 따라 architecture-specific입니다. 전체 목록은 include/zephyr/arch//arch.h에 있습니다.

/* Denote partition is privileged read/write, unprivileged read/write */
K_MEM_PARTITION_P_RW_U_RW
/* Denote partition is privileged read/write, unprivileged read-only */
K_MEM_PARTITION_P_RW_U_RO

거의 모든 경우 K_MEM_PARTITION_P_RW_U_RW가 적절합니다.

설정과 API 참고: mem_domain_apis

관련 option은 CONFIG_MAX_DOMAIN_PARTITIONS입니다. API는 include/zephyr/kernel.h에서 제공합니다.

K_MEM_PARTITION_DEFINE(name, start, size, attr)

Memory partition을 static하게 선언합니다.

매개변수

  • name: partition symbol
  • start: region start address
  • size: region byte size
  • attr: architecture-specific access attribute
k_mem_domain_init(domain, num_parts, parts)

Memory domain을 주어진 partition으로 한 번만 초기화합니다. 같은 domain에 두 번 호출하면 undefined behavior입니다.

매개변수

  • domain: 초기화할 domain
  • num_parts: parts array item 수
  • parts: partition pointer array; num_parts=0이면 NULL 가능

반환값

  • 0: 성공
  • -EINVAL: invalid parameter
  • -ENOMEM: memory 부족
k_mem_domain_add_partition(domain, part)

Initialized domain에 partition을 추가합니다. 같은 domain partition은 overlap할 수 없고 private kernel data/object를 노출하면 안 되며 address alignment와 size가 hardware constraint를 만족해야 합니다. CONFIG_EXECUTE_XOR_WRITE에서는 write와 execute를 동시에 허용하면 안 됩니다. 위반하면 CPU exception 또는 undefined behavior가 생길 수 있습니다.

매개변수

  • domain: target domain
  • part: 추가할 partition

반환값

  • 0: 성공
  • -EINVAL: invalid parameter
  • -ENOSPC: free partition slot 없음
k_mem_domain_remove_partition(domain, part)

Matching partition을 domain에서 제거합니다.

매개변수

  • domain: target domain
  • part: 제거할 partition

반환값

  • 0: 성공
  • -EINVAL: invalid parameter
  • -ENOENT: matching partition 없음
k_mem_domain_add_thread(domain, thread)

Thread를 domain에 넣고 이전 default 또는 다른 domain에서는 제거합니다.

매개변수

  • domain: 새 domain
  • thread: target thread ID

반환값

  • 0: 성공
  • nonzero: 실패
k_mem_domain_default

모든 thread가 속하는 default domain입니다. 다른 domain에 add/inherit되지 않은 supervisor·user thread가 여기 속하고 main thread도 여기서 시작합니다. 존재하면 C library용 z_libc_partition이 포함됩니다.

k_mem_partition

Linear address-space region과 access policy를 표현합니다. Start·size alignment requirement는 hardware마다 달라 arbitrary value는 거의 동작하지 않습니다.

start

Memory partition의 start address입니다.

size

Memory partition의 byte size입니다.

attr

Memory partition의 architecture-specific access attribute입니다.

k_mem_domain

User thread의 linear address-space access policy를 나타내는 partition collection입니다. Thread는 domain 하나에만 속하고 domain에는 여러 thread가 속할 수 있습니다. Supervisor membership은 direct access에 영향이 없지만 child가 membership을 상속합니다. Active partition이 없어도 user thread는 자기 stack, program text와 read-only data에 접근합니다.

partitions

CONFIG_MAX_DOMAIN_PARTITIONS 크기의 domain partition storage입니다.

mem_domain_q

Domain member thread의 doubly linked list입니다.

num_partitions

현재 active partition 수입니다.

Source

출처

영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.