Linux 系统的启动是不需要人为参与和控制的,只要按下电源,系统就会按照设定好的方式进行启动。不过,了解系统的启动有助于我们在系统出现问题时能够快速地修复 Linux 系统。
目前,CentOS 6.x 的启动流程经历以下几步:
总的来说,Linux 系统的启动,要经历BIOS自检 -> 启动 GRUB -> 加载内核 -> 执行第一个进程 -> 配置系统初始环境.
前面提到,服务器通电后,会直接进入 BIOS,BIOS 全称 Basic Input/Output System,中文可译为基本输入/输出系统。
简单地理解 BIOS,它就是固化在主板上一个 ROM(只读存储器)芯片上的程序,主要保存计算机的基本输入/输出信息、系统设置信息、开机自检程和系统自启动程序,用来为计算机提供最底层和最直接的硬件设置与控制。
也就是说,BIOS 是硬件与软件之间的接口,而且是非常基本的接口,BIOS 提供了一组基本的操作系统使用的指令,系统启动的成功与否,依赖于 BIOS。
BIOS 的初始化主要完成以下 3 项工作:
当 MBR 被加载到 RAM 之后,BIOS 就会将控制权交给 MBR,进入系统引导的第二阶段。
MBR 中最主要的功能就是存储 Boot Loader ( 启动引导程序 )。启动引导程序用于引导操作系统启动,Linux 系统中默认使用的启动引导程序是 GRUB。
关于 MBR 结构,作如下记录:
MBR 也就是主引导记录,位于硬盘的 0 磁道、0 柱面、1 扇区中,主要记录了启动引导程序和磁盘的分区表。我们通过图 1 来看看 MBR 的结构。
MBR 共占用了一个扇区,也就是 512 Byte。其中 446 Byte 安装了启动引导程序,其后 64 Byte 描述分区表,最后的 2 Byte 是结束标记。我们已经知道,每块硬盘只能划分 4 个主分区,原因就是在 MBR 中描述分区表的空间只有 64 Byte。其中每个分区必须占用 16 Byte,那么 64 Byte 就只能划分 4 个主分区。每个分区的 16 字节的规划如表 1 所示。
存储字节 | 数据内容及含义 |
---|---|
第 1 字节 | 引导标志 |
第 2 字节 | 本分区的起始磁道号 |
第 3 字节 | 本分区的起始扇区号 |
第 4 字节 | 本分区的起始柱面号 |
第 5 字节 | 分区类型,可以识别主分区和扩展分区 |
第 6 字节 | 本分区的结束磁道号 |
第 7 字节 | 本分区的结束扇区号 |
第 8 字节 | 本分区的结束柱面号 |
第 9~12 字节 | 本分区之前已经占用的扇区数 |
第 13~16 字节 | 本分区的总扇区数 |
BIOS 的作用就是自检,然后从 MBR 中读取出启动引导程序。那么,启动引导程序最主要的作用就是加载操作系统的内核。当然,每种操作系统的启动引导程序都是不同的。
每种操作系统的文件格式不同,因此,每种操作系统的启动引导程序也不一样。不同的操作系统只有使用自己的启动引导程序才能加载自己的内核。如果我的服务器上只安装了一个操作系统,那么这个操作系统的启动引导程序就会安装在 MBR 中。BIOS 调用 MBR 时读取出启动引导程序,就可以加载内核了。
但是在有些时候,我的服务器中安装了多个操作系统,而 MBR 只有一 个,那么在 MBR 中到底安装哪个操作系统的启动引导程序呢?
很明显,一个 MBR 是不够用的。每块硬盘只能有一个 MBR 是不能更改的,所以不可能増加 MBR 的数量。系统只能在每个文件系统(可以看成分区)中单独划分出一个扇区,称作引导扇区(Boot Sector)。每个分区的引导扇区中也能安装启动引导程序,也就是说,在 MBR 和每个单独分区的引导扇区中都可以安装启动引导程序。这样多个操作系统才能安装在同一台服务器中(每个操作系统要安装在不同的分区中),而且每个操作系统都是可以启动的。
还有一个问题,BIOS 只能找到 MBR 中的启动引导程序,而找不到在分区的引导扇区中的启动引导程序。那么,要想完成多系统启动,我们的方法是増加启动引导程序的功能,让安装到 MBR 中的启动引导程序(GRUB)可以调用在分区的引导扇区中的其他启动引导程序。
因此,启动引导程序拥有以下功能:
当然,这个安装顺序不是绝对的,就算最后安装了 Windows,我们也可以通过手工再安装 GRUB 的方法,来保证 MBR 中安装的还是 Linux 的启动引导程序。
图2 展示了启动引导程序的作用。
GRUB 加载了内核之后,内核首先会再进行二次系统的自检,而不一定使用 BIOS 检测的硬件信息。这时内核终于开始替代 BIOS 接管 Linux 的启动过程了。
内核完成再次系统自检之后,开始采用动态的方式加载每个硬件的模块,这个动态模块大家可以想象成硬件的驱动(默认 Linux 硬件的驱动是不需要手工安装的,如果是重要的功能,则会直接编译到内核当中;如果是非重要的功能,比如硬件驱动会编译为模块,则在需要时由内核调用。不过,如果没有被内核硬件,要想驱动,就需要手工安装个硬件的硬块了)。
那么,Linux 的内核到底放在了哪里呢?当然是 /boot 的启动目录中了,我们来看看这个目录下的内容吧。
[root@localhost ~]#ls /boot/
config-2.6.32-279.el6.i686
#内核的配置文件,内核编译时选择的功能与模块
efi
#可扩展固件接口,为英特尔为全新PC固件的体系结构、接口和服务提出的建议标准
grub
#启动引导程GRUB的数据目录
initramfe-2.6.32-279.el6.i686.img
#虚拟文件系统(CentOS 6.x 中用initramfs替代了initrd,但功能是一样的)
lost+found
#boot分区的备份目录
symvers-2_6.32-279.el6.i686.gz
#模块符号信息
System.map-2.6.32-279.el6.i686
#内核功能和内存地址的对应列表
vmlinuz-2.6.32-279.el6.i686
#用于启动的Linux内核。这个文件是一个压缩的内核镜像
我们已经知道,Linux 会把不重要的功能编译成内核模块,在需要时再调用,从而保证了内核不会过大。在多数 Linux 中,都会把硬件的驱动程序编译为模块, 这些模块保存在 /lib/modules 目录中。常见的 USB、SATA 和 SCSI 等硬盘设备的驱动,还有一些特殊的文件系统(如 LVM、RAID 等)的驱动,都是以模块的方式来保存的。
如果 Linux 安装在 IDE 硬盘之上,并且采用的是默认的 ext3/4 文件系统,那么内核启动后加载根分区和模块的加载都没有什么问题,系统会顺利启动。但是,如果 Linux 安装在 SCSI 硬盘之上,或者采用的是 LVM 文件系统,那么内核(内核载入内存是启动引导程序 GRUB 调用的,并不存在硬盘驱动不识别的问题)在加载根目录之前是需要加载 SCSI 硬盘或 LVM 文件系统的驱动的。
SCSI 硬盘和 LVM 文件系统的驱动都放在硬盘的 /lib/modules 目录中,既然内核没有办法识别 SCSI 硬盘或 LVM 文件系统,那怎么可能读取 /lib/modules 目录中的驱动呢?Linux 给出的解决办法是使用 initramfs 这个虚拟文件系统来处理这个问题。
CentOS 6.x 中使用 initramfs 虚拟文件系统取代了 CentOS 5.x 中的 initrd RAM Disk。它们的作用类似,可以通过启动引导程序加载到内存中,然后会解压缩并在内存中仿真成一个根目录,并且这个仿真的文件系统能够提供一个可执行程序,通过该程序来加载启动过程中所需的内核模块,比如 USB、SATA. SCSI 硬盘的驱动和 LVM、RAID 文件系统的驱动。
也就是说,通过 initramfs 虚拟文件系统在内存中模拟出一个根目录,然后在这个模拟根目录中加载 SCSI 等硬件的驱动,就可以加载真正的根目录了,之后才能调用 Linux 的第一个进程 /sbin/init。
Initramfs 虚拟文件系统主要有以下优点:
其实只需要知道 initramfs 是为了在内核中建立一个模拟根目录,这个模拟根目录是为了可以调用 USB、SATA、SCSI、LVM、RAID 等硬盘接口或文件系统的驱动模块,加载了驱动模块后才可以加载真正的系统根目录。我们可以通过示意图 3来表示这个过程。
那么既然 initramfs 是一个仿真根目录,那么我们是否可以看看这个仿真根目录中到底是什么样子的呢?当然可以,命令如下:
[root@localhost ~]# mkdir /tmp/initramfs
#建立测试目录
[root@localhost ~]# cp/boot/initramfs-2.6.32-279.el6.i686.img /tmp/initramfs/
#复制initramfs文件
[root@localhost ~]# cd /tmp/initramfs/
[root@localhost initramfs]# file
initramfs-2.6.32-279.el6.i686.img
initramfe-2.6.32-279.el6.i686.img:gzip compressed
data,from Unix,last modified:
Wed Apr 10 21:49:34 2013, max compression
#查看文件类型,发现这个文件是一个使用gzip命令打包的压缩包
[root@localhost initramfs]# mv initramfs-2.6.32-279.el6.i686.imginitramfs-2.6.32-279.el6.i686.img.gz
#修改文件的扩展名为.gz
[root@localhost initramfs]# gunzip
initramfs-2.6.32-279.el6.i686.img.gz
#解压缩
[root@localhost initramfs]# ls
initramfs-2.6.32-279.el6.i686.img
[root@localhost initramfs]# file
initramfs-2.6.32-279.el6.i686.img
initramfe-2.6.32-279.el6.i686.img: ASCII cpio archive (SVR4withnoCRC)
#查看文件类型,使用cpio命令的压缩文件
[root@localhost initramfs]# cpio -ivcdu
在内核加载完毕,并完成硬件检测与驱动程序加载后,此时主机硬件已经准备完毕,内核会主动呼叫第一个进程,也就是 /sbin/init,此配置文件最主要的功能就是准备软件执行的环境,包括系统的主机名、网络设定、语言、文件系统格式及其他服务的启动等。
这里和 CentOS 5.x 系统相比也有较大变化。在 CentOS 5.x 系统中,主要通过 init 进程的配置文件 /etc/inittab 来设定系统,并启动 Linux。但是在 CentOS 6.x 系统中,由于用 Upstart 启动服务来替换以前的 init,所以在 /etc/inittab 配置文件中只能定义系统的默认运行级别,而其他的功能是靠 /etc/init/ 目录中的其他配置文件实现的。
大家可以把 /etc/init/ 目录中的配置文件看成以前 /etc/inittab 这个文件功能的分拆。
前面提到,由 /sbin/init 进程可通过 /etc/init/rcS.conf 配置文件,分别找到 /etc/rc.d/rc.sysinit 配置文件和 /etc/inittab 配置文件,前者用于初始化系统,配置计算机的初始环境;后者用于确定系统的默认运行级别。
接下来,先介绍 /etc/rc.d/rc.sysinit 配置文件。如果我们使用 Vim 查看 /etc/rc.d/rc.sysinit 配置文件,就会发现这个这个配置文件主要进行了以下几项工作:
在以上过程中,比较值得注意的是自定模块的加载。在 CentOS 中,如果我们想要加载核心模块的话,就可以将整个模块写入到 /etc/sysconfig/modules/*.modules 中。当然,此过程并不是必须的,通常我们的默认模块已经够用,除非对硬件太新,不得不自己加载模块,否则无需刻意添加任何模块。
在 /etc/rc[06].d/ 目录中的程序启动之后,系统的启动就已经完成。不过,我们总有一些程序是需要在系统启动之后随着系统一起启动的。这时我们并不需要自己把需要启动的服务链接到 /etc/rc3.d/ 目录中,因为系统给我们准备了 /etc/rc.d/rc.local 配置文件。
这个配置文件会在用户登陆之前读取,这个文件中写入了什么命令,在每次系统启动时都会执行一次。也就是说,如果有任何需要在系统启动时运行的工作,则只需写入 /etc/rc.d/rc.local 配置文件即可。这个文件的内容如下:
[root@localhost ~]# ll /etc/rc.local
Irwxrwxrwx. 1 root root 13 4月10 21:46 /etc/rc.local -> rc.d/rc.local
#有一个链接文件,两个文件修改哪一个都可以
[root@localhost ~]#vi /etc/rc.d/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
#默认会touch这个文件,每次系统启动时都会touch这个文件,这个文件的修改时间就是系统的启动时间
/etc/rc.d/init.d/httpd start
#如果写入RPM包安装的apache服务的启动命令,apache服务就会在开机时自动启动
Linux 的
我们把启动流程图补充完整,如图 1 所示。
系统的启动过程就是这样的,最终我们就能登录字符界面的 Linux 了。不过,如果需要启动图形界面的 Linux,则还需要调用 X Window 接口。
在启动过程中还有一个配置文件会生效,就是 /etc/init/start-ttys.conf,这个文件主要定义了 Linux 支持的 1~6 个本地终端(tty[1-6])。如果在本机,则可以使用快捷键 Alt+F1~F6 来切换这 6 个本地终端;如果安装并启动了图形界面,则可以使用快捷键 Ctrl+Alt+F7 来切换图形终端。
至此,字符界面的启动过程就介绍完了。是不是非常复杂?如果你是初学者,则只需了解,不需要彻底掌握。但是掌握了 Linux 系统的启动过程,会更加了解 Linux 的结构。当然,如果你非常自信,则可以尝试安装 gentoo Linux 这个版本,因为这个 Linux 版本到目前为止还是纯源码安装的,如果能够安装成功,就会对 Linux 系统的结构及启动过程有更深入的理解。
关于系统默认运行级别,作如下记录:
可以看到,/etc/rc.d/rc.sysinit 配置文件已经将基本的系统配置数据都写好了,我们可以查询 /var/log/dmesg 文件或使用 dmesg 命令查看系统在启动时到底发生了什么。当然,我们也可以通过这个命令来看看 Linux 服务器的硬件信息。
在 CentOS 6.x 中,/etc/inittab 配置文件只能用来设置系统的默认运行级别。
那么,什么是运行级别呢?其实 Linux 是通过运行级别来确定系统启动时到底启动哪些服务的。Linux 默认有 7 个运行级别,具体如表 1 所示。
运行级别 | 含 义 |
---|---|
0 | 关机 |
1 | 单用户模式,可以想象为 Windows 的安全模式,主要用于系统修复 |
2 | 不完全的命令行模式,不含 NFS 服务 |
3 | 完全的命令行模式,就是标准字符界面 |
4 | 系统保留 |
5 | 图形模式 |
6 | 重新启动 |
在 Linux 系统中可以使用 runlevel 命令来查看系统的运行级别,命令如下:
[root@localhost ~]# runlevel
N 3
#N代表在进入这个级别前,上一个级别是什么;3代表当前级别
在这个命令的结果中,"N 3"中的N代表在进入这个级别前,上一个级别是什么;3 代表当前级别。"N" 就是 None 的意思,也就是说,系统是开机直接进入 3 运行级别的,没有上一个运行级别。那如果从图形界面切换到字符界面,再查看运行级别,就应该是这样的:
[root@localhost ~]# runlevel
5 3
#代表由5级别进入3级别
那么,可以手工改变当前的运行级别吗?当然可以了,只需使用 init 命令(注意这不是 init 进程)即可,命令如下:
[root@localhost ~]# init 5
#进入图形界面,当然要已经安装了图形界面才可以
[root@localhost ~]# init 0
#关机
[root@localhost ~]# init 6
#重新启动
不过要注意,使用 init 命令关机和重启并不是太安全,容易造成数据丟失。所以推荐大家使用 shutdown 命令进行关机和重启。
知道了运行级别的作用,我们回到系统启动过程中来。/etc/inittab 配置文件的功能就是确定系统的默认运行级别,也就是系统开机后会进入那个运行级别。此文件的内容如下:
[root@localhost ~]#vim /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
#系统会先调用/etc/init/rcS.conf
# Individual runlevels are started by /etc/init/rc.conf
#在调用/etc/init/rc.conf,在不同的运行级别启动不同的服务
# Ctrl-Alt-Delete is handled by/etc/init/control-alt-delete.conf
#通过这个配置文件判断Ctri+Alt+Delete热启动键是否可用
# 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)
#很眼熟吧,就是刚刚的 0~6运行级别的说明
id:3:initdefault:
#这就是系统的默认运行级别,也就是系统开机后直接进入哪个运行级别
注意,这里的默认运行级别只能写 3 或 5,其他的级别要么是关机重启,要么是保留或单用户,都不能作为系统默认运行级别。