探索Linux系统的启动过程

阅读目录

  • 引言
  • Linux启动过程概述
  • Grub加载Linux内核和initramfs
  • 探索initramfs文件的方法
  • 基于systemd的init系统
  • 进入图形界面

这篇文章有点长,我花了大约两个星期的时间才完成,虽然不是写得巨细无遗,但是我认为还是很有价值的,求推荐。不管你是不是 Linux 爱好者,也不管你是否能耐心读完,请果断 mark 之,你值得拥有。

引言

这一篇可能是我这系列随笔除总结之外的最后一篇,也可能不是。为什么这么说呢?因为我觉得在 Linux 应用环境方面再写下去也可能不会有什么新意了,至少以我的水平写不出什么新意了。我的这一个系列,基本上不涉及到编程,而是真真正正地将 Linux 桌面系统当成日常使用的工具,即使在我的这个系列中有探讨 C 语言或者 Bash 语言的部分,我也是展示的相关的开发工具,比如 binutils 工具包和 autotools 工具链。我曾经说过,折腾 Linux 就是折腾 Linux 系统下的软件,Linux 世界软件之多,可写的应该还有不少,但是我觉得已经没有办法写得很好了。比如有一些工作我还没有找到适当的工具,像 JavaScript 和 CSS 开发,对于博客园中的广大前端攻城狮来说,肯定是各有神通,但是我确实不知道用什么工具可以做到即顺手又高效率,我只会简单地用用 Firefox 提供的开发者工具。还有 3D 开发、游戏编程、移动开发等等,虽然可以写的领域比较广阔,但是毕竟我不是做这些的,没什么经验,不能说光写某某软件试用体验吧。再说,程序员终归还是程序员,以后写博客应该还是介绍编程经验比较好。

之所以想到写这些东西,那是因为我确实想让大家也和我一样,把 Linux 桌面系统打造成真真正正日常使用的工具,而不是安装之后试用几把再删掉。我是真的在日常生活和工作中都使用 Linux,比如在 Linux 下编程、写博客、写论文和做幻灯。当然,对于 LibreOffice 这样的软件使用起来都不会有什么困难,所以在我的博客中就基本没有提到,就像 Windows 下的程序员不会去写 MS Office 的使用指南一样。如果有人不能坚持使用 Linux,那一定是 Linux 中的某些困难打败了他。刚使用 Linux 时确实会碰到很多困难,比如界面不够美观啊、字体不够顺眼啊、输入法太难用啊,还有就是想做某个工作找不到工具、想改一个属性找不到入口、系统崩溃了无法恢复等等,甚至是刚安装一个软件包或者下载一个文件,却不知道放到文件系统的什么地方了。所以,我的这一个系列就是在向大家展示我的方法论,用什么软件不重要,怎么用好也不重要,而是碰到问题了,用什么思路去找答案,到哪里去找答案。经过这二十几篇的展示,我的方法论也基本上讲完了。所以我决定,写完这一篇探索 Linux 系统的启动过程之后,我将开始另外一个系列的工作。

关于 Linux 系统的启动过程也是几经变革。首先是操作系统从磁盘载入的过程发生了变化,很老旧的操作系统需要自己写启动扇区,现在的操作系统早就将这个过程交给了专门的启动工具。最常用的操作系统加载程序是 Grub,这个名字大家肯定早就如雷灌耳了,而且 Grub 也已经从第1版发展到了第2版,我们称之为 Grub2。 Grub2 和 Grub1 有很大的差别,网络上有很多讲 Linux 的文章动不动就拿 Grub1 说事儿,比如让大家修改 menu.lst 文件这样的,都是很过时的内容了。关于 Grub2 的详细资料可以阅读文档info grub或者info grub2。Grub 的优点是支持多操作系统的启动,Windows、Linux、BSD都不在话下,但是它们的启动协议是不一样的,启动 Windows 靠的是 chainloader,启动 Linux 直接使用 linux 命令,而 BSD 系统好像是使用的 multiboot,特别是 Grub 的 multiboot 规范对那些很想自己写操作系统的程序员很有用,因为再也不用从启动扇区的代码开始写了,可以直接进入保护模式用 C 语言写操作系统。Linux 系统在启动的时候还有 initrd 或 initramfs 机制,这个也需要 Linux 用户特别注意。initrd 或 initramfs 也经历过一次技术革新,最开始采用的是 RamDisk 技术,目前采用的 tmpfs 技术。最后,内核加载入内存后,会将系统初始化的控制权交给 init 程序,而 init 程序也不是一成不变的,最开始使用的是 SysVinit,后来又改成 Upstart,现在最流行的又变成了 systemd。现在仍然有很多书和网文讲的还是 SysVinit 时代的做法,当然这些文章只能以 RedHat EL 5 以及之前的发行版作为示例,而对于最新的发行版,这些文章中讲的方法又会给新入门的用户造成困难。

