(syslinux.exe)

通过前面几个章节,我们对Linux已经有了一些基本的概念,这个章节,会讲解一下Linux的整个启动过程,不过不会进行非常深入的分析,只是希望你在总体上对Linux启动过程有一个基本的了解,如果你看完本小节内容,却不是很明白,也没关系,继续往后看,随着逐步深入了解,在不时的回过头来看看本小结,或者就明白了。书有时候就要来回看,反复看,俗话说,读书百遍,其义自现,说的就是这个道理,对于这个道理,我自己也深有体会,我本科学的是建筑工程,也是一个很不错的专业,但就是没有兴趣,所以后来决定考计算机的研究生,然后就是自学,很多专业课,完全没有基础,看一遍根本就看不懂,然后就反反复复的看,每一遍都有新的收获,后来发现慢慢的,居然全部都看懂了,没有报任何辅导班,全都是自己学习的。

Linux的启动,主要包括固件初始化,引导加载程序的执行,Linux内核映像的加载和启动,以及文件系电脑统中各种脚本和守护程序的执行,最后就是图形桌面系统的启动(当然,也有很多Linux可能是不带图形系统的,这种情况通常启动到一个可以交互的命令行下面),总体步骤基本上就是这几个步骤,但实际是实现方式和技术,在Linux发展过程中,是在不断的变化和演进的。不同CPU硬件和架构,细节上也不台一样,在引导程序这边,比如X86的引导程序,目前使用的最多的是grub(早期还有LILO等),而ARM架构,目前基本上都是用Uboot引导(早期还有ecos等)。另外比如当内核加载完毕后,开始启动Linux根文件系统时,不同的Linux发展阶段,也有不同的方式,总的来说,主要有以下三种:

SysvinitUpstartSystemd

举例以Ubuntu的发展来看:

Ubuntu 6.10及以前版本使用Sysvinit。

Ubuntu 14.10及以前版本使用Upstart但是还留着Sysvinit并存。

Ubuntu 15.04开始预设使用Systemd,但是可以在开机选项选择使用Systemd或Upstart,但是不可同時使用Sysvinit或Upstart并存。

如今,最新的Linux发行版,都已经默认采用的systemd的系统启动方式。Upstart应该已经不见踪迹,Sysvinit历史悠久,直到今天,还有很多嵌入式Linux在采用这种方式来启动,因为它比较简单。所以,这几种方式,也没有绝对的好与坏,只有合适不合适,根据需要采用合适的启动方式。

所以接下来后续介绍中,upstart,就不介绍了,因为基本已经没有Linux在用了,Sysvinit启动方式,用的也比较少,我会简单介绍,而重点会介绍systemd机制,目前这种方式,使用最为广泛。

刚才也提到,不同系统,不同架构,启动方式也有差异,所以,这里以X86平台,Ubuntu 20.04为例来讲解整个启动过程,但不做深入分析,只是简单了解过程。其实X86 Linux的启动过程,都差不多。

第一步:开机自检,加载BIOS(针对X86,ARM等没有该步骤)第二步:Grub2(引导程序)引导(ARM平台通常使用uboot作为引导)第三步:加载和运行Linux 内核第四步:systemd机制启动文件系统(systemd是用户态第一个进程,有内核调用启动)第五步:通过systemd服务机器加载启动图形系统和系统登录


linux系统启动过程


第一步:开机自检,加载BIOS

当我们打开计算机电源的时候,以前很多台式计算机随后会滴的一声响(现在笔记本好像不响),自检开始,这个过程中主要是检测我们的计算机硬件设备比如:CPU,内存,主板,显卡,CMOS等设备是否有故障存在,并完成硬件的初始化工作。

当你按下你机器上的电源键时,存储在主板 EEPROM 芯片中的固件初始化 (通电自检) 检查系统硬件资源的状态, 结束后,固件会搜索并加载位于第一块可用磁盘上的 MBR 或 EFI 分区的第一阶段引导程序,并把控制权交给引导程序。

