[X]关闭

[米联客-XILINX-H3_CZ08_7100] LINUX基础篇连载-07 Linux下GCC编译

文档创建者:LINUX课程
浏览次数:266
最后更新:2024-09-09
文档课程分类-AMD-ZYNQ
AMD-ZYNQ: ZYNQ-SOC » 2_LINUX应用开发
本帖最后由 LINUX课程 于 2024-9-9 13:10 编辑

软件版本:vitis2021.1(vivado2021.1)
操作系统:WIN10 64bit
硬件平台:适用XILINX Z7/ZU系列FPGA
登录“米联客”FPGA社区-www.uisrc.com视频课程、答疑解惑!

1 交叉编译
有计算机背景的人应该多多少少听到过交叉编译这个词,一些人很耳熟但是不明白是什么,也有一些人明白其实就是编译其他平台软件用的编译器。其实交叉编译的兴起与嵌入式的发展密不可分,嵌入式的短小精悍与pc通用强大的发展路线背道而驰,当需要在嵌入式设备上运行代码时,不得不借用一下通用计算机的力量。因此,什么是交叉编译,编译为何需要交叉,如何使用交叉编译,本小章都会详细一一道来。
1.1 什么是交叉编译
交叉编译就是在一个平台上生成另外一个平台上的可执行代码。比如写了一段c的代码,在win下我们可以使用Visual Studio一键编译,VS会自动生成一个.exe的可执行文件,这个可执行文件就能运行出需要的结果,但是如果我们有一个树莓派,基本就是告别Visual Studio了,而且exe文件也无法在Linux下运行。所以我们就要使用交叉编译工具,在win下生成Linux下ARM架构所可以执行的文件,当然这个文件win是运行不了的,但是却能在树莓派上正常运行。
在上面这个例子中,在win x64下使用VS编译出exe的过程我们称为本地编译,而在win下编译出其他架构系统下可执行文件的过程就是交叉编译。
严格意义上来说,交叉编译器指的是交叉编译版本的gcc。但是实际上为了叫法上的方便,我们常说的交叉编译器都是指交叉工具链,即常说的交叉编译版本的gcc。比如arm-linux-gnueabi-gcc,实际上是指包含了一系列交叉编译版本的交叉工具链(编译器,链接器,汇编工具等等),而在后文中,如无特殊指明,均用交叉编译器指的叫法代交叉工具链。
GCC是以GPL许可证所发行的自由软件,也是GNU计划的重要组成部分。GCC的起初是为GNU操作系统专门编写的一款编译器,但是由于其开源易用的特性,现已被大多数类Unix操作系统(如GNU/Linux、FreeBSD等)采纳为标准的编译器,甚至在Windows上也可以使用GCC配合VS code进行编译。GCC支持多种计算机架构的芯片,如x86、ARM、MIPS等,并已被移植到其他多种硬件平台。
下图为GCC的标志:
image.jpg
1.2 为什么使用交叉编译工具
之所以要有交叉编译,主要原因是嵌入式系统中的资源太少,其次是有时候裸机跑代码不得不使用交叉编译工具,最后在开发环境与生产环境的区别上也有一定的差异
在所要运行的目标环境中,各种资源都相对有限,所以很难进行直接的本地编译。在紧张的硬件资源的前提下,还要运行嵌入式Linux,编译出可供运行的文件是需要花费大量的时间的,另外即便是可以克服时间上的困难,想要搭建起环境所要耗费的时间精力也是十分不划算,嵌入式的资源只够嵌入式Linux系统运行,没太多剩余的资源供本地编译。
当然,如今的嵌入式系统也在突飞猛进,有一些性能较强的开发板也能进行本地编译,但是如果需要编译系统,编译大型项目,抑或是开发板本身没有系统,需要定制的话,依旧是离不开交叉编译的。
最后,嵌入式一般代表了生产环境,对于一个需要大规模部署的项目,可能会有上万块的开发板需要配置。给每块开发板都装上编译工具的行为无疑是出力不讨好,使用交叉编译好的文件可以快速拷贝投入运营。
1.3 交叉编译的流程
学习过编译原理的同学应该明白,一个c代码文件不是一下子就变成可执行文件的。想要得到一个可执行文件,我们需要经历预处理、编译、汇编、链接等流程。每个步骤都是可能由不同的工具来完成的,这些工具的集合就是交叉编译链,上面05篇1.1节已经解释过使用交叉编译器代替交叉编译链一词,但是具体情况下还是需要区分一下。
按照不同功能,我们可以画出如下这张图:
image.jpg
其中各个部分的具体分工如下:
预处理:预处理的作用是对.c文件进行处理,生成.c的扩展文件.i,这个文件可以被打开查看,可以看到原来的代码和大量的头文件,预处理的任务有以下几点:
  • 包含头文件:写在include内的所有头文件都会被包含进来,头文件内一般会存在大量的声明、宏定义,这些内容支持了代码的运行。因此,在编译前需要把这些内容与代码整合到一起再编译。
  • 宏替换:将宏替换为真实的内容,比如程序性中有使用times这个宏,那么这个宏的定义为#define times 1,程序中所有的times都会被替换为1。
  • 处理条件编译:处理类似#if、#endif 之类的代码。
  • 处理一些特殊的预处理关键字。
