8、系统名称与处理器:CMAKE_SYSTEM_NAME详解(Linux、Generic、Android等)、CMAKE_SYSTEM_PROCESSOR

好,咱们接着聊交叉编译。上一章我们把工具链文件的骨架搭起来了,但说实话,那个骨架里最核心的两个变量——CMAKE_SYSTEM_NAMECMAKE_SYSTEM_PROCESSOR——我还没细讲。这两个家伙,才是真正决定「你要编译成什么玩意儿」的关键。

我刚开始用 CMake 做交叉编译时,就吃过它们的亏。当时给一个 ARM Linux 板子写工具链,随手把 CMAKE_SYSTEM_NAME 写成了 Linux,结果 CMake 自作主张检测了一堆宿主机上的库路径,编译出来根本跑不了。后来才明白,这玩意儿不是随便填的。

CMAKE_SYSTEM_NAME:告诉 CMake 你在为谁编译

说白了,CMAKE_SYSTEM_NAME 就是告诉 CMake:「嘿,我现在要编译的目标系统是什么?」。默认情况下,如果你不设置它,CMake 会认为你在为本机编译,也就是 CMAKE_HOST_SYSTEM_NAME 的值。但一旦你显式设置了它,CMake 就会切换到交叉编译模式。

常用的值有哪些?我列一下:

适用场景 我踩过的坑
Linux 标准 Linux 系统(ARM、x86、RISC-V 等) 注意它会启用 find_library 搜索系统路径,容易找到宿主机库
Generic 裸机、RTOS、没有完整操作系统的环境 我曾经用它编译 FreeRTOS 项目,CMake 不会尝试检测任何系统特性
Android Android NDK 交叉编译 这个值会触发 CMake 内部一套特殊的 Android 处理逻辑
Windows MinGW 或交叉编译 Windows 程序 在 Linux 上交叉编译 Windows 程序时用
Darwin macOS / iOS 交叉编译 一般配合 Xcode 工具链使用
QNX QNX 实时操作系统 汽车电子项目中常见

嗯,这里要注意:Generic 这个值特别有意思。它告诉 CMake:「别管什么系统特性,你就当我是个裸板」。我在做 STM32 裸机项目时,就用的 Generic。CMake 不会去检查 dlopenpthread 这些库,也不会尝试检测 sizeof(int)——因为根本没有操作系统在运行。

核心原则:CMAKE_SYSTEM_NAME 决定了 CMake 的「行为模式」。选错了,轻则编译警告,重则链接出一堆没用的符号。

CMAKE_SYSTEM_PROCESSOR:告诉 CMake 你的 CPU 是什么

这个变量指定目标处理器的架构。比如 armaarch64x86_64riscv64mips 等等。

你可能会问:「CMake 拿这个干嘛用?」

其实它主要影响两件事:

  1. 编译器默认参数——有些工具链会根据处理器名字自动添加 -march-mtune 标志
  2. 系统库搜索路径——比如在 Linux 上,aarch64 会搜索 /usr/aarch64-linux-gnu/lib 这样的路径

我个人习惯把 CMAKE_SYSTEM_PROCESSOR 设置成工具链前缀中的那个架构名。比如工具链叫 arm-linux-gnueabihf-gcc,那我就写 arm。如果是 aarch64-linux-gnu-gcc,就写 aarch64

小技巧:如果你不确定该填什么,可以运行 ${CROSS_COMPILE}gcc -dumpmachine,输出的前半部分通常就是处理器名。比如输出 arm-linux-gnueabihf,那处理器就是 arm

实战:一个完整的工具链文件示例

光说不练假把式。咱们写一个给 ARM Cortex-A7 用的 Linux 工具链文件:

# arm-linux-gnueabihf.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

# 指定交叉编译器
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)

# 告诉 CMake 不要检测编译器(交叉编译下检测容易出错)
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)

# 指定 sysroot(如果有的话)
set(CMAKE_SYSROOT /opt/arm-linux-gnueabihf/sysroot)