探索 Linux 系统的启动过程是每一个 Linux 用户进步到一定程度后必然要去做的一件事。有的人可能只是好奇,也有的人可能确实需要从启动过程中去解决某一个特定的问题。这种探索我进行过多次,也早就想写一写,但是总是写不好。这一次是借着 systemd 普及的东风,让这个探索过程成为我这一系列的压轴之作。为什么说是借着 systemd 普及的东风呢?因为听说本月即将发布的 Ubuntu 15.04 版本将会从 upstart 更换为 systemd,目前正在紧张地测试中。当然,一下子迁移过来对开发人员来讲还是有点难度的,但是只要确定目标,即使 Ubuntu 15.04 不使用 systemd,到 15.10 也肯定是它了。因此,对于我这个喜欢折腾多个 Linux 发行版的人来说,就不用多线作战了,只需要学好 systemd 即可。

Linux启动过程概述

这里先简单列一下 Linux 操作系统启动的全过程:

  1. 按下电脑的电源键后,电脑通电,BIOS启动;
  2. BIOS读取硬盘的MBR,运行启动扇区中的代码,旧系统往往需要自己写启动扇区,而新系统基本上由专用的启动软件接管了,在 Linux 世界中,目前都是用的 Grub2。由于启动扇区空间太小,放不下太复杂的代码逻辑,所以 Grub2 也使用了多阶段启动的策略;
  3. Grub2 负责将操作系统内核加载到内存,如果有必要,也会把 initramfs 文件加载到内存,然后将控制权交给内核;
  4. 内核进行初始化,内核的初始化过程结束后,就会把控制权交给/init程序,从此进入用户空间;
  5. 因为内核先是将 initramfs 文件挂在为根文件系统,所以刚开始运行的/init程序其实是 initramfs 文件中的,所以该文件需要的重要的初始化脚本、内核模块、配置文件等,都位于 initramfs 文件中,这也是为什么很多时候我们修改了某些配置文件后,需要先更新 initramfs 文件再重启操作系统才会生效;
  6. initramfs 文件中的/init程序负责挂载硬盘上的文件系统,然后再把根文件系统切换到硬盘上的根分区,再运行/sbin/init程序,这时所有程序、配置文件、脚本都是使用的硬盘上的了,当然,网络文件系统也是同理。可以看出 init 程序的运行也是一个分阶段的过程;
  7. /sbin/init程序负责系统的初始化、各种服务的运行、用户的登陆等等;
  8. 如果需要运行图形界面,则/sbin/init程序会运行 Display Manager,在 Fedora 中是 gdm,在 Ubuntu 中是 lightdm。然后 Display Manager 负责启动整个图形界面。

Grub加载Linux内核和initramfs

研究 Linux 启动过程的第一站就是要研究 Grub。大部分时候我们不用考虑 Grub 的安装,因为安装 Linux 桌面系统的时候已经帮我们自动安装好了,我们要做的,最多就是改改 Grub 的配置文件。只有在一种情况下需要手动安装 Grub,那就是在系统崩溃后急救的时候,当然,需要先运行一个光盘版或 U 盘版的 Linux 系统,然后运行grub-install命令,这个命令需要指定将 Grub 安装到那个硬盘的 MBR,具体操作看文档即可。

Grub2 和 Grub 相比,有了很多改进,改进到 Grub2 都相当于一个小型的操作系统了,比如 Grub2 能识别各种格式的硬盘分区及文件系统,能识别各种格式的图像,甚至还能动态加载模块。其中最重要的改变是配置文件由 Grub1 时代的/boot/menu.lst文件变成 Grub2 时代的/boot/grub/grub.cfg文件了(注意,有的系统中是/boot/grub2/grub.cfg,比如 Fedora),配置文件的语法也变复杂了很多。虽然 Grub2 配置文件的语法比较复杂,但是我们一般不需要直接修改该文件,而是修改/etc/default/grub文件后,再使用update-grub命令自动生成配置文件。当然,要完成一些简单任务时对/boot/grub/grub.cfg文件进行直接修改也是可以的,只是一旦系统更新,则对该文件的修改有可能被覆盖。

