理解linux系统的开机流程,有助于我们解决开机过程中遇到的各种问题。 例如配置操作系统多重引导,忘记root密码的解决,设置系统开机默认运行模式,解决因为/etc/fstab设置错误而无法顺利挂载根目录问题。

Linux 的开机流程分析

回到顶部

linux的启动是一个非常复杂的过程,本章不涉及linux内核的启动过程。
按下电源按键后计算机硬件会主动读取BIOS或UEFI BIOS来加载硬件信息及进行硬件系统的自我测试, 之后系统会主动的去读取第一个可开机的设备(由BIOS设定的), 此时就可以读入开机管理程序(Boot Loader)了。
目前大部分主流Linux distributions默认的bootloader为grub2,早期Linux默认使用grub1或LILO。 bootloader用来引导启动linux内核,在多操作系统环境下,bootloader可以选择启动用户指定的操作系统内核文件来开机, 并实际加载内核到内存当中解压缩与执行, 这样内核就能够开始在内存内活动并检测所有硬件信息与加载适当的驱动程序来使整部主机开始运作。 linux内核启动完成后,就会加载外部程序开始准备软件执行的环境,并且实际的加载所有系统运作所需要的软件程序哩! 最后系统就会开始等待你的登入与操作啦!简单来说,系统开机的经过可以汇整成底下的流程的:

1. 加载BIOS的硬件信息与进行自我测试,并依据设定取得第一个可启动的设备;
2. 读取并执行第一个启动设备内MBR的boot Loader,也就是grub2;
3. 依据boot loader的设定加载linux Kernel ,Kernel开始执行一系列初始化,侦测硬件与加载驱动程序;
4. 内核初始化完成后,会加载第一个用户程序systemd,并以default.target流程开机;
o systemd 执行 sysinit.target 初始化系统及 basic.target 准备操作系统;
o systemd 启动 multi-user.target 下的本机与服务器服务;
o systemd 执行 multi-user.target 下的 /etc/rc.d/rc.local 文件;
o systemd 执行 multi-user.target 下的 getty.target 及登入服务;
o systemd 执行 graphical 需要的服务

BIOS, boot loader 与 kernel 载入

回到顶部

BIOS: 不论传统BIOS还是UEFIBIOS都会被简称为BIOS;
MBR: 虽然分区表有传统MBR以及新式GPT,不过GPT也有保留一块兼容MBR的区块,下面在提到安装boot loader的部份,还是会简称为MBR! 总之,MBR就代表该磁盘的最前面可安装boot loader的那个区块!

BIOS, 开机自我测试与 MBR/GPT

回到顶部

在个人计算机架构下,想要启动整部系统,系统首先加载BIOS(Basic Input Output System), BIOS程序会加载CMOS信息,根据CMOS内的设置值获取主机的各项硬件配置,例如CPU与接口设备的通信频率、 启动设备的启动顺序、硬盘大小与类型、 系统时间、各外设总线是否启动Plug and Play(PnP, 即插即用设备)、 各接口设备的I/O地址、以及与CPU沟通的 IRQ 中断等等的信息。
获取到这些信息后,BIOS还会进行开机自我测试(Power-on Self Test, POST)。 然后开始执行硬件侦测的初始化,并设定PnP设备,之后再定义出可启动的设备顺序,接下来就会开始进行开机 设备的数据读取了。
由于我们的系统软件大多存放在硬盘中! 所以BIOS会指定启动的设备好让我们可以读取磁盘中的操作系统内核文件。 但由于不同的操作系统他的文件系统格式不相同,因此我们必须要以一个开机管理程序来处理内核文件加载 (load) 的问题, 因此这个开机管理程序就被称为 Boot Loader 了。 那这个 Boot Loader 程序安装在哪里呢?就在启动设备的第一个扇区 (sector) 内,也就是我们一直谈 到的 MBR (Master Boot Record, 主要启动记录区)。
那你会不会觉得很奇怪啊?既然内核文件需要 loader 来读取,那每个操作系统的 loader 都不相同, 这样的话 BIOS 又是如何读取 MBR 内的 loader 呢?很有趣的问题吧! 其实 BIOS 是透过硬件的INT 13 中断功能来读取 MBR 的,也就是说,只要 BIOS 能够侦测的到你的磁盘 (不论该磁盘是SATA 还是 SAS 接口), 那他就有办法透过 INT 13 这条信道来读取该磁盘的第一个扇区内的 MBR软件啦!这样 boot loader 也就能够被执行了!
我们知道每颗硬盘的最前面区块含有MBR或GPT分区表的提供loader的区块,那么如果我的主机上面有两颗硬盘的话, 系统会去哪颗硬盘的最前面区块读取boot loader呢?这个就得要看BIOS的设定了。
基本上,我们常常讲的『系统的 MBR』其实指的是 第一个启动设备的 MBR! 所以,改天如果你要将开机管理程序安装到某颗硬盘的 MBR 时, 要特别注意当时系统的『第一个启动设备』是哪个,否则会安 装到错误的硬盘上面的 MBR 喔!重要重要!

Boot Loader 的功能

回到顶部

