第二章、基础准备——Linux 系统定制原理
Linux 系统的组成及架构
共享库和静态库
在 Linux 系统中,函数库包含两种形式:共享库和静态库。
共享库
共享库在程序运行时,为其提供所需要的函数,所以程序运行时该共享库必须存在系统中,且能够被系统找到并且使用。
静态库
静态库在编译程序的期间就将各种函数编译到程序中,程序运行的时候,即使该静态库不存在也可以正常调用其中的函数。
C 库及各类功能库
在常规的 Linux 系统中,一般提供一个 C 函数库的实现,所谓的 C 函数库就是将标准 C 语言中的各种函数功能(如标准输入/输出)进行实现的库程序文件,在 Linux 中很多程序都是采用 C 语言来进行编写的,所以 C 函数库是作为一个比较重要的函数库存在于系统中的。
除了标准的 C 库实现外,还需要实现一些通用的或者特定的功能,这些功能也被组织成为函数库形式存在与系统中,有了这些丰富的函数库,Linux 系统的功能得到了不断的增强,编写各类程序也越来越容易了,这些函数库也成为了 Linux 系统中常见的组成部分。
在常规的 Linux 系统中, C 库一般采用 Glibc,各类功能的函数库则非常丰富,如 Zlib、Ncurses 等。
FHS 与 LSB
为了使各种发行版之间有良好的兼容性,国际相关组织对 Linux 系统中重要目录的命名,存放为止和用途都做了规定,这份规定则称为 FHS
FHS 包含在 LSB(Linux standards Bases)规则中,为了能够保证不同 Linux 发行版提供的应用程序能够很好的相互兼容,LSB 规则中包含了目录,文件和函数库等的明明,存放及一系列要求。
Linux 系统的基本目录组成及其主要作用
Linux 系统的启动
BIOS/启动器
一般的计算机设备都会有一个 BIOS 固化软件,BIOS 会对计算机设备进行最基本的很粗实话工作,而启动器则承担了从逻辑状态设备进入操作系统控制设备的链接作用。
启动器一般通过引导程序来实现。
不同架构的计算机常用的启动器是不一样的,有的启动器独立于 BIOS ,如 x86 下的 GRUB 和 LILO,BIOS 初始化完成后读取存储设备中的固定区域,GRUB 或者 LILO 等启动器会降一部分代码存放在这个区域中,这样会从 BIOS 中获得计算机的控制权。
一些嵌入式的设备则会使用一些带有 BIOS 功能的启动器,如 U-Boot,这类启动器一般将直接固话在计算机中。
不同的启动器使用的设置方式是不同的,大多数具备相对高级功能的启动器会使用配置文件的方法,如 GRUB,启动器按照可以识别的设备进行读取内核。
完成读取后会将计算机的控制权交给读取的内核中去处理,通常是跳转到内核的执行起始位置。
Linux 内核的初始化
内核在活的了计算机的控制权之后就会惊醒各种设备的初始化工作,虽然部分初始化工作已经在 BIOS 中进行了,但是内核为了保证设备的初始化的可靠性会再次进行初始化。
Linux 内核也会进行各种自身功能的初始化工作,比如内存管理,进程管理等。
这个阶段的 Linux 内核是无法加载模块的,因此启动设备及其文件系统的驱动必须要编译进内核,不能使用模块的方式。
Linux 内核启动完成后会将计算机的控制交给启动控制程序,分别有三个程序。
Initramfs
这是 Linux 2.5 内核中最新加入的启动控制方式,Initramfs 是一个使用 CPIO 格式打包的文件,还原该文件将得到一个小型的 Linux 系统,Linux 内核将以该系统作为启动系统,并且将执行权交给其中的 /init 命令。
initrd
initrd 文件是一个小型的文件系统,内部包含了启动所需要的命令及其内核模块,内核将该文件镜像到 /dev/ram0 设备中,/deb/ram0 是内存磁盘设备。
镜像还原后 Linux 内核会执行其中的 /linuxrc 文件,该文件执行结束后返回 Linux 内核,内核将执行权限交给了根文件系统中的 /sbin/init 命令。
/sbin/init
这个是一个标准的 启动控制程序的启动位置和命令名称。
神奇的 Initramfs 和 initrd
Linux 内核启动时,启动器传递给内核的启动参数中可以指定 init 程序,内核在完成初始化后会将执行权交给该命令。
Initramfs 和 initrd 都提供了一个小型的系统,该系统可以根据不同的硬件环境来进行特定的工作,这使得 Linux 系统可以适应很多的硬件平台。
在 LiveCD 中就采用 initramfs 或者 initrd 来保证在大多数的同类计算机上正常的使用, Initramfs 和 initrd 还可以用来帮助使得系统的启动过程更加灵活。
本地定制 Linux 系统的原理
如何确定依赖的共享库?
对于一个二进制的程序文件,确定其依赖的共享库可以通过使用如下命令:
ldd <文件名>
牛逼的 Glibc
编译工具中包含了 GCC、Binutils,辅助命令则包括 Make 等编译相关的命令程序,他们和其他程序一样必须要有运行的环境。
而基本这些工具都会依赖 Glibc(当然并不是所有的编译相关的命令都会依赖这个库,但是基本大多数都是依赖这个库的)
工具链
工具连是由编译器、汇编器、链接器及其函数库组成的,在 Linux 系统中常识用 GCC、Binutils 和 Glibc 的组合。
需要记住一条工具连的原则是
由某一个工具链编译出来的二进制程序文件或者函数库文件,必然链接该工具连中的函数库,无论是静态链接方式还是动态连接方式。
工具链的「外部依赖」和「内部依赖」
根据上面说的工具链的原则,可以知道,如果一个程序,它的运行需要依赖工具链之外的 Glibc 或者其他库,就叫做外部依赖,它必然不是由含有 Glibc 的库的工具链编译出来的。
而如果它的运行根本就不需要外部的其他库,而是依赖工具链中的 Glibc ,也就是编译它的工具链中含有 Glibc 我们就说这个程序是内部依赖。
变换工具链的依赖方式
假如你有一个完整的工具链,我们称他为 A 组,内里含有「Glibc_A」「GCC_A」「Binutils_A」
你用它编译出了 B 组工具链,内里含有「Glibc_B」「Binutils_B」唯独不含有「Glibc_B」,这样的工具链我们称为是不完整的工具链。
你如果用 B 组工具链去编译一个程序,这个程序生成后依赖的库将会是 A 组的 「Glibc_A」,因为 B 组工具链是由 A 组工具链编译出来的,因为 B 组没有 Glibc_B 这个库,但是根据工具链的原则,他含有 Glibc_A 这个库,所以你用 B 组工具链来编译一个程序,当然是链接的 A 组的 Glibc_A
然后你用 B 组工具链,使用外部的 Glibc 而不是内部的 Glibc_A 编译出了一个 Glibc_B ,并且分别编译出了「GCC_C」「Binutils_C」,这个时候这三个程序合称为 C 组工具链。
需要注意的是,最后的这个工程,就体现了通过一定的设置,内部依赖和外部依赖可以相互转换,实际的制作过程中,工具链 B 的依赖模式的确是从内部依赖变为了外部依赖,而这个设置的过程,就叫做「调整工具链」
初始工具链
工具链 A 称为「初始工具链」或者叫做「原工具链」
过渡工具链
可以看出,工具链 B 的存在就是为了形成工具链 C ,所以工具链 B 也成为「过渡工具链」或者叫做「临时工具链」
目的工具链
工具链 C 称为「目的工具链」或者叫做「目标工具链」