网易首页 > 网易号 > 正文 申请入驻

抽丝剥茧 Linux 浮点运算的原理 | Linux 中国

0
分享至

  导读:最近我们有一个需求,需要把用户态的浮点数运算全部放到内核态运行,以提高运行速度,移植的过程中发现问题没有这么简单,然后我们抽丝剥茧,揭开 Linux 对浮点处理的原理。

  本文字数:5233,阅读时长大约: 6分钟

  作者:李季, 李幸福

编者按:本文来自华辰连科技术团队,分享了他们在将浮点运算放到内核态时的探索。

  最近我们有一个需求,需要把用户态的浮点数运算全部放到内核态运行,以提高运行速度,移植的过程中发现问题没有这么简单,然后我们抽丝剥茧,揭开 Linux 对浮点处理的原理。

  此文章的代码基于 x86 64 位 CPU,Linux 4.14 内核。

  一、 Linux 内核添加浮点运算出现的问题

  我们以一个简单的浮点运算例子来说明:

  1.   #include

  2.   #include

  3.   #include

  4.   #include

  5.   #include

  6.   static noinline double float_divide(double float1, double float2)

  7.   {

  8.   return float1 / float2;

  9.   }

  10.   static int __init test_float_init(void)

  11.   {

  12.   double result, float1 = 4.9, float2 = 0.49;

  13.   result = float_divide(float1, float2);

  14.   printk("result = %d\n", (int)result);

  15.   return 0;

  16.   }

  17.   static void __exit test_float_exit(void)

  18.   {

  19.   ;

  20.   }

  21.   module_init(test_float_init);

  22.   module_exit(test_float_exit);

  23.   MODULE_LICENSE("GPL");

  test_float.c

  1.   obj-m := test_float.o

  2.   KDIR := /lib/modules/$(shell uname -r)/build

  3.   all:

  4.   make -C $(KDIR) M=$(PWD) modules

  Makefile

  这个内核模块就是计算了两个浮点数除的结果,然后将结果打印出来 。但是我们执行 make 编译的时候发现报错:

  提示 SSE 寄存器返回的报错信息为 “SSE disabled”。我们执行 make V=1 查看关键的编译信息:

  我们发现在 gcc 的参数中有 -mno-sse -mno-mmx -mno-sse2 选项,原来 gcc 默认的编译选项禁用了 sse、mmx、sse2 等浮点运算指令。

  二、通过添加 gcc 编译参数和 kernel_fpu_begin/kernel_fpu_end 来解决问题

  为了让内核支持浮点运算,我们在 Makefile 中添加支持 sse 等选项,源码中添加 kernel_fpu_begin/kernel_fpu_end 函数,修改后的源码如下所示:

  1.   #include

  2.   #include

  3.   #include

  4.   #include

  5.   #include

  6.   static noinline double float_divide(double float1, double float2)

  7.   {

  8.   return float1 / float2;

  9.   }

  10.   static int __init test_float_init(void)

  11.   {

  12.   double result, float1 = 4.9, float2 = 0.49;

  13.   kernel_fpu_begin();

  14.   result = float_divide(float1, float2);

  15.   kernel_fpu_end();

  16.   printk("result = %d\n", (int)result);

  17.   return 0;

  18.   }

  19.   static void __exit test_float_exit(void)

  20.   {

  21.   ;

  22.   }

  23.   module_init(test_float_init);

  24.   module_exit(test_float_exit);

  25.   MODULE_LICENSE("GPL");

  test_float.c

  1.   obj-m := test_float.o

  2.   KDIR := /lib/modules/$(shell uname -r)/build

  3.   FPU_CFLAGS += -mhard-float

  4.   FPU_CFLAGS += -msse -msse2

  5.   CFLAGS_test_float.o += $(FPU_CFLAGS)

  6.   all:

  7.   make -C $(KDIR) M=$(PWD) modules

  Makefile

  此时执行 make,发现编译正确通过了:

  然后 insmod test_float.ko,观察 dmesg 的输出:

  从上面的例子,结合内核源码中 arch/x86/Makefile 中的 KBUILD_CFLAGS,可以看到编译内核及内核模块时,gcc 选项继承 Linux 中的规则,指定了 -mno-sse -mno-mmx -mno-sse2,也就是禁用了 FPU 。所以,要想内核模组支持浮点运算,编译选项需要显示的指定 -msse -msse2

  三、 Linux 内核态对浮点运算处理方式的分析

  从上面可以看到,我们为了实现一个内核模块的浮点运算,添加了编译参数 -mhard-float和-msse -msse2,对于编译参数来说,-mhard-float 是告诉编译器直接生成浮点运算的指令,而 -msse -msse2 则是告诉编译器可以使用 sse/sse2 指令集来编译代码。

  kernel_fpu_beginkernel_fpu_end 也是必须的,因为 Linux 内核为了提高系统的运行速率,在任务上下文切换时,只会保存/恢复普通寄存器的值,并不包括 FPU 浮点寄存器的值,而调用 kernel_fpu_begin 主要作用是关掉系统抢占,浮点计算结束后调用 kernel_fpu_end 开启系统抢占,这使得代码不会被中断,从而安全的进行浮点运算,并且要求这之间的代码不能有休眠或调度操作,另外不得有嵌套的情况出现(将会覆盖原始保存的状态,然后执行 kernel_fpu_end() 最终将恢复错误的 FPU 状态)。

  1.   void kernel_fpu_begin(void)

  2.   {

  3.   preempt_disable();

  4.   __kernel_fpu_begin();

  5.   }

  四、三角函数在 Linux 内核态的实现

  由于内核态不支持浮点运算,所以像三角函数之类浮点运算都没有实现,如果需要,可以将用户态 glibc 中相关的三角函数的实现移植到内核态。

  五、 Linux 用户态对浮点运算处理方式的分析

  为什么用户态浮点运算就不需要指定编译选项以及显示调用 kernel_fpu_beginkernel_fpu_end 函数呢?我们在用户状态下写一个简单的带浮点运算的例子:

  1.   #include

  2.   int main(int argc, char **argv)

  3.   {

  4.   int result, float1=4.9, float2=0.49;

  5.   result = float1 / float2;

  6.   printf("result = %d\n", result);

  7.   return 0;

  8.   }

  user_float.c

  我们分别使用下面四条编译指令查看编译出来的汇编:

  1. gcc -S user_float.c

  2. gcc -S user_float.c -msoft-float

  3. gcc -S user_float.c -mhard-float

  4. gcc -S user_float.c -msoft-float -mno-sse -mno-mmx -mno-sse2

  前三条命令编译成功。依次查看编译生成的汇编代码,发现生成的汇编代码是完全一样的,都是用到了 sse 指令中的 mmx 寄存器,也就是使用到了 FPU。

  第四条命令编译失败 ,提示 error: SSE register return with SSE disabled。从上面的现象中我们可以得出结论,系统默认使用 gcc 编译用户态程序时,gcc 默认使用 FPU,也就是使用硬浮点来编译。

  经过查阅各种文档和分析代码,x86 CPU 提供如下特性:CPU 提供的 TS 寄存器的第三个位是任务已切换标志(Task Switched bit),CPU 在每次任务切换时会设置这个位。而且 TS 的这个位被设置时,当进程使用 FPU 指令时 CPU 会产生一个 DNA(Device Not Availabel)异常。Linux 使用此特性,当用户态应用程序进行浮点运算时(SSE 等指令),触发 DNA 异常,同时使用 FPU 专用寄存器和指令来执行浮点数功能,此时 TS_USEDFPU 标志为 1,表示用户态进程使用了 FPU。

  1.   void fpu__restore(struct fpu *fpu)

  2.   {

  3.   fpu__initialize(fpu);

  4.   /* Avoid __kernel_fpu_begin() right after fpregs_activate() */

  5.   kernel_fpu_disable();

  6.   trace_x86_fpu_before_restore(fpu);

  7.   fpregs_activate(fpu);

  8.   copy_kernel_to_fpregs(&fpu->state);

  9.   trace_x86_fpu_after_restore(fpu);

  10.   kernel_fpu_enable();

  11.   }

  12.   EXPORT_SYMBOL_GPL(fpu__restore);

  假设用户态进程 A 使用到了 FPU 执行浮点运算,此时用户态进程 B 被调度执行,那么当进程 A 被调度出去的时候,内核设置 TS 并调用 fpu__restore 将 FPU 的内容保存。当进程 A 恢复浮点运算执行时,触发 DNA 异常,相应的异常处理程序会恢复 FPU 之前保存的状态。

  假设用户态进程 A 使用到了 FPU 执行浮点运算(TS_USEDFPU 标志为 1),此时内核态进程 C 调度并使用 FPU,由于内核只会保存普通的寄存器的值,并不包括 FP 等寄存器的值,所以内核会主动调用 kernel_fpu_begin 函数保存寄存器内容,使用完之后调用 kernel_fpu_end。当用户态进程 A 恢复浮点运算执行时,触发 DNA 异常,相应的异常处理程序会恢复 FPU 寄存器的内容。

  六、 结论

  1. Linux 中当任务切换时,缺省不保存浮点器寄存器。

  2. 如果需要内核态支持浮点运算,需要增加支持浮点的编译选项和使用 kernel_fpu_beginkernel_fpu_end 函数手动处理上下文。

  3. 用户态缺省支持浮点运算,但是需要内核来辅助。

  欢迎遵照 CC-BY-NC-SA 协议规定转载,

  如需转载,请在文章下留言 “ 转载:公众号名称”,

  我们将为您添加白名单,授权“ 转载文章时可以修改”。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
