前言:

    作为一个Linux用户,每天都在Linux系统中进行各种各样的操作,或工作、或学习、或娱乐,那么Linux系统的启动流程是怎样的呢?

    本文将就Linux系统的启动做一个详细的说明,也为以后的系统裁剪工作打下坚实的基础。

开始:

    先上图

学习:Linux系统裁剪前传之系统启动流程(一)_启动流程

上图就是Linux系统的一个启动流程图,接下来会对各个启动流程进行说明。

第一回合:开机自检(BIOS)

计算机在接通电源之后首先会由BIOS进行POST自检,然后根据BIOS内设置的引导顺序从硬盘、软盘或者CDROM中读入“引导块”。在 PC 中,引导 Linux 是从 BIOS 中的地址 0xFFFF0 处开始的。BIOS 的第一个步骤是加电自检(POST)。POST 的工作是对硬件进行检测。BIOS 的第二个步骤是进行本地设备的枚举和初始化。给定 BIOS 功能的不同用法之后,BIOS 由两部分组成:POST 代码和运行时服务。当 POST 完成之后,它被从内存中清理了出来,但是 BIOS 运行时服务依然保留在内存中,目标操作系统可以使用这些服务。

第二回合:MBR引导

    自检通过后,我们的系统通常会从硬盘引导的,其中主引导分区,也就是我们的MBR中包含主引导加载程序。MBR位于磁盘的第一个扇区中(0磁道0柱面1扇区),大小为521字节,其中前446字节为引导程序,中间64字节为分区表,最后2个字节为硬盘有效标志。

第三回合:GRUB引导

    前面我们说过,MBR中包含主引导加载程序,也就是Boot Loader,在Linux系统中,有GRUB和LILO两种引导加载程序,由于LILO没有交互式命令界面,也不支持网络引导,所以现在已经逐渐的被淘汰,取而代之的就是功能强大的GRUB引导加载程序。

    GRUB分为三个阶段

    第一阶段:stage 1:也就是Boot Loader,位于MBR中,作用为引导stage2

    第二阶段:stage1.5:位于boot分区,作用:识别内核文件系提供的文件系统、识别扩展

    第三阶段:stage2:也位于boot分区,作用:Grub的引导程序

在/boot/grub目录下看以看到这些stage

[root@myb362 grub]# ls /boot/grub/
device.map     grub.conf         minix_stage1_5     stage2
e2fs_stage1_5  iso9660_stage1_5  reiserfs_stage1_5  ufs2_stage1_5
fat_stage1_5   jfs_stage1_5      splash.xpm.gz      vstafs_stage1_5
ffs_stage1_5   menu.lst          stage1             xfs_stage1_5

GRUB引导加载程序是由一个配置文件控制的,如果这个配置文件中没有任何信息或者配置错误的话,开机之后就会自动转到GRUB命令行界面。

grub.conf文件

# 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/mapper/vg_myb362-lv_root
#          initrd /initrd-[generic-]version.img
#boot=/dev/sda
default=0 #0表示默认启动第一个title
timeout=5 #默认5秒自动进入操作系统
splashp_w_picpath=(hd0,0)/grub/splash.xpm.gz#设置GRUB背景图片
hiddenmenu#表示隐藏GRUB的启动菜单,可选项
title CentOS (2.6.32-358.el6.x86_64)#定义一个操作系统以及名字
root (hd0,0)#指定相应的Linux所有的/boot分区(hd0,0)表示第一块硬盘的第一个分区
kernel /vmlinuz-2.6.32-358.el6.x86_64 ro root=/dev/mapper/vg_myb362-lv_root rd_NO_LUKS LANG=en_US.UTF-8 rd_NO_MD SYSFONT=latarcyrheb-sun16 crashkernel=auto rd_NO_DM rd_LVM_LV=vg_myb362/lv_swap  KEYBOARDTYPE=pc KEYTABLE=us rd_LVM_LV=vg_myb362/lv_root rhgb quiet #这么一长串指定的是kernel的绝对路径和一些启动选项
initrd /initramfs-2.6.32-358.el6.x86_64.img#指定initrd文件的路径

第四回合:加载内核kernel

    加载内核kernel实际上将内核映像加载到内存中,内核映像通常是一个zImage(压缩映像,小于512KB)或者一个bzImage(比较大的压缩映像,大于512KB),是之前就通过zlib进行压缩过的。其实在内核映像之前还有一个例程,它实现了少量硬件设置,并对内核映像中所包含的内核进行解压,然后将其放入高端内存中。然后kernel就要以读写的方式挂载根文件系统(chroot切换),那就有一个问题了,要想挂载根文件系统rootfs,那么就需要装载根文件系统模块,然而这个模块在/lib/modules/`uname -r`下,那么问题就出现了,根没有挂载上,我怎么能找到这个模块呢?这就需要用到initrd了,initrd是安装在内存中一个临时的根,从这个临时根中我们就可以获取并装载这个模块,接着使用chroot切换到我们的真实根下,就实现了根文件系统的挂载。那么简单总结一下kernel的工作流程就是

    探测硬件-->加载驱动(initrd)-->挂载根文件系统-->rootfs(/sbin/init)