刚刚说到 Loader 的最主要功能是要认识操作系统的文件格式并据以加载内核到主存储器中去执行。 由于不同操作系统的文件格式不一致,因此每种操作系统都有自己的 boot loader 啦!用自己的 loader 才有办法载入内核文件嘛!那问题就来啦,你应该有听说过多重操作系统吧?也就是在一部主机上面 安装多种不同的操作系统。 既然你
(1)必须要使用自己的 loader 才能够加载属于自己的操作系统内核,而
(2)系统的 MBR 只有一个,那你怎么会有办法同时在一部主机上面安装 Windows 与 Linux 呢?
这就得要回到第七章的磁盘文件系统去回忆一下文件系统功能了。 其实每个文件系统 (filesystem, 或者是 partition) 都会保留一块启动扇区 (boot sector) 提供操作系统安装 boot loader , 而通常操作 系统默认都会安装一份 loader 到他根目录所在的文件系统的 boot sector 上。如果我们在一部主机上 面安装 Windows 与 Linux 后,该 boot sector, boot loader 与 MBR 的相关性会有点像下图:
图 19.1.1、boot loader 安装在 MBR, boot sector 与操作系统的关系
如上图所示,每个操作系统默认是会安装一套 boot loader 到他自己的文件系统中 (就是每个 filesystem 左下角的方框),而在 Linux 系统安装时,你可以选择将 boot loader 安装到 MBR 去,也 可以选择不安装。 如果选择安装到 MBR 的话,那理论上你在 MBR 与 boot sector 都会保有一份 boot loader 程序的。 至于 Windows 安装时,他预设会主动的将 MBR 与 boot sector 都装上一份 boot loader!所以啦, 你会发现安装多重操作系统时,你的 MBR 常常会被不同的操作系统的 boot loader 所覆盖啦! ^_^
我们刚刚提到的两个问题还是没有解决啊!虽然各个操作系统都可以安装一份 boot loader 到他们的 boot sector 中, 这样操作系统可以透过自己的 boot loader 来加载内核了。问题是系统的 MBR 只 有一个哩! 你要怎么执行 boot sector 里面的 loader 啊?这个我们得要回忆一下第二章约略提过的 boot loader 的功能了。boot loader 主要的功能如下:
提供选单:用户可以选择不同的开机项目,这也是多重引导的重要功能!
载入内核文件:直接指向可开机的程序区段来开始操作系统;
转交其他 loader:将开机管理功能转交给其他 loader 负责。
由于具有选单功能,因此我们可以选择不同的内核来开机。而由于具有控制权转交的功能,因此我们 可以加载其他 boot sector 内的 loader 啦!不过 Windows 的 loader 预设不具有控制权转交的功能, 因此你不能使用 Windows 的 loader 来加载 Linux 的 loader 喔!这也是为啥第二章谈到 MBR 与 多重引导时,会特别强调先装 Windows 再装 Linux 的缘故。 我们将上述的三个功能以底下的图标 来解释你就看的懂了!(与第二章的图示也非常类似啦!)图 19.1.2、开机管理程序的选单功能与控制权转交功能示意图 如上图所示,我的 MBR 使用 Linux 的 grub2 这个开机管理程序,并且里面假设已经有了三个选单, 第一个选单可以直接指向 Linux 的内核文件并且直接加载内核来开机;第二个选单可以将开机管理 程控权交给 Windows 来管理,此时 Windows 的 loader 会接管开机流程,这个时候他就能够启动 windows 了。第三个选单则是使用 Linux 在 boot sector 内的开机管理程序,此时就会跳出另一个 grub2 的选单啦!了解了吗?
选单一:MBR(grub2) -> kernel file -> booting
选单二:MBR(grub2) -> boot sector(Windows loader) -> Windows kernel -> booting
选单三:MBR(grub2) -> boot sector(grub2) -> kernel file -> booting
而最终 boot loader 的功能就是『加载 kernel 文件』啦!

加载内核侦测硬件与 initramfs 的功能

回到顶部

当我们藉由 boot loader 的管理而开始读取内核文件后,接下来, Linux 就会将内核解压缩到主存储 器当中, 并且利用内核的功能,开始测试与驱动各个周边设备,包括储存设备、CPU、网络卡、声 卡等等。 此时 Linux 内核会以自己的功能重新侦测一次硬件,而不一定会使用 BIOS 侦测到的硬件 信息喔!也就是说,内核此时才开始接管 BIOS 后的工作了。 那么内核文件在哪里啊?一般来说, 他会被放置到 /boot 里面,并且取名为 /boot/vmlinuz 才对!

            [root@initroot ~]# ls --format=single-column -F /boot
            config-3.10.0-229.el7.x86_64 #此版本内核被编译时选择的功能与模块配置文件
            grub/ #旧版 grub1 ,不需要理会这目录了!
            grub2/ #就是开机管理程序 grub2 相关数据目录
            initramfs-0-rescue-309eb890d3d95ec7a.img #底下几个为虚拟文件系统档!这一个是用来救援的!
            initramfs-3.10.0-229.el7.x86_64.img #正常开机会用到的虚拟文件系统
            initramfs-3.10.0-229.el7.x86_64kdump.img #内核出问题时会用到的虚拟文件系统
            System.map-3.10.0-229.el7.x86_64 #内核功能放置到内存地址的对应表
            vmlinuz-0-rescue-309eb890d09543d95ec7a* #救援用的内核文件
            vmlinuz-3.10.0-229.el7.x86_64* #就是内核文件啦!最重要者!
            
从上表中的特殊字体,我们也可以知道 CentOs 7.x 的 Linux 内核为 3.10.0-229.el7.x86_64 这个版本! 为了硬件开发商与其他内核功能开发者的便利, 因此 Linux 内核是可以透过动态加载内核模块的 (就请想成驱动程序即可),这些内核模块就放置在 /lib/modules/ 目录内。 由于模块放置到磁盘根目录内 (要记得 /lib 不可以与 / 分别放在不同的 partition !), 因此在开机的过程中内核必须要挂载 根目录,这样才能够读取内核模块提供加载驱动程序的功能。 而且为了担心影响到磁盘内的文件系 统,因此开机过程中根目录是以只读的方式来挂载的喔。
一般来说,非必要的功能且可以编译成为模块的内核功能,目前的 Linux distributions 都会将他编译 成为模块。 因此 USB, SATA, SCSI... 等磁盘设备的驱动程序通常都是以模块的方式来存在的。 现 在来思考一种情况,假设你的 linux 是安装在 SATA 磁盘上面的,你可以透过 BIOS 的 INT 13 取 得 boot loader 与 kernel 文件来开机,然后 kernel 会开始接管系统并且侦测硬件及尝试挂载根目录 来取得额外的驱动程序。
问题是,内核根本不认识 SATA 磁盘,所以需要加载 SATA 磁盘的驱动程序, 否则根本就无法挂 载根目录。但是 SATA 的驱动程序在 /lib/modules 内,你根本无法挂载根目录又怎么读取到 /lib/modules/ 内的驱动程序?是吧!非常的两难吧!在这个情况之下,你的 Linux 是无法顺利开机 的! 那怎办?没关系,我们可以透过虚拟文件系统来处理这个问题。
虚拟文件系统 (Initial RAM Disk 或 Initial RAM Filesystem) 一般使用的文件名为 /boot/initrd 或 /boot/initramfs ,这个文件的特色是,他也能够透过 boot loader 来加载到内存中,然后这个文件会被 解压缩并且在内存当中仿真成一个根目录, 且此仿真在内存当中的文件系统能够提供一支可执行的 程序,透过该程序来加载开机过程中所最需要的内核模块, 通常这些模块就是 USB, RAID, LVM, SCSI 等文件系统与磁盘接口的驱动程序啦!等载入完成后, 会帮助内核重新呼叫 systemd 来开始 后续的正常开机流程。
图 19.1.3、BIOS 与 boot loader 及内核加载流程示意图
如上图所示,boot loader 可以加载 kernel 与 initramfs ,然后在内存中让 initramfs 解压缩成为根目 录, kernel 就能够藉此加载适当的驱动程序,最终释放虚拟文件系统,并挂载实际的根目录文件系 统,就能够开始后续的正常开机流程。 更详细的 initramfs 说明,你可以自行使用 man initrd 去查 阅看看。 底下让我们来了解一下 CentOS 7.x 的 initramfs 文件内容有什么吧! ^_^
# 1. 先来直接看一下 initramfs 里面的内容有些啥数据?
            [root@initroot ~]# lsinitrd /boot/initramfs-3.10.0-229.el7.x86_64.img
            # 首先会呼叫出 initramfs 最前面文件头的许多数据介绍,这部份会占用一些容量!
            Image: /boot/initramfs-3.10.0-229.el7.x86_64.img: 18M
            ========================================================================
            Early CPIO image========================================================================
            drwxr-xr-x 3 root root 0 May 4 17:56 .
            -rw-r--r-- 1 root root 2 May 4 17:56 early_cpio
            drwxr-xr-x 3 root root 0 May 4 17:56 kernel
            drwxr-xr-x 3 root root 0 May 4 17:56 kernel/x86
            drwxr-xr-x 2 root root 0 May 4 17:56 kernel/x86/microcode
            -rw-r--r-- 1 root root 10240 May
            4 17:56 kernel/x86/microcode/GenuineIntel.bin
            ========================================================================
            Version: dracut-033-240.el7
            Arguments: -f
            dracut modules:
            # 开始一堆模块的加载行为
            bash
            nss-softokn
            .....(中间省略).....
            ========================================================================
            drwxr-xr-x 12 root root
            0 May 4 17:56 .
            crw-r--r-- 1 root root
            5, 1 May 4 17:56 dev/console
            crw-r--r-- 1 root root 1, 11 May 4 17:56 dev/kmsg
            crw-r--r-- 1 root root 1, 3 May 4 17:56 dev/null
            .....(中间省略).....
            lrwxrwxrwx
            1 root
            root 23 May
            4 17:56 init -> usr/lib/systemd/systemd
            .....(中间省略).....
            drwxr-xr-x 2 root root 0 May
            4 17:56 var/lib/lldpad
            lrwxrwxrwx 1 root root 11 May 4 17:56 var/lock -> ../run/lock
            lrwxrwxrwx 1 root root 10 May 4 17:56 var/log -> ../run/log
            lrwxrwxrwx 1 root root 6 May
            4 17:56 var/run -> ../run
            ========================================================================
            
