深度探索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” 结尾。


目录
相关文章
|
2月前
|
Ubuntu 物联网 Linux
从零安装一个Linux操作系统几种方法,以Ubuntu18.04为例
一切就绪后,我们就可以安装操作系统了。当系统通过优盘引导起来之后,我们就可以看到跟虚拟机中一样的安装向导了。之后,大家按照虚拟机中的顺序安装即可。 好了,今天主要介绍了Ubuntu Server版操作系统的安装过程,关于如何使用该操作系统,及操作系统更深层的原理,还请关注本号及相关圈子。
|
8月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
7月前
|
存储 Linux iOS开发
【Linux】冯诺依曼体系与操作系统理解
本文深入浅出地讲解了计算机体系的两大核心概念:冯诺依曼体系结构与操作系统。冯诺依曼体系作为现代计算机的基础架构,通过中央处理器、存储器和输入输出设备协同工作,解决了硬件性能瓶颈问题。操作系统则是连接硬件与用户的桥梁,管理软硬件资源,提供运行环境。文章还详细解析了操作系统的分类、意义及管理方式,并重点阐述了系统调用的作用,为学习Linux系统编程打下坚实基础。适合希望深入了解计算机原理和技术内幕的读者。
165 1
|
2月前
|
监控 Ubuntu Linux
什么Linux,Linux内核及Linux操作系统
上面只是简单的介绍了一下Linux操作系统的几个核心组件,其实Linux的整体架构要复杂的多。单纯从Linux内核的角度,它要管理CPU、内存、网卡、硬盘和输入输出等设备,因此内核本身分为进程调度,内存管理,虚拟文件系统,网络接口等4个核心子系统。
204 0
|
2月前
|
Unix 物联网 Linux
都什么年代了,你还不懂啥是Linux操作系统
至于华为鸿蒙操作系统是不是独树一帜,这个留给各位阅读本文的网友们来讨论
63 0
|
2月前
|
安全 Linux iOS开发
linux属于什么操作系统
Linux是一种自由和开放源代码的操作系统,具有高度的灵活性和可定制性。与常见的操作系统如Windows和macOS相比,Linux具有自由、安全和稳定等优势。Linux已广泛应用于服务器、桌面电脑、超级计算机和嵌入式设备等领域,并且在未来的发展前景广阔。由于其自由和开放源代码的特性,Linux还促进了计算机技术和社区的发展,为全球的计算机用户提供了更多的选择和可能性。
|
2月前
|
安全 Ubuntu Unix
关于Linux操作系统,你必须要知道的事
我们可以看到无论是Debian还是Buildroot都有各自的特点,为客户提供了更大的选择空间和灵活性,大家可以根据自己的需求选择合适的版本来满足终端用户的体验和功能需求。从平技术将会一直关注更多更安全、灵敏、易于开发的Linux版本,做好适配工作,不断为客户带来“简单开发、方便应用”的使用体验。
|
2月前
|
安全 Ubuntu Linux
如何安装Linux操作系统?
此时,您可以选择重新启动计算机,然后从硬盘上的Linux系统启动。以上是一个大致的安装过程。请注意,不同的Linux发行版可能会在细节上有所差异,因此在进行安装之前,请确保您阅读并理解了相应发行版的安装指南或文档。
|
2月前
|
Ubuntu Linux 开发者
Linux发行版比较:选择适合你的操作系统
在做出选择之前,建议您先在虚拟机或双系统环境中尝试不同的发行版,根据自己的体验和需求做出决策。选择适合自己的Linux发行版是一个个人化和主观的过程,最重要的是找到符合自己需求和喜好的发行版,让您在使用Linux系统时感到舒适和方便。
|
2月前
|
Ubuntu Unix Linux
玩机强化技能,动手安装Ubuntu Linux操作系统
(13)Ubuntu重启过程中,你将在关机画面中看到提示文字“Please remove the installation medium, then press ENTER:”,按下“Enter”键即可重启电脑。