一、Linux的开机流程分析

1.开机流程

当你按下电源后,电脑硬件会主动读取BIOS或UEFI BOIOS来加载硬件信息以及进行硬件的自我测试,然后系统就会主动读取第一个可开机的设备(由BIOS设置),此时就可以读入开机管理程序了。

开机管理程序可以指定使用哪个核心文件来开机,并加载核心到内存中解压缩执行,此时核心就能够在内存中活动,并检测所有硬件信息加载适当的启动程序来使整个电脑工作,等到核心检测硬件与加载驱动程序完成后,操作系统就开始运行了。

操作系统开始运行,Linux才会调用外部程序开始准备软件执行的环境,并且实际加载所有系统运行所需要的软件程序。

总的来说,系统开机是这样的流程:

(1)加载BIOS的硬件信息与进行自我测试,并依据设定取得第一个可开机的装置;

(2)读取并执行第一个开机装置内MBR的boot Loader(亦即是grub2,spfdisk等程序);

(3)依据bootloader的设定加载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需要的服务。

下面来介绍下一个步骤。

2.BIOS, boot loaderkernel加载:lsinitrd

(1)如果我想安装不同的操作系统,那么怎样才能在同一台电脑上安装Windows和Linux呢?

答:每个文件系统都会保留一块boot sector提供操作系统安装boot loader,而通常操作系统都会默认安装一个loader到他根目录所在的文件系统的boot sector。如果我们在一台电脑上安装Windows和Linux后,那么boot sector、boot loader和MBR的相关性如下图所示:

图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的主要功能如下:

①提供菜单:用户可以选择不同的开机项目;

②加载核心文件:直接指向可开机的程序区段来开始操作系统;

③转交其他loader:将开机管理功能转交给其他loader负责。

由于具有菜单功能,因此我们可以选择不同的核心来开机。而由于具有控制权转交的功能,因此我们可以加载其他boot sector内的loader啦!不过Windowsloader预设不具有控制权转交的功能,因此你不能使用Windowsloader来加载Linuxloader,这就是为什么会特别强调先装Windows再装Linux的缘故

如下图所示。

图1.2 开机管理程序的菜单功能与控制权移交示意图

如上所示,不同菜单栏的启动内容有所不同,如下所示:

①菜单1:MBR(grub2)àkernel fileàbooting。

②菜单2:MBR(grub2)àboot sector(Windows loader)àWindows kernelàbooting。

③菜单3:MBR(grub2)àboot sector(grub2)àkernel fileàbooting。

(2)加载核心检测硬件与initramfs的功能

当我们由boot loader的管理而开始读取核心文件后,接下来,Linux就会将核心解压缩到主存储器当中,并且利用核心的功能,开始测试与驱动各个周围装置,包括储存装置、CPU、网卡、声卡等。此时Linux核心会以自己的功能重新侦测一次硬件,而不一定会使用BIOS侦测到的硬件信息。也就是说,核心此时开始接管BIOS后的工作了。一般来书哦核心文件被放在/boot目录下,并且命名为/boot/vmlinuz才对。

root@xiaoao-virtual-machine:/home/xiaoao# ls --format=single-column -F /boot

abi-4.15.0-20-generic

config-4.15.0-20-generic                   #开机管理程序

grub/

initrd.img-4.15.0-20-generic

memtest86+.bin

memtest86+.elf

memtest86+_multiboot.bin

retpoline-4.15.0-20-generic

System.map-4.15.0-20-generic               #核心功能放置到内存位置的对应表

vmlinuz-4.15.0-20-generic                  #核心文件

从上面我们可以知道Ubuntu 18.04LTS的核心为4.15.0-20这个版本。核心模块是放置到/lib/modules目录内。由于模块放置到硬盘根目录内(/lib不可以与/放在不同的partition!),因此开机过程中核心必须挂载根目录,这样才能够读取核心模块提供加载驱动程序的功能。

一般来说,非必要的功能且可以编译成为模块的核心功能,目前的Linux distributions都会将他编译成为模块。 因此 USB, SATA, SCSI... 等磁盘装置的驱动程序通常都是以模块的方式来存在的

