第三章 交叉编译环境搭建:交叉编译工具链安装、环境变量配置、Makefile体系解析

好,咱们进入第三章。说实话,交叉编译环境这块,是很多新手栽跟头的地方。我见过太多人,SDK 拿到了,板子也亮了,结果卡在编译环境上,一卡就是好几天。今天我就把这块掰开了揉碎了讲清楚。

3.1 交叉编译工具链安装

先说说什么是交叉编译。说白了,就是在你的 PC 上(x86 架构)编译出能在海思芯片上(ARM 架构)运行的程序。你想想看,你的电脑是 Intel 的 CPU,海思芯片是 ARM 的,指令集都不一样,直接编译出来的东西肯定跑不了。

海思官方提供的工具链,一般放在 SDK 的 toolchain 目录下。我习惯先把它解压到一个固定的位置,比如 /opt/hisi-linux。嗯,这里要注意,路径里最好不要有中文和空格,否则后面会出一些莫名其妙的错误。

安装步骤(以 Hi3516DV300 为例):

# 解压工具链
tar -xvf arm-himix200-linux.tgz -C /opt/

# 进入解压后的目录
cd /opt/arm-himix200-linux

# 运行安装脚本
./install.sh

安装脚本跑完之后,它会自动把工具链的路径加到系统的 PATH 里。不过,我个人建议你手动确认一下,因为有些版本的脚本写得不太严谨。

小技巧: 安装完成后,执行 arm-himix200-linux-gcc -v,如果能正常显示版本号,说明装好了。如果提示「command not found」,别慌,八成是环境变量没生效。

3.2 环境变量配置

环境变量配置,说白了就是告诉系统:「嘿,我的交叉编译工具链放在这儿了,你编译的时候去那儿找。」

我一般会在 /etc/profile 或者 ~/.bashrc 里加几行配置。为什么是这两个文件?/etc/profile 是全局的,所有用户都能用;~/.bashrc 是当前用户专用的。我个人习惯用 ~/.bashrc,因为不会影响到其他同事。

# 在 ~/.bashrc 末尾添加
export PATH=/opt/arm-himix200-linux/bin:$PATH
export CROSS_COMPILE=arm-himix200-linux-
export ARCH=arm

这里解释一下这三个变量:

  • PATH:告诉系统去哪里找编译工具
  • CROSS_COMPILE:指定交叉编译器的前缀。比如你写 gcc,系统会自动补全成 arm-himix200-linux-gcc
  • ARCH:指定目标架构,这里是 ARM

配置完记得 source ~/.bashrc 让它生效。我曾经遇到过一位同事,配完环境变量没 source,然后跑来问我为什么编译不过……嗯,这种坑咱们就别踩了。

避坑指南: 我曾经在配置环境变量时,不小心把 PATH 写成了 PATH=/opt/arm-himix200-linux/bin,少写了 $PATH。结果系统原来的命令全找不到了,连 ls 都报错。正确的写法是 export PATH=/opt/arm-himix200-linux/bin:$PATH,把新路径追加到原有路径前面。

3.3 Makefile 体系解析

海思 SDK 的 Makefile 体系,说实话,刚接触的时候会觉得有点绕。它不像普通的单片机工程那样一个 Makefile 搞定,而是分了好几层。为什么要这么设计?因为海思的 SDK 要支持不同的芯片型号、不同的功能模块,如果全写在一个文件里,维护起来会非常痛苦。

咱们来看一下典型的 Makefile 层级结构:

层级 文件 作用
顶层 Makefile 定义编译目标、调用子目录 Makefile
配置层 config.mk / rules.mk 定义编译器、编译选项、链接选项
模块层 各模块下的 Makefile 编译具体的源文件,生成 .o 或 .a
应用层 sample 或 app 下的 Makefile 编译用户自己的应用程序

顶层 Makefile 一般长这样:

# 顶层 Makefile 示例
include config.mk

export CROSS_COMPILE
export ARCH

all: kernel uboot sample

kernel:
    make -C kernel

uboot:
    make -C uboot

sample:
    make -C sample

clean:
    make -C kernel clean
    make -C uboot clean
    make -C sample clean

你看,它通过 make -C 递归进入子目录去编译。每个子目录里又有自己的 Makefile。这种设计的好处是,你想单独编译内核就 make kernel,想单独编译 sample 就 make sample,互不干扰。

config.mk 这个文件很关键,它定义了整个工程的编译规则。我打开一个典型的 config.mk 给你们看看:

# config.mk 示例
CC = $(CROSS_COMPILE)gcc
AR = $(CROSS_COMPILE)ar
LD = $(CROSS_COMPILE)ld

CFLAGS = -Wall -O2 -march=armv7-a
LDFLAGS = -lm -lpthread

INCLUDE = -I$(SDK_PATH)/include \
          -I$(SDK_PATH)/mpp/include

这里定义了编译器、编译选项、链接选项和头文件路径。你想想看,如果每个子目录都自己写一遍这些,那得多麻烦?而且万一要改个编译选项,得改几十个文件。所以海思把公共的部分抽出来,放到 config.mk 里,子目录的 Makefile 只需要 include 一下就行了。

个人经验: 我在做项目时,经常需要添加第三方库。这时候不要直接改 config.mk,而是新建一个 local.mk,在里面定义自己的编译选项和库路径,然后在顶层 Makefile 里 include 它。这样既不影响 SDK 原有的配置,又能灵活扩展。说白了,就是「不要动别人的代码,加自己的东西」。

3.4 实战:编写一个简单的交叉编译 Makefile

光说不练假把式。咱们来写一个最简单的交叉编译 Makefile,编译一个 hello world。

# 文件名:Makefile
CROSS_COMPILE = arm-himix200-linux-
CC = $(CROSS_COMPILE)gcc
CFLAGS = -Wall -O2

TARGET = hello
SRCS = hello.c
OBJS = $(SRCS:.c=.o)

all: $(TARGET)

$(TARGET): $(OBJS)
    $(CC) $(CFLAGS) -o $@ $^

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

clean:
    rm -f $(TARGET) $(OBJS)

把这个 Makefile 和 hello.c 放在同一个目录下,执行 make,就会生成一个 ARM 架构的可执行文件。你可以用 file hello 命令查看它的属性:

$ file hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped

看到「ARM」两个字了吗?说明编译成功了。把这个文件拷贝到板子上,就能跑起来。

注意: 如果你在 PC 上直接执行这个 hello,会报错 cannot execute binary file: Exec format error。这是正常的,因为它是 ARM 架构的程序,在 x86 的 PC 上跑不了。别问我为什么知道,我曾经就犯过这种傻……

3.5 常见问题与排查思路

最后,我总结几个交叉编译环境搭建时的常见问题,你们遇到了可以对照着排查:

  1. 编译时报错「command not found」:八成是环境变量没配好,检查 PATH 里有没有工具链的路径。
  2. 编译时报错「undefined reference to xxx」:链接时找不到某个函数或库。检查 LDFLAGS 里有没有加 -lxxx,或者库路径有没有写对。
  3. 编译时报错「cannot find -lxxx」:链接器找不到指定的库文件。检查库文件是否存在,以及路径是否在 -L 里指定了。
  4. 编译出来的程序在板子上跑不了:用 file 命令检查一下是不是 ARM 架构的。如果不是,检查 CROSS_COMPILE 有没有设置正确。

嗯,交叉编译环境搭建这块,说白了就是「配路径、设变量、写规则」。只要把这三件事做好了,后面编译内核、编译驱动、编译应用,都是水到渠成的事。下一章咱们就进入内核编译,看看海思的 Linux 内核是怎么裁剪和配置的。