初识Linux(十六)------ 开机流程、模块管理与 Loader
系统开机是一项复杂的程序,核心要侦测硬件并载入适当的驱动程序后, 接下来则必须要调用程序来准备好系统运行的环境,以让使用者能够顺利的操作主机系统。 如果能够理解开机的原理,那么将有助于在系统出问题时能够很快速的修复系统。而且还能够顺利的配置多重操作系统的多重开机问题。为了多重开机的问题,就需要学习 Linux 下面优秀的开机管理程序 (boot loader)------- grub2,而在系统运行期间,也要学会管理核心模块。
1. Linux 的开机流程分析
如果想要多系统开机,那要怎么安装系统?如果 root 密码忘记了,那要如何救援?如果默认登陆模式为图形界面,那要如何在开机时直接指定进入纯文本模式? 如果因为 /etc/fstab 设置错误,导致无法顺利挂载根目录,那要如何在不重装的情况下修改的 /etc/fstab 让它变成正常?这些都需要了解开机流程。
1.1 开机流程简述
开机管理程序(Boot Loader)使用的软件,目前linux发行商的主流为 grub2 ,早期的linux使用的是 grub1 , LILO 或者 spfdisk 。
开机流程:
1. 按下开机键后,计算机硬件会自动载入 BIOS 或者 UEFI BIOS 的硬件信息与进行自我测试,并依据设置取得第一个可开机的设备;
2. 读取并执行第一个开机设备内 MBR 的 boot Loader (亦即是 grub2, spfdisk 等程序);
3. 依据 boot loader 的设置载入 Kernel ,Kernel 会开始侦测硬件与载入驱动程序;
4. 在硬件驱动成功后,Kernel 会主动调用 systemd 程序,并以 default.target 流程开机;
- systemd 执行 sysinit.target 初始化系统及 basic.target 准备操作系统;
- systemd 启动 multi-user.target 下的本机与服务器服务;systemd 执行 multi-user.target 下的 /etc/rc.d/rc.local 文件;
- systemd 执行 multi-user.target 下的 getty.target 及登陆服务;
- systemd 执行 graphical 需要的服务。
1.2 BIOS, boot loader 与 kernel 载入
BIOS 和 MBR 在最开始的文章中提过,这里再对名词解释一下:
- BIOS:BIOS --Basic Input Output System ,不论传统 BIOS 还是 UEFI BIOS 都会被简称为 BIOS;
- MBR:虽然分区表有传统 MBR 以及新式 GPT,不过 GPT 也有保留一块相容 MBR 的区块,因此,下面的说明在安装 boot loader 的部份, 还是简称为 MBR 。总之,MBR 就代表该磁盘的最前面可安装 boot loader 的那个区块。
BIOS,开机自测与MBR/GPT
在个人计算机架构下,想要启动系统首先就得要载入 BIOS ,并通过 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 软件。
我们知道每颗硬盘的最前面区块含有 MBR 或 GPT 分区表的提供 loader 的区块,那么如果我的主机上面有两颗硬盘的话, 系统会去哪颗硬盘的最前面区块读取 boot loader 呢?这个就得要看 BIOS 设置里的第一个开机设备是哪个了。
Boot Loader 的功能
每个文件系统 (filesystem, 或者是 partition) 都会保留一块开机扇区 (boot sector) 提供操作系统安装 boot loader , 而通常操作系统默认都会安装一份 loader 到他根目录所在的文件系统的 boot sector 上。如果我们在一部主机上面安装 Windows 与 Linux 后,该 boot sector, boot loader 与 MBR 的相关性会有点像下图:
如上图所示,每个操作系统默认会安装 boot loader 到他自己的文件系统中 (就是每个 filesystem 左下角的方框),而在 Linux 系统安装时,你可以选择将 boot loader 安装到 MBR 去,也可以选择不安装。 如果选择安装到 MBR 的话,那在 MBR 与 boot sector 都会保有一份 boot loader 程序。 至于 Windows 安装时,默认会将 MBR 与 boot sector 都装上一份 boot loader!所以, 在安装多重操作系统时会发现,你的 MBR 常常会被不同的操作系统的 boot loader 所覆盖。
如果要在一台主机上安装多个操作系统,但MBR只有一个,那如何执行boot sector里的loader?这就需要boot loader来解决,他主要提供以下功能:
- 提供菜单:使用者可以选择不同的开机项目;
- 载入核心文件:直接指向可开机的程序区段来开始操作系统;
- 转交其他 loader:将开机管理功能转交给其他 loader 负责。
由于具有菜单功能,因此我们可以选择不同的核心来开机。而由于具有控制权转交的功能,因此我们可以载入其他 boot sector 内的 loader 。不过 Windows 的 loader 默认不具有控制权转交的功能,因此不能使用 Windows 的 loader 来载入 Linux 的 loader 。这也是为什么会特别强调先装 Windows 再装 Linux 的原因。
如上图所示,如果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/vmlinuz 。
[root@study ~]# 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 来开始后续的正常开机流程。
下面让我们来了解一下 CentOS 7.x 的 initramfs 文件内容:
# 1. 先来直接看一下 initramfs 里面的内容有些啥数据?
[root@study ~]# 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@study ~]# mkdir /tmp/initramfs
[root@study ~]# cd /tmp/initramfs
[root@study initramfs]# dd if=/boot/initramfs-3.10.0-229.el7.x86_64.img of=initramfs.gz \
> bs=11264 skip=1
[root@study 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@study initramfs]# gzip -d initramfs.gz
[root@study initramfs]# file initramfs
initramfs: ASCII cpio archive (SVR4 with no CRC)
# 3. 解开后又产生一个 cpio 文件,得要将它用 cpio 的方法解开!加上不要绝对路径的参数较保险!
[root@study initramfs]# cpio -i -d -H newc --no-absolute-filenames < initramfs
[root@study 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 3041 Aug 24 19:40 shutdown
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
# 看吧!上面几乎就像是一个小型的文件系统根目录耶!这样就能让 kernel 去挂载了!
# 4. 接下来瞧一瞧到底这个小型的文件系统中,systemd 是要以哪个 target 来执行开机呢?
[root@study 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@study 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 !
1.3 systemd 及 default.target 分析
在核心载入完毕、进行完硬件侦测与驱动程序载入后,此时主机硬件应该已经准备就绪了 (ready) , 核心会主动的调用第一支程序,那就是 systemd ,这就是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@study ~]# 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 程序依旧是保留下来,这两个东西的对应为:
systemd 的处理流程
当取得 /etc/systemd/system/default.target 这一个默认操作界面的设置之后,系统会链接到 /usr/lib/systemd/system/ 这个目录下去取得 multi-user.target 或 graphical.target 这两个其中的一 ,假设使用 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@study ~]# 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
第5行和第8行表示 graphical.target 必须要完成 multi-user.target 之后才能够进行,而进行完 graphical.target 之后,还得要启动 display-manager.service 才行。
通过同样的方式,multi-user.target 要执行的载入的项目有:
# 先来看看 multi-user.target 配置文件内规范了相依的操作环境有哪些呢?
[root@study ~]# 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@study ~]# 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@study ~]# 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 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 有做什么动作呢?简单了解一下。
1.4 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 的阶段主要启动的服务大概有这些:
- 载入 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 类别,于是就可以进入下个服务启动的阶段了。
1.5 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/ 这个目录下面做个链接~ 这样就可以在开机的时候去启动他。
# 将 vsftpd.service 先 disable 再 enable 看看输出的信息为何?
[root@study ~]# systemctl disable vsftpd.service
rm '/etc/systemd/system/multi-user.target.wants/vsftpd.service'
[root@study ~]# systemctl enable vsftpd.service
ln -s '/usr/lib/systemd/system/vsftpd.service' '/etc/systemd/system/multi-user.target.
wants/vsftpd.service'
可以发现,使用 systemctl 来处理,不是从 /etc/systemd/system/multi-user.target.wants/ 里面删除链接文件,就是创建链接文件。,另外,这些程序除非在脚本设置里面原本就有规范服务的相依性, 这样才会有顺序的启动之外,大多数的服务都是同时启动的,这就是 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@study ~]# ll /etc/rc.d/rc.local
-rw-r--r--. 1 root root 473 Mar 6 13:48 /etc/rc.d/rc.local
[root@study ~]# 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@study ~]# systemctl list-dependencies multi-user.target | grep rc-local
# 明明就有这个服务,但是 rc.local 不具有可执行 (x) 的权限,因此这个服务不会被执行
# 2. 加入可执行权限后,再看一下 rc-local 是否可被启用!
[root@study ~]# 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@study ~]# systemctl daemon-reload
[root@study ~]# systemctl list-dependencies multi-user.target | grep rc-local
├─rc-local.service # 这个服务确实被记录到启动的环境下
提供 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 服务尚未执行完毕的话,那么你还是无法登陆系统的。
1.6 systemd 启动 graphical.target 下面的服务
如果你的 default.target 是 multi-user.target 的话,那么这个步骤就不会进行。如果是 graphical.target 的话,那么 systemd 就会开始载入用户管理服务与图形界面管理员 (window display manager, DM) 等,启动图形界面来让用户以图形界面登陆系统。
[root@study ~]# 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 ,那是让使用者可以利用图形界面登陆的最重要服务。
到此为止,systemd 就已经完整的处理完毕,你可以使用图形界面或文字界面的方式来登陆系统,系统也顺利的开机完毕, 也能够将你写入到 /etc/rc.d/rc.local 的脚本实际执行一次。那如果默认是图形界面 (graphical.target) 但是想要关掉而进入文字界面 (multi-user.target) 呢? 很简单,使用“ systemctl isolate multi-user.target ”即可!如果使用“ init 3 ”呢?也是可以,只是系统实际执行的还是“ systemctl isolate multi-user.target ”。
1.7 开机过程会用到的主要配置文件
基本上, 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@study ~]# vim /etc/modules-load.d/vbird.conf
nf_conntrack_ftp
一个模块 (驱动程序) 写一行~然后,上述的模块基本上是针对默认 FTP 端口,亦即 port 21 所设置的,如果需要调整到 port 555 的话, 得要外带参数才行。模块外加参数的设置方式得要写入到另一个地方。
[root@study ~]# vim /etc/modprobe.d/vbird.conf
options nf_conntrack_ftp ports=555
之后重新开机就能够顺利的载入并且处理好这个模块了。不过,如果你不想要开机测试,想现在处理,有个方式可以来进行看看:
[root@study ~]# lsmod | grep nf_conntrack_ftp
# 没东西!因为还没有载入这个模块!所以不会出现任何信息
[root@study ~]# systemctl restart systemd-modules-load.service
[root@study ~]# lsmod | grep nf_conntrack_ftp
nf_conntrack_ftp 18638 0
nf_conntrack 105702 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 里面的文件,则是主要用在设置网卡。
2. 核心与核心模块
了解完整个开机的流程,就会知道,在整个开机的过程当中,是否能够成功的驱动主机的硬件配备, 是核心 (kernel) 的工作!而核心一般都是压缩文件,因此在使用核心之前,就得要将他解压缩后,才能载入内存当中。
另外,为了应付日新月异的硬件,目前的核心都是具有“可读取模块化驱动程序”的功能, 亦即所谓的“ modules (模块化)”的功能。模块化可以想成是一个“外挂程序”, 该外挂程序可能由硬件开发厂商提供,也有可能是核心本来就支持~不过,较新的硬件, 通常都需要硬件开发商提供驱动程序模块。
那么核心与核心模块放在哪?
- 核心: /boot/vmlinuz 或 /boot/vmlinuz-version;
- 核心解压缩所需 RAM Disk: /boot/initramfs (/boot/initramfs-version);
- 核心模块: /lib/modules/version/kernel 或 /lib/modules/$(uname -r)/kernel;
- 核心源代码: /usr/src/linux 或 /usr/src/kernels/ (要安装才会有,默认不安装)
如果该核心被顺利的载入系统当中了,那么就会有几个信息记录下来:
- 核心版本: /proc/version
- 系统核心功能: /proc/sys/kernel/
如果有个新的硬件,操作系统不支持,该怎么办?
- 重新编译核心,并加入最新的硬件驱动程序源代码;
- 将该硬件的驱动程序编译成为模块,在开机时载入该模块
2.1 核心模块与相依性
基本上,核心模块的放置处是在 /lib/modules/$(uname -r)/kernel 当中,里面主要还分成几个目录:
arch :与硬件平台有关的项目,例如 CPU 的等级等等;
crypto :核心所支持的加密的技术,例如 md5 或者是 des 等等;
drivers :一些硬件的驱动程序,例如显卡、网卡、PCI 相关硬件等等;
fs :核心所支持的 filesystems ,例如 vfat, reiserfs, nfs 等等;
lib :一些函数库;
net :与网络有关的各项协定数据,还有防火墙模块 (net/ipv4/netfilter/*) 等等;
sound :与音效有关的各项模块;
Linux 查看模块相依性的方法,就是检查 /lib/modules/$(uname -r)/modules.dep 这个文件,利用 depmod 这个指令可以创建该文件。
[root@study ~]# depmod [-Ane]
选项与参数:
-A :不加任何参数时, depmod 会主动的去分析目前核心的模块,并且重新写入
/lib/modules/$(uname -r)/modules.dep 当中。若加入 -A 参数时,则 depmod
会去搜寻比 modules.dep 内还要新的模块,如果真找到新模块,才会更新。
-n :不写入 modules.dep ,而是将结果输出到屏幕上(standard out);
-e :显示出目前已载入的不可执行的模块名称
范例一:若我做好一个网卡驱动程序,文件名为 a.ko,该如何更新核心相依性?
[root@study ~]# cp a.ko /lib/modules/$(uname -r)/kernel/drivers/net
[root@study ~]# depmod
kernel 核心模块扩展名一定是 .ko 结尾的, 当使用 depmod 之后,该程序会到模块标准放置目录 /lib/modules/$(uname -r)/kernel 下, 并依据相关目录的定义将全部的模块捕捉来分析,最终才将分析的结果写入 modules.dep 文件中。
2.2 核心模块的观察
使用 lsmod 之后,系统会显示出目前已经存在于核心当中的模块,显示的内容包括有:
- 模块名称(Module);
- 模块的大小(size);
- 此模块是否被其他模块所使用 (Used by)。
[root@study ~]# lsmod
Module Size Used by
nf_conntrack_ftp 18638 0
nf_conntrack 105702 1 nf_conntrack_ftp
....(中间省略)....
qxl 73766 1
drm_kms_helper 98226 1 qxl
ttm 93488 1 qxl
drm 311588 4 qxl,ttm,drm_kms_helper # drm 还被 qxl, ttm..等模块使用
....(下面省略)....
从上面可以看出,nf_conntrack 先被载入后,nf_conntrack_ftp这个模块才能够进一步的载入系统中,这两者间是有相依性。
modinfo 除了可以“查阅在核心内的模块”之外,还可以检查“某个模块文件”。
[root@study ~]# modinfo [-adln] [module_name|filename]
选项与参数:
-a :仅列出作者名称;
-d :仅列出该 modules 的说明 (description);
-l :仅列出授权 (license);
-n :仅列出该模块的详细路径。
范例一:由上个表格当中,请列出 drm 这个模块的相关信息:
[root@study ~]# modinfo drm
filename: /lib/modules/3.10.0-229.el7.x86_64/kernel/drivers/gpu/drm/drm.ko
license: GPL and additional rights
description: DRM shared core routines
author: Gareth Hughes, Leif Delgass, José Fonseca, Jon Smirl
rhelversion: 7.1
srcversion: 66683E37FDD905C9FFD7931
depends: i2c-core
intree: Y
vermagic: 3.10.0-229.el7.x86_64 SMP mod_unload modversions
signer: CentOS Linux kernel signing key
sig_key: A6:2A:0E:1D:6A:6E:48:4E:9B:FD:73:68:AF:34:08:10:48:E5:35:E5
sig_hashalgo: sha256
parm: edid_fixup:Minimum number of valid EDID header Bytes (0-8, default 6) (int)
.....(下面省略).....
# 可以看到这个模块的来源,以及该模块的简易说明!
范例二:我有一个模块名称为 a.ko ,请问该模块的信息为?
[root@study ~]# modinfo a.ko
....(省略)....
2.3 核心模块的载入与移除
使用 modprobe 这个指令来载入模块,modprobe 会主动的去搜寻 modules.dep 的内容,先克服了模块的相依性后, 才决定需要载入的模块有哪些,很方便。
还有一种是 insmod ,完全由使用者自行载入一个完整文件名的模块, 并不会主动的分析模块相依性。
[root@study ~]# insmod [/full/path/module_name] [parameters]
范例一:请尝试载入 cifs.ko 这个“文件系统”模块
[root@study ~]# insmod /lib/modules/$(uname -r)/kernel/fs/fat/fat.ko
[root@study ~]# lsmod | grep fat
fat 65913 0
如何移除这个模块呢?
[root@study ~]# rmmod [-fw] module_name
选项与参数:
-f :强制将该模块移除掉,不论是否正被使用;
范例一:将刚刚载入的 fat 模块移除!
[root@study ~]# rmmod fat
范例二:请载入 vfat 这个“文件系统”模块
[root@study ~]# insmod /lib/modules/$(uname -r)/kernel/fs/vfat/vfat.ko
insmod: ERROR: could not load module /lib/modules/3.10.0-229.el7.x86_64/kernel/fs/vfat/
vfat.ko: No such file or directory
使用 insmod 与 rmmod 的问题就是,必须要自行找到模块的完整文件名才行,而且如同上述范例二的结果, 万一模块有相依属性的问题时,将无法直接载入或移除该模块。所以建议直接使用 modprobe 来处理模块载入的问题,这个指令的用法是:
[root@study ~]# modprobe [-cfr] module_name
选项与参数:
-c :列出目前系统所有的模块!(更详细的代号对应表)
-f :强制载入该模块;
-r :类似 rmmod ,就是移除某个模块啰~
范例一:载入 vfat 模块
[root@study ~]# modprobe vfat
# 很方便,不需要知道完整的模块文件名,这是因为该完整文件名已经记录到
# /lib/modules/`uname -r`/modules.dep 当中的原因,如果要移除的话:
[root@study ~]# modprobe -r vfat
使用 modprobe 真的是要比 insmod 方便很多!因为他是直接去搜寻 modules.dep 的记录, 所以,当然可以克服模块的相依性问题,而且还不需要知道该模块的详细路径。
2.4 核心模块的额外参数设置:/etc/modprobe.d/*conf
如果有某些特殊的需求必须要让核心模块加上某些参数时,请回到本文1.7小节看一看。重点是要自己创建扩展名为 .conf 的文件,通过 options 来带入核心模块参数。
3. Boot Loader: Grub2
3.1 boot loader 的两个 stage
在 BIOS 读完信息后,就会到第一个开机设备的 MBR 去读取 boot loader 了。这个 boot loader 可以具有菜单功能、直接载入核心文件以及控制权移交的功能等, 系统必须要有 loader 才有办法载入该操作系统的核心。MBR 是整个硬盘的第一个 sector 内的一个区块,充其量整个大小也才 446 Bytes 而已。即使是 GPT 也没有很大的扇区来储存 loader 的数据。loader 功能这么强,光是程序码与设置数据不可能只占这么一点点的容量,那如何安装?
为了解决这个问题,Linux 将 boot loader 的程序码执行与设置值载入分成两个阶段 (stage) 来执行:
- Stage 1:执行 boot loader 主程序:
第一阶段为执行 boot loader 的主程序,这个主程序必须要被安装在开机区,亦即是 MBR 或者是 boot sector 。但如前所述,因为 MBR 实在太小了,所以,MBR 或 boot sector 通常仅安装 boot loader 的最小主程序, 并没有安装 loader 的相关配置文件; - Stage 2:主程序载入配置文件:
第二阶段为通过 boot loader 载入所有配置文件与相关的环境参数文件 (包括文件系统定义与主要配置文件 grub.cfg), 一般来说,配置文件都在 /boot 下面。
这些配置文件和与 grub2 有关的文件都放置到 /boot/grub2 中。
[root@study ~]# ls -l /boot/grub2
-rw-r--r--. device.map <==grub2 的设备对应档(下面会谈到)
drwxr-xr-x. fonts <==开机过程中的画面会使用到的字体数据
-rw-r--r--. grub.cfg <==grub2 的主配置文件!相当重要!
-rw-r--r--. grubenv <==一些环境区块的符号
drwxr-xr-x. i386-pc <==针对一般 x86 PC 所需要的 grub2 的相关模块
drwxr-xr-x. locale <==就是语系相关的数据啰
drwxr-xr-x. themes <==一些开机主题画面数据
[root@study ~]# ls -l /boot/grub2/i386-pc
-rw-r--r--. acpi.mod <==电源管理有关的模块
-rw-r--r--. ata.mod <==磁盘有关的模块
-rw-r--r--. chain.mod <==进行 loader 控制权移交的相关模块
-rw-r--r--. command.lst <==一些指令相关性的列表
-rw-r--r--. efiemu32.o <==下面几个则是与 uefi BIOS 相关的模块
-rw-r--r--. efiemu64.o
-rw-r--r--. efiemu.mod
-rw-r--r--. ext2.mod <==EXT 文件系统家族相关模块
-rw-r--r--. fat.mod <==FAT 文件系统模块
-rw-r--r--. gcry_sha256.mod <==常见的加密模块
-rw-r--r--. gcry_sha512.mod
-rw-r--r--. iso9660.mod <==光盘文件系统模块
-rw-r--r--. lvm.mod <==LVM 文件系统模块
-rw-r--r--. mdraid09.mod <==软件磁盘阵列模块
-rw-r--r--. minix.mod <==MINIX 相关文件系统模块
-rw-r--r--. msdospart.mod <==一般 MBR 分区表
-rw-r--r--. part_gpt.mod <==GPT 分区表
-rw-r--r--. part_msdos.mod <==MBR 分区表
-rw-r--r--. scsi.mod <==SCSI 相关模块
-rw-r--r--. usb_keyboard.mod <==下面两个为 USB 相关模块
-rw-r--r--. usb.mod
-rw-r--r--. vga.mod <==VGA 显卡相关模块
-rw-r--r--. xfs.mod <==XFS 文件系统模块
#只列出部分
3.2 grub2 的配置文件 /boot/grub2/grub.cfg
grub2 的优点有:
- 认识与支持较多的文件系统,并且可以使用 grub2 的主程序直接在文件系统中搜寻核心文件名;
- 开机的时候,可以“自行编辑与修改开机设置项目”,类似 bash 的指令模式;
- 可以动态搜寻配置文件,而不需要在修改配置文件后重新安装 grub2 。只要修改完 /boot/grub2/grub.cfg 里头的设置后,下次开机就生效了。
磁盘与分区在 grub2 中的代号
安装在 MBR 的 grub2 主程序,最重要的任务之一就是从磁盘当中载入核心文件, 以让核心能够顺利的驱动整个系统的硬件。所以, grub2 必须要认识硬盘才行,grub2 对硬盘的识别使用的是如下的代号:
(hd0,1) # 一般的默认语法,由 grub2 自动判断分区格式
(hd0,msdos1) # 此磁盘的分区为传统的 MBR 模式
(hd0,gpt1) # 此磁盘的分区为 GPT 模式
- 硬盘代号以小括号 ( ) 包起来;
- 硬盘以 hd 表示,后面会接一组数字;
- 以“搜寻顺序”做为硬盘的编号!(这个重要!)
- 第一个搜寻到的硬盘为 0 号,第二个为 1 号,以此类推;
- 每颗硬盘的第一个 partition 代号为 1 ,依序类推。
旧版的 grub 不论磁盘还是分区的起始号码都是 0 号,而 grub2 在分区的部份是以 1 号开始。
所以说,整个硬盘代号为:
/boot/grub2/grub.cfg 配置文件
[root@study ~]# vim /boot/grub2/grub.cfg
# 开始是 /etc/grub.d/00_header 这个脚本执行的结果展示,主要与基础设置与环境有关
### BEGIN /etc/grub.d/00_header ###
set pager=1
if [ -s $prefix/grubenv ]; then
load_env
fi
.....(中间省略).....
if [ x$feature_timeout_style = xy ] ; then
set timeout_style=menu
set timeout=5
# Fallback normal timeout code in case the timeout_style feature is
# unavailable.
else
set timeout=5
fi
### END /etc/grub.d/00_header ###
# 开始执行 /etc/grub.d/10_linux,主要针对实际的 Linux 核心文件的开机环境
### BEGIN /etc/grub.d/10_linux ###
menuentry 'CentOS Linux 7 (Core), with Linux 3.10.0-229.el7.x86_64' --class rhel fedora \
--class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option \
'gnulinux-3.10.0-229.el7.x86_64-advanced-299bdc5b-de6d-486a-a0d2-375402aaab27' {
load_video
set gfxpayload=keep
insmod gzio
insmod part_gpt
insmod xfs
set root='hd0,gpt2'
if [ x$feature_platform_search_hint = xy ]; then
search --no-floppy --fs-uuid --set=root --hint='hd0,gpt2' 94ac5f77-cb8a-495e-a65b-...
else
search --no-floppy --fs-uuid --set=root 94ac5f77-cb8a-495e-a65b-2ef7442b837c
fi
linux16 /vmlinuz-3.10.0-229.el7.x86_64 root=/dev/mapper/centos-root ro \
rd.lvm.lv=centos/root rd.lvm.lv=centos/swap crashkernel=auto rhgb quiet \
LANG=zh_TW.UTF-8
initrd16 /initramfs-3.10.0-229.el7.x86_64.img
}
### END /etc/grub.d/10_linux ###
.....(中间省略).....
### BEGIN /etc/grub.d/30_os-prober ###
### END /etc/grub.d/30_os-prober ###
### BEGIN /etc/grub.d/40_custom ###
### END /etc/grub.d/40_custom ###
.....(下面省略).....
grub2 不希望用户修改 grub.cfg 这个配置文件,而是修改几个特定的配置文件,由 grub2-mkconfig 这个指令来产生新的 grub.cfg 文件。 不过,还是得要了解一下 grub2.cfg 的大致内容。
在 grub.cfg 最开始的部份,其实大多是环境设置与默认值设置等,比较重要的当然是默认由哪个选项开机 (set default) 以及默认的秒数 (set timeout), 再来则是每一个菜单的设置,就是在“ menuentry ”这个设置值之后的项目。
在 menuentry 之后会有几个项目的规范,包括“ --class, --unrestricted --id ”等等的指定项目,之后通过“ { } ”将这个菜单会用到的数据框起来, 在选择这个菜单之后就会进行括号内的动作的意思。如果真的点选了这个菜单,那 grub2 首先会载入模块,例如上表中的“ load_video, insmod gzio, insmod part_gpt, insmod xfs ”等等的项目, 都是在载入要读取核心文件所需要的磁盘、分区、文件系统、解压缩等等的驱动程序。之后就是三个比较重要的项目:
- set root='hd0,gpt2'
这 root 是指定 grub2 配置文件所在的那个设备。举例来说,安装的时候分区出 / 与 /boot 两个设备,grub2 是在 /boot/grub2 , 这个位置的磁盘文件名为 /dev/vda2 ,因此完整的 grub2 磁盘名称就是 (hd0,2) 。因为我们的系统用的是 GTP 的磁盘分区格式, 因此全名就是“ hd0,gpt2 ”。 - linux16 /vmlinuz-... root=/dev/mapper/centos-root ...
Linux 核心文件以及核心执行时所下达的参数。你应该会觉得比较奇怪的是,我们的核心文件不是 /boot/vmlinuz-xxx 吗? 怎么这里的设置会是在根目录呢?这个跟上面的 root 有关。大部分的系统大多有 /boot 这个分区,如果 /boot 没有分区, 那会是怎么回事呢?我们用下面的迭代来说明一下:- 如果没有 /boot 分区,仅有 / 分区:所以文件名会这样变化:
/boot/vmlinuz-xxx --> (/)/boot/vmlinuz-xxx --> (hd0,msdos1)/boot/vmlinuz-xxx - 如果 /boot 是独立分区,则文件名的变化会是这样:
/boot/vmlinuz-xxx --> (/boot)/vmlinuz-xxx --> (hd0,msdos1)/vmlinuz-xxx
- 如果没有 /boot 分区,仅有 / 分区:所以文件名会这样变化:
因此,这个 linux16 后面接的文件名得要跟上面的 root 搭配在一起,才是完整的绝对路径文件名。至于 linux16 /vmlinuz-xxx root=/file/name 那个 root 指的是“ linux 文件系统中,根目录是在哪个设备上”的意思!从本章一开始的开机流程中,我们就知道核心会主动去挂载根目录,并且从根目录中读取配置文件, 再进一步开始开机流程。所以,核心文件后面一定要接根目录的设备。
- initrd16 /initramfs-3.10...
这个就是 initramfs 所在的文件名,跟 linux16 那个 vmlinuz-xxx 相同,这个文件名也是需要搭配“ set root=xxx ”那个项目的设备, 才会得到正确的位置。
3.3 grub2 配置文件维护 /etc/default/grub 与 /etc/grub.d
/etc/default/grub 主要环境配置文件
这个主配置文件的内容大概是长这样:
[root@study ~]# cat /etc/default/grub
GRUB_TIMEOUT=5 # 指定默认倒数读秒的秒数
GRUB_DEFAULT=saved # 指定默认由哪一个菜单来开机,默认开机菜单之意
GRUB_DISABLE_SUBMENU=true # 是否要隐藏次菜单,通常是藏起来的好!
GRUB_TERMINAL_OUTPUT="console" # 指定数据输出的终端机格式,默认是通过文字终端机
GRUB_CMDLINE_LINUX="rd.lvm.lv=centos/root rd.lvm.lv=centos/swap crashkernel=auto rhgb quiet"
# 就是在 menuentry 括号内的 linux16 项目后续的核心参数
GRUB_DISABLE_RECOVERY="true" # 取消救援菜单的制作
- 倒数时间参数: GRUB_TIMEOUT
这个设置值相当简单,后面就是接要倒数的秒数即可~例如要等待 30 秒,就在这边改成“GRUB_TIMEOUT=30”即可!如果不想等待则输入 0 , 如果一定要使用者选择,则填 -1 即可! - 是否隐藏菜单项目:GRUB_TIMEOUT_STYLE
这个项目可选择的设置值有 menu, countdown, hidden 等等。如果没有设置,默认是 menu 的意思。这个项目主要是在设置要不要显示菜单! 如果你不想要让使用者看到菜单,这里可以设置为 countdown!那 countdown 与 hidden 有啥差异呢?countdown 会在屏幕上显示剩余的等待秒数, 而 hidden 则空空如也~除非你有特定的需求,否则这里一般建议设置为 menu 较佳。 - 信息输出的终端机模式:GRUB_TERMINAL_OUTPUT
这个项目是指定输出的画面应该使用哪一个终端机来显示的意思,主要的设置值有“ console, serial, gfxterm, vga_text ”等等。 除非有特别的需求,否则一般使用 console 即可。 - 默认开机菜单项目:GRUB_DEFAULT
这个项目在指定要用哪一个菜单 (menuentry) 来作为默认开机项目的意思。能使用的设置值包括有“ saved, 数字, title 名, ID 名”等等。 假设你有三笔 menuentry 的项目大约像这样:
menuentry '1st linux system' --id 1st-linux-system { ...}
menuentry '2nd linux system' --id 2nd-linux-system { ...}
menuentry '3rd win system' --id 3rd-win-system { ...}
几个常见的设置值是这样的:
[root@study ~]#
GRUB_DEFAULT=1
代表使用第二个 menuentry 开机,因为数字的编号是以 0 号开始
GRUB_DEFAULT=3rd-win-system
代表使用第三个 menuentry 开机,因为里头代表的是 ID 的项目!它会找到 --id
GRUB_DEFAULT=saved
代表使用 grub2-set-default 来设置哪一个 menuentry 为默认值的意思。通常默认为 0
一般来说,默认就是以第一个开机菜单来作为默认项目,如果想要有不同的菜单设置,可以在这个项目填选所需要的 --id 即可。 当然, id 不要重复。
- 核心的外加参数功能:GRUB_CMDLINE_LINUX
如果你的核心在启动的时候还需要加入额外的参数,就在这里加入吧!举例来说,如果你除了默认的核心参数之外,还需要让你的磁盘读写机制为 deadline 这个机制时, 可以这样处理:
GRUB_CMDLINE_LINUX="..... crashkernel=auto rhgb quiet elevator=deadline"
这个主要环境配置文件编写完毕之后,必须要使用 grub2-mkconfig 来重建 grub.cfg 才行。
假设你需要 (1)开机菜单等待 40 秒钟、 (2)默认用第一个菜单开机、 (3)菜单请显示出来不要隐藏、 (4)核心外带“elevator=deadline”的参数值, 那应该要如何处理 grub.cfg 呢?
答:直接编辑主要环境配置文件后,再以 grub2-mkconfig 来重建 grub.cfg。
# 1. 先编辑主要环境配置文件: [root@study ~]# vim /etc/default/grub GRUB_TIMEOUT=40 GRUB_DEFAULT=0 GRUB_TIMEOUT_STYLE=menu GRUB_DISABLE_SUBMENU=true GRUB_TERMINAL_OUTPUT="console" GRUB_CMDLINE_LINUX="rd.lvm.lv=centos/root rd.lvm.lv=centos/swap crashkernel=auto rhgb quiet elevator=deadline" GRUB_DISABLE_RECOVERY="true" # 2. 开始重新创建 grub.cfg ! [root@study ~]# grub2-mkconfig -o /boot/grub2/grub.cfg Generating grub configuration file ... Found linux image: /boot/vmlinuz-3.10.0-229.el7.x86_64 Found initrd image: /boot/initramfs-3.10.0-229.el7.x86_64.img Found linux image: /boot/vmlinuz-0-rescue-309eb890d09f440681f596543d95ec7a Found initrd image: /boot/initramfs-0-rescue-309eb890d09f440681f596543d95ec7a.img done # 3. 检查看看 grub.cfg 的内容是否真的是改变了? [root@study ~]# grep timeout /boot/grub2/grub.cfg set timeout_style=menu set timeout=40 [root@study ~]# grep default /boot/grub2/grub.cfg set default="0" [root@study ~]# grep linux16 /boot/grub2/grub.cfg linux16 /vmlinuz-3.10.0-229.el7.x86_64 root=/dev/.... elevator=deadline linux16 /vmlinuz-0-rescue-309eb890d09f440681f5965.... elevator=deadline
菜单创建的脚本 /etc/grub.d/*
grub2-mkconfig 执行之后,屏幕怎么会主动的去抓到 linux 的核心,还能够找到对应核心版本的 initramfs 呢? 其实 grub2-mkconfig 会去分析 /etc/grub.d/* 里面的文件,然后执行该文件来创建 grub.cfg 的。所以, /etc/grub.d/* 里面的文件就显得很重要了。一般来说,该目录下会有这些文件存在:
- 00_header:主要在创建初始的显示项目,包括需要载入的模块分析、屏幕终端机的格式、倒数秒数、菜单是否需要隐藏等等,大部分在 /etc/default/grub 里面所设置的变量,大概都会在这个脚本当中被利用来重建 grub.cfg 。
- 10_linux:根据分析 /boot 下面的文件,尝试找到正确的 linux 核心与读取这个核心需要的文件系统模块与参数等,都在这个脚本运行后找到并设置到 grub.cfg 当中。 因为这个脚本会将所有在 /boot 下面的每一个核心文件都对应到一个菜单,因此核心文件数量越多,你的开机菜单项目就越多了。 如果未来你不想要旧的核心出现在菜单上,那可以通过移除旧核心来处理即可。
- 30_os-prober:这个脚本默认会到系统上找其他的 partition 里面可能含有的操作系统,然后将该操作系统做成菜单来处理就是了。 如果你不想要让其他的操作系统被侦测到并拿来开机,那可以在 /etc/default/grub 里面加上“ GRUB_DISABLE_OS_PROBER=true ”取消这个文件的运行。
- 40_custom:如果还有其他想要自己手动加上去的菜单项目,或者是其他的需求,那么建议在这里补充。
一般来说,会改动仅有 40_custom 这个文件。这个文件大多放管理员加进来的菜单项目。 menuentry 就是一个菜单,那后续的项目有:
- 直接指定核心开机
略
- 通过 chainloader 的方式移交 loader 控制权
略
3.4 initramfs 的重要性与创建新 initramfs 文件
略
3.5 测试与安装 grub2
略
3.6 开机前的额外功能修改
略
3.7 关于开机画面与终端机画面的图形显示方式
略
3.8 为个别菜单加上密码
略
4. 开机过程的问题解决
4.1 忘记 root 密码
如果 root 的密码忘记了,在 Linux 中还是可以救回来的。只要能够进入并且挂载 / , 然后重新设置一下 root 的密码,就救回来了。
在 systemd 的管理机制中,默认的 rescue 模式是无法直接取得 root 权限的,还是得要使用 root 的密码才能够登陆 rescure 环境。可以通过“ rd.break ”核心参数来处理,需要注意的是, rd.break 是在 Ram Disk 里面的操作系统状态,因此不能直接取得原本的 linux 系统操作环境。所以,还需要 chroot 的支持。由于 SELinux 的问题,可能还得要加上某些特殊的流程才能顺利的搞定 root 密码的救援。
具体流程如下:
(1)开机;
(2)进入到开机画面,在开机的菜单上按下 e 来进入编辑模式, 然后在 linux16 的那个核心项目上面使用这个参数来处理:
添加完之后按下 [crtl]+x ,开机完成后屏幕会出现如下的类似画面。此时请注意,这是在 RAM Disk 环境,并不是原来的环境, 因此根目录下面的东西跟原来的系统无关。而且,系统被挂载到了 /sysroot 目录下,因此,得要这样做:
Generating "/run/initramfs/rdsosreport.txt"
Enter emergency mode. Exit the shell to continue.
Type "journalctl" to view system logs.
You might want to save "/run/initramfs/rdsosreport.txt" to a USB stick or /boot
after mounting them and attach it to a bug report.
switch_root:/# # 无需输入密码即可取得 root 权限
switch_root:/# mount # 检查一下挂载点, /sysroot 才是对的
.....(前面省略).....
/dev/mapper/centos-root on /sysroot type xfs (ro,relatime,attr,inode64,noquota)
switch_root:/# mount -o remount,rw /sysroot # 要先让它挂载成可读写
switch_root:/# chroot /sysroot # 实际切换了根目录的所在,取回你的环境
sh-4.2# echo "your_root_new_pw" | passwd --stdin root
sh-4.2# touch /.autorelabel # 很重要!变回 SELinux 的安全本文~
sh-4.2# exit
switch_root:/# reboot
chroot 目录:代表将根目录“暂时”切换到 chroot 之后所接的目录。以上面为例, /sysroot 将会被暂时作为根目录, 这个目录是最原先的系统根目录,所以能够用来处理文件系统与相关的帐号管理。
为何需要 /.autorelabel:在 rd.break 的 RAM Disk 环境下,系统是没有 SELinux 的,而你刚刚更改了 /etc/shadow (因为改密码), 所以“这个文件的 SELinux 安全本文的特性将会被取消”。如果你没有让系统于开机时自动回复 SELinux 的安全本文, 你的系统将产生“无法登陆”的问题 (在 SELinux 为 Enforcing 的模式下),加上 /.autorelabel 就是要让系统在开机的时候自动的使用默认的 SELinux type 重新写入 SELinux 安全本文到每个文件去。
不过加上 /.autorelabel 之后,系统在开机就会重新写入 SELinux 的 type 到每个文件,因此会花不少的时间,如果你不想要花太多时间, 还有个方法可以处理:
- 在 rd.break 模式下,修改完 root 密码后,将 /etc/selinux/config 内的 SELinux 类型改为 permissive;
- 重新开机后,使用 root 的身份下达“ restorecon -Rv /etc ”仅修改 /etc 下面的文件;
- 重新修改 /etc/selinux/config 改回 enforcing ,然后“ setenforce 1 ”即可。
4.2 直接开机就以 root 执行 bash 的方法
开机取得系统根目录后,让系统直接给一个不需要 root 密码而有 root 权限的 bash 。方法是在开机的过程中,同在 linux16 的那一行,最后面输入“ init=/bin/bash ” 即可。
但是要完整的操作该系统是不可能的,因为我们将 PID 一号更改为 bash 。所以,最多还是用在救援方面。而且,要操作该系统还是得要 remount 根目录才行,否则无法更改文件系统。基本上,这个系统的处理方法要这样做:
如上图所示,默认的是 bash 环境, PATH 仅有 /bin ~所以不能下达 reboot 。同时, 由于没有 systemd 或者是 init 的存在,所以使用绝对路径来下达 reboot 时,系统也是无法重新开机。此时只能按下 reset 或者是强制关机后,才能再次开机。所以...感觉上还是 rd.break 比较保险。
此时应该是无法登陆的,请重新开机进入 rd.break 模式,然后使用 SELinux 改为 permissive 。以 root 登陆系统后, 使用 restorecon -Rv /etc 来看一下,应该如下:
[root@study ~]# getenforce
Permissive
[root@study ~]# restorecon -Rv /etc
restorecon reset /etc/shadow context system_u:object_r:unlabeled_t:s0
->system_u:object_r:shadow_t:s0
restorecon reset /etc/selinux/config context system_u:object_r:unlabeled_t:s0
->system_u:object_r:selinux_config_t:s0
[root@study ~]# vim /etc/selinux/config
SELINUX=enforcing
[root@study ~]# setenforce 1
4.3 因文件系统错误而无法开机
如果因为设置错误导致无法开机时,通常发生在 /etc/fstab 这个文件。如果不正常关机后,也可能导致文件系统不一致 (Inconsistent) 的情况, 有可能会出现相同的问题啊。如果是扇区错乱的情况,请看到图中的第二行处, fsck 告知其实是 /dev/md0 出错, 此时就应该要利用 fsck.ext3 去检测 /dev/md0 。
输入 root 的密码来取得 bash 并以 mount -o remount,rw / 将根目录挂载成可读写后,继续处理。
如果是 XFS 文件系统的话,可能就得要使用 xfs_repair 这个指令来处理。这个 fsck/xfs_repair 的过程可能会很长,而且如果你的 partition 上面的 filesystem 有过多的数据损毁时,即使 fsck/xfs_repair 完成后,可能因为伤到系统盘,导致某些关键系统文件数据的损毁,那么依旧是无法进入 Linux 的。此时,就好就是将系统当中的重要数据复制出来,然后重新安装,并且检验一下。
突然有一天假期结束,时来运转,人生才是真正开始了。