如果我们linux安装在sata硬盘上,那么当然可以通过BIOS的INT13取得boot loader与kernel文件来开机,然后kernel会开始结果系统并检测硬件和尝试挂在根目录获得额外的驱动程序。但是核心根本不认识SATA硬盘,所以要加载SATA硬盘的驱动程序,否则无法挂在根目录,但是SATA硬盘的驱动程序又在/lib/modules里面,无法挂在根目录怎么读取这里面的驱动程序呢?

可以通过虚拟文件系统(Initial RAM Disk或Initial RAM Filesystem)来解决这个问题。一般来说虚拟文件系统的文件名为/boot/initrd或者/boot/initramfs。这个文件的特点是,可以通过boot loader加载到内存中,后这个文件会被解压缩并且在内存当中仿真成一个根目录,且此仿真在内存当中的文件系统能够提供一支可执行的程序,透过该程序来加载开机过程中所最需要的核心模块,通常这些模块就是USB,RAID,LVM,SCSI等文件系统与磁盘接口的驱动程序。等加载完成以后,就会帮助核心重新调用systemd来开始正常的开机流程。

其整个流程图如图1.3所示。

图1.3 BIOS与boot loader及核心加载流程示意图

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

在核心加载完成、进行完硬件检测与驱动程序加载后,主机已经准备就绪了,此时就会调用第一个程序,即systemd。systemd的最主要功能就是准备软件执行的环境,包括系统的主机名、网络设定、语言处理、文件系统格式及其他服务的启动等。而所有的动作都会通过systemd的默认启动服务集合,亦即是/etc/systemd/system/default.target(ubuntu在ubuntu是在/usr/lib/systemd/user/default.target)来配置。

system为了与旧的systemV相兼容,所以也将runlevel与操作环境进行了结合。可以通过下面这个方式来查询两者间的对应(Ubuntu中没有这个部分):

