概念
内核可以配置为仅提供应用程序所需的浮点服务。支持三种操作模式:
- 无 FP 寄存器模式
- 非共享 FP 寄存器模式
- 共享 FP 寄存器模式
无 FP 寄存器模式
当应用程序没有使用浮点寄存器的线程时,将使用此模式。它是内核的默认浮点服务模式。
如果线程使用任何浮点寄存器,内核将生成致命错误条件并中止该线程。
非共享 FP 寄存器模式
当应用程序只有一个使用浮点寄存器的线程时,将使用此模式。
在 x86 平台上,内核初始化浮点寄存器,以便任何线程都可以使用它们(在 ARM Cortex-M 平台和 ARCv2 平台上跳过初始化)。每当发生上下文切换时,浮点寄存器保持不变。
共享FP 寄存器模式
当应用程序具有两个或多个使用浮点寄存器的线程时,将使用此模式。根据底层 CPU 体系结构,内核支持以下一个或多个线程子类:
- 非用户:不能使用任何浮点寄存器的线程
- FPU 用户:可以使用标准浮点寄存器的线程
- SSE 用户:可同时使用标准浮点寄存器和 SSE 寄存器的线程
内核初始化并允许访问浮点寄存器,以便任何线程都可以使用它们,然后在上下文切换期间保存并恢复这些寄存器,以确保每个 FPU 用户或 SSE 用户执行的计算不受其他用户执行的计算的影响。
ARM Cortex-M 架构
共享 FP 寄存器模式是 ARM Cortex-M 中的默认浮点服务模式。
在具有浮点扩展的 ARM Cortex-M 体系结构上,当启用共享 FP 寄存器模式时,内核会将所有线程视为 FPU 用户。这意味着允许任何线程访问浮点寄存器。ARM 内核会在给定线程首次访问浮点寄存器时自动检测该线程是否正在使用浮点寄存器。
使用下面列出的技术之一对打算使用 FP 寄存器的线程进行预标记。
- 通过将
K_FP_REGS
选项传递给K_THREAD_DEFINE
,可以预先标记静态创建的 ARM 线程。 - 通过将
K_FP_REGS
选项传递给k_thread_create()
,可以预先标记动态创建的 ARM 线程。 - 使用
K_FP_REGS
选项预标记线程指示基于 MPU 的堆栈保护机制正确配置线程保护区域的大小,以始终保证堆栈溢出检测,并在创建线程时为给定线程启用延迟堆叠。
在线程上下文切换期间,如果线程使用FP寄存器,那在线程切换期间就会将FP寄存器保存在线程的堆栈中。
每个打算使用浮点寄存器的线程都必须提供额外的 72 字节堆栈空间,用于保存 FP 上下文。
目前在 ARM Cortex-M 架构上的 Zephyr 应用程序中启用了惰性堆叠,当浮点上下文处于活动状态时,可最大程度地减少中断延迟。
如果未启用基于 MPU 的堆栈保护机制,则 Zephyr 应用程序中的惰性堆栈始终处于活动状态。启用基于 MPU 的堆栈保护后,以下规则适用于延迟堆叠:
- 默认情况下,在预标记为
K_FP_REGS
的线程上激活惰性堆叠 - 一旦内核检测到它们正在使用浮点寄存器,就会在没有预先标记
K_FP_REGS
的线程上动态激活惰性堆叠。
如果 ARM 线程不再需要使用浮点寄存器,它可以调用k_float_disable()
。这将指示内核在线程上下文切换期间不要保存或恢复其 FP 上下文。
ARM64 架构
共享 FP 寄存器模式是 ARM64 上的默认浮点服务模式。
在 ARM64 (Aarch64) 架构上,内核根据具体情况将每个线程视为 FPU 用户。在上下文切换期间使用延迟保存
算法,该算法仅在绝对必要时才更新浮点寄存器。例如,从 FPU 用户切换到非用户线程,然后再切换回原始 FPU 用户时,不会保存寄存器。
支持 ISR 使用 FPU 寄存器,但不建议这样做。当 ISR 使用浮点或 SIMD 寄存器时,访问将被捕获。由于 ISR 不能存储寄存器上下文,因此 IRQ 处于禁用状态。
启用共享 FP 寄存器模式时,每个线程对象将变大 512 个字节。
ARCv2 架构
在 ARCv2 架构上,内核将每个线程视为非用户或 FPU 用户,并且必须使用以下技术之一标记线程。
- 可以通过将
K_FP_REGS
选项传递给K_THREAD_DEFINE
来标记静态创建的 ARC 线程。 - 可以通过将
K_FP_REGS
传递给k_thread_create()
来标记动态创建的 ARC 线程。
如果 ARC 线程不再需要使用浮点寄存器,它可以调用k_float_disable()
。这将指示内核在线程上下文切换期间不要保存或恢复其 FP 上下文。
在线程上下文切换期间,如果线程使用FP寄存器,那在线程切换期间就会将FP寄存器保存在线程的堆栈中。
加载和存储浮点寄存器需要额外的 16 字节(单浮点硬件)或 32 字节(双浮点硬件)堆栈空间。
RISC-V 架构
在RISC-V架构上,内核将每个线程视为非用户或FPU用户,并且线程必须使用以下技术之一进行标记:
- 静态创建的RISC-V线程可以通过将
K_FP_REGS
选项传递给K_THREAD_DEFINE
来标记。 - 动态创建的RISC-V线程可以通过将
K_FP_REGS
传递给k_thread_create()
来标记。 - 可以通过调用
k_float_enable()
来标记正在运行的 RISC-V 线程。此函数只能从线程本身调用。
如果RISC-V线程不再需要使用浮点寄存器,它可以调用k_float_disable()
。这将指示内核在线程上下文切换期间不要保存或恢复其 FP 上下文。此函数只能从线程本身调用。
在线程上下文切换期间,如果线程使用FP寄存器,那在线程切换期间就会将FP寄存器保存在线程的堆栈中。
加载和存储浮点寄存器需要额外的 84 字节(单浮点硬件)或 164 字节(双浮点硬件)堆栈空间。
SPARC 架构
在 SPARC 体系结构上,内核将每个线程视为非用户或 FPU 用户,并且必须使用以下技术之一标记该线程:
- 可以通过将
K_FP_REGS
选项传递给K_THREAD_DEFINE
来标记静态创建的线程。 - 可以通过将
K_FP_REGS
传递给k_thread_create()
来标记动态创建的线程。
在从中断处理程序退出时进行线程上下文切换期间,如果在已切换的线程中启用了 FPU,则 SPARC 内核将保存所有浮点寄存器。浮点寄存器保存在线程的堆栈上。当线程上下文恢复时,浮点寄存器将恢复,而它们在上下文保存时保存。浮点寄存器的保存和恢复是同步的,因此不会延迟。调用 ISR 时,FPU 始终处于禁用状态(独立于CONFIG_FPU_SHARING
)。
不能通过k_float_disable()
来实现禁用浮点。
使用CONFIG_FPU_SHARING
时,每个 FPU 用户线程需要 136 字节的堆栈空间来加载和存储浮点寄存器。如果未使用CONFIG_FPU_SHARING
,则不需要额外的堆栈。
执行浮点运算
如果内核配置正确,则线程使用浮点算术不需要特殊编码。
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);
}
浮点模式的配置
配置非共享 FP 寄存器模式
,请启用CONFIG_FPU
配置选项,并将CONFIG_FPU_SHARING
配置选项保留为禁用状态。
配置共享 FP 寄存器模式
,请同时启用CONFIG_FPU
配置选项和CONFIG_FPU_SHARING
配置选项。此外,请确保使用浮点寄存器的任何线程都有足够的附加堆栈空间,以便在上下文切换期间保存浮点寄存器值。