ARM-GCC和开发板的HelloWorld

文章目录

  • 系列文章推荐
  • 前言
  • 在ARM板上运行x86_64平台的程序
  • 安装并使用交叉编译工具链
    • 使用 APT 安装 ARM-GCC
    • 交叉编译 Hello World 程序
  • 如何选择编译器
  • 编译器类型对程序的影响
    • 安装软浮点编译器
    • 运行软浮点动态编译的程序
    • 开发板的 glibc 库类型
    • 运行软浮点静态编译的程序

系列文章推荐

Linux文件系统目录结构
Linux必备基础
Linux构建一个deb软件安装包

前言

本文主要来自正点原子、野火Linux教程及本人理解,若有侵权请及时联系本人删除。如果本篇对您有帮助的话希望能一键三连,万分感谢。

在ARM板上运行x86_64平台的程序

在 GCC和HelloWorld 中,Ubuntu编译生成了X86_64平台的Hello World程序,我们尝试ARM开发板上直接运行它。在开发板的终端上,切换至主机共享目录下的 hello_c 文件夹,可以直接执行如下命令测试:

# 以下命令在开发板上的终端上执行
# 进入共享的 NFS 目录,下面的目录根据自己的实际配置情况修改
cd /mnt/example/hello_c
# 查看是否有存在前面编译生成的文件
ls
# 执行主机编译的 x86 架构程序
./hello

如下图
ARM-GCC和开发板的HelloWorld_第1张图片如图所示,程序无法正常运行,终端提示 ARM 开发板在执行 x86 架构(Intel 或 AMD)的 hello程序时提示格式错误,原因是 x86_64 和 ARM 架构的程序不兼容,本质是由于这些 CPU 使用的指令集不同。这就是为什么需要针对芯片架构定制文件系统、软件工具,apt 使用的源也针对不同架构提供不同的软件包。前面说 Debian 系统有支持多种架构的优势,本质上就是说它的文件系统针对不同芯片架构提供了适配的软件包。

当然,对于由 Python 等跨平台语言编写的源程序,它们不需要编译,只需要使用匹配的解释器即可运行,与芯片架构甚至操作系统无关。

安装并使用交叉编译工具链

在 GCC和HelloWorld 中,我们编译了两个程序,分别是:

  • 编译器运行在 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 平台的应用程序,最后下载到板子执行。

使用 APT 安装 ARM-GCC

安装交叉编译工具链有如下三种方式:

  • 直接在 Ubuntu 下使用 APT 包管理工具下载安装,操作简单,在本章节我们直接使用该方式安装即可。
  • 自行下载第三方制作好的工具链,如 Linaro,好处是选择丰富,能找到很多不同的版本。
  • 使用 crosstool-ng 根据需要自己制作,过程复杂,不推荐。

本书中提供的示例程序对编译器版本没什么要求,为简便起见,此处直接在 Ubuntu 系统下使用APT 包管理工具安装。

本书使用的编译器主要有两种类型:

  • arm-linux-gnueabihf-gcc:名称中的 Linux 表示目标应用程序是运行在 Linux 操作系统之上的,例如前面的 hello.c 程序。
  • arm-none-eabi-gcc,名称中的 none 表示无操作系统,目标应用程序的运行环境是不带操作系统的,例如裸机代码、uboot、内核代码本身。

不过在开发中比较多的开发者对所有程序都直接用 arm-linux-gnueabihf-gcc 来编译,包括裸机代码和 uboot,虽然可能因为代码本身没有调用到 Linux 相关的内容而不会出错,但这样做不够严谨,条件允许的话,我们还是严格区分开来。

本章节示例代码只需要使用 arm-linux-gnueabihf-gcc 编译器,可通过 APT 包管理工具可直接执行以下命令安装:

# 在主机上执行如下命令
sudo apt install gcc-arm-linux-gnueabihf
# 安装完成后使用如下命令查看版本
arm-linux-gnueabihf-gcc –v

可以看到下图的内容,它表明交叉编译工具链安装成功了,输出信息表明了它是 7.4.0 版本的编译器,其中的“Target:arm-linux-gnueabihf”也表明了它的目标架构。

安装完成后输入“arm-linux-gnueabihf-”,再按两下 TAB 键,终端会提示可用的相关命令,如下图包含了 ARM-GCC 工具链 Binutils 的各种工具。
ARM-GCC和开发板的HelloWorld_第2张图片
安装后包含的 Binutils 工具集

交叉编译 Hello World 程序

安装好交叉编译器后,直接使用它对 Hello World 程序进行交叉编译即可。

交叉编译器与本地编译器使用起来并没有多大区别。对于源文件的编译过程,都是四个阶段:预处理,编译,汇编以及链接,区别只在于编译工具。因此,我们可以依葫芦画瓢,修改一下前面GCC 编译章节的命令,就可以完成这个过程。

在主机上执行如下命令对 Hello World 程序进行交叉编译

