ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

嵌入式Linux下交叉编译Armadillo线性代数库完整指南

2026/10/3 1:10:37 拓冰建站 浏览量
嵌入式Linux下交叉编译Armadillo线性代数库完整指南 1. 为什么要交叉编译armadillo库先说结论如果你手头有一块ARM架构的嵌入式板子需要在上面跑SLAM、点云处理、或是一些控制算法而板子本身的算力又不足以支撑原生编译那在Ubuntu宿主机上用交叉编译方式为ARM目标平台生成armadillo库就是一条绕不开的路。armadillo是一个面向C的线性代数库语法风格接近MATLAB底层又封装了LAPACK、BLAS、OpenBLAS这些高性能计算库。它的价值在于你不需要自己手写矩阵求逆、特征值分解、SVD这类数值算法直接调用API就能拿到结果而且性能足够接近手写优化过的Fortran/C库。我见过不少团队在IMU融合、卡尔曼滤波、位姿估计里直接拿armadillo当主力工具哪怕是嵌入式平台上只要交叉编译做好跑起来一点问题没有。那为什么不直接在板子上编道理很朴素。ARM板子的CPU和内存都受限尤其是一些低成本的Cortex-A系列SoC跑一个面向桌面环境的编译任务可能要几十分钟甚至更久而宿主机编译可能也就两三分钟。更关键的是很多项目的依赖项非常多CMake的配置、头文件解析、模块编译这些在板子上迭代一次非常痛苦。所谓交叉编译就是在自己的x86_64 Ubuntu开发机上用一套可以生成ARM机器码的工具链把代码编译、链接成ARM平台可以执行的二进制文件然后拷到板子上运行。armadillo本身是header-only和预编译混合的结构纯头文件部分比较多但底层仍然需要链接BLAS/LAPACK。这就带来了一个容易被新手忽略的问题你光交叉编译了armadillo本体还不够BLAS/LAPACK的ARM版本也得同步交编译好否则最后链接阶段会报一堆找不到符号的错误。这一点我在后面会专门拆开讲。这篇文章适合谁主要两类人一是正在做嵌入式Linux项目、需要移植科学计算组件的工程师二是在校学生或研究者想把宿主机上的原型代码部署到ARM盒子或机器人平台上。无论你是第一次碰交叉编译还是已经在Qt交叉编译环境里踩过几个坑这篇文章都能给你一套完整可复用的参考路径看完可以直接抄作业。2. 交叉编译的前置准备工具链与依赖2.1 工具链选型与安装交叉编译的核心工具链就是编译器、汇编器、链接器的ARM版本组合。在Ubuntu仓库里有一组现成的包可以直接用gcc-aarch64-linux-gnu和g-aarch64-linux-gnu对应64位ARM平台的GCC工具链。如果你的目标平台是32位ARM则是gcc-arm-linux-gnueabihf这一组。绝大多数现代嵌入式Linux系统都已经是aarch64了所以我以64位为主。在Ubuntu 22.04或24.04上安装非常简单sudo apt update sudo apt install gcc-aarch64-linux-gnu g-aarch64-linux-gnu装完以后交叉编译器的名称前缀是aarch64-linux-gnu-也就是说你会在/usr/bin/下看到aarch64-linux-gnu-gcc、aarch64-linux-gnu-g、aarch64-linux-gnu-ld等一整套工具。这个前缀前缀其实就是GNU工具链的target三元组它决定了编译出的机器码目标架构。还有一点要提醒交叉编译不仅需要编译器还需要目标平台上的系统库头文件和预编译库比如glibc、libstdc、libgcc等。Ubuntu的ARM交叉编译器包本身会把这些依赖一并拉进来但版本是否与板子上的系统版本匹配却是个容易埋雷的问题。假如你的板子是老旧的glibc 2.28而宿主机交叉工具链默认链接的是glibc 2.35那编译出来的程序拷到板子上运行时会直接报FATAL: kernel too old或version GLIBC_2.34 not found。这个问题的根源在于交叉编译环境里的sysroot与目标设备系统不一致。展开说明一下。工具链安装后通常会提供一个sysroot目录这个目录模拟了目标系统的根文件系统里面包含头文件和库文件。Ubuntu的交叉编译器默认sysroot位于/usr/aarch64-linux-gnu。如果你的嵌入式系统不是基于相同版本Ubuntu构建的最好自己下载或制作一个与板子系统匹配的sysroot然后通过--sysroot参数或用CMake的CMAKE_SYSROOT变量来指定。我的建议是拿到板子后先确认/etc/os-release里的版本信息再决定直接用Ubuntu工具链还是构建自定义sysroot。2.2 armadillo的源码获取与版本选择armadillo的GitHub仓库地址是https://gitlab.com/conradsnicta/armadillo-codeRelease页面提供了稳定版的源码压缩包。从版本节奏来看它的更新还算频繁但整体API相对稳定。我建议优先选择最新的Release版别直接clone master分支因为master分支偶尔会有文档或边界情况的改动未必经过同样严格的回归。下载一个具体版本比如12.6.xwget https://sourceforge.net/projects/arma/files/armadillo-12.6.6.tar.xz/download tar -xf armadillo-12.6.6.tar.xz cd armadillo-12.6.6解压后你会看到目录结构里有include/头文件、src/少量cpp文件、examples/示例代码、misc/辅助脚本和配置文件模板、还有CMakeLists.txt。我之所以强调看CMakeLists是因为armadillo的交叉编译关键点几乎全在这个构建脚本里。armadillo从某个版本起构建方式改为以CMake为主同时保留了对老式Makefile的支持。但交叉编译场景下CMake远比直接Makefile灵活。原因很简单CMake可以极其干净地指定工具链文件、sysroot、搜索路径Makefile则把这些东西散落在各变量里改起来容易出错。我推荐用CMake。3. 交叉编译armadillo的核心流程3.1 先搞定BLAS/LAPACK再谈armadillo之前提过armadillo底层对BLAS/LAPACK有依赖。交叉编译之前我们必须先确认目标板子上有没有这些库或者先用交叉工具链把它们编出来。这里要说清楚一点armadillo不是强依赖BLAS/LAPACK。它在纯头文件模式下有一个内置的降级实现用的是模板表达式和简单的循环代性能虽然远不如BLAS但求个逆、解个小型方程还是能跑的。真正需要BLAS/LAPACK的是大型矩阵运算或者对性能有硬要求的场景。所以你有两条路线路线A只要库能跑通不考虑极致性能那就直接编armadillo纯头文件版本链接时什么额外库都不用加。路线B需要OpenBLAS级别的性能那就先交叉编译OpenBLAS或LAPACK再让armadillo在构建时检测并关联上去。我的经验是嵌入式设备做控制或传感器融合矩阵规模普遍不大路线A完全够用。但如果你的矩阵维度到了几百乘几百或者有大量特征值分解还是老老实实走路线B。路线B的具体做法从GitHub拉取OpenBLAS源码交叉编译时指定工具链前缀即可make CCaarch64-linux-gnu-gcc FCaarch64-linux-gnu-gfortran HOSTCCgcc TARGETARMV8 make PREFIX/path/to/install/root install注意这里的FC是Fortran编译器LAPACK用Fortran写的所以还需要安装gfortran-aarch64-linux-gnu。如果你不想为Fortran的事费心可以只编OpenBLAS而不编LAPACKarmadillo对它们两个的检测是独立的能找到一个是一个。3.2 编写CMake工具链文件CMake是交叉编译时最好用的配置载体。我们需要一个工具链文件toolchain file它负责告诉CMake三件事用什么编译器、目标平台是什么、去哪里找头文件和库。下面是我一直在用的一份模板路径可以放在任何地方比如arm-linux.toolchain.cmakeset(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g) set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu) 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_NAME设为LinuxCMake才会以Linux的规则去找系统库。CMAKE_SYSTEM_PROCESSOR设为aarch64影响部分平台相关的判断。CMAKE_FIND_ROOT_PATH非常关键它指定了一个“根”任何find_library、find_path操作都会优先在这个根下搜索。前面说了Ubuntu交叉编译器把目标平台的库文件放在/usr/aarch64-linux-gnu下所以这里指向它。你从这也能看出sysroot的意义。后面四个MODE变量的含义是程序查找比如编译器本身不要到目标根目录里找直接找宿主机的库、头文件、包配置则只允许在目标根目录里找。这个设置可以避免CMake一不小心抓到宿主机的x86_64库或头文件。如果你的依赖库是自己交叉编译的比如OpenBLAS安装在了某个自定义目录那就把该目录也追加到CMAKE_FIND_ROOT_PATH里set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu /opt/arm/lib)这样写有点过于理想化实际使用中lib目录下面的.so或.a文件是否能被链接到还取决于CMake是否能在库名前找到对应的头文件或配置文件。如果OpenBLAS源码本身就提供CMake config文件则会被顺利找到。3.3 执行交叉编译完整命令与参数选择工具链文件准备好后编译armadillo本身非常直接mkdir build-arm cd build-arm cmake .. -DCMAKE_TOOLCHAIN_FILE../arm-linux.toolchain.cmake \ -DCMAKE_BUILD_TYPERelease \ -DCMAKE_INSTALL_PREFIX/opt/arm/armadillo \ -DDETECT_HDF5OFF make -j$(nproc) make install几个参数说明一下。CMAKE_BUILD_TYPERelease开启优化对于数学库来说这是必须的Debug模式下性能会差到让人怀疑人生。DETECT_HDF5OFF是因为armadillo在构建时会尝试自动检测HDF5库如果目标root里没有就会多出一个警告甚至失败。嵌入式环境普遍不用HDF5干脆显式关掉。编译过程中你会看到CMake把armadillo配置成header-only模式还是需要额外编译的模式。这取决于你的依赖情况。如果找不到BLAS/LAPACK它就把内置的机制顶上结果也算成功只是性能打折。如果找到了它会额外生成一个libarmadillo.so或者静态库把对BLAS/LAPACK的链接封装进去。一个容易踩的坑如果CMake在宿主机上先找到了x86_64的BLAS链接时就会报“wrong ELF class”或者类似的架构不匹配错误。原因多半是你没有把CMAKE_FIND_ROOT_PATH_MODE_LIBRARY设成ONLY或者宿主机的/usr/lib/x86_64-linux-gnu被意外加入了搜索路径。一旦出现这种错误优先检查工具链文件不要急着改代码。4. 实操中常见的坑与排查要点交叉编译这件事真正让人头疼的往往不是编译本身而是编译出来之后跟预想不一致。我把自己踩过的坑和身边朋友经常遇到的问题整理到一起列成速查表你照着排查能省很多时间。4.1 宿主机libstdc版本与目标板不匹配这是交叉编译里最隐蔽也最致命的问题。宿主机Ubuntu 22.04的libstdc版本是GLIBCXX_3.4.30如果你的板子系统是基于Ubuntu 18.04或者更老的那它带的libstdc可能只有GLIBCXX_3.4.25。交叉编译时编译器默认链接的是工具链自带的libstdc静态链接进去的符号版本是新的拿到板子上加载时加载器一看版本号不支持直接报错。解决方案分几种。要么把板子的系统更新到与宿主机基础版本接近要么用更老的工具链来编要么在链接时静态链接libstdc命令加一个-static-libstdc参数。armadillo本身对libstdc版本没有硬性要求但你的应用代码是C写的逃不掉。如果是CMake工程可以在链接选项中加set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -static-libstdc)这个方法简单粗暴但代价是二进制文件体积多了几百KB。在嵌入式空间紧张时我不建议轻易这么干还是尽量让板子系统版本和工具链匹配。4.2 CMake找不到armadillo的头文件交叉编译好armadillo并安装到/opt/arm/armadillo之后紧接着要交叉编译你自己的应用这时CMake里写find_package(Armadillo REQUIRED) target_link_libraries(my_app Armadillo)结果报错找不到ArmadilloConfig.cmake。这通常是因为CMAKE_FIND_ROOT_PATH里没有把/opt/arm/armadillo加进去。CMake的find_package查找路径受CMAKE_FIND_ROOT_PATH影响如果你只写了/usr/aarch64-linux-gnu那安装在自定义目录下的包自然找不到。解决方式是追加参数cmake .. -DCMAKE_TOOLCHAIN_FILE../arm-linux.toolchain.cmake \ -DCMAKE_PREFIX_PATH/opt/arm/armadillo还有一种情况你明明装了armadillo的CMake config但它内部引用的BLAS/LAPACK名字不是标准的blas和lapack而是OpenBLAS提供的openblas。这时CMake在生成链接指令时可能会漏掉-lopenblas导致最后链接不过。我解决的办法是在CMakeLists显式指定set(BLAS_LIBRARIES /opt/arm/openblas/lib/libopenblas.a) set(LAPACK_LIBRARIES /opt/arm/openblas/lib/libopenblas.a)这类问题大多发生在你用自己编的OpenBLAS时因为它的库名和标准LAPACK不一致。4.3 运行时出现找不到共享库armadillo如果被编译成链接BLAS的共享库模式那运行板子上的程序时libarmadillo.so和libopenblas.so都需要存在于文件系统中并且ldconfig能找到它们。很多人在板子上遇到/lib/aarch64-linux-gnu/libarmadillo.so.12: cannot open shared object file本质就是动态链接器搜索路径里没有它。排查步骤就三步确认.so文件是否拷到了板子上确认路径是否在ldconfig配置里或者直接设置LD_LIBRARY_PATH来临时指定export LD_LIBRARY_PATH/opt/arm/lib:$LD_LIBRARY_PATH如果懒得拷动态库更省事的做法是让armadillo以静态库方式编出来到时候整个程序就是一个独立二进制的什么都不用额外带。代价是库文件体积变大链接时也更慢但嵌入式上换来的省心是值得的。4.4 aarch64工具链和目标板子CPU架构不完全兼容有些ARM处理器虽然是aarch64架构但存在一些指令集差异。CMake默认不会开启特定CPU指令优化所以大多数情况下没问题。但如果你在OpenBLAS那一步指定了特殊优化参数比如-mcpucortex-a53而目标板子其实是cortex-a55编译产物的确可能行为异常。我的建议是没有特殊性能需求就不用加这些特定指令参数默认-marcharmv8-a是兼容性最好的选择。5. armadillo交叉编译完成后的质量与性能验证交叉编译完成不是终点用起来才是终点。我一般在安装完成后会快速写一个小程序验证功能正确性和性能是否达标。5.1 写一个跨平台验证程序下面这段代码就是拿来验证用的创建一个随机矩阵求逆再做一次矩阵乘法同时统计耗时。如果这样简单运算都跑不对那就别指望后面更复杂的算法能跑对了。#include armadillo #include iostream #include chrono using namespace arma; int main() { mat A randumat(64, 64); auto start std::chrono::high_resolution_clock::now(); mat B inv(A); auto end std::chrono::high_resolution_clock::now(); std::chrono::durationdouble elapsed end - start; std::cout inv time: elapsed.count() s std::endl; std::cout B(0,0) B(0,0) std::endl; mat C A * B; std::cout max error: accu(abs(C - eyemat(64, 64))) std::endl; return 0; }这段代码在x86宿主机上可以跑交叉编译后在ARM板子上也可以跑行为应该一致。重点看max error如果远大于10的负十几次方量级那大概率是BLAS或LAPACK的交叉编译出了问题而不是armadillo本身的问题。编译时用法aarch64-linux-gnu-g test.cpp -I/opt/arm/armadillo/include -L/opt/arm/armadillo/lib -larmadillo -o test_arm如果编译时提示找不到armadillo头文件先检查路径有没有写对。如果提示找不到链接库那么你需要确认/opt/arm/armadillo/lib下确实有.so或.a文件。5.2 用VSCode或命令行远程调试我通常的做法是在宿主机交叉编译然后把二进制文件用scp传到板子上运行。来回迭代虽然不比直接在板子上开发快但比原生编译快太多了。如果你用得顺还可以配一个VSCode Remote-SSH插件直接在板子上打开工作区用宿主机编译好的二进制文件来调试。板子上面装一个gdbserver宿主机这边使用aarch64-linux-gnu-gdb连接断点、变量查看都很丝滑。我曾经用过这个方案在一款RK3588的开发板上调试了一个在线参数辨识程序方便到什么程度基本和本地写单元测试一样痛快。6. 交叉编译层层拆解armadillo的特殊姿态armadillo这个库跟OpenCV、Qt这类庞大的库不太一样它天然就是为“轻量、可移植、接近MATLAB体验”设计的。因此它的交叉编译门槛比很多其他库都要低。但它也有自己的癖好最典型的就是它在CMake中会自动探测一系列外部库这个自动探测机制在交叉编译时经常捣乱。展开讲armadillo的CMakeLists.txt里有一段逻辑会依次检测BLAS、LAPACK、HDF5、SuperLU、ARPACK等环境是否存在。在宿主机上这些库可能在/usr/lib/x86_64-linux-gnu下都有安装所以CMake会顺利找到并启用它们。但在交叉编译时如果CMAKE_FIND_ROOT_PATH设置正确就只会在目标root下寻找不会误用宿主机的库。但如果你的工具链文件里某项配置怪怪的CMake就可能把宿主机库误认为目标库链接进去。一旦编译完成注意检查生成的CMakeCache.txt里面有详细的探测结果。我经常用一句话来判断是否正常grep -E USE_BLAS|USE_LAPACK|USE_ARPACK|USE_HDF5 CMakeCache.txt正常情况下你期望看到USE_BLAS:BOOLON、USE_LAPACK:BOOLON而不期望看到USE_HDF5:BOOLON除非你确实需要且已经交叉编译好了HDF5。这里网关检查了一下OpenBLAS对BLAS和LAPACK都有实现所以如果你用OpenBLAS提供两者USE_BLAS和USE_LAPACK都是ON这是没问题的。如果这些项的结果看起来是宿主机上的配置比如BLAS路径写着/usr/lib/x86_64-linux-gnu/libblas.so说明CMake的搜索路径被污染了必须回头检查CMAKE_FIND_ROOT_PATH和那三个MODE是不是都设对了。这个检查特别重要因为错误都会被编译期不报错只会在链接时报一堆错。偶尔甚至会链接成功但编译产物的ELF头是x86_64的放到板子上运行直接segfault。这种错误最棘手因为它不给你任何提示。7. 进阶玩法让armadillo交叉编译成为自动化流水线的一环一次手动交叉编译成功了只是起点如果你要在一个嵌入式产品里长期迭代就得考虑把交叉编译流程自动化。就我参与的多个项目来看现代团队普遍用CI/CD来管理这类构建任务。GitLab CI里有现成的ARM交叉编译镜像或者你也可以自己在Docker里搭一个编译环境。我自己的做法很简单写一个Dockerfile把工具链、依赖、armadillo构建步骤都固化下来FROM ubuntu:22.04 RUN apt update apt install -y \ gcc-aarch64-linux-gnu g-aarch64-linux-gnu \ gfortran-aarch64-linux-gnu cmake wget WORKDIR /build然后在CI的脚本任务里克隆代码配置交叉编译一气呵成。好处很明显换任何一台新电脑拉一下镜像就能恢复完全相同的编译环境不用再被“我本机能编别人本机编不了”这种荒谬问题折磨。如果在企业内部推进这个流程还能把编译好的armadillo安装目录直接做成一个内网软件包供其他同事find_package使用减少重复劳动。8. 一个完整案例从零到板子上跑通讲一个我在实际项目里的完整流程更能帮你有画面感。背景是某巡检机器人项目主控用的是ARM Cortex-A53双核方案系统是基于Yocto构建的嵌入式Linux需要移植一版基于armadillo的多传感器融合算法。我的操作顺序是这样的写好test.cpp和CMakeLists.txt。在同一条命令里一次性配置并编译mkdir build cd build cmake .. -DCMAKE_TOOLCHAIN_FILE../arm.toolchain.cmake -DCMAKE_BUILD_TYPERelease make -j$(nproc) file ./test_arm通过scp把test_arm传到板子上加上可执行权限运行。file命令这一步已经被我养成条件反射了。它会显示ELF的架构如果不是aarch64那前期的工具链配置多半有误。我甚至见过有同学把x86的二进制改个名字就传板子上跑这种行为在开发板上只能得到一个“Exec format error”折腾半天还没搞清楚方向。运行完验证程序再继续把封装了armadillo的算法库交叉编译一遍输出动态库给上层应用去调用。整个过程里最耗时的是解决opencv和armadillo的头文件冲突跟armadillo本身没太大关系。如果遇到类似情况我的经验是两类库的头文件不要在同一个全局命名空间下直接混用尽量用类封装隔离类的边界上用基础数据类型传递。9. 关于交叉编译的最终建议交叉编译armadillo技术栈其实不复杂复杂的是对交叉编译原理的理解。我无数次帮人排查问题后发现凡是能把工具链文件里那几个变量含义讲清楚的人几乎不会再卡在环境问题上。根据我个人的经验最值得花时间钻研的环节只有两个一是toolchain文件是否能导致依赖被正确找到二是目标设备系统的ABI与工具链是否真的匹配。这两点解决了armadillo就只是个顺势而为的普通组件罢了。至于OpenBLAS/LAPACK要不要编取决于你的运行环境对性能要求有多高反正armadillo自己有一层兜底位置在头文件里不需要额外操心。最后再分享一个小技巧处理交叉编译问题时记得编译加-v参数GCC会把实际的include路径和链接指令全部打印出来。这些信息往往比任何CMake报错都直观你一眼就能看出它到底在用哪里的头文件、哪里的库问题基本就现形了。这个方法我已经用了很多年屡试不爽。