内核模式 NEON

TL;DR 总结

  • 仅使用 NEON 指令,或不依赖于支持代码的 VFP 指令

  • 将您的 NEON 代码隔离在一个单独的编译单元中,并使用 '-march=armv7-a -mfpu=neon -mfloat-abi=softfp' 进行编译

  • 在调用您的 NEON 代码前后分别调用 kernel_neon_begin() 和 kernel_neon_end()

  • 不要在您的 NEON 代码中睡眠,并注意它将在禁用抢占的情况下执行

简介

可以在内核模式下运行的代码中使用 NEON 指令(在某些情况下,也可以使用 VFP 指令)。但是,出于性能原因,NEON/VFP 寄存器文件不会像常规寄存器文件那样在每次上下文切换或异常发生时都保存和恢复,因此需要一些手动干预。此外,对于可能睡眠 [即,可能调用 schedule()] 的代码需要特别注意,因为由于下面概述的原因,NEON 或 VFP 指令将在不可抢占的部分中执行。

延迟保存和恢复

NEON/VFP 寄存器文件使用延迟保存(在 UP 系统上)和延迟恢复(在 SMP 和 UP 系统上)进行管理。这意味着寄存器文件保持“活动”状态,并且仅在多个任务争用 NEON/VFP 单元时(或者,在 SMP 的情况下,当任务迁移到另一个核心时)才会保存和恢复。延迟恢复的实现方式是在每次上下文切换后禁用 NEON/VFP 单元,导致随后发出 NEON/VFP 指令时发生陷阱,从而允许内核介入并在必要时执行恢复。

内核模式下对 NEON/VFP 单元的任何使用都不应干扰这一点,因此需要“急切”地保存 NEON/VFP 寄存器文件,并显式启用 NEON/VFP 单元,以便在首次后续使用时不会生成异常。这由函数 kernel_neon_begin() 处理,应在发出任何内核模式 NEON 或 VFP 指令之前调用。同样,使用后应再次禁用 NEON/VFP 单元,以确保用户模式在下次使用时会命中延迟恢复陷阱。这由函数 kernel_neon_end() 处理。

内核模式下的中断

出于性能和简单性的考虑,决定不为内核模式 NEON/VFP 寄存器内容提供保存/恢复机制。这意味着只有在保证不触及 NEON/VFP 寄存器的情况下,才能允许中断内核模式 NEON 部分。因此,内核中适用以下规则和限制: * 不允许在中断上下文中使用 NEON/VFP 代码; * 不允许 NEON/VFP 代码睡眠; * NEON/VFP 代码在禁用抢占的情况下执行。

如果延迟是一个问题,可以在您的代码中 NEON 寄存器都不活动的地方背靠背调用 kernel_neon_end() 和 kernel_neon_begin()。(如果在 meantime 中没有发生上下文切换,则对 kernel_neon_begin() 的其他调用应该相当便宜)

VFP 和支持代码

早期版本的 VFP(版本 3 之前)依赖于软件支持来实现 IEEE-754 兼容的下溢处理等。当 VFP 单元需要此类软件辅助时,它会通过引发未定义指令异常来向内核发出信号。内核通过检查 VFP 控制寄存器和当前指令和参数来响应,并在软件中模拟该指令。

目前尚未为在内核模式下执行的 VFP 指令实现此类软件辅助。如果遇到这种情况,内核将失败并生成 OOPS。

将 NEON 代码与普通代码分离

编译器不知道 kernel_neon_begin() 和 kernel_neon_end() 的特殊含义,即只允许在调用这些相应函数之间发出 NEON/VFP 指令。此外,如果选择了 -mfpu=neon,GCC 可能会在 -O3 级别生成它自己的 NEON 指令,即使内核当前是在 -O2 级别编译的,如果不采取特殊措施,未来的更改可能会导致 NEON/VFP 指令出现在意想不到的位置。

因此,在内核中使用 NEON/VFP 的推荐和唯一支持的方法是遵守以下规则

  • 将 NEON 代码隔离在一个单独的编译单元中,并使用 '-march=armv7-a -mfpu=neon -mfloat-abi=softfp' 进行编译;

  • 从 *未* 使用 GCC 标志 '-mfpu=neon' 设置构建的编译单元中,发出对 kernel_neon_begin()、kernel_neon_end() 以及包含 NEON 代码的单元的调用。

由于内核是使用 '-msoft-float' 编译的,因此以上将保证 NEON 和 VFP 指令都只会在任何优化级别下出现在指定的编译单元中。

NEON 汇编器

只要遵循上述规则,就支持 NEON 汇编器,没有其他注意事项。

GCC 生成的 NEON 代码

GCC 选项 -ftree-vectorize(由 -O3 隐含)尝试利用隐式并行性,并从普通 C 源代码生成 NEON 代码。只要遵循上述规则,就完全支持这一点。

NEON 内联函数

也支持 NEON 内联函数。但是,由于使用 NEON 内联函数的代码依赖于 GCC 标头 <arm_neon.h>(其中 #includes <stdint.h>),除了上述规则之外,还应注意以下事项

  • 使用 '-ffreestanding' 编译包含 NEON 内联函数的单元,以便 GCC 使用其内置版本的 <stdint.h>(这是一个内核不提供的 C99 标头);

  • 最后包含 <arm_neon.h>,或者至少在 <linux/types.h> 之后包含