Linux From Scratch

LFS(Linux From Scratch)也就是从零构建Linux系统,目的是为了完全通过源代码来构建一个Linux系统环境。当然从零构建并不是真正的从什么都没有来开始构建,毕竟没有鸡哪来的蛋,必须得有一个能够编译Linux源码以及Linux软件包源码的宿主系统才行,而这个宿主系统可以是物理机也可以是虚拟机,唯一的要求是至少得是Linux环境。至于LFS系统,得需要单独准备一个硬盘分区,LFS系统的构建过程也就是这个硬盘分区中文件不断增加的过程,这些文件包括Linux系统所必备的各种库文件(如在/lib中),Linux系统必备的一些命令对应的二进制可执行文件(如在/bin中)等等。要说一个能够启动并运行的Linux系统其实结构比较清晰,主要包括可执行文件、运行库、Linux内核以及各种配置文件,只需要在一个硬盘分区上包含有上述的各种内容就行。

关于具体的操作过程,LFS官网 (http://www.linuxfromscratch.org/) 提供了详细的教程,一般来说,按照步骤一步步走一般不会出错,但是其实也没必要太过严格执行其中的步骤,毕竟有些操作会浪费时间。我实验的过程都是基于VirtualBox,首先安装Ubuntu16.04虚拟机,然后添加一个40GB大小的虚拟机磁盘,接下来需要将磁盘挂载到宿主系统的固定目录,如/mnt/lfs,可以在/etc/fstab中添加设置,使得每次启动都自动挂载。在挂载前非常重要的一步就是需要对挂载的磁盘进行分区,这里如果不进行分区的话在最后grub引导启动的时候会产生错误,其实道理也比较明显,一般在安装系统之前都需要指定对应的分区,而不是指定对应的硬盘。分区可以使用fdisk /dev/sdb直接对挂载的sdb硬盘进行分区。

构建临时系统工具链

之后的一个过程主要就是临时工具链的构建,临时工具链的建立过程其实主要就是将编译LFS系统所需要的编译环境安装到/mnt/lfs/tools文件夹下,这样之后就能够利用这个目录下的编译环境安装LFS系统的必备软件及库文件。这里需要注意的一点是,官网提供的操作方式是创建一个单独的lfs用户,然后将/mnt/lfs的目录授权给这个用户,切换到这个用户进行LFS工具链的安装,这样的一个好处就是在安装的过程中不会污染宿主系统,比如说在对一个源码包进行configure的时候,本来需要的--prefix=/tools没写,会报出权限错误,而不是安装到宿主系统的目录中去了。

LFS临时工具链的建立过程其实是非常有意思的,如何建立一套与主Linux无关的编译环境,LFS官网的操作过程是分多次安装Binutils和GCC,前者包含一些基本的二进制工具,GCC和Glibc在安装的过程中都会需要到这些二进制工具进行一些检测来确定是否开启某些特性,安装的顺序是在Binutils安装完之后,基于新安装的Binutils来安装GCC,当然这时候还需要宿主Linux的编译器,然后安装Linux头文件,而再之后安装Glibc基本上使用的都是/tools下面的编译环境。那么问题来了,为什么在之后还需要进行Binutils和GCC的重新编译安装呢,原因在于第一次编译的gcc工具和链接器属于交叉编译器和交叉链接器,其特点是针对当前机器的架构来编译,因而需要利用当前临时系统的环境进行再次编译。交叉编译器不会依赖主机的任何东西进行编译,这样就尽最大可能减小了宿主系统对于LFS系统可能造成的影响。在临时系统工具链以及各种基本软件建立好之后,还需要将/mnt/lfs工作目录的权限交还给root,再之后建立真正的LFS系统的时候就需要root权限。

建立LFS系统其实主要是依靠一个非常有意思的命令chroot,chroot可以改变系统识别的根目录,在chroot到/mnt/lfs目录之后,当前运行的内核会将其当做系统的根目录,然后执行命令比如ls会直接考虑/mnt/lfs/bin目录下的ls,当然这是在确保在使用env –i命令开启一个新的环境以及在chroot的过程中设置好对应的环境变量PATH的情况下。使用chroot切换根目录之后就好像在一个新的Linux环境中,当然内核仍然使用的是系统的内核,这一点还是不可改变的。

就LFS系统而言,后续的操作基本上就是基本软件的安装,再之后主要就是各种文件的配置,包括网络以及systemd的配置,内核的编译安装等等。需要注意的就是grub启动引导的修改容易出现问题,主系统是grub引导的话其实可以直接在主系统中尝试使用grub-install /dev/sdb1来安装引导项,或者直接使用update-grub会直接在主系统的grub中加入硬盘的启动信息。如果实在不行就直接修改/etc/grub/grub.cfg文件,将对应的内核路径设置到对应的位置即可。

Linux 引导方式

在机器加电运行之后,最开始会执行固件代码也就是BIOS,一般来说BIOS会将要运行的操作系统入口加载到0x7c00,那么这一部分操作Linux的启动一般是如何进行的。Linux启动一般都是通过grub进行引导,grub其实全称 GNU GRand Unified Bootloader,是GNU项目的一个系统加载器,被大多数Linux发行版用来加载内核。提到grub引导系统启动就不得不提硬盘的分区问题,操作系统也就是这里的Linux内核开始时保存在硬盘中,内核在编译安装之后主要内容在/boot目录下的一个vmlinuz的文件中。硬盘要作为系统盘进行启动的话就不得不进行分区,为什么要进行分区呢,主要是系统引导盘需要一个MBR启动区,这个MBR也就是 Master Boot Record 主引导记录会记录硬盘分区的信息,在BIOS执行后会将MBR加载到0x7c00处,而不是直接加载vmlinuz内核文件的内容。也就是说在硬盘分区的时候会在硬盘最小的地址有一个MBR,然后才开始分区,比如硬盘sdb,在使用fdisk进行分区的时候会提示从2048处开始分区,意味着前面的MBR会有2048字节大小,也就是4个扇区,用来保存加载系统的代码以及分区的信息,当然MBR的大小也不是固定的,最早的时候一个扇区也就是512字节就已经足够使用,现在有的时候可能已经需要4096字节了。MBR的概念最早在1983年在IBM的PC Dos 2.0中被公开引入,之后一直到如今都被广泛使用,现在虽然有新的分区方式如GPT,MBR依然广泛存在。

现在就比较清楚了,硬盘sdb分区最前面是MBR,MBR后面依次分区为sdb1、sdb2等,如若Linux内核vmlinuz保存在sdb1分区中,那么Linux系统启动的过程就是这样子的,首先机器加电执行BIOS代码,BIOS代码加载MBR区到0x7c00处,然后MBR代码加载sdb1分区的内核系统启动。那么grub在这中间扮演了一个什么角色呢,grub的功能主要可以说是收集各个硬盘的系统启动信息,如若sda硬盘上也有一个系统,那么通过grub就可以选择进入哪个硬盘的系统,这一点可以通过grub直接进行手动配置,比如在启动的时候直接修改要进入的硬盘所在的系统,也可以在grub.cfg中进行配置。当然,其实最方便的方式是使用grub-install直接安装系统的启动项,或者使用grub-update来直接自动更新。之前在LFS的构建中一次由于开始没有进行分区导致MBR不存在,所以上述grub的这几个操作一直报错,这么说来,出错一定会有出错的原因,从原理上来分析就容易理解了。