kernel/services/other/float.html · kernel/services/other/float.rst 공식 원문 ↗

Zephyr 3.7.0 · Kernel · Other Services

Floating Point Service

Thread별 FP·SSE context를 architecture 특성에 맞게 감지·저장·복구하고 필요한 stack과 ISR 제약을 관리합니다.

Official pathkernel/services/other/float.html
Source filekernel/services/other/float.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

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

Part 1

요약·해설

No-FP·unshared·shared mode와 ARM Cortex-M/64, ARCv2, RISC-V, SPARC, x86의 서로 다른 tagging·lazy-save 규칙을 다룹니다.

Modes

none · unshared · shared

Context

tag · lazy save

Cost

stack · thread object

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 DFA31CB07A32BD8F9E7F49123A0E2424E333D112A4EE211190DBA71E31CE78C8
.. _float_v2:

Floating Point Services
#######################

The kernel allows threads to use floating point registers on board
configurations that support these registers.

.. note::
    Floating point services are currently available only for boards
    based on ARM Cortex-M SoCs supporting the Floating Point Extension,
    the Intel x86 architecture, the SPARC architecture and ARCv2 SoCs
    supporting the Floating Point Extension. The services provided
    are architecture specific.

    The kernel does not support the use of floating point registers by ISRs.

.. contents::
    :local:
    :depth: 2

Concepts
********

The kernel can be configured to provide only the floating point services
required by an application. Three modes of operation are supported,
which are described below. In addition, the kernel's support for the SSE
registers can be included or omitted, as desired.

No FP registers mode
====================

This mode is used when the application has no threads that use floating point
registers. It is the kernel's default floating point services mode.

If a thread uses any floating point register,
the kernel generates a fatal error condition and aborts the thread.

Unshared FP registers mode
==========================

This mode is used when the application has only a single thread
that uses floating point registers.

On x86 platforms, the kernel initializes the floating point registers so they can
be used by any thread (initialization in skipped on ARM Cortex-M platforms and
ARCv2 platforms). The floating point registers are left unchanged whenever a
context switch occurs.

.. note::
    The behavior is undefined, if two or more threads attempt to use
    the floating point registers, as the kernel does not attempt to detect
    (or prevent) multiple threads from using these registers.

Shared FP registers mode
========================

This mode is used when the application has two or more threads that use
floating point registers. Depending upon the underlying CPU architecture,
the kernel supports one or more of the following thread sub-classes:

* non-user: A thread that cannot use any floating point registers

* FPU user: A thread that can use the standard floating point registers

* SSE user: A thread that can use both the standard floating point registers
  and SSE registers

The kernel initializes and enables access to the floating point registers,
so they can be used
by any thread, then saves and restores these registers during
context switches to ensure the computations performed by each FPU user
or SSE user are not impacted by the computations performed by the other users.

ARM Cortex-M architecture (with the Floating Point Extension)
-------------------------------------------------------------

.. note::
    The Shared FP registers mode is the default Floating Point
    Services mode in ARM Cortex-M.

On the ARM Cortex-M architecture with the Floating Point Extension, the kernel
treats *all* threads as FPU users when shared FP registers mode is enabled.
This means that any thread is allowed to access the floating point registers.
The ARM kernel automatically detects that a given thread is using the floating
point registers the first time the thread accesses them.

Pretag a thread that intends to use the FP registers by
using one of the techniques listed below.

* A statically-created ARM thread can be pretagged by passing the
  :c:macro:`K_FP_REGS` option to :c:macro:`K_THREAD_DEFINE`.

* A dynamically-created ARM thread can be pretagged by passing the
  :c:macro:`K_FP_REGS` option to :c:func:`k_thread_create`.

Pretagging a thread with the :c:macro:`K_FP_REGS` option instructs the
MPU-based stack protection mechanism to properly configure the size of
the thread's guard region to always guarantee stack overflow detection,
and enable lazy stacking for the given thread upon thread creation.

During thread context switching the ARM kernel saves the *callee-saved*
floating point registers, if the switched-out thread has been using them.
Additionally, the *caller-saved* floating point registers are saved on
the thread's stack. If the switched-in thread has been using the floating
point registers, the kernel restores the *callee-saved* FP registers of
the switched-in thread and the *caller-saved* FP context is restored from
the thread's stack. Thus, the kernel does not save or restore the FP
context of threads that are not using the FP registers.

Each thread that intends to use the floating point registers must provide
an extra 72 bytes of stack space where the callee-saved FP context can
be saved.

`Lazy Stacking
<https://developer.arm.com/documentation/dai0298/a>`_
is currently enabled in Zephyr applications on ARM Cortex-M
architecture, minimizing interrupt latency, when the floating
point context is active.