老百姓突然发现现在这个社会不缺大学生不缺人才,缺的是学费!

老百姓突然发现现在这个社会不缺大学生不缺人才,缺的是学费!

逍遥论经
2024-12-31 09:26:29
太激烈!梁靖崑林昀儒苦战五局,3-2艰难战胜薛飞向鹏先拔头筹

太激烈!梁靖崑林昀儒苦战五局,3-2艰难战胜薛飞向鹏先拔头筹

杨哥乒乓
2025-01-01 20:01:43
为什么日本现任首相石破茂一定会改写中日和世界历史?

为什么日本现任首相石破茂一定会改写中日和世界历史?

阿离家居
2025-01-01 00:20:54
6年烧光270亿,1人负债750亿!“山寨神车”跌落神坛,彻底凉了?

6年烧光270亿,1人负债750亿!“山寨神车”跌落神坛,彻底凉了?

凉羽亭
2024-12-30 16:49:25
年进账1400亿!河北小伙靠扛煤气罐发家,身价560亿元,登顶首富

年进账1400亿!河北小伙靠扛煤气罐发家,身价560亿元,登顶首富

毒sir财经
2024-12-29 16:17:06
中俄资产全被冻结!普京彻底不忍了,危急时刻,一重磅消息传出

中俄资产全被冻结!普京彻底不忍了,危急时刻,一重磅消息传出