# 最后则会列出这个 initramfs 里头的所有文件!也就是说,这个 initramfs 文件大概存着两部份,
# 先是档头宣告的许多文件部份,再来才是真的会被内核取用的全部附加的文件数据!
从上面我们大概知道了这个 initramfs 里头含有两大区块,一个是事先宣告的一些数据,包括 kernel/x86/microcode/GenuineIntel.bin 这些东西。 在这些数据后面,才是真的我们的内核会去读取的 重要文件~如果看一下文件的内容,你会发现到 init 那只程序已经被 systemd 所取代啰!这样理解 否? 好~如果你想要进一步将这个文件解开的话,那得要先将前面的 kernel/x86/microcode/GenuineIntel.bin 之前的文件先去除掉,这样才能够顺利的解开。 因此,得要这 样进行:
# 1. 先将 /boot 底下的文件进行去除前面不需要的文件头数据部份。
            [root@initroot ~]# mkdir /tmp/initramfs
            [root@initroot ~]# cd /tmp/initramfs
            [root@initroot initramfs]#  dd if=/boot/initramfs-3.10.0-229.el7.x86_64.img of=initramfs.gz bs=11264 skip=1
            [root@initroot initramfs]#  ll initramfs.gz; file initramfs.gz
            -rw-r--r--. 1 root root 18558166 Aug 24 19:38 initramfs.gz
            initramfs.gz: gzip compressed data, from Unix, last modified: Mon May
            4 17:56:47 2015,
            max compression
            
# 2. 从上面看到文件是 gzip 压缩文件,所以将它解压缩后,再查阅一下文件的类型!
            [root@initroot initramfs]#  gzip -d initramfs.gz
            [root@initroot initramfs]#  file initramfs
            initramfs: ASCII cpio archive (SVR4 with no CRC)
            
# 3. 解开后又产生一个 cpio 文件,得要将它用 cpio 的方法解开!加上不要绝对路径的参数较保险!
            [root@initroot initramfs]#  cpio -i -d -H newc --no-absolute-filenames < initramfs
            [root@initroot initramfs]#  ll
            lrwxrwxrwx. 1 root root
            7 Aug 24 19:40 bin -> usr/bin
            drwxr-xr-x. 2 root root 42 Aug 24 19:40 dev
            drwxr-xr-x. 12 root root 4096 Aug 24 19:40 etc
            lrwxrwxrwx. 1 root root
            23 Aug 24 19:40 init -> usr/lib/systemd/systemd
            -rw-r--r--. 1 root root 42263552 Aug 24 19:38 initramfs
            lrwxrwxrwx. 1 root root 7 Aug 24 19:40 lib -> usr/lib
            lrwxrwxrwx. 1 root root 9 Aug 24 19:40 lib64 -> usr/lib64
            drwxr-xr-x. 2 root root 6 Aug 24 19:40 proc
            drwxr-xr-x. 2 root root 6 Aug 24 19:40 root
            drwxr-xr-x. 2 root root 6 Aug 24 19:40 run
            lrwxrwxrwx. 1 root root 8 Aug 24 19:40 sbin -> usr/sbin
            -rwxr-xr-x. 1 root root drwxr-xr-x. 2 root root 6 Aug 24 19:40 sys
            drwxr-xr-x. 2 root root 6 Aug 24 19:40 sysroot
            drwxr-xr-x. 2 root root 6 Aug 24 19:40 tmp
            drwxr-xr-x. 7 root root 61 Aug 24 19:40 usr
            drwxr-xr-x. 3 root root 47 Aug 24 19:40 var
            3041 Aug 24 19:40 shutdown
            
# 看吧!上面几乎就像是一个小型的文件系统根目录耶!这样就能让 kernel 去挂载了!
# 4. 接下来瞧一瞧到底这个小型的文件系统中,systemd 是要以哪个 target 来执行开机呢?
            [root@initroot initramfs]#  ll usr/lib/systemd/system/default.target
            lrwxrwxrwx. 1 root root 13 Aug 24 19:40 usr/lib/systemd/system/default.target -> initrd.target
            
# 5. 最终,让我们瞧一瞧系统内默认的 initrd.target 相依的所有服务数据吧!
            [root@initroot initramfs]#  systemctl list-dependencies initrd.target
            initrd.target
            ├─dracut-cmdline.service
            .....(中间省略).....
            ├─basic.target
            │ ├─alsa-restore.service.....(中间省略).....
            │ ├─slices.target
            │ │ ├─-.slice
            │ │ └─system.slice
            │ ├─sockets.target
            │ │ ├─dbus.socket
            .....(中间省略).....
            │ │ └─systemd-udevd-kernel.socket
            │ ├─sysinit.target
            │ │ ├─dev-hugepages.mount
            .....(中间省略).....
            │ │ ├─local-fs.target
            │ │ │ ├─-.mount
            │ │ │ ├─boot.mount
            .....(中间省略).....
            │ │ └─swap.target
            │ │
            ├─dev-centos-swap.swap
            .....(中间省略).....
            │ │
            └─dev-mapper-centos\x2dswap.swap
            │ └─timers.target
            │
            └─systemd-tmpfiles-clean.timer
            ├─initrd-fs.target
            └─initrd-root-fs.target
            