Grub 最重要的任务就是加载 Linux 内核以及 initramfs 文件了,这几个命令是非常简单易懂的,在/boot/grub/grub.cfg文件中也非常容易找到,如下图:
探索Linux系统的启动过程

上图主要是展示 Grub 载入 Linux 内核并且给 Linux 内核传递参数的过程,同时展示载入 initramfs 文件的过程。

探索initramfs文件的方法

Grub 将 Linux 内核和 initramfs 文件载入内存后,就把控制权交给内核,内核会将内存中的 initramfs 文件挂载为根文件系统,当内核初始化完成后需要进入用户空间时,会运行/init,其实这个/init是在 initramfs 文件中的,所以如果想知道系统启动后都干了些什么,研究一下 initramfs 文件中的内容是有必要的。

/boot目录下,一般都有一个/boot/initrd.img文件或一个/boot/initramfs.img文件,但是要看里面的内容还得有点技巧。先得学习一下历史。第一个问题就是为什么要有 initrd 或者 initramfs,答案是这样的,为了减小 Linux 内核的大小,有一些驱动并没有编译进内核,而是以模块的形式存在于文件系统中,但是有些文件系统呢又需要内核加载了相应的驱动模块才能读取,这样就形成了一个是先有鸡还是先有蛋这样的一个矛盾的问题,为了解决这个问题,就是把这些驱动模块放到 initrd 或 initramfs 文件中,由启动器将其载入内存,然后内核加载内存中的驱动模块,再驱动其它的文件系统,然后进行更多的工作。同时,对于很多嵌入式操作系统而言,可能载入内存的 initrd 或 initramfs 就是最终的文件系统。第二个问题就是 initrd 文件和 initramfs 文件是什么格式的,怎么创建和打开。这个问题有点复杂,且看我下面细细道来。

其实在 Linux 的历史中,内存中的文件系统使用的技术还不一样。在很老的系统中,是将某一块内存模拟成磁盘,称之为 RamDisk,这个技术效率不高,为什么不高呢,那是因为将内存模拟成磁盘后,依然需要像对待磁盘一样对待它,需要给它创建特定格式的文件系统,读取或写入文件的时候还是要用到内核的缓存系统,这样同一份数据就在内存中存在了两份,所以很浪费。老系统中使用的 initrd 文件其实就相当于是一个磁盘的镜像,所以要读取或写入它的内容,就需要将它挂在到系统中,然后进行读写。

现在我们用的都是新系统,initrd 已经被 initramfs 取代了,虽然很多系统中仍然用initrd.img作为文件名,其实使用的是 initramfs 技术。initramfs 技术不使用 RamDisk,而是使用 tempfs,也就是复用了内核中的缓存系统,所以 tempfs 中的内容在内存中只存在一份,那就是在内核的缓存中,不仅节约了内存开销,也提高了效率。那么 initramfs 文件是什么格式呢?它就是经过压缩的 CPIO 格式,只要会cpio,打开它就不是问题。

其实也不是完全没有问题,我在研究 Fedora 21 的 initramfs.img 文件时就碰到了困难。如果大家使用cat initramfs.img | cpio -imd将该 CPIO 文件解开,会发现只有少许几个文件,文件数量和文件大小都远远低于我们的预期,如下图:
探索Linux系统的启动过程

问题究竟出在哪里呢?这是因为 Fedora 21 采用了最新的 Early User Space 技术。这个问题在网上还暂时搜不到答案,我也是看到解压出的文件中有一个early_cpio,然后以 early 为关键词去看内核的代码,才知道的。但是关于该技术的具体信息,我也没搞懂。不过这不是重点,重点是如何正确地将该文件解包。首先,我找到了 Fedora 21 系统中用来构建 initramfs 文件的工具是 dracut,采用的方法论请看我该系列的第一篇,如下图:
探索Linux系统的启动过程

然后,我发现 dracut 软件包中提供的lsinitrd工具可以查看 initramfs 中的内容,而且该工具是一个脚本。所以我就把它打开看了一下,如下图:
探索Linux系统的启动过程

在这个脚本中,我发现它需要用到一个skipcpio程序,跳过 initramfs 文件的头部后,再将剩下的部分当成压缩的 CPIO 文件进行解包。看来,会读脚本程序有时也很重要,所以我的《Bash脚本编程语言中的美学与哲学》这一篇没有白写。最后,解包 initramfs 文件的命令是这样的:

sudo /usr/lib/dracut/skipcpio initramfs-$(uname -r).img | zcat | cpio -imd

如下图:
探索Linux系统的启动过程

接下来,我们就可以愉快地研究/init程序都干些什么了。

相关推荐