# 以下命令在主机上运行
# 在 hello.c 程序所在的目录执行如下命令
arm-linux-gnueabihf-gcc hello.c –o hello

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

readelf 工具在系统安装 GCC 编译工具链时一起被安装了,我们可以直接使用。在主机上执行以下命令:

# 以下命令在主机上运行
readelf -a hello

ARM-GCC和开发板的HelloWorld_第3张图片
使有 readelf 查看交叉编译器生成的 hello 程序,可看到 hello 程序的系统架构为 ARM 平台,标志中表明了它是 hard-float 类型的 EABI 接口。编译好后,即可尝试在开发板上运行,在开发板的终端执行以下命令,执行结果如下图所示。

# 以下命令在开发板上的终端上执行
# 切换至共享的 NFS 目录,下面的目录根据自己的配置修改
cd /mnt/example/hello_c
# 查看是否有存在前面编译生成的文件
ls
# 执行主机编译的 ARM 平台程序
./hello

ARM-GCC和开发板的HelloWorld_第4张图片

如何选择编译器

除了我们安装的 arm-linux-gnueabihf-gcc 外,编译器还有很多版本,如 arm-linux-gnueabi-gcc,《GCC和Hello World》章节中使用的本地编译器 gcc 全名为 x86_64-linux-gnu-gcc。

目前大部分 ARM 开发者使用的都是由 Linaro 组织提供的交叉编译器,包括前面使用 APT 安装的 ARM-GCC 工具链,它的来源也是 Linaro。Linaro 是由 ARM 发起,与其它 ARM SOC 公司共同投资的非盈利组织。我们可以在它官网的如下地址找到它提供的 ARM 交叉编译器:https://releases.linaro.org/components/toolchain/binaries/ ,如下图所示,在它提供的编译器列表中首先选择版本号,然后可选择 ARM 架构类型,最后是具体的编译器平台。
ARM-GCC和开发板的HelloWorld_第5张图片
ARM-GCC和开发板的HelloWorld_第6张图片
编译器的命名没有严格的规则,但它们的名字中一般包含我们最关心的内容,可根据它们的名字选择要使用的编译器:arch [-os] [-(gnu)eabi(hf)] -gcc

其中的各字段如下表所示:
GCC 编译器命名格式
ARM-GCC和开发板的HelloWorld_第7张图片
以我们安装的 arm-linux-gnueabihf-gcc 编译器为例,表示它的目标芯片架构为 ARM,目标操作系统为 Linux,使用 GNU 的 C 标准库即 glibc,使用嵌入式应用二进制接口(eabi),编译器的浮点模式为硬浮点 hard-float。而另一种名为 arm-linux-gnueabi- gcc 的编译器与它的差别就在于是否带“hf”,不带“hf”表示它使用 soft-float 模式。

关于编译器的各个字段详细说明如下:

  • 目标芯片架构
    目标芯片架构就是指交叉编译器生成的程序运行的平台,如 ARM、MIPS,其中 ARM 交叉编译器又分为 ARMv7、ARMv8 及 aarch64 架构。本书使用的 i.MX 6ULL 的内核为 Cortex-A7,它使用的是 ARMv7 架构。arm-linux-gnueabihf-gcc 直接以 arm 表示 ARMv7 架构。
  • 大小端
    指目标芯片的大小端模式,i.MX 6ULL 使用的是小端模式。若是大端模式(big edian),编译器名字中会带“be”或“eb”字段进行标标注。
  • 目标操作系统
    目标操作系统表示编译后的程序运行的系统,主要有 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,所以才不会出错。
  • C标准库类型
    C 标准库类型通常有 gnu、uclibc 等,分别表示 GNU 的 glibc 库和 uclibc 库,这取决于目标操作系统提供的 C 库类型,不过由于 glibc 和 uclibc 库是兼容的,所以开发者在编通常直接使用 GNU 类型的编译器而不管目标系统中的 C 库类型。除了裸机编译器不带 C 库之外,其它编译器的 C 库类型都是 glibc 库的,如 arm-linux-gnueabihf-gcc。
  • 应用二进制接口
    应用二进制接口(Application Binary Interface),描述了应用程序和操作系统之间或其他应用程序的低级接口。在编译器选项中主要有“abi”和“eabi”两种类型,abi 通常用在 x86 架构上,而eabi 表示 embed abi,即嵌入式架构,如 ARM、MIPS 等。
  • 浮点模式
    部分 ARM 处理器带浮点运算单元,代码需要进行浮点运算时若交给 fpu 处理,可以加快运算速度。编译器针对浮点运算的不同处理情况提供了以下几种模式:
    一、hard:硬浮点类型(hard-float),采用 fpu 参与浮点运算。arm-linux-gnueabihf-gcc、armeb-linuxgnueabihf-gcc 都是硬浮点类型,即名字中带“hf”。
    二、soft:软浮点类型(soft-float),即使有 fpu 浮点运算单元也不用,而是使用软件模式,armlinux-gnueabi-gcc、armeb-linux-gnueabi-gcc 都是软浮点类型,即名字中不带“hf”。
    三、softfp:允许使用浮点指令,但保持与软浮点 ABI 的兼容性。

    i.MX6ULL 带有 fpu,对于 soft-float 和 hard-float 模式都支持,不过本开发板中提供 Linux 文件系统中的库都是使用 hard 模式编译的,所以编写应用程序时也需要使用相同类型的编译器,否则会应用程序运行时会提示找不到库文件。
  • 编译器版本号
    通常来说高版本的编译器是向后兼容的,但开发特定程序时会使用固定的某个版本编译器,所以程序可能会依赖该版本的编译器,根据自己要编译的程序的要求选择即可。

编译器类型对程序的影响

讲解编译器类型时提到,编译器名字中带 hf 和不带 hf 的差异为硬浮点和软浮点模式,此处通过小实验来进行讲解,对比两种编译器对同样程序的影响。

安装软浮点编译器

首先安装浮点模式为 soft-float 类型的编译器,即 arm-linux-gnueabi-gcc,它与前面使用的 arm-linux-gnueabihf-gcc 差异为编译器名字不带“hf”:

# 在主机上执行如下命令
sudo apt install gcc-arm-linux-gnueabi
# 安装完成后使用如下命令查看版本
arm-linux-gnueabi-gcc -v

ARM-GCC和开发板的HelloWorld_第8张图片

运行软浮点动态编译的程序

安装好 arm-linux-gnueabi-gcc 软浮点编译器后,继续使用 hello.c 程序进行实验。切换至前面 hello.c 的目录,使用不带“hf”的软浮点编译器重新编译:

# 以下命令在主机上运行
# 在 hello.c 程序所在的目录执行如下命令,注意编译器名字不带 hf
sudo arm-linux-gnueabi-gcc hello.c –o hello

此处我们使用的是同样的 hello.c 代码文件,只是编译器的类型不同,再次通过 readelf 工具来查看具体的程序头信息,在主机上执行以下命令:

# 以下命令在主机上运行
readelf -h hello

ARM-GCC和开发板的HelloWorld_第9张图片
可以看到结果与前面的差异在于此处的是 soft-float 类型,而前面的是 hard-float 类型,这正是编译器类型不同导致的。编译好后,尝试在开发板上运行该程序,在开发板的终端执行以下命令。

# 以下命令在开发板上的终端上执行
# 切换至共享的 NFS 目录,下面的目录根据自己的配置修改
cd /mnt/example/hello_c
# 查看是否有存在前面编译生成的文件
ls
# 执行主机编译的 ARM 平台程序,soft-float 类型
./hello

ARM-GCC和开发板的HelloWorld_第10张图片
很遗憾,使用 arm-linux-gnueabi-gcc 软浮点编译的程序无法正常执行,它提示找不到文件或目录,这是因为程序内部调用了软浮点的类库(如 glibc 库文件 libc.so.6),而开发板配套的库文件是硬浮点类型的。

开发板的 glibc 库类型

关于库文件的类型,同样可以使用 readelf 工具查看,在开发板中执行以下命令

# 使用 readelf 查看开发板的 libc.so.6 类型
readelf -h libc.so.6

ARM-GCC和开发板的HelloWorld_第11张图片
表示开发板的 glibc 库文件 libc.so.6 为 ARM 架构的 hard-float 类型库,所以不带 hf 编译器生成的hello 程序与它不兼容,无法正常运行。

运行软浮点静态编译的程序

既然 hello 程序是因为库不兼容,那如果程序使用静态编译,即程序自带相关库的内容,是不是就可以正常运行呢?答案是可以的。我们继续进行如下测试:

在主机执行如下命令,对 hello.c 进行静态编译生成 hello_static 程序:

# 以下命令在主机上运行
# 使用不带 hf 的编译器对 hello.c 进行静态编译,生成的程序名为 hello_static
sudo arm-linux-gnueabi-gcc hello.c -o hello_static --static
# 查看生成的程序大小
ls -lh
# 查看 hello_static 文件头
readelf -h hello_static

ARM-GCC和开发板的HelloWorld_第12张图片
可看到使用静态编译得到的 hello_static 程序比动态编译的 hello 大,这是因为它自身包含了库文件,使用 readelf 也可以看到 hello_static 程序依然是 soft-float 类型的。

接着尝试在开发板中执行生成的 hello_static 静态程序:

# 以下命令在开发板上的终端上执行
# 切换至共享的 NFS 目录,下面的目录根据自己的配置修改
cd /mnt/example/hello_c
# 查看是否有存在前面编译生成的文件
ls
# 执行主机编译的 ARM 平台程序,soft-float 类型,静态可执行文件
./hello_static

如下图:
ARM-GCC和开发板的HelloWorld_第13张图片
hello_static 程序正常运行,这就是编译器及系统库文件对程序运行的影响。

你可能感兴趣的:(Linux,#,Linux基础,linux,ubuntu,arm)