# 依旧透过 systemd 的方式,一个一个的将所有的侦测与服务加载系统中!
透过上面解开 initramfs 的结果,你会知道其实 initramfs 就是一个小型的根目录,这个小型根目录 里面也是透过 systemd 来进行管理,同时观察 default.target 的链接,会发现其实这个小型系统就是 透过 initrd.target 来开机,而 initrd.target 也是需要读入一堆例如 basic.target, sysinit.target 等等的硬 件侦测、内核功能启用的流程, 然后开始让系统顺利运作。最终才又卸除 initramfs 的小型文件系 统,实际挂载系统的根目录!
此外,initramfs 并没有包山包海,它仅是带入开机过程会用到的内核模块而已。所以如果你在 initramfs 里面去找 modules 这个关键词的话, 就可以发现主要的内核模块大概就是 SCSI、virtio、 RAID 等等跟磁盘相关性比较高的模块就是了!现在由于磁盘大部分都是使用 SATA 这玩意儿, 并 没有 IDE 的格式啰!所以,没有 initramfs 的话,你的 Linux 几乎就是不能顺利开机的啦!除非你 将 SATA 的模块直接编译到内核去了! ^_^
在内核完整的加载后,您的主机应该就开始正确的运作了,接下来,就是要开始执行系统的第一支程 序: systemd !

第一支程序 systemd 及使用 default.target 进入开机程序分析

回到顶部

在内核加载完毕、进行完硬件侦测与驱动程序加载后,此时你的主机硬件应该已经准备就绪了 (ready) , 此时内核会主动的呼叫第一支程序,那就是 systemd 啰。
这也是为啥第十六章的 pstree 指令介绍时,你会发现 systemd 的 PID 号码是一号啦。 systemd 最主要的功能就是准备软件执行的 环境,包括系统的主机名、网络设定、语系处理、文件系统格式及其他服务的启动等。 而所有的动 作都会透过 systemd 的默认启动服务集合,亦即是 /etc/systemd/system/default.target 来规划。 另外, systemd 已经舍弃沿用多年的 system V 的 runlevel 了喔!

常见的操作环境 target 与兼容于 runlevel 的等级

回到顶部

可以作为预设的操作环境 (default.target) 的主要项目有: multi-user.target 以及 graphical.target 这两 个。当然还有某些比较特殊的操作环境, 包括在第十七章里面谈到的 rescue.target, emergency.target, shutdown.target 等等,以及本章在 initramfs 里面谈到的 initrd.target 啰!
但是过去的 systemV 使用的是一个称为 runlevel (执行等级) 的概念来启动系统的,systemd 为了兼 容于旧式的 systemV 操作行为, 所以也将 runlevel 与操作环境做个结合喔!你可以使用底下的方 式来查询两者间的对应:

            [root@initroot ~]# ll -d /usr/lib/systemd/system/runlevel*.target | cut -c 28-
            May 4 17:52 /usr/lib/systemd/system/runlevel0.target -> poweroff.target
            May 4 17:52 /usr/lib/systemd/system/runlevel1.target -> rescue.target
            May 4 17:52 /usr/lib/systemd/system/runlevel2.target -> multi-user.target
            May 4 17:52 /usr/lib/systemd/system/runlevel3.target -> multi-user.target
            May 4 17:52 /usr/lib/systemd/system/runlevel4.target -> multi-user.target
            May 4 17:52 /usr/lib/systemd/system/runlevel5.target -> graphical.target
            May 4 17:52 /usr/lib/systemd/system/runlevel6.target -> reboot.target
            
如果你之前已经使用过 systemV 的方式来管理系统的话,那应该会知道切换执行等级可以使用『 init 3 』转成文字界面,『 init 5 』转成图形界面吧? 这个 init 程序依旧是保留下来的,只是 init 3 会 相当于 systemctl isolate multi-user.target 就是了!如果做个完整的迭代,这两个东西的对应为: SystemV
systemd
init 0 systemctl poweroff
init 1 systemctl rescue
init [234] systemctl isolate multi-user.target
init 5 systemctl isolate graphical.target
init 6 systemctl reboot

systemd 的处理流程

回到顶部

如前所述,当我们取得了 /etc/systemd/system/default.target 这一个预设操作界面的设定之后,接下来 系统帮我们做了什么呢? 首先,它会链接到 /usr/lib/systemd/system/ 这个目录下去取得 multi-user.target 或 graphical.target 这两个其中的一 (当然, 说的是正常的进入 Linux 操作环境的情况下!), 假设我们是使用 graphical.target 好了,接着下来 systemd 会去找两个地方的设定, 就 是如下的目录:
/etc/systemd/system/graphical.target.wants/:使用者设定加载的 unit
/usr/lib/systemd/system/graphical.target.wants/:系统默认加载的 unit
然后再由 /usr/lib/systemd/system/graphical.target 这个配置文件内发现如下的资料:

            [root@initroot ~]# cat /usr/lib/systemd/system/graphical.target
            [Unit]
            Description=Graphical Interface
            Documentation=man:systemd.special(7)
            Requires=multi-user.target
            After=multi-user.target
            Conflicts=rescue.target
            Wants=display-manager.service
            AllowIsolate=yes
            [Install]
            Alias=default.target
            
这表示 graphical.target 必须要完成 multi-user.target 之后才能够进行,而进行完 graphical.target 之 后,还得要启动 display-manager.service 才行的意思。 好了!那么透过同样的方式,我们来找找 multi-user.target 要执行完毕得要加载的项目有哪些呢?
# 先来看看 multi-user.target 配置文件内规范了相依的操作环境有哪些呢?
            [root@initroot ~]# cat /usr/lib/systemd/system/multi-user.target
            [Unit]
            Description=Multi-User System
            Documentation=man:systemd.special(7)
            Requires=basic.target
            Conflicts=rescue.service rescue.target
            After=basic.target rescue.service rescue.target
            AllowIsolate=yes
            [Install]
            Alias=default.target
            
# 然后看看系统默认要加载的 unit 有哪些?
            [root@initroot ~]# ls /usr/lib/systemd/system/multi-user.target.wants
            brandbot.path plymouth-quit.service systemd-logind.service
            dbus.service plymouth-quit-wait.service systemd-user-sessions.service
            getty.target systemd-ask-password-wall.path# 使用者自定义要加载的 unit 又有哪些呢?
            [root@initroot ~]# ls /etc/systemd/system/multi-user.target.wants
            abrt-ccpp.service crond.service mdmonitor.service sshd.service
            abrtd.service hypervkvpd.service ModemManager.service sysstat.service
            abrt-oops.service hypervvssd.service NetworkManager.service tuned.service
            abrt-vmcore.service irqbalance.service postfix.service vmtoolsd.service
            abrt-xorg.service kdump.service remote-fs.target vsftpd2.service
            atd.service ksm.service rngd.service vsftpd.service
            auditd.service ksmtuned.service rsyslog.service backup2.timer 
            libstoragemgmt.service smartd.service backup.timer libvirtd.service sshd2.service
            