When the MPU-based stack protection mechanism is not enabled, lazy stacking
is always active in the Zephyr application. When the MPU-based stack protection
is enabled, the following rules apply with respect to lazy stacking:

* Lazy stacking is activated by default on threads that are pretagged with
  :c:macro:`K_FP_REGS`
* Lazy stacking is activated dynamically on threads that are not pretagged with
  :c:macro:`K_FP_REGS`, as soon as the kernel detects that they are using the
  floating point registers.


If an ARM thread does not require use of the floating point registers any
more, it can call :c:func:`k_float_disable`. This instructs the kernel
not to save or restore its FP context during thread context switching.

ARM64 architecture
------------------

.. note::
    The Shared FP registers mode is the default Floating Point
    Services mode on ARM64. The compiler is free to optimize code
    using FP/SIMD registers, and library functions such as memcpy
    are known to make use of them.

On the ARM64 (Aarch64) architecture the kernel treats each thread as a FPU
user on a case-by-case basis. A "lazy save" algorithm is used during context
switching which updates the floating point registers only when it is absolutely
necessary. For example, the registers are *not* saved when switching from an
FPU user to a non-user thread, and then back to the original FPU user.

FPU register usage by ISRs is supported although not recommended. When an
ISR uses floating point or SIMD registers, then the access is trapped, the
current FPU user context is saved in the thread object and the ISR is resumed
with interrupts disabled so to prevent another IRQ from interrupting the ISR
and potentially requesting FPU usage. Because ISR don't have a persistent
register context, there are no provision for saving an ISR's FPU context
either, hence the IRQ disabling.

Each thread object becomes 512 bytes larger when Shared FP registers mode
is enabled.

ARCv2 architecture
------------------

On the ARCv2 architecture, the kernel treats each thread as a non-user
or FPU user and the thread must be tagged by one of the
following techniques.

* A statically-created ARC thread can be tagged by passing the
  :c:macro:`K_FP_REGS` option to :c:macro:`K_THREAD_DEFINE`.

* A dynamically-created ARC thread can be tagged by passing the
  :c:macro:`K_FP_REGS` to :c:func:`k_thread_create`.

If an ARC thread does not require use of the floating point registers any
more, it can call :c:func:`k_float_disable`. This instructs the kernel
not to save or restore its FP context during thread context switching.

During thread context switching the ARC kernel saves the *callee-saved*
floating point registers, if the switched-out thread has been using them.
Additionally, the *caller-saved* floating point registers are saved on
the thread's stack. If the switched-in thread has been using the floating
point registers, the kernel restores the *callee-saved* FP registers of
the switched-in thread and the *caller-saved* FP context is restored from
the thread's stack. Thus, the kernel does not save or restore the FP
context of threads that are not using the FP registers. An extra 16 bytes
(single floating point hardware) or 32 bytes (double floating point hardware)
of stack space is required to load and store floating point registers.

RISC-V architecture
-------------------

On the RISC-V architecture the kernel treats each thread as an FPU
user on a case-by-case basis with the FPU access allocated on demand.
A "lazy save" algorithm is used during context switching which updates
the floating point registers only when it is absolutely necessary.
For example, the FPU registers are *not* saved when switching from an
FPU user to a non-user thread (or an FPU user that doesn't touch the FPU
during its scheduling slot), and then back to the original FPU user.

FPU register usage by ISRs is supported although not recommended. When an
ISR uses floating point or SIMD registers, then the access is trapped, the
current FPU user context is saved in the thread object and the ISR is resumed
with interrupts disabled so to prevent another IRQ from interrupting the ISR
and potentially requesting FPU usage. Because ISR don't have a persistent
register context, there are no provision for saving an ISR's FPU context
either, hence the IRQ disabling.

As an optimization, the FPU context is preemptively restored upon scheduling
back an "active FPU user" thread that had its FPU context saved away due to
FPU usage by another thread. Active FPU users are so designated when they
make the FPU state "dirty" during their most recent scheduling slot before
being scheduled out. So if a thread doesn't modify the FPU state within its
scheduling slot and another thread claims the FPU for itself afterwards then
that first thread will be subjected to the on-demand regime and won't have
its FPU context restored until it attempts to access it again. But if that
thread does modify the FPU before being scheduled out then it is likely to
continue using it when scheduled back in and preemptively restoring its FPU
context saves on the exception trap overhead that would occur otherwise.

Each thread object becomes 136 bytes (single-precision floating point
hardware) or 264 bytes (double-precision floating point hardware) larger
when Shared FP registers mode is enabled.

SPARC architecture
------------------

