深度探索Linux操作系统 —— 构建工具链

简介: 深度探索Linux操作系统 —— 构建工具链

一、GNU工具链组成

   编译过程分为4个阶段,分别是:编译预处理、编译、汇编以及链接。每个阶段都涉及了若干工具,GNU将这些工具分别包含在3个软件包中:Binutils、GCC、Glibc。


Binutils:GNU将凡是与二进制文件相关的工具,都包括在软件包Binutils中。Binutils就是 Binary utilities 的简写,其中主要包括生成目标文件的汇编器(as),链接目标文件的链接器(ld)以及若干处理二进制文件的工具,如objdump、strip等。但是也不是Binutils中的所有的工具都是处理二进制文件的,比如处理文本文件的预编器(cpp)也包含在其中。


GCC:GNU将编译器包含在GCC中,包括C编译器、C++编译器、Fortran编译器、Ada编译器等。GCC中还提供了C++的启动文件。


Glibc:C库包含在Glibc中。除了C库外,动态链接器(dynamic loader/linker)也包含在这个包中。另外这个包中还提供C的启动文件。事实上,有很多C库的实现,比如适用于Linux桌面系统的Glibc、EGlibc、uClibc;在嵌入式系统上,可以使用EGlibc或者uClibc;对于没有操作系统的系统,也就是所说的 freestanding enviroment ,可以选择newlib、dietlibc,或者根本就不用C库。


   除了这三个软件包外,工具链中还需要包括内核头文件。用户空间中的很多操作需要借助内核来完成,但是通常用户程序不必直接和内核打交道,而是通过更易用的C库。C库中的很大一部分函数是对内核服务的封装。在某种意义上,内核头文件可以看作是内核与C库之间的协议。因此,构建C库之前,需要首先在工具链中安装内核头文件。

二、构建工具链的过程

   C99标准定义了两种运行环境,一种是 “hosted environment” ,针对的是具有操作系统的环境,程序一般是运行在操作系统之上的,因此这个操作系统不仅是内核,还包括外围的C库,对于程序来说,就是一个"hosted environment"。另外一种是 “freestanding environment”,就是程序不需要额外环境的支持,直接运行在裸机(bare metal)上,比如Linux内核,以及一些运行在没有操作系统的裸板上的程序,不再依赖操作系统内核和C库,所有的功能都在单个程序的内部实现。


   通常 “hosted implementation” 的实现包含编译器(比如 GCC)和 C 库(比如 Glibc)。而 “freestanding implementation” 的实现通常只包含编译器,如 GCC,最多再加上一个简单的库,比如典型的 newlib。但是如果没有 newlib 的支持,GCC 自己也可以自给自足。


   “freestanding implementation” 的实现,恰恰解决了我们提到的 GCC 和 Glibc 的循环依赖问题。我们可以先编译一个仅支持 “freestanding implementation” 的 GCC,因为在这种情况下,不需要 C 库的支持。但是 “freestanding implementation” 的 GCC 却可以编译 Glibc,因为 Glibc 也是一个自包含的,完全自给自足。事实上,Glibc 中也有小部分地方使用了 GCC 的代码,但是这不会带来依赖的麻烦,因为 GCC 一定是在 Glibc 之前编译的。


   在编译目标系统的 C 库,甚至是编译 GCC 中包含的目标系统上的库时,都需要链接器,因此,Binutils 是编译器和 C 库共同依赖的。索性 Binutils 几乎没有任何依赖,只需要利用宿主系统的工具链构建一套交叉 Binutils 即可。


   另外值得一提的是内核头文件。在 Linux 系统上,在编译 C 库前需要安装目标系统的内核头文件,从某种意义上讲,内核头文件就是 C 库和内核之间的一个协议(Protocol)。而且,C 库会根据内核头文件检查内核提供了哪些特性,以及需要在 C 库层面模拟哪些内核没有提供的服务。



综上所述,构建工具链步骤如下:

  1. 构建交叉 Binutils,包括汇编器as、链接器ld等。
  2. 构建临时的交叉编译器(仅支持 freestanding)。
  3. 安装目标系统的内核头文件。
  4. 构建目标系统的 C 库。
  5. 构建完整的交叉编译器(支持 hostedfreestanding )。


三、准备工作

1、环境变量

i686

