Linux 内核使用浮点问题​

本文详细探讨了Linux内核中硬浮点和软浮点的区别,包括ARM处理器的软硬浮点编译选项。在内核代码中使用浮点运算涉及到浮点寄存器的保存与恢复,不推荐常规使用,但特定场景下如加密算法加速可能需要。ARM64进程切换会涉及浮点寄存器切换,内核使用浮点需谨慎,如需使用应遵循特定函数调用规则。
一、硬浮点与软浮点
1. 硬浮点

编译器将代码直接编译成硬件浮点协处理器(浮点运算单元FPU)能识别的指令,这些指令在执行的时候ARM核直接把它转给协处理器执行。FPU 通常有一套额外的寄存器来完成浮点参数传递和运算。使用实际的硬件浮点运算单元(FPU)会带来性能的提升

2. 软浮点

编译器把浮点运算转成浮点运算的函数调用和库函数调用(即用整数运算模拟浮点运算),没有FPU的指令调用,也没有浮点寄存器的参数传递。浮点参数的传递也是通过ARM寄存器或者堆栈完成。现在的Linux系统默认编译选择使用hard-float,如果系统没有任何浮点处理器单元,这就会产生非法指令和异常。因而一般的系统镜像都采用软浮点以兼容没有VFP的处理器。

3. ARM软浮点与硬浮点编译

出于低功耗、封装限制等种种原因,以前的一些ARM处理器没有独立的硬件浮点运算单元,需要用软件来实现浮点运算。随着技术发展,现在高端的ARM处理器基本都具备了硬件执行浮点操作的能力。 新旧两种架构之间的差异,就产生了两个不同的接口 – 软浮点与硬浮点。

编译选项:

-mfpu =name(neon or vfp)指定FPU 单元

-mfloat-abi= name(soft、hard、 softfp):指定软件浮点或硬件浮点或 兼容软浮点调用接口

-mfpu可以指定使用FPU单元类型,以ARM A76为例有两种:VFP和Neon

在这里插入图片描述

VFP与Neon

  • VFP (vector floating-point)

    VFP是一个按顺序工作的浮点协处理器, 它对一组输入执行一个操作并返回一个输出。目的是加快浮点计算,支持单精度和双精度浮点。

  • NEON

    Neon是SIMD (Single Instruction Multiple Data) , 支持单指令多数据操作,意味着在执行一条指令期间,将对多达16个数据集并行执行相同的操作,支持整数和单精度浮点数向量化(并行)操作。

  • 二者区别

    VFP 支持单精度和双精度浮点,顺序执行,目的加快浮点计算

    Neon 是SIMD,支持单指令多数据操作,支持整数和单精度浮点数向量化(并行)操作,不支持双浮点 。Neon最大的好处是如果想要执行矢量操作,如对视频的编码/解码, 它可以并行执行单精度浮点(float)操作。 从而提高对视频编码/解码性能

    VFP 和Neon是共用浮点寄存器,只是执行的指令不同。

soft、hard、 softfp

在有fpu的情况下,可以通过-mfloat-abi来指定使用哪种,有如下三种值:

  • **soft:**不用fpu计算,即使有fpu浮点运算单元也不用。
  • armel:(arm eabi little endian) 也即softfp,用fpu计算(即会将浮点运算编译成对应的浮点指令),但是传参数用普通寄存器传,这样中断的时候,只需要保存普通寄存器,中断负荷小,但是参数需要转换成浮点的再计算。
  • armhf:(arm hard float)也即hard,用fpu计算,传参数用fpu中的浮点寄存器传,省去了转换性能最好,但是中断负荷高。

而arm64,64位的arm默认就是hard float的,因此不需要hf的后缀。kernel、rootfs和app编译的时候,指定的必须保持一致才行。

使用softfp模式,会存在不必要的浮点到整数、整数到浮点的转换。而使用hard模式,在每次浮点相关函数调用时,平均能节省20个CPU周期。

4. ARM启用硬浮点运算

