基于编译型语言开发的应用程序,其编译后得到可执行程序,可执行程序执行时依赖的指令是芯片架构相关的。因此,软件迁移后鲲鹏架构上的代码和x86架构的代码会不一致,代码归一需要使用合适的措施隔离不同架构代码,本节以编译型语言代表C/C++语言为例,分别从代码片段和文件两个方面说明归一方法。
针对同一个文件内函数级别不同架构的代码,一般采用编译宏来区分隔离,编译宏可以使用GCC编译器自带的,也可以用自定义的编译宏。
处理步骤:
示例:
下面示例代码是CRC指令在x86和鲲鹏平台下的不同实现方法,可以通过编译宏控制来进行区分隔离,代码可以放到同一个文件中,实现代码归一。
#ifdef __x86_64__ static inline uint32_t crc32_u8(uint32_t crc, uint8_t v) { __asm__("crc32b %1, %0" : "+r"(crc) : "rm"(v)); return crc; } static inline uint32_t crc32_u16(uint32_t crc, uint16_t v) { __asm__("crc32w %1, %0" : "+r"(crc) : "rm"(v)); return crc; } static inline uint32_t crc32_u32(uint32_t crc, uint32_t v) { __asm__("crc32l %1, %0" : "+r"(crc) : "rm"(v)); return crc; } static inline uint32_t crc32_u64(uint32_t crc, uint64_t v) { uint64_t result = crc; __asm__("crc32q %1, %0" : "+r"(result) : "rm"(v)); return result; } #endif
#ifdef __aarch64__ static inline uint32_t crc32_u8(uint32_t crc, uint8_t value) { __asm__("crc32cb %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value)); return crc; } static inline uint32_t crc32_u16(uint32_t crc, uint16_t value) { __asm__("crc32ch %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value)); return crc; } static inline uint32_t crc32_u32(uint32_t crc, uint32_t value) { __asm__("crc32cw %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value)); return crc; } static inline uint32_t crc32_u64(uint32_t crc, uint64_t value) { __asm__("crc32cx %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value)); return crc; } #endif
C/C++语言中通过条件编译#ifdef,#endif等关键词实现将代码块编译区分,示例代码中采用了__x86_64__宏控制x86环境下运行的代码,采用了__aarch64__宏控制ARM环境下运行的代码,这两个宏是GCC预定义好的通用宏,GCC根据编译所在服务器的架构自行激活对应的宏,用户无需定义,使用比较方便。
各编译器支持用户自定义的宏,通过编译命令行带入传递到代码编译中,如,GCC通过添加 -DDEFINES或 -DDEFINES=CONDITION参数来定义用户的宏。
自定义宏和代码应用示例:
#include <stdio.h> int main(void) { #ifdef MY_X86_MAC printf("Running in x86 model. \n"); #endif #if MY_AARCH64_MAC printf("Running in Kunpeng model. \n"); #endif return 0; }
将上述示例代码保存到test.c文件中,MY_X86_MAC和MY_AARCH64_MAC分别是自定义的控制x86和鲲鹏代码块的宏,利用GCC编译定义不同的宏参数,可以实现不懂架构代码段:
执行gcc -DMY_X86_MAC test.c -o demo1编译后,运行demo1程序会显示:
Running in x86 model.
执行gcc -DMY_AARCH64_MAC test.c -o demo2编译后,运行demo2程序会显示:
Running in Kunpeng model.
至此,无论是GCC自带的宏还是自定义的编译宏,都可以实现通过编译宏方式对不同架构代码片段进行隔离,放到同一个代码文件中实现归一。
如果业务代码中涉及不同架构的代码较多,使用代码片段隔离方式操作繁琐,易读性差,推荐将统一架构相关的代码放到同一个文件中,使用文件隔离的方式并采用makefile分文件编译方式实现代码归一。
处理步骤:
根据业务代码逻辑,将同一架构相关的代码放到同一个文件中,并做好目录和命名区分文件,修改makefile文件实现不同架构下编译对应的依赖文件实现归一。makefile可设置多个伪目标,不同目标实现对不同平台架构代码文件的编译,调用者只需要执行不同的make命令参数即可。更进一步通过脚本判断编译服务器架构类型,自动完成响应的编译调用,无需用户去判断平台架构。
示例:
假如两个代码文件src_x86.c、src_aarch64.c分别存储了x86架构和鲲鹏架构下的源代码,下面makefile文件示例可以区分编译不同的c文件代码。
通过伪目标区分不同架构编译makefile代码片段示例:
# other codes .PHONY x86 aarch64 x86:src_x86.c gcc c1.c -o test arm:src_aarch64.c gcc src_aarch64.c -o test # other codes
这是一个简单的示例,运行make x86 编译器会对src_x86.c进行编译,运行make aarch64编译器对src_aarch64.c进行编译,都会生成test可执行文件。示例代码阐述了核心思想,实际项目中可以增加更多的源文件或者其他选项。
可以通过脚本自动识别芯片架构类型并执行不同的编译命令,高效不易出错,如下脚本示例可以完成自动判断编译:
... # 获取芯片类型 ARCH_TYPE=`lscpu | grep Archit | awk '{print $2}'` echo ${ARCH_TYPE} # 如果是鲲鹏架构执行make aarch64编译 if [ "$ARCH_TYPE" = "aarch64" ];then make aarch64 fi # 如果是x86架构执行make x86 编译 if [ "$ARCH_TYPE" = "x86_64" ];then make x86 fi ... }
将上述代码保存到build.sh文件中,执行sh build.sh自动完成编译,用户不感知芯片架构对应编译代码的差异。
源代码分文件存放,makefile中根据芯片类型区别编译的方法,保持很好的代码可维护性,是同一套代码适配多种平台的较为通用的方法之一。