听风者说
2024-12-31 22:06:03
现在是1月1号夜晚,刚刚曝出1个惊天大消息,要来大动作了吗?

现在是1月1号夜晚,刚刚曝出1个惊天大消息,要来大动作了吗?

股市皆大事
2025-01-01 16:56:46
新年第一天!朱立伦定调2025,抛出“新两岸论述”,风向真变了!

新年第一天!朱立伦定调2025,抛出“新两岸论述”,风向真变了!

手工制作阿歼
2025-01-01 13:32:01
NASA被中国新飞机不断暴击,连夜放出最新超音速客机视频大肆吹嘘

NASA被中国新飞机不断暴击,连夜放出最新超音速客机视频大肆吹嘘

凯撒谈兵
2025-01-01 07:51:29
1990年成龙去北京拍戏,被房产中介忽悠,花了400万购买8套四合院

1990年成龙去北京拍戏,被房产中介忽悠,花了400万购买8套四合院

玲子日记
2024-12-25 15:28:32
刚宣布:今年,入市!

刚宣布:今年,入市!

中国基金报
2025-01-01 20:14:23
紧急通知!福建全省今晚严查!

紧急通知!福建全省今晚严查!

荷兰豆爱健康
2025-01-01 16:21:28
不知火舞加入《街霸6》被要求穿上丝袜,老一辈的玩家怒了

不知火舞加入《街霸6》被要求穿上丝袜,老一辈的玩家怒了

街机时代
2025-01-01 18:00:02
小区里越来越常见的“自助水站”千万不要随便喝!

小区里越来越常见的“自助水站”千万不要随便喝!

小鹿姐姐情感说
2025-01-01 11:26:04
侯耀华谈何云伟:我说收小伟是在台上,没收成我们爷俩也没有隔阂

侯耀华谈何云伟:我说收小伟是在台上,没收成我们爷俩也没有隔阂

天天热点见闻
2025-01-01 13:51:49
建国后剿匪200万亡魂背后的血泪史,真相远比你想象残酷

建国后剿匪200万亡魂背后的血泪史,真相远比你想象残酷

小米亚的故事
2024-12-31 14:37:15
证监会无法阻挡砸盘!A股跳水,今日凌晨重要消息全面袭来(1.1)

证监会无法阻挡砸盘!A股跳水,今日凌晨重要消息全面袭来(1.1)

风口招财猪
2025-01-01 02:06:43
弹尽粮绝,辽宁男篮遭北京双杀,杨鸣末节提前练兵,难阻27分奇兵

弹尽粮绝,辽宁男篮遭北京双杀,杨鸣末节提前练兵,难阻27分奇兵

替补席看球
2025-01-01 21:44:14
国务院国资委:聚焦航空航天、集成电路、工业母机、生物技术等领域迫切需求 扎实推进重大技术装备攻关工程

国务院国资委:聚焦航空航天、集成电路、工业母机、生物技术等领域迫切需求 扎实推进重大技术装备攻关工程

澎湃新闻
2025-01-01 11:46:03
雷霆逆转森林狼12连胜西部第一 亚历山大40分本季总分破千首人

雷霆逆转森林狼12连胜西部第一 亚历山大40分本季总分破千首人

醉卧浮生
2025-01-01 11:20:32
2025-01-01 22:04:49
Linux incentive-icons
Linux
Linux 中国开源社区
8016文章数 73118关注度
往期回顾 全部

科技要闻

2024年成绩单!4家车企完成全年销量目标

头条要闻

柯文哲等4人交保被撤销 台北地方法院将再度传唤

头条要闻

柯文哲等4人交保被撤销 台北地方法院将再度传唤

体育要闻

4年3200万的湖人太子,血脉觉醒了?

娱乐要闻

赵露思自曝病情瘦到72斤,长文回应

财经要闻

2024A股收官 94只牛股股价涨幅超100%

汽车要闻

10万元级无图智驾 悦也PLUS全路况实测

态度原创

手机
本地
时尚
家居
公开课

手机要闻

Google Pixel 成为 2025 年澳大利亚网球公开赛官方指定智能手机

本地新闻

我和我的家乡|这个冬天,查干湖“鱼”你有约

伊姐祝大家元旦快乐!

家居要闻

紫云府居 隐入尘烟

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版