• Linux 引导过程内幕

    从主引导记录到第一个用户空间应用程序的指导

  • 引导 Linux 系统的过程包括很多阶段。不管您是引导一个标准的 x86 桌面系统,还是引导一台嵌入式的 PowerPC 机器,很多流程都惊人地相似。本文将探索 Linux 的引导过程,从最初的引导到启动第一个用户空间应用程序。在本文介绍的过程中,您将学习到各种与引导有关的主题,例如引导加载程序、内核解压、初始 RAM 磁盘以及 Linux 引导的其他一些元素。

  • 早期时,启动一台计算机意味着要给计算机喂一条包含引导程序的纸带,或者手工使用前端面板地址/数据/控制开关来加载引导程序。尽管目前的计算机已经装备了很多工具来简化引导过程,但是这一切并没有对整个过程进行必要的简化。

    让我们先从高级的视角来查看 Linux 引导过程,这样就可以看到整个过程的全貌了。然后将回顾一下在各个步骤到底发生了什么。在整个过程中,参考一下内核源代码可以帮助我们更好地了解内核源代码树,并在以后对其进行深入分析。

    概述

    图 1 是我们在 20,000 英尺的高度看到的视图。

    图 1. Linux 引导过程在 20,000 英尺处的视图
    Linux 引导过程在 20,000 英尺处的视图

    当系统首次引导时,或系统被重置时,处理器会执行一个位于已知位置处的代码。在个人计算机(PC)中,这个位置在基本输入/输出系统(BIOS)中,它保存在主板上的闪存中。嵌入式系统中的中央处理单元(CPU)会调用这个重置向量来启动一个位于闪存/ROM 中的已知地址处的程序。在这两种情况下,结果都是相同的。因为 PC 提供了很多灵活性,BIOS 必须确定要使用哪个设备来引导系统。稍后我们将详细介绍这个过程。

    当找到一个引导设备之后,第一阶段的引导加载程序就被装入 RAM 并执行。这个引导加载程序在大小上小于 512 字节(一个扇区),其作用是加载第二阶段的引导加载程序。

    当第二阶段的引导加载程序被装入 RAM 并执行时,通常会显示一个动画屏幕,并将 Linux 和一个可选的初始 RAM 磁盘(临时根文件系统)加载到内存中。在加载映像时,第二阶段的引导加载程序就会将控制权交给内核映像,然后内核就可以进行解压和初始化了。在这个阶段中,第二阶段的引导加载程序会检测系统硬件、枚举系统链接的硬件设备、挂载根设备,然后加载必要的内核模块。完成这些操作之后启动第一个用户空间程序(init),并执行高级系统初始化工作。

    这就是 Linux 引导的整个过程。现在让我们深入挖掘一下这个过程,并深入研究一下 Linux 引导过程的一些详细信息。

系统启动

系统启动阶段依赖于引导 Linux 系统上的硬件。在嵌入式平台中,当系统加电或重置时,会使用一个启动环境。这方面的例子包括 U-Boot、RedBoot 和 Lucent 的 MicroMonitor。嵌入式平台通常都是与引导监视器搭配销售的。这些程序位于目标硬件上的闪存中的某一段特殊区域,它们提供了将 Linux 内核映像下载到闪存并继续执行的方法。除了可以存储并引导 Linux 映像之外,这些引导监视器还执行一定级别的系统测试和硬件初始化过程。在嵌入式平台中,这些引导监视器通常会涉及第一阶段和第二阶段的引导加载程序。

提取 MBR 的信息

要查看 MBR 的内容,请使用下面的命令:

# dd if=/dev/hda of=mbr.bin bs=512 count=1 # od -xa mbr.bin

这个 dd 命令需要以 root 用户的身份运行,它从 /dev/hda(第一个 IDE 盘) 上读取前 512 个字节的内容,并将其写入 mbr.bin 文件中。od 命令会以十六进制和 ASCII 码格式打印这个二进制文件的内容。

在 PC 中,引导 Linux 是从 BIOS 中的地址 0xFFFF0 处开始的。BIOS 的第一个步骤是加电自检(POST)。POST 的工作是对硬件进行检测。BIOS 的第二个步骤是进行本地设备的枚举和初始化。

