自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

抽絲剝繭 Linux 浮點運算的原理

系統(tǒng) Linux
最近我們有一個需求,需要把用戶態(tài)的浮點數(shù)運算全部放到內(nèi)核態(tài)運行,以提高運行速度,移植的過程中發(fā)現(xiàn)問題沒有這么簡單,然后我們抽絲剝繭,揭開 Linux 對浮點處理的原理。

[[405358]]

編者按:本文來自華辰連科技術(shù)團隊,分享了他們在將浮點運算放到內(nèi)核態(tài)時的探索。

最近我們有一個需求,需要把用戶態(tài)的浮點數(shù)運算全部放到內(nèi)核態(tài)運行,以提高運行速度,移植的過程中發(fā)現(xiàn)問題沒有這么簡單,然后我們抽絲剝繭,揭開 Linux 對浮點處理的原理。

此文章的代碼基于 x86 64 位 CPU,Linux 4.14 內(nèi)核。 

一、 Linux 內(nèi)核添加浮點運算出現(xiàn)的問題

我們以一個簡單的浮點運算例子來說明:

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/debugfs.h>
  4. #include <asm/fpu/api.h>
  5. #include <linux/delay.h>
  6.  
  7. static noinline double float_divide(double float1, double float2)
  8. {
  9.     return float1 / float2;
  10. }
  11.  
  12. static int __init test_float_init(void)
  13. {
  14.   double result, float1 = 4.9, float2 = 0.49;
  15.   result = float_divide(float1, float2);
  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. all:
  4. make -C $(KDIR) M=$(PWD) modules

Makefile

這個內(nèi)核模塊就是計算了兩個浮點數(shù)除的結(jié)果,然后將結(jié)果打印出來 。但是我們執(zhí)行 make 編譯的時候發(fā)現(xiàn)報錯:

 

提示 SSE 寄存器返回的報錯信息為 “SSE disabled”。我們執(zhí)行 make V=1 查看關(guān)鍵的編譯信息:

 

我們發(fā)現(xiàn)在 gcc 的參數(shù)中有 -mno-sse -mno-mmx -mno-sse2 選項,原來 gcc 默認(rèn)的編譯選項禁用了 sse、mmx、sse2 等浮點運算指令。

二、通過添加 gcc 編譯參數(shù)和 kernel_fpu_begin/kernel_fpu_end 來解決問題

為了讓內(nèi)核支持浮點運算,我們在 Makefile 中添加支持 sse 等選項,源碼中添加 kernel_fpu_begin/kernel_fpu_end 函數(shù),修改后的源碼如下所示:

  1. #include <linux/module.h>
  2. #include <linux/kernel.h>
  3. #include <linux/debugfs.h>
  4. #include <asm/fpu/api.h>
  5. #include <linux/delay.h>
  6.  
  7. static noinline double float_divide(double float1, double float2)
  8. {
  9.     return float1 / float2;
  10. }
  11.  
  12. static int __init test_float_init(void)
  13. {
  14.   double result, float1 = 4.9, float2 = 0.49;
  15.   kernel_fpu_begin();
  16.   result = float_divide(float1, float2);
  17.   kernel_fpu_end();
  18.   printk("result = %d\n", (int)result);
  19.   return 0;
  20. }
  21. static void __exit test_float_exit(void)
  22. {
  23.   ;
  24. }
  25. module_init(test_float_init);
  26. module_exit(test_float_exit);
  27. 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

此時執(zhí)行 make,發(fā)現(xiàn)編譯正確通過了:

 

然后 insmod test_float.ko,觀察 dmesg 的輸出:

 

從上面的例子,結(jié)合內(nèi)核源碼中 arch/x86/Makefile 中的 KBUILD_CFLAGS,可以看到編譯內(nèi)核及內(nèi)核模塊時,gcc 選項繼承 Linux 中的規(guī)則,指定了 -mno-sse -mno-mmx -mno-sse2,也就是禁用了 FPU 。所以,要想內(nèi)核模組支持浮點運算,編譯選項需要顯式的指定 -msse -msse2。

三、 Linux 內(nèi)核態(tài)對浮點運算處理方式的分析

從上面可以看到,我們?yōu)榱藢崿F(xiàn)一個內(nèi)核模塊的浮點運算,添加了編譯參數(shù) -mhard-float和-msse -msse2,對于編譯參數(shù)來說,-mhard-float 是告訴編譯器直接生成浮點運算的指令,而 -msse -msse2 則是告訴編譯器可以使用 sse/sse2 指令集來編譯代碼。

kernel_fpu_beginkernel_fpu_end 也是必須的,因為 Linux 內(nèi)核為了提高系統(tǒng)的運行速率,在任務(wù)上下文切換時,只會保存/恢復(fù)普通寄存器的值,并不包括 FPU 浮點寄存器的值,而調(diào)用 kernel_fpu_begin 主要作用是關(guān)掉系統(tǒng)搶占,浮點計算結(jié)束后調(diào)用 kernel_fpu_end 開啟系統(tǒng)搶占,這使得代碼不會被中斷,從而安全的進行浮點運算,并且要求這之間的代碼不能有休眠或調(diào)度操作,另外不得有嵌套的情況出現(xiàn)(將會覆蓋原始保存的狀態(tài),然后執(zhí)行 kernel_fpu_end() 最終將恢復(fù)錯誤的 FPU 狀態(tài))。

  1. void kernel_fpu_begin(void)
  2. {
  3. preempt_disable();
  4.   __kernel_fpu_begin();
  5. }

四、三角函數(shù)在 Linux 內(nèi)核態(tài)的實現(xiàn)

