欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

Linux是如何启动的?

程序员文章站 2022-10-25 23:55:24
参考资料: An introduction to the Linux boot and startup processes 这篇随笔,可以理解为是对这篇英文文章的翻译与个人理解、笔记的整合。 扩展阅读: GNU GRUB - Wikipedia systemd - Wikipedia BIOS in ......

参考资料:

an introduction to the linux boot and startup processes

这篇随笔,可以理解为是对这篇英文文章的翻译与个人理解、笔记的整合。

扩展阅读:

gnu grub - wikipedia

systemd - wikipedia

bios interrupt call - wikipedia

multiboot specification - wikipedia

chain loading - wikipedia

master boot record - wikipedia

best couple of 2016: display manager and window manager

浅析 linux 初始化 init 系统,第 1 部分:sysv init

浅析 linux 初始化 init 系统,第 2 部分:upstart

浅析 linux 初始化 init 系统,第 3 部分:systemd

 

前言

理解linux是如何启动的对于配置和针对linux启动失败的排错是至关重要的。

本篇文章大体上把linux启动过程分成了两部分:引导(boot)和启动(startup)。

引导:从计算机开机至内核初始化完毕启动systemd程序的这个阶段。

启动:从systemd程序启动至系统完全启动完毕的这个阶段。

引导阶段主要涉及了引导装载程序(boot loader),对于我所学习的centos系列的系统来说,主要涉及的均是grub这个引导装载程序。

  • 0.x系列的版本,是老的版本,也叫做grub 1/legacy,主要用于以前的linux发行版,例如centos 5/6。
  • 2.x系列的版本,是新的版本,也叫做grub 2,现在主流的例如centos 7上会使用它。虽然是新版的grub,版本号差别也不是很大,但是grub 2几乎是完全重写的grub!

启动阶段主要涉及了系统初始化进程的方式(init 进程/系统)。

  • centos 5:system v init,可简称system v或者sysv。
  • centos 6:upstart。
  • centos 7:systemd。

这3种进程初始化方式,配置文件的写法有较大的不同。

本篇随笔,所涉及的引导和启动的版本是当前的主流版本,即grub 2和systemd。下文如果没特别说明或者grub单独出现的话,那么grub就表示grub 2了。

将引导和启动的过程,更细致的划分的话,如下所示。

  1. boot
    1. bios post
    2. boot loader(grub 2)
      1. stage 1
      2. stage 1.5
      3. stage 2
    3. kernel initialization
  2. startup
    1. systemd

 

引导过程(boot)

想要启动引导过程的话,主要有两种方式。一种是计算机处于关机状态,用户物理性开机;另一种是计算机处于开机状态,用户通过gui或者cli程序性重启(当然如果有物理重启按钮,也可以物理性重启)。

译者注:我也不晓得为何作者要说这句简单的话。

bios post

引导的第一步和linux系统是无关的,所有的os都会有这个步骤。这步是bios中提供的post(power on self test,开机自检)功能,主要用于检测硬件是否可正常工作。

如果post检测失败的话,则引导终止,后续的步骤也不会再有了,系统启动失败。

post检测成功,确保硬件可正常工作后,bios会释出bios中断(bios interrupt,详见扩展阅读)int 13h,它用于在所有已连接的可引导设备上定位引导扇区(boot sector)。第一个找到的包含有效引导记录(boot record)引导扇区会被加载入内存当中,并且将控制权传递给从引导扇区中加载的代码。

引导扇区是引导装载程序的第一个阶段(stage)。现代大多数的linux发行版中采用三种引导装载程序,分别是lilo、grub 1和grub 2,grub 2是当前阶段最新也是最常用的引导装载程序。

grub 2

grub 2的全称是“grand unified bootloader, version 2”,它使得计算机可以找到os的内核文件并将其载入内存中。

grub被设计用于兼容多引导规范(multiboot specification,详见扩展阅读),这个规范使得它可以引导许多的linux和*os;它还可以链式加载(chain load,详见扩展阅读)专有(proprietary)os的引导记录。