给定 BIOS 功能的不同用法之后,BIOS 由两部分组成:POST 代码和运行时服务。当 POST 完成之后,它被从内存中清理了出来,但是 BIOS 运行时服务依然保留在内存中,目标操作系统可以使用这些服务。

要引导一个操作系统,BIOS 运行时会按照 CMOS 的设置定义的顺序来搜索处于活动状态并且可以引导的设备。引导设备可以是软盘、CD-ROM、硬盘上的某个分区、网络上的某个设备,甚至是 USB 闪存。

通常,Linux 都是从硬盘上引导的,其中主引导记录(MBR)中包含主引导加载程序。MBR 是一个 512 字节大小的扇区,位于磁盘上的第一个扇区中(0 道 0 柱面 1 扇区)。当 MBR 被加载到 RAM 中之后,BIOS 就会将控制权交给 MBR。

第一阶段引导加载程序

MBR 中的主引导加载程序是一个 512 字节大小的映像,其中包含程序代码和一个小分区表(参见图 2)。前 446 个字节是主引导加载程序,其中包含可执行代码和错误消息文本。接下来的 64 个字节是分区表,其中包含 4 个分区的记录(每个记录的大小是 16 个字节)。MBR 以两个特殊数字的字节(0xAA55)结束。这个数字会用来进行 MBR 的有效性检查。

图 2. MBR 剖析
MBR 剖析

主引导加载程序的工作是查找并加载次引导加载程序(第二阶段)。它是通过在分区表中查找一个活动分区来实现这种功能的。当找到一个活动分区时,它会扫描分区表中的其他分区,以确保它们都不是活动的。当这个过程验证完成之后,就将活动分区的引导记录从这个设备中读入 RAM 中并执行它。

第二阶段引导加载程序

次引导加载程序(第二阶段引导加载程序)可以更形象地称为内核加载程序。这个阶段的任务是加载 Linux 内核和可选的初始 RAM 磁盘。

GRUB 阶段引导加载程序

/boot/grub 目录中包含了 stage1stage1.5stage2 引导加载程序,以及很多其他加载程序(例如,CR-ROM 使用的是 iso9660_stage_1_5)。

在 x86 PC 环境中,第一阶段和第二阶段的引导加载程序一起称为 Linux Loader(LILO)或 GRand Unified Bootloader(GRUB)。由于 LILO 有一些缺点,而 GRUB 克服了这些缺点,因此下面让我们就来看一下 GRUB。(有关 GRUB、LILO 和相关主题的更多内容,请参阅本文后面的 参考资料 部分的内容。)

关于 GRUB,很好的一件事情是它包含了有关 Linux 文件系统的知识。GRUB 不像 LILO 一样使用裸扇区,而是可以从 ext2 或 ext3 文件系统中加载 Linux 内核。它是通过将两阶段的引导加载程序转换成三阶段的引导加载程序来实现这项功能的。阶段 1 (MBR)引导了一个阶段 1.5 的引导加载程序,它可以理解包含 Linux 内核映像的特殊文件系统。这方面的例子包括reiserfs_stage1_5(要从 Reiser 日志文件系统上进行加载)或 e2fs_stage1_5(要从 ext2 或 ext3 文件系统上进行加载)。当阶段 1.5 的引导加载程序被加载并运行时,阶段 2 的引导加载程序就可以进行加载了。

当阶段 2 加载之后,GRUB 就可以在请求时显示可用内核列表(在 /etc/grub.conf 中进行定义,同时还有几个软符号链接/etc/grub/menu.lst/etc/grub.conf)。我们可以选择内核甚至修改附加内核参数。另外,我们也可以使用一个命令行的 shell 对引导过程进行高级手工控制。

将第二阶段的引导加载程序加载到内存中之后,就可以对文件系统进行查询了,并将默认的内核映像和 initrd 映像加载到内存中。当这些映像文件准备好之后,阶段 2 的引导加载程序就可以调用内核映像了。

内核

GRUB 中的手工引导

在 GRUB 命令行中,我们可以使用 initrd 映像引导一个特定的内核,方法如下:

grub> kernel /bzImage-2.6.14.2
[Linux-bzImage, setup=0x1400, size=0x29672e]
grub> initrd /initrd-2.6.14.2.img
[Linux-initrd @ 0x5f13000, 0xcc199 bytes]
grub> boot
Uncompressing Linux... Ok, booting the kernel.

如果您不知道要引导的内核的名称,只需使用斜线(/)然后按下 Tab 键即可。GRUB 会显示内核和 initrd 映像列表。

当内核映像被加载到内存中,并且阶段 2 的引导加载程序释放控制权之后,内核阶段就开始了。内核映像并不是一个可执行的内核,而是一个压缩过的内核映像。通常它是一个 zImage(压缩映像,小于 512KB)或一个 bzImage(较大的压缩映像,大于 512KB),它是提前使用 zlib 进行压缩过的。在这个内核映像前面是一个例程,它实现少量硬件设置,并对内核映像中包含的内核进行解压,然后将其放入高端内存中,如果有初始 RAM 磁盘映像,就会将它移动到内存中,并标明以后使用。然后该例程会调用内核,并开始启动内核引导的过程。

当 bzImage(用于 i386 映像)被调用时,我们从 ./arch/i386/boot/head.Sstart 汇编例程开始执行(主要流程图请参看图 3)。这个例程会执行一些基本的硬件设置,并调用./arch/i386/boot/compressed/head.S 中的 startup_32 例程。此例程会设置一个基本的环境(堆栈等),并清除 Block Started by Symbol(BSS)。然后调用一个叫做decompress_kernel 的 C 函数(在 ./arch/i386/boot/compressed/misc.c 中)来解压内核。当内核被解压到内存中之后,就可以调用它了。这是另外一个 startup_32 函数,但是这个函数在 ./arch/i386/kernel/head.S 中。

在这个新的 startup_32 函数(也称为清除程序或进程 0)中,会对页表进行初始化,并启用内存分页功能。然后会为任何可选的浮点单元(FPU)检测 CPU 的类型,并将其存储起来供以后使用。然后调用 start_kernel 函数(在 init/main.c 中),它会将您带入与体系结构无关的 Linux 内核部分。实际上,这就是 Linux 内核的 main 函数。

图 3. Linux 内核 i386 引导的主要函数流程
Linux 内核 i386 引导的主要函数流程

通过调用 start_kernel,会调用一系列初始化函数来设置中断,执行进一步的内存配置,并加载初始 RAM 磁盘。最后,要调用kernel_thread(在 arch/i386/kernel/process.c 中)来启动 init 函数,这是第一个用户空间进程(user-space process)。最后,启动空任务,现在调度器就可以接管控制权了(在调用 cpu_idle 之后)。通过启用中断,抢占式的调度器就可以周期性地接管控制权,从而提供多任务处理能力。

在内核引导过程中,初始 RAM 磁盘(initrd)是由阶段 2 引导加载程序加载到内存中的,它会被复制到 RAM 中并挂载到系统上。这个initrd 会作为 RAM 中的临时根文件系统使用,并允许内核在没有挂载任何物理磁盘的情况下完整地实现引导。由于与外围设备进行交互所需要的模块可能是 initrd 的一部分,因此内核可以非常小,但是仍然需要支持大量可能的硬件配置。在内核引导之后,就可以正式装备根文件系统了(通过 pivot_root):此时会将 initrd 根文件系统卸载掉,并挂载真正的根文件系统。

decompress_kernel 输出

函数 decompress_kernel 就是显示我们通常看到的解压消息的地方:

Uncompressing Linux... Ok, booting the kernel.

initrd 函数让我们可以创建一个小型的 Linux 内核,其中包括作为可加载模块编译的驱动程序。这些可加载的模块为内核提供了访问磁盘和磁盘上的文件系统的方法,并为其他硬件提供了驱动程序。由于根文件系统是磁盘上的一个文件系统,因此 initrd 函数会提供一种启动方法来获得对磁盘的访问,并挂载真正的根文件系统。在一个没有硬盘的嵌入式环境中,initrd 可以是最终的根文件系统,或者也可以通过网络文件系统(NFS)来挂载最终的根文件系统。

