第三章 交叉编译环境搭建:交叉编译工具链安装、环境变量配置、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 常见问题与排查思路
最后,我总结几个交叉编译环境搭建时的常见问题,你们遇到了可以对照着排查:
- 编译时报错「command not found」:八成是环境变量没配好,检查 PATH 里有没有工具链的路径。
- 编译时报错「undefined reference to xxx」:链接时找不到某个函数或库。检查 LDFLAGS 里有没有加
-lxxx,或者库路径有没有写对。 - 编译时报错「cannot find -lxxx」:链接器找不到指定的库文件。检查库文件是否存在,以及路径是否在
-L里指定了。 - 编译出来的程序在板子上跑不了:用
file命令检查一下是不是 ARM 架构的。如果不是,检查 CROSS_COMPILE 有没有设置正确。
嗯,交叉编译环境搭建这块,说白了就是「配路径、设变量、写规则」。只要把这三件事做好了,后面编译内核、编译驱动、编译应用,都是水到渠成的事。下一章咱们就进入内核编译,看看海思的 Linux 内核是怎么裁剪和配置的。