[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

做一个完整对应关系如下表:

SystemVsystemd
init 0systemctl poweroff
init 1systemctl rescue
init [234]systemctl isolate multi-user.target
init 5systemctl isolate graphical.target
init 6systemctl reboot

(1)systemd的处理流程

当我们取得了default.target之后,它会先连接到/usr/lib/systemd/system(ubuntu是在/usr/lib/systemd/user)这个目录下去获得multi-user.target或者graphical.target中的一个。为了说明方便,假设加载的是graphical.target,接下来systemd会去找这两个地方的配置,如下目录(ubuntu待更新,目前使用的是CentOS7.x):

①/etc/systemd/system/graphical.target.wants/:用户设定加载的unit;

②/usr/lib/systemd/system/graphical.target.wants/:系统默认加载的unit。

基本上来说,CentOS 7.x的systemd开机流程是这样的:

①local-fs.target+swap.target:这两个target主要在挂在本机/etc/fstab里面的文件系统与相关内存交换空间。

②sysinit.target:检测硬件,加载所需要的核心模块。

③basic.target:加载主要的周围硬件驱动程序与防火墙相关任务。

④multi-user.target下面的其它一般系统或者网络服务加载;

⑤图形界面相关服务的加载。

 

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,用在动态对应实际装置存取与装置文件名对应的一个服务,相当重要,也是在这里启动的。

basic.target是一个最简单的操作系统,它启动的服务大概有这些:

①加载alsa音效驱动程序:这个alsa是个音效相关的驱动程序,会让你的系统有音效。

②加载firewalld防火墙:CentOS7.x以后使用firewalld取代iptables的防火墙设定,虽然最终都是使用iptables的架构,不过在设定上面差很多。

③加载CPU的微指令功能。

④启动与设定SELinux的安全本文:如果由disable的状态改成enable的状态,或者是管理员设定强制重新设定一次SELinux的安全本文,在这个阶段处理。

⑤将目前的开机过程所产生的开机信息写入到/var/log/dmesg当中。

⑥由/etc/sysconfig/modules/*.modules及/etc/rc.modules加载管理员指定的模块。

⑦加载systemd支持的timer功能。

 

5.systemd启动multi-user.target下的服务:相容的rc.local, getty.target启动

新的systemd机制中,它建议直接写一个systemd的启动脚本配置文件到/etc/systemd/system底下,然后使用systemctlenable的方式来设定启用它,而不要直接使用rc.local这个文件。

新的systemd也兼容了systemV的rc.local功能,即使用rc-local.service这个功能。该服务不需要启动,它会自动判断/etc/rc.d/rc.local是否具有可执行的权限来判断要不要启动这个服务。

(1)提供tty界面与登录服务

在multi-user.target底下还有个getty.target的操作界面项目。能不能提供适当的登录服务也是multi-user.target底下的内容,包括systemd-logind. service,systemd-user-sessions. service等服务。

比较有趣的地方是,由于服务都是同步运作,不一定哪个服务先启动完毕。如果getty服务先启动完毕时,你会发现到有可用的终端机尝试让你登录系统了。问题是,如果systemd-logind.service或systemd-user-sessions.service服务尚未执行完毕的话,那么你还是无法登录系统的。

这就是为什么看到屏幕出现tty1可以让他登入了,但是一开始输入正确的帐密却无法登录系统,总要隔了数十秒之后才能够顺利的登录的原因。

6.systemd启动graphical.target下面的服务

同理,我们可以直接运行“systemctl list-dependencies graphical.target”,可以得到

graphical.target

● ├─accounts-daemon.service

● ├─apport.service

● ├─gdm.service

● ├─grub-common.service

● ├─speech-dispatcher.service

● ├─systemd-update-utmp-runlevel.service

● ├─udisks2.service

● ├─ureadahead.service

● ├─vmware-tools-thinprint.service

● ├─vmware-tools.service

● └─multi-user.target

●   ├─anacron.service

●   ├─apport.service

(以下省略)

事实上就是多了上面列出来的这些服务而已,大多数都是图形界面账号管理的功能,至于实际让用户可以登入的服务,就是gdm.service。如果打开看一下gdm.service的内容,就会发现最重要的执行文件是/usr/sbin/gdm,这是让用户可以利用图形界面登入的最重要服务。

 

7.开机过程中用到的配置文件

基本上,systemd有自己的配置文件处理方式,不过为了兼容于systemV,其实很多的服务脚本设定还是会读取位于/etc/sysconfig/底下的环境配置文件!底下我们就来谈谈几个常见的比较重要的配置文件。

(1)/etc/modprobe.d/*.conf以及/etc/modules-load.d/*conf

/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端口,即port21所配置的,如果需要调整到555,需要外带参数,那么模块外加参数的配置方式要写入到另一个地方,也就是/etc/modprobe.d/中。即:

[root@study ~]# vim /etc/modprobe.d/vbird.conf

options nf_conntrack_ftp ports=555

之后就可以重新开机,然后执行这个步骤了。

(2)/etc/sysconfig/*

这里面有这些常见的配置文件:

①authconfig:这个文件主要在规范使用者的身份认证的机制,包括是否使用本机的/etc/passwd, /etc/shadow等,以及/etc/shadow密码记录使用何种加密算法,还有是否使用外部密码服务器提供的账号验证(NIS, LDAP)等。

②cpupower:如果你有启动cpupower.service服务时,他就会读取这个配置文件。主要是Linux核心如何操作CPU的原则。一般来说,启动cpupower.service之后,系统会让CPU以最大效能的方式来运作,否则预设就是用多少算多少的模式来处理的。

③firewalld, iptables-config, ip6tables-config, ebtables-config:与防火墙服务的启动外带的参数有关。

④network-scripts/:主要用在设定网卡。

二、核心与核心模块

现在的kernel都是模块化的程序。核心与核心模块放在哪里呢?

(1)核心:/boot/vmlinuz或者/boot/vmlinuz-version;

(2)核心解压缩所需RAM Disk:/boot/initramfs(/boot/initramfs-version);

(3)核心模块:/lib/modules/version/kernel或/lib/modules/$(uname -r)/kernel;

(4)核心源代码:/usr/src/linux或/usr/src/kernels(要安装才会有,默认不安装)。

如果kernel被顺利加载到当前系统,那么会有几个信息被记录下来:

(1)核心版本:/proc/version;

(2)系统核心功能:/proc/sys/kernel。

1.核心模块与相依性:depmod

核心模块的文件夹主要有以下几个文件(Ubuntu下文件夹更多):

文件夹名称作用
arch与硬件平台有关的项目,例如CPU的等级等。
crypto核心所支持的加密技术,例如md5等。
drivers顾名思义,一些硬件的驱动程序。
fs核心所支持的文件系统。
lib一些函数库。
net与网络有关的各项协议,还有防火墙模块等。
sound与音效有关的各种模块。

如果我们一个一个区检查这些模块的主要信息并定义出他们的相依性,这是不可行的。所以我们可以检查/lib/modules/$(uname-r)/modules.dep(在Ubuntu下是/lib/modules/version/kernel/modules.dep)。如何建立这个文件呢?使用depmod这个命令,用法如下:

[root@study ~]# depmod [-Ane]

選項與參數:

-A:不加任何参数时,depmod会主动的去分析目前核心的模块,并且重新写入modules.dep当中。若加入-A参数时,则depmod会去搜寻比modules.dep内还要新的模块,如果真找到新模块,才会更新。

-n:不写入modules.dep,而是将结果输出到屏幕上(standard out);

-e:显示出目前已加载的不可执行的模块名称

 

2.核心模块的观察:lsmod, modinfo

如果我们想要了解目前核心加载了多少个模块,那就使用lsmod命令即可。

在lsmod之中,系统显示的三列分别是:模块名称(Module)、模块大小(Size)和此模块是否被其它模块使用(Used by)。

假如我们想看某个模块的信息,那么我们可以使用modinfo这个命令,其使用方式如下:

[root@study ~]# modinfo [-F field] [-adln] [module_name|filename]

选项与参数:

-F:仅列出后面所接的字段,字段内容主要有author(作者),description(描述),license(授权),parm(参数),depends(相依性),alias等等。后续的-adln为旧版相容。

-a:仅列出作者名称;

-d:仅列出该modules的说明(description);

-l:仅列出授权(license);

-n:仅列出该模块的详细路径。

例如我对drm进行modinfo,结果如下:

filename:       /lib/modules/4.15.0-20-generic/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

license:        GPL and additional rights

description:    DRM bridge infrastructure

author:         Ajay Kumar <ajaykumar.rs@samsung.com>

license:        GPL and additional rights

description:    DRM panel infrastructure

author:         Thierry Reding <treding@nvidia.com>

srcversion:     4CCB8C1AD414A1D4CEE5BA7

depends:

retpoline:      Y

intree:         Y

name:           drm

vermagic:       4.15.0-20-generic SMP mod_unload

signat:         PKCS#7

signer:

sig_key:

sig_hashalgo:   md4

parm:           edid_firmware:Do not probe monitor, use specified EDID blob from built-in data or /lib/firmware instead.  (string)

(剩余省略)

 

  1. 核心模块的加载和移除:insmod, modprobe, rmmod

(1)自行手动加载模块

最简单而且建议的是,使用modprobe这个命令来加载模组。modprobe会主动寻找modules.dep中的内容,先客服了模块的相依性后,才决定需要载入的模组有哪些。

insmod则完全由使用者自行加载一个完整文件名的模组,并不会分析模块的相依性。

[root@study ~]# insmod [/full/path/module_name] [parameters]

 

(2)手动移除模块

使用rmmod,rm取自remove,mod即为module。使用方法如下:

[root@study ~]# rmmod [-fw] module_name

选项与参数:

-f:强制将该模块移除掉,不论是否正被使用。

使用insmodrmmod的問題就是,你必須要自行找到模組的完整檔名才行。万一模块有相依性问题,你将无法直接载入或移除该模块。

modprobe的用法是:

[root@study ~]# modprobe [-cfr] module_name

选项与参数:

-c:列出目前系统所有的模块。

-f:强制加载该模块。

-r:类似rmmod,就是移除某个模块。