unset LANG
export HOST=i686-pc-linux-gnu
export BUILD=$HOST
export TARGET=i686-none-linux-gnu
export CROSS_TOOL=/vita/cross-tool

export CROSS_GCC_TMP=/vita/cross-gcc-tmp
export SYSROOT=/vita/sysroot
export PATH=$CROSS_TOOL/bin:$CROSS_GCC_TMP/bin:/sbin:/usr/sbin:$PATH

x86_64

unset LANG
export HOST=x86_64-pc-linux-gnu
export BUILD=$HOST
export TARGET=x86_64-none-linux-gnu
export CROSS_TOOL=/vita/cross-tool

export CROSS_GCC_TMP=/vita/cross-gcc-tmp
export SYSROOT=/vita/sysroot
export PATH=$CROSS_TOOL/bin:$CROSS_GCC_TMP/bin:/sbin:/usr/sbin:$PATH

2、Binutils

Linux下载安装Binutils工具集

wget https://ftp.gnu.org/gnu/binutils/binutils-2.23.tar.gz
wget https://ftp.gnu.org/gnu/binutils/binutils-2.39.tar.xz

Linux 编写 configure.ac 和 Makefile.am 示例

Linux命令详解./configure、make、make install 命令


../binutils-2.23/configure --prefix=$CROSS_TOOL --target=$TARGET --with-sysroot=$SYSROOT
../configure --prefix=$CROSS_TOOL --target=$TARGET --with-sysroot=$SYSROOT

make && make install

c: error: this statement may fall through [-Werror=implicit-fallthrough=]

报错解决:error: this statement may fall through [-Werror=implicit-fallthrough=]

make CFLAGS='-Wno-implicit-fallthrough'
make CFLAGS='-Wno-error'

-Wno-error取消编译选项-Werror

GCC详解


makeinfo:未找到命令

    Binutils 将二进制工具安装在 $CROSS_TOOL/bin 目录下,链接脚本安装在 $CROSS_TOOL/i686-none-linux-gnu/lib/ldscripts 目录下。

    其中 elf_i386.x 用于IA32上ELF文件的链接,elf_i386.xbn、elf_i386.xc 等分别对应ld使用不同的链接参数时使用的链接脚本,如果使用了 “-N” 参数,那么ld使用链接脚本elf_i386.xbn。

    Binutils 在 $CROSS_TOOL/i686-none-linux-gnu/bin 目录下也安装了一些二进制工具,这些是编译器内部使用的,我们不必关心,其实这个目录下的工具与 $CROSS_TOOL/bin 目录下的工具完全相同,只是名称不同而已。

四、编译freestanding的交叉编译器

1、GCC 下载

gcc7.3.0下载与安装

GCC FTP下载页

GCC Releases页


gcc configure: error: Building GCC requires GMP…

wget https://ftp.gnu.org/gnu/gcc/gcc-4.7.2/gcc-4.7.2.tar.gz
wget https://ftp.gnu.org/gnu/gcc/gcc-4.7.2/gcc-4.7.2.tar.bz2

wget https://ftp.gnu.org/gnu/gmp/gmp-5.0.5.tar.xz
wget https://ftp.gnu.org/gnu/mpfr/mpfr-3.1.1.tar.xz
wget https://ftp.gnu.org/gnu/mpc/mpc-1.0.1.tar.gz

2、编译

tar -xf gmp-5.0.5.tar.xz -C ../build/
mv gmp-5.0.5/ gmp

tar -xf mpc-1.0.1.tar.gz -C ../build/
mv mpfr-3.1.1/ mpfr

tar -xf mpfr-3.1.1.tar.xz -C ../build/
mv mpc-1.0.1/ mpc
# gcc-4.7.2
../gcc-4.7.2/configure --prefix=$CROSS_GCC_TMP --target=$TARGET --with-sysroot=$SYSROOT --with-newlib --enable-languages=c --with-mpfr-include=/vita/build/gcc-4.7.2/mpfr/src --with-mpfr-lib=/vita/build/gcc-build/mpfr/src/.libs --disable-shared --disable-threads --disable-decimal-float --disable-libquadmath --disable-libmudflap --disable-libgomp --disable-nls --disable-libssp