On the SPARC architecture, the kernel treats each thread as a non-user
or FPU user and the thread must be tagged by one of the
following techniques:

* A statically-created thread can be tagged by passing the
  :c:macro:`K_FP_REGS` option to :c:macro:`K_THREAD_DEFINE`.

* A dynamically-created thread can be tagged by passing the
  :c:macro:`K_FP_REGS` to :c:func:`k_thread_create`.

During thread context switch at exit from interrupt handler, the SPARC
kernel saves *all* floating point registers, if the FPU was enabled in
the switched-out thread. Floating point registers are saved on the thread's
stack. Floating point registers are restored when a thread context is restored
iff they were saved at the context save. Saving and restoring of the floating
point registers is synchronous and thus not lazy. The FPU is always disabled
when an ISR is called (independent of :kconfig:option:`CONFIG_FPU_SHARING`).

Floating point disabling with :c:func:`k_float_disable` is not implemented.

When :kconfig:option:`CONFIG_FPU_SHARING` is used, then 136 bytes of stack space
is required for each FPU user thread to load and store floating point
registers. No extra stack is required if :kconfig:option:`CONFIG_FPU_SHARING` is
not used.

x86 architecture
----------------

On the x86 architecture the kernel treats each thread as a non-user,
FPU user or SSE user on a case-by-case basis. A "lazy save" algorithm is used
during context switching which updates the floating point registers only when
it is absolutely necessary. For example, the registers are *not* saved when
switching from an FPU user to a non-user thread, and then back to the original
FPU user. The following table indicates the amount of additional stack space a
thread must provide so the registers can be saved properly.

=========== =============== ==========================
Thread type FP register use Extra stack space required
=========== =============== ==========================
cooperative any             0 bytes
preemptive  none            0 bytes
preemptive  FPU             108 bytes
preemptive  SSE             464 bytes
=========== =============== ==========================

The x86 kernel automatically detects that a given thread is using
the floating point registers the first time the thread accesses them.
The thread is tagged as an SSE user if the kernel has been configured
to support the SSE registers, or as an FPU user if the SSE registers are
not supported. If this would result in a thread that is an FPU user being
tagged as an SSE user, or if the application wants to avoid the exception
handling overhead involved in auto-tagging threads, it is possible to
pretag a thread using one of the techniques listed below.

* A statically-created x86 thread can be pretagged by passing the
  :c:macro:`K_FP_REGS` or :c:macro:`K_SSE_REGS` option to
  :c:macro:`K_THREAD_DEFINE`.

* A dynamically-created x86 thread can be pretagged by passing the
  :c:macro:`K_FP_REGS` or :c:macro:`K_SSE_REGS` option to
  :c:func:`k_thread_create`.

* An already-created x86 thread can pretag itself once it has started
  by passing the :c:macro:`K_FP_REGS` or :c:macro:`K_SSE_REGS` option to
  :c:func:`k_float_enable`.

If an x86 thread uses the floating point registers infrequently it can call
:c:func:`k_float_disable` to remove its tagging as an FPU user or SSE user.
This eliminates the need for the kernel to take steps to preserve
the contents of the floating point registers during context switches
when there is no need to do so.
When the thread again needs to use the floating point registers it can re-tag
itself as an FPU user or SSE user by calling :c:func:`k_float_enable`.

Implementation
**************

Performing Floating Point Arithmetic
====================================

No special coding is required for a thread to use floating point arithmetic
if the kernel is properly configured.

The following code shows how a routine can use floating point arithmetic
to avoid overflow issues when computing the average of a series of integer
values.

.. code-block:: c

    int average(int *values, int num_values)
    {
        double sum;
        int i;

        sum = 0.0;

        for (i = 0; i < num_values; i++) {
            sum += *values;
            values++;
        }

        return (int)((sum / num_values) + 0.5);
    }

Suggested Uses
**************

Use the kernel floating point services when an application needs to
perform floating point operations.

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

To configure unshared FP registers mode, enable the :kconfig:option:`CONFIG_FPU`
configuration option and leave the :kconfig:option:`CONFIG_FPU_SHARING` configuration
option disabled.

To configure shared FP registers mode, enable both the :kconfig:option:`CONFIG_FPU`
configuration option and the :kconfig:option:`CONFIG_FPU_SHARING` configuration option.
Also, ensure that any thread that uses the floating point registers has
sufficient added stack space for saving floating point register values
during context switches, as described above.

For x86, use the :kconfig:option:`CONFIG_X86_SSE` configuration option to enable
support for SSEx instructions.

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

.. doxygengroup:: float_apis

Part 3

한국어 전문 번역