MBR是一种老的方式,目前新的计算机基本上都采用EFI进行引导。这一块也是比较复杂的,但我觉得对于初学者,先不用去管,你只要知道,上电后,BIOS来完成硬件的检测,如果检测OK,就会调到grub引导程序,由grub来接替BIOS,开始下一步的启动工作。

第二步:Grub2(引导程序)引导

对于使用 EFI/UEFI 方式的系统, UEFI 固件读取它的设置来决定从哪里启动哪个 UEFI 应用。(例如, EFI 分区位于哪块磁盘或分区)

接下来,加载并运行第二阶段引导程序(又名引导管理器)。GRUB[GRand Unified Boot] 是 Linux 中最常使用的引导管理器。今天大部分使用的系统中都能找到它两个中的其中一个版本。

grub也在经历不断的发展,目前基本使用的都是grub2这个版本。我这里也就不讲grub这个版本了,只讲grub2这个版本。

grub2引导程序有个配置文件,通常在/boot/grub/grub.cfg

grub引导程序就是考读取这个配置文件的信息,并依照此配置信息来启动Linux操作系统的。这个配置文件,最主要的就是告诉grub引导程序,使用哪个版本的内核启动,这个内核在哪里,文件名是什么。理论上,硬盘上是可以存在多个内核文件的,根据你的需要,在grub引导程序中指定你需要的内核进行引导即可。另外,在这个文件中,也可以指定需要传递给内核的参数,内核也是一个程序,在启动中,针对同一个内核,可以给他传递不同的参数,传递不同的参数,内核就可能有不同的行为。还有一个重要的作用,就是配置ramdisk,通常我们也叫initrd或者initramfs镜像,这个镜像其实是一个小型的Linux系统的根文件系统,因为他足够小,所以整个文件系统可以全部加载在内存中运行。这里,可能有人为问,我的Linux系统不是已经安装在硬盘上了吗?为什么这里还需要一个小型的,可以再内存中运行的Linux系统?

很好的问题。

我来说说我的理解,前面好像提到过驱动是运行在内核中的,不过Linux有个特点,虽然驱动运行的时候,是作为内核级别来运行的,但是,驱动本身既可以直接编译在内核中,也是可以编译成独立的模块文件的,在需要的时候加载到内核中去执行就可以,Linux内核有非常非常多的驱动,广泛的支持各种各样的硬件,编译在内核中的好处,就是grub加载内核后,跳转到内核执行时,驱动就可以执行起来了,但如果把所有驱动都编译到内核中,内核就会变得非常非常大,而且很多驱动可能是你不需要的,这样就浪费了很多资源。initrd 或者 initramfs 其实就是为了避免这个问题,把需要的一些基本驱动,初始化脚本等,做成一个小型的Linux系统,让内核先把这个小型的Linux启动起来,帮助完成硬件检测、必要的内核模块加载、以及发现挂载真正硬盘上的根目录文件系统需要的设备。比如,硬盘的驱动,跟文件系统的驱动,也是在initrd中作为模块插入和驱动的,只有驱动了硬盘,内核才能挂载硬盘上真正的Linux跟文件系统。

到那可能大家又有一个问题,既然一开始硬盘都没有驱动,那内核和initrd又是怎么运行起来的,其实,大家都知道,程序运行之前,是要加载到内存中的,当加载到内存中以后,运行的时候,其实就不需要硬盘了,除非你运行的程序需要读写硬盘上的资料。grub2引导程序有自带硬盘驱动,可以从硬盘上读取配置文件,然后找到内核和initrd,把他们加载到内核中,然后跳到内核在内存中的位置开始执行,并且告诉内核,初始化的initrd在哪里,这时候,grub的使命就完成了,内核就启动了,但内核要挂载真正的跟文件系统,还需要重新在内核中驱动硬盘,所以他会先执行initrd,让initrd来进行包括硬盘初始化在内的很多初始化工作,然后再正式切换到硬盘上的真正的根文件系统。

一旦真正的根目录文件系统启动,为了显示用户界面,内核就会执行系统和服务管理器(init 或 systemd,进程号 PID 一般为 1)开始普通用户态的引导程序。

