- 鯤鵬架構入門與實戰
- 張磊編著
- 5621字
- 2022-07-29 14:17:50
5.2.3 移植常見問題
在進行實際的代碼移植過程中,因為環境的多樣性,會遇到多種問題,下面按照源碼修改和嵌入式匯編兩個類別,分別對可能出現的問題進行分析并給出建議的解決方法,需要特別注意的是,給出的解決方法僅供參考,本書不對實際的使用作任何擔保。
1.源碼修改類問題
1)代碼中匯編指令需要重寫
■ 現象描述:
ARM的匯編語言與x86完全不同,需要重寫,涉及使用嵌入匯編的代碼,都需要針對ARM進行配套修改。
■ 處理步驟:
需要重新實現匯編代碼段。
■ 示例:
在x86架構下,示例代碼如下:

在鯤鵬平臺下,使用gcc內置函數實現,示例代碼如下:

以__sync_add_and_fetch為例,編譯后其反匯編對應代碼如下:
<__sync_add_and_fetch>: ldxr x2, [x0] add x2, x2, x1 stlxr w3, x2, [x0]
2)快速移植內聯SSE/SSE2應用
■ 現象描述:
部分應用采用了gcc封裝的用SSE/SSE2實現的函數,但是gcc目前沒有提供對應的鯤鵬平臺版本,需要實現對應函數。
■ 處理步驟:
目前已有開源代碼實現了部分鯤鵬平臺的函數,代碼下載網址:https://GitHub.com/open-estuary/sse2neon.git,使用方法如下:
步驟1:將已下載項目中的SSE2NEON.h文件復制到待移植項目中。
步驟2:在源文件中刪除如下代碼:
#include <xmmintrin.h> #include <emmintrin.h>
步驟3:在源代碼中包含頭文件SSE2NEON.h。
3)對結構體中的變量進行原子操作時程序異常coredump
■ 現象描述:
程序調用原子操作函數對結構體中的變量進行原子操作,程序coredump,堆棧如下:

■ 問題原因:
鯤鵬平臺對變量的原子操作、鎖操作等用到了ldaxr、stlxr等指令,這些指令要求變量地址必須按變量長度對齊,否則執行指令會觸發異常,導致程序coredump。一般是因為代碼中對結構體進行強制字節對齊,導致變量地址不在對齊位置上,對這些變量進行原子操作、鎖操作等會觸發問題。
■ 處理步驟:
代碼中搜索#pragmapack關鍵字(該宏改變了編譯器默認的對齊方式),找到使用了字節對齊的結構體,如果結構體中變量會被作為原子操作、自旋鎖、互斥鎖、信號量、讀寫鎖的輸入參數,則需要修改代碼保證這些變量按變量長度對齊。
4)核數目硬編碼
■ 問題原因:
鯤鵬服務器相對于x86服務器,CPU核數會有變化,如果模塊代碼針對處理器核數目硬編碼,則會造成無法充分利用系統能力的情況,例如CPU核的利用率差異大或者綁核出現跨numa的情況。
■ 處理步驟:
可以通過搜索代碼中的綁核接口(sched_setaffinity)來排查綁核的實現是否存在CPU核數硬編碼的情況。如果存在,則根據鯤鵬服務器實際核數進行修改,消除硬編碼,可通過接口sysconf(_SC_NPROCESSORS_CONF)獲取實際核數再進行綁核。
5)雙精度浮點型轉整型時數據溢出,與x86平臺表現不一致
■ 現象描述:
C/C++雙精度浮點型數轉整型數據時,如果超出了整型的取值范圍,鯤鵬平臺的表現與x86平臺的表現不同。