# 查找库和头文件的路径
set(CMAKE_FIND_ROOT_PATH /opt/arm-linux-gnueabihf/sysroot)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

你看,CMAKE_SYSTEM_NAMECMAKE_SYSTEM_PROCESSOR 就两行,但它们是整个文件的基石。没有它们,CMake 就不知道自己在交叉编译。

Android 的特殊之处

Android 的情况比较特殊。当你设置 CMAKE_SYSTEM_NAMEAndroid 时,CMake 会启用一套内置的 Android NDK 支持逻辑。这时候你通常不需要手动指定编译器,而是通过 CMAKE_ANDROID_NDK 指向 NDK 路径。

我记得有一次帮朋友调试一个 Android 的 native 库,他死活编译不过。我一看,他把 CMAKE_SYSTEM_NAME 写成了 Linux,然后手动指定了 NDK 里的编译器。结果 CMake 找不到 Android 特有的库路径,链接阶段报了一堆 undefined reference。改成 Android 后,一切正常。

注意:Android 交叉编译时,CMAKE_SYSTEM_PROCESSOR 的值要填 armv7-aaarch64x86_64 等,而不是简单的 arm。因为 Android 的 ABI 区分很细,填错了会导致运行时崩溃。

Generic 的妙用:裸机与 RTOS

如果你在做嵌入式裸机开发(比如 STM32、ESP32),或者用 FreeRTOS、RT-Thread 这类 RTOS,Generic 就是你的好朋友。

为什么?因为 Generic 模式下,CMake 不会做任何系统相关的检测。它不会去检查 dlfcn.h 是否存在,不会尝试链接 libpthread,也不会检测 signals 支持。这些在裸机环境下统统没有,强行检测只会报错。

我曾经接手过一个项目,前任工程师用 Linux 作为 CMAKE_SYSTEM_NAME 来编译 FreeRTOS 项目。结果 CMake 在配置阶段就报错,说找不到 dlopen。我改成 Generic 后,问题迎刃而解。

# stm32f4-freertos.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)

# 裸机编译不需要 sysroot
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)

知识体系:系统名称与处理器的决策树

为了让你更直观地理解这两个变量的关系,我画了一张图:

CMAKE_SYSTEM_NAME 与 CMAKE_SYSTEM_PROCESSOR 决策树 设置 CMAKE_SYSTEM_NAME? 未设置 → 本机编译模式(非交叉编译) 已设置 → 交叉编译模式启动 Linux Generic Android Linux 模式 • 启用系统库搜索 • 检测 pthread、dl 等 • 处理器:arm/aarch64/x86_64 Generic 模式 • 不检测任何系统特性 • 适合裸机/RTOS • 处理器:arm/mips/riscv Android 模式 • 启用 NDK 内置逻辑 • 自动设置 ABI 相关参数 • 处理器:armv7-a/aarch64

避坑指南:我踩过的几个雷

最后,分享几个我实际工作中遇到的坑:

  • CMAKE_SYSTEM_NAME 和 CMAKE_SYSTEM_PROCESSOR 写反了——别笑,我真见过有人把处理器名填到系统名字段里。结果 CMake 报错说找不到名为 arm 的操作系统。
  • 大小写问题——Linux 首字母大写,Generic 也是首字母大写。写成 linuxgeneric 小写,CMake 不认。
  • 处理器名不要带版本号——比如 cortex-a7 这种具体型号,应该填 arm。具体优化参数通过 CMAKE_C_FLAGS 传递 -mcpu=cortex-a7
  • Android 的处理器名要精确——armv7-aarmv8-a 不能混用,否则链接的库 ABI 不匹配。

好了,关于 CMAKE_SYSTEM_NAMECMAKE_SYSTEM_PROCESSOR 就讲这么多。这两个变量虽然简单,但它们是交叉编译的「门神」。门神站对了,后面的路就好走了。


公众号:蓝海资料掘金营,微信deep3321