下图就是grub.cfg配置文件的部分摘录内容

menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-d047dd69-4d9e-454f-ae15-4f895f353014' { recordfail load_video gfxmode $linux_gfx_mode insmod gzio if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi insmod part_msdos insmod ext2 set root='hd0,msdos5' if [ x$feature_platform_search_hint = xy ]; then search --no-floppy --fs-uuid --set=root --hint-bios=hd0,msdos5 --hint-efi=hd0,msdos5 --hint-baremetal=ahci0,msdos5 d047dd69-4d9e-454f-ae15-4f895f353014 else search --no-floppy --fs-uuid --set=root d047dd69-4d9e-454f-ae15-4f895f353014 fi linux /boot/vmlinuz-5.15.0-53-generic root=UUID=d047dd69-4d9e-454f-ae15-4f895f353014 ro quiet splash $vt_handoff initrd /boot/initrd.img-5.15.0-53-generic}

其中,这两行,就是指定了内核和initrd,其中内核就是/boot/vmlinuz-5.15.0-53-generic, 而initrd就是/boot/initrd.img-5.15.0-53-generic。

linux /boot/vmlinuz-5.15.0-53-generic root=UUID=d047dd69-4d9e-454f-ae15-4f895f353014 ro quiet splash $vt_handoffinitrd /boot/initrd.img-5.15.0-53-generic

一般来说,Linux内核和initrd都放在/boot/目录下,如下图,这里就有两个内核版本,没有内核版本都有对应的initrd文件,你想使用那个内核,就可以再grub.cfg文件中进行指定即可。

存放内核和initrd的/boot/目录


第三步:加载和运行kernel内核

上面已有部分调,grub根据grub设定的内核映像所在路径,系统读取内存映像,并进行解压缩操作。此时,屏幕一般会输出“Uncompressing Linux”的提示。当解压缩内核完成后,屏幕输出“OK, booting the kernel”。

系统将解压后的内核放置在内存之中,并调用start_kernel()函数来启动一系列的初始化函数并初始化各种设备,完成Linux核心环境的建立。至此,Linux内核已经建立起来了,基于Linux的程序应该可以正常运行了。

从全局启动历程start_kernel开始,

内核完成的任务主要有:

硬件的特测硬件驱动的初始化,挂载根文件系统(根切换,如前面有讲,一般是先挂载initrd小型内存根文件系统,完成初步初始化后,在挂载硬盘上真正的根文件系统)启动init进程。内核在系统启动后的功能先提前介绍一下:进程的调度,内存管理,文件系统的管理,硬件驱动,网络等内核自身初始化完成后开始下一步第四步:system机制启动文件系统

前面提到,真正的根文件系统就是你安装在硬盘上的这个Linux根文件系统,虽然内核会通常用先挂载initrd进行必要的初始化,但大部分初始化工作,是由你硬盘上的真正的Linux根文件系统来完成的。其实内核和initrd,也是包含安装在硬盘上的,也就是说,也是在根文件系统里面,只是这几个文件比较特殊,是靠grub去加载内存中的,其实系统起来后,这两个文件已经没有什么用了,只要你不关机,只是这两个文件就算删除了,系统还是照常在正常运行的。当然,如果你删除了,那重启系统,肯定就起不来了,因为grub找不到内核文件了。

说的有点远了,回过头了看文件系统,前面提到,Linux的文件系统启动方式有好几种,目前最重要的是systemd启动机制,另外一种就是sysvinit,所以在重点介绍说明 systemd引导之前,先简单说明一下Sysvinit的引导方式,这里不讲的话,后续可能也没有适合的时候来讲了。最新的Ubuntu不会使用Sysvinit启动,但有些嵌入式系统还有在用,所以有必要简单介绍一下。

Systeminit(sysvinit)启动机制:

内核被加载执行到最后,就会挂载磁盘,执行磁盘上的应用程序,进行应用层的启动,第一个运行的程序便是/sbin/init,init进程执行rc.sysinit,在Systeminit启动模式下,rc.sysinit会读取/etc/inittab文件,并依据此文件来进行初始化工作。