Kernel은 floating-point register가 있는 board configuration에서 thread가 해당 register를 사용하도록 지원합니다.

개념

Application에 필요한 FP service만 제공하도록 kernel을 구성할 수 있습니다. 세 operation mode를 지원하며 SSE register 지원도 별도로 포함하거나 뺄 수 있습니다.

FP register 없음 mode

Floating-point register를 쓰는 thread가 하나도 없을 때 사용하는 default mode입니다. 어느 thread라도 FP register를 사용하면 kernel이 fatal error를 만들고 그 thread를 abort합니다.

공유하지 않는 FP register mode

FP register를 쓰는 thread가 하나뿐일 때 사용합니다. x86에서는 어느 thread든 쓸 수 있도록 kernel이 FP register를 초기화하며 ARM Cortex-M·ARCv2에서는 이 초기화를 생략합니다. Context switch 때 FP register를 그대로 둡니다.

공유 FP register mode

FP register를 쓰는 thread가 둘 이상일 때 사용합니다. CPU architecture에 따라 FP register를 전혀 못 쓰는 non-user, standard FP register를 쓰는 FPU user, standard FP와 SSE를 모두 쓰는 SSE user 중 일부 또는 전부를 구분합니다.

Kernel이 FP register access를 초기화·활성화하고 context switch마다 저장·복구해 각 FPU/SSE user 계산이 다른 user 계산에 영향받지 않게 합니다.

ARM Cortex-M + Floating Point Extension

Shared mode에서 모든 thread를 FPU user로 취급해 누구나 FP register에 접근할 수 있습니다. ARM kernel은 thread가 처음 FP register를 접근할 때 사용 사실을 자동 감지합니다.

Static ARM thread는 K_THREAD_DEFINE에 K_FP_REGS를, dynamic thread는 k_thread_create에 같은 option을 전달해 미리 tag할 수 있습니다. Pretag는 MPU stack protection guard 크기를 올바르게 구성해 overflow detection을 항상 보장하고 thread 생성 때 lazy stacking을 활성화합니다.

Context switch 때 FP를 쓰던 outgoing thread의 callee-saved register를 kernel이 저장하고 caller-saved register는 thread stack에 저장됩니다. Incoming thread가 FP를 썼다면 callee-saved를 kernel이 복구하고 caller-saved context는 stack에서 복구합니다. FP를 쓰지 않는 thread의 context는 저장·복구하지 않습니다.

FP를 사용할 thread는 callee-saved context용 stack 72 byte를 추가로 제공해야 합니다.

ARM Cortex-M Zephyr application은 Lazy Stacking을 사용해 FP context가 active일 때 interrupt latency를 줄입니다. MPU stack protection이 없으면 항상 active입니다. MPU protection이 있으면 K_FP_REGS pretag thread는 처음부터, pretag하지 않은 thread는 kernel이 FP 사용을 감지한 순간부터 활성화됩니다.

더 이상 FP register가 필요 없는 ARM thread는 k_float_disable()로 context switch 중 저장·복구를 중단할 수 있습니다.

ARM64

AArch64 kernel은 thread마다 필요할 때 FPU user로 취급하고 lazy save로 꼭 필요할 때만 register를 갱신합니다. FPU user에서 non-user로 갔다가 같은 FPU user로 돌아오는 switch에서는 저장하지 않습니다.

ISR의 FP/SIMD 사용은 지원하지만 권장하지 않습니다. Access trap이 발생하면 현재 FPU user context를 thread object에 저장하고 ISR을 interrupt disabled 상태로 재개해 다른 IRQ가 다시 FPU를 요구하지 못하게 합니다. ISR은 persistent register context가 없어 자체 FPU context를 저장할 방법이 없기 때문입니다.

Shared FP mode를 켜면 thread object 하나가 512 byte 커집니다.

ARCv2

Thread를 non-user 또는 FPU user로 취급하며 static thread는 K_THREAD_DEFINE, dynamic thread는 k_thread_create에 K_FP_REGS를 전달해 tag해야 합니다. 더 이상 필요 없으면 k_float_disable()로 보존을 중단합니다.

Outgoing FP user의 callee-saved register는 kernel이, caller-saved register는 thread stack이 저장합니다. Incoming FP user에서는 두 context를 각각 복구하며 non-user는 처리하지 않습니다. Single-precision hardware는 stack 16 byte, double-precision은 32 byte가 추가로 필요합니다.

RISC-V

Thread별로 FPU access를 on-demand 할당하고 lazy save로 꼭 필요할 때만 register를 갱신합니다. FPU user가 scheduling slot 동안 FPU를 건드리지 않았다면 다른 user를 거쳐 돌아와도 저장하지 않습니다.