grub还可以允许用户从某个linux发行版中选择不同的内核来引导。这样在某个内核更新发生了错误,或者更新使得某些重要的软件使用出现异常的时候,就可以引导之前版本的内核来正常工作。

grub 1的配置文件是/boot/grub/grub.conf,grub 2的配置文件是/boot/grub2/grub.cfg。红帽系的linux,从fedora 15和centos/rhel 7开始将grub升级到了grub 2版本。grub 2和grub 1在功能上是相同的,但是经过了几乎完全的重写,所以是要优于grub 1的。

grub 1和2都是有三个阶段(stage),本文会大致阐述这三个阶段。不过,关于grub具体是如何配置的,不在本文的范围中。

grub 2在官方中并不是使用这3个阶段,即stage 1/1.5/2,这个应该是grub 1所使用的,不过使用这3个阶段来阐述grub 2,也是可以的。

译者注:在扩展阅读中,grub 2应该是4个阶段。

stage 1

当bios post的自检阶段通过后,bios从已连接的设备上寻找可用的引导记录,引导记录一般位于磁盘的mbr上。stage 1的引导记录叫做boot.img。

mbr位于磁盘的第一个扇区sector 0,大小为512b,扣除64b的分区信息和2个字节的boot signature,仅剩下446b的空间。这个空间非常小,所以boot.img的代码量也必须很小,它不会包含文件系统的驱动(即没有识别文件系统相关的代码),boot.img的唯一作用就是找到并加载stage 1.5。为了完成stage 1的任务,stage 1.5的位置必须位于stage 1自身和第一个分区之间。如图所示。

by shmuel csaba otto traian, cc by-sa 3.0, https://commons.wikimedia.org/w/index.php?curid=28427221

Linux是如何启动的?

将stage 1.5加载入内存后,stage 1就将控制权转交给了stage 1.5了。

译者注:在我的centos 7系统上,该文件位于/boot/grub2/i386-pc/boot.img,大小为512b,理论上是应该小于446b才对。

stage 1.5

如上所述,stage 1.5位于stage 1和第一个分区之间,即sector 1~62,总容量为512*62=31,744。

译者注:上面的图片stage 1.5的容量为32,256b,比31,744多了一个512b,不懂为什么。

这个阶段的文件是core.img,作者的该文件大小是25,389b,我的是26,664(/boot/grub2/i386-pc/core.img)。可见该阶段还可以有许多的剩余容量。

这个阶段主要是包含了各种各样的文件系统驱动程序,例如ext家族、xfs、ntfs等。只有具备了文件系统驱动,才可以基于文件的路径和名称(例如/boot/vmlinuz-3.10.0-862.el7.x86_64)来查找/加载某个文件。

剩余的容量,应该是让开发人员自己扩展、增加想要加载的文件系统驱动程序的。

这个阶段所加载的文件系统驱动,就是用于识别stage 2的文件所在的文件系统的。

grub 2的stage 1.5中的core.img要比grub 1版本中的更加复杂和强大,因此grub 2中的stage 2可以位于标准的ext系列文件系统,但不能是在lvm分区上。

标准的stage 2的文件的位置是在/boot文件系统上,准确地说,是/boot/grub2。

stage 2

该阶段的文件位于/boot/grub2目录中。该阶段不算前2个阶段有镜像文件(image file:boot.img和core.img),它主要由许多运行时按需加载的内核模块文件构成(位于/boot/grub2/i386-pc目录)。

该阶段的主要功能是定位并加载内核文件,而后将控制权转交给内核。内核文件的名称均以vmlinuz开头,可在/boot目录下找到当前系统已安装的内核。

