本文目录:
14.1 按下电源和 bios 阶段
14.2 MBR 和各种 bootloader 阶段
14.2.1 boot loader
14.2.2 分区表
14.2.3 采用 VBR/EBR 方式引导操作系统
14.3 grub 阶段
14.3.1 使用 grub2 时的启动过程
14.3.2 使用传统 grub 时的启动过程
14.4 内核加载阶段
14.4.1 加载 init ramdisk
14.4.2 initrd
14.4.3 initramfs
14.5 操作系统初始化
14.5.1 运行级别
14.5.2 系统环境初始化
14.5.3 运行级别环境初始化
14.6 终端初始化和登录系统
14.6.1 终端初始化
14.6.2 登录过程
计算机启动分为内核加载前、加载时和加载后 3 个大阶段,这 3 个大阶段又可以分为很多小阶段,本文将非常细化分析每一个重要的小阶段。
内核加载前的阶段和操作系统无关,Linux 或 Windows 在这部分的顺序是一样的。由于使用 anaconda 安装 Linux 时,默认的图形界面是不支持 GPT 分区的,即使是目前最新的 CentOS 7.3 也仍然不支持,所以在本文中主要介绍传统 BIOS 平台 (MBR 方式) 的启动方式(其实是本人愚笨,看不懂 uefi 启动方式)。
在内核加载时和加载后阶段,由于 CentOS 7 采用的是 systemd,和 CentOS 5 或 CentOS 6 的 sysV 风格的 init 大不相同,所以本文也只介绍 sysV 风格的 init。
按下电源,计算机开始通电,最重要的是要接通 cpu 的电路,然后通过 cpu 的针脚让 cpu 运行起来,只有 cpu 运行起来才能执行相关代码跳到 bios。
bios 是按下开机键后第一个运行的程序,它会读取 CMOS 中的信息,以了解部分硬件的信息,比如硬件自检 (post)、硬件上的时间、硬盘大小和型号等。其实,手动进入 bios 界面看到的信息,都是在这一阶段获取到的,如下图。对本文来说,最重要的还是获取到了启动设备以及它们的启动顺序(顺序从上到下) 信息。
当硬件检测和信息获取完毕,开始初始化硬件,最后从排在第一位的启动设备中读取 MBR,如果第一个启动设备中没有找到合理的 MBR,则继续从第二个启动设备中查找,直到找到正确的 MBR。
这小节将介绍各种 BR(boot record) 和各种 boot loader,但只是简单介绍其基本作用。
MBR 是主引导记录,位于磁盘的第一个扇区,和分区无关,和操作系统无关,bios 一定会读取 MBR 中的记录。
在 MBR 中存储了 bootloader / 分区表 / BRID。bootloader 占用 446 个字节,用于引导加载;分区表占用 64 个字节,每个主分区或扩展分区占用 16 个字节,如果 16 个字节中的第一个字节为 0x80,则表示该分区为激活的分区 (活动分区),且只允许有一个激活的分区;最后 2 个字节是 BRID(boot record ID),它固定为 0x55AA,用于标识该存储设备的 MBR 是否是合理有效的 MBR,如果 bios 读取 MBR 发现最后两个字节不是 0x55AA,就会读取下一个启动设备。
MBR 中的 bootloader 只占用 446 字节,所以可存储的代码有限,能加载引导的东西也有限,所以在磁盘的不同位置上设计了多种 boot loader。下面将说明各种情况。
在创建文件系统时,是否还记得有些分区的第一个 block 是 boot sector?这个启动扇区中也放了 boot loader,大小也很有限。如果是主分区上的 boot sector,则该段 boot loader 所在扇区称为 VBR(volumn boot record),如果是逻辑分区上的 boot sector,则该段 boot loader 所在扇区称为 EBR(Extended boot sector)。但很不幸,这两种方式的 boot loader 都很少被使用上了,因为它们很不方便,加上后面出现了启动管理器 (LILO 和 GRUB),它们就被遗忘了。但即使如此,在分区中还是存在 boot sector。
硬盘分区的好处之一就是可以在不同的分区中安装不同的操作系统,但 boot loader 必须要知道每个操作系统具体是在哪个分区。
分区表的长度只有 64 个字节,里面又分成四项,每项 16 个字节。所以,一个硬盘最多只能分四个主分区。
每个主分区表项的 16 个字节,都由 6 个部分组成:
(1). 第 1 个字节:只能为 0 或者 0x80。0x80 表示该主分区是激活分区,0 表示非激活分区。单磁盘只能有一个主分区是激活的。
(2). 第 2-4 个字节:主分区第一个扇区的物理位置(柱面、磁头、扇区号等等)。
(3). 第 5 个字节:主分区类型。
(4). 第 6-8 个字节:主分区最后一个扇区的物理位置。
(5). 第 9-12 字节:该主分区第一个扇区的逻辑地址。
(6). 第 13-16 字节:主分区的扇区总数。
最后的四个字节 "主分区的扇区总数",决定了这个主分区的长度。也就是说,一个主分区的扇区总数最多不超过 2 的 32 次方。如果每个扇区为 512 个字节,就意味着单个分区最大不超过 2TB。
暂且先不讨论 grub 如何管理启动操作系统的,以 VBR 和 EBR 引导操作系统为例。
当 bios 读取到 MBR 中的 boot loader 后,会继续读取分区表。分两种情况:
(1) 如果查找分区表时发现某个主分区表的第一个字节是 0x80,也就是激活的分区,那么说明操作系统装在了该主分区,然后执行已载入的 MBR 中的 boot loader 代码,加载该激活主分区的 VBR 中的 boot loader,至此,控制权就交给了 VBR 的 boot loader 了;
(2) 如果操作系统不是装在主分区,那么肯定是装在逻辑分区中,所以查找完主分区表后会继续查找扩展分区表,直到找到 EBR 所在的分区,然后 MBR 中的 boot loader 将控制权交给该 EBR 的 boot loader。
也就是说,如果一块硬盘上装了多个操作系统,那么 boot loader 会分布在多个地方,可能是 VBR,也可能是 EBR,但 MBR 是一定有的,这是被 bios 给 "绑定" 了的。在装 LINUX 操作系统时,其中有一个步骤就是询问你 MBR 装在哪里的,但这个 MBR 并非一定真的是 MBR,可能是 MBR,也可能是 VBR,还可能是 EBR,并且想要单磁盘多系统共存,则 MBR 一定不能被覆盖 (此处不考虑 grub)。
如下图,是我测试单磁盘装 3 个操作系统时的分区结构。其中 / dev/sda{1,2,3} 是第一个 CentOS 6 系统,/dev/sda{5,6,7} 是第二个 CentOS 7 系统,/dev/sda{8,9,10} 是第三个 CentOS 6 系统,每一个操作系统的分区序号从前向后都是 / boot 分区、根分区、swap 分区。
再看下图,是装第三个操作系统时的询问 boot loader 安装位置的步骤。
装第一个操作系统时,boot loader 可以装在 / dev/sda 上,也可以选择装在 / dev/sda1 上,这时装的是 MBR 和 VBR,任选一个都会将另一个也装上,从第二个操作系统开始,装的是 EBR 而非 MBR,且应该指定 boot loader 位置 (如 / dev/sda5 和 / dev/sda8),否则默认选项是装在 / dev/sda 上,但这会覆盖原有的 MBR。
另外,在指定 boot loader 安装路径的下方,还有一个方框是操作系统列表,这就是操作系统菜单,其中可以指定默认的操作系统,这里的默认指的是 MBR 默认跳转到哪个 VBR 或 EBR 上。
所以,MBR/VBR 和 EBR 之间的跳转关系如下图。
使用这种方式的菜单管理操作系统启动,无需什么 stage1,stage1.5 和 stage2 的概念,只要跳转到了分区上的 VBR 或 EBR,那么直接就可以加载引导该分区上的操作系统。
但是,这种管理操作系统启动的菜单已经没有意义了,现在都是使用 grub 来管理,所以装第二个操作系统或第 n 个操作系统时不手动指定 boot loader 安装位置,覆盖掉 MBR 也无所谓,想要实现单磁盘多系统共存所需要做的,仅仅只是修改 grub 的配置文件而已。
使用 grub 管理引导菜单时,VBR/EBR 就毫无用处了,具体的见下文。
使用 grub 管理启动,则 MBR 中的 boot loader 是由 grub 程序安装的,此外还会安装其他的 boot loader。CentOS 6 使用的是传统的 grub,而 CentOS 7 使用的是 grub2。
如果使用的是传统的 grub,则安装的 boot loader 为 stage1、stage1_5 和 stage2,如果使用的是 grub2,则安装的是 boot.img 和 core.img。传统 grub 和 grub2 的区别还是挺大的,所以下面分开解释,如果对于 grub 有不理解之处,见我的另一篇文章 grub2 详解。
grub2 程序安装 grub 后,会在 / boot/grub2/i386-pc / 目录下生成 boot.img 和 core.img 文件,另外还有一些模块文件,其中包括文件系统类的模块。
- [root@xuexi ~]#find/boot/grub2/i386-pc/ -name'*.img'-o -name"*fs.mod"-o -name"*ext[0-9].mod"/boot/grub2/i386-pc/affs.mod
- /boot/grub2/i386-pc/afs.mod
- /boot/grub2/i386-pc/bfs.mod
- /boot/grub2/i386-pc/btrfs.mod
- /boot/grub2/i386-pc/cbfs.mod
- /boot/grub2/i386-pc/ext2.mod # ext2、ext3和ext4都使用该模块
- /boot/grub2/i386-pc/hfs.mod
- /boot/grub2/i386-pc/jfs.mod
- /boot/grub2/i386-pc/ntfs.mod
- /boot/grub2/i386-pc/procfs.mod
- /boot/grub2/i386-pc/reiserfs.mod
- /boot/grub2/i386-pc/romfs.mod
- /boot/grub2/i386-pc/sfs.mod
- /boot/grub2/i386-pc/xfs.mod
- /boot/grub2/i386-pc/zfs.mod
- /boot/grub2/i386-pc/core.img
- /boot/grub2/i386-pc/boot.img
其中 boot.img 就是安装在 MBR 中的 boot loader。当然,它们的内容是不一样的,安装 boot loader 时,grub2-install 会将 boot.img 转换为合适的代码写入 MBR 中的 boot loader 部分。
core.img 是第二段 Boot loader 段,grub2-install 会将 core.img 转换为合适的代码写入到紧跟在 MBR 后面的空间,这段空间是 MBR 之后、第一个分区之前的空闲空间,被称为 MBR gap,这段空间最小 31KB,但一般都会是 1MB 左右。
实际上,core.img 是多个 img 文件的结合体。它们的关系如下图:
这张图解释了开机过程中 grub2 阶段的所有过程,boot.img 段的 boot loader 只有一个作用,就是跳转到 core.img 对应的 boot loader 的第一个扇区,对于从硬盘启动的系统来说,该扇区是 diskboot.img 的内容,diskboot.img 的作用是加载 core.img 中剩余的内容。
由于 diskboot.img 所在的位置是以硬编码的方式写入到 boot.img 中的,所以 boot.img 总能找到 core.img 中 diskboot.img 的位置并跳转到它身上,随后控制权交给 diskboot.img。随后 diskboot.img 加载压缩后的 kernel.img(注意,是 grub 的 kernel 不是操作系统的 kernel) 以初始化 grub 运行时的各种环境,控制权交给 kernel.img。
但直到目前为止,core.img 都还不识别 / boot 所在分区的文件系统,所以 kernel.img 初始化 grub 环境的过程就包括了加载模块,严格地说不是加载,因为在安装 grub 时,文件系统类的模块已经嵌入到了 core.img 中,例如 ext 类的文件系统模块 ext2.mod。
加载了模块后,kernel.img 就能识别 / boot 分区的文件系统,也就能找到 grub 的配置文件 / boot/grub2/grub.cfg,有了 grub.cfg 就能显示启动菜单,我们就能自由的选择要启动的操作系统。
当选择某个菜单项后,kernel.img 会根据 grub.cfg 中的配置加载对应的操作系统内核 (/boot 目录下 vmlinuz 开头的文件),并向操作系统内核传递启动时参数,包括根文件系统所在的分区,init ramdisk(即 initrd 或 initramfs) 的路径。例如下面是某个菜单项的配置:
- menuentry'CentOS 6'--unrestricted {
- search --no-floppy --fs-uuid --set=root f5d8939c-4a04-4f47-a1bc-1b8cbabc4d32
- linux16 /vmlinuz-2.6.32-504.el6.x86_64 root=UUID=edb1bf15-9590-4195-aa11-6dac45c7f6f3 ro quiet
- initrd16 /initramfs-2.6.32-504.el6.x86_64.img
- }
加载完操作系统内核后 grub2 就将控制权交给操作系统内核。
总结下,从 MBR 开始后的过程是这样的:
1. 执行 MBR 中的 boot loader(即 boot.img) 跳转到 diskboot.img。
2. 执行 diskboot.img,加载 core.img 剩余的部分,并跳转到 kernel.img。
3.kernel.img 读取 / boot/grub2/grub2.cfg,并显示启动管理菜单。
4. 选中某菜单后,kernel.img 加载该菜单项配置的操作系统内核 / boot/vmlinux-XXX,并传递内核启动参数,包括根文件系统所在分区和 init ramdisk 的路径。
5. 控制权交给操作系统内核。
传统 grub 对应的 boot loader 是 stage1 和 stage2,从 stage1 跳转到 stage2 大多数情况下还会用到 stage1_5 对应的 boot loader。
与 grub2 相比,stage1 和 boot.img 的作用是类似的,都在 MBR 中。当该段 boot loader 执行后,它的目的是跳转到 stage1_5 的第一个扇区上,然后由该扇区的代码加载剩余的内容,并跳转到 stage2 的第一个扇区上。
stage1_5 存在的理由是因为 stage2 功能较多,导致其文件体积较大 (一般至少都有 100 多 K),所以并没有像 core.img 一样嵌入到磁盘上,而是简单地将其放在了 boot 分区上,但 stage1 并不识别 boot 分区的文件系统类型,所以借助中间的辅助 boot loader 即 stage1_5 来跳转。
stage1_5 的目的之一是识别文件系统,但文件系统的类型有很多,所以对应的 stage1_5 也有很多种。
- [root@xuexi~]#ls - C / boot / grub
- /*stage1_5*
- /boot/grub/e2fs_stage1_5 /boot/grub/jfs_stage1_5 /boot/grub/vstafs_stage1_5
- /boot/grub/fat_stage1_5 /boot/grub/minix_stage1_5 /boot/grub/xfs_stage1_5
- /boot/grub/ffs_stage1_5 /boot/grub/reiserfs_stage1_5
- /boot/grub/iso9660_stage1_5 /boot/grub/ufs2_stage1_5*/
虽然有很多种 stage1_5,但每个 boot 分区也只能对应一种 stage1_5。这个 stage1_5 对应的 boot loader 一般会被嵌入到 MBR 后、第一个分区前的中间那段空间 (即 MBR gap)。
当执行了 stage1_5 对应的 boot loader 后,stage1_5 就能识别出 boot 所在的分区,并找到 stage2 文件的第一个扇区,然后跳转过去。
当控制权交给了 stage2,stage2 就能加载 grub 的配置文件 / boot/grub/grub.conf 并显示菜单并初始化 grub 的运行时环境,当选中操作系统后,stage2 将和 kernel.img 一样加载操作系统内核,传递内核启动参数,并将控制权交给操作系统内核。
所以,stage1、stage1_5 和 stage2 之间的关系如下图:
虽然绝大多数都提供了 stage1_5,但它不是必须的,它的作用仅仅只是识别 boot 分区的文件系统类型,对于一个会编程的人来说,可以将固定 boot 分区的文件系统识别代码嵌入到 stage1 中,这样 stage1 自身就能识别 boot 分区,就不需要 stage1_5 了。
看看安装 grub 时,grub 到底做了些什么工作。
- grub> setup (hd0)
- Checking if "/boot/grub/stage1" exists... yes
- Checking if "/boot/grub/stage2" exists... yes
- Checking if "/boot/grub/e2fs_stage1_5" exists... yes
- Running "embed /boot/grub/e2fs_stage1_5 (hd0)"...15 sectors are embedded.
- succeeded
- Running "install /boot/grub/stage1 (hd0) (hd0)1+15 p (hd0,0)/boot/grub/stage2 /boot/grub/menu.lst"... succeeded
- Done.
首先检测各 stage 文件是否存在于 / boot/grub 目录下,随后嵌入 stage1_5 到磁盘上,该文件系统类型的 stage1_5 占用了 15 个扇区,最后安装 stage1,并告知 stage1 stage1_5 的位置是第 1 到第 15 个扇区,之所以先嵌入 stage1_5 再嵌入 stage1 就是为了让 stage1 知道 stage1_5 的位置,最后还告知了 stage1 stage2 和配置文件 menu.lst(它是 grub.conf 的软链接) 的路径。
提前说明,下文所述均为 sysV init 系统启动风格,systemd 的启动管理方式大不相同,所以不要将 systemd 管理的启动方式与此做比较。
到目前为止,内核已经被加载到内存掌握了控制权,且收到了 boot loader 最后传递的内核启动参数以及 init ramdisk 的路径。
所有的内核都是以 bzImage 方式压缩过的,压缩后 CentOS 6 的内核大小大约为 4M,CentOS 7 的内核大小大约为 5M。内核要能正常运作下去,它需要进行解压释放。
解压释放之后,将创建 pid 为 0 的 idle 进程,该进程非常重要,后续内核所有的进程都是通过 fork 它创建的,且很多 cpu 降温工具就是强制执行 idle 进程来实现的。
然后创建 pid=1 和 pid=2 的内核进程。pid=1 的进程也就是 init 进程,pid=2 的进程是 kthread 内核线程,它的作用是在真正调用 init 程序之前完成内核环境初始化和设置工作,例如根据 grub 传递的内核启动参数找到 init ramdisk 并加载。
在前面,已经创建了 pid=1 的 init 进程和 pid=2 的 kthread 进程,但注意,它们都是内核线程,全称是 kernel_init 和 kernel_kthread,而真正能被 ps 捕获到的 pid=1 的 init 进程是由 kernel_init 调用 init 程序后形成的。
要加载 / sbin/init 程序,首先要找到根分区,根分区是有文件系统的,所以内核需要先识别文件系统并加载文件系统的驱动,但文件系统的驱动又是放在根分区的,这就出现了先有鸡还是先有蛋的矛盾。
解决的方法之一是像 grub2 识别 boot 分区的文件系统一样,将根文件系统驱动模块嵌入到内核中,但文件系统的种类太多,而且会升级,这样就导致内核不断的嵌入新的文件系统驱动模块,内核不断增大,这显然是不合适的。
解决方法之二则像传统 grub 借助中间过渡引导段 stage1_5 一样,将根文件系统的驱动模块放入一个中间过渡文件,在加载根文件系统之前先加载这个过渡文件,再由过渡文件跳转到根文件系统。
方法二正是现在采用的,其采用的中间过渡文件称为 init ramdisk,它是在安装完操作系统时生成的,这样它会收集到当前操作系统的根文件系统是什么类型的文件系统,也就能只嵌入一个对应的文件系统驱动模块使其变得足够小。如下图,它是安装操作系统时安装完所有软件包后执行的一个收集过程。
在 CentOS 5 上采用的 init ramdisk 称为 initrd,而 CentOS 6 和 CentOS 7 采用的则是 initramfs,它们的目的是一样的,但在实现上却大有不同。但它们都存放在 / boot 目录下。
- [root@xuexi ~]# ll -h /boot/init*
- -rw-------.1root root 19M Feb25 11:53/boot/initramfs-2.6.32-504.el6.x86_64.img
可以看到,它们的大小有十多兆,由此也可知道 init ramdisk 的作用肯定不仅仅只是找到根文件系统,它还会做其他工作。具体还做什么工作,请继续阅读下文。
initrd 其实是一个镜像文件系统,是在内存中划分一片区域模拟磁盘分区,在该文件中包含了找到根文件系统的脚本和驱动。
既然是文件系统,那么内核也必须要带有对应文件系统的驱动,另外文件系统要使用就必须有根 "/",这个根是内存中的 "虚根"。由于内核加载到这里已经初始化一些运行环境了,所以内核的运行状态等参数也要保存下来,保存的位置就是内存中虚根下的 / proc 和 / sys,此外还有收集到的硬件设备信息以及设备的运行环境也要保存下来,保存的位置是 / dev。到此为止,pid=2 的内核线程 kernel_kthread 就完成了基本工作,开始转到 kernel_init 进程上了。
再之后就是 kernel_init 挂载真正的根文件系统并从虚根切换到实根,最后 kernel_init 将调用 init 程序,也就是真正的能被我们看见的 pid=1 的 init 进程,然后将控制权交给 init,所以从现在开始,将切换到用户空间,后续剩余的事情都将由用户空间的程序完成。
以下是 CentOS 5.8 中 initrd 文件的解压过程和捷报后的目录结构。
- [root@localhost ~]#cp/boot/initrd-2.6.18-308.el5.img /tmp/initrd.gz
- [root@localhost tmp]# gunzip initrd.gz
- [root@localhost tmp]# cpio -id< initrd
- [root@localhost tmp]# ls
- bin dev etc init initrd lib proc sbin sys sysroot
initramfs 比 initrd 先进了一些,initrd 必须是一个文件系统,是在内存中模拟出磁盘分区的,所以内核必须要带有它的文件系统驱动,而 initramfs 则仅仅只是一个镜像压缩文件而非文件系统,所以它不需要带文件系统驱动,在加载时,内核会将其解压的内容装入到一个 tmpfs 中。
initramfs 和 initrd 最大的区别在于 init 进程的区别对待。initramfs 为了尽早进入用户空间,它将 init 程序集成到了 initramfs 镜像文件中,这样就可以在 initramfs 装入 tmpfs 时直接运行 init 进程,而不用去找根文件系统下的 / sbin/init,由此挂载根文件系统的工作将由 init 来完成,而不再是内核线程 kernel_init 完成。最后从虚根切换到实根。
那根分区下的 / sbin/init 是干嘛的呢?可以认为是 init ramdisk 中 init 的一个备份,如果 ramdisk 中找不到 init 就会去找 / sbin/init。另外,在正常运行的操作系统环境下,/sbin/init 还经常用来完成其他工作,如发送信号。
其实 initramfs 完成了很多工作,解开它的镜像文件就能发现它的目录结构和真实环境下的目录结构类似。以下是 CentOS 7 上 initramfs-3.10.0-327.el7.x86_64 解包过程和解包后的目录结构。
- [root@xuexi ~]#cp/boot/initramfs-3.10.0-327.el7.x86_64.img /tmp/initramfs.gz
- [root@xuexi ~]# cd /tmp;gunzip/tmp/initramfs.gz
- [root@xuexi tmp]# cpio -id< initramfs
- [root@xuexi tmp]# ls-l
- total 8
- lrwxrwxrwx 1root root7Jun29 23:28bin -> usr/bin
- drwxr-xr-x2root root42Jun29 23:28 dev
- drwxr-xr-x11root root4096Jun29 23:28 etc
- lrwxrwxrwx 1root root23Jun29 23:28init -> usr/lib/systemd/systemd
- lrwxrwxrwx 1root root7Jun29 23:28lib -> usr/lib
- lrwxrwxrwx 1root root9Jun29 23:28lib64 -> usr/lib64
- drwxr-xr-x2root root6Jun29 23:28 proc
- drwxr-xr-x2root root6Jun29 23:28 root
- drwxr-xr-x2root root6Jun29 23:28 run
- lrwxrwxrwx 1root root8Jun29 23:28sbin -> usr/sbin
- -rwxr-xr-x1root root3041Jun29 23:28 shutdown
- drwxr-xr-x2root root6Jun29 23:28 sys
- drwxr-xr-x2root root6Jun29 23:28 sysroot
- drwxr-xr-x2root root6Jun29 23:28 tmp
- drwxr-xr-x7root root61Jun29 23:28 usr
- drwxr-xr-x2root root27Jun29 23:28var
另外,还可以在其 sbin 目录下发现 init 程序。
- [root@xuexi tmp]# ll sbin/init
- lrwxrwxrwx 1root root22Jun29 23:28sbin/init -> ../lib/systemd/systemd
下文解释的是 sysV 风格的系统环境,与 systemd 初始化大不相同。
当 init 进程掌握控制权后,意味着已经进入了用户空间,后续的事情也将以用户空间为主导来完成。
init 的名称是 initialize 的缩写,是初始化的意思,所以它的作用也就是初始化的作用。在内核加载阶段,也有初始化动作,初始化的环境是内核的环境,是由 kernel_init、kernel_thread 等内核线程完成的。而 init 掌握控制权后,已经可以和用户空间交互,意味着真正的开始进入操作系统,所以它初始化的是操作系统的环境。
操作系统初始化涉及了不少过程,大致如下:读取运行级别;初始化系统类的环境;根据运行级别初始化用户类的环境;执行 rc.local 文件完成用户自定义开机要执行的命令;加载终端;
在 sysV 风格的系统下,使用了运行级别的概念,不同运行级别初始化不同的系统类环境,你可以认为 windows 的安全模式也是使用运行级别的一种产物。
在 Linux 系统中定义了 7 个运行级别,使用 0-6 的数字表示。
0:halt,即关机
1:单用户模式
2:不带 NFS 的多用户模式
3:完整多用户模式
4:保留未使用的级别
5:X11,即图形界面模式
6:reboot,即重启
实际上,执行关机或重启命令的本质就是向 init 进程传递 0 或 6 这两个运行级别。
sysV 的 init 程序读取 / etc/inittab 文件来获取默认的运行级别,并根据此文件所指定的配置执行默认运行级别对应的操作。注意,systemd 管理的系统是没有 / etc/inittab 文件的,即使有也仅仅只是出于提醒的目的,因为 systemd 没有了运行级别的概念,说实话,systemd 管的真的太多了。
CentOS 6.6 上该文件内容如下:
- [root@xuexi ~]#cat/etc/inittab
- # inittab is only used by upstart for the default runlevel.
- #
- # ADDING OTHER CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM.
- #
- # System initialization is started by /etc/init/rcS.conf
- #
- # Individual runlevels are started by /etc/init/rc.conf
- #
- # Ctrl-Alt-Delete is handled by /etc/init/control-alt-delete.conf
- #
- # Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf,
- # with configuration in /etc/sysconfig/init.
- #
- # For information on how to write upstart event handlers, or how
- # upstart works, see init(5), init(8), and initctl(8).
- #
- # Default runlevel. The runlevels used are:
- # 0- halt (Do NOT set initdefault to this)
- # 1- Single user mode
- # 2- Multiuser, without NFS (The same as3,ifyoudo not have networking)
- # 3- Full multiuser mode
- # 4- unused
- # 5- X11
- # 6- reboot (Do NOT set initdefault to this)
- #
- id:3:initdefault:
该文件告诉我们,系统初始化过程由 / etc/init/rcS.conf 完成,运行级别类的初始化过程由 / etc/init.conf 来完成,按下 CTRL+ALT+DEL 键要执行的过程由 / etc/init/control-alt-delete.conf 来完成,终端加载的过程由 / etc/init/tty.conf 和 / etc/init/serial.conf 读取配置文件 / etc/sysconfig/init 来完成。再文件最后,还有一行 "id:3:initdefault",表示默认的运行级别为 3,即完整的多用户模式。
确认了要进入的运行级别后,init 将先读取 / etc/init/rcS.conf 来完成系统环境类初始化动作,再读取 / etc/init/rc.conf 来完成运行级别类动作。
先看看 / etc/init/rcS.conf 文件的内容。
- [root@xuexi ~]#cat/etc/init/rcS.conf
- # rcS - runlevel compatibility
- #
- # This task runs the old sysv-rc startup scripts.
- #
- # Do not edit this file directly. If you want to change the behaviour,
- # please create a file rcS.override and put your changes there.
- start on startup
- stop on runlevel
- task
- # Note: there can be no previous runlevel here, ifwe have one it's bad# information (we enter rc1 not rcSformaintenance). Run /etc/rc.d/rc
- # without information so that it defaults to previous=N runlevel=S.
- console output
- pre-start script
- fortin$(cat/proc/cmdline);do
- case$tin
- emergency)
- start rcS-emergency
- break
- ;;
- esac
- done
- end script
- exec /etc/rc.d/rc.sysinit
- post-stop script
- if["$UPSTART_EVENTS"="startup"];then
- [ -f /etc/inittab ] && runlevel=$(/bin/awk-F':' '$3 == "initdefault" && $1 !~ "^#" { print $2 }'/etc/inittab)
- [ -z"$runlevel"] && runlevel="3"
- fortin$(cat/proc/cmdline);do
- case$tin-s|single|S|s) runlevel="S" ;;
- [1-9]) runlevel="$t" ;;
- esac
- done
- exec telinit $runlevel
- fi
- end script
其中 "exec /etc/rc.d/rc.sysinit" 这一行就表示要执行 / etc/rc.d/rc.sysinit 文件,该文件定义了系统初始化 (system initialization) 的内容,包括:
(1). 确认主机名。
(2). 挂载 / proc 和 / sys 等特殊文件系统,使得内核参数和状态可与人进行交互。是否还记得在内核加载阶段时的 / proc 和 / sys?
(3). 启动 udev,也就是启动类似 windows 中的设备管理器。
(4) 初始化硬件参数,如加载某些驱动,设置时钟等。
(5). 设置主机名。
(6). 执行 fsck 检测磁盘是否健康。
(7). 挂载 / etc/fstab 中除 / proc 和 NFS 的文件系统。
(8). 激活 swap。
(9). 将所有执行的操作写入到 / var/log/dmesg 文件中。
执行完系统初始化后,接下来就是执行运行级别的初始化。先看看 / etc/init/rc.conf 的内容。
- [root@xuexi ~]#cat/etc/init/rc.conf
- # rc - System V runlevel compatibility
- #
- # This task runs the old sysv-rc runlevel scripts. It
- # is usually started by the telinit compatibility wrapper.
- #
- # Do not edit this file directly. If you want to change the behaviour,
- # please create a file rc.override and put your changes there.
- start on runlevel [0123456]
- stop on runlevel [!$RUNLEVEL]
- task
- export RUNLEVEL
- console output
- exec /etc/rc.d/rc $RUNLEVEL
最后一行 "exec /etc/rc.d/rc $RUNLEVEL" 说明调用 / etc/rc.d/rc 这个脚本来初始化指定运行级别的环境。Linux 采用了将各运行级别初始化内容分开管理的方式,将 0-6 这 7 个运行级别要执行的初始化脚本分别放入 rc[0-6].d 这 7 个目录中。
- [root@xuexi ~]#ls-l /etc/rc.d/
- total 60
- drwxr-xr-x.2root root4096Jun11 02:42 init.d
- -rwxr-xr-x.1root root2617Oct16 2014 rc
- drwxr-xr-x.2root root4096Jun11 02:42 rc0.d
- drwxr-xr-x.2root root4096Jun11 02:42 rc1.d
- drwxr-xr-x.2root root4096Jun11 02:42 rc2.d
- drwxr-xr-x.2root root4096Jun11 02:42 rc3.d
- drwxr-xr-x.2root root4096Jun11 02:42 rc4.d
- drwxr-xr-x.2root root4096Jun11 02:42 rc5.d
- drwxr-xr-x.2root root4096Jun11 02:42 rc6.d
- -rwxr-xr-x.1root root220Oct16 2014 rc.local
- -rwxr-xr-x.1root root19914Oct16 2014rc.sysinit
实际上 / etc/init.d / 下的脚本才是真正的脚本,放入 rcN.d 目录中的文件只不过是 / etc/init.d / 目录下脚本的软链接。注意,/etc/init.d 是 Linux 耍的一个小把戏,它是 / etc/rc.d/init.d 的一个符号链接,在有些类 unix 系统中是没有 / etc/init.d 的,都是直接使用 / etc/rc.d/init.d。
以 / etc/rc.d/rc3.d 为例。
- [root@xuexi ~]# ll /etc/rc.d/rc3.d/ |head
- total 0
- lrwxrwxrwx. 1root root16Feb25 11:52K01smartd -> ../init.d/smartd
- lrwxrwxrwx. 1root root16Feb25 11:52K10psacct -> ../init.d/psacct
- lrwxrwxrwx. 1root root19Feb25 11:51K10saslauthd -> ../init.d/saslauthd
- lrwxrwxrwx 1root root22Jun10 08:59K15htcacheclean -> ../init.d/htcacheclean
- lrwxrwxrwx 1root root15Jun10 08:59K15httpd -> ../init.d/httpd
- lrwxrwxrwx 1root root15Jun11 02:42K15nginx -> ../init.d/nginx
- lrwxrwxrwx. 1root root18Feb25 11:52K15svnserve -> ../init.d/svnserve
- lrwxrwxrwx. 1root root20Feb25 11:51K50netconsole -> ../init.d/netconsole
- lrwxrwxrwx 1root root17Jun10 00:50K73winbind -> ../init.d/winbind
可见,rcN.d 中的文件都以 K 或 S 加一个数字开头,其后才是脚本名称,且它们都是 / etc/rc.d/init.d 中文件的链接。S 开头表示进入该运行级别时要运行的程序,S 字母后的数值表示启动顺序,数字越大,启动的越晚;K 开头的表示退出该运行级别时要杀掉的程序,数值表示关闭的顺序。
所有这些文件都是由 / etc/rc.d/rc 这个程序调用的,K 开头的则传给 rc 一个 stop 参数,S 开头的则传给 rc 一个 start 参数。
打开 rc0.d 和 rc6.d 这两个目录,你会发现在这两个目录中除了 "S00killall" 和 "S01reboot",其余都是 K 开头的文件。
而在 rc[2-5].d 这几个目录中,都有一个 S99local 文件,且它们都是指向 / etc/rc.d/rc.local 的软链接。S99 表示最后启动的一个程序,所以 rc.local 中的程序是 2345 这 4 个运行级别初始化过程中最后运行的一个脚本。这是 Linux 提供给我们定义自己想要在开机时 (严格地说是进入运行级别) 就执行的命令的文件。
当初始化完运行级别环境后,将要准备登录系统了。
Linux 是多任务多用户的操作系统,它允许多人同时在线工作。但每个人都必须要输入用户名和密码才能验证身份并最终登录。但登陆时是以图形界面的方式给用户使用,还是以纯命令行模式给用户使用呢?这是终端决定的,也就是说在登录前需要先加载终端。至于什么是终端,见我的另一篇文章 Linux 终端类型。
在 Linux 上,每次开机都必然会开启所有支持的虚拟终端,如下图。
这些虚拟终端是由 getty 命令 (get tty) 来完成的,getty 命令有很多变种,有 mingetty、agetty、rungettty 等,在 CentOS 5 和 CentOS 6 都使用 mingetty,在 CentOS 7 上使用 agetty。getty 命令的作用之一是调用登录程序 / bin/login。
例如,在 CentOS 6 下,捕获 tty 终端情况。
- [root@xuexi ~]#ps-elf |grep tt[y]
- 4S root1412 1 0 80 0-1016n_tty_ Jun21 tty200:00:00/sbin/mingetty /dev/tty2
- 4S root1414 1 0 80 0-1016n_tty_ Jun21 tty300:00:00/sbin/mingetty /dev/tty3
- 4S root1417 1 0 80 0-1016n_tty_ Jun21 tty400:00:00/sbin/mingetty /dev/tty4
- 4S root1419 1 0 80 0-1016n_tty_ Jun21 tty500:00:00/sbin/mingetty /dev/tty5
- 4S root1421 1 0 80 0-1016n_tty_ Jun21 tty600:00:00/sbin/mingetty /dev/tty6
- 4S root1492 1410 0 80 0-27118n_tty_ Jun21 tty100:00:00-bash
在 CentOS 7 下,捕获 tty 终端情况。
- [root@xuexi tmp]#ps-elf |grep tt[y]
- 4S root8258 1 0 80 0-27507n_tty_04:17tty200:00:00/sbin/agetty --noclear tty2 linux
- 4S root8259 1 0 80 0-27507n_tty_04:17tty300:00:00/sbin/agetty --noclear tty3 linux
- 4S root8260 1 0 80 0-27507n_tty_04:17tty400:00:00/sbin/agetty --noclear tty4 linux
- 4S root8262 915 0 80 0-29109n_tty_04:17tty100:00:00-bash
- 4S root8307 8305 0 80 0-29109n_tty_04:17tty500:00:00-bash
- 4S root8348 8346 0 80 0-29136n_tty_04:17tty600:00:00-bash
细心一点会发现,有的 tty 终端仍然以 / sbin/mingetty 进程或 / sbin/agetty 进程显示,有些却以 bash 进程显示。这是因为 getty 进程在调用 / bin/login 后,如果输入用户名和密码成功登录了某个虚拟终端,那么 gettty 程序会融合到 bash(假设 bash 是默认的 shell) 进程,这样 getty 进程就不会再显示了。
虽然 getty 不显示了,但并不代表它消失了,它仍以特殊的方式存在着。是否还记得 / etc/inittab 文件?此文件中提示了终端加载的过程由 / etc/init/tty.conf 读取配置文件 / etc/sysconfig/init 来完成。
- [root@xuexi ~]#greptty -A1/etc/inittab
- # Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf,
- # with configuration in/etc/sysconfig/init.
那么就看看 / etc/init/tty.conf 文件。
- [root@xuexi ~]#cat/etc/init/tty.conf
- # tty - getty
- #
- # This service maintains a getty on the specified device.
- #
- # Do not edit this file directly. If you want to change the behaviour,
- # please create a file tty.override and put your changes there.
- stop on runlevel [S016]
- respawn
- instance $TTY
- exec /sbin/mingetty $TTY
- usage 'tty TTY=/dev/ttyX - where X is console id'
此文件中的 respawn 表示进程由 init 进程监视,一发现被杀掉了 init 会立即重启它。所以,只要 getty 进程一结束,init 会立即监视到而重启该进程。因此,用户登录成功后 getty 只是融合到了 bash 进程中,并非退出,否则 init 会立即重启它,而它会调用 login 程序让你再次输入用户和密码。
再看看 / etc/sysconfig/init 文件。
- [root@xuexi ~]#cat/etc/sysconfig/init
- # color => new RH6.0 bootup
- # verbose => old-style bootup
- # anything else=> new style bootup without ANSI colors or positioning
- BOOTUP=color
- # column to start "[ OK ]"labelin
- RES_COL=60
- # terminal sequence to move to that column. You could change this
- # to something like "tput hpa ${RES_COL}" if your terminal supports it
- MOVE_TO_COL="echo -en \\033[${RES_COL}G"
- # terminal sequence to set color to a 'success' color (currently: green)
- SETCOLOR_SUCCESS="echo -en \\033[0;32m"
- # terminal sequence to set color to a 'failure' color (currently: red)
- SETCOLOR_FAILURE="echo -en \\033[0;31m"
- # terminal sequence to set color to a 'warning' color (currently: yellow)
- SETCOLOR_WARNING="echo -en \\033[0;33m"
- # terminal sequence to reset to the default color.
- SETCOLOR_NORMAL="echo -en \\033[0;39m"
- # Set to anything other than 'no' to allow hotkey interactive startup...
- PROMPT=yes
- # Set to 'yes'to allow probingfor devices with swap signatures
- AUTOSWAP=no
- # What ttys should gettys be started on?
- ACTIVE_CONSOLES=/dev/tty[1-6]
- # Set to '/sbin/sulogin' to prompt for password on single-user mode
- # Set to '/sbin/sushell' otherwise
- SINGLE=/sbin/sushell
其中 ACTIVE_CONSOLES 指令决定了要开启哪些虚拟终端。SINGLE 决定了在单用户模式下要调用哪个 login 程序和哪个 shell。
如果不在虚拟终端登录,而是通过为 ssh 分配的伪终端登录,那么到创建完 getty 进程那一步其实开机流程已经完成了。但不管在哪种终端下登录,登录过程也可以算作开机流程的一部分,所以也简单说明下。
getty 进程启用虚拟终端后将调用 login 进程提示用户输入用户名或密码 (或伪终端的连接程序如 ssh 提示输入用户名和密码),当用户输入完成后,将验证输入的用户名是否合法,密码是否正确,用户名是否是明确被禁止登陆的,PAM 模块对此用户的限制是如何的等等,还要将登录过程记录到各个日志文件中。如果登录成功,将加载该用户的 bash,加载 bash 过程需要读取各种配置文件,初始化各种环境等等。但不管怎么说,只要登录成功就表示开机流程全部完成了。
返回系列文章大纲:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
来源: http://www.cnblogs.com/f-ck-need-u/p/7100336.html