1. 项目缘起与整体设计思路
1.1 为什么要在Ubuntu上折腾交叉编译
做过嵌入式界面开发的朋友大概都有这种体会:目标板子的CPU架构跟你的开发机完全不是一回事,板子上的资源又紧巴巴的,根本不可能把整套编译工具链塞进去跑。MiniGUI这个轻量级图形界面库,恰恰就是为这种资源受限的场景设计的。它的核心优势在于体积小、可裁剪、启动快,在工控HMI、医疗设备面板、车载仪表这类场景里出镜率相当高。
但问题来了——你不可能直接在目标板上编译它。一是板子的存储和内存扛不住编译过程的消耗,二是编译一次动辄十几分钟甚至半小时,调试效率低到让人抓狂。所以交叉编译就成了必由之路:在性能强劲的Ubuntu开发机上,用目标架构的工具链把MiniGUI编译成能在板子上跑的二进制文件,然后打包丢过去。
这个项目要解决的核心问题就三个:第一,搭好一套干净可用的交叉编译工具链;第二,把MiniGUI的依赖库(主要是zlib、libpng、jpeg这些)先交叉编译出来;第三,配置并编译MiniGUI本身,生成目标板能直接运行的库和示例程序。适合谁看?适合刚接触嵌入式Linux界面开发、手头有块开发板想跑个图形界面出来的朋友,也适合之前只用过Qt、想试试更轻量方案的老手。
1.2 整体方案选型与目录规划
交叉编译最怕的就是文件到处乱飞,最后自己都搞不清哪个库装到哪了。我的习惯是在home目录下建一个专门的嵌入式工作区,把所有跟目标平台相关的东西都收进去。这样做的好处是,将来要换工具链或者换目标架构,直接整个目录删掉重来就行,不会污染系统环境。
目录结构我一般这样规划:
~/embedded/ ├── toolchain/ # 交叉编译工具链解压目录 ├── src/ # 所有源码包 │ ├── zlib-1.2.11/ │ ├── libpng-1.6.37/ │ ├── jpeg-9d/ │ └── minigui-3.2.0/ ├── build/ # 编译中间产物 └── output/ # 最终安装目录(按目标平台分) └── arm-target/ ├── include/ ├── lib/ └── bin/这里有个关键决策:所有依赖库和MiniGUI都安装到同一个output目录下。为什么?因为交叉编译时,MiniGUI的configure脚本需要能找到zlib、libpng这些库的头文件和链接库。如果每个库装到各自独立的prefix下,你就得在编译MiniGUI时手动指定一堆CFLAGS和LDFLAGS,容易出错还不好维护。统一prefix之后,只需要指定一个--prefix,剩下的让pkg-config或者configure自己去猜就行。
工具链的选择上,我用的是arm-linux-gnueabihf这套。gnueabihf表示目标系统用的是glibc、硬件浮点ABI。如果你的板子是musl libc或者软浮点,工具链名字会不一样,但整个流程是相通的。选工具链的时候一定要跟板子上实际运行的系统匹配,ABI对不上,编译出来的程序一跑就报“cannot execute binary file”,这个坑我踩过不止一次。
2. 核心细节解析与实操要点
2.1 工具链的获取与环境变量配置
工具链的获取方式有好几种,可以用发行版仓库里的,也可以从芯片原厂或者工具链厂商官网下载。我倾向于用后者,因为版本可控,而且通常针对特定架构做过优化。下载下来一般是个tar.xz包,解压到~/embedded/toolchain/下面就行。
解压完之后,最关键的一步是把工具链的bin目录加到PATH里。我习惯在~/.bashrc里加一行:
export PATH=$PATH:$HOME/embedded/toolchain/gcc-arm-linux-gnueabihf/bin然后source一下,敲arm-linux-gnueabihf-gcc -v看看能不能正常输出版本信息。能输出版本号,说明工具链本身没问题。
但这里有个隐藏的坑:有些工具链自带的sysroot路径是写死的。什么叫sysroot?简单说就是工具链认为的“目标系统的根目录”,里面放着目标平台的头文件和库。如果sysroot路径不对,编译的时候会找不到stdio.h这类基础头文件。验证方法很简单,写个hello world用交叉编译器编一下:
arm-linux-gnueabihf-gcc -o hello hello.c如果报“stdio.h: No such file or directory”,那就是sysroot的问题。解决办法是在编译时显式指定--sysroot=/path/to/toolchain/sysroot,或者用工具链自带的sysroot路径。我一般会在环境变量里加一个:
export SYSROOT=$HOME/embedded/toolchain/gcc-arm-linux-gnueabihf/arm-linux-gnueabihf/libc后面编译各个库的时候,把这个SYSROOT通过--sysroot参数传给编译器。
注意:不同厂商的工具链目录结构差异很大,有的sysroot在
arm-linux-gnueabihf/libc,有的直接在工具链根目录下的sysroot文件夹。拿到工具链第一件事就是find . -name "stdio.h",看看头文件到底在哪,心里有数再动手。
2.2 依赖库的交叉编译顺序
MiniGUI本身依赖几个基础库,最核心的是zlib(压缩)、libpng(PNG图片支持)、libjpeg(JPEG图片支持)。这三个库的编译顺序不能乱:zlib最先,因为libpng依赖它;libpng和libjpeg之间没有依赖关系,谁先谁后都行。
为什么必须按这个顺序?因为libpng在configure阶段会去检测zlib是否存在,如果zlib还没装到output目录里,libpng就会报“zlib not found”,然后要么编译失败,要么编译出来的libpng不带压缩支持——后者更坑,因为编译能过,但运行时加载PNG图片会失败,你还得回头重编。
每个库的编译流程大同小异,都是configure、make、make install三步。但交叉编译的configure跟本地编译不一样,必须显式指定host和编译器。以zlib为例:
cd ~/embedded/src/zlib-1.2.11 CHOST=arm-linux-gnueabihf ./configure --prefix=$HOME/embedded/output/arm-target make -j4 make installzlib的configure脚本比较特殊,它不认--host参数,而是用CHOST环境变量来指定交叉编译器前缀。这一点跟其他autotools项目不一样,第一次编zlib的人很容易在这里卡住。
libpng的configure就标准多了:
cd ~/embedded/src/libpng-1.6.37 ./configure --host=arm-linux-gnueabihf \ --prefix=$HOME/embedded/output/arm-target \ CPPFLAGS="-I$HOME/embedded/output/arm-target/include" \ LDFLAGS="-L$HOME/embedded/output/arm-target/lib" make -j4 make install这里显式加了CPPFLAGS和LDFLAGS,告诉编译器去哪里找zlib的头文件和库。虽然理论上可以用PKG_CONFIG_PATH来搞定,但交叉编译环境下pkg-config经常不灵光,直接写死路径更稳妥。
libjpeg的编译稍微麻烦一点,因为它用的是libtool,有时候会尝试链接宿主机的库。保险的做法是在configure时加上--disable-shared先只编静态库,等确认没问题了再考虑动态库。静态库的好处是部署简单,直接把.a文件链进最终的可执行文件里,板子上不需要额外放.so文件。
实操心得:每编译完一个库,立刻去output目录下检查对应的头文件和库文件是不是真的生成了。我遇到过make install“成功”了但文件没拷过去的情况,原因是configure时prefix路径写错了。检查命令很简单:
ls $HOME/embedded/output/arm-target/lib/libz*,能看到libz.a或者libz.so就对了。
2.3 MiniGUI的configure参数详解
依赖库都就位之后,终于轮到MiniGUI本体了。MiniGUI 3.x版本用的是自己的构建系统,configure脚本支持的参数相当多,但交叉编译场景下真正需要关心的就那么几个。
最核心的参数是--host和--prefix,这个跟其他库一样。但MiniGUI有几个特有的开关需要特别注意:
--enable-static和--disable-shared:控制生成静态库还是动态库。如果目标板子的存储空间够,我建议两个都开,静态库用于链接示例程序,动态库留给实际项目用。如果板子空间紧张,只编静态库也行。
--with-osname=linux:指定目标操作系统。MiniGUI支持Linux、uCOS、eCos等多种RTOS,交叉编译到Linux板子上必须显式指定,否则configure可能会猜错。
--disable-vbfsupport:如果你的项目不需要位图字体(VBF),可以关掉,能省不少空间。但注意,关掉之后就只能用TrueType字体或者内嵌的点阵字体了。
--enable-incore-res:这个选项决定资源文件是编译进库里还是作为外部文件加载。开这个选项,MiniGUI会把默认的字体、图标等资源直接编进libminigui.a,部署的时候不需要额外拷贝资源文件,省事。代价是库体积会大一些。
一个典型的configure命令长这样:
cd ~/embedded/src/minigui-3.2.0 ./configure --host=arm-linux-gnueabihf \ --prefix=$HOME/embedded/output/arm-target \ --with-osname=linux \ --enable-static \ --disable-shared \ --enable-incore-res \ --disable-vbfsupport \ CPPFLAGS="-I$HOME/embedded/output/arm-target/include" \ LDFLAGS="-L$HOME/embedded/output/arm-target/lib"configure跑完之后,它会输出一个总结,告诉你哪些功能开了、哪些关了。一定要仔细看这个总结,特别是“Graphics engines”和“Fonts”那几行。如果发现PNG支持是“no”,说明libpng没被正确检测到,得回头检查libpng的安装路径和CPPFLAGS。
3. 实操过程与核心环节实现
3.1 从零开始的完整编译流程
假设你现在拿到一块全新的开发板,Ubuntu开发机也是刚装好的,下面是我实测下来最顺滑的一套流程。
第一步,创建工作目录并下载源码包。zlib、libpng、libjpeg、MiniGUI的源码都可以从各自的官方渠道获取。下载完之后统一放到~/embedded/src/下面,解压待用。
第二步,配置工具链环境。把工具链解压到~/embedded/toolchain/,然后在~/.bashrc末尾追加PATH和SYSROOT两个环境变量。source之后用arm-linux-gnueabihf-gcc -v验证。
第三步,编译zlib。进入zlib源码目录,执行:
CHOST=arm-linux-gnueabihf ./configure --prefix=$HOME/embedded/output/arm-target make -j$(nproc) make install编译完成后检查$HOME/embedded/output/arm-target/lib/libz.a是否存在。
第四步,编译libpng。进入libpng源码目录,执行前面提到的configure命令。这里有个细节:libpng的configure脚本会尝试运行编译出来的测试程序,但交叉编译出来的程序在x86开发机上跑不了,所以它会报“cannot run test program”的警告。这个警告可以忽略,只要最后的总结里PNG相关功能是“yes”就行。
第五步,编译libjpeg。同样configure、make、make install三连。libjpeg的configure脚本对交叉编译的支持比较好,一般不会出幺蛾子。
第六步,编译MiniGUI。这是重头戏,configure命令前面已经给过了。configure通过之后,make -j$(nproc)开始编译。MiniGUI的代码量不小,即使用4核并行编译,大概也需要几分钟。编译过程中如果报错,大概率是某个依赖库没找到,根据错误信息定位是哪个库的问题。
第七步,安装并验证。make install之后,去output目录下检查:
ls $HOME/embedded/output/arm-target/lib/libminigui* ls $HOME/embedded/output/arm-target/include/minigui/能看到libminigui.a和一堆头文件,说明编译安装成功了。
3.2 编译一个示例程序验证成果
光编译出库还不算完,得实际编个程序跑跑看。MiniGUI源码包里自带了不少示例,在src/minigui-3.2.0/examples/下面。挑一个最简单的,比如helloworld,试试能不能交叉编译通过。
编译示例程序需要手动指定头文件和库路径:
arm-linux-gnueabihf-gcc -o helloworld helloworld.c \ -I$HOME/embedded/output/arm-target/include \ -I$HOME/embedded/output/arm-target/include/minigui \ -L$HOME/embedded/output/arm-target/lib \ -lminigui -lpng -ljpeg -lz -lpthread -lm链接的时候顺序有讲究:被依赖的库要放在后面。libminigui依赖libpng和libjpeg,libpng又依赖libz,所以链接顺序是-lminigui -lpng -ljpeg -lz。如果顺序反了,链接器会报“undefined reference”错误。这个规则在交叉编译时同样适用,跟本地编译没区别。
编译出来的helloworld用file命令看一下:
file helloworld输出里应该显示“ELF 32-bit LSB executable, ARM, EABI5”,这就对了。如果显示的是“x86-64”,说明你用的还是宿主机的gcc,不是交叉编译器。
把helloworld拷贝到板子上运行之前,还需要确认板子上的运行环境。MiniGUI运行需要配置文件,通常在/usr/local/etc/MiniGUI.cfg。这个文件里定义了显示引擎、输入引擎、字体路径等关键信息。板子上的路径可能跟开发机不一样,需要根据实际情况调整。
注意:MiniGUI的配置文件里有一项
[system]段的gal_engine,指定图形引擎。常见的值有fbcon(framebuffer控制台)、qvfb(虚拟framebuffer,用于开发机调试)、dummy(空引擎,用于测试)。板子上一般用fbcon,前提是内核已经开启了framebuffer支持,并且/dev/fb0设备节点存在。
3.3 参数计算与路径选择的底层逻辑
交叉编译过程中涉及不少路径和参数的决策,这些决策背后都有实际原因,不是随便拍脑袋定的。
先说--prefix为什么选在home目录下而不是/usr/local。交叉编译出来的库是给ARM架构用的,跟宿主机的x86库完全不兼容。如果装到/usr/local/lib,会跟宿主机的库混在一起,轻则导致本地编译的程序链接到错误的库,重则把系统搞崩。装到home目录下的独立prefix,跟系统环境完全隔离,安全又干净。
再说CPPFLAGS和LDFLAGS为什么要显式指定。configure脚本在检测依赖库时,默认会去/usr/include和/usr/lib找。但交叉编译的库在~/embedded/output/arm-target/下面,configure不知道。虽然可以通过设置PKG_CONFIG_PATH让pkg-config帮忙找,但很多嵌入式库的.pc文件写得并不规范,pkg-config经常找不到或者找到错误的版本。直接写死CPPFLAGS和LDFLAGS,虽然看起来笨,但最可靠。
关于静态库和动态库的选择,这里展开说一下。静态库(.a)在链接时会被完整地拷贝进最终的可执行文件,好处是部署简单,一个文件丢过去就能跑,不依赖板子上的库版本。坏处是体积大,如果多个程序都用MiniGUI,每个程序里都有一份MiniGUI的代码,浪费存储空间。动态库(.so)则相反,多个程序共享一份库文件,省空间,但部署时得确保板子上的库版本跟编译时一致,否则可能出现符号找不到的问题。
我的建议是:开发调试阶段用静态库,省去部署库文件的麻烦;产品发布阶段用动态库,节省存储空间。MiniGUI的configure支持同时生成静态库和动态库,所以不用二选一,两个都编出来,用的时候按需链接就行。
4. 常见问题与排查技巧实录
4.1 编译过程中的典型报错与解决
交叉编译MiniGUI的过程中,报错是家常便饭。我把这些年遇到的高频问题整理成了一张速查表,方便对照排查。
| 报错信息 | 根本原因 | 解决方法 |
|---|---|---|
stdio.h: No such file or directory | sysroot路径不对或未指定 | 在CFLAGS中加--sysroot=/path/to/sysroot |
cannot find -lz | zlib未编译或LDFLAGS路径错误 | 检查libz.a是否存在,确认-L路径正确 |
undefined reference to 'png_create_read_struct' | libpng未链接或链接顺序错误 | 确保-lpng在-lminigui之后 |
error: unrecognized command line option '-mfloat-abi=hard' | 工具链不支持硬浮点 | 换用gnueabihf工具链或去掉该选项 |
configure: error: C compiler cannot create executables | 交叉编译器路径错误或权限问题 | 检查PATH,确认编译器可执行 |
relocation R_ARM_THM_MOVW_ABS_NC against ... can not be used | 静态库编译时未加-fPIC | 重新编译依赖库,CFLAGS加-fPIC |
这张表里的每一个报错我都实际遇到过,其中“链接顺序错误”和“sysroot路径不对”是最常见的两个。链接顺序的问题尤其隐蔽,因为报错信息只告诉你某个符号找不到,不会直接说“你把-lz放错位置了”。记住一个原则:依赖别人的库放前面,被依赖的库放后面。
还有一个不太常见但很坑的问题:编译MiniGUI时提示libminigui.a里的某个函数未定义,但那个函数明明在libpng里。这种情况多半是因为libpng编译时用了宿主机的头文件,导致函数签名不一致。解决办法是清理libpng的编译产物,重新用交叉编译器编一遍,确保CPPFLAGS里指定的include路径是交叉编译版本的。
4.2 运行时的坑与调试手段
编译通过只是第一步,程序在板子上跑不起来才是真正的噩梦。MiniGUI程序在板子上运行失败,通常表现为黑屏、花屏、或者直接段错误退出。
黑屏最常见的原因是framebuffer没配置好。先在板子上执行cat /proc/fb,看看有没有fb0设备。如果没有,说明内核没开framebuffer驱动,得重新配置内核。如果有fb0但程序还是黑屏,检查MiniGUI.cfg里的gal_engine是不是设成了fbcon,以及defaultmode的分辨率是不是跟屏幕实际分辨率匹配。
花屏通常是颜色深度不匹配导致的。MiniGUI.cfg里有一项[fbcon]段的defaultmode,格式是宽x高-色深,比如1024x768-32bpp。如果屏幕实际是16bpp,配置里写32bpp,就会花屏。用fbset命令可以查看当前framebuffer的实际参数。
段错误的原因就比较多了,可能是配置文件路径不对、字体文件缺失、或者内存分配失败。调试段错误最有效的工具是gdb,但交叉编译环境下的gdb配置比较麻烦。一个更简单的办法是在程序里加打印,把关键步骤的日志输出到串口或者文件,通过日志定位崩溃点。
实操心得:MiniGUI有一个环境变量
MINIGUI_CFG_PATH,可以指定配置文件的搜索路径。在板子上调试的时候,我习惯把MiniGUI.cfg放在跟可执行文件同一个目录下,然后export MINIGUI_CFG_PATH=.,这样就不用去改系统默认路径了,换程序的时候也方便。
4.3 性能优化与体积裁剪
MiniGUI编译出来之后,如果觉得体积太大或者运行太慢,还有不少优化空间。
体积方面,最有效的手段是裁剪不需要的功能。configure阶段可以关掉VBF字体支持、关掉不用的图形引擎、关掉不需要的输入设备支持。编译阶段可以加-Os优化选项,让编译器优先优化体积而不是速度。链接阶段可以用arm-linux-gnueabihf-strip去掉符号表和调试信息,通常能减小30%到50%的体积。
性能方面,如果板子的CPU比较弱,可以关掉一些耗时的渲染特性,比如抗锯齿、透明混合等。MiniGUI.cfg里有一项[system]段的text_rendering,设成mono比gray快,但字体边缘会有锯齿。这是一个典型的画质与性能的权衡,根据实际需求选就行。
还有一个容易被忽略的点:字体文件的选择。TrueType字体虽然好看,但渲染开销比点阵字体大得多。如果界面上的文字不多,用内嵌的点阵字体就够了,启动速度和运行流畅度都会有明显提升。MiniGUI自带的incore字体就是点阵字体,编译时开--enable-incore-res就能用。
5. 交叉编译环境的维护与复用
5.1 把编译流程脚本化
手动敲一遍编译命令,熟悉流程是可以的,但每次换目标平台或者升级库版本都重敲一遍,效率太低还容易出错。我的做法是把整个流程写成一个shell脚本,参数化目标平台和工具链路径,需要的时候改几个变量就能跑。
脚本的核心结构大概是这样的:
#!/bin/bash set -e # 任何一步失败就退出 TARGET=arm-linux-gnueabihf PREFIX=$HOME/embedded/output/arm-target SRC_DIR=$HOME/embedded/src JOBS=$(nproc) build_zlib() { cd $SRC_DIR/zlib-1.2.11 CHOST=$TARGET ./configure --prefix=$PREFIX make -j$JOBS && make install } build_libpng() { cd $SRC_DIR/libpng-1.6.37 ./configure --host=$TARGET --prefix=$PREFIX \ CPPFLAGS="-I$PREFIX/include" LDFLAGS="-L$PREFIX/lib" make -j$JOBS && make install } # ... 其他库的编译函数 build_zlib build_libpng build_libjpeg build_miniguiset -e这行很重要,它保证任何一步出错就立刻停止,不会带着错误继续往下跑。我见过有人编译脚本没加这个,zlib编译失败了但脚本继续跑,最后MiniGUI也“编译成功”了,但链接的时候一堆符号找不到,排查半天才发现是zlib根本没编出来。
脚本化还有一个好处是可重复。今天在这台机器上编成功了,明天换一台机器,把脚本拷过去,改一下工具链路径,跑一遍就能得到一模一样的结果。这比手动敲命令靠谱得多。
5.2 多目标平台的目录管理
实际项目中,经常需要同时维护多个目标平台的编译环境。比如一个项目用ARM Cortex-A7,另一个项目用ARM Cortex-A53,两者的工具链和库都不兼容。这时候目录规划就很重要了。
我的做法是在output目录下按平台建子目录:
~/embedded/output/ ├── arm-a7-gnueabihf/ │ ├── include/ │ ├── lib/ │ └── bin/ ├── arm-a53-gnueabihf/ │ ├── include/ │ ├── lib/ │ └── bin/ └── mips32el-gnueabi/ ├── include/ ├── lib/ └── bin/每个平台的库完全独立,互不干扰。编译脚本里把PREFIX设成对应的平台目录就行。源码目录可以共用,因为源码本身跟目标平台无关,只是编译产物不同。
工具链也按平台分开存放:
~/embedded/toolchain/ ├── gcc-arm-a7-gnueabihf/ ├── gcc-arm-a53-gnueabihf/ └── gcc-mips32el-gnueabi/切换平台的时候,改一下PATH和PREFIX两个环境变量,其他都不用动。这种管理方式清晰明了,不容易搞混。
注意:不同工具链的sysroot路径可能不一样,切换平台时记得同步更新SYSROOT环境变量。我一般会在每个平台的编译脚本开头显式设置SYSROOT,而不是依赖全局环境变量,这样更不容易出错。
5.3 版本升级与兼容性处理
MiniGUI的版本迭代不算快,但偶尔也会有新版本发布。升级版本的时候,最怕的就是API不兼容,之前写的代码编译不过。
我的经验是:升级之前先看ChangeLog。MiniGUI的源码包里有个ChangeLog文件,里面记录了每个版本的重大变更。如果看到“removed”或者“renamed”之类的字眼,就要警惕了,可能你正在用的某个函数被删了或者改名了。
升级的步骤建议这样走:先把新版本的源码下载下来,用同样的configure参数编译一遍,看看能不能过。如果能过,再拿一个现有的项目代码试着编译链接,看看有没有API报错。如果都通过了,再正式替换旧版本。如果编译不过,根据报错信息决定是改代码适配新版本,还是继续用旧版本。
还有一点:保留旧版本的编译产物。升级到新版本之后,旧版本的库文件不要急着删,万一新版本有问题需要回退,直接切回旧版本的PREFIX就行,不用重新编译。磁盘空间现在都很便宜,留几个版本的库文件不占多少地方。
6. 从编译到部署的完整链路
6.1 部署包的制作与传输
编译出来的库和可执行文件要传到板子上,方式有好几种。最简单的是用U盘或者SD卡拷贝,适合开发调试阶段。批量部署的时候,一般用网络传输,比如scp或者tftp。
我习惯把需要部署的文件打包成一个tar包,里面包含:
deploy/ ├── lib/ │ ├── libminigui.a │ ├── libpng.a │ ├── libjpeg.a │ └── libz.a ├── bin/ │ └── helloworld ├── etc/ │ └── MiniGUI.cfg └── fonts/ └── (字体文件)打包命令很简单:
tar czf deploy.tar.gz deploy/传到板子上之后解压到/opt/minigui/或者/usr/local/下面。具体路径取决于板子的文件系统布局和你的项目规范。
MiniGUI.cfg里的路径需要根据实际部署位置调整。比如字体路径,如果字体放在/opt/minigui/fonts/,配置里就要写这个路径。路径写错了,程序启动时会报“font not found”,然后要么用默认字体凑合,要么直接退出。
6.2 板子上的运行环境准备
板子上的运行环境比开发机简单,但有几个前提条件必须满足。
首先是framebuffer驱动。内核配置里要开CONFIG_FB和对应的显示驱动。启动之后/dev/fb0必须存在。用cat /proc/fb可以查看当前注册的framebuffer设备。
其次是输入设备。如果程序需要触摸屏或者键盘输入,/dev/input/event*设备节点必须存在。用cat /proc/bus/input/devices可以查看已注册的输入设备。MiniGUI.cfg里的[input]段需要配置对应的输入引擎,比如tslib用于触摸屏,console用于键盘。
最后是运行库。如果用的是动态链接,板子上需要有对应的.so文件。用ldd helloworld可以查看程序依赖哪些动态库。如果板子上缺库,程序启动时会报“error while loading shared libraries”。解决办法要么是把缺失的库拷到板子上,要么是改用静态链接重新编译。
实操心得:板子上的存储空间通常很有限,部署之前先用
arm-linux-gnueabihf-strip把可执行文件和库文件都strip一遍,去掉调试符号,能省不少空间。strip之后的文件如果出了问题不好调试,所以建议保留一份未strip的版本在开发机上,板子上只放strip后的版本。
6.3 自动化构建的延伸思路
如果项目需要频繁编译和部署,可以考虑把整个流程进一步自动化。比如用Makefile或者CMake来管理编译过程,用脚本自动打包和传输。
Makefile的好处是可以增量编译,改了一个文件只重编那一个,不用整个项目重来。对于MiniGUI这种代码量比较大的项目,增量编译能节省大量时间。不过交叉编译的Makefile写起来比本地编译复杂一些,需要显式指定CC、AR、RANLIB等工具。
更进一步的自动化可以用CI/CD的思路,在代码提交时自动触发编译和测试。不过嵌入式项目的CI/CD比纯软件项目麻烦,因为需要真实的硬件来跑测试。一个折中方案是在开发机上用qvfb模拟运行,先做基本的冒烟测试,通过之后再部署到真实硬件上做完整测试。
qvfb是MiniGUI自带的一个虚拟framebuffer工具,可以在X11环境下模拟一个framebuffer设备。编译MiniGUI时开--enable-qvfb选项,然后在开发机上运行qvfb,再运行MiniGUI程序,就能在窗口里看到界面效果。这对于没有硬件在手的情况下调试界面非常有用。
整个交叉编译MiniGUI的流程,从工具链准备到最终部署,涉及的知识点不少,但核心逻辑其实很清晰:用正确的工具链,按正确的顺序编译依赖库,配置正确的路径,然后编译MiniGUI本身。每一步都做到位,结果就不会差。真正花时间的往往不是编译本身,而是排查各种路径错误和版本不匹配的问题。把目录规划好、把脚本写好、把日志留好,后面的事情就顺了。