第三章 交叉编译环境搭建:GCC交叉工具链配置、CMake交叉编译文件编写、Sysroot与依赖库管理
好,咱们直接进入正题。嵌入式语音引擎移植,第一步就是搭环境。这步要是没弄好,后面全是白费功夫。我见过太多人卡在编译阶段,报错信息看得头皮发麻,最后发现是工具链版本不对。
说白了,交叉编译就是在一台电脑上(宿主机),编译出能在另一台设备上(目标机)跑的程序。你的PC是x86架构,目标板可能是ARM、RISC-V或者MIPS。编译器得知道目标平台长什么样。
3.1 GCC交叉工具链配置
工具链的选择,我个人习惯先看芯片厂商。比如你用全志的芯片,他们官网通常会提供预编译好的工具链。用ST的STM32系列,ARM官方GCC也是个好选择。
举个例子,假设目标平台是ARM Cortex-A7,运行Linux系统。我会这样配置:
# 下载并解压工具链
wget https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf.tar.xz
tar -xf gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf.tar.xz -C /opt/
# 设置环境变量
export PATH=/opt/gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf/bin:$PATH
export CROSS_COMPILE=arm-none-linux-gnueabihf-
export CC=${CROSS_COMPILE}gcc
export CXX=${CROSS_COMPILE}g++
export AR=${CROSS_COMPILE}ar
export LD=${CROSS_COMPILE}ld
这里有个坑,我必须要说。环境变量最好写到 ~/.bashrc 或者 /etc/profile 里,别每次开终端都手动export。我曾经有个项目,同事每次编译前都要敲一遍环境变量,结果有一次漏了,编译出来的程序在板子上跑不起来,查了两天才发现是用了宿主机的gcc。
验证工具链是否配置成功,很简单:
arm-none-linux-gnueabihf-gcc --version
arm-none-linux-gnueabihf-gcc -v 2>&1 | grep "Target:"
看到输出里有 Target: arm-none-linux-gnueabihf,基本就稳了。
3.2 CMake交叉编译文件编写
CMake这东西,说实话,刚开始用的时候我也觉得烦。但用顺手了,真香。它最大的好处是,你写一个工具链文件,整个项目都能复用。
创建一个 arm-linux-gnueabihf.cmake 文件:
# 设置目标系统
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
# 指定编译器
set(CMAKE_C_COMPILER arm-none-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-none-linux-gnueabihf-g++)
# 设置查找策略
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)
# 指定Sysroot路径
set(CMAKE_SYSROOT /opt/sysroot/arm-linux-gnueabihf)
使用的时候,只需要在构建命令里指定这个文件:
mkdir build && cd build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../arm-linux-gnueabihf.cmake
make -j4
嗯,这里要注意 CMAKE_FIND_ROOT_PATH_MODE 这几个参数。我刚开始做的时候,没设置好,结果CMake在宿主机上找到了x86的库,链接进去,编译倒是通过了,但一运行就段错误。你想想看,ARM的代码里混进了x86的库,不出问题才怪。
set(CMAKE_PREFIX_PATH /opt/sysroot/arm-linux-gnueabihf/usr/local),这样CMake会自动去Sysroot里找这些库。
3.3 Sysroot与依赖库管理
Sysroot,说白了就是目标文件系统的根目录。里面放着目标板上的所有库文件、头文件、甚至一些配置文件。交叉编译时,编译器会优先去Sysroot里找依赖。
构建Sysroot,我推荐两种方法:
- 方法一:从目标板拷贝。直接在板子上执行
rsync -a / lib/ usr/ /path/to/sysroot/。简单粗暴,但要注意板子上的空间够不够。 - 方法二:使用Yocto/Buildroot。这些构建系统会自动生成Sysroot。我个人更推荐这个,因为版本可控,依赖清晰。
举个例子,假设你的语音引擎依赖了 libasound.so(ALSA音频库),你需要确保Sysroot里有这个库:
# 检查Sysroot里是否有ALSA库
ls /opt/sysroot/arm-linux-gnueabihf/usr/lib/libasound.so*
# 如果没有,需要手动拷贝或交叉编译ALSA
# 交叉编译ALSA示例
./configure --host=arm-none-linux-gnueabihf --prefix=/opt/sysroot/arm-linux-gnueabihf/usr
make && make install
依赖库管理,我建议用表格记录清楚:
| 库名称 | 版本要求 | 来源 | 安装路径 |
|---|---|---|---|
| libasound | >= 1.2.0 | 交叉编译 | /usr/lib/libasound.so.2 |
| libpthread | 系统自带 | 目标板拷贝 | /lib/libpthread.so.0 |
| libm | 系统自带 | 目标板拷贝 | /lib/libm.so.6 |
| libfftw3 | >= 3.3.8 | 交叉编译 | /usr/lib/libfftw3.so.3 |
我曾经遇到过一个很头疼的问题。语音引擎里用到了 libfftw3,我在Sysroot里放的是3.3.8版本,但目标板系统里自带了3.3.7。编译时链接的是3.3.8,运行时却加载了3.3.7,结果FFT计算结果不对,语音识别率直接掉到30%。
怎么解决?两个办法:
- 在CMake里指定
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-rpath,/opt/sysroot/arm-linux-gnueabihf/usr/lib"),强制运行时去Sysroot路径找库。 - 或者,直接把编译好的库拷贝到目标板的
/usr/lib下覆盖掉旧版本。
最后,检查依赖是否完整,可以用这个命令:
# 查看编译出的可执行文件依赖了哪些库
arm-none-linux-gnueabihf-readelf -d your_voice_engine | grep NEEDED
# 检查这些库在Sysroot里是否存在
for lib in $(arm-none-linux-gnueabihf-readelf -d your_voice_engine | grep NEEDED | awk '{print $5}'); do
find /opt/sysroot/arm-linux-gnueabihf -name "$lib" 2>/dev/null || echo "MISSING: $lib"
done
嗯,到这里,交叉编译环境基本就搭好了。下一章我们会讲怎么用这个环境去编译语音引擎的核心算法库。到时候你会发现,环境搭得越扎实,后面越省心。