LV100-ARM平台GCC
一、准备工作
这一节是直接用的正点原子 alpha-imx6ull 开发板出厂系统,按照后面搭建好 nfs 环境,也就是可以在开发板可以挂载 vmware 中 ubuntu 系统中的目录。
1. 烧写出厂系统
看这篇笔记《LV015-固化系统 | Linux》
2. 配置网络环境
看这篇《LV005-网络环境方案一 | Linux》,还有这篇:《LV020-基本功能测试 | Linux》,主要是参考 usb wifi 测试那部分,完成开发板联网,方便后面在开发板下载 gcc 编译器。
3. NFS 环境搭建
看这篇《LV025-nfs 环境 | Linux》
4. arm 平台 GCC
看这篇笔记《LV005-交叉编译简介 | Linux》。这里安装的是这个 4.9.4 版本的交叉编译工具软件包:Linaro Releases
https://releases.linaro.org/components/toolchain/binaries/4.9-2017.01/arm-linux-gnueabihf/
5. 测试文件
LV01_GCC_COMPILE/00_compile/08_armgcc_x86_64/hello.c:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int i = 0;
/* code */
printf("hello, world! This is a C program.\n");
for(i = 0; i < 10; i++)
{
printf("output i=%d\n",i);
}
return 0;
}二、在 ARM 板上运行 x86_64 平台的程序
1. 先做个测试
我们把 ubuntu 的 gcc 编译的测试程序放到开发板跑一下:
gcc hello.c -o hello -Wall
cp -pvf hello ~/4nfs/app/然后我们在开发板中执行:

发现程序无法正常运行,终端提示 ARM 开发板在执行 x86 架构(Intel 或 AMD)的 hello 程序时提示格式错误,原因是 x86_64 和 ARM 架构的程序不兼容,本质是由于这些 CPU 使用的指令集不同。这就是为什么需要针对芯片架构定制文件系统、软件工具,apt 使用的源也针对不同架构提供不同的软件包。
当然,对于由 Python 等跨平台语言编写的源程序,它们不需要编译,只需要使用匹配的解释器即可运行,与芯片架构甚至操作系统无关。
2. ARM 板上的 GCC
我们使用的开发板若是性能允许的话,是可以直接在开发板安装 gcc 的,就像在 ubuntu 中一样,只是开发板需要联网,并且支持这个 apt 下载:
sudo apt install gcc -y
sudo apt-get install gcc -y或者自己移植一个,但是把,没啥必要,这里了解一下就行了。这里用一下野火教程(教程用的是 debian 根文件系统)中的图:

GCC 工具链包括了 binutils、readelf 工具, 因此 GCC 安装完成后,binutils、readelf 等工具也可以直接使用。运行以下指令查看 GCC 版本与安装路径。
gcc -v #查看 gcc 编译器版本
which gcc #查看 gcc 的安装路径
与前面介绍的 Ubuntu 中 GCC 不一样的是,开发板中 gcc 编译工具链的目标平台是 arm 架构的,表示它生成的应用程序只能运行于 ARM 平台的开发板, 而不适合用于 X86 平台。然后我们创建一个 hello.c 测试 demo,把上面的代码复制进去,然后编译一下:
#在Debian 的 hello_c 目录下执行如下命令
gcc hello.c –o hello #使用 gcc 把 hello.c 编译成 hello 程序
ls #查看目录下的文件
./hello #执行生成的 hello 程序
#若提示权限不够或不是可执行文件,执行如下命令再运行 hello 程序
chmod u+x hello #给hello 文件添加可执行权限
3. 交叉编译工具链
3.1 交叉编译
我们编译了两个程序,分别是:
- 编译器运行在 X86_64 架构平台上,编译生成 X86_64 架构的可执行程序
- 编译器运行在 ARM 架构平台上,编译生成 ARM 架构的可执行程序
这种编译器和目标程序都是相同架构的编译过程,被称为 本地编译 。而当前我们希望的是编译器运行在 x86 架构平台上,编译生成 ARM 架构的可执行程序,这种编译器和目标程序运行在不同架构的编译过程,被称为 交叉编译。
既然已经有本地编译,为什么需要交叉编译?这是因为通常编译工具链对编译环境有较高的要求,编译复杂的程序时,可能需要巨大的存储空间以及强大的 CPU 运算能力加快编译速度。常见的 ARM 架构平台资源有限,无论是存储空间还是 CPU 运算能力,都与 X86 平台相去甚远,特别是对于 MCU 平台,安装编译器根本无从谈起。有了交叉编译,我们就可以在 PC 上快速编译出针对其他架构的可执行程序。
相对的,能进行架构“交叉”编译过程的编译器,就被称为 交叉编译器(Cross compiler)。 交叉编译器听起来是个新概念,但在 MCU 开发中一直使用的就是交叉编译器, 例如开发 STM32、RT1052 所使用的 IDE 软件 Keil(MDK)或 IAR,就是在 Windows x86 架构编译,生成 MCU 平台的应用程序,最后下载到芯片执行。
3.2 安装 ARM-GCC
安装交叉编译工具链有如下三种方式:
- 直接在 Ubuntu 下使用 APT 包管理工具下载安装,操作简单。
- 自行下载第三方制作好的工具链,如 Linaro,好处是选择丰富,能找到很多不同的版本。(像之前我就装过一个)
- 使用 crosstool-ng 根据需要自己制作,过程复杂,不推荐。
使用 apt 管理工具下载安装时命令如下:
# 在主机上执行如下命令
sudo apt install gcc-arm-linux-gnueabihf
sudo apt install gcc-arm-linux-gnueabi
# 安装完成后使用如下命令查看版本
arm-linux-gnueabihf-gcc -v
arm-linux-gnueabi-gcc -v不过我这里用的是自己从 Linaro 官网下载的:

版本是这样的:
使用内建 specs。
COLLECT_GCC=arm-linux-gnueabihf-gcc
COLLECT_LTO_WRAPPER=/home/sumu/2software/gcc-linaro-4.9.4/bin/../libexec/gcc/arm-linux-gnueabihf/4.9.4/lto-wrapper
目标:arm-linux-gnueabihf
配置为:#这里省略...
线程模型:posix
gcc 版本 4.9.4 (Linaro GCC 4.9-2017.01)后边会用到另一种软浮点类型的交叉编译工具,这里直接也安装一下,我们打开 Linaro Releases

选择这个不带 hf 的下载安装,我们同样选择 4.9 版本:
cd ~/2software/
wget -c https://releases.linaro.org/components/toolchain/binaries/4.9-2017.01/arm-linux-gnueabi/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi.tar.xz
tar xvf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi.tar.xz
rm -rf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi.tar.xz
echo "export PATH=$PATH:/home/sumu/2software/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin" >> ~/.bashrc
source ~/.bashrc我们重新开一个终端,看一下版本:
sumu@sumu-virtual-machine:~$ arm-linux-gnueabi-gcc -v
使用内建 specs。
COLLECT_GCC=arm-linux-gnueabi-gcc
COLLECT_LTO_WRAPPER=/home/sumu/2software/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin/../libexec/gcc/arm-linux-gnueabi/4.9.4/lto-wrapper
目标:arm-linux-gnueabi
配置为:#这里省略...
线程模型:posix
gcc 版本 4.9.4 (Linaro GCC 4.9-2017.01)所以目前,我的虚拟机中有两个交叉编译工具链:

3.3 交叉编译 hello.c
交叉编译器与本地编译器使用起来并没有多大区别。对于源文件的编译过程,都是四个阶段:预处理,编译,汇编以及链接,区别只在于编译工具。因此,我们可以依葫芦画瓢,修改一下前面 GCC 编译章节的命令,就可以完成这个过程。
在 ubuntu 上执行如下命令对 Hello World 程序进行交叉编译:
# 以下命令在主机上运行
# 在 hello.c 程序所在的目录执行如下命令
arm-linux-gnueabihf-gcc hello.c然后我们直接执行:

发现是不行的,我们到开发板试一下:

3.4 readelf 查看一下
同样的 C 代码文件,使用交叉编译器编译后,生成的 hello 已经变成了 ARM 平台的可执行文件,可以通过 readelf 工具来查看具体的程序信息。

看到 hello 程序的系统架构为 ARM 平台,标志中表明了它是 hard-float 类型的 EABI 接口。
三、如何选择编译器
1. 去哪下载编译器?
除了我们安装的 arm-linux-gnueabihf-gcc 外,编译器还有很多版本,如 arm-linux-gnueabi-gcc。目前大部分 ARM 开发者使用的都是由 Linaro 组织提供的交叉编译器,包括前面使用 APT 安装的 ARM-GCC 工具链,它的来源也是 Linaro。Linaro 是由 ARM 发起,与其它 ARM SOC 公司共同投资的非盈利组织。我们可以在它官网的如下地址找到它提供的 ARM 交叉编译器:Linaro Releases

2. 怎么选择?
编译器的命名没有严格的规则,但它们的名字中一般包含我们最关心的内容,可根据它们的名字选择要使用的编译器:
arch [-os] [-(gnu)eabi(hf)] -gcc其中的各字段如下所示:
| 字段 | 含义 |
|---|---|
| arch | 目标芯片架构 |
| os | 操作系统 |
| gnu | C 标准库类型 |
| eabi | 应用二进制接口 |
| hf | 浮点模式 |
以我们安装的 arm-linux-gnueabihf-gcc 编译器为例,表示它的目标芯片架构为 ARM,目标操作系统为 Linux,使用 GNU 的 C 标准库即 glibc,使用嵌入式应用二进制接口(eabi),编译器的浮点模式为硬浮点 hard-float。而另一种名为 arm-linux-gnueabi- gcc 的编译器与它的差别就在于是否带“hf”,不带“hf”表示它使用 soft-float 模式。
2.1 目标芯片架构
目标芯片架构就是指交叉编译器生成的程序运行的平台,如 ARM、MIPS,其中 ARM 交叉编译器又分为 ARMv7、ARMv8 及 aarch64 架构。这里使用的 i.MX 6ULL 的内核为 Cortex-A7,它使用的是 ARMv7 架构。 arm-linux-gnueabihf-gcc 直接以 arm 表示 ARMv7 架构。
2.2 大小端
目标芯片的大小端模式,i.MX 6ULL 使用的是小端模式。若是大端模式(big edian),编译器名字中会带“be”或“eb”字段进行标标注。
2.3 目标操作系统
目标操作系统表示编译后的程序运行的系统,主要有 Linux 或 bare-metal(无操作系统)两种,arm-linux-gnueabi-gcc 表示它目标程序的运行环境为 Linux 系统,程序可以使用 Linux 下的 C 标准库或 Linux 内核提供的 API,如 fork 等进程函数。而 arm- eabi-gcc 或 arm-none-eabi-gcc 表示它们的目标程序运行在无操作系统的环境中。
所以严格来说,我们编译 Linux 应用程序时应该使用带“linux”的编译器,而编译 uboot、裸机程序时,应该使用“bare-metal”类型的裸机编译器,但很多开发者常常把它们混用也没有出现问题,这一般是因为开发者编写的裸机程序本身就没有使用到 Linux 系统提供的 API,所以才不会出错。
2.4 C 标准库类型
C 标准库类型通常有 gnu、uclibc 等,分别表示 glibc 库(GNU 项目)和 uclibc 库(一个独立开源项目,一种针对没有内存管理单元 MMU 的嵌入式 Linux 开发的 C 标准库),这取决于目标操作系统提供的 C 库类型。
由于 glibc 和 uclibc 库在源代码级兼容性上 大部分兼容,但不完全兼容,在二进制级兼容性上 不兼容。我们不能拿针对 glibc 编译好的动态库或可执行文件,直接放到 uClibc 的系统上去运行,反之亦然。
除了裸机编译器不带 C 库之外,其它编译器的 C 库类型大部分都是 glibc 库的,如 arm-linux-gnueabihf-gcc,反正针对于芯片来说,厂家一定会提供适配的交叉编译工具链和 SDK 开发套件,这个使用芯片厂家的就可以了。
2.5 应用二进制接口
应用二进制接口(Application Binary Interface),描述了应用程序和操作系统之间或其他应用程序的低级接口。在编译器选项中主要有“abi”和“eabi”两种类型,abi 通常用在 x86 架构上,而 eabi 表示 embed abi,即嵌入式架构,如 ARM、MIPS 等。
2.6 浮点模式
部分 ARM 处理器带浮点运算单元,代码需要进行浮点运算时若交给 fpu 处理,可以加快运算速度。编译器针对浮点运算的不同处理情况提供了以下几种模式:
- hard: 硬浮点类型(hard-float),采用 fpu 参与浮点运算。 arm-linux-gnueabihf-gcc、armeb-linux-gnueabihf-gcc 都是硬浮点类型,即名字中带“hf”。
- soft:软浮点类型(soft-float),即使有 fpu 浮点运算单元也不用,而是使用软件模式,arm-linux-gnueabi-gcc、armeb-linux-gnueabi-gcc 都是软浮点类型,即名字中不带“hf”。
- softfp:允许使用浮点指令,但保持与软浮点 ABI 的兼容性。
i.MX6ULL 带有 fpu,对于 soft-float 和 hard-float 模式都支持,不过开发板中提供 Linux 文件系统中的库若是都是使用 hard 模式编译的,那么编写应用程序时也需要使用相同类型的编译器,否则会应用程序运行时会提示找不到库文件。
2.7 编译器版本号
通常来说高版本的编译器是向后兼容的,但开发特定程序时会使用固定的某个版本编译器,所以程序可能会依赖该版本的编译器,根据自己要编译的程序的要求选择即可。
四、编译器类型对程序的影响
前面学习编译器类型时提到,编译器名字中带 hf 和不带 hf 的差异为硬浮点和软浮点模式,此处我们实际验证一下,对比两种编译器对同样程序的影响。
1. 编译两种程序
- 软浮点动态编译的程序
arm-linux-gnueabi-gcc hello.c -o a_gnueabi.out -Wall
cp -pv a_gnueabi.out ~/4nfs/app/- 硬浮点动态编译的程序
arm-linux-gnueabihf-gcc hello.c -o a_gnueabihf.out -Wall
cp -pv a_gnueabihf.out ~/4nfs/app/
2. 运行两个程序
- 软浮点动态编译的程序
a_gnueabi.out
- 硬浮点动态编译的程序
a_gnueabihf.out

使用 arm-linux-gnueabi-gcc 软浮点编译的程序无法正常执行,它提示找不到文件或目录,这是因为程序内部调用了软浮点的类库(如 glibc 库文件 libc.so.6),而开发板配套的库文件是硬浮点类型的。
3. 开发板的 glibc 库类型
关于库文件的类型,同样可以使用 readelf 工具查看,在开发板中执行以下命令:
# 使用 readelf 查看开发板的 libc.so.6 类型
readelf -h /lib/libc.so.6我使用的正点原子出厂系统并不带这个工具我们可以拷贝到 nfs 挂载目录去,通过 ubuntu 中的工具查看

我们来看一下:

表示开发板的 glibc 库文件 libc.so.6 为 ARM 架构的 hard-float 类型库,所以不带 hf 编译器生成的 hello 程序与它不兼容,无法正常运行。
4. 运行软浮点静态编译的程序
既然软浮点动态编译的程序是因为库不兼容,那如果程序使用静态编译,即程序自带相关库的内容,是不是就可以正常运行呢?答案是可以的。我们继续进行如下测试:
#以下命令在主机上运行
# 使用不带 hf 的编译器对 hello.c 进行静态编译,生成的程序名为 a_gnueabi_static.out
arm-linux-gnueabi-gcc hello.c -o a_gnueabi_static.out --static -Wall
ls -lh # 查看生成的程序大小
readelf -h a_gnueabi_static.out # 查看 hello_static 文件头
可看到使用静态编译得到的 a_gnueabi_static.out 程序比动态编译的 a_gnueabi.out 大,这是因为它自身包含了库文件,使用 readelf 也可以看到 a_gnueabi_static.out 程序依然是 soft-float 类型的。我们拷贝到开发板运行一下:

这就是编译器及系统库文件对程序运行的影响。