1. 前言
Linux内核主要的编写语言是C语言和汇编语言.因此,在学习Linux内核前的必要条件是熟悉C语言,因为Linux是全球顶尖的程序员编写的,里边可能涉及许多的C语言的编写技巧.
Linux内核使用的是GCC编译器来编译,了解和熟悉GCC编译器和GDB调试器也是很有必要的.
2. GCC
GCC是GNU Linux操作系统的默认编译器,而且GCC还支持多种不同硬件平台,如x86, ARM 等体系结构.
GCC的编译流程主要分为4个步骤:
- 预处理
用C语言编写 test 程序的源代码 test.c .首先进入GCC的预处理器 cpp 进行预处理,把头文件,宏进行展开,生成 test.i 文件.
- 编译
进入GCC编译器后,由于GCC支持多种编程语言,在这里调用C语言的编译器 ccl .编译完成后生成汇编程序,输出 test.s 文件
- 汇编
在汇编阶段,GCC调用汇编器 as 进行汇编,生成可重定位的目标程序 test.o .
- 链接
GCC调用连接器 ld 把所有的目标文件和C语言的标准库链接成可执行的二进制文件 test .
由此可见, C语言代码需要经过两次编译和一次链接后才能生成可执行的程序.
3. ARM GCC
GCC具有良好的可扩展性,除了可以编译x86体系结构的二进制程序外,还支持很多其他的体系结构的处理器, 如ARM, MIPS, RISC-V 等.
这里涉及到两个概念,本地编译和交叉编译.
- 本地编译: 在当前目标平台编译出来的程序,并且可以运行在当前平台上。
- 交叉编译: 在一种平台上编译,然后放到另一种平台上运行,这个过程称为交叉编译。之所以有交叉编译,主要是因为嵌入式系统的资源有限,不适合在嵌入式系统中进行编译,如早期ARM处理器性能低下,要编译一个完整的 Linux系统是不现实的。因此,首先会在某个高性能的计算机上编译出能在ARM处理器运行的Linux二进制文件,然后烧录到ARM系统中运行。
- 交叉工具链: 交叉工具链不只是GCC,还包含binutils、glibc等工具组成的综合开发环境,可以实现编译、链接等功能。在嵌入式环境中,通常使用uclibc等小型的C语言库。
交叉工具链的命名规则一般如下:
[ arch ] [ - os ] [ - (gnu) eabi ]
- arch:表示体系结构,如ARM、MIPS等。
- os:表示目标操作系统。
- eabi:嵌入式应用二进制接口。
如:
- arm-linux-gnueabi:主要用于基于ARM32架构的Linux系统,可以用来编译ARM32架构的u-boot、Linux内核以及Linux应用程序等。
- aarch-linux-gnueabi:主要用于基于ARM64架构的Linux系统。
- arm-linux-gnueabihf:hf指的是支持硬件浮点(Hard Float)的ARM处理器。在之前的一些ARM处理器中不支持硬件浮点单元,所以由软件浮点来实现。但是最新的一些高端ARM处理器内置了硬件浮点单元,这样新旧两种架构的差异就产生了两个不同的EABI接口.
- arm-linux-gcc: 与arm-linux-gnueabi相比,它支持更为有限的指令集,并且通常被用于编译适用于较老的ARM32架构的代码。arm-linux-gcc只能生成与ARM指令集兼容的机器代码,无法生成与其他指令集(如x86)兼容的机器代码。它适用于编译代码,生成可在ARM32架构的Linux系统上运行的可执行文件。然而,在使用最新版本的ARM64架构时,通常建议使用aarch-linux-gnueabi工具链来代替arm-linux-gcc,以确保获得对该架构的更好支持。
4. GCC编译
GCC编译一般格式:
gcc [选项] 源文件 [选项] 目标文件
常用选项表:
选项 | 功能描述 |
---|---|
-o | 生成目标文件,可以是 .i , .s , .o 文件 |
-E | 只运行C预处理器 |
-c | 通知GCC取消链接,即只编译生成目标文件,并不做最后的链接 |
-Wall | 生成所有警告信息 |
-w | 不生成任何警告信息 |
-I | 指定头文件目录路径 |
-L | 指定库文件的目录路径 |
-static | 链接成静态库 |
-g | 包含调试信息 |
-v | 打印编译过程中的命令行和编译器版本等信息 |
-Werror | 把所有警告信息转换成错误信息,并在警告发生时终止编译 |
-O0 | 关闭所有优化选项 |
-O或者-O1 | 最基本的优化等级 |
-O2 | -O1的进阶等级,推荐使用的优化等级,编译器会尝试提高代码性能,而不会增加体积和大量占用编译时间 |
-O3 | 最高优化等级,会延长编译时间 |