Init

当内核被引导并进行初始化之后,内核就可以启动自己的第一个用户空间应用程序了。这是第一个调用的使用标准 C 库编译的程序。在此之前,还没有执行任何标准的 C 应用程序。

在桌面 Linux 系统上,第一个启动的程序通常是 /sbin/init。但是这不是一定的。很少有嵌入式系统会需要使用 init 所提供的丰富初始化功能(这是通过 /etc/inittab 进行配置的)。在很多情况下,我们可以调用一个简单的 shell 脚本来启动必需的嵌入式应用程序。

结束语

与 Linux 本身非常类似,Linux 的引导过程也非常灵活,可以支持众多的处理器和硬件平台。最初,加载引导加载程序提供了一种简单的方法,不用任何花架子就可以引导 Linux。LILO 引导加载程序对引导能力进行了扩充,但是它却缺少文件系统的感知能力。最新一代的引导加载程序,例如 GRUB,允许 Linux 从一些文件系统(从 Minix 到 Reise)上进行引导。

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文 。

  • centos修复引导。Boot Records Revealed 是有关 MBR 和各种引导加载程序很好的资源。这个资源不仅仅是有关 MBR 的资料的汇编,还讨论了 GRUB、LILO 和各种 Windows 引导加载程序的问题。

  • 请查看 Disk Geometry 页面来理解磁盘及其结构。您会发现有关磁盘的有用属性。

  • live CD 是一个可以从 CD 或 DVD 上引导的操作系统,它不需要使用硬盘。

  • “引导加载程序之争:了解 LILO 和 GRUB”(developerWorks,2005 年 8 月)详细介绍了 LILO 和 GRUB 引导加载程序。

  • 在 developerWorks 上的 LPI 考试准备 系列教程中,我们可以学习有关引导 Linux 系统的详细介绍,以及在准备参加系统管理员认证考试时需要准备的 Linux 基础知识。

  • LILO 是 GRUB 的先驱,但是我们可能发现它依然可以引导 Linux。

  • 系统引导的过程?mkintrd 命令用来创建初始的 RAM 磁盘映像。这个命令可以用来构建初始的根文件系统,它可以用来引导允许提前加载访问真正根文件系统所需要的块设备的配置。

  • 在 Debian Linux Kernel Project 中,我们可以找到更多有关 Linux 内核、引导和嵌入式开发的信息。

  • 在 developerWorks Linux 专区 中可以找到为 Linux 开发人员准备的更多资源。

  • 随时关注 developerWorks 技术事件和网络广播。







  • ******************************************************************************************************************************************************************************************************************************************

  •   最近在自学 Linux kernel 方面的东西,这两天了粗浅的研究了下 kernel boot 过程,在此记录。这里所指 Linux 引导加载未涉及虚拟化环境,即系统未运行在 hypervisor 之上。


             Linux 通过执行不同阶段的引导加载程序(boot loader)程序来引导操作系统,在完成内核等引导之后,最终会由调度器接管 CPU,其通过启用中断来周期性的抢占控制权,处理多个用户进程/客户进程(kvm 虚拟化)。Top level 的引导过程如下图。


    Top level boot loader



             整个 Linux 系统引导共分 5 步执行操作:


    1. 引导过程,BIOS/BootMonitor 引导程序;BIOS 包括 POST 和 Runtime 服务。

    2. 被称为第一阶段的 MBR (Master boot record)引导程序;位于 BIOS 配置的启动磁盘 0 柱面 1 扇区的主引导记录,用于启动第二阶段的 linux boot loader。

    3. 被称为第二阶段的 linux boot loader;主要有 LILO (Linux loader)和 GNU GRUB (Grand unified boot loader)两种 boot loader 程序,现主流为 GRUB。包括了通过 initrd 来创建 RAM 盘,执行 init 脚本,通过 LKM (linux kernel module)加载本地磁盘等驱动程序来挂载磁盘中的 root 文件系统。RAM 盘中是个完整的小型 linux 环境,在没有磁盘的嵌入式环境中,initrd 可以是最终的根文件系统,也可以通过 NFS 来挂载最终的文件系统。

    4. linux kernel (及 initrd 函数)引导;负责加载并解压 zImage/bzImage kernel 及 initrd 映像,并开始执行 kernel 初使化和引导程序/过程。

    5. init 进程。用于启动 linux 配置的各项用户空间服务(demon)进程。


    加电后首先被执行的是 BIOS (Base input/output system)程序。 嵌入式环境使用 boot monitor,它负责在一个位于 rom/flash 中预定地址开始执行引导程序,而在 PC 环境中这个启动地址是 0xFFFF0,相对来讲 BIOS 提供了更多的配置功能。它主要由两部分组成:


    1. POST (Power On Self Test)程序;其负责接通电源时对硬件检测,包括创建中断向量、设置寄存器、对一些外部设备进行初始化和检测等。

    2. LINUX教程。BIOS Runtime 服务;负责为操作系统提供一些基础服务,主要与IO外设有关。

             当 BIOS POST 执行完后,其将会从内存中清理,而 Runtime 服务会常驻内存,为操作系统提供一些底层的支持。最后 BIOS 将控制权交给称为第一阶段引导程序的 MBR (Master boot record)程序。


    接下来执行的 MBR 是一个512 byte 固定大小的映像。 包括 446 byte 长的被称为初始程序加裁程序 (Initial program loader, IPL)的可执行代码和 64 byte 分区表(16 byte * 4 个),最后以 0xaa55 特殊字节结束。如下图所示。


    MBR


             MBR 引导程序会将扫描分区表,获得唯一活动分区后,将其中的引导程序读入 RAM 并开始执行。


             MBR 启动的引导程序被称为第二阶段引导程序,它是引导的主体,是引导加载的真正部分。 Linux 中该阶段有两个流行的程序,LILO (较老)和 GRUB。如果安装了 lilo 程序,可以通过 root 用户执行如下命令来通过 lilo 生成默认配置的 MBR ,并写入到启动磁盘 0 柱面 1扇区位置上。


    Shell代码  收藏代码
    1. # /sbin/lilo -v -v  


             一般需要修改 lilo 的配置文件,使生成的 MBR 有效。位于 /etc/lilo.conf 。lilo 配置示例。


    Config代码  收藏代码
  • boot=/dev/hda
    map=/boot/map
    install=/boot/boot.b
    prompt
    timeout=100
    compact
    default=Linux
    p_w_picpath=/boot/vmlinuz-2.4.18-14label=Linuxroot=/dev/hdb3read-onlypassword=linux
    other=/dev/hdalabel=WindowsXP
  •          boot 键指定了 lilo 在哪里安装 MBR。可以通过替换 boot=/dev/fd0 配置来指定 lilo 创建有引导记录的软盘。


    LILO 天生存在一些缺点和不足,因此 linux 在新版本中引入了 GRUB 程序。 它为了从磁盘来加裁配置和 kernel 映像,不像 LILO 只能从裸扇区中执行引导程序,而具有了访问磁盘文件系统(ext2/3、reiserfs、jfs、fat 等)的能力。GRUB 是通过引入所谓 1.5 阶段的引导加载程序来实现这项功能的,在该阶段中,GRUB 主要来加载特殊的文件系统驱动。此后,阶段 2 的引导加载程序就可以进行加载了。

             一般 GRUB 有一个不错的 GUI 界面,其中通过分析配置文件来显示了一此引导选项。在我的 ubuntu 8.10 系统中,该配置文件位于 /boot/grub/menu.lst。我们可以选择内核甚至修改附加内核参数,甚至可以使用 GRUB shell 对引导过程进行高级手工控制。我的 menu.lst 文件内容如下。


    Config代码  收藏代码
    default  0
    timeout  3
    hiddenmenu
    title  Ubuntu 8.10, kernel 2.6.27-11-generic
    uuid   e2cf53c5-11de-4d57-a532-878901afd9b4
    kernel /boot/vmlinuz-2.6.27-11-generic root=UUID=e2cf53c5-11de-4d57-a532-878901afd9b4 ro locale=zh_CN quiet splash
    initrd /boot/initrd.img-2.6.27-11-generic
    quiet
    title  Ubuntu 8.10, kernel 2.6.27-11-generic (recovery mode)
    uuid   e2cf53c5-11de-4d57-a532-878901afd9b4
    kernel /boot/vmlinuz-2.6.27-11-generic root=UUID=e2cf53c5-11de-4d57-a532-878901afd9b4 ro locale=zh_CN  single
    initrd /boot/initrd.img-2.6.27-11-generic
    title  Ubuntu 8.10, kernel 2.6.27-7-generic
    uuid   e2cf53c5-11de-4d57-a532-878901afd9b4
    kernel /boot/vmlinuz-2.6.27-7-generic root=UUID=e2cf53c5-11de-4d57-a532-878901afd9b4 ro locale=zh_CN quiet splash
    initrd /boot/initrd.img-2.6.27-7-generic
    quiet
    title  Ubuntu 8.10, kernel 2.6.27-7-generic (recovery mode)
    uuid   e2cf53c5-11de-4d57-a532-878901afd9b4
    kernel /boot/vmlinuz-2.6.27-7-generic root=UUID=e2cf53c5-11de-4d57-a532-878901afd9b4 ro locale=zh_CN  single
    initrd /boot/initrd.img-2.6.27-7-generic
    title  Ubuntu 8.10, memtest86+
    uuid   e2cf53c5-11de-4d57-a532-878901afd9b4
    kernel /boot/memtest86+.bin
    quiet



             将第二阶段的引导加载程序加载到内存中之后,就可以对文件系统进行查询了,并将默认的内核映像和 initrd 映像加载到内存中。当这些映像文件准备好之后,阶段 2 的引导加载程序就可以调用内核映像。 正如上面配置文件描述的那样,我的 ubuntu 启动会将加载 /boot/vmlinuz-2.6.27-11-generic (zImage/bzImage 格式的 kernel 映像)和 /boot/initrd.img-2.6.27-11-generic (cpio 格式的 initrd 映像)。


    接下来就是 kernel 引导加载过程,这个过程包括如下 6 步。


    1. 执行一个对硬件做些基本设置的例程;
      (,/arch/i386/boot/head.S 中的 start 例程)

    2. 设置一个基本的环境(堆栈等),并清除 Block Started by Symbol(BSS);
      (./arch/i386/boot/compressed/head.S 中的 startup_32 例程)

    3. 通过连接在映像中的函数来解压内核;
      (./arch/i386/boot/compressed/misc.c 中的 decompress_kernel C 函数)

    4. vim使用技巧,启动 swapper (0 进程)进程,初始化页表,启用 CPU 内存分页。然后会为任何可选的浮点单元(FPU)检测 CPU 的类型,并将其存储起来供以后使用;
      (./arch/i386/kernel/head.S 中的 startup_32 函数)

    5. 调用 linux kernl main 函数,进入与体系结构无关的 Linux 内核部分。
      (init/main.c 中的 start_kernel 函数 )
      这会调用一系列初始化函数来设置中断,执行进一步的内存配置,并加载已初始化的 RAM 盘。最后启动 init 进程,这是第一个用户空间进程(user-space process);
      (./arch/i386/kernel/process.c 中的 kernel_thread)

    6. 最后,启动空任务。现在调度器就可以接管控制权了(在调用 cpu_idle 之后)。通过启用中断,抢占式的调度器就可以周期性地接管控制权,从而提供多任务处理能力。

             上面第 5 步加载的 RAM 盘(initrd)是由阶段 2 引导加载程序加载到内存中的,它用来加载必要的磁盘驱动内核模块,来挂载真正磁盘的 root 文件系统。


    Kernel boot sequence


    引导加载的最后的一步就是执行 init (1 进程),该进程会根据配置来启动服务。 一般的配置都会写在 inittab 里,不过我这里用的 ubuntu 使用的是 upstart,它是基于事件驱动的,发生什么 event 怎么处理,在这里 init 进程会产生 startup event, upstart 据此来启动 rc.* 配置的进程。不过无论如何,此时引导加载程序已经放权了。


    这里抄录一段 LILO 与 GURB 的优缺点对比。


    1. LILO 没有交互式命令界面,而 GRUB 拥有。

    2. LILO 不支持网络引导,而 GRUB 支持。

    3. LILO 将关于可以引导的操作系统位置的信息物理上存储在 MBR 中。如果修改了 LILO 配置文件,必须将 LILO 第一阶段引导加载程序重写到 MBR。相对于 GRUB,这是一个更为危险的选择,因为错误配置的 MBR 可能会让系统无法引导。使用 GRUB,如果配置文件配置错误,则只是默认转到 GRUB 命令行界面。

    关于 kernel 和 initrd 两个映像。 技术含量很高的,嵌入式开发中 bootloader 可是很大一块。值得深入,只可惜现在的技术水平,哎~


    1. 引导,kernel /boot/vmlinuz-2.6.27-7-generic

    2. initrd /boot/initrd.img-2.6.27-7-generic


    initrd 映像是打包的 RAM 盘根文件系统。 一般 initrd.img-2.6.27-7-generic 是一个 cpio 包文件,老版本也有 gzip 压缩格式的。通过 cpio 命令将其解包到当前目录中,如下。cpio 使用方法可参见 cpio 命令详解 。


    Shell代码  收藏代码
    1. zcat initrd.img-2.6.27-11-generic | cpio -i -d --no-absolute-filenames  

             在我这里解包后的根文件系统包括如下内容。


    initrd.img-2.6.27-7-generic


             从上面的 directory tree 可以看到 initrd 中主要包括的就是磁盘、网络、文件系统的驱动 lkm 文件。其中还有最主要是的 init shell 脚本,它包括了初使化的全过程。


    Shell代码  收藏代码
    #!/bin/sh
    echo "Loading, please wait..."
    [ -d /dev ] || mkdir -m 0755 /dev
    [ -d /root ] || mkdir -m 0700 /root
    [ -d /sys ] || mkdir /sys
    [ -d /proc ] || mkdir /proc
    [ -d /tmp ] || mkdir /tmp
    mkdir -p /var/lock
    mount -t sysfs -o nodev,noexec,nosuid none /sys
    mount -t proc -o nodev,noexec,nosuid none /proc
    # Note that this only becomes /dev on the real filesystem if udev's scripts
    # are used; which they will be, but it's worth pointing out
    mount -t tmpfs -o mode=0755 udev /dev
    [ -e /dev/console ] || mknod -m 0600 /dev/console c 5 1
    [ -e /dev/null ] || mknod /dev/null c 1 3
    > /dev/.initramfs-tools
    mkdir /dev/.initramfs
    # Export the dpkg architecture
    export DPKG_ARCH=
    . /conf/arch.conf
    # Set modprobe env
    export MODPROBE_OPTIONS="-Qb"
    # Export relevant variables
    export ROOT=
    export ROOTDELAY=
    export ROOTFLAGS=
    export ROOTFSTYPE=
    export break=
    export init=/sbin/init
    export quiet=n
    export readonly=y
    export rootmnt=/root
    export debug=
    export panic=
    export blacklist=
    export resume_offset=
    # Bring in the main config
    . /conf/initramfs.conf
    for conf in conf/conf.d/*; do[ -f ${conf} ] && . ${conf}
    done
    . /scripts/functions
    # Parse command line options
    for x in $(cat /proc/cmdline); docase $x ininit=*)init=${x#init=};;root=*)ROOT=${x#root=}case $ROOT inLABEL=*)ROOT="/dev/disk/by-label/${ROOT#LABEL=}";;UUID=*)ROOT="/dev/disk/by-uuid/${ROOT#UUID=}";;/dev/nfs)[ -z "${BOOT}" ] && BOOT=nfs;;esac;;rootflags=*)ROOTFLAGS="-o ${x#rootflags=}";;rootfstype=*)ROOTFSTYPE="${x#rootfstype=}";;rootdelay=*)ROOTDELAY="${x#rootdelay=}"case ${ROOTDELAY} in*[![:digit:].]*)ROOTDELAY=;;esac;;resumedelay=*)RESUMEDELAY="${x#resumedelay=}";;loop=*)LOOP="${x#loop=}";;loopflags=*)LOOPFLAGS="-o ${x#loopflags=}";;loopfstype=*)LOOPFSTYPE="${x#loopfstype=}";;cryptopts=*)cryptopts="${x#cryptopts=}";;nfsroot=*)NFSROOT="${x#nfsroot=}";;netboot=*)NETBOOT="${x#netboot=}";;ip=*)IPOPTS="${x#ip=}";;boot=*)BOOT=${x#boot=};;resume=*)RESUME="${x#resume=}";;resume_offset=*)resume_offset="${x#resume_offset=}";;noresume)noresume=y;;panic=*)panic="${x#panic=}"case ${panic} in*[![:digit:].]*)panic=;;esac;;quiet)quiet=y;;ro)readonly=y;;rw)readonly=n;;debug)debug=yquiet=nexec >/tmp/initramfs.debug 2>&1set -x;;debug=*)debug=yquiet=nset -x;;break=*)break=${x#break=};;break)break=premount;;blacklist=*)blacklist=${x#blacklist=};;esac
    done
    if [ -z "${noresume}" ]; thenexport resume=${RESUME}
    elseexport noresume
    fi
    depmod -a
    maybe_break top
    # export BOOT variable value for compcache,
    # so we know if we run from casper
    export BOOT
    # Don't do log messages here to avoid confusing usplash
    run_scripts /scripts/init-top
    maybe_break modules
    log_begin_msg "Loading essential drivers..."
    load_modules
    log_end_msg
    maybe_break premount
    [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-premount"
    run_scripts /scripts/init-premount
    [ "$quiet" != "y" ] && log_end_msg
    maybe_break mount
    log_begin_msg "Mounting root file system..."
    . /scripts/${BOOT}
    parse_numeric ${ROOT}
    mountroot
    log_end_msg
    maybe_break bottom
    [ "$quiet" != "y" ] && log_begin_msg "Running /scripts/init-bottom"
    run_scripts /scripts/init-bottom
    [ "$quiet" != "y" ] && log_end_msg
    # Move virtual filesystems over to the real filesystem
    mount -n -o move /sys ${rootmnt}/sys
    mount -n -o move /proc ${rootmnt}/proc
    # Check init bootarg
    if [ -n "${init}" ] && [ ! -x "${rootmnt}${init}" ]; thenecho "Target filesystem doesn't have ${init}."init=
    fi
    # Search for valid init
    if [ -z "${init}" ] ; thenfor init in /sbin/init /etc/init /bin/init /bin/sh; doif [ ! -x "${rootmnt}${init}" ]; thencontinuefibreakdone
    fi
    # No init on rootmount
    if [ ! -x "${rootmnt}${init}" ]; thenpanic "No init found. Try passing init= bootarg."
    fi
    # Confuses /etc/init.d/rc
    if [ -n ${debug} ]; thenunset debug
    fi
    # Chain to real filesystem
    maybe_break init
    exec run-init ${rootmnt} ${init} "$@" <${rootmnt}/dev/console >${rootmnt}/dev/console 2>&1
    panic "Could not execute run-init."


    Kernel 映像与 initrd 不同,它是个 zImage/bzImage 文件。 通过 linux 编译脚本可以确认 zImage 实际上就是是由一个压缩后的内核(piggy.o),连接上一段初始化及解压功能的代码(head.o、misc.o)组成的。前面 kernel 引导加载过程中的硬件基本设置、设置基本环境(堆栈等)并清除BSS,直至解压内核都是 kernel 映像中压缩内核所连接的代码完成的。关于 kernel 映像这块还在研究、学习中。


    学习的资料有如下文档,但不限于此。


    1. Linux 引导过程内幕 。全面的讲解了 linux 系统引导过程。

    2. 引导加载程序之争:了解 LILO 和 GRUB 。其给出了 LILO 详细介绍和配置方法。

    3. centos7启动找不到引导,zImage内核镜像解压过程详解 。从内核开发角度深入介绍了 zImage 内核映像。

    4. Linux 内核映象文件解密 。简要介绍了 initrd 映像。

    5. Ubuntu upstart 简单说明 。

    整个的 Linux kernel 引导过程还在研究、学习中欢迎大家分享、指正。

    最后再次对技术前辈的进取和谦虚致敬!