1. 项目概述与核心思路拿到一块新的开发板第一件事是什么通电看灯我的习惯是先把它的“灵魂”——也就是固件——给编译出来。这就像给一台新电脑装系统只有系统跑起来了你才能在上面施展拳脚。今天要聊的就是给眺望电子的EVM-T113-S3这块基于全志T113-S3芯片的开发板从零开始编译一整套Linux SDK。全志T113-S3这颗芯片挺有意思双核A7加一个HiFi4 DSP主打的就是一个多媒体和工控的混合场景。而眺望电子提供的这套SDK就是围绕这颗芯片把U-Boot、Linux内核、文件系统、Qt图形库这些核心组件都打包好了还配好了编译脚本。我们的目标就是在一个干净的Ubuntu系统里把这些源代码变成可以烧录到板子里运行的固件镜像。这个过程听起来有点门槛但说白了就是三步搭好环境、获取代码、执行编译。难点不在于步骤有多复杂而在于环境依赖的琐碎和编译过程中可能遇到的“坑”。这篇文章我会结合我多次编译全志平台SDK的经验把每一步的操作意图、背后的原理以及最容易翻车的地方都讲清楚。无论你是刚接触嵌入式Linux的新手还是想快速上手这块板子的老鸟跟着走一遍保准你能拿到属于你自己的、热气腾腾的固件。2. 编译环境搭建奠定成功的基石编译环境是整个过程的地基地基没打牢后面楼盖得再高也容易塌。官方文档明确要求使用Ubuntu 16.04这可不是随便写的。全志的这套编译工具链比如后面会看到的gcc-linaro-5.3.1和内核构建系统对宿主机的库版本、Python版本乃至一些工具的行为有非常具体的要求。Ubuntu 16.04提供了一个相对稳定且兼容性经过验证的基础。你用18.04、20.04甚至更新的版本很可能在某个不起眼的环节比如某个Python脚本的语法、或某个32位库的链接上栽跟头错误信息还往往晦涩难懂。注意强烈建议使用物理机安装Ubuntu 16.04或者使用虚拟机如VMware、VirtualBox安装。如果使用WSLWindows Subsystem for Linux由于其文件系统性能和权限管理的特殊性极易在编译过程中出现不可预知的问题不推荐。2.1 系统准备与用户权限首先安装一个纯净的Ubuntu 16.04.7 LTS。安装时创建一个普通的用户账户比如我叫它t113user。请务必牢记整个编译过程都必须使用这个普通用户身份进行。除了第一步安装软件包需要临时提权外后续解压、配置、编译都严禁使用root用户。为什么这主要是出于安全和避免权限污染的考虑。编译过程会产生大量属于当前用户的中间文件和最终文件。如果用root编译所有产物的所有者都是root后续你可能需要sudo来操作它们徒增麻烦。更严重的是如果编译脚本有bugroot权限可能误操作系统文件。所以养成好习惯非必要不用root。2.2 依赖软件包安装这是搭建环境中最关键的一步需要安装一长串的开发工具和库。打开终端我们一条命令搞定虽然很长sudo apt-get update sudo apt-get install git gnupg flex bison gperf build-essential zip curl \ libc6-dev libncurses5-dev:i386 x11proto-core-dev libx11-dev:i386 \ libreadline6-dev:i386 libgl1-mesa-glx:i386 libgl1-mesa-dev g-multilib mingw32 \ tofrodos python-markdown libxml2-utils xsltproc zlib1g-dev:i386 gawk libssl-dev \ u-boot-tools逐项解读与避坑指南sudo apt-get update这是必须的先导步骤更新软件源列表确保能安装到最新版本的包。build-essential这是基础中的基础包含了gcc,g,make等编译C/C程序的必备工具。git用于版本管理虽然SDK是源码包但内部脚本或某些组件可能调用git。flex和bison词法和语法分析器生成工具。编译Linux内核和U-Boot时解析其配置文件如.dts设备树文件需要它们。gperf哈希函数生成器某些库的编译会用到。libncurses5-dev:i386等带:i386后缀的包这是最大的坑点全志的交叉编译工具链是32位的arm-linux-gnueabi但它运行在64位的Ubuntu上。因此我们需要安装这些库的32位i386兼容版本以便工具链能够正确链接和运行。如果漏装通常会报错“找不到 -lncurses”或“错误找不到某个32位.so文件”。libgl1-mesa-glx:i386和libgl1-mesa-dev前者是32位OpenGL运行时库后者是开发包。即使板子上用不到图形界面编译环境也可能依赖它们来处理一些依赖关系尤其是后续如果涉及Qt编译这几乎是必须的。g-multilib允许GCC编译32位和64位代码对于支持32位工具链至关重要。u-boot-tools包含了mkimage等工具用于打包和生成U-Boot可识别的镜像格式编译U-Boot时必不可少。实操心得执行这条长命令时请耐心等待并密切注意终端输出。如果遇到“无法定位软件包”的错误很可能是软件源的问题。可以检查/etc/apt/sources.list文件确保启用了universe和multiverse仓库。安装完成后可以运行dpkg -l | grep -E \libncurses|libx11\等命令确认i386版本的包是否已成功安装在架构Architecture一栏会显示i386。建议将上述命令保存为一个脚本文件方便在新环境中快速部署。3. SDK获取与目录结构解析环境准备好后我们就要把“原材料”——SDK源码包——请进来了。3.1 获取与解压源码眺望电子通常会提供SDK的压缩包如talowe-t113-S3-linux-sdk.tar.gz。你需要通过U盘、网络共享或下载的方式将其放到Ubuntu系统中。重要原则路径不能有中文或空格这是编译器的普遍要求奇怪的路径可能导致解析错误。不要放在虚拟机共享文件夹VMware或VirtualBox的共享文件夹如/mnt/hgfs/通常是挂载的其文件系统特性如符号链接支持、inode行为可能与原生ext4不同编译复杂项目时极易出错。建议放在用户家目录这是最安全、最方便的位置。权限自然属于当前用户路径也简单。我们来操作# 1. 进入当前用户的家目录 cd ~ # 2. 创建一个专门的工作目录可选但推荐 mkdir t113_work cd t113_work # 3. 假设你已经将tar.gz包放到了当前目录执行解压 # 这里-C参数指定了解压目标目录会自动创建 talowe-t113-linux-sdk 文件夹 tar -xvf talowe-t113-S3-linux-sdk.tar.gz -C talowe-t113-linux-sdk解压过程可能会花点时间因为SDK包含内核、工具链等大量文件。解压完成后进入这个目录看看cd talowe-t113-linux-sdk ls -la3.2 核心目录深度解读光看一堆文件夹容易懵我们来拆解几个最关键的brandy/这是U-Boot的源码目录。全志将他们的U-Boot称为“brandy”。里面包含了启动引导程序的所有代码负责初始化最基础的硬件如DDR内存、时钟、加载设备树和内核镜像。linux-4.9/Linux内核源码目录。版本是4.9这是一个在嵌入式领域非常成熟和稳定的长期支持LTS版本。里面包含了驱动、文件系统支持、网络协议栈等所有内核组件。buildroot/或out/…/t113/(具体看SDK组织)这里是根文件系统rootfs的源头。Buildroot是一个用于构建嵌入式Linux根文件系统的工具集。它通过配置可以非常精简地只包含你需要的软件包如busybox、tzdata、必要的库等生成一个尺寸很小的rootfs.ext4镜像。有些SDK可能直接提供预编译的根文件系统在out目录下。qt-everywhere-src-5.x.x/Qt库的源代码。如果你需要开发图形界面应用就需要编译它。Qt体积庞大编译耗时很长。tools/或toolchain/这里存放着交叉编译工具链。通常是gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi这样的目录。这是整个交叉编译的核心它是一套运行在x86_64你的电脑上但生成ARM架构代码的编译器gcc、链接器ld、调试器gdb等工具的集合。编译时系统会自动调用这个工具链下的arm-linux-gnueabi-gcc而不是系统自带的gcc。build.sh这是灵魂脚本全志SDK使用这个统一的脚本来管理所有组件的编译、配置和打包。你后面所有操作基本都围绕它进行。configs/存放着不同板型evb1, evb2等和配置linux,qt等的预设文件。当你执行./build.sh init时脚本就是读取这里的配置。理解交叉编译为什么需要这么复杂的工具链因为你的开发板Target目标机是ARM CPU而你的电脑Host宿主机是x86_64 CPU。两者指令集不同直接在电脑上编译出的程序板子根本跑不起来。交叉编译工具链就是解决这个“鸡同鸭讲”的问题它在x86电脑上“模拟”出ARM的编译环境生成ARM能执行的二进制文件。4. 完整编译流程实操详解好了环境有了代码也有了工具链也备好了可以开始“烹饪”了。全志的build.sh脚本把复杂的流程封装成了简单的命令但我们得明白每条命令背后在干什么。4.1 初始配置与全局编译这是你拿到SDK后的第一次编译必须执行。# 确保你在SDK根目录 cd ~/t113_work/talowe-t113-linux-sdk # 步骤1初始化配置 ./build.sh init执行init后脚本会交互式地提问一般是这样Which board would you like? 1. evb1 2. evb2 ... Which system would you like? 1. linux 2. qt ...对于眺望EVM-T113-S3通常选择evb1或其他对应的板型编号请以实际SDK提示为准和linux如果你只需要基础Linux系统。选择后脚本会做几件事根据你的选择从configs/目录复制对应的配置文件到顶层目录如.config。设置一些环境变量指向正确的工具链路径、内核配置、文件系统配置等。可能会下载或准备一些额外的软件包如果之前没做过。注意事项init操作通常只需要执行一次除非你更换了板型或者想换一个系统类型比如从linux换成qt。执行后可以查看当前目录下是否生成了新的配置文件或者out/目录下是否有了对应板型的文件夹结构。# 步骤2执行全局编译 ./build.sh这条命令是最重量级的。它会按照依赖关系依次编译工具链确保交叉编译工具就绪。U-Boot (brandy)编译出u-boot.bin、boot0.bin等引导文件。Linux Kernel根据配置编译内核生成zImage压缩的内核镜像和对应的设备树二进制文件*.dtb。根文件系统 (rootfs)使用Buildroot构建一个最小的根文件系统镜像rootfs.ext4。其他可选组件如Qt。这个过程耗时很长取决于你的电脑CPU性能可能从十几分钟到一小时以上。期间终端会疯狂滚动编译信息。编译现场观察你可以看到它正在进入brandy/、linux-4.9/等目录执行make。注意观察有无红色的error字样出现。黄色的warning比较常见通常可以忽略但如果是“致命错误”或“未定义的引用”编译就会停止。如果编译中途出错仔细阅读错误信息。常见的错误原因包括依赖包没装全特别是32位库、磁盘空间不足、源码包损坏、环境变量冲突等。# 步骤3打包固件 ./build.sh pack这是临门一脚。pack脚本的作用是将前面编译好的各个部件U-Boot、内核、设备树、根文件系统按照全志芯片要求的特定格式打包成一个单一的、可以直接用于烧录的镜像文件*.img。打包过程解析它会调用tools/pack/pctools/下的打包工具。将u-boot.bin、boot0.bin等处理成SRAM、DRAM的加载镜像。将zImage和dtb文件合并成boot.img。将rootfs.ext4作为系统分区。按照全志的“分区表”概念将这些镜像排列、填充最终生成一个完整的t113_linux_evb1_auto_uart0.img文件。成果验收 编译打包成功后最终的固件镜像位于out/t113_linux_evb1_auto_uart0.img同时在out/t113/evb1_auto/longan/目录下你也能找到中间产物如boot.img、rootfs.ext4等。这个.img文件就是你可以用全志的烧录工具如PhoenixSuit或Allwinner的烧录软件直接烧写到开发板eMMC或SD卡中的完整系统镜像。4.2 模块化编译只更新特定组件在开发过程中你不可能每次修改都进行长达一小时的全局编译。比如你只改了内核驱动或者只更新了某个应用程序这时候就需要模块化编译。编译U-Boot./build.sh brandy ./build.sh pack # 编译后必须重新打包新的U-Boot才会被整合进img当你修改了brandy/目录下的代码比如板级初始化文件board.c或环境变量就需要单独编译U-Boot。切记单独编译任何组件后都必须执行./build.sh pack来重新打包固件否则你的修改不会生效在最终的.img文件里。编译Linux内核./build.sh kernel ./build.sh pack这是最常用的单独编译操作。当你修改了设备树.dts文件或内核驱动代码后执行这个。它会重新编译内核模块并生成新的zImage和dtb。编译根文件系统./build.sh rootfs ./build.sh pack如果你通过修改Buildroot配置make menuconfig进入配置界面增删了软件包或者修改了overlay/目录下的文件用于覆盖默认根文件系统内容就需要重新编译根文件系统。注意这也会花费较长时间因为Buildroot要重新下载、配置、编译你选中的所有包。编译Qt./build.sh qt ./build.sh # 注意编译Qt后通常需要重新执行build.sh来整合这里根据具体SDK逻辑可能需要直接pack或需要先执行./build.sh。以实际SDK脚本为准。 ./build.sh packQt的编译极其耗时可能比编译内核还久。除非你确定需要更新Qt库本身否则不要轻易执行。通常应用程序开发是动态链接Qt库不需要重新编译Qt。模块化编译的精髓理解每个组件编译后产出的文件是什么以及pack阶段如何将它们组装起来。这能帮助你在出错时快速定位是哪个环节的问题。5. 高级配置、问题排查与效能优化掌握了基础编译我们来看看如何“玩转”这个SDK以及遇到问题时怎么解决。5.1 内核与文件系统配置定制默认配置可能不符合你的需求比如你需要增加某个内核驱动或者给根文件系统添加iperf网络测试工具。配置Linux内核# 方法1使用SDK脚本进入内核配置菜单推荐 ./build.sh kernel menuconfig这会启动一个经典的文本图形化配置界面。你可以在这里浏览和启用成千上万的驱动和内核功能。修改后保存退出配置会保存在linux-4.9/.config。之后一定要执行./build.sh kernel和./build.sh pack来编译并打包新内核。配置Buildroot根文件系统# 方法1使用SDK脚本 ./build.sh rootfs menuconfig同样会进入Buildroot的配置界面。你可以在这里选择需要的软件包配置工具链细节、系统启动脚本等。修改后保存然后执行./build.sh rootfs和./build.sh pack。向根文件系统添加自定义文件SDK通常有一个overlay/目录可能在buildroot/下或顶层目录。这个目录的结构会覆盖到最终生成的根文件系统里。例如你在overlay/root/myapp下放一个可执行文件那么打包后根文件系统的/root/目录下就会出现myapp。这是添加你自己的应用程序、脚本或配置文件的推荐方式。5.2 常见编译错误与解决方案实录即使严格按照指南也难免会遇到问题。下面是我踩过的一些坑和解决办法问题1编译过程中报错提示找不到某个头文件如fatal error: xxx.h: No such file or directory。排查思路这通常是开发依赖库的头文件没装。错误信息里会给出文件名比如ncurses.h。解决方案使用apt-file search命令查找是哪个包提供了这个头文件。首先安装apt-filesudo apt-get install apt-file sudo apt-file update。然后搜索apt-file search ncurses.h。它会告诉你需要安装libncurses5-dev。注意如果是32位工具链需要的很可能要安装libncurses5-dev:i386。问题2执行./build.sh时早期就报错提示工具链相关错误如arm-linux-gnueabi-gcc: not found。排查思路交叉编译工具链路径没设置对或者工具链本身损坏、权限不对。解决方案检查toolchain/目录是否存在里面的bin/目录下是否有arm-linux-gnueabi-gcc。检查SDK根目录下的build.sh脚本或envsetup.sh如果有是否正确地导出了工具链路径PATH环境变量。你可以手动尝试export PATH$PWD/toolchain/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabi/bin:$PATH然后再执行./build.sh。检查工具链二进制文件是否有可执行权限ls -l toolchain/.../bin/arm-linux-gnueabi-gcc。问题3编译Qt时卡在某个模块如webengine很久最后内存不足OOM被系统杀死。排查思路Qt的WebEngine模块编译需要巨大的内存可能超过8GB和交换空间。解决方案最直接在./build.sh qt menuconfig或Qt的配置阶段中取消勾选qtwebengine模块。对于很多嵌入式GUI应用根本用不到浏览器引擎。增加系统的交换空间swap。对于虚拟机可以增加虚拟内存文件的大小。使用-j参数限制并行编译任务数减少内存峰值占用。但SDK的build.sh可能内部已经控制了你可以尝试修改脚本或直接进入qt目录手动编译。问题4打包pack阶段失败提示img文件大小计算错误或dragon工具执行失败。排查思路可能是分区表配置不正确或者某个输入镜像如boot.img或rootfs.ext4不存在或异常。解决方案检查out/t113/evb1_auto/longan/目录下boot.img和rootfs.ext4等文件是否正常生成文件大小是否合理例如rootfs.ext4不应为0字节。检查tools/pack/目录下的配置文件如sys_config.fex但全志新SDK可能已改用其他格式看看分区大小设置是否与镜像大小匹配。如果根文件系统变大了可能需要调整分区配置。查看详细的错误日志通常pack脚本会在终端或某个log文件中输出更具体的信息。问题5烧录后板子无法启动串口无输出或输出乱码。排查思路这是最令人头疼的。需要分段排查。解决方案确认串口连接波特率是否设置为115200TX/RX线是否接反确认镜像正确性尝试烧录SDK提供的官方预编译镜像如果官方镜像可以启动说明你的编译环境或过程有问题。如果官方镜像也不行可能是硬件或烧录问题。分析串口输出完全无输出可能是U-Boot都没跑起来。检查编译的brandy是否正确或者烧录地址是否正确全志芯片通常有固定烧录地址。用./build.sh brandy后检查brandy/u-boot.bin是否更新。输出停在某个地方例如停在“Starting kernel ...”之后就没动静了。这很可能是内核或设备树问题。检查./build.sh kernel是否成功特别是设备树编译。确认你选择的板型evb1与你的实际硬件匹配。内核恐慌Kernel Panic串口会打印详细的错误信息如“Unable to mount root fs”。这通常是根文件系统镜像rootfs.ext4损坏、格式不对、或者内核没有配置对应的文件系统支持如ext4。检查rootfs的编译和打包过程。5.3 编译效能优化技巧全局编译一次一小时太折磨人试试这些方法提速利用ccache这是一个编译器缓存工具。如果之前编译过相同的代码再次编译时直接使用缓存能极大提升增量编译速度。在Ubuntu上安装sudo apt-get install ccache然后在SDK环境中设置export CCACHE_DIR/path/to/a/large/drive和export USE_CCACHE1具体设置方法需参考SDK文档或脚本有时SDK已集成。增加并行编译任务数build.sh脚本内部调用make时通常会使用-j参数。你可以通过环境变量控制例如在执行编译前export JOB_NUM$(nproc)让make使用与你CPU核心数相同的任务并行编译。但要注意内存消耗。使用更快的存储将SDK源码和编译输出目录放在SSD硬盘上能显著减少文件读写等待时间尤其是编译Qt或内核这种涉及大量小文件操作的任务。保持环境纯净定期清理out/目录先备份需要的镜像或执行./build.sh clean可以避免一些因中间文件冲突导致的奇怪错误但这是以牺牲编译时间为代价的。开发阶段除非必要不必频繁clean。6. 从编译到开发下一步做什么当你成功编译出第一个能启动的镜像后真正的嵌入式Linux开发之旅才刚刚开始。这里提供几个后续方向1. 应用程序开发这是最常见的需求。你不需要每次都编译整个SDK。正确姿势是在Ubuntu上安装好交叉编译工具链就是SDK里那个gcc-linaro...。编写你的C/C程序例如hello.c。使用交叉编译器编译arm-linux-gnueabi-gcc -o hello hello.c。将编译好的hello可执行文件通过overlay机制放入根文件系统或者通过TFTP、NFS、U盘等方式传到已启动的开发板文件系统中运行。调试可以使用gdbserver在板子上和arm-linux-gnueabi-gdb在电脑上进行远程调试。2. 驱动开发与内核调试如果你需要添加或修改外设驱动将驱动代码以内核模块.ko文件的形式编译。修改内核源码drivers/目录下的对应子目录并修改Kconfig和Makefile。使用./build.sh kernel menuconfig启用你的驱动选择为M模块。执行./build.sh kernel重新编译内核模块。生成的.ko文件会在out/.../目录下的模块路径中。将.ko文件拷贝到板子用insmod加载测试。3. 系统裁剪与优化默认的根文件系统可能包含很多你用不到的工具占用宝贵的存储空间尤其是SPI NOR Flash。使用./build.sh rootfs menuconfig深入Buildroot配置大胆地去掉不需要的包如Python、Perl、各种调试工具。研究内核配置关闭不需要的驱动和功能让内核体积更小启动更快。这需要对系统组件有较深了解建议在功能稳定后进行。我个人最深刻的体会是嵌入式Linux开发编译系统只是入口。最大的挑战不在于一次性能把固件编出来而在于建立一个稳定、可重复、可追溯的构建环境以及形成一套高效的“修改-编译-测试”的迭代循环。把这份指南里的步骤走通你就有了一张进入全志T113-S3世界的门票。接下来舞台交给你去调试你的硬件去编写你的应用去解决那些真正有趣的问题吧。记住串口调试信息是你最好的朋友耐心和细致是嵌入式开发最宝贵的品质。