其实/etc/inittab文件最主要的作用就是设定Linux的运行等级,其设定形式是“:id:5:initdefault:”,这就表明Linux需要运行在等级5上。

Linux的运行等级设定如下:

0:-halt 关机1:-single user mode 单用户模式2:-Multi-user,without NFS无网络支持的多用户模式  类似于下面的run level33:-Full multi-user mode 有网络支持的多用户模式4:-unused 保留,未使用5:-X11 有网络支持有X-Window支持的多用户模式6:- reboot 重新引导系统,即重启

通常,图形系统的Linux是运行在等级5,是怎么个运行法呢:

在Linux下/etc目录下,通常有rc0.d到rc6.d这样7个目录,如果你想深入了解这种启动方式,可以去安装一个旧一点的Ubuntu系统,比如Ubuntu 6.10之前的版本,但我不确定网络上是否还可以找到这样旧的版本,估计是很难了,所以我觉得不用去找了,听我这里讲讲就好了,这几个目录,如果你打开去看一下,都是一推脚本,根据运行级别的不同,系统会运行rc0.d到rc6.d中的相应的脚本程序,来完成相应的初始化工作和启动相应的服务,比如运行在级别5,系统就会去执行rc5.d中的脚本,里面的脚本一般都是S开头或者K开头的,比如S01.sh,S02.sh,假设这样两个脚本,就是先执行S01,再执行S02, 所以是有一定的执行顺序的,S就表示start,就是开机的时候执行的脚本,而K表示Kill,杀死的意思,一般是关机的时候执行的,也会有类似K01.sh, K02.sh这样的脚本,也会有一定的执行顺序,在开机执行的基本中,肯定就是启动各种服务,包括图形系统等,那关机的脚本,肯定就是杀死各种进程和服务,大概的逻辑就是这样的。如果你再在开机的时候执行一个脚本,也可以在这个目录下添加一个S99.sh的脚本,然后在这个脚本中执行你想要在开机是执行的任务,那系统开机的时候,就会自动执行到这个脚本,从而执行你脚本中的相关任务。

另外,还有一个脚本,/etc/rc.d/rc.local,你如果打开了此文件,里面有一句话,读过之后,你就会对此命令的作用一目了然:

# This script will be executed *after* all the other init scripts.# You can put your own initialization stuff in here if you don’t# want to do the full Sys V style init stuff.

rc.local就是在一切初始化工作后,Linux留给用户进行个性化的地方。你可以把你想设置和启动的东西放到这里。执行/bin/login程序,启动mingetty,进入登录状态,启动图形系统。刚才前面提到,你可以添加一个S99.sh来让系统开机时启动你的脚本,其实,你也可以修改rc.local这个脚本,把你想要开机执行的代码写到这个脚本,也是可以的。

Systemd启动

接下里来,我们来讲systemd,关于 systemd,在Linux中,是一个非常非常重要的机制,Systemd是一个系统管理守护进程、工具和库的集合,用于取代System V初始进程。Systemd的功能是用于集中管理和配置类UNIX系统。不过这里也只是结合启动过程简单介绍,让大家知道上systemd启动机制是如何启动系统的,即可。后续等你详细了解后,你甚至可以添加自己的启动项到systemd里面去。之前也反复提到过,学习Linux,最重要是要动手,所以看完本章,你可以尝试自己添加一个systemd服务,让你的某个程序可以开机启动。

上面介绍的sysvinit看起来也不错,也比较简单,为何还需要systemd,systemd它的主要目标之一是允许系统启动时多个任务尽可能并行,从而缩短系统的启动时间,(而 sysvinit 并非如此,sysvinit是串行的,一个任务执行完,才能执行下一个任务,CPU很多时候再空闲中,效率比较低,导致系统启动时间 一般比较慢,因为它每次只启动一个进程,而且会检查彼此之间是否有依赖,在启动其它服务之前还要等待守护进程启动),充当运行中系统动态资源管理的角色。