由于內(nèi)核態(tài)不支持浮點運算,所以像三角函數(shù)之類浮點運算都沒有實現(xiàn),如果需要,可以將用戶態(tài) glibc 中相關(guān)的三角函數(shù)的實現(xiàn)移植到內(nèi)核態(tài)。

五、 Linux 用戶態(tài)對浮點運算處理方式的分析

為什么用戶態(tài)浮點運算就不需要指定編譯選項以及顯式調(diào)用 kernel_fpu_beginkernel_fpu_end 函數(shù)呢?我們在用戶態(tài)下寫一個簡單的帶浮點運算的例子:

  1. #include <stdio.h>
  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

前三條命令編譯成功。依次查看編譯生成的匯編代碼,發(fā)現(xiàn)生成的匯編代碼是完全一樣的,都是用到了 sse 指令中的 mmx 寄存器,也就是使用到了 FPU。

第四條命令編譯失敗 ,提示 error: SSE register return with SSE disabled。從上面的現(xiàn)象中我們可以得出結(jié)論,系統(tǒng)默認(rèn)使用 gcc 編譯用戶態(tài)程序時,gcc 默認(rèn)使用 FPU,也就是使用硬浮點來編譯。

經(jīng)過查閱各種文檔和分析代碼,x86 CPU 提供如下特性:CPU 提供的 TS 寄存器的第三個位是任務(wù)已切換標(biāo)志Task Switched bit,CPU 在每次任務(wù)切換時會設(shè)置這個位。而且 TS 的這個位被設(shè)置時,當(dāng)進程使用 FPU 指令時 CPU 會產(chǎn)生一個 DNA(Device Not Availabel)異常。Linux 使用此特性,當(dāng)用戶態(tài)應(yīng)用程序進行浮點運算時(SSE 等指令),觸發(fā) DNA 異常,同時使用 FPU 專用寄存器和指令來執(zhí)行浮點數(shù)功能,此時 TS_USEDFPU 標(biāo)志為 1,表示用戶態(tài)進程使用了 FPU。

  1. void fpu__restore(struct fpu *fpu)
  2. {
  3.   fpu__initialize(fpu);
  4.  
  5.   /* Avoid __kernel_fpu_begin() right after fpregs_activate() */
  6.   kernel_fpu_disable();
  7.   trace_x86_fpu_before_restore(fpu);
  8.   fpregs_activate(fpu);
  9.   copy_kernel_to_fpregs(&fpu->state);
  10.   trace_x86_fpu_after_restore(fpu);
  11.   kernel_fpu_enable();
  12. }
  13. EXPORT_SYMBOL_GPL(fpu__restore);

假設(shè)用戶態(tài)進程 A 使用到了 FPU 執(zhí)行浮點運算,此時用戶態(tài)進程 B 被調(diào)度執(zhí)行,那么當(dāng)進程 A 被調(diào)度出去的時候,內(nèi)核設(shè)置 TS 并調(diào)用 fpu__restore 將 FPU 的內(nèi)容保存。當(dāng)進程 A 恢復(fù)浮點運算執(zhí)行時,觸發(fā) DNA 異常,相應(yīng)的異常處理程序會恢復(fù) FPU 之前保存的狀態(tài)。

假設(shè)用戶態(tài)進程 A 使用到了 FPU 執(zhí)行浮點運算(TS_USEDFPU 標(biāo)志為 1),此時內(nèi)核態(tài)進程 C 調(diào)度并使用 FPU,由于內(nèi)核只會保存普通的寄存器的值,并不包括 FP 等寄存器的值,所以內(nèi)核會主動調(diào)用 kernel_fpu_begin 函數(shù)保存寄存器內(nèi)容,使用完之后調(diào)用 kernel_fpu_end。當(dāng)用戶態(tài)進程 A 恢復(fù)浮點運算執(zhí)行時,觸發(fā) DNA 異常,相應(yīng)的異常處理程序會恢復(fù) FPU 寄存器的內(nèi)容。

六、 結(jié)論

  1. Linux 中當(dāng)任務(wù)切換時,缺省不保存浮點器寄存器。
  2. 如果需要內(nèi)核態(tài)支持浮點運算,需要增加支持浮點的編譯選項和使用 kernel_fpu_beginkernel_fpu_end 函數(shù)手動處理上下文。
  3. 用戶態(tài)缺省支持浮點運算,但是需要內(nèi)核來輔助。

 

責(zé)任編輯:龐桂玉 來源: Linux中國
相關(guān)推薦

2024-04-01 00:07:20

LinuxeBPF源碼

2021-06-16 07:56:21

Redis分布式

2022-07-11 11:28:45

數(shù)據(jù)分析業(yè)務(wù)消費

2022-07-05 21:31:21

索引SQL分庫分表

2015-06-09 11:13:18

2022-01-17 17:55:29

Python變量交換開發(fā)

2020-05-06 08:01:39

黑客惡意攻擊網(wǎng)絡(luò)安全

2021-04-19 11:07:13

Windbg程序.NET

2019-05-14 14:51:40

Java語法糖用法

2019-05-23 11:42:04

Java語法糖編程語言

2018-09-13 15:21:36

CTO訓(xùn)練營

2024-01-03 16:39:07

2018-03-19 10:39:28

Java序列化對象

2019-12-10 15:30:27

SaaSIaaS云計算

2022-02-22 07:40:10

邊緣計算云原生中心云

2022-04-01 15:18:42

Web 框架網(wǎng)絡(luò)通信

2014-09-17 09:55:09

頑固漏洞遺留代碼應(yīng)用開發(fā)

2020-06-09 15:15:31

運維中臺技術(shù)

2017-09-15 09:18:27

JavaSQLDBA

2015-12-28 16:09:20

物聯(lián)網(wǎng)市場
點贊
收藏

51CTO技術(shù)棧公眾號