3. 环境搭建:Linux主机环境准备、安装依赖包、配置环境变量、验证工具链是否可用、第一个交叉编译程序(Hello World)

好,咱们正式开始动手了。

这一章,说白了就是“把家伙事儿备齐”。很多初学者一上来就急着写代码,结果编译报错半天找不到原因——十有八九是环境没搭对。我当年刚入行时也吃过这个亏,折腾了一整天,最后发现是路径写错了。嗯,咱们别走这个弯路。

3.1 Linux主机环境准备

交叉编译,主机必须是Linux。我个人习惯用Ubuntu 20.04 LTS或22.04 LTS,这两个版本对嵌入式工具链的支持最友好。如果你用的是CentOS或Debian,也没问题,但包管理命令要对应调整。

先确认你的系统是64位的:

uname -m
# 输出应为 x86_64

再看一下系统版本:

cat /etc/os-release

为什么要确认这些?因为很多交叉编译工具链只提供64位版本。我曾经在32位虚拟机上折腾过,结果工具链跑不起来,白白浪费了两个小时。

3.2 安装依赖包

交叉编译需要一些基础工具。别嫌多,这些都是必需品。

在Ubuntu上,执行:

sudo apt update
sudo apt install -y build-essential
sudo apt install -y git wget curl
sudo apt install -y libncurses5-dev
sudo apt install -y flex bison
sudo apt install -y texinfo
sudo apt install -y gawk
sudo apt install -y python3 python3-pip

这里我重点说一下 build-essential。它包含了gcc、g++、make等核心编译工具。如果你连这个都没装,后面所有操作都白搭。

另外,libncurses5-dev 是用于配置内核时的菜单界面。你想想看,如果没有它,配置内核时只能靠手写文本,那效率得多低。

我的小技巧: 每次搭建新环境,我都会先跑一遍这个安装命令。别偷懒,少装一个包,后面可能就要花半小时排查。

3.3 配置环境变量

工具链装好后,需要让系统知道它在哪里。环境变量就是系统的“通讯录”。

假设你的交叉编译工具链安装在 /opt/toolchains/gcc-arm-none-eabi-10.3-2021.10 目录下。那么,在 ~/.bashrc~/.zshrc 中添加:

export PATH=$PATH:/opt/toolchains/gcc-arm-none-eabi-10.3-2021.10/bin
export CROSS_COMPILE=arm-none-eabi-
export ARCH=arm

然后执行:

source ~/.bashrc

这里有个坑,我必须要提醒你:

我曾经犯过的错: 把PATH写成了 export PATH=/opt/toolchains/.../bin,注意是 $PATH: 开头,不是直接等号。否则系统原有的命令全找不到了,连ls、cd都会报错。那场面,真是欲哭无泪。

CROSS_COMPILE 这个变量,很多Makefile会用它来拼接编译器名字。比如 $(CROSS_COMPILE)gcc 就变成了 arm-none-eabi-gcc。你想想看,这样多方便,换一个平台只需要改这一个变量。

3.4 验证工具链是否可用

环境配好了,咱们得验证一下。别等到编译时才发现工具链有问题。

先看版本:

arm-none-eabi-gcc --version

正常会输出类似:

arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10.3-2021.10) 10.3.1 20210824
Copyright (C) 2020 Free Software Foundation, Inc.

再看一下它能不能编译:

echo 'int main() { return 0; }' | arm-none-eabi-gcc -x c - -o /tmp/test.elf
file /tmp/test.elf

输出应该包含 ELF 32-bit LSB executable, ARM 字样。这说明你编译出来的确实是ARM架构的程序,不是x86的。

关键点: 如果 file 命令显示的是 x86-64,那说明你用的还是本机gcc,不是交叉编译器。赶紧检查PATH顺序。

3.5 第一个交叉编译程序(Hello World)

好了,环境没问题,咱们写个Hello World庆祝一下。

新建文件 hello.c

#include <stdio.h>

int main()
{
    printf("Hello from ARM!\n");
    return 0;
}

然后编译:

arm-none-eabi-gcc -o hello.elf hello.c

等等,这里有个问题。如果你直接这样编译,大概率会报错:

undefined reference to `_exit'
undefined reference to `_sbrk'
...

为什么会这样?因为嵌入式环境没有操作系统帮你打理这些底层函数。你需要指定链接脚本和启动文件。

对于裸机程序,正确的编译方式应该是:

arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb hello.c -o hello.o
arm-none-eabi-ld -T link.ld hello.o -o hello.elf

但咱们现在只是验证工具链,可以加个 --specs=nosys.specs 参数,让编译器用半主机模式(semihosting):

arm-none-eabi-gcc --specs=nosys.specs -o hello.elf hello.c

这样就能编译通过了。查看生成的elf文件:

arm-none-eabi-objdump -d hello.elf | head -20

你会看到ARM汇编指令,比如 push {r7, lr}bl printf 等。这说明你的交叉编译工具链真的在工作了。

我的习惯: 每搭建一个新平台,我都会用这个Hello World做“冒烟测试”。它能快速暴露90%的环境问题。如果连Hello World都跑不通,后面的项目代码就更别想了。

本章小结

咱们这一章做了四件事:

  • 准备了Linux主机环境
  • 安装了编译依赖包
  • 配置了环境变量
  • 验证了工具链并编译了第一个ARM程序

嗯,到这里,你的开发环境已经就绪了。下一章,咱们会深入Makefile,看看如何用自动化脚本管理这些编译过程。到时候你会发现,手动敲命令的日子一去不复返了。

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