注释:initrd是RHEL5上的,在RHEL6上是initramfs,initrd的工作流程是系统加载驱动时将内存模拟成磁盘设备,需要再次在内存中缓存,而initramfs则是直接将内存模拟成文件系统,直接使用内存的速度肯定要比使用硬盘的速度快的多,所以在最新的RHEL6上是使用了initramfs。

initrd和initramfs文件都是可以在/boot目录下找到的,文件名为

RHEL5:/boot/initrd-`uname -r`.img

RHEL6:/boot/initramfs-`uname -r`.img

我们展开一下RHEL6下的initramfs来看一下

[root@myb362 initramfs]# mkdir ~/initramfs #创建一个目录用来存放文件
[root@myb362 initramfs]# cp /boot/initramfs-2.6.32-431.11.2.el6.x86_64.img ~/initramfs/ #复制到该目录
[root@myb362 initramfs]# cd ~/initramfs/#切换到该目录
[root@myb362 initramfs]# zcat initramfs-2.6.32-431.11.2.el6.x86_64.img | cpio -id#展开initramfs文件
91602 blocks
[root@myb362 initramfs]# ls#使用ls查看里面的文件
bin                     initqueue-settled                         proc
cmdline                 initqueue-timeout                         sbin
dev                     initramfs-2.6.32-431.11.2.el6.x86_64.img  sys
dracut-004-336.el6_5.2  lib                                       sysroot
emergency               lib64                                     tmp
etc                     mount                                     usr
init                    pre-pivot                                 var
initqueue               pre-trigger
initqueue-finished      pre-udev

可以看到很多熟悉的目录bin、lib、sbin、lib64、sys、sysroot、proc......这就是initramfs\initrd可以为我们装载模块并且切换根的原因。

当然,这里还有一个init脚本文件,这个init脚本文件中与/sbin/init可不是相同的。打开来看之后,你会发现他就是进行了一些操作之后切换到真实根,具体在切换根之前做了哪些操作,可以打开这个脚本简单看一下。

接下来就可以交给init来工作了。

第五回合:启动init

    init进程是系统所有进程的起点,内核在完成核内引导之后,即在本线程(进程)空间内加载init程序,init的进程号是1.init进程是所有进程的发起者和控制者。因为在任何基于Unix的系统(比如Linux)中,它都是第一个运行的进程,所有init进程的编号(Porcess ID ,PID)永远是1,。如果init出现了问题,那么系统的其余部分也就随之而垮掉了。

    init进程有两个作用。第一个作用是扮演终结父进程的角色。因为init进程永远不会被终止,所以系统总是可以确信它的存在,并在必要的时候以它为参照。如果某个进程在它衍生出来的全部子进程结束之前被终止,就会出现必须以init为参照物的情况。此时那些失去了父进程的子进程的子进程就都会以init作为他们的父进程。

    init的第二个作用是在进入某个特定的运行级别(Runlevel)时运行相应的程序,以此对各种运行级别进行管理。它的这个作用是由/etc/inittab文件定义的。也就是下边会讲到的。