../configure --prefix=$CROSS_GCC_TMP --target=$TARGET --with-sysroot=$SYSROOT --with-newlib --enable-languages=c --with-mpfr-include=/vita/build/gcc-4.7.2/mpfr/src --with-mpfr-lib=/vita/build/gcc-4.7.2/build/mpfr/src/.libs --disable-shared --disable-threads --disable-decimal-float --disable-libquadmath --disable-libmudflap --disable-libgomp --disable-nls --disable-libssp

# gcc-11.3.0
../configure --prefix=$CROSS_GCC_TMP --target=$TARGET --with-sysroot=$SYSROOT --with-newlib --enable-languages=c --with-mpfr-include=/vita2/build/11.3.0/mpfr/src --with-mpfr-lib=/vita2/build/gcc-11.3.0/build/mpfr/src/.libs --disable-shared --disable-threads --disable-decimal-float --disable-libquadmath --disable-libmudflap --disable-libgomp --disable-nls --disable-libssp

五、安装内核头文件

wget https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/linux-3.7.4.tar.xz

tar xf ../source/linux-3.7.4.tar.xz
make mrproper
make ARCH=i386 headers_check
make ARCH=i386 INSTALL_HDR_PATH=$SYSROOT/usr/headers_install

make ARCH=x86_64 headers_check
make ARCH=x86_64 INSTALL_HDR_PATH=$SYSROOT/usr/headers_install

六、编译目标系统的C库

apt-get install gawk
tar xvf ../source/glibc-2.15.tar.xz

../glibc-2.15/configure --prefix=/usr --host=$TARGET \
--enable-kernel=3.7.4 --enable-add-ons \
--with-headers=$SYSROOT/usr/include \
libc_cv_forced_unwind=yes libc_cv_c_cleanup=yes \
libc_cv_ctors_header=yes

make
make install_root=$SYSROOT install

编译GLIBC

Glibc编译过程总结

Ubuntu16.04多个版本GCC编译器的安装和切换

Installing GCC

七、构建完整的交叉编译器

# gcc-4.7.2
../gcc-4.7.2/configure --prefix=$CROSS_TOOL --target=$TARGET --with-sysroot=$SYSROOT --enable-languages=c,c++ --with-mpfr-include=/vita/build/gcc-4.7.2/mpfr/src --with-mpfr-lib=/vita/build/gcc-4.7.2/build/mpfr/src/.libs --enable-threads=posix

# gcc-4.9.4
../configure --prefix=$CROSS_TOOL --target=$TARGET --with-sysroot=$SYSROOT --enable-languages=c,c++ --with-mpfr-include=/vita/build/gcc-4.9.4/mpfr/src --with-mpfr-lib=/vita/build/gcc-4.9.4/build/mpfr/src/.libs --enable-threads=posix

# gcc-11.3.0
../configure --prefix=/vita2/cross-tool --target=x86_64-none-linux-gnu --with-sysroot=/vita2/sysroot --enable-languages=c,c++ --with-mpfr-include=/vita/build/gcc-11.3.0/mpfr/src --with-mpfr-lib=/vita/build/gcc-11.3.0/build/mpfr/src/.libs --enable-threads=posix 

gcc7.3.0下载与安装

最终的交叉编译器安装的主要文件如下:

  1. 驱动程序
    GCC 安装的最主要的是交叉编译器的驱动程序,包括 i686-none-linux-gnu-gcc、i686-none-linux-gnu-g++ 等。

2.目标系统的库和头文件

GCC 中也包含了一些用于目标系统的运行时库及头文件,它们安装在 $CROSS_TOOL/i686-none-linux-gnu 目录下。在该目录下,子目录 lib 存放包括目标系统的运行时库以及供目标系统编译程序使用的静态库,子目录 include 下包含开发目标系统上的程序需要的 C++ 头文件。


3.helper program

前面我们提到,gcc 仅仅是一个驱动程序,它将调用具体的程序完成具体的任务,这些程序被 GCC 安装在 libexec 目录下,典型的有编译器 cc1,链接过程调用的 collect2 等。


libexec 与 sbin/bin 目录下存放的可执行文件的一个区别是:sbin/bin 目录下的可执行文件一般是用户使用的;而 libexec 目录下的可执行文件一般是由某个程序或工具使用的,所以一般称为 “helper program”。


4.freestanding 实现文件

