CentOS5和6的启动流程

Linux(CentOS5&6)的系统启动流程

CentOS 5和6 的启动流程

前菜

抛开底层的硬件不谈, 我们知道LInux最核心的两个部分就是**kernelrootfs**了

从内核的角度上说, 内核主要进行的工作有: 进程管理, 内存管理, 网络管理, 驱动程序, 文件系统, 安全功能

对程序员而说, 不会直接选择进行系统调用而是对已经对系统调用进行了一层封装的库进行调用, 在Linux下这个标准库就是glibc 而这些库文件都放在rootfs里面.

那么到底什么是库呢? 说白了就是一堆函数的集合, 而函数其实又只是一堆代码片段, 一个功能的实现,

库文件也是可以执行的, 只不过他自己没有入口, 只能依靠别的程序来调用他.

对于调用, 我们分为两种调用: 过程调用函数调用.

过程调用就是没有返回值的调用 而函数调用一定是有返回值的.

库之上就是我们真正生产能力的应用程序了.

作为整个系统的核心, 内核有两种设计流派: 单内核设计, 微内核设计

简单的说, 微内核就好像是联邦制, 每一个功能都有一个小系统,这样造成的结果就是效率略低, 但是却能在某些功能模块出现异常的情况下, 不会使得整个内核出现致命性的错误. 而单内核即使将所有的功能集成到同一个程序.

这两种的典型例子: 单内核: Linux 微内核: Windows, Solaris.

然而, Linux的内核特点: 支持模块化. *.ko ( 之前说过共享连接库的文件扩展名是*.so(shared), 而这里的k就是(kernel)) 也就是说, 你可以在线进行装载和卸载.

进入/boot下, 会发现一些和你开机引导菜单同名的几个文件, 这些文件就是Linux的压缩系统映像, 版本号的格式和我们之前说过的软件包的命名是一样的.

至于模块的位置都在/lib/modules下 , 同样, 系统的模块还是以版本号来进行的命名. (e.g : 在/boot下有一个叫vmlinuz-3.10.0-514.21.2.el7.x86_64的系统映像, 那么他的模块就在3.10.0-514.21.2.el7.x86_64这个目录下)

现在进入模块的目录里面来看一下:

之前说过的*.ko文件都在模块目录下的kernel文件夹内, 这个位置也是真正的Linux内核模块所在.由于内核之间同样存在依赖关系, 所以也会有一些元信息文件.

那么这些文件一共有多大呢?:

1
2
[root@WWW ~]$ du -sh /lib/modules/3.10.0-514.21.2.el7.x86_64/
127M /lib/modules/3.10.0-514.21.2.el7.x86_64/

是不是大小超乎你的意料, 事实上这个大小还是可以继续缩减的.

不知道大家有没有想象到这样的情况: 比如说在系统启动的时候我们要先加载内核进系统, 接着等到内核加载完就要开始加载根文件系统了, 这些东西都在哪里呢? 自然是在我们的硬盘上, 那么为了读取硬盘就需要硬盘驱动, 但是由于Linux是模块化的内核所以有可能这个驱动正好是被做了模块处理, 那么为了获得读取硬盘的能力, 就要先加载模块, 但是模块在哪呢? 在/usr/lib/modules下, 也是根文件系统, 这样就会出现矛盾.对于这种情况, 我们就需要一种外部的辅助手段: 把硬盘驱动先装在内核上, 让内核识别.

这样就会有两种解决方法:

  • 直接把硬盘驱动编译进内核, 但是这样的问题显然易见, 不可能将所有的硬盘驱动都扔到内核里.
  • 加一个用来辅助的微型的文件系统, 仅包含内核需要的一些必要的驱动. 这么一个文件系统其实是一个LOOP设备, 也就是一个文件

这么一个文件系统中的驱动显然是只用于当前用户的, 也就是说这个的内容并不是在最初就制作好的, 而是在安装操作系统的时候就生成的. 当然这么一个文件系统也不是必须的, 如果内核可以直接识别你的设备, 那么自然就不需要这么搞了.

这么个生成的工具就是ramdisk 这个有两种格式, 在CentOS5上是模拟成的硬盘的, 而6,7上是文件系统, 这是因为文件系统更加快速, 提高效率.在CentOS5上表现的是: /boot/initrd-VERSION-release.img. CentOS6,7上就是: /boot/initramfs-VERSION-release.img. (基于内存的文件系统) 还可以看到,叫System.map开头的文件, 这个是内核放置到物理内存上的映射表.