编译:编译阶段会将c语言翻译成汇编语言,同时这个阶段也会进行语法检查,我们得到的error与warning都是在这个阶段给出的。.i文件会被翻译成.s的汇编文件,仍旧可以打开查看,学习过汇编的同学应该会很熟悉其中的代码指令。
汇编:这个阶段的目标是将汇编翻译成纯二进制的机器指令,.s文件被汇编成.o文件,打开后全是二进制指令,基本就是完全看不懂了,不必深究其中内容的含义,需要底层优化的话使用汇编足矣。
链接:目标.o文件还不能被直接执行,链接阶段会将各个.o文件链接成一个可执行文件,可执行文件在win下的后缀为.exe,Linux下没有固定的执行文件后缀,一般约定以.out后缀为文件后缀。链接阶段需要完成的事有以下几件:
  • 将大量的.o合成一个完整的可执行文件。 .o实现相互依赖的,比如a.o中调用的函数,实现在了b.o中,如果不链接在一起的话,是无法工作的。
  • 链接时,需要加入额外的启动代码。这个启动代码是编译器添加的,main函数就是由启动代码调用的,而程序也是从启动代码开始运行的。
  • 链接为一个可执行文件时,需要进行符号解析和地址重定位。
2 交叉编译链的命名
可能有细心的读者已经发现,我们在3.2节就已经安装过两个交叉编译器了,名字分别是gcc-arm-linux-gnueabi和gcc-aarch64-linux-gnu。可以看到这两个都是gcc编译器,我们为什么需要各种各样的gcc编译器?这就涉及到了交叉编译器的命名规则与具体作用。
这些编译器的命名会按照一定的规则:
  1. arch-core-kernel-system
复制代码
  • arch: 针对的目标平台。
  • core: 使用的是哪个CPU Core,如Cortex A8,但是这一组命名比较灵活,也可能是用硬件平台的名称来命名,也可能是none。
  • kernel: 所运行的OS,常见的有Linux,bare(无OS)。
  • systen:交叉编译链所选择的库函数和目标映像的规范,如gnu,gnueabi等。其中gnu等价于glibc+oabi,gnueabi等价于glibc+eabi。
就拿我们安装的两个来说,arm-linux-gnueabi-gcc 和aarch64-linux-gnu-gcc 适用于Arm Cortex-A 系列芯片,前者针对32 位芯片,后者针对64 位芯片,针对的都是Linux,使用的是glibc 库。知道了这些,我们根据名字就能大致地推断出这个交叉编译器是不是和当前的系统匹配。
3 交叉编译器的使用
语法:
  1. gcc 选项 文件名