前面我们提到,C99 标准定义了两种实现方式:一种称为 “hosted implementation”,支持全部 C 标准,包括语言标准以及库标准;另外一种是 “freestanding implementation” 。在 lib 目录下的头文件即为 "freestanding implementation"实现标准要求的头文件。


5.启动文件

与 C++ 相关的启动文件在 GCC 中,包括 crtbegin.o、crtend.o 等。

    讨论完 C 库和编译器后,我们看到,无论是 C 库,还是 GCC 都各自安装了头文件、运行库,GCC 还安装了一些内部使用的可执行程序。那么在编译程序时,GCC 是怎么找到这些文件的呢?答案就是 GCC 内部定义的两个环境变量 LIBRARY_PATHCOMPILER_PATH。GCC 会根据用户的一些配置参数,包括 –target、–with-sysroot 等设置这些环境变量的值。我们可以在编译程序时,使用参数 “-v” 查看这两个变量的值。

八、定义工具链相关的环境变量

export CC="$TARGET-gcc"
export CXX="$TARGET-g++"
export AR="$TARGET-ar"
export AS="$TARGET-as"
export RANLIB="$TARGET-ranlib"
export LD="$TARGET-ld"
export STRIP="$TARGET-strip"

九、封装“交叉”pkg-config

Cmake命令之find_package介绍

“轻松搞定 CMake”系列之 find_package 用法详解

   在 GNU 中大部分的软件都使用 Autoconf 配置,Autoconf 通常借助工具 pkg-config 去获取将要编译的程序依赖的共享库的一些信息,比如库的头文件存放在哪个目录下,共享库存放在哪个目录下以及链接哪些共享库等,我们将其称为库的元信息。通常,这些信息都被保存在一个以软件包的名称命名,并以 “.pc” 作为扩展名的文件中。而 pkg-config 会到特定的目录下寻找这些 pc 文件,一般而言,其首先搜索环境变量 PKG_CONFIG_PATH 指定的目录,然后搜索默认路径,一般是 /usr/lib/pkgconfig、/usr/share/pkgconfig、/usr/local/lib/pkgconfig 等。显然,使用环境变量 PKG_CONFIG_PATH 不能满足我们的要求。因为在交叉编译环境中,我们是不能允许正在编译的程序链接到宿主系统的库上的,也就是说,我们除了告诉 pkg-config 到目标系统的文件系统中寻找外,还要禁止它搜索默认的宿主系统的路径。而另外一个环境变量 PKG_CONFIG_LIBDIR 可以满足我们这个需求,一旦设置了 PKG_CONFIG_LIBDIR ,其将取代 pkg-config 默认的搜索路径。因此,在交叉编译时,这两个变量的设置如下:

unset PKG_CONFIG_PATH
export PKG_CONFIG_LIBDIR=$SYSROOT/usr/lib/pkgconfig:$SYSROOT/usr/share/pkgconfig

10、关于使用libtool链接库的讨论

    GNU 中的大部分软件包都使用 libtool 处理库的链接。通常,大部分的软件在包发布时都已经包含了 libtool 所需的脚本工具等。但是如果一旦准备使用  autoconf、automake 重新生成编译脚本,且这些脚本中包含了 libtool 提供的 M4 宏,则需要安装 libtool 。

11、启动代码

   启动代码是工具链中 C 库和编译器都提供了的重要部分之一,但是由于应用程序员很少接触它们,因此非常容易引起程序员的困惑,所以我们特将其单独列出,使用一点篇幅加以讨论。


   不知读者是否留意过这个问题:无论是在 DOS 下、Windows 下,还是在 Linux 操作系统下,程序员使用 C 语言编程时,几乎所有程序的入口函数都是 main,这是因为启动代码的存在。在 “hosted environment” 下,应用程序运行在操作系统之上,程序启动前和退出前需要进行一些初始化和善后工作,而这些工作与 “hosted environment” 密切相关,并且是公共的,不属于应用程序范畴的事情,这些应用程序员无需关心。更重要的一点是,有些初始化动作需要在 main 函数运行前完成,比如 C++ 全局对象的构造。有些操作是不能使用 C 语言完成的,必须要使用汇编指令,比如栈的初始化。于是编译器和 C 库将它们抽取出来,放在了公共的代码中。


   这些公共代码被称为启动代码,其实不只是程序启动时,也包括在程序退出时执行的一些代码,我们统称它们为启动代码,并将启动代码所在的文件称为启动文件。对于 C 语言来说,Glibc 提供启动文件。显然,对于 C++ 语言来说,因为启动代码是和语言密切相关的,所以其启动代码不在 C 库中,而由 GCC 提供。这些启动文件以 “crt”(可以理解为 C RunTime 的缩写)开头、以 “.o” 结尾。