■ 問題原因:
在兩個平臺下,是兩套CPU架構,其中的算數邏輯單元的實現可能會有差異,操作系統、編譯器的實現都會有所不同。x86(指令集)中的浮點到整型的轉換指令,定義了一個indefinite integer value——“不確定數值”(64bit:0x8000000000000000),大多數情況下x86平臺確實都在遵循這個原則,但是在從double向無符號整型轉換時,又出現了不同的結果。鯤鵬的處理則非常清晰和簡單,在上溢出或下溢出時,保留整型能表示的最大值或最小值,開發者并不會面對不確定或無法預期的結果。
■ 處理步驟:
參考如下數據轉換的表格,調整代碼中的實現。
double型數據向long轉換,如表5-3所示。
表5-3 double型數據向long轉換

double型數據向unsigned long轉換,如表5-4所示。
表5-4 double型數據向unsigned long轉換

double型數據向int轉換,如表5-5所示。
表5-5 double型數據向int轉換

double型數據向unsigned int轉換,如表5-6所示。
表5-6 double型數據向unsigned int轉換

2.嵌入式匯編類問題
1)替換x86 pause匯編指令
■ 現象描述:
編譯報錯:Error:unknown mnemonic 'pause'--'pause'。
■ 問題原因:
pause指令給處理器提供提示,以提高spin-wait循環的性能,需替換為鯤鵬平臺的yield指令。
■ 處理步驟:
x86平臺實現樣例:
static inline void PauseCPU() { __asm__ __volatile__("pause"::: "memory"); }
鯤鵬平臺實現樣例:
static inline void PauseCPU() { __asm__ __volatile__("yield"::: "memory"); }
2)替換x86 pcmpestri匯編指令
■ 現象描述:
編譯報錯Error:unknown mnemonic 'pcmpestri-'-'pcmpestri'。
■ 問題原因:
與pcmpestrm指令類似,pcmpestri也是x86 SSE4指令集中的指令。根據指令介紹,其用途是根據指定的比較模式,判斷字符串str2的字節是否在str1中出現,返回匹配到的位置索引(首個匹配結果為0的位置)。同樣,對于該指令,需要徹底了解其功能,通過C代碼重新實現其功能。
指令介紹:
https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=SSE4_2&expand=834
https://docs.microsoft.com/zh-cn/previous-versions/visualstudio/visualstudio-2010/bb531465(v=vs.100)。
■ 處理步驟:
如下代碼段是Impala中對pcmpestri指令的調用,該調用參考Intel的_mm_cmpestri接口實現將pcmpestri指令封裝成SSE4_cmpestri,代碼如下:

從指令介紹中看,不同的模式所執行的操作差異較大,完全實現指令功能所需代碼行太多。結合代碼中對接口的調用,實際使用到的模式為PCMPSTR_EQUAL_EACH|PCMPSTR_UBYTE_OPS|PCMPSTR_NEG_POLARITY。即按照字節長度進行匹配,對str1與str2做對應位置字符是否相等判斷,若相等,則將對應bit位置置1,最后輸出首次出現1的位置。根據該思路進行代碼實現,代碼如下:

3)替換x86 movqu匯編指令
■ 現象描述:
編譯報錯:unknown mnemonic 'movqu'--'movqu'。
■ 問題原因:
movqu為x86指令集中的指令,在鯤鵬上無法使用。該指令可以實現寄存器到寄存器,寄存器到地址的數據復制。x86上movqu指令用法有兩種:
第一種是將xmm2寄存器或者128位內存地址的內容復制到xmm1寄存器,代碼如下:
MOVDQU xmm1, xmm2/m128
第二種是將xmm1寄存器的內容復制到128位內存地址或者xmm2寄存器,代碼如下:
MOVDQU xmm2/m128, xmm1
參考資料:https://x86.puri.sm/html/file_module_x86_id_184.html。
■ 處理步驟:
對于第一種調用,可以用NEON指令ld1替代:
ld1指令Load multiple 1-element structures to one,two,three or four registers。
LD1 {Vt.T}, [Xn|SP]
可參考指令集手冊的9.98節,下載網址:
http://infocenter.arm.com/help/topic/com.arm.doc.dui0802a/DUI0802A_armasm_reference_guide.pdf。
對于第二種調用,可以用st1指令來替代:
st1指令Store multiple 1-element structures from one,two three or four registers.
ST1 {Vt.T}, [Xn|SP]
可參考指令集手冊的9.202節,下載網址:
http://infocenter.arm.com/help/topic/com.arm.doc.dui0802a/DUI0802A_armasm_reference_guide.pdf。
以下是一個簡單的示例,代碼如下:

4)替換x86 pand匯編指令
■ 現象描述:
編譯報錯:unknown mnemonic 'pand'--'pand'。
■ 問題原因:
pand是x86指令集中的指令,無法在鯤鵬設備上使用。其功能是按位進行and運算,使用方法有兩種:
第一種用法是對寄存器xmm2或內存地址中128位內容與xmm1進行按位與運算,結果存放于xmm2中,指令用法如下:
PAND xmm1, xmm2/m128
第二種用法是對寄存器mm2或內存地址中64位內容與mm1進行按位與運算,結果存放于mm2中,指令用法如下:
PAND mm1, mm2/m64
指令使用方法參考:https://c9x.me/x86/html/file_module_x86_id_230.html。
■ 處理步驟:
對于以上兩種情況,在鯤鵬上均可以用NEON指令AND替換,采用64或者128位長度的向量寄存器存放數據,代碼如下:
AND Vd.<T>, Vn.<T>, Vm.<T>
Bitwise AND(vector).Where<T>is 8B or 16B(though an assembler shouldaccept any valid format)。
其中Vn、Vm為待操作的寄存器,Vd是目的寄存器,<T>即是選擇寄存器位數。
參考指令集手冊的9.7節,下載網址:http://infocenter.arm.com/help/topic/com.arm.doc.dui0802a/DUI0802A_armasm_reference_guide.pdf。
下面是一個簡單的使用NEON指令AND對數據進行按位與操作的過程,供參考,代碼如下:

5)替換x86 pxor匯編指令
■ 現象描述:
編譯報錯:unknown mnemonic 'pxor'--'pxor'。
■ 問題原因:
pxor是x86指令集中的指令,無法在鯤鵬設備上使用。其功能是按位進行xor運算,使用方法有兩種:
第一種用法是對寄存器xmm2或內存地址中128位內容與xmm1進行按位異或運算,結果存放于xmm1中,指令用法如下:
PXOR xmm1, xmm2/m128
第二種用法是對寄存器mm2或內存地址中64位內容與mm1進行按位異或運算,結果存放于mm1中,指令用法如下:
PXOR mm1, mm2/m64
指令用法參考網址:https://c9x.me/x86/html/file_module_x86_id_272.html。
■ 處理步驟:
對于以上兩種情況,在鯤鵬上均可以用NEON指令EOR替換,采用64或者128位長度的向量寄存器存放數據,代碼如下:
EOR Vd.<T>, Vn.<T>, Vm.<T>
Bitwise exclusive OR(vector).Where<T>is 8B or 16B(an assembler shouldaccept any valid arrangement)。其中Vn、Vm為待操作的寄存器,Vd是目的寄存器,<T>是選擇寄存器位數。參考指令集手冊的9.29節,下載網址為http://infocenter.arm.com/help/topic/com.arm.doc.dui0802a/DUI0802A_armasm_reference_guide.pdf。
下面是一個簡單的使用NEON指令EOR對數據進行按位異或操作的過程,參考代碼如下:

6)替換x86 pshufb指令
■ 現象描述:
編譯報錯:unknown mnemonic 'pshufb'--'pshufb'。
■ 問題原因:
pshufb(Packed Shuffle Bytes)指令的功能是根據第二個操作數指定的控制掩碼對第一個操作數執行散列操作,產生一個組合數。它是x86平臺的匯編指令,在鯤鵬平臺上需要進行替換。x86上的指令用法如下:
pshufb xmm1, xmm2/m128
■ 處理步驟:
pshufb指令對應的SSE intrinsic函數是_mm_shuffle_epi8,因此pshufb在鯤鵬上的替換可以分為兩步:
步驟1:將pshufb匯編指令替換成SSE intrinsic。
x86上實現樣例,代碼如下:
__asm__("pshufb %1, %0": "+x" (mmdesc): "xm" (shuf_mask));
在鯤鵬上先替換成SSE intrinsic函數,代碼如下:
_mm_shuffle_epi8(mmdesc, shuf_mask);
步驟2:移植內聯SSE函數_mm_shuffle_epi8。gcc目前沒有提供對應的鯤鵬平臺版本,因此需要實現對應函數,代碼如下:

7)替換x86 cpuid匯編指令
■ 現象描述:
編譯報錯:/tmp/ccfaVZfw.s:Assembler messages:/tmp/ccfa VZfw.s:34:Error:unknown mnemonic 'cpuid'--'cpuid'。
■ 問題原因:
cpuid是x86平臺上專有的獲取cpuid信息的匯編指令,在鯤鵬平臺上需要重寫。在鯤鵬平臺上,midr_el1寄存器里存放的是cpuid信息,可以通過讀寄存器獲取cpuid。
■ 處理步驟:
x86實現樣例,代碼如下:

midr_el1是64位寄存器,其中高32位為預留位,其值為0。讀出來是一個32位的值。鯤鵬平臺上可替換成的代碼如下:

8)替換x86 xchgl匯編指令
■ 現象描述:
編譯報錯:{standard input}:Assembler messages:{standard input}:1222:Error:unknown mnemonic 'xchgl'--'xchgl x1,[x19,112]'。
■ 問題原因:
xchgl是x86上的匯編指令,作用是交換寄存器/內存變量和寄存器的值,如果交換的兩個變量中有內存變量,則會對內存變量增加原子鎖操作。鯤鵬上可用GCC的原子操作接口__atomic_exchange_n替換。__atomic_exchange_n的第3個入參是內存屏障類型,使用者可以根據自身代碼邏輯選擇不同的屏障。當對多線程訪問臨界區的邏輯不清晰時,建議使用__ATOMIC_SEQ_CST屏障,避免由屏障使用不當帶來一致性問題。
■ 處理步驟:
x86實現樣例,代碼如下:

鯤鵬上可替換成的代碼如下:

9)替換x86 cmpxchgl匯編指令
■ 現象描述:
編譯報錯:{standard input}:Assembler messages:{standard input}:1222:Error:unknown mnemonic 'cmpxchgl'
■ 問題原因:
與xchgl類似,cmpxchgl是x86上的匯編指令,其作用是比較并交換操作數。鯤鵬上無對應指令,可用GCC的原子操作接口__atomic_compare_exchange_n進行替換。
■ 處理步驟:
x86實現樣例,代碼如下:

鯤鵬上可替換成的代碼如下:

10)替換x86 rep匯編指令
■ 現象描述:
編譯報錯:Error:unknown mnemonic 'rep'--r'ep'。
■ 問題原因:
rep為x86平臺的重復執行指令,需替換為鯤鵬平臺的rept指令。
■ 處理步驟:
修改方法參考如下:
x86實現樣例,代碼如下:
#define nop __asm__ __volatile__("rep;nop": : :"memory")
鯤鵬平臺實現樣例,本樣例實現空指令,參數n為循環次數,代碼如下:
#define __nops(n) ".rept " #n "\nnop\n.endr\n" #define nops(n) asm volatile(__nops(n))
11)替換x86 bswap匯編指令
■ 現象描述:
編譯報錯:Error:unknown mnemonic 'bswap'--'bswap x3'。
■ 問題原因:
bswap是x86平臺的字節序反序指令,需替換為鯤鵬平臺的rev指令。
■ 處理步驟:
在x86平臺下的實現,代碼如下:

在鯤鵬平臺下的實現,代碼如下:

12)替換x86 crc32匯編指令
■ 現象描述:
編譯錯誤:Error:unknown mnemonic c'rc32q'--c'rc32q(x3),x2'或operand 1should be an integer register--c'rc32b [sp,11],x0'或unrecognized command line option-'msse4.2'。
■ 問題原因:
x86平臺使用的是crc32b、crc32w、crc32l、crc32q匯編指令完成CRC32C校驗值計算
功能,而鯤鵬平臺使用crc32cb、crc32ch、crc32cw、crc32cx 4個匯編指令完成CRC32C校驗值計算功能。
■ 處理步驟:
使用crc32cb、crc32ch、crc32cw、crc32cx取代x86的CRC32系列匯編指令,替換方法如表5-7所示,并在編譯時添加編譯參數-march=armv8+crc。
表5-7 替換方法

■ 示例:
在x86平臺下的實現,代碼如下:

在鯤鵬平臺下的實現,代碼如下:

13)替換x86 rdtsc匯編指令
■ 現象描述:
編譯報錯:error:impossible constraint in a'sm'__asm__ __volatile__("rdtsc":"=a"(lo),"=d"(hi));
■ 問題原因:
TSC是時間戳計數器的縮寫,它是Pentium兼容處理器中的一個計數器,它記錄自啟動以來處理器消耗的時鐘周期數。在每個時鐘到來時,該計數器自動加1。因為TSC隨著處理器周期速率的變化而變化,所以它提供了非常高的精確度。它經常被用來分析和檢測代碼。x86平臺TSC的值可以通過rdtsc指令來讀取,而鯤鵬平臺需要使用類似算法實現。
■ 處理步驟:
x86平臺實現樣例,代碼如下:

鯤鵬平臺實現樣例:
方法一:使用Linux提供的獲取時間函數clock_gettime進行近似替換,代碼如下:

方法二:鯤鵬有Performance Monitors Control Register系列寄存器,其中PMCCNTR_EL0類似于x86的TSC寄存器。但默認情況下用戶態是不可讀的,需要內核態使能后才能讀取。具體可參考網址http://iLinux Kernel.com/?p=1755。
a.下載read aarch64 TSC(http://www.iLinux Kernel.com/files/aarch64_tsc.tar.bz2),解壓壓縮包,在aarch64_tsc目錄下執行make命令,安裝相應內核驅動,生成文件,生成文件中包括一個文件名為pmu.ko的文件。
b.執行insmod pmu.ko命令安裝內核模塊,使能內核態(初次執行即可)。
c.代碼替換。
示例代碼如下:

其中Cent Speed和External Clock的值可由以下命令獲取:
dmidecode |grep MHz
14)替換x86 popcntq匯編指令
■ 現象描述:
編譯報錯:Error:unknown mnemonic 'popcnt'--'popcnt [sp,8],x0'。
■ 問題原因:
popcnt為x86平臺的位1計數指令,鯤鵬平臺無對應指令,需使用替換算法實現。
■ 處理步驟:
x86平臺實現樣例,代碼如下:

鯤鵬平臺實現樣例,代碼如下:

15)替換x86 atomic原子操作函數
■ 現象描述:
部分應用會通過封裝匯編指令實現原子操作,如原子加及原子減。由于指令集差異,x86上所使用的原子操作指令在ARM平臺并不能保證原子性,因此需要進行相應替換。
①atomic_add指令
函數功能:對整數變量進行原子加。
處理步驟:
x86平臺實現樣例,代碼如下:

在鯤鵬上進行替換:
第1種方法:使用GCC自帶原子操作替換,代碼如下:

第2種方法:使用內聯匯編替換,代碼如下:

②atomic_sub指令
函數功能:對整數變量進行原子減。
處理步驟:
x86平臺實現樣例,代碼如下:

在鯤鵬上進行替換:
第1種方法:使用GCC自帶原子操作替換,代碼如下:

第2種方法:使用內聯匯編替換,代碼如下:

③atomic_dec_and_test指令
函數說明:對整數進行減操作,并判斷執行原子減后結果是否為0。
處理步驟:
x86平臺實現樣例,代碼如下:

在鯤鵬上進行替換:
第1種方法:使用GCC自帶原子操作函數替換,代碼如下:

第2種方法:使用內聯匯編替換,代碼如下:

④atomic_inc_and_test指令
函數說明:對整數進行加操作,并判斷返回結果是否為0。
處理步驟:
x86平臺實現樣例,代碼如下:

在鯤鵬上進行替換:
第1種方法:使用GCC自帶原子操作函數替換,代碼如下:

第2種方法:使用內聯匯編替換,代碼如下:

⑤atomic64_add_and_return指令
函數說明:對兩個長整數進行加操作,并將結果作為返回值返回。
處理步驟:
需要重新實現匯編代碼段。
在x86平臺實現樣例,代碼如下:

在鯤鵬平臺下,使用GCC內置函數實現,代碼如下:

16)替換x86 pcmpestrm匯編指令
■ 現象描述:
編譯報錯Error:unknown mnemonic 'pcmpestrm'--'pcmpestrm'。
■ 問題原因:
pcmpestrm指令是x86指令集中SSE4中的指令。根據指令介紹,其用途是根據指定的比較模式,判斷字符串str2的字節是否在字符串str1中出現,將每個字節的對比結果返回(最大長度為16字節)。該指令是典型的x86復雜指令,通過一條指令即可完成復雜的字符串匹配功能,鯤鵬架構中無類似實現。對于這種指令,需要徹底了解其功能,通過C代碼重新實現其功能。
指令介紹:
https://software.intel.com/sites/landingpage/IntrinsicsGuide/#techs=SSE4_2&expand=835。
https://docs.microsoft.com/zh-cn/previous-versions/visualstudio/visualstudio-2010/bb514080(v=vs.100)。
■ 處理步驟:
以下代碼段是Impala中對pcmpestrm指令的調用,該調用參考Intel的_mm_cmpestrm接口實現將pcmpestrm指令封裝成SSE4_cmpestrm,代碼如下:

從指令介紹中看,不同的模式所執行的操作差異較大,完全實現指令功能所需代碼行太多。結合代碼中對接口的調用,實際使用到的模式為PCMPSTR_EQUAL_ANY|PCMPSTR_UBYTE_OPS。即按照字節長度進行匹配,對比字符串str2中的每個字符是否在字符串str1中出現,若出現,則將對應bit位置置1。
根據識別到的功能進行代碼實現,代碼如下:

注意:無直接替代指令的場景,需要結合指令功能、所需功能共同分析,切忌生搬硬套直接代碼復制及替換。
注意:5.2.3節移植常見問題內容引用自華為《鯤鵬代碼遷移參考手冊》4.2節和4.3節,網址為http://ic-openlabs.huawei.com/chat/download/鯤鵬代碼遷移參考手冊.pdf。
- Embedded Linux Projects Using Yocto Project Cookbook
- Unity 2020 By Example
- 前端跨界開發指南:JavaScript工具庫原理解析與實戰
- PHP 7底層設計與源碼實現
- Data Analysis with IBM SPSS Statistics
- FFmpeg入門詳解:音視頻原理及應用
- Learning Python Design Patterns
- SQL 經典實例
- Learning VMware vSphere
- 石墨烯改性塑料
- Instant Automapper
- Penetration Testing with the Bash shell
- C語言程序設計
- 大學計算機應用基礎(Windows 7+Office 2010)(IC3)
- Python高性能編程(第2版)