[root@c7 ~]# ls -l /boot/vmlinuz-*
-rwxr-xr-x. 1 root root 6224704 sep 27  2018 /boot/vmlinuz-0-rescue-341d78e441db4d8b985b51fc31f40be9
-rwxr-xr-x. 1 root root 6224704 apr 21  2018 /boot/vmlinuz-3.10.0-862.el7.x86_64
[root@c7 ~]# file /boot/vmlinuz-*
/boot/vmlinuz-0-rescue-341d78e441db4d8b985b51fc31f40be9: linux kernel x86 boot executable bzimage, version 3.10.0-862.el7.x86_64 (builder@kbuilder.dev.centos.org) #1 smp , ro-rootfs, swap_dev 0x5, normal vga
/boot/vmlinuz-3.10.0-862.el7.x86_64:                     linux kernel x86 boot executable bzimage, version 3.10.0-862.el7.x86_64 (builder@kbuilder.dev.centos.org) #1 smp , ro-rootfs, swap_dev 0x5, normal vga

grub有一个预引导菜单,可以让用户选择想要引导的内核,包含了救援(rescue)模式,配置得当的话还包含恢复(recovery)模式。

kernel

内核是一种可自我展开的压缩格式的文件,它们和ram disk镜像文件、硬盘的设备映射都位于/boot目录下。

/boot/initramfs-0-rescue-341d78e441db4d8b985b51fc31f40be9.img
/boot/initramfs-3.10.0-862.el7.x86_64.img
/boot/system.map-3.10.0-862.el7.x86_64

当内核被载入内存执行,自我展开完毕后,它会启动systemd进程。此时引导过程就结束了,正式进入启动过程。

注:引导过程的结束时,linux仍然无法执行任何生产任务。因为生产任务是需要具体的进程来完成的,而systemd进程只是所有生产任务进程的最上级父进程(系统的第一个进程)。

 

启动过程(startup)

systemd

systemd是linux系统中的第一个进程,即最*的父进程。它的任务是启动系统中的其他进程使得linux可以进入生产环境得状态。包含挂载文件系统、启动和管理服务。和启动顺序无关的systemd任务不在本文的讨论范围之内。

首先,systemd根据/etc/fstab挂载文件系统,包含swap文件和分区。此时systemd就可以访问位于/etc目录下的配置文件了,包含自己的配置文件。它使用自己的配置文件(/etc/systemd/system/default.target)来决定要将系统启动为哪种状态(state)或者target。default.target仅仅是一个到真实target文件的字符链接文件。对于桌面工作站来说,是链接到graphical.target(相当于system v init中的运行级别5)。对于服务器来说,一般是链接到multi-user.target(相当于system v init中的运行级别3)。emergency.target则相当于单用户模式。

注意:target和服务是systemd的单元(unit)。

table 1是systemd的target和system v init的运行级别的对照表格。systemd提供了target别名(alias)用于向后兼容。target别名允许脚本和管理员使用sysv的命令(例如init 3)来切换运行级别,当然,这些命令会传递给systemd解析与执行。

table 1: comparison of systemv runlevels with systemd targets and some target aliases.

systemv runlevel

systemd target

systemd target aliases

description

  halt.target  

halts the system without powering it down.

0 poweroff.target runlevel0.target

halts the system and turns the power off.

s emergency.target  

single user mode. no services are running; filesystems are not mounted. this is the most basic level of operation with only an emergency shell running on the main console for the user to interact with the system.

1 rescue.target runlevel1.target

a base system including mounting the filesystems with only the most basic services running and a rescue shell on the main console.

2   runlevel2.target

multiuser, without nfs but all other non-gui services running.

3 multi-user.target runlevel3.target

all services running but command line interface (cli) only.

4   runlevel4.target

unused.

5 graphical.target runlevel5.target

multi-user with a gui.

6 reboot.target runlevel6.target

reboot.

  default.target  

this target is always aliased with a symbolic link to either multi-user.target or graphical.target. systemd always uses the default.target to start the system. the default.target should never be aliased to halt.target, poweroff.target, or reboot.target.

每个target在其配置文件中会说明依赖关系,有依赖就有启动的先后顺序之分,被依赖方启动失败,那么依赖方也会启动失败(依赖方依赖于被依赖方)。这些依赖一般是系统要运行于某个功能级别下(例如web服务器、db服务器、缓存服务器等)所需要的服务。当target配置文件中所有的依赖都正常启动完毕后,系统会运行于那个target级别下。