另外,systemd可以让服务只在需要的时候启动,而不是系统启动时毫无缘由地启动全部服务,可以防止系统资源不必要的消耗。

首先,systemd 挂载在 /etc/fstab 中配置的文件系统,包括内存交换文件或分区。据此,systemd 必须能够访问位于 /etc 目录下的配置文件,包括它自己的。systemd 借助其配置文件 /etc/systemd/system/default.target 决定 Linux 系统应该启动达到哪个状态(或目标态target)。default.target 是一个真实的 target 文件的符号链接。对于桌面系统,其链接到 graphical.target,该文件相当于旧式 systemV init 方式的 runlevel 5。对于一个服务器操作系统来说,default.target 更多是默认链接到 multi-user.target, 相当于 systemV 系统的 runlevel 3。 emergency.target 相当于单用户模式。

Systemctl是一个systemd工具,主要负责控制systemd系统和服务管理器。

下面将简单介绍启动中的4个关键步骤:

1) 执行默认的target:

systemd 执行默认target 配置,配置文件/etc/systemd/system/default.target,它一般是一个软链接,指向multi-user.target或者graphical.target。可以通过如下命令查询和修改default.target:

systemctl get-default #查询

systemctl set-default multi-user.target #设置

2) 执行依赖的target

systemd 执行启动所依赖的目标basic.target和sysinit.target初始化系统

通过查看cat /etc/systemd/system/default.target查看依赖:

[Unit]Description=Multi-User SystemDocumentation=man:systemd.special(7)Requires=basic.targetConflicts=rescue.service rescue.targetAfter=basic.target rescue.service rescue.targetAllowIsolate=yes

After指定的target需要在default.target之前运行。如果想上面这样,systemd就会先去执行basic.target rescue.service rescue.target。

3) 执行用户相关的target

systemd 启动multi-user.target 下的本机与服务器服务,由于default.target指向multi-user.target,那么这一步就启动对应的multi-user.target相关的服务。它的服务存在于 /etc/systemd/system/multi-user.target.wants 目录中,这个目录下很很多的服务,systemd就会一个一个去启动相关的服务:

lrwxrwxrwx. 1 root root 37 Aug 8 2018 acpid.service -> /usr/lib/systemd/system/acpid.servicelrwxrwxrwx. 1 root root 35 Aug 8 2018 atd.service -> /usr/lib/systemd/system/atd.servicelrwxrwxrwx. 1 root root 38 Aug 8 2018 auditd.service -> /usr/lib/systemd/system/auditd.servicelrwxrwxrwx. 1 root root 37 Aug 8 2018 crond.service -> /usr/lib/systemd/system/crond.servicelrwxrwxrwx. 1 root root 37 Aug 8 2018 kdump.service -> /usr/lib/systemd/system/kdump.servicelrwxrwxrwx. 1 root root 46 Aug 8 2018 libstoragemgmt.service -> /usr/lib/systemd/system/libstoragemgmt.servicelrwxrwxrwx 1 root root 36 Aug 13 2018 ntpd.service -> /usr/lib/systemd/system/ntpd.servicelrwxrwxrwx. 1 root root 39 Aug 8 2018 postfix.service -> /usr/lib/systemd/system/postfix.servicelrwxrwxrwx. 1 root root 40 Aug 8 2018 remote-fs.target -> /usr/lib/systemd/system/remote-fs.targetlrwxrwxrwx. 1 root root 46 Aug 8 2018 rhel-configure.service -> /usr/lib/systemd/system/rhel-configure.servicelrwxrwxrwx. 1 root root 39 Aug 8 2018 rpcbind.service -> /usr/lib/systemd/system/rpcbind.servicelrwxrwxrwx. 1 root root 39 Aug 8 2018 rsyslog.service -> /usr/lib/systemd/system/rsyslog.servicelrwxrwxrwx. 1 root root 36 Aug 8 2018 sshd.service -> /usr/lib/systemd/system/sshd.servicelrwxrwxrwx. 1 root root 37 Aug 8 2018 tuned.service -> /usr/lib/systemd/system/tuned.servicelrwxrwxrwx. 1 root root 35 Aug 8 2018 vdo.service -> /usr/lib/systemd/system/vdo.service