透过上面的结果,我们又能知道 multi-usre.target 需要在 basic.target 运作完毕才能够载入上述的许 多 unit 哩!然后再去 basic.target 里头找数据等等~ 最终这些数据就可以透过『 systemctl list-dependencies graphical.target 』这个指令来列出所有的相关性的服务啰!这就是 systemd 的呼叫 所需要的服务的流程喔!
要知道系统的服 务启用的流程,最简 单的方法就是『 systemctl list-dependencies Tips graphical.target 』这个指令!只是,如果你想要知道背后的配置文件意义, 那就是分别去找出 /etc 与 /usr/lib 底 下的 graphical.target.wants/ 目录下的数据就对了!当然,配置文件脚本里面的 Requires 这个设定值所代表的服务, 也是需要是先加载喔!
约略分析一下『 systemctl list-dependencies graphical.target 』所输出的相依属性服务,基本上我们 CentOS 7.x 的 systemd 开机流程大约是这样:
1. local-fs.target + swap.target:这两个 target 主要在挂载本机 /etc/fstab 里面所规范的文件系统与相关的内存 置换空间。
2. sysinit.target:这个 target 主要在侦测硬件,加载所需要的内核模块等动作。
3. basic.target:加载主要的外围硬件驱动程序与防火墙相关任务
4. multi-user.target 底下的其它一般系统或网络服务的加载
5. 图形界面相关服务如 gdm.service 等其他服务的加载
除了第一步骤 local-fs.target, swap.target 是透过 /etc/fstab 来进行挂载的行为之外,那其他的 target 有做啥动作呢?简单得来说说!

systemd 执行 sysinit.target 初始化系统、basic.target 准备系统

回到顶部