需要使用硬浮点,需要内核开启对硬浮点支持,同时编译时要使用上面的softfp或hard,才可以使用FPU/Neon进行计算。

二、 在内核代码中使用浮点问题
1. ARM64的用户空间进程切换 ---- 浮点寄存器切换

ARM64,默认使用的是硬浮点,在进程切换时会涉及对浮点寄存器的操作。

对于ARM64而言,进程切换的context包括:

(1)通用寄存器

(2)浮点寄存器

(3)地址空间寄存器(ttbr0_el1和ttbr1_el1)

(4)其他寄存器(ASID、thread process ID register等)

Path:arch/arm64/kernel/process.c
/*
 * Thread switching.
 */
__notrace_funcgraph struct task_struct *__switch_to(struct task_struct *prev,
				struct task_struct *next)
{
   
   
	struct task_struct *last;

	fpsimd_thread_switch(next); 
    /*fp是float-point的意思,和浮点运算相关。fpsimd_thread_switch
    其实就是把当前FPSIMD的状态保存到了内存中(task.thread.fpsimd_state),
    从要切入的next进程描述符中获取FPSIMD状态,并加载到CPU上。*/
	tls_thread_switch(next);
	hw_breakpoint_thread_switch(next);
	contextidr_thread_switch(next);
	........
	return last;
}
---------------------------------------
Path:arch/arm64/kernel/fpsimd.c
void fpsimd_thread_switch(struct task_struct *next)
{
   
   
	......
	/* Save unsaved fpsimd state, if any: */
	fpsimd_save(); //这里将struct fpsimd_state保存到通用寄存器X0中
    ......
}
-----------------------------------------
Path:arch/arm64/kernel/entry-fpsimd.S
/* Save the FP registers. x0 - pointer to struct fpsimd_state*/
ENTRY(fpsimd_save_state)
	fpsimd_save x0, 8
	ret
ENDPROC(fpsimd_save_state)
2. 内核使用浮点

关于内核使用浮点, Robert Love’s 《Linux Kernel Development》(Linux内核设计与实现)书中提到

No (Easy) Use of Floating Point

When a user-space process uses floating-point instructions, the kernel manages the transition from integer to floating point mode. What the kernel has to do when using floating-point instructions varies by architecture, but the kernel normally catches a trap and then initiates the transition from integer to floating point mode.

Unlike user-space, the kernel does not have the luxury of seamless support for floating point because it cannot easily trap itself. Using a floating point inside the kernel requires manually saving and restoring the floating point registers, among other possible chores. The short answer is: Don’t do it! Except in the rare cases, no floating-point operations are in the kernel.

后面这句话提到如果内核使用浮点则需要保存恢复浮点寄存器等其他杂项,这样会导致内核性能下降,所以一般不建议使用浮点,除非特殊情况。

但是Robert Love’s在这里并没有讨论如何在内核中正确使用浮点,以及未正确使用浮点可能会出现什么问题。**当你需要在内核中使用浮点,如果按照用户空间的写法可能会出现一些意想不到的情况。如:程序Crash,内存越界、访问非法内存地址,浮点计算出错等稀奇古怪的问题。出现这种问题的原因是:内核由于性能原因,在内核运行的代码,内核在进行上下文切换时,不会主动保存和恢复浮点寄存器。**这样可能会导致内核在进行浮点运算时,可能会破坏此时用户空间的浮点寄存器状态,导致用户空间的fpsimd_state状态异常,随后程序的行为将变的不可控。

  • 如何在内核使用浮点

    在内核中使用浮点,在不同架构下会有不同操作流程。这部分需要查阅内核文档,如在X86上要用 kernel_fpu_begin()/kernel_fpu_end(),在arm上用 kernel_neon_begin()/ kernel_neon_end()。但并非在使用浮点时简单的使用上面代码就可以,以arm为例,内核文档里介绍了为何要使用该函数,以及如何使用

    https://www.kernel.org/doc/Documentation
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值