复制代码

参数说明:
  • -o:FILE        生成指定名称的输出文件。
  • -E:只运行C预编译器。
  • -S:运行编译,将*.i文件中源码转化为汇编代码*.s文件
  • -c:只编译并生成目标文件。
  • -g:生成调试信息。GNU调试器可利用该信息。
  • -O0:不进行优化处理。
  • -O1/-O:优化生成代码。
  • -O2:进一步优化。
  • -O3:比-O2更进一步优化,包括inline函数。
  • -w:不生成任何警告信息。
  • -Wall:生成所有警告信息。
以上仅为常用参数,gcc还有众多的参数,在此便不一一列举了。此外,gcc还能不带参数使用,可以看以下案例:
  1. gcc helloword.c  
复制代码
gcc会在当前自动生成名为a.out的文件,这个便是输出的可执行文件。使用下图所示的命令可以查看结果:
其中helloword.c的代码如下:
image.jpg
  1. #include<stdio.h>  
  2.   
  3. int main(void)  
  4. {  
  5.     printf("Hello Word!\n");  
  6.     return 0;  
  7. }
复制代码
4 手动完成编译全流程
由于gcc参数对大家有点陌生,因此本小节将会按照预处理、编译、汇编、链接的顺序用gcc来完整地模拟一遍。虽然在以后的工作中这些知识可能用不到,但是这是一个加深对gcc了解很好的材料。
为了方便理解,本小节使用以下名为helloword.c的修改代码:
  1. #include <stdio.h>  
  2. #define times 1  
  3.   
  4. int main(void)  
  5. {  
  6.     for (int i = 0; i < times; i++)  
  7.         printf("Hello Word!\n");  
  8.     return 0;  
  9. }  
复制代码
这段代码定义了一个times的宏,作用为让“Hello Word!”打印times次。首先要对这个.c文件进行预处理,输入如下指令:
  1. gcc -E helloword.c -o helloword.i  
复制代码
通过ls指令,我们看到文件大了许多,因为预处理将头文件也包含进来了:
image.jpg
通过cat helloword.i我们可以看到宏定义的地方也已经被替换了,另外前面还加入了大段的头文件:
image.jpg
此外,预处理阶段会将所有的注释都删除,所以仅仅是预处理完,我们就看不到代码的任何注释了。还有一点就是使用gcc -E的选项时,不搭配-o选项时预处理文件会直接打印在终端内,而不会作为文件保存。
接下来就是编译阶段,使用如下指令将.i的预处理文件编译为.s的汇编文件,命令:
  1. gcc -S helloword.i -o helloword.s  
复制代码
使用ls指令查看文件大小,汇编文件又变小了许多:
image.jpg
打开这个.s文件,就是熟悉的汇编的味道了:
image.jpg
在这个指令中,即使不使用-o选项,依然会在当前目录下产生.s文件。在这个阶段,所有有关语法的错误都会被指出,只有语法通过了检查才会生成所需的汇编文件。
编译的下一个阶段是汇编,使用如下指令生成目标文件:
  1. gcc -c helloword.s -o helloword.o
复制代码
依旧使用ls查看文件列表:
image.jpg
编译阶段生成的是目标文件,里面包含了机器可以读懂的机器指令,因为.o文件打开是一堆乱码,所以在此便不作展示。
最后一步是链接,通过链接我们可以将不同的.o文件链接到一起成为一个可执行文件,由于上述代码并没有函数间调用代码的操作,所以只有一个目标文件,如果有多个文件应该将它们写在一起同时链接,执行如下的指令:
  1. gcc helloword.o -o helloword.out  
复制代码
使用ls查看文件列表,可执行文件helloword.out就产生了:
image.jpg
最后执行这个.out文件,就能看见预期的结果:
image.jpg

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则