如果你自己使用『 systemctl list-dependencies sysinit.target 』来瞧瞧的话,那就会看到很多相依的服 务!这些服务你应该要一个一个去查询看看设定脚本的内容, 就能够大致理解每个服务的意义。基 本上,我们可以将这些服务归类成几个大项就是了:
特殊文件系统设备的挂载:包括 dev-hugepages.mount dev-mqueue.mount 等挂载服务,主要在挂载跟巨量内 存分页使用与消息队列的功能。 挂载成功后,会在 /dev 底下建立 /dev/hugepages/, /dev/mqueue/ 等目录;
特殊文件系统的启用:包括磁盘阵列、网络驱动器 (iscsi)、LVM 文件系统、文件系统对照服务 (multipath) 等等,也会在这里被侦测与使用到!
开机过程的讯息传递与动画执行:使用 plymouthd 服务搭配 plymouth 指令来传递动画与讯息 日志式登录文件的使用:就是 systemd-journald 这个服务的启用啊!
加载额外的内核模块:透过 /etc/modules-load.d/*.conf 文件的设定,让内核额外加载管理员所需要的内核模 块!
加载额外的内核参数设定:包括 /etc/sysctl.conf 以及 /etc/sysctl.d/*.conf 内部设定!
启动系统的随机数生成器:随机数生成器可以帮助系统进行一些密码加密演算的功能
设定终端机 (console) 字形
启动动态设备管理器:就是 udevd 这个家伙!用在动态对应实际设备存取与设备文件名对应的一个服务!
相当重要喔!也是在这里启动的!
不论你即将使用哪种操作环境来使用系统,这个 sysinit.target 几乎都是必要的工作!从上面你也可 以看的出来,基本的内核功能、文件系统、文件系统设备的驱动等等, 都在这个时刻处理完毕~所 以,这个 sysinit.target 的阶段是挺重要的喔!
执行完 sysinit.target 之后,再来则是 basic.target 这个项目了。 sysinit.target 在初始化系统,而这个 basic .target 则是一个最阳春的操作系统了! 这个 basic.target 的阶段主要启动的服务大概有这些:
加载 alsa 音效驱动程序:这个 alsa 是个音效相关的驱动程序,会让你的系统有音效产生啰;
载入 firewalld 防火墙:CentOS 7.x 以后使用 firewalld 取代 iptables 的防火墙设定,虽然最终都是使用 iptables 的架构, 不过在设定上面差很多喔!
加载 CPU 的微指令功能;
启动与设定 SELinux 的安全本文:如果由 disable 的状态改成 enable 的状态,或者是管理员设定强制重 新设定一次 SELinux 的安全本文, 也在这个阶段处理喔!
将目前的开机过程所产生的开机信息写入到 /var/log/dmesg 当中 由 /etc/sysconfig/modules/*.modules 及 /etc/rc.modules 加载管理员指定的模块!
加载 systemd 支持的 timer 功能;
在这个阶段完成之后,你的系统已经可以顺利的运作!就差一堆你需要的登入服务、网络服务、本机 认证服务等等的 service 类别啰!于是就可以进入下个服务启动的阶段了!

systemd 启动 multi-user.target 下的服务

回到顶部

在加载内核驱动硬件后,经过 sysinit.target 的初始化流程让系统可以存取之后,加上 basic.target 让 系统成为操作系统的基础, 之后就是服务器要顺利运作时,需要的各种主机服务以及提供服务器功能的网络服务的启动了。
这些服务的启动则大多是附挂在 multi-user.target 这个操作环境底下, 你 可以到 /etc/systemd/system/multi-user.target.wants/ 里头去瞧瞧预设要被启动的服务喔! 也就是说,一般来说服务的启动脚本设定都是放在底下的目录内:
/usr/lib/systemd/system (系统默认的服务启动脚本设定)
/etc/systemd/system (管理员自己开发与设定的脚本设定)
而用户针对主机的本地服务与服务器网络服务的各项 unit 若要 enable 的话,就是将它放到 /etc/systemd/system/multi-user.target.wants/ 这个目录底下做个链接~ 这样就可以在开机的时候去启 动他。这时回想一下,你在第十七章使用 systemctl enable/disable 时,系统的响应是什么呢?再次回 想一下:
# 将 vsftpd.service 先 disable 再 enable 看看输出的信息为何?

            [root@initroot ~]# systemctl disable vsftpd.service
            rm '/etc/systemd/system/multi-user.target.wants/vsftpd.service'
            [root@initroot ~]# systemctl enable vsftpd.service
            ln -s '/usr/lib/systemd/system/vsftpd.service' '/etc/systemd/system/multi-user.target.
            wants/vsftpd.service'
            
有没有发现亮点了?不是从 /etc/systemd/system/multi-user.target.wants/ 里面删除连结档,就是建立连 结档~这样说,理解吧? 你当然不需要手动作这些连结,而是使用 systemctl 来处理即可!另外, 这些程序除非在脚本设定里面原本就有规范服务的相依性, 这样才会有顺序的启动之外,大多数的 服务都是同时启动的!这就是 systemd 的多任务啰。

相容 systemV 的 rc-local.service

回到顶部

另外,过去用过 Linux 的朋友大概都知道,当系统完成开机后,还想要让系统额外执行某些程序的 话,可以将该程序指令或脚本的绝对路径名称写入到 /etc/rc.d/rc.local 这个文件去!新的 systemd 机 制中,它建议直接写一个 systemd 的启动脚本配置文件到 /etc/systemd/system 底下,然后使用 systemctl enable 的方式来设定启用它,而不要直接使用 rc.local 这个文件啦!
但是像这种老人家就是喜欢将开机后要立刻执行的许多管理员自己的脚本,将它写入到 /etc/rc.d/rc.local 去嘛!那新版的 systemd 有没有支援呢? 当然有!那就是 rc-local.service 这个服务 的功能了!这个服务不需要启动,它会自己判断 /etc/rc.d/rc.local 是否具有可执行的权限来判断要不 要启动这个服务! 你可以这样检查看看:
# 1. 先看一下 /etc/rc.d/rc.local 的权限,然后检查 multi-user.target 有没有这个服务

            [root@initroot ~]# ll /etc/rc.d/rc.local
            -rw-r--r--. 1 root root 473 Mar
            6 13:48 /etc/rc.d/rc.local
            [root@initroot ~]# systemctl status rc-local.service
            rc-local.service - /etc/rc.d/rc.local Compatibility
            Loaded: loaded (/usr/lib/systemd/system/rc-local.service; static)Active: inactive (dead)
            [root@initroot ~]# systemctl list-dependencies multi-user.target | grep rc-local
            # 明明就有这个服务,但是 rc.local 不具有可执行 (x) 的权限,因此这个服务不会被执行
            
# 2. 加入可执行权限后,再看一下 rc-local 是否可被启用!
            [root@initroot ~]# chmod a+x /etc/rc.d/rc.local; ll /etc/rc.d/rc.local
            -rwxr-xr-x. 1 root root 473 Mar
            6 13:48 /etc/rc.d/rc.local
            [root@initroot ~]# systemctl daemon-reload
            [root@initroot ~]# systemctl list-dependencies multi-user.target | grep rc-local
            ├─rc-local.service
            
# 这个服务确实被记录到启动的环境下啰!
透过这个 chmod a+x /etc/rc.d/rc.local 的步骤,你的许多脚本就可以放在 /etc/rc.d/rc.local 这个文件内, 系统在每次开机都会去执行这文件内的指令喔!非常简单吧!

提供 tty 界面与登入的服务

回到顶部

在 multi-user.target 底下还有个 getty.target 的操作界面项目喔! 这个项目就是我们在第十七章用来 举例的 tty 终端机界面的个数案例。 能不能提供适当的登入服务也是 multi-user.target 底下的内容! 包括 systemd-logind.service, systemd-user-sessions.service 等服务。
比较有趣的地方是,由于服务都是同步运作,不一定哪个服务先启动完毕。如果 getty 服务先启动完 毕时,你会发现到有可用的终端机尝试让你登入系统了。 问题是,如果 systemd-logind.service 或 systemd-user-sessions.service 服务尚未执行完毕的话,那么你还是无法登入系统的。
Tips
有些比较急性子的伙伴在启动 CentOS 7.x 时,看到屏幕出现 tty1 可以让他登入了~ 但是一开始输入正确的帐密却无法登入系统! 总要隔了数十秒之后才能够顺利的登入!知道原因了吗? ^_^

systemd 启动 graphical.target 底下的服务

回到顶部

如果你的 default.target 是 multi-user.target 的话,那么这个步骤就不会进行。反之,如果是 graphical.target 的话,那么 systemd 就会开始加载用户管理服务与图形界面管理员 (window display manager, DM) 等,启动图形界面来让用户以图形界面登入系统喔! 如果你对于 graphical.target 多 了哪些服务有兴趣,那就来检查看看:

            [root@initroot ~]# systemctl list-dependencies graphical.target
            graphical.target
            ├─accounts-daemon.service├─gdm.service
            ├─network.service
            ├─rtkit-daemon.service
            ├─systemd-update-utmp-runlevel.service
            └─multi-user.target
            ├─abrt-ccpp.service
            .....(底下省略).....
            
事实上就是多了上面列出来的这些服务而已~大多数都是图形界面账号管理的功能,至于实际让用户 可以登入的服务,倒是那个 gdm.service 哩! 如果你去瞧瞧 gdm.service 的内容,就会发现最重要 的执行档是 /usr/sbin/gdm 喔!那就是让用户可以利用图形界面登入的最重要服务啰! 我们未来讲到 X 窗口界面时再来聊聊 gdm 这玩意儿喔!
到此为止,systemd 就已经完整的处理完毕,你可以使用图形界面或文字界面的方式来登入系统,系 统也顺利的开机完毕, 也能够将你写入到 /etc/rc.d/rc.local 的脚本实际执行一次啰。那如果默认是图 形界面 (graphical.target) 但是想要关掉而进入文字界面 (multi-user.target) 呢? 很简单啊!19.1.3 小 节就谈过了,使用『 systemctl isolate multi-user.target 』即可!如果使用『 init 3 』呢?也是可以啦! 只是系统实际执行的还是『 systemctl isolate multi-user.target 』就是了! ^_^

开机过程会用到的主要配置文件

回到顶部

基本上, systemd 有自己的配置文件处理方式,不过为了兼容于 systemV ,其实很多的服务脚本设 定还是会读取位于 /etc/sysconfig/ 底下的环境配置文件! 底下我们就来谈谈几个常见的比较重要的 配置文件啰!
关于模块: /etc/modprobe.d/*.conf 及 /etc/modules-load.d/*.conf
还记得我们在 sysinit.target 系统初始化 当中谈到的加载用户自定义模块的地方吗?其实有两个地方 可以处理模块加载的问题,包括:
/etc/modules-load.d/*.conf:单纯要内核加载模块的位置;
/etc/modprobe.d/*.conf:可以加上模块参数的位置
基本上 systemd 已经帮我们将开机会用到的驱动程序全部加载了,因此这个部份你应该无须更动才 对!不过, 如果你有某些特定的参数要处理时,应该就得要在这里进行了。举例来说,我们在第十 七章曾经谈过 vsftpd 这个服务对吧! 而且当时将这个服务的埠口更改到 555 这个号码上去了!那 我们可能需要修改防火墙设定,其中一个针对 FTP 很重要的防火墙模块为 nf_conntrack_ftp, 因此, 你可以将这个模块写入到系统开机流程中,例如:

            [root@initroot ~]# vim /etc/modules-load.d/initroot.conf
            
nf_conntrack_ftp一个模块 (驱动程序) 写一行~然后,上述的模块基本上是针对默认 FTP 埠口,亦即 port 21 所设 定的,如果需要调整到 port 555 的话, 得要外带参数才行!模块外加参数的设定方式得要写入到另 一个地方喔!
            [root@initroot ~]# vim /etc/modprobe.d/initroot.conf
            options nf_conntrack_ftp ports=555
            
之后重新启动就能够顺利的载入并且处理好这个模块了。不过,如果你不想要开机测试,想现在处理 呢?有个方式可以来进行看看:
            [root@initroot ~]# lsmod | grep nf_conntrack_ftp
            # 没东西!因为还没有加载这个模块!所以不会出现任何讯息!
            [root@initroot ~]# systemctl restart systemd-modules-load.service
            [root@initroot ~]# lsmod | grep nf_conntrack_ftp
            nf_conntrack_ftp
            nf_conntrack
            18638
            105702
            0
            1 nf_conntrack_ftp
            
透过上述的方式,你就可以在开机的时候将你所需要的驱动程序加载或者是调整这些模块的外加参数 啰!
/etc/sysconfig/*
还有哪些常见的环境配置文件呢?我们找几个比较重要的来谈谈:
authconfig:
这个文件主要在规范使用者的身份认证的机制,包括是否使用本机的 /etc/passwd, /etc/shadow 等, 以及 /etc/shadow 密码记录使用何种加密算法,还有是否使用外部密码服务器提供的账号验证 (NIS, LDAP) 等。 系统默认使用 SHA512 加密算法,并且不使用外部的身份验证机制;另外,不建议手动修改这个文件喔! 你应该使用『 authconfig-tui 』指令来修改较佳!
cpupower:
如果你有启动 cpupower.service 服务时,他就会读取这个配置文件。主要是 Linux 内核如何操作 CPU 的 原则。 一般来说,启动 cpupower.service 之后,系统会让 CPU 以最大效能的方式来运作,否则预设就是 用多少算多少的模式来处理的。
firewalld, iptables-config, iptables-config, ebtables-config:
与防火墙服务的启动外带的参数有关,这些资料我们会在服务器篇慢慢再来讨论。
network-scripts/:
至于 network-scripts 里面的文件,则是主要用在设定网络卡~ 这部份我们在服务器架设篇才会提到!

在描述linux系统启动过程之前,先上一张著名的图: Linux内核在内存中的布局 这张图为我们的linux系统启动之后,物理内存的前几兆字节的布局情况。我们可以通过查看文件/proc/iomen获得更详细的物理内存布局图,在我的系统中,/proc/iomen文件内容如下:

                peter@initroot:/boot$ sudo cat /proc/iomem
                [sudo] password for peter:       
                00000000-00000fff : Reserved
                00001000-0009fbff : System RAM
                0009fc00-0009ffff : Reserved
                000a0000-000bffff : PCI Bus 0000:00
                000c0000-000c7fff : Video ROM
                000e2000-000e2fff : Adapter ROM
                000f0000-000fffff : Reserved
                  000f0000-000fffff : System ROM
                00100000-dffeffff : System RAM
                  d2600000-d32031d0 : Kernel code
                  d32031d1-d3c6a5bf : Kernel data
                  d3ee2000-d413dfff : Kernel bss
                dfff0000-dfffffff : ACPI Tables
                e0000000-fdffffff : PCI Bus 0000:00
                  f0000000-f0ffffff : 0000:00:02.0
                    f0000000-f0ffffff : vmwgfx probe
                  f1000000-f11fffff : 0000:00:02.0
                    f1000000-f11fffff : vmwgfx probe
                  f1200000-f121ffff : 0000:00:03.0
                    f1200000-f121ffff : e1000
                  f1400000-f17fffff : 0000:00:04.0
                    f1400000-f17fffff : vboxguest
                  f1800000-f1803fff : 0000:00:04.0
                  f1804000-f1804fff : 0000:00:06.0
                    f1804000-f1804fff : ohci_hcd
                  f1805000-f1805fff : 0000:00:0b.0
                    f1805000-f1805fff : ehci_hcd
                  f1806000-f1807fff : 0000:00:0d.0
                    f1806000-f1807fff : ahci
                fec00000-fec00fff : Reserved
                  fec00000-fec003ff : IOAPIC 0
                fee00000-fee00fff : Local APIC
                  fee00000-fee00fff : Reserved
                fffc0000-ffffffff : Reserved
                100000000-15fffffff : System RAM
            
很多人在用cat或者vi查看这个文件的时候,发现前面的地址值都是清一色的0,不知道为什么?是时候展现真正的技术了,只需要在cat或者vi前面加上sudo就好了, 可能这是linux比较隐私的部位,没有点特殊权限是不会让你看的。另外,上面这张图是在32位系统上的布局图。如果你想深入研究linux的内核原理,建议在32位系统上, 这样在查看内存布局方面比较方便。如果只是“使用”linux,可以直接用64位的。 在形成上图这样的布局之前,系统做了哪些工作呢? 这里再上一张图 Linux内核在内存中的布局 这张图摘自《linux内核完全剖析-基于0.12内核》,在该书中详细描述了内核启动加载的详细过程,不过这本书因为描述的是0.12版本内核,所以有些内容和最新的内核加载过程有些不同。 在0.12系统中,由于内核小于640kb,所以被加载到0-640k空间内。而我们在上图中看到,内核是从1M开始的内存区域开始加载的。 这是因为,在后来版本的linux内核大小超过了640kb,如果加载到从4kb开始的640kb空间里,就会占用后面显示缓冲区和各种ROM区域,该部分区域是由系统保留的, 用于映射系统BIOS和显卡ROM。而内核必须被装载到一个连续的内存区中,所以只能从1M开始的位置进行加载。这里注意BIOSROM和显卡ROM是和内存RAM统一编址的。 首先,在我们启动电源的那一刻,cpu首先跳转到内存0xFFFFFF0处读取指令并执行,该地址位置正是BIOS ROM的映射区,存放的是BIOS的代码。 系统执行BIOS用于各个硬件子系统的自检和初始化。执行完BIOS后,我们的硬盘等设备都已经处于可用状态了,系统就会去读取第一个可启动设备(通常就是硬盘)的第一个扇区, 也就是我们经常说的MBR,这里存放的就是用于加载内核的bootloader,现在基本都是grub了。grub会将我们的linux内核加载到内存中,那么grub是从哪里加载linux内核的呢? 当然是从硬盘加载到内存了。我们的linux内核映像文件就放在硬盘/boot分区里。在我的系统中,/boot目录如下:
                peter@initroot:~$ cd /boot/
                peter@initroot:/boot$ ls -al
                total 69608
                drwxr-xr-x  3 root root     4096 Dec 23 10:43 .
                drwxr-xr-x 23 root root     4096 Dec 23 10:43 ..
                -rw-r--r--  1 root root   217278 Jun 24 17:39 config-4.15.0-54-generic
                drwxr-xr-x  5 root root     4096 Dec 17 20:50 grub
                -rw-r--r--  1 root root 58128186 Dec 23 10:43 initrd.img-4.15.0-54-generic
                -rw-r--r--  1 root root   182704 Jan 28  2016 memtest86+.bin
                -rw-r--r--  1 root root   184380 Jan 28  2016 memtest86+.elf
                -rw-r--r--  1 root root   184840 Jan 28  2016 memtest86+_multiboot.bin
                -rw-------  1 root root  4051606 Jun 24 17:39 System.map-4.15.0-54-generic
                -rw-r--r--  1 root root  8294136 Jul 29 19:13 vmlinuz-4.15.0-54-generic
              
其中vmlinuz-4.15.0-20-generic这个文件就是grub将会加载到内存中的linux内核映像文件了,这里采用的是压缩的形式存放的,所以加载到内存中还需要解压缩。 而grub目录中存放的是grub的配置文件,grub被加载到内存中,首先会读取该目录中的配置文件,所以如果你想对grub进行配置,就可以在这个目录里修改配置文件。 注意,该目录中存放的是配置文件,并不是grub的可执行代码文件,grub的可执行代码我们刚才已经说了,是存放在硬盘的第一个扇区中,这个是在安装系统的时候写入该扇区的。 在该目录中,值得关注的还有一个initrd.img-4.15.0-20-generic文件,这个文件是用来做什么的呢?我们知道,内核在启动的过程中,要先挂载根目录,挂载根目录肯定要能识别硬盘, 而识别硬盘需要硬盘设备驱动程序,而我们的linux为了减少自己的体积,并不会将设备驱动程序编译到自己的内核映像中,而是将大部分设备驱动程序编译成单独的模块, 动态的加载设备驱动程序。模块文件一般都放置在/lib/moduls/目录下。这时候就出现了一个问题了,linux要挂载硬盘,首先需要能识别硬盘,需要硬盘设备驱动程序, 而设备驱动程序又在硬盘的/lib/moduls/目录下,而此时硬盘还没有挂载,就无法取得该目录,无法取得该目录就没法加载设备驱动程序,没有设备驱动程序,就无法加载硬盘, 好像陷入了无法解决的死循环。Linux内核的开发者们,当然不允许这种互相踢皮球的现象存在,于是想出了一个办法,那就是虚拟文件系统RAM disk, 也就是我们看到的这个文件initrd.img-4.15.0-20-generic,该文件也会在系统启动的过程中由boot loader(grub)加载,加载到内存中解压缩后,会临时充当linux的根文件系统, 该文件系统中存放了linux所需设备的驱动程序。我们可以查看该文件的内容,就是一个比较完整的跟文件系统。具体的查看方式,这里就不详细阐述了。 /boot目录下还有几个文件,config是内核在编译阶段的一些配置选项,system.map为系统的符号在虚拟地址空间的地址。 到这里我们的内核就加载到内存中了,并开始执行,内核会在执行一系列的初始化操作后,变成0号idle进程,0号进程会启动一个叫kthreadd的内核守护线程, 由该线程启动一系列的内核线程,最后0号进程会启动我们的第一个用户层进程init,目前大部分发行版的init进程执行的是systemd了。我们用ps -ef可以查看系统中启动的进程, 在我的系统中执行ps -ef命令如下:
                [root@initroot ~]# ps -ef
                UID        PID  PPID  C STIME TTY          TIME CMD
                root         1     0  0 Oct30 ?        00:02:55 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
                root         2     0  0 Oct30 ?        00:00:00 [kthreadd]
                ...省略...
                root       501     1  0 Oct30 ?        00:00:09 /usr/sbin/crond -n
                root       506     1  0 Oct30 ?        00:00:00 /usr/sbin/atd -f
                root       530     1  0 Oct30 ttyS0    00:00:00 /sbin/agetty --keep-baud 115200,38400,9600 ttyS0 vt220
                ...省略...
                root      3311     2  0 Dec20 ?        00:00:02 [kworker/u2:2]
                root      7393     1  0 Dec21 ?        00:00:00 npm
                root      7404  7393  0 Dec21 ?        00:00:11 node /home/wwwroot/default/kblog/node_modules/.bin/nuxt start
                apache    8272 10351  0 Dec05 ?        00:01:13 php-fpm: pool www
                ...省略...
              
其中COMMAND列中带中括号的就是系统的内核线程,剩下的就是系统启动的用户层进程。其实他们本质上都是一样的,都是linux进程,只是一个是在内核空间执行, 另一个是在用户空间执行的。通过pid和ppid我们也能看出,所有内核守护线程都是由kthreadd启动的,而kthreadd的父进程id号是0。而我们看到pid号为1的进程就是我们的init进程, 他同样也是由0号进程启动的。通过COMMAND列我们看到init进程启动的是/sbin/init这个可执行文件,通过ls -al发现,该文件正是systemd的链接,所以我们的init进程其实就是systemd了。 systemd正是用户空间所有进程的父进程了,所有的用户空进进程都是由他来启动的,我们也可以通过pstree命令直观的看到系统中进程的父子关系:
                [root@initroot ~]# pstree
                systemd─┬─AliYunDun───22*[{AliYunDun}]
                        ├─AliYunDunUpdate───3*[{AliYunDunUpdate}]
                        ├─2*[agetty]
                        ├─aliyun-service───2*[{aliyun-service}]
                        ├─atd
                        ├─auditd───{auditd}
                        ├─crond
                        ├─dbus-daemon
                        ├─dhclient
                        ├─mysqld_safe───mysqld───18*[{mysqld}]
                        ├─nginx───nginx
                        ├─npm─┬─node───10*[{node}]
                        │     └─10*[{npm}]
                        ├─ntpd
                        ├─php-fpm───15*[php-fpm]
                        ├─polkitd───6*[{polkitd}]
                        ├─rsyslogd───2*[{rsyslogd}]
                        ├─sshd─┬─4*[sshd───sftp-server]
                        │      └─sshd───bash───pstree
                        ├─systemd-journal
                        ├─systemd-logind
                        ├─systemd-udevd
                        └─tuned───4*[{tuned}]
              
我们通过pstree发现系统中所有的进程都是由systemd启动的,由于我是通过ssh远程登录的linux,所以也可以看到bash是由sshd启动的,这个也就是我在前几篇文章重点讨论的shell了。 再往下就是systemd的启动过程了,我不打算在这篇文章中详细讨论了,我会在下一篇尝试探讨systemd的启动过程。为什么说用尝试探讨,因为我发现,很多东西, 懂了跟写出来是完全不一样的,就像这篇文章,一开始我以为写个启动过程会很容易,但是写着写着就会发现,内容实在是太庞大了,很多东西即使弄懂了, 但是把它写出来好像也没有那么容易,所以只能说尝试了。 结合上一篇文章 《linux shell环境配置文件》 ,到这里,基本上把linux启动过程的方面方面都涉及到了,但是不可能在一篇文章中讨论所有的细节。 比如linux内核的启动过程,只是几句话带过了,建议看看《linux内核设计与实现》和《linux内核架构》,这两本书都对启动过程有详细的讨论。本文权当是给各位抛砖引玉了。