systemd也会去查找sysv配置目录下是否含有启动脚本,如果有的话,也会加载的。基本上在centos 7上,服务已经都使用systemd风格的配置文件来管理了,不过在sysv配置目录下还有一个网络相关的配置文件,可能只是用作一个学习案例吧,或者网络服务在centos 7上依然使用sysv管理(概率较低)。

[root@c7 ~]# ls -l /etc/rc.d/init.d/
total 40
-rw-r--r--. 1 root root 18104 jan  3  2018 functions
-rwxr-xr-x. 1 root root  4334 jan  3  2018 netconsole
-rwxr-xr-x. 1 root root  7293 jan  3  2018 network
-rw-r--r--. 1 root root  1160 apr 11  2018 readme

接下来我们来看一张文本图,figure 1。这张图来源于bootup(7)。展示了systemd的启动流程。

           local-fs-pre.target
                    |
                    v
           (various mounts and   (various swap   (various cryptsetup
            fsck services...)     devices...)        devices...)       (various low-level   (various low-level
                    |                  |                  |             services: udevd,     api vfs mounts:
                    v                  v                  v             tmpfiles, random     mqueue, configfs,
             local-fs.target      swap.target     cryptsetup.target    seed, sysctl, ...)      debugfs, ...)
                    |                  |                  |                    |                    |
                    \__________________|_________________ | ___________________|____________________/
                                                         \|/
                                                          v
                                                   sysinit.target
                                                          |
                     ____________________________________/|\________________________________________
                    /                  |                  |                    |                    \
                    |                  |                  |                    |                    |
                    v                  v                  |                    v                    v
                (various           (various               |                (various          rescue.service
               timers...)          paths...)              |               sockets...)               |
                    |                  |                  |                    |                    v
                    v                  v                  |                    v              rescue.target
              timers.target      paths.target             |             sockets.target
                    |                  |                  |                    |
                    v                  \_________________ | ___________________/
                                                         \|/
                                                          v
                                                    basic.target
                                                          |
                     ____________________________________/|                                 emergency.service
                    /                  |                  |                                         |
                    |                  |                  |                                         v
                    v                  v                  v                                 emergency.target
                display-        (various system    (various system
            manager.service         services           services)
                    |             required for            |
                    |            graphical uis)           v
                    |                  |           multi-user.target
|                  |                  |
                    \_________________ | _________________/
                                      \|/
                                       v
                             graphical.target

sysinit.target和basic.target可以看作是一个检测点(checkpoint)。尽管systemd的设计目标之一是并行启动系统服务,但是依然存在某些特定的服务和target是需要在其他服务和target启动之前启动的。直到所有被该检测点依赖的服务和target被满足后,那么该检测点才可以通过。

因此直到所有被依赖的单元完成后,才可以到达sysinit.target。所有的那些单元,挂载文件系统、配置swap文件、启动udev、建立随机生成器种子(random genorator seed)、初始化低级设备、如果有文件系统需要加密的话则配置加密服务。不过在sysinit.target内部,上述的这些单元是可以并行执行的。也就是说systemd支持单元在某个target内部的并行执行。

sysinit.target完成了一些底层的功能,即较接近硬件层,慢慢地会往高层发展,接近用户层,即basic.target。这个阶段涉及的unit,如图所示。

最后就可以启动multi-user.target和graphical.target这两个我们熟悉的用户级别的target了。注意:multi-user.target必须启动成功,才可以启动graphical.target。

figure 1中,加粗带下划线的target,是常用的启动target,到达这些target,就表示系统已经启动完毕了。multi-user.target启动完毕后展示了文本模式的登录界面,而graphical.target则展示了图形化的登陆界面,想了解关于图形化登陆界面的话,可查阅扩展阅读中的best couple of 2016: display manager and window manager。