Linux驱动开发HTR3218项目BUG(一):驱动程序中的浮点运算问题

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xi_xix_i/article/details/134087946

一、编译环境

linux版本:4.19.9
芯片架构:arm64

二、驱动程序中的浮点运算问题

在驱动程序中,编写了如下代码:

ret_copy = copy_from_user(&led_brightness_2_args, (void*)arg, sizeof(remo_led_brightness_set_2));
led_color_args.led = led_brightness_2_args.led;
for(loop_cnt = 0; loop_cnt < 3; loop_cnt++)
{                
	if(pdata->device.led_state_remain[led_brightness_2_args.led].led_color_rgb[loop_cnt])
		led_color_args.led_color_rgb[loop_cnt] = (u8)led_brightness_2_args.brightness* 0.01 *255;
	else`
		led_color_args.led_color_rgb[loop_cnt] = 0;
}

然后在编译时报错

在这里插入图片描述
搜索引擎搜索该错误,从GCC官网了解到,-megeneral-regs-only是个GCC的ARM编译选项,具体解释如下:

-mgeneral-regs-only
    Generate code which uses only the general-purpose registers. This will prevent
    the compiler from using floating-point and Advanced SIMD registers but will
    not impose any restrictions on the assembler.

大致意思就是添加这个编译器选项后,将生成只使用通用寄存器的代码。这将阻止编译器使用浮点寄存器和高级SIMD寄存器,但不会对汇编代码使用的寄存器施加任何限制。所以如果代码中有可能使用到浮点寄存器的浮点数操作的话,代码是无法编译通过的。

这篇文章提到,使用 ARM.v8 架构的CPU 有4个运行等级(每个不同等级可以访问的寄存器的不同,就像arm v7的user mode和supervisor mode),分别为EL0、EL1、EL2、EL3, 其中EL0 运行等级最低,基本上只能访问通用寄存器、栈指针寄存器、STR、LDR,EL3为最高等级,其中用户空间的进程运行在EL0 等级,各进程只能看到自己的信息,而感知不到其他进程,也不能访问其他进程的地址空间。而操作系统是运行在EL1 运行等级,该等级能够访问系统寄存器和配置管理进程资源。任何访问SVE、高级SIMD、浮点寄存器的操作都会被trap到更高级别的运行等级。贴一张该文章中老哥用的图:

在这里插入图片描述

所以我猜是为了实现进程隔离,让程序尽量不去操作不该操作的寄存器,默认的编译内核驱动程序的Makefile中都会加上这个编译器选项。同时这篇文章提到,Robert Love在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.

大致意思就是:当用户空间进程使用浮点指令时,内核来处理从整数模式到浮点模式的转换。虽然当使用浮点指令时,内核要做的处理会随着架构的不同而不同,但是往往会使操作系统进行一次用户态与内核态的切换,然后初始化从整数到浮点模式的转换;不像用户空间,内核对浮点运算的支持并不是那么完备,因为它trap起来比较麻烦。内核中使用浮点运算需要手动保存和恢复浮点运算寄存器,以及手动处理一些其他的杂务。

这一段话我看的有点懵(也可能是我翻译的有问题),我的理解是用户态进行浮点运算,从用户到trap到更高运行等级,并操作浮点运算寄存器,然后再返回用户态,这一套都已经优化的很好(可能是操作系统做的工作,也或者是编译器做的工作?)。但是内核驱动程序中使用浮点运算则没有这么一套完备的优化过程,所以需要代码作者手动进行一些现场的恢复和保存工作。(我不知道我理解的有没有问题,还没有更深入的研究一下,有懂的老哥可以指点一下。)

总之,就是非必要,不要在内核中进行浮点运算,这篇文章的作者确实是因为内核中使用了浮点运算操作,导致覆盖了原来用户态已储存在浮点运算寄存器的值(这么看内核如果非要用浮点运算的话,确实是要人工保存和恢复fpu寄存器)。

我想实现的功能是根据命令行参数调整led灯的亮度,在我的驱动代码中我是先进行了浮点数的运算,然后再强转为unsigned char类型,该功能确实可以先在用户空间中实现浮点数的运算,然后再取整转换为u8后把参数通过ioctl传入驱动程序。后来我也是这样做的。

二、解决此问题的一些尝试

1.删除该编译器选项

先在编译时打印一下编译的过程信息,看看是否有这个编译器选项:

在这里插入图片描述
确实是在编译的时候加了这个编译器选项,因为编译的时候传入的参数是ARCH:=arm64,所以去arm64下的Makefile中搜索一下这个编译器选项,看一下是在哪里添加的,搜索结果如下:

在这里插入图片描述

KBUILD_CFLAGS是linux源码目录顶层Makefile中定义的变量,这篇博客梳理了这个变量的用法,这个变量就是最终传给编译器的编译器选项。

既然知道了这个变量的用发,那么如果想在linux驱动中使用浮点运算,按说去掉这个编译器选项就可以了。去掉之后再次编译,可以正常编译,运行结果如下:
在这里插入图片描述
但是既然大佬们都建议非必要不要在驱动程序中进行浮点运算,所以这种方法尽量不要采用。

2. 在用户空间程序中进行浮点运算

建议采用这种方法。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值