了解一个系统的启动过程,对于一位系统管理员 and 运维是非常重要的。了解系统启动方式对于在系统出现故障时进行有效的故障排除非常重要。当系统启动并在几分钟后知道我们到了登录提示阶段。我们是否试图找出启动序列的所有阶段已经正常通过,以及系统启动期间这些场景背后发生了什么。下面我们就来熟悉一下Centos6系统的启动流程。
整体流程大概如下:
第一阶段硬件引导
系统供电开机后,主板BIOS(Basic Input / Output System)运行POST(Power on self test)代码,检测系统外围关键设备(如:CPU、内存、显卡、I/O、键盘鼠标等)。硬件配置信息及一些用户配置参数存储在主板的CMOS( Complementary Metal Oxide Semiconductor)上(一般64字节),实际上就是主板上一块可读写的RAM芯片,由主板上的电池供电,系统掉电后,信息不会丢失。
执行POST代码对系统外围关键设备检测通过后,系统启动自检程序,根据我们在BIOS中设置的启动顺序搜索启动驱动器(比如的硬盘、光驱、网络服务器等)。选择合适的启动器,比如通常情况下的硬盘设备,BIOS会读取硬盘设备的第一个扇区(MBR,512字节),并执行其中的代码。实际上这里BIOS并不关心启动设备第一个扇区中是什么内容,它只是负责读取该扇区内容、并执行,BIOS的任务就完成了。此后将系统启动的控制权移交到MBR部分的代码。
BIOS
- BIOS代表基本输入/输出系统
- 执行一些系统完整性检查
- 搜索,加载和执行引导加载程序
- 它在软盘,CD-ROM或硬盘驱动器中查找引导加载程序(提示选择可启动设备,可以是硬盘,CD/DVD-ROM,软驱,USB闪存)。您可以在BIOS启动期间按一个键(通常为F2的F12,但这取决于您的系统)以更改启动顺序。
- 一旦检测到引导加载程序并将其加载到内存中,BIOS就会为其提供控制。
- 操作系统尝试从MBR包含主引导加载程序的硬盘引导。
MBR: MBR的大小为512Bytes,位于HDD的第一扇区或最后扇区(取决于制造商)。
- MBR代表主引导记录;
- 它位于可引导磁盘的第一个扇区中,通常是/dev/hda或/dev/sda;
- MBR的大小为512字节,它三部分组成:
主引导加载程序代码(446Bytes): 此代码提供引导加载程序信息和硬盘上实际引导加载程序代码的位置详细信息。这有助于CPU加载Boot loader的第二阶段; 分区表信息(64Bytes): MBR包含64字节的数据,用于存储分区表信息,例如每个分区的开始和结束,分区大小,分区类型(无论是主分区还是扩展分区等); Magic Number(2Bytes): 作为MBR的验证检查。
Bootloader:
我们知道不同操作系统的文件系统格式不同?还有我们知道一个磁盘可以安装多个操作系统,boot loader
怎么能够做到引导的就是我们想要的操作系统呢?这么多不同的功能单靠一个446字节的boot loader是远远不够的。因此必须弄一个相对应的程序来处理各自对应的操作系统核心文件,这个程序就是操作系统的loader(注意不是MBR中的boot loader),这样一来bootloader
只需要将控制权交给对应操作系统的loader,让它负责去启动操作系统就行了。
下图能更好地解释Bootloader
的作用:
解读上图内容,我们知道一个硬盘的每个分区的第一个扇区叫做bootsector
,这个扇区存放的就是操作系统的loader,所以我们常说一个分区只能安装一个操作系统,如上图,第一个分区的boot sector
存放着windows的loader,第二个分区放着Linux的loader,第三个第四个由于没有安装操作系统所以空着。至于MBR的bootloader
是干嘛呢, bootloader
有三个功能:提供选单,读取内核文件,转交给其他loader 。
提供选单就是给用户提供一张选项单,让用户选择进入哪个操作系统;
读取内核文件,我们知道系统会有一个默认启动的操作系统,这个操作系统的loader在所在分区的boot sector有一份,除此之外,也会将这个默认启动的操作系统的loader复制一份到MBR的boot loader中,这样一来MBR就会直接读取boot loader中的loader了,然后就是启动默认的操作系统;
转交个其他的loader,当用户选择其他操作系统启动的时候,bootloader会将控制权转交给对应的loader,让它负责操作系统的启动。
MBR使用16Bytes来存储单个分区信息,这就是为什么MBR概念中的分区数限制为4(4×16 = 64)的原因。
64个字节包含4个分区(16x4)的分区信息。这就是为什么硬盘只能有4个主分区,因为MBR只能存储4个分区的信息。因此,如果硬盘上需要4个以上的分区,则必须扩展其中一个主分区,并在这些扩展分区之外创建逻辑分区。
它还包含有关GRUB(或旧系统中的LILO)的信息,因此,简单来说,MBR加载并执行GRUB引导加载程序。
注意: 现在MBR直接无法加载内核,因为它不知道文件系统的概念,并且需要为每个支持的文件系统提供带文件系统驱动程序的引导加载程序,以便引导加载程序本身可以理解和访问它们。
为了克服这种情况,GRUB与/boot/grub.conf
和文件系统驱动程序中的文件系统的详细信息一起使用。
第二阶段GRUB启动引导
一旦Bootloader
阶段1完成并且能够找到实际的引导加载程序位置,第1阶段引导加载程序通过将Bootloader
加载到内存中来启动第二阶段。
在此阶段,位于MBR之后的前30KB硬盘中的GRUB(Grand Unified Bootloader)被加载到RAM中以读取其配置并显示GRUB引导菜单(用户可以手动指定引导参数)到用户。
GRUB将用户选择的(或默认)内核加载到内存中,并将控制权传递给内核。如果用户没有选择操作系统,则在定义的超时后,GRUB将在内存中加载默认内核以启动它。
根据gnu.org说的"引导加载程序是计算机启动时运行的第一个软件程序"。GRUB或GRand Unified Bootloader
是Linux操作系统的引导加载程序。Grub有两个主要版本(Grub版本1和2)。目前,大多数linux ditros已经开始使用grub版本2. grub的一个主要特性是它可以使用linux映像安装,并且不需要运行操作系统。
Grub是一个多阶段引导程序(Stage1,Stage1.5和Stage2),grub版本1和版本2的3个阶段解释如下。
GRUB第1阶段:
- 主引导加载程序在MBR中占用的磁盘空间少于512个字节 - 空间太小,无法包含加载复杂操作系统所需的指令;
- 相反,主引导加载程序执行加载阶段1.5或阶段2引导加载程序的功能。
GRUB第1.5阶段:
- 阶段1可以直接加载阶段2,但通常设置为加载阶段1.5;
- 当/boot分区位于硬盘驱动器的1024柱头之上时,可能会发生这种情况;
- 在MBR之后和第一个分区之前,GRUB Stage 1.5位于硬盘的前30KB中;
- 该空间用于存储文件系统驱动程序和模块;
- 这使得阶段1.5能够加载阶段2以从文件系统上的任何已知位置加载,即/boot/grub。
GRUB第2阶段:
- 负责从/boot/grub/grub.conf和所需的任何其他模块加载内核;
- 加载GUI界面,即位于/grub/splash.xpm.gz的启动图像,其中包含可用内核列表,您可以在其中手动选择内核,或者在默认超时值之后,所选内核将启动
原始文件是/etc/grub.conf,您可以在/boot/grub/grub.conf中查看符号链接文件:
代码语言:javascript复制# grub.conf generated by anaconda## Note that you do not have to rerun grub after making changes to this file# NOTICE: You have a /boot partition. This means that# all kernel and initrd paths are relative to /boot/, eg.# root (hd0,0)# kernel /vmlinuz-version ro root=/dev/sda3# initrd /initrd-[generic-]version.img#boot=/dev/sdadefault=0
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.32-431.el6.x86_64)
root (hd0,0)
kernel /vmlinuz-2.6.32-431.el6.x86_64 ro root=UUID=73f96693-ed87-4953-9b51-d6f2cca370eb rd_NO_LUKS rd_NO_LVM LANG=en_US.UTF-8 rd_NO_MD SYSFONT=latarcyrheb-sun16 crashkernel=auto KEYBOARDTYPE=pc KEYTABLE=us rd_NO_DM rhgb quiet
initrd /initramfs-2.6.32-431.el6.x86_64.img
正如您从上面的信息中注意到的那样,它包含内核和initrd映像,因此,简单来说,GRUB只是加载并执行内核和initrd映像。
grub> root (hd0,0) ---> root指令为grub指定了一个根分区 grub> kernel /vmlinuz-2.6.32-431.el6.x86_64 ro root=UUID=73f96693-ed87-4953-9b51-d6f2cca370eb .... ---> 加载指定的模块 grub> initrd /initramfs-2.6.32-431.el6.x86_64.img ---> 指定initrd文件
第三阶段内核引导
如阶段二所述,grub>boot指令后,系统启动的控制权移交给kernel。Kernel会立即初始化系统中各设备并做相关配置工作,其中包括CPU、I/O、存储设备等。
关于设备驱动加载,有两部分:
一部分设备驱动编入Linux Kernel中,Kernel会调用这部分驱动初始化相关设备,同时将日志输出到kernel message buffer,系统启动后dmesg可以查看到这部分输出信息; 另一部分设备驱动并没有编入Kernel,而是作为模块形式放在initrd(ramdisk)中。
initrd
是一种基于内存的文件系统,启动过程中,系统在访问真正的根文件系统时,会先访问initrd文件系统。将initrd中的内容打开来看,会发现有bin、devetc、lib、procsys、sysroot、init等文件(包含目录)。其中包含了一些设备的驱动模块,比如scsi ata
等设备驱动模块,同时还有几个基本的可执行程序insmod,modprobe,lvm,nash。主要目的是加载一些存储介质的驱动模块,如上面所说的scsi ideusb
等设备驱动模块,初始化LVM,把根文件系统以只读方式挂载。
initrd
中的内容释放到rootfs中后,Kernel会执行其中的init文件,这里的init是一个脚本,由nash
解释器执行。这个时候内核的控制权移交给init文件处理,我们查看init文件的内容,主要也是加载各种存储介质相关的设备驱动。
驱动加载后,会创建一个根设备,然后将根文件系统以只读的方式挂载。这步结束后释放未使用内存并执行switchroot
,转换到真正的根上面去,同时运行/sbin/init
程序,开启系统的1号进程,此后系统启动的控制权移交给init进程。关于switchroot是在nash中定义的程序。
Linux Kernel
需要适应多种不同的硬件架构,但是将所有的硬件驱动编入Kernel又是不实际的,而且Kernel也不可能每新出一种硬件结构,就将该硬件的设备驱动写入内核。实际上Linux Kernel
仅是包含了基本的硬件驱动,在系统安装过程中会检测系统硬件信息,根据安装信息和系统硬件信息将一部分设备驱动写入initrd。这样在以后启动系统时,一部分设备驱动就放在initrd中来加载。
总结过程如下:
- 按照grub.conf中"root="的指定安装根文件系统
- 内核执行/sbin/init程序
- 由于init是Linux内核执行的第一个程序,因此它的进程ID(PID)为1. 执行'ps -ef | grep init'并检查pid
- initrd代表初始RAM磁盘
- initrd被内核用作临时根文件系统,直到引导内核并挂载真正的根文件系统。它还包含内部编译的必要驱动程序,这有助于它访问硬盘驱动器分区和其他硬件。
四阶段init初始化
init进程起来后,系统启动的控制权移交给init进程, /sbin/init
进程是所有进程的父进程,当init起来之后,它首先会读取配置文件/etc/inittab
,进行以下工作:
- 执行系统初始化脚本(/etc/rc.d/rc.sysinit),对系统进行基本的配置,以读写方式挂载根文件系统及其它文件系统,到此系统基本算运行起来了,后面需要进行运行级别的确定及相应服务的启动
- 确定启动后进入的运行级别
- 执行
/etc/rc.d/rc
,该文件定义了服务启动的顺序是先K后S,而具体的每个运行级别的服务状态是放在/etc/rc.d/rcn.d (n=0~6)
目录下,所有的文件均链接至/etc/init.d下的相应文件; - /etc/rc.d/rc RUNLEVEl # RUNLEVEL为缺省的运行模式
- /etc/rc.d/rc.local
- 启动虚拟终端/sbin/mingetty
- 在运行级别5上运行X
这时呈现给用户的就是最终的登录界面, 至此,系统启动过程完毕。
说明系统启动运行级别的概念以及服务的定制方法:
当initrd
可以正常检测和装载之后,最后的工作就基本上由操作系统来进行了。当系统的init进程起来之后系统启动的控制权移交给init
进程。
/sbin/init进程是所有进程的父进程,当init起来之后,它首先会读取配置文件/etc/inittab
,判断运行级别。
id:3:initdefault:
id:runlevel:action:process
id: 是用于标识此文件中的条目的唯一字符序列 runlevels: 是该条目适用的运行级别 action: 是对运行级别采取的操作 process: 指定要执行的进程
runleve:共7级别 为0-6,默认级别为3
runlevel | 含义 |
---|---|
0 | 关机 |
1 | 单用户模式(root, 无须登录), single, 维护模式; |
2 | 多用户模式,会启动网络功能,但不会启动NFS;维护模式; |
3 | 多用户模式,正常模式;文本界面; |
4 | 预留级别;可同3级别; |
5 | 多用户模式,正常模式;图形界面; |
6 | 重启 |
还有一些额外的运行级别,很少使用,例如:
s,S or single: 单用户模式 emergency: 绕过rc.sysinit
总结运行级别:
运行级别1用于维护目的,因为这是一个非常有限的运行级别。只有最小脚本在此运行级别中运行。只有root用户才能登录。没有其他用户可以登录此运行级别。运行级别2比运行级别1更宽松。这里,所有用户都可以登录,但网络服务没有运行。Runlevel 3提供了完整的工作环境。所有用户都可以登录,启用网络。运行级别4仅用于实验目的。在运行级别5中,可以使用图形控制台。运行级别'0'是系统的暂停状态,切换到运行级别6将重启系统。
action | 含义 |
---|---|
wait | 切换至此级别运行一次 |
respawn | 此process终止,就重新启动之 |
initdefault | 设定 init 默认运行级别 |
sysinit | 设定系统初始化方式,/etc/rc.d/rc.sysinit |
.... | 以上为常见action |
然后init将执行/etc/rc.d/rc.sysinit
脚本,这是init在引导过程中执行的第一个脚本。
该脚本仅在引导期间执行一次,它将执行以下操作:
- 设置主机名(通过引用/sbin/hostname)
- 挂载/proc和/sys虚拟文件系统
- 它将加载USB模块
- 启用SWAP
- 设置SELinux (通过引用/etc/sysconfig/selinux)
- 提供启动屏幕消息和图形启动屏幕
- 初始化所有硬件
- 加载所有用户定义的模块(通过引用/etc/sysconfig/modules和/etc/rc.modules)
- 配置所有内核参数(通过引用/etc/sysctl.conf)
- 扫描并加载存储设备和多路径设备模块
- 加载mdraid和lvm模块
- 引用/etc/fstab并在适用的地方执行fsck。(系统将通过引用/etc/fstab在启动时执行fsck,这就是为什么它最初将root“/”文件系统挂载为只读的原因)
- 通过引用/etc/fstab来挂载Filesystem
- 最后它将根文件系统重新安装为读/写(r/w)
完成rc.sysinit后,内核会查看/etc/rc.d/rcx.d/目录(X是从/etc/inittab获取的运行级别)。
在/etc/rc.d
目录下有与每个运行级别对应的目录。根据这一行,运行/etc/rc.d/rc3.d
目录中的脚本。我们列出这个目录中的文件。(其他目录如rc1.d,rc2.d ......中也有类似的文件)。
[root@Centos6.9 ~]# ls -l /etc/rc.d/rc3.d/总用量 0
lrwxrwxrwx. 1 root root 19 9月 19 2016 K10saslauthd -> ../init.d/saslauthd
lrwxrwxrwx. 1 root root 22 11月 17 2016 K14zabbix-agent -> ../init.d/zabbix-agent
lrwxrwxrwx 1 root root 22 5月 9 2018 K15htcacheclean -> ../init.d/htcacheclean
lrwxrwxrwx 1 root root 15 5月 9 2018 K15httpd -> ../init.d/httpd
lrwxrwxrwx. 1 root root 18 11月 15 2016 K15svnserve -> ../init.d/svnserve
lrwxrwxrwx. 1 root root 20 9月 19 2016 K50netconsole -> ../init.d/netconsole
lrwxrwxrwx. 1 root root 15 11月 15 2016 K50snmpd -> ../init.d/snmpd
lrwxrwxrwx. 1 root root 19 11月 15 2016 K50snmptrapd -> ../init.d/snmptrapd
lrwxrwxrwx. 1 root root 14 11月 15 2016 K74ntpd -> ../init.d/ntpd
lrwxrwxrwx. 1 root root 17 11月 15 2016 K75ntpdate -> ../init.d/ntpdate
lrwxrwxrwx. 1 root root 21 9月 19 2016 K87restorecond -> ../init.d/restorecond
lrwxrwxrwx. 1 root root 15 9月 19 2016 K89rdisc -> ../init.d/rdisc
lrwxrwxrwx 1 root root 18 2月 23 2018 K92iptables -> ../init.d/iptables
lrwxrwxrwx. 1 root root 19 9月 19 2016 S08ip6tables -> ../init.d/ip6tables
lrwxrwxrwx. 1 root root 17 9月 19 2016 S10network -> ../init.d/network
lrwxrwxrwx. 1 root root 16 9月 19 2016 S11auditd -> ../init.d/auditd
lrwxrwxrwx. 1 root root 17 9月 19 2016 S12rsyslog -> ../init.d/rsyslog
lrwxrwxrwx. 1 root root 15 9月 19 2016 S25netfs -> ../init.d/netfs
lrwxrwxrwx. 1 root root 19 9月 19 2016 S26udev-post -> ../init.d/udev-post
lrwxrwxrwx 1 root root 14 5月 21 02:16 S55sshd -> ../init.d/sshd
lrwxrwxrwx 1 root root 15 5月 9 2018 S59Redis -> ../init.d/Redis
lrwxrwxrwx 1 root root 17 5月 11 2018 S69Tengine -> ../init.d/Tengine
lrwxrwxrwx 1 root root 14 7月 23 06:09 S80exim -> ../init.d/exim
lrwxrwxrwx 1 root root 15 7月 23 06:09 S90crond -> ../init.d/crond
lrwxrwxrwx. 1 root root 11 9月 19 2016 S99local -> ../rc.local
此目录中的某些文件以S
开头,其他文件以K
开头。以S
开头的文件对应于必须在该特定运行级别中启动
的脚本,而具有K
的文件对应于要被杀死
的脚本。这些文件只是/etc/rc.d/init/d
目录下脚本的软链接(一个软链接指向/etc/rc.local
,它本身是/etc/rc.d/rc/local
的软链接。)。/etc/rc.d/init.d/
中的脚本是守护进程。守护进程是在后台运行并提供某种服务的进程。例如,http守护进程(httpd)提供Web服务。
执行所有这些脚本后,将运行/etc/rc.local
脚本,init运行在/etc/rc.d/rc.local
中找到的任何内容(无论运行级别如何)。 rc.local
非常特别,每次更改运行级别时都会执行它。它是在初始化过程或甚至启动过程中运行的最后一个脚本。一切都完成后,控制权将返回给内核。
注意:
rc.local
不用于所有发行版,例如Debian就没有这个文件。
如果您希望在系统启动时执行命令或脚本,则可以将其放在此脚本中:
代码语言:javascript复制[root@Centos6.9 ~]# cat /etc/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# start solution service/data/app/data-solution-service/solution-start.sh
现在所有这些脚本都已成功执行.
启动终端 接下来会由/sbin/mingetty指令启动终端,由于系统设置启动tty1-tty6 ,所以会启动6个命令行终端。最终呈现给我们的就是这样一个画面:
五阶段用户登录
/bin/login
通过读取/etc/passwd文件, 成功登录后启动交互式登录shell 。此shell调用通常在启动时读取/etc/profile及其私有等配置项~/.bash_profile
, ~/.bash_login
, ~/.profile
。
通常使用shell程序(例如[prompt]$/bin/bash)或/bin/su命令在命令行启动交互式非登录shell 。还可以在图形环境中使用诸如xterm或konsole之类的终端程序启动交互式非登录shell 。这种类型的shell调用通常会复制父环境,然后读取用户的~/.bashrc文件以获取其他启动配置说明。
当shell脚本运行时,通常会出现非交互式shell。它是非交互式的,因为它正在处理脚本而不是等待命令之间的用户输入。对于这些shell调用,仅使用从父shell继承的环境。
该文件~/.bash_logout
不用于shell的调用。当用户退出交互式登录shell时,它将被读取并执行。
许多发行版/etc/bashrc用于非登录shell的系统范围初始化。此文件通常从用户的~/.bashrc文件中调用,而不是直接构建到bash本身。本节遵循此约定。
/etc/profile:
该文件首先设置一些辅助函数和一些基本参数。它指定了一些bash历史参数,并且出于安全考虑,禁用为root用户保留永久历史文件。它还设置默认用户提示。然后它调用目录中的小型单用途脚本/etc/profile.d
以提供大部分初始化