CentOS系统启动流程

概述:系统启动流程是Linux一个重要的内容,深入了解启动流程会对我们学习Linux起到一个顺水推舟的作用。因为CentOS 7改动较大,所以下面的内容只是针对CentOS 5和6来说的。下面进入正题。

启动流程:

 第一步:POST加电自检

   此过程的就是为了检测一下外界的硬件设备是否能够正常运行,如CPU,内存设备,硬盘等等这些硬件设备是否可以正常工作。

 第二步:BIOS选择启动方式

   BIOS对于经常基础计算机的人应该不会陌生,特别是那些经常装系统的人,它就是列出几个选项,让你选择以什么方式来启动系统,常见的有硬盘启动,光盘,以及网络方式启动。

 第三步:BootLoader

   这个步骤略有复杂,但是其实现的功能就是,引导加载系统中的核心文件,并提交到内存运行,它会列出一个grub菜单,其中的选项是我们操作系统的内核,你选择的内核文件会被加载至内存中运行。

    引导加载器grub:找到内核文件,提供grub菜单

        1)Stage  1,读取MBR,目的是为了驱动stage2所在的分区(stage2上存放的是内核文件以及rootfs的文件系统驱动)
        2)Stage 1.5,通过Stage1可以识别到stage2所在的分区,然而分区上的文件系统是需要文件系统驱动程序的,stage 1.5就是为了stage2提供文件系统驱动的(在0柱面1扇区后面的63个扇区中存储)        

        3) 通过stage1和1.5,stage2将被加载(一般为/boot分区),此时stage2,将提供一个内核选择菜单,并且stage2分区内还有一个 ramdisk或者ramfs文件,其为一个临时的根文件系统,其中包含了真正的rootfs所需要的驱动文件,所以stage2及内核文件通常放置于一 个基本的磁盘分区(一般为/boot分区).
        注意:ramdisk临时根文件系统是在安装操作系统后临时生成的,它在安装操作系统后,能扫描当前主机硬盘设备的型号,并找到相关驱动做成一个临时根


wKiom1fTrJCgYwSaAAAo3bBzkxo171.png

      代码分析:

[root@localhost testdir]# cp /boot/initramfs-2.6.32-642.el6.x86_64.img . //将/boot下的ramfs文件拷贝至当前目录
[root@localhost testdir]# zcat initramfs-2.6.32-642.el6.x86_64.img |cpio -id //将其解压缩
140023 blocks
[root@localhost testdir]# ls        //其包含的内如如下,因为是临时的根文件,所以目录结构也类似于我们的rootfs,其中包含rootfs所需要的文件系统的驱动
bin      dracut-004-409.el6  init                initqueue-settled                    lib    netroot    pre-trigger  sbin     tmp
cmdline  emergency           initqueue           initqueue-timeout                    lib64  pre-mount  pre-udev     sys      usr
dev      etc                 initqueue-finished  initramfs-2.6.32-642.el6.x86_64.img  mount  pre-pivot  proc         sysroot  var
[root@localhost testdir]#

 第四步:加载内核文件

   通过上面所选择的内核文件,来将其加载至内存中解压缩,分为以下四个步骤

     1)探测可识别到的所有硬件设备。

     2)加载硬件驱动程序(可能借助于ramdisk/ramfs加载驱动)

     3)以只读方式挂载根文件系统

     4)运行用户空间的第一个应用程序:/sbin/init

       注意:其中Ramdisk/ramfs即stage2所在分区的rootfs文件系统驱动的文件,有了内核文件及所需要的rootfs的文件系统驱动,为避免内核文件有bug或者人为操作问题,先以只读方式挂载rootfs

    代码分析:

wKioL1fTriPQjHNmAAB9ILqHZZQ654.png

 第五步:Init程序初始化

   1)根据init的配置文件获取到运行级别信息,并获取系统初始化脚本的文件路径。(CentOS 5的init文件为/etc/inittab,CentOS6将/etc/inittab文件拆分为多个文件)

     init的配置文件:

       CentOS 5:采用SysV init方式,其特点是启动用户空间的服务程序,通常通过脚本进行,有依赖关系的服务将被串行启动,这也导致了CentOS 5的启动过程相当缓慢,配置文件为/etc/inittab

       CentOS 6:采用Upstart的方式,其特点是类似于并行启动;配置文件:/etc/inittab,/etc/init/*.conf

    2)读取系统初始化脚本/etc/rc.d/rc.sysinit,并按照脚本内容执行,作用如下:
       (1) 设置主机名
       (2) 设置欢迎信息
       (3) 激活udev和selinux
       (4) 挂载/etc/fstab文件中定义的文件系统
       (5) 检测根文件系统,并以读写方式重新挂载根文件系统
       (6) 设置系统时钟
       (7) 激活swap设备
       (8) 根据/etc/sysctl.conf文件设置内核参数
       (9) 激活lvm及software raid设备
       (10) 加载额外设备的驱动程序
       (11) 清理操作
    3)根据前面获取的运行级别,运行/etc/rc.d/rc脚本文件
       /etc/rc.d/目录下有几个rc#.d(#号数字,也就是代表运行级别),其目录下文件为链接文件,其指向/etc/init.d/下的服务脚本文家,根据在/etc/inittab获取的默认运行级别和/etc/rc#.d下的链接文件,来启动和关闭系统的服务,想必现在也能联想到了为什么不同级别下启动的服务不相同,为什么有的服务开机启动,有的却关闭
       /etc/rc#.d/下的链接文件以K或者S开头,K表示开机要被停止的服务,S表示开机要被启动的服务,而且服务脚本都会有一个优先级,

        K*:K##*:##运行次序;数字越小,越先运行;数字越小的服务,通常为依赖到别的服务
        S*:S##*:##运行次序;数字越小,越先运行;数字越小的服务,通常为被依赖到的服务

[root@localhost boot]# cd /etc/rc.d
[root@localhost rc.d]# ls
init.d  rc  rc0.d  rc1.d  rc2.d  rc3.d  rc4.d  rc5.d  rc6.d  rc.local  rc.sysinit
[root@localhost rc.d]# cd rc.3.d
root@localhost rc.d]# cd rc3.d
[root@localhost rc3.d]# ls
K01dnsmasq         K35vncserver   K88wpa_supplicant   S08ip6tables    S14nfslock     S26lm_sensors              S95anacron
K02avahi-dnsconfd  K50netconsole  K89dund             S08iptables     S15mdmonitor   S26lvm2-monitor            S95atd
K02NetworkManager  K50snmpd       K89netplugd         S08mcstrans     S18rpcidmapd   S28autofs                  S97rhnsd
K05conman          K50snmptrapd   K89pand             S10network      S19rpcgssd     S55sshd                    S97rhsmcertd
K05saslauthd       K69rpcsvcgssd  K89rdisc            S11auditd       S22messagebus  S56cups                    S97yum-updatesd
K05wdaemon         K73ypbind      K99readahead_later  S12restorecond  S25bluetooth   S56rawdevices              S98avahi-daemon
K10psacct          K74ipmi        S00microcode_ctl    S12syslog       S25netfs       S57vmware-tools-thinprint  S99firstboot
K15httpd           K74nscd        S03vmware-tools     S13cpuspeed     S25pcscd       S80sendmail                S99local
K15svnserve        K74ntpd        S04readahead_early  S13irqbalance   S26acpid       S85gpm                     S99smartd
K20nfs             K85mdmpd       S05kudzu            S13iscsi        S26haldaemon   S90crond
K24irda            K87multipathd  S07iscsid           S13portmap      S26hidd        S90xfs

       wKiom1fTuE3jn7HjAADGphozqfA218.png

     注意:在2345级别的/etc/rc#.d目录下都会有一个rc.local,它其实也是一个链接文件,链接到/etc/rc.d/rc.local,它并不是启动文件,而是一个普通的文件,不过它的优先级最小,所以最后启动,如果你想要开机做一些什么操作,可以写到这个脚本里面。

[root@localhost rc3.d]# find /etc/ -name *local
/etc/rc.d/rc2.d/S99local
/etc/rc.d/rc5.d/S99local
/etc/rc.d/rc3.d/S99local
/etc/rc.d/rc4.d/S99local

 wKioL1fTuOCBMVgMAAA29uWUGEc613.png

   代码分析:以下为CentOS 5中的/etc/inittab文件 

id:3:initdefault:        //获取默认运行级别;3代表运行级别

# System initialization.
si::sysinit:/etc/rc.d/rc.sysinit    //执行/etc/rc.d/rc.sysinit系统初始化脚本
// 根据前面获取到的默认运行级别,执行/etc/rc.d/rc脚本文件
l0:0:wait:/etc/rc.d/rc 0
l1:1:wait:/etc/rc.d/rc 1
l2:2:wait:/etc/rc.d/rc 2
l3:3:wait:/etc/rc.d/rc 3
l4:4:wait:/etc/rc.d/rc 4
l5:5:wait:/etc/rc.d/rc 5
l6:6:wait:/etc/rc.d/rc 6
//下面为/etc/rc.d/rc脚本中的一段代码
# First, run the KILL scripts.        
for i in /etc/rc$runlevel.d/K* ; do        //这是一个for循环,根据前面获取的默认级别信息,来关闭/etc/rc#.d/下的服务
        check_runlevel "$i" || continue

        # Check if the subsystem is already up.
        subsys=${i#/etc/rc$runlevel.d/K??}
        [ -f /var/lock/subsys/$subsys -o -f /var/lock/subsys/$subsys.init ] \
                || continue

        # Bring the subsystem down.
        if LC_ALL=C egrep -q "^..*init.d/functions" $i ; then
                $i stop        //stop  关闭服务!
        else
                action $"Stopping $subsys: " $i stop
        fi
done

# Now run the START scripts.
for i in /etc/rc$runlevel.d/S* ; do    //这也是个for循环,与上面相反,是启动/etc/rc#.d/下面对应的脚本文件
        check_runlevel "$i" || continue

        # Check if the subsystem is already up.
        subsys=${i#/etc/rc$runlevel.d/S??}
        [ -f /var/lock/subsys/$subsys -o -f /var/lock/subsys/$subsys.init ] \
                && continue

        # If we're in confirmation mode, get user confirmation
        if [ -f /var/run/confirm ]; then
                confirm $subsys
                test $? = 1 && continue
        fi

        update_boot_stage "$subsys"
        # Bring the subsystem up.
        if [ "$subsys" = "halt" -o "$subsys" = "reboot" ]; then
                export LC_ALL=C
                exec $i start    //启动服务
        fi
        if LC_ALL=C egrep -q "^..*init.d/functions" $i \
                        || [ "$subsys" = "single" -o "$subsys" = "local" ]; then
                $i start        //启动服务
        else
                action $"Starting $subsys: " $i start
        fi
done

      下图为系统启动时服务开启的界面wKiom1fTotnyQ5d3AAAo8kiQZsQ648.png

  第六步:启动终端

     根据前面获取的默认运行级别来启动终端,如果运行级别为5,则启动图形界面

  第七步:用户登录

     系统启动流程结束!

问题总结:

 在此之前,一直有几点问题困惑着我,我对它们做了一下总结

   1)内核文件在磁盘上,系统还没有启动,系统还没有启动,/目录也没有挂载,前面说先找到boot分区,但是boot分区也是在/的目录下,/还没有,去哪找boot!?

   问题解答:注意,此时系统去寻找boot分区下的grub菜单、内核文件及rootfs的驱动并不是通过/目录来寻找,因为此时的/还没有挂载,无法找到/下面的boot目录,而是直接去boot的那个磁盘分区去寻找所需要的文件,具体看一下代码

[root@localhost ~]# cat /boot/grub/grub.conf //grub的配置文件
....省略.....
title CentOS 6 (2.6.32-642.el6.x86_64)
    root (hd0,0)    //hd0,0表示的是磁盘分区,即stage2所在的分区,此处的意义表示的是将hd0,0先设为/目录,那么下面
    的 kernel和initrd后面的根"/",表示的也是hd0,0,也就是stage2所在的磁盘分区。避免了通过/目录来寻找
    boot分区的难题.
    //此时的/vmlinuz-2.6.32....其实就是boot分区下的vmlinuz文件
    kernel /vmlinuz-2.6.32-642.el6.x86_64 ro root=/dev/mapper/vg0-root rd_NO_LUKS rd_NO_DM LANG=en_US.UTF-8 rd_LVM_LV=vg0/swap rd_NO_MD SYSFONT=latarcyrheb-sun16 crashkernel=auto rd_LVM_LV=vg0/root  KEYBOARDTYPE=pc KEYTABLE=us rhgb crashkernel=auto quiet rhgb quiet
    //同样的 /initramfs-2.6.32...其实也是boot分区下的initfamfs文件,只不过直接通过磁盘分区去找到的
    initrd /initramfs-2.6.32-642.el6.x86_64.img
[root@localhost ~]#

   2)上面问题的继续,即使你先加载boot分区,boot分区系统的系统驱动在哪里呢

   问题解答:从以下代码得知,分区信息是从1柱面开始的,那么0柱面被狗吃了么?答案是没有被狗吃,MBR存放在了0柱面,0磁道的第一个扇区内,但是它只占据了512个字节,因为0柱面包括了好多扇区,后面的扇区就是为了存放/boot分区的文件系统驱动的。stage1->stage1.5->stage2这个过程就是为了挂载/boot分区,而其中的stage1.5就是寻找/boot分区的文件系统驱动的。

[root@localhost ~]# df    //df查看/目录的挂在分区
Filesystem           1K-blocks      Used Available Use% Mounted on
/dev/sda2              7858552   5884648   1568264  79% /
/dev/sda1               295561     16787    263514   6% /boot
tmpfs                   511932         0    511932   0% /dev/shm
[root@localhost ~]# fdisk -l    //查看磁盘分区信息

Disk /dev/sda: 10.7 GB, 10737418240 bytes
255 heads, 63 sectors/track, 1305 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *           1          38      305203+  83  Linux    //注意到,起始柱面是从1柱面开始的,根据此前所学内容,第一个柱面是0柱面
/dev/sda2              39        1048     8112825   83  Linux
/dev/sda3            1049        1305     2064352+  82  Linux swap / Solaris

   3)加载内核后,为避免bug或者人为操作失误,rootfs先以只读方式挂载,只读方式挂载怎么写数据呢?

   问题解答:内核在读取到init程序后,其中有一个系统初始化脚本,即/etc/rc.d/rc.sysinit脚本,其中有一段代码如下,在这rootfs会被重新以读写方式挂载。

remount_needed() {
  local state oldifs
  [ "$READONLY" = "yes" ] && return 1
  state=`LC_ALL=C awk '/ \/ / && ($3 !~ /rootfs/) { print $4 }' /proc/mounts`
  oldifs=$IFS
  IFS=","
  for opt in $state ; do
        if [ "$opt" = "rw" ]; then
                IFS=$oldifs
                return 1
        fi
  done
  IFS=$oldifs
  return 0
}

# Remount the root filesystem read-write.
update_boot_stage RCmountfs
if remount_needed ; then    //根据前面定义的函数,来实现rootfs的读写挂载 mount -n -o remount,rw /
  action $"Remounting root filesystem in read-write mode: " mount -n -o remount,rw /
fi

    

流程图:

   俗话说的好,一图抵千言,我将上面所述的启动流程又画了一幅图,希望以更加清晰地描述CentOS的启动流程。

wKiom1fTj5zC5WgFAAOOI4WkZ3g804.png

   

原创文章,作者:我的滑板鞋,如若转载,请注明出处:http://www.178linux.com/45982

评论列表(1条)

  • 马哥教育
    马哥教育 2016-09-20 10:42

    对于开机启动流程,文章总结的很棒哦,读完之后,能让人学到很多知识呢。

联系我们

400-080-6560

在线咨询

工作时间:周一至周五,9:30-18:30,节假日同时也值班

QR code