在完成加载驱动后, 就会进行根的切换, 因为上面的这个根是个假的. 如果没有这一步, 那么内核就会认为自己已经完成了根的加载了, 而放弃加载真实的根了.

正餐

CentOS系统的启动流程: (x86)

POST: 加电自检: 当按下电源按钮, 这个时候会开始检查各个硬件是否存在以及他们能否正常工作(CPU, 风扇, 内存, 输入输出设备等等) 这些依靠ROM中的CMOS, BIOS加载CMOS,取得系统的硬件信息. BIOS程序的执行也依靠CPU. 所以说当我开机的时候, 主机激活CPU, 并且执行第一条指令: 叫醒 . 接着CPU就会去某ROM的某一个固定地址去加载那个地方的指令, 从而完成自检等等操作. 内存包括ROM+RAM, 在内存进行编址的时候, 都是先进行的ROM编址, 完成之后才进行的RAM编址.

BIOS是进行主板设定的地方, 但并不是说我们每一次都要进行设定, 设定的结果是保存在CMOS中的 所以在开始时, 只提供一个菜单供用户选择. 任何一台电脑都需要操作系统对不.而操作系统都在哪呢? 显然是在我们外部的存储设备上(硬盘, USB闪存等等) 问题是, 多个硬盘的情况下, 我怎么知道哪一块才是我要进的OS所在呢?

这就说到了一个Linux中非常重要的引导次序(BOOT Sequence). BIOS根据这个设置的引导次序来进行加载.决定因素就是设备需要存在并且有引导程序在. 比如: 我的引导次序是光盘, 网卡, 硬盘. 实际的系统在硬盘上.那么当开始进行引导的时候就会开始按照次序寻找: 首先是光盘, 发现当前电脑上并没有安装硬盘, 跳过. 接着是网卡, 网卡存在但是却没有引导程序, 跳过. 最后是硬盘, 存在并且在MBR分区中找到了引导程序. (How?*)开始加载相应的引导程序. 接着 这个bootloader所指向的操作系统是损坏的, 仍启动不起来.

MBR分区内的引导程序寻找, 是依靠于磁盘设备的INT-13中断功能, 只要BIOS能够识别磁盘硬件就可以通过INT-13中断来获得磁盘第一扇区的MBR.

这里提到了bootloader 见名知意:引导加载器 这个是一个程序.在操作系统安装完成就会自动装载到相应的位置上.Windows上的引导加载器叫做:ntloader. 不同版本的Win, 这个引导加载也是不一样的. 对于Linux而言, 他有两种引导加载:

  • LILO: LInux LOader 但是这种加载程序太古老了, 当时硬盘容量也太小了, 所以他不允许将操作系统安装在1024柱面之后. 而现在的硬盘动辄都是几万的柱面.吧
  • GRUB: GRand Uniform Bootloader, 统一引导加载器 这种更加常用.之所以叫统一, 是因为它支持启动Windows, Linux, BSD.当前的Linux使用的都是叫做grub2, 其实是grub1.X版本 和过去的grub相比, 这个更新是进行的完全重写, 和原来是完全不一样的. 我们把过去的GRUB叫做GRUB Legacy.

这个程序就放在MBR中引导程序部分, 当然不同于原来的446个字节, grub采用了一个精巧的方式突破了这个限制. 接着是64字节的fat(磁盘分区表), 最后两个字节是55aa, 盘的有效标识.

小结: bootloader功能就是提供一个菜单, 允许用户选择要启动的系统或者是不同的内核版本. 把用户选定的内核装载到内存的特定空间中, 解压, 展开, 之后将控制权转交给内核. 这个时候就是宣告了BIOS的使命结束.

来补充一下刚刚说过的精巧的GRUB的解决446字节限制.整个grub分成两个过程:

1st stage: 第一阶段说白了什么都不做, 就是向磁盘上去寻真正的第二阶段, 只要能会识别硬件就可以实现了.

1.5阶段: grub识别硬盘上的文件系统. 这个时候遇到了一个和上面一样的问题了, 首先明确grub是存在在文件系统上的, 为了访问磁盘分区, 我就要能够使用文件系统, 而使用文件系统我还要能够访问磁盘分区.