目录
相关文章
|
1月前
|
人工智能 分布式计算 大数据
Linux操作系统:开源力量的崛起与影响###
一场技术革命的火种,如何燎原? 本文将带您深入探索Linux操作系统的诞生背景、核心特性及其对现代科技世界的深远影响。从1991年芬兰学生Linus Torvalds的一个小众项目,到如今成为支撑全球无数服务器、超级计算机及物联网设备的基石,Linux的发展既是一部技术创新史,也是开源文化胜利的见证。通过剖析其设计哲学、安全性、灵活性等关键优势,结合实例展示Linux在云计算、大数据处理等领域的广泛应用,本文旨在揭示Linux为何能在众多操作系统中脱颖而出,以及它如何塑造了我们今天的数字生活。 ###
|
1月前
|
安全 Linux 编译器
探索Linux内核的奥秘:从零构建操作系统####
本文旨在通过深入浅出的方式,带领读者踏上一段从零开始构建简化版Linux操作系统的旅程。我们将避开复杂的技术细节,以通俗易懂的语言,逐步揭开Linux内核的神秘面纱,探讨其工作原理、核心组件及如何通过实践加深理解。这既是一次对操作系统原理的深刻洞察,也是一场激发创新思维与实践能力的冒险。 ####
|
13天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
40 9
|
13天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
31 2
|
17天前
|
缓存 网络协议 Linux
Linux操作系统内核
Linux操作系统内核 1、进程管理: 进程调度 进程创建与销毁 进程间通信 2、内存管理: 内存分配与回收 虚拟内存管理 缓存管理 3、驱动管理: 设备驱动程序接口 硬件抽象层 中断处理 4、文件和网络管理: 文件系统管理 网络协议栈 网络安全及防火墙管理
36 4
|
15天前
|
安全 网络协议 Linux
Linux操作系统的内核升级与优化策略####
【10月更文挑战第29天】 本文深入探讨了Linux操作系统内核升级的重要性,并详细阐述了一系列优化策略,旨在帮助系统管理员和高级用户提升系统的稳定性、安全性和性能。通过实际案例分析,我们展示了如何安全有效地进行内核升级,以及如何利用调优技术充分发挥Linux系统的潜力。 ####
37 1
|
18天前
|
物联网 Linux 云计算
Linux操作系统的演变与未来趋势####
【10月更文挑战第29天】 本文深入探讨了Linux操作系统从诞生至今的发展历程,分析了其在服务器、桌面及嵌入式系统领域的应用现状,并展望了云计算、物联网时代下Linux的未来趋势。通过回顾历史、剖析现状、预测未来,本文旨在为读者提供一个全面而深入的视角,以理解Linux在当今技术生态中的重要地位及其发展潜力。 ####
|
24天前
|
边缘计算 人工智能 运维
Linux操作系统:开源力量的崛起与影响###
一场技术革命的回顾 回溯至1991年,当Linus Torvalds宣布Linux操作系统的诞生时,世界或许并未意识到这一举措将如何深刻地改变技术领域的面貌。本文旨在探讨Linux操作系统的发展历程、核心特性、以及它如何引领了一场开源运动,重塑了软件行业的生态。从最初的个人爱好项目成长为全球最广泛采用的服务器操作系统之一,Linux的故事是技术创新与社区精神共同推动下的辉煌篇章。 ###
|
23天前
|
人工智能 安全 Linux
|
25天前
|
物联网 Linux 5G
Linux操作系统的演变与未来趋势####
本文深入探讨了Linux操作系统的发展历程,从最初的一个学生项目到如今全球最流行的开源操作系统之一。文章将分析Linux的核心优势、关键特性以及它在云计算、物联网和嵌入式系统中的应用前景。通过具体案例展示Linux如何推动技术创新,并预测其在未来技术生态中的角色。本文旨在为读者提供一个全面而深入的理解,帮助他们认识到Linux在现代计算环境中的重要性及其未来的潜力。 ####
下一篇
无影云桌面