第六回合:通过/etc/inittab文件进行初始化

    init的工作是根据/etc/inittab来执行相应的脚本进行系统初始化,比如设置键盘、字体、装载模块、设置网络等等。

    说明:在RHEL5中,是根据/etc/inittab来执行相应的脚本,但是在RHEL6中,是根据/etc/init/*.conf文件来执行相应的脚本的。

    为了更好的演示,那我们来看一下RHEL5中的/etc/inittab文件

# inittab        This file describes how the INIT process should set up
#                the system in a certain run-level.
#
# Author:        Miquel van Smoorenburg, <miquels@drinkel.nl.mugnet.org>
#                Modified for RHS Linux by Marc Ewing and Donnie Barnes
#
# Default runlevel. The runlevels used by RHS 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:3:initdefault:
# System initialization.
si::sysinit:/etc/rc.d/rc.sysinit
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
# Things to run in every runlevel
ud::once:/sbin/update
# Trap CTRL-ALT-DELETE
ca::ctrlaltdel:/sbin/shutdown -t3 -r now
# When our UPS tells us power has failed, assume we have a few minutes
# of power left.   Schedule a shutdown for 2 minutes from now.
# This does, of course, assume you have powerd installed and your
# UPS connected and working correctly.
pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"
# If power was restored before the shutdown kicked in, cancel it.
pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"
# If power was restored before the shutdown kicked in, cancel it.
pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"
# Run gettys in standard runlevels
1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6
# Run xdm in runlevel 5
x:5:respawn:/etc/X11/prefdm -nodaemon

配置文件很长,我们拆开来说:

    系统运行级别

#这是运行级别,一共7个级别,0-6
# Default runlevel. The runlevels used by RHS 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)#多用户模式,无NFS功能
#    3 - Full multiuser mode#字符界面
#    4 - unused#未使用
#    5 - X11#图形界面
#    6 - reboot (Do NOT set initdefault to this)#重启
#

    系统默认运行级别

id:3:initdefault:

    初始化系统脚本,后边会继续讲

si::sysinit:/etc/rc.d/rc.sysinit

  定义不同级别下启动的服务

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

  定义ALT+CTRL+DEL组合键的动作,很熟悉有没有。

ca::ctrlaltdel:/sbin/shutdown -t3 -r now

  定义电源选项:

# UPS connected and working correctly.
pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"
# If power was restored before the shutdown kicked in, cancel it.
pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"
# If power was restored before the shutdown kicked in, cancel it.
pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"

  定义虚拟终端个数:

# Run gettys in standard runlevels
1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6

好了,这就是分割之后这些脚本的功能,那么你可能也发现了这个脚本的语法格式是

[选项]:[runlevel]:[动作]:[操作]

这里其他的没什么好说了,就先来说说这些动作的含义

    initdefault:设置默认运行级别,无需定义操作

    sysinit:代表系统初始化操作选项

    ctrlaltdel:定义组合键Ctrl+Alt+Del被按下时的动作

    wait:等待系统切换到此级别时运行一次

    respawn:当指定操作进程被关闭时立即再启动一次

好了,还记得刚才看到的/etc/inittab文件中的初始化脚本那一行吗?接下来就来说说rc.sysinit。

第七回合:执行/etc/rc.d/rc.sysinit初始化脚本

这个初始化脚本主要完成这些任务:

 激活selinux和udev
 根据/etc/sysctl.conf文件设置内核参数
 设置系统时钟
 装载键映射
 启用交换分区
 设置主机名
 根文件系统检测并重新挂载其为读写;
 激活RAID和LVM;
 检查和挂载其它文件系统;/etc/fstab中定义;

 装入模块/etc/rc.d/rcX.d/[K/S]
 清理操作;

由于这个脚本实在太大,这里不便贴出,就只列出这个脚本执行的任务。

初始化脚本时会根据当前运行级别装入不同的模块,模块在/etc/rc.d/rcX.d/目录下,如果是运行3级别,则装载/etc/rc.d/rcX.d/目录下的[K/S]模块

我们随便打开/etc/rc.d/rc3.d目录看下

学习:Linux系统裁剪前传之系统启动流程(一)_initrd_02

可以看到,这里的都是链接文件,而链接的这些文件则都是一些程序脚本。

那开头S25*或者K89*又是什么意思呢?

    S:表示系统在此级别下运行时,启动的服务

    K:表示系统在此级别下运行时,关闭的服务

    数字:表示服务的优先级,取值范围是0-99,数字越小,优先级越高。

而且,这些文件的命名方式都是由这些程序脚本决定的。我们随便打开一个rsyslog的服务脚本看一下

[root@myb362 init]# cat /etc/init.d/rsyslog
#!/bin/bash
#
# rsyslog        Startup script for rsyslog.#服务脚本介绍
#
# chkconfig: 2345 12 88#不同级别下的启动与否
# description: Syslog is the facility by which many daemons use to log \#脚本描述

就看这一行好了

# chkconfig: 2345 12 88

2345:表示运行级别:2、3、4、5

12:表示启动优先级

88:表示关闭优先级

那么这一行就意味着:在2、3、4、5级别下,rsyslog会启动,并且优先级为12,在0,1,6级别下关闭,并且优先级为88

我们可以在这改配置文件来决定程序在不同级别下是否启动和优先级,也可以通过命令来改变程序的启动与否

checkconfig
命令格式:
chkconfig [options] Service_Name [on|off]
Options:--add #添加程序
--list #列出当前系统上有的程序在不同级别下的启动与否
--del #删除某个服务(只是删除链接文件,不删除原文件)
--level [on|off] #设置程序在某级别下的启动与否(on或off)

列出rsyslog程序的启动级别

[root@myb362 init]# chkconfig --list rsyslog
rsyslog         0:off   1:off   2:on    3:on    4:on    5:on    6:off

改变rsyslog程序的启动级别

[root@myb362 init]# chkconfig --level 24 rsyslog off
[root@myb362 init]# chkconfig --list rsyslog
rsyslog         0:off   1:off   2:off   3:on    4:off   5:on    6:off

第八回合:执行/sbin/mingetty,启动Login Shell,这个就不说了,就是系统成功启动后出来的那个提示框。

总结:Linux系统启动流程大概就是这样,经过这样一写,对系统又有了一些比较深的理解。每天进步一点,总有成功那天。明天就要做系统裁剪了,欢迎大家关注!