所以说, 其实这个匹配的文件系统在系统安装的时候就已经装载在了MBR剩余的地方了.这样就解决了驱动的问题.

2nd stage: 由于硬盘上的容量远大于446字节, grub就可以任意施展了.

内核的天下

grub加载好内核之后, 就是kernel来统一天下了.

首先, kernel进行自身的初始化: 探测可识别到的所有硬件设备, 加载硬件驱动程序(当然有可能借助于ramdisk加载驱动), 以只读的方式根文件系统. 接着运行用户空间的第一个应用程序: /sbin/init | /usr/lib/systemd/systemd

init程序可以是一个程序, 也有可能是一个脚本(CentOS5, 安卓)

  • SysV: init, CentOS 5 早期的init基本上是依靠脚本来启动的各个的服务, 这就导致每一个命令都会进行进程创建和销毁, 使得启动速度很慢, 而且不能并行并发启动服务, 因为服务间是存在依赖关系的 (配置文件/etc/inittab)
  • Upstart: (Ubuntu研发) CentOS 6的init, 采用了不太一样的启动方式, 当服务开始启动的时候就解决依赖关系. 兼容SysV, 但是CentOS6并没有发挥出Upstart的威力 (配置文件/etc/inittab /etc/init/*.conf)
  • Systemd: 改名为systemd, 从Mac OS X中得到的灵感, Systemd不需要任何脚本来启动服务, 他自己就是一个解释器, 当要启动一个服务的时候, 如果不是必要的就只是标志启动, 但不实际上去启动它, 当第一次使用的时候才会去真正启动它. (/usr/lib/systemd /etc/systemd/system)

现在再回来说说ramdisk: 上面说了使用模拟文件系统形式的ramdisk更加快速, 原因是什么呢?

内核的特性之一是: 使用缓存和缓冲来加速磁盘文件访问.但这样就会有问题: RAM当中有一段内存是分配给内核的, 而ramdisk将文件加载到内存之中, 供内核读取, 而内核把这个的当做硬盘来看待, 而我们都知道硬盘远没有内存快, 所以内核会将这部分原来就已存在在内存的一块再加载到内存中.

因此CentOS6之后就将ramdisk --> ramfs 我们说过这些img镜像是由工具生成的.在CentOS5上是一个脚本,叫做mkinitrd, 而在CentOS6上虽然还有mkinitrd, 但现在我们更多在使用的是一个叫做dracut的工具.

**系统初始化: **

POST –> BootSequence (BIOS) –> Bootloader(MBR) –> kernel(ramdisk) –> rootfs(只读) –> init/systemd

上一张图吧:

Boot

关于上面的systemd, 是CentOS7所采用的init程序, 参考这里CentOS7启动流程

/sbin/init

在Windows上我们也许见过这样的问题, 升级了某些驱动之后再次进入系统会出现蓝屏的现象, 原因有很多,可能是驱动和硬件不兼容, 也有可能是驱动程序有Bug.但不管怎么说, 现在要进行修复了, 在Windows上我们会进入安全模式, 这个模式下只加载最基本的核心组件, 从而最低维持系统.(几乎失去生产能力), 而在Linux下这个被叫做运行级别(Run-level). 这个概念在CentOS7上已经发生了改变, 但在传统的CentOS5和6上依然保持了原本的意义.

运行级别: 为了系统的运行和维护等应用目的而设定. 在原先的Linux系统-服务管理学习笔记也已经提过. 共划分7个等级.

配置文件所在, 在上文已经提过了: /etc/inittab 每一行对应一个action以及与之对应的process(并不意味着一个进程) 由于我这部分的实验主机使用的是CentOS6, 因此实验结果没有5版本好.

现在来看看配置文件都写了什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@livecd ~]# 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 as 3, if you do not have networking)
# 3 - Full multiuser mode
# 4 - unused
# 5 - X11
# 6 - reboot (Do NOT set initdefault to this)
#
id:5:initdefault:

最后一行是说默认的runlevel设置为5. 格式就是: id:runlevel:action:process

现在来聊下常用的action吧: wait(等待, 切换至此级别就运行一次), respawn(此process终止时候重启), initdefault(设定默认优先级别),sysinit(完成系统初始化, 一般指定为/etc/rc.d/rc.sysinit )

在CentOS5上, 下一项配置就是si::sysinit:/etc/rc.d/rc.sysinit 这个是一个非常大的脚本, 加载了许多东西.(挂载/etc/fstab中的文件系统, 读取/etc/sysctl.conf的内核参数, 激活交换设备, 加载LVM分区, 网络设备, 系统时钟等等…)

接着就根据不同的级别加载不同的程序了.

1
2
3
4
l0:0:wait:/etc/rc.d/rc 0
l1:1:wait:/etc/rc.d/rc 1
...(omitted)
l6:6:wait:/etc/rc.d/rc 6

后面意味着读取/etc/rc.d/rcX.d/下的所有脚本, 这些脚本分成了K开头和S开头的.并且都有数字, 这些数字决定了执行次序. 越小就越早执行. 所有K开头的都进行stop操作.而S开头的就进行start操作.

有一个专门用来管理这些的工具, 叫做chkconfig, 它能够来查看和管理rcX.d下这些链接文件.

使用–list来查看所有的自启动服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@WWW ~]$ chkconfig -list
NetworkManager 0:off 1:off 2:on 3:on 4:on 5:on 6:off
acpid 0:off 1:off 2:on 3:on 4:on 5:on 6:off
atd 0:off 1:off 2:off 3:off 4:off 5:off 6:off
...(omitted)
# 假设现在我们将级别5时候的NetworkManager关闭, 这样操作
[root@WWW rc0.d]$ chkconfig --level 5 NetworkManager off
[root@WWW rc0.d]$ chkconfig --list
NetworkManager 0:off 1:off 2:on 3:on 4:on 5:off 6:off
...(omitted)
# 看, 服务的自动启动已经被关闭了
[root@WWW rc0.d]$ ll /etc/rc.d/rc5.d/
total 0
...(omitted)
lrwxrwxrwx. 1 root root 24 Jul 10 16:11 K84NetworkManager -> ../init.d/NetworkManager
...(omitted)
# 此时查看就会发现, 已经变成K开头的了.那么, 为什么是84呢?
# 要知道这个问题我们需要看一下NetworkManager的真正脚本
[root@WWW ~]$ head -10 /etc/init.d/NetworkManager
#!/bin/sh
#
# NetworkManager: NetworkManager daemon
#
# chkconfig: - 23 84
# description: This is a daemon for automatically switching network \
# connections to the best available connection.
#
# processname: NetworkManager
# pidfile: /var/run/NetworkManager/NetworkManager.pid

除了这些文件, 在系统启动的时候还会加载/etc/sysconfig下的种种配置.

chkconfig & rc脚本

查看服务在所有级别的启动情况:

1
chkconfig [--list] [name]

添加:

sysV的脚本放在/etc/rc.d/init.d (/etc/init.d)

1
2
3
4
5
6
chkconfig --add name
# 脚本的开头只要满足格式就可以:

#!/bin/bash
#
# chkconfig: LLLL(写了就是S, 没写就是K) nn nn

将脚本按照格式写完放到/etc/rc.d/init.d/的下面, 并且赋予权限, 接着就可以通过service XXX start来运行.接着执行添加指令就可以在相应的位置上创建.

1
2
3
4
5
[root@livecd sysconfig]# chkconfig --add test
[root@livecd sysconfig]# chkconfig --list test
test 0:off 1:off 2:off 3:on 4:on 5:on 6:off
[root@livecd sysconfig]$ ls -l /etc/rc.d/rc0.d/K33test
lrwxrwxrwx. 1 root root 14 Jul 10 16:45 /etc/rc.d/rc0.d/K33test -> ../init.d/test

删除也很简单, 直接把add换成del就行了:

1
2
3
[root@livecd sysconfig]# chkconfig --del test
[root@livecd sysconfig]# chkconfig --list test
service test supports chkconfig, but is not referenced in any runlevel (run 'chkconfig --add test')

这个时候就会发现已经找不到对应的链接了.

还可以对当前已经存在的连接修改等级:

1
2
3
4
5
6
7
8
9
[root@WWW ~]$ chkconfig [--level levels] name <on|off|reset>
--level LLLL 省略时默认为2345
# 比如说
[root@WWW ~]$ chkconfig NetworkManager off
[root@WWW ~]$ chkconfig --list NetworkManager
NetworkManager 0:off 1:off 2:off 3:off 4:off 5:off 6:off
[root@WWW ~]$ chkconfig --level 2345 NetworkManager on
[root@WWW ~]$ chkconfig --list NetworkManager
NetworkManager 0:off 1:off 2:on 3:on 4:on 5:on 6:off

接着在探索一下这一堆神奇的目录, 你会发现在2,3,4,5这四个等级的启动任务的最后都有一个叫S99local的链接, 更神奇的是, 它指向上级目录的一个叫rc.local的脚本. 进来看看是个什么东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@WWW ~]$ ls -l /etc/rc.d/rc{1,2,3,4,5,6}.d/S99local
ls: cannot access /etc/rc.d/rc1.d/S99local: No such file or directory
ls: cannot access /etc/rc.d/rc6.d/S99local: No such file or directory
lrwxrwxrwx. 1 root root 11 Dec 16 2011 /etc/rc.d/rc2.d/S99local -> ../rc.local
lrwxrwxrwx. 1 root root 11 Dec 16 2011 /etc/rc.d/rc3.d/S99local -> ../rc.local
lrwxrwxrwx. 1 root root 11 Dec 16 2011 /etc/rc.d/rc4.d/S99local -> ../rc.local
lrwxrwxrwx. 1 root root 11 Dec 16 2011 /etc/rc.d/rc5.d/S99local -> ../rc.local
[root@WWW ~]$ cat ../rc.local
#!/bin/sh
#
# This script will be executed *after* all the other init scripts.
# You can put your own initialization stuff in here if you don't
# want to do the full Sys V style init stuff.

touch /var/lock/subsys/local

所以这就很显然了, 当你有一个想要开机执行的任务(不适合写成脚本)可以把它添加到这个脚本中.网上有很多教程介绍开机任务的时候都说过编辑/etc/rc.local, 有的却说编辑rc.d/rc.local. 其实这个都是说一个文件etc的下的那个其实是一个软链./etc/rc.local -> rc.d/local .

好了这个时候, 用户就要登录了.也就执行到了:

1
2
3
4
5
1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
...
6:2345:respawn:/sbin/mingetty tty6
x:5:respawn:/etc/X11/prefdm -nodaemon # 图形界面

这个时候开始加载终端, 也就进入了我们看到的第一个画面:

login

这个界面的出现是因为mingetty之后调用了一个叫login的程序. Warning: 在远程连接的shell里执行这个指令远程主机就会关闭连接!

在SysV的inittab里的第一句话写了这么一句: si::sysinit:/etc/rc.d/rc.sysinit.在CentOS6上这个由于使用了startup所以没有出现在inittab中, 而是写在了别的配置文件中, 这意味着在实际运行之前还要再把整个系统的环境设置好.这个脚本做的初始化有哪些呢?

这个脚本还是很大的(670行)主要做的事情有:

  • 获取主机名以及加载网络配置(如果没有就命名为localhost)
  • 设置和显示banner
  • 激活udev和selinux
  • 挂载/etc/fstab文件中定义的文件系统
  • 检测根文件系统, 并用读写的方式的方式重新挂载根文件系统
  • 设置系统时钟
  • 激活swap设备
  • 根据/etc/sysctl.conf文件来设置内核参数
  • 激活LVM和software raid设备
  • 加载额外设备的驱动
  • 清理操作

**总结:/sbin/init –> (/etc/inittab) –> 设置默认运行级别 –> 运行系统初始脚本,完成系统的初始化 –> 关闭对应下需要关闭的服务, 启动需要运行的服务 –> 设置登录终端 **

CentOS6和5的区别

init程序尽管更换为了startup, 但仍旧命名为init, 并且配置文件不是单一的inittab, 而是分了多个文件都放在/etc/init下, 每一个配置项都是一个*.conf的文件.

在这个目录下的rc.conf其实最终还是执行的/etc/rc.d/rc $RUNLEVEL 而负责执行脚本的rcS.conf实际上运行的也就是刚刚说过的rc.sysinit类似的, 大家也可以猜出来tty.conf实际上就是执行的mingetty.

注意: /etc/init/*.conf文件语法, 遵循upstart配置文件的语法格式.