ISR FP/SIMD 사용은 지원하지만 권장하지 않으며 ARM64와 같이 trap 뒤 current user context를 저장하고 ISR을 interrupt-disabled 상태로 재개합니다.

최근 scheduling slot에서 FPU state를 dirty로 만든 active FPU user는 돌아올 때 context를 미리 복구합니다. Dirty하지 않은 thread는 다시 접근할 때까지 on-demand regime을 유지합니다. 이 예측적 restore는 이후 exception trap overhead를 줄입니다.

Shared FP mode에서 thread object는 single-precision이면 136 byte, double-precision이면 264 byte 커집니다.

SPARC

Thread를 non-user 또는 FPU user로 구분하며 static·dynamic 생성 때 K_FP_REGS로 tag합니다.

Interrupt handler exit의 context switch에서 outgoing thread의 FPU가 enabled였다면 모든 FP register를 thread stack에 저장하고, 저장된 경우에만 restore합니다. 동기 방식이며 lazy가 아닙니다. ISR을 호출할 때는 CONFIG_FPU_SHARING과 무관하게 FPU를 항상 disable합니다.

k_float_disable은 구현되지 않았습니다. CONFIG_FPU_SHARING을 쓰면 FPU user thread마다 stack 136 byte가 필요하고 쓰지 않으면 추가 stack이 없습니다.

x86

Thread별로 non-user, FPU user, SSE user를 구분하고 lazy save를 사용합니다. 필요한 추가 stack은 다음과 같습니다.

Thread typeFP register 사용추가 stack
cooperativeany0 byte
preemptivenone0 byte
preemptiveFPU108 byte
preemptiveSSE464 byte

Kernel은 첫 FP access에서 사용을 자동 감지하고 CONFIG_X86_SSE를 켰으면 SSE user, 아니면 FPU user로 tag합니다. 잘못된 분류를 피하거나 auto-tag exception overhead를 줄이려면 미리 tag할 수 있습니다.

  • Static thread는 K_THREAD_DEFINE에 K_FP_REGS 또는 K_SSE_REGS를 전달합니다.
  • Dynamic thread는 k_thread_create에 같은 option을 전달합니다.
  • 이미 시작한 thread는 k_float_enable()에 option을 전달해 스스로 pretag합니다.

FP를 드물게 쓰는 x86 thread는 k_float_disable()로 FPU/SSE tag를 제거해 불필요한 context 보존을 피하고, 다시 필요할 때 k_float_enable()로 retag할 수 있습니다.

구현

Floating-point arithmetic 수행

Kernel이 올바르게 구성되어 있으면 thread code에 특별한 처리가 필요 없습니다. 다음 routine은 integer series 평균 계산에서 overflow를 피하려고 double sum을 사용합니다.

int average(int *values, int num_values)
{
    double sum;
    int i;

    sum = 0.0;

    for (i = 0; i < num_values; i++) {
        sum += *values;
        values++;
    }

    return (int)((sum / num_values) + 0.5);
}

권장 용도

Application이 floating-point operation을 수행해야 할 때 kernel FP service를 사용합니다.

설정 옵션

Unshared mode는 CONFIG_FPU를 켜고 CONFIG_FPU_SHARING을 끕니다. Shared mode는 둘 다 켜고 architecture별 context 저장에 필요한 stack 공간을 모든 FP user thread에 추가합니다. x86 SSEx instruction은 CONFIG_X86_SSE로 지원합니다.

API 참고: float_apis

k_float_disable(thread)

지정 thread가 더 이상 FP register를 쓰지 않는다고 kernel에 알려 floating-point context 보존을 중지합니다. Architecture마다 요청 제약이 있으며 현재 FP 지원이 활성화된 thread에만 사용해야 합니다.

매개변수

  • thread: thread ID

반환값

  • 0: 성공
  • -ENOTSUP: FP disable 미구현
  • -EINVAL: disable 수행 불가
k_float_enable(thread, options)

지정 thread가 FP register를 사용한다고 kernel에 알려 context 보존을 활성화하고 reset된 FPU 상태로 초기화합니다. 다음 z_swap()에서 sane FPU를 상속하거나 자신의 저장 context를 load하며 이후 preemptive switch에서도 FP context를 보호합니다.

매개변수

  • thread: thread ID
  • options: architecture-dependent register set option. x86에서는 K_FP_REGS가 x87 FPU·MMX, K_SSE_REGS가 SSE와 x87·MMX를 뜻함

반환값

  • 0: 성공
  • -ENOTSUP: FP enable 미구현
  • -EINVAL: enable 수행 불가

Source

출처

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