4) 执行rc.local服务

systemd 也会执行/etc/rc.d/rc.local。systemd是可以兼容systemv init中的rc.local配置的,通过rc-local.service来实现兼容的,systemd在启动的很早就会判断/etc/rc.local是否存在并且是可执行的,如果满足条件,那么systemd会调用/usr/lib/systemd/system-generators/下面的小程序来把rc-local.service服务加入到default.target中来。这样在后面的执行时就会触发rc.local的运行:

[Unit]Description=/etc/rc.d/rc.local CompatibilityConditionFileIsExecutable=/etc/rc.d/rc.localAfter=network.target[Service]Type=simpleExecStart=/etc/rc.d/rc.local startTimeoutStartSec=0TimeoutStopSec=1RemainAfterExit=yes

所以,如果再systemd机制下,如果你想开机时让系统执行一个程序,你可以自己写一个systemd的service,或者也可以直接修改/etc/rc.d/rc.local.

第五步:图形系统启动和系统登录

1) 启动命令

systemctl isolate graphical.target

2) 启动过程:

文件:/etc/systemd/system/graphical.target
来自:systemd包
内容:

[Unit]Description=Graphical InterfaceDocumentation=man:systemd.special(7)Requires=multi-user.targetWants=display-manager.serviceConflicts=rescue.service rescue.targetAfter=multi-user.target rescue.service rescue.target display-manager.serviceAllowIsolate=yes

文件:/etc/systemd/system/display-manager.service
来自:安装lightdm包时自己主动生成
内容:链接到/lib/systemd/system/lightdm.service

文件:/lib/systemd/system/lightdm.service
来自:lightdm包
内容:

[Unit]Description=Light Display ManagerDocumentation=man:lightdm(1)Conflicts=getty@tty1.serviceAfter=systemd-user-sessions.service getty@tty1.service plymouth-quit.service livesys-late.service[Service]ExecStart=/usr/sbin/lightdmRestart=alwaysIgnoreSIGPIPE=noBusName=org.freedesktop.DisplayManager[Install]Alias=display-manager.service

文件: /user/sbin/lightdm
来自:lightdm包
内容:二进制可运行文件。启动图形界面。

所以一层一层执行下来,最后就执行到了/usr/bin/lightdm这个程序,这个程序就是Ubuntu的图形登录界面,这样,整个图形系统就启动了。

另外,systemd机制也提供了其他一些命令,比如可以来分析和统计启动各个服务消耗的时间,如有需要,可以根据分析的结果来优化系统,比如有些服务启动时间过长,但你又不需要这个服务,那么就可以禁止这个服务,让这个服务下次启动时不再启动。

分析服务启动时间的方法如下:

systemd-analyze blame #列举出每个服务的启动耗时,前面第一列就是这个服务启动使用的时间。电脑

systemd-analyze plot > boot.svg #图形化展示启动耗时(生成的图很大,贴上去也看不清楚,我这里就不贴了,你们可以动手试一下)

如何禁止一个服务,启动一个服务,都有相关的命令,我这里就不详细讲了,这个小结我也不想讲的太复杂,不行牵扯太多的细节,只是想把Linux的启动过程做一个简单梳理,让初学者大概了解这个过程即可,其实上面说的,我觉细节可以还不够完整,很多细节没有讲,我计划后面可以录个视频来讲解一下,这样可能会比较清楚。关于systemd, 是Linux根文件系统启动的关键,初学者也可以再网络上进行搜索,继续深入学习,我这里计算抛砖引玉了。

以上写的有点乱,我也不确定初始学时候可以看明白,即使不是很明白,应该也大概有个印象把,我们后面还有规划,会讲如何自己自作一个Linux系统,等你自己会制作Linux系统了,可能就完全明白整个过程了,慢慢学,欢迎持续关注我的文章。


电脑