Linux文件系统目录结构
Linux必备基础
Linux构建一个deb软件安装包
本文主要来自正点原子、野火Linux教程及本人理解,若有侵权请及时联系本人删除。如果本篇对您有帮助的话希望能一键三连,万分感谢。
在 GCC和HelloWorld 中,Ubuntu编译生成了X86_64平台的Hello World程序,我们尝试ARM开发板上直接运行它。在开发板的终端上,切换至主机共享目录下的 hello_c 文件夹,可以直接执行如下命令测试:
# 以下命令在开发板上的终端上执行
# 进入共享的 NFS 目录,下面的目录根据自己的实际配置情况修改
cd /mnt/example/hello_c
# 查看是否有存在前面编译生成的文件
ls
# 执行主机编译的 x86 架构程序
./hello
如下图
如图所示,程序无法正常运行,终端提示 ARM 开发板在执行 x86 架构(Intel 或 AMD)的 hello程序时提示格式错误,原因是 x86_64 和 ARM 架构的程序不兼容,本质是由于这些 CPU 使用的指令集不同。这就是为什么需要针对芯片架构定制文件系统、软件工具,apt 使用的源也针对不同架构提供不同的软件包。前面说 Debian 系统有支持多种架构的优势,本质上就是说它的文件系统针对不同芯片架构提供了适配的软件包。
当然,对于由 Python 等跨平台语言编写的源程序,它们不需要编译,只需要使用匹配的解释器即可运行,与芯片架构甚至操作系统无关。
在 GCC和HelloWorld 中,我们编译了两个程序,分别是:
这种编译器和目标程序都是相同架构的编译过程,被称为 本地编译。
而当前我们希望的是编译器运行在 x86 架构平台上,编译生成 ARM 架构的可执行程序,这种编译器和目标程序运行在不同架构的编译过程,被称为 交叉编译。
既然已经有本地编译,为什么需要交叉编译?这是因为通常编译工具链对编译环境有较高的要求,编译复杂的程序时,可能需要巨大的存储空间以及强大的 CPU 运算能力加快编译速度。常见的 ARM 架构平台资源有限,无论是存储空间还是 CPU 运算能力,都与 X86 平台相去甚远,特别是对于 MCU 平台,安装编译器根本无从谈起。有了交叉编译,我们就可以在 PC 上快速编译出针对其他架构的可执行程序。
相对的,能进行架构“交叉”编译过程的编译器,就被称为 交叉编译器(Cross compiler)。交叉编译器听起来是个新概念,但在 MCU 开发中一直使用的就是交叉编译器,例如开发 STM32、RT1052 所使用的 IDE 软件 Keil(MDK)或 IAR,就是在 Windows x86 架构编译,生成 MCU 平台的应用程序,最后下载到板子执行。
安装交叉编译工具链有如下三种方式:
本书中提供的示例程序对编译器版本没什么要求,为简便起见,此处直接在 Ubuntu 系统下使用APT 包管理工具安装。
本书使用的编译器主要有两种类型:
不过在开发中比较多的开发者对所有程序都直接用 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 的各种工具。
安装后包含的 Binutils 工具集
安装好交叉编译器后,直接使用它对 Hello World 程序进行交叉编译即可。
交叉编译器与本地编译器使用起来并没有多大区别。对于源文件的编译过程,都是四个阶段:预处理,编译,汇编以及链接,区别只在于编译工具。因此,我们可以依葫芦画瓢,修改一下前面GCC 编译章节的命令,就可以完成这个过程。
在主机上执行如下命令对 Hello World 程序进行交叉编译
# 以下命令在主机上运行
# 在 hello.c 程序所在的目录执行如下命令
arm-linux-gnueabihf-gcc hello.c –o hello
同样的 C 代码文件,使用交叉编译器编译后,生成的 hello 已经变成了 ARM 平台的可执行文件,可以通过 readelf 工具来查看具体的程序信息。
readelf 工具在系统安装 GCC 编译工具链时一起被安装了,我们可以直接使用。在主机上执行以下命令:
# 以下命令在主机上运行
readelf -a hello
使有 readelf 查看交叉编译器生成的 hello 程序,可看到 hello 程序的系统架构为 ARM 平台,标志中表明了它是 hard-float 类型的 EABI 接口。编译好后,即可尝试在开发板上运行,在开发板的终端执行以下命令,执行结果如下图所示。
# 以下命令在开发板上的终端上执行
# 切换至共享的 NFS 目录,下面的目录根据自己的配置修改
cd /mnt/example/hello_c
# 查看是否有存在前面编译生成的文件
ls
# 执行主机编译的 ARM 平台程序
./hello
除了我们安装的 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 架构类型,最后是具体的编译器平台。
编译器的命名没有严格的规则,但它们的名字中一般包含我们最关心的内容,可根据它们的名字选择要使用的编译器:arch [-os] [-(gnu)eabi(hf)] -gcc
其中的各字段如下表所示:
GCC 编译器命名格式
以我们安装的 arm-linux-gnueabihf-gcc 编译器为例,表示它的目标芯片架构为 ARM,目标操作系统为 Linux,使用 GNU 的 C 标准库即 glibc,使用嵌入式应用二进制接口(eabi),编译器的浮点模式为硬浮点 hard-float。而另一种名为 arm-linux-gnueabi- gcc 的编译器与它的差别就在于是否带“hf”,不带“hf”表示它使用 soft-float 模式。
关于编译器的各个字段详细说明如下:
讲解编译器类型时提到,编译器名字中带 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-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
可以看到结果与前面的差异在于此处的是 soft-float 类型,而前面的是 hard-float 类型,这正是编译器类型不同导致的。编译好后,尝试在开发板上运行该程序,在开发板的终端执行以下命令。
# 以下命令在开发板上的终端上执行
# 切换至共享的 NFS 目录,下面的目录根据自己的配置修改
cd /mnt/example/hello_c
# 查看是否有存在前面编译生成的文件
ls
# 执行主机编译的 ARM 平台程序,soft-float 类型
./hello
很遗憾,使用 arm-linux-gnueabi-gcc 软浮点编译的程序无法正常执行,它提示找不到文件或目录,这是因为程序内部调用了软浮点的类库(如 glibc 库文件 libc.so.6),而开发板配套的库文件是硬浮点类型的。
关于库文件的类型,同样可以使用 readelf 工具查看,在开发板中执行以下命令
# 使用 readelf 查看开发板的 libc.so.6 类型
readelf -h libc.so.6
表示开发板的 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
可看到使用静态编译得到的 hello_static 程序比动态编译的 hello 大,这是因为它自身包含了库文件,使用 readelf 也可以看到 hello_static 程序依然是 soft-float 类型的。
接着尝试在开发板中执行生成的 hello_static 静态程序:
# 以下命令在开发板上的终端上执行
# 切换至共享的 NFS 目录,下面的目录根据自己的配置修改
cd /mnt/example/hello_c
# 查看是否有存在前面编译生成的文件
ls
# 执行主机编译的 ARM 平台程序,soft-float 类型,静态可执行文件
./hello_static