第 3 章 系统初始化

目录

3.1. 启动过程概述
3.1.1. 第一阶段:UEFI
3.1.2. 第二阶段:引载加载程序
3.1.3. 第三阶段:迷你 Debian 系统
3.1.4. 第四阶段:常规 Debian 系统
3.2. Systemd 初始化
3.2.1. 主机名
3.2.2. 文件系统
3.2.3. 网络接口初始化
3.3. 内核消息
3.4. 系统消息
3.5. 系统管理
3.6. 其它系统监控
3.7. 定制 systemd
3.7.1. 套接字激活
3.8. udev 系统
3.8.1. 内核模块初始化

作为系统管理员,粗略地了解 Debian 系统的启动和配置方式是明智的。尽管准确的细节在安装的软件包及对应的文档中,但这些知识对我们大多数人来说都是必须掌握的。

下面是 Debian 系统初始化的要点概述。由于 Debian 系统在不断发展,您应该参考最新的文档。

计算机系统从上电事件到能为用户提供完整的操作系统(OS)功能为止,需要经历几个阶段的启动过程

为简便起见,笔者将讨论范围限定在具有默认安装的典型 PC 平台上。

典型的启动过程像是一个四级的火箭。每一级火箭将系统控制权交给下一级。

当然,这些阶段可以有不同的配置。比如,你编译了自己的内核,则可能会跳过迷你 Debian 系统的步骤。因此,在读者亲自确认之前,请勿假定自己系统的情况也是如此。

Unified Extensible Firmware Interface (UEFI) 统一可扩展固件接口 定义了启动管理器作为 UEFI 规范的一部分。当一个计算机打开电源,启动管理器是启动流程的第一阶段,它检查启动配置并基于启动配置的设置,执行特定的操作系统引导加载程序或操作系统内核(通常是引导加载程序)。启动配置通过变量存储在 NVRAM,变量包括指示操作系统引导加载程序或操作系统内核的文件系统路径的变量。 EFI system partition (ESP) EFI 系统分区 是一个数据存储设备分区,在计算机里用来遵照 UEFI 规范。当计算机打开电源时,由 UEFI 固件来访问,它存储了 UEFI 应用程序和这些应用程序运行所需要的文件,包括操作系统的引导加载程序。(在老的 PC 系统,存放在 MBR 里的 BIOS 可以用来代替。)

引导加载程序是启动过程的第二阶段,由 UEFI 启动。引导加载程序将系统内核映像和 initrd 映像加载到内存并将控制权交给它们。initrd 映像是根文件系统映像,其支持程度依赖于所使用的引导加载程序。

Debian 系统通常使用 Linux 内核作为默认的系统内核。当前的 5.x Linux 内核的 initrd 映像在技术上是 initramfs(初始 RAM 文件系统)映像。

有许多引导加载程序和配置选项存在。


[警告] 警告

假如没有从 grub-rescue-pc 软件包中的映像制作出来的可引导修复盘(U盘、CD 或软盘),请勿玩弄引导加载程序。即使硬盘上没有可正常工作的引导加载程序,可引导修复盘也能引导你的系统。

GRUB 2 的菜单配置文件位于 /boot/grub/grub.cfg,它的菜单条目的关键部分看起来像:

menuentry 'Debian GNU/Linux' ... {
        load_video
        insmod gzio
        insmod part_gpt
        insmod ext2
        search --no-floppy --fs-uuid --set=root fe3e1db5-6454-46d6-a14c-071208ebe4b1
        echo    'Loading Linux 5.10.0-6-amd64 ...'
        linux   /boot/vmlinuz-5.10.0-6-amd64 root=UUID=fe3e1db5-6454-46d6-a14c-071208ebe4b1 ro quiet
        echo    'Loading initial ramdisk ...'
        initrd  /boot/initrd.img-5.10.0-6-amd64
}

对于这部分的 /boot/grub/grub.cfg,这个菜单条目的意义如下。


[提示] 提示

通过设置在 /etc/default/grubGRUB_BACKGROUND 变量指向到图像文件,或者把图像文件本身放入 /boot/grub/,你能够定制 GRUB 的启动图像。

参见 “info grub” 及 grub-install(8)。

迷你 Debian 系统是启动流程的第三阶段,由引导加载程序启动。它会在内存中运行系统内核和根文件系统。这是启动流程的一个可选准备阶段。

[注意] 注意

“迷你 Debian 系统”是笔者自创的术语,用于在本文档中描述启动流程的第三个阶段。这个系统通常被称为 initrd 或 initramfs 系统。内存中类似的系统在 Debian 安装程序中使用。

/init 程序是内存中的根文件系统上执行的第一个程序。这个程序在用户空间把内核初始化,并把控制权交给下一阶段。迷你 Debian 系统能够在主引导流程之前添加内核模块或以加密形式挂载根文件系统,使引导流程更加灵活。

  • 如果 initramfs 是由 initramfs-tools 创建,则"/init" 程序是一个 shell 脚本程序。

    • 通过给内核添加 “break=init" 等启动参数,你可以中断这部分启动流程以获取 root shell。更多中断条件请参见 ”/init“ 脚本。这个 shell 环境已足够成熟,你可通过它很好地检查机器的硬件。

    • 迷你 Debian 系统中可用的命令是精简过的,且主要由一个称为 busybox(1) 的 GNU 工具提供。

  • 如果 initramfs 是由 dracut 创建,则 "/init" 程序是一个二进制 systemd 程序。

    • 迷你 Debian 系统中可用的命令是一个精简过的 systemd(1) 环境。

[小心] 小心

当在一个只读的根文件系统上时,使用 mount 命令需要添加 -n 选项。

常规 Debian 系统是启动流程的第四阶段,由迷你 Debian 系统启动。迷你 Debian 系统的内核在此环境下继续运行。根文件系统将由内存切换到实际的硬盘文件系统上。

init 程序是系统执行的第一个程序(PID=1),它启动其它各种程序以完成主引导流程。init 程序的默认路径是 ”/sbin/init“,但可通过内核启动参数修改,例如 ”init=/path/to/init_program"。

在 Debian 8 jessie(2015 年发布)版本后,"/sbin/init" 是一个到 "/lib/systemd/systemd" 的符号链接。

[提示] 提示

你的系统中实际使用的 init 命令可以使用 “ps --pid 1 -f” 命令确认。


[提示] 提示

有关启动流程加速的最新信息,请参见 Debian 维基:启动流程加速词条。

本节描述系统是怎样通过 PID=1systemd(1) 程序来启动(即初始化进程)。

systemd 初始化进程基于单元配置文件 (参见 systemd.unit(5)) 来并行派生进程,这些单元配置文件使用声明样式来书写,代替之前的类 SysV 的过程样式。这些单元配置文件从下面的一系列路径来加载 (参见 systemd-system.conf(5)) :

派生的进程被放在一个单独的 Linux control groups,在单元后命名,它们属于一个私有的 systemd 层级结构(参见 cgroups第 4.7.4 节 “Linux 安全特性”)。

单元配置文件从下列一系列路径中加载(参见 systemd-system.conf(5)):

  • "/lib/systemd/system": OS 默认配置文件

  • "/etc/systemd/system": 系统管理员的配置文件,它将忽略操作系统默认的配置文件

  • "/run/systemd/system": 运行时产生的配置文件,它将忽略安装的配置文件

他们的相互依赖关系通过"Wants=", "Requires=", "Before=", "After=", … 等指示来配置,(参见 systemd.unit(5) 里的 "MAPPING OF UNIT PROPERTIES TO THEIR INVERSES")。 资源控制也是被定义 (参见 systemd.resource-control(5)).

根据单元配置文件的后缀来区分它们的类型:

  • *.service 描述由 systemd 控制和监管的进程.参见 systemd.service(5).

  • *.device 描述在 sysfs(5) 里面作为 udev(7) 设备树展示的设备。参见 systemd.device(5).

  • *.mount 描述由 systemd 控制和监管的文件系统挂载点。参见 systemd.mount(5).

  • *.automount 描述由 systemd 控制和监管的文件系统自动挂载点。参见 systemd.automount(5).

  • *.swap 描述由 systemd 控制和监管的 swap 文件或设备。参见 systemd.swap(5).

  • *.path 描述被 systemd 监控的路径,用于基于路径的活动。参见 systemd.path(5).

  • *.socket 描述被 systemd 控制和监管的套接字,用于基于套接字的活动。参见 systemd.socket(5).

  • *.timer 描述被 systemd 控制和监管的计时器,用于基于时间的活动。参见 systemd.timer(5).

  • *.slice 管理 cgroups(7) 的资源。参见 systemd.slice(5).

  • *.scope 使用 systemd 的总线接口来程序化的创建,用以管理一系列系统进程。 参见 systemd.scope(5).

  • *.target 把其它单元配置文件分组,在启动的时候,来创建同步点。参见systemd.target(5).

系统启动时(即,init),systemd 进程会尝试启动"/lib/systemd/system/default.target(通常是到"graphical.target"的符号链接)。首先,一些特殊的 target 单元(参见 systemd.special(7)),比如 "local-fs.target"、"swap.target"和"cryptsetup.target"会被引入以挂载文件系统。之后,其它 target 单元也会根据单元依赖关系而被引入。详细情况,请阅读 bootup(7)。

systemd 提供向后兼容的功能。在 "/etc/init.d/rc[0123456S].d/[KS]name" 里面的 SysV 风格的启动脚本仍然会被分析;telinit(8) 会被转换为 systemd 的单元活动请求。

[小心] 小心

模拟的运行级别 2 到 4 全部被符号链接到了相同的“multi-user.target”。

硬盘和网络文件系统的挂载选项可以在 "/etc/fstab" 中设置,参见 fstab(5) 和 第 9.6.7 节 “通过挂载选项优化文件系统”

加密文件系统的配置设置在“/etc/crypttab”中。参见 crypttab(5)

软 RAID 的配置 mdadm(8) 设置在 "/etc/mdadm/mdadm.conf". 参见 mdadm.conf(5).

[警告] 警告

每次启动的时候,在挂载了所有文件系统以后,"/tmp", "/var/lock", 和 "/var/run" 中的临时文件会被清空。

在控制台上显示的内核错误信息,能够通过设置他们的阈值水平来配置。

# dmesg -n3

systemd 下, 内核和系统的信息都通过日志服务 systemd-journald.service (又名 journald)来记录,放在"/var/log/journal"下的不变的二进制数据,或放在"/run/log/journal/"下的变化的二进制数据.这些二进制日志数据,可以通过 journalctl(1) 命令来访问。例如,你可以显示从最后一次启动以来的日志,按如下所示:

$ journalctl -b

systemd 下,系统日志工具 rsyslogd(8) 可以被卸载。如果安装了它,它会改变它的行为来读取易失性二进制日志数据(代替在 systemd 之前默认的 "/dev/log")并创建传统的永久性 ASCII 系统日志数据。"/etc/default/rsyslog" 和 "/etc/rsyslog.conf" 能够自定义日志文件和屏幕显示。参见 rsyslogd(8) 和 rsyslog.conf(5),也可以参见第 9.3.2 节 “日志分析”

systemd 不仅仅提供系统初始化,还用 systemctl(1) 命令提供通用的系统管理操作。

表 3.6. 典型的 systemctl 命令片段列表

操作 命令片段
列出所有 target 单元配置 "systemctl list-units --type=target"
列出所有 service 单元配置 "systemctl list-units --type=service"
列出所有单元配置类型 "systemctl list-units --type=help"
列出内存中所有 socket 单元 "systemctl list-sockets"
列出内存中所有 timer 单元 "systemctl list-timers"
启动 "$unit" "systemctl start $unit"
停止 "$unit" "systemctl stop $unit"
重新加载服务相关的配置 "systemctl reload $unit"
停止和启动所有 "$unit" "systemctl restart $unit"
启动 "$unit" 并停止所有其它的 "systemctl isolate $unit"
转换到 "图形" (图形界面系统) "systemctl isolate graphical"
转换到 "多用户" (命令行系统) "systemctl isolate multi-user"
转换到 "应急模式" (单用户命令行系统) "systemctl isolate rescue"
向"$unit"发送杀死信号 "systemctl kill $unit"
检查"$unit"服务是否是活动的 "systemctl is-active $unit"
检查"$unit"服务是否是失败的 "systemctl is-failed $unit"
检查"$unit|$PID|device"的状态 "systemctl status $unit|$PID|$device"
显示"$unit|$job"的属性 "systemctl show $unit|$job"
重设失败的"$unit" "systemctl reset-failed $unit"
列出所有单元服务的依赖性 "systemctl list-dependencies --all"
列出安装在系统上的单元文件 "systemctl list-unit-files"
启用 "$unit" (增加符号链接) "systemctl enable $unit"
禁用 "$unit" (删除符号链接) "systemctl disable $unit"
取消遮掩 "$unit" (删除到 "/dev/null" 的符号链接) "systemctl unmask $unit"
遮掩 "$unit" (增加到 "/dev/null" 的符号链接) "systemctl mask $unit"
获取默认的 target 设置 "systemctl get-default"
设置默认 target 为"graphical" (图形系统) "systemctl set-default graphical"
设置默认的 target 为"multi-user" (命令行系统) "systemctl set-default multi-user"
显示工作环境变量 "systemctl show-environment"
设置环境变量 "variable" 的值为 "value" "systemctl set-environment variable=value"
取消环境变量 "variable" 的设置 "systemctl unset-environment variable"
重新加载所有单元文件和后台守护进程(daemon) "systemctl daemon-reload"
关闭系统 "systemctl poweroff"
关闭和重启系统 "systemctl reboot"
挂起系统 "systemctl suspend"
休眠系统 "systemctl hibernate"

这里, 上面例子中的"$unit",可以是一个单元名(后缀.service.target 是可选的),或者,在很多情况下,也可以是匹配的多个单元 (shell 式样的全局通配符"*", "?", "[]",通过使用 fnmatch(3) ,来匹配目前在内存中的所有单元的基本名称).

上面列子的系统状态改变命令,通常是通过"sudo"来处理,用以获得需要的系统管理权限。

"systemctl status $unit|$PID|$device" 的输出使用有颜色的点("●")来概述单元状态,让人看一眼就知道。

  • 白色的 "●" 表示一个 "不活动"或"变为不活动中"的状态。

  • 红色的 "●"表示“失败”或者“错误”状态。

  • 绿色"●"表示“活动”、“重新加载中”或“激活中”状态。

这里是 systemd 下其它零星的监控命令列表。请阅读包括 cgroups(7) 在内的相关的 man 手册页。


使用默认安装,通过 systemd 启动的过程中,在 network.target 启动后,很多网络服务 (参见 第 6 章 网络应用)作为后台守护进程(daemon)启动。 "sshd" 也不列外。让我们修改为按需启动"sshd" 作为一个定制化的例子。

首先,禁用系统安装的服务单元。

 $ sudo systemctl stop sshd.service
 $ sudo systemctl mask sshd.service

传统 Unix 服务的按需套接字激活(on-demand socket activation)系统由 inetd (或 xinetd)超级服务来提供。在 systemd 下, 相同功能能够通过增加*.socket*.service 单元配置文件来启用。

sshd.socket 用来定义一个监听的套接字

[Unit]
Description=SSH Socket for Per-Connection Servers

[Socket]
ListenStream=22
Accept=yes

[Install]
WantedBy=sockets.target

sshd@.service 作为 sshd.socket 匹配的服务文件

[Unit]
Description=SSH Per-Connection Server

[Service]
ExecStart=-/usr/sbin/sshd -i
StandardInput=socket

然后重新加载。

 $ sudo systemctl daemon-reload

从 Linux 内核 2.6 版开始,udev 系统 提供了自动硬件发现和初始化机制。(参见 udev(7)).在内核发现每个设备的基础上,udev 系统使用从 sysfs 文件系统 (参见 第 1.2.12 节 “procfs 和 sysfs”)的信息启动一个用户进程,使用 modprobe(8) 程序 (参见 第 3.8.1 节 “内核模块初始化”)加载支持它所要求的内核模块, 创建相应的设备节点。

[提示] 提示

如果由于某些理由,"/lib/modules/kernel-version/modules.dep"没有被 depmod(8) 正常生成,模块可能不会被 udev 系统按期望的方式加载。执行"depmod -a" 来修复它。

"/etc/fstab"里面的挂载规则,设备节点不必需是静态的。你能够使用 UUID 来挂载设备,来代替"/dev/sda"之类的设备名. 参见 第 9.6.3 节 “使用 UUID 访问分区”.

由于 udev 系统是一个正在变化的事物,我在其它文档进行了详细描述,在这里只提供了最少的信息。

通过 modprobe(8) 程序添加和删除内核模块,使我们能够从用户进程来配置正在运行的 Linux 内核。udev 系统(参见 第 3.8 节 “udev 系统”)自动化它的调用来帮助内核模块初始化。

下面的非硬件模块和特殊的硬件驱动模块,需要被预先加载,把它们在"/etc/modules"文件里列出 (参见 modules(5)).

modprobe(8) 程序的配置文件是按 modprobe.conf(5)的说明放在"/etc/modprobes.d/" 目录下,(如果你想避免自动加载某些内核模块,考虑把它们作为黑名单放在"/etc/modprobes.d/blacklist" 文件里.)

"/lib/modules/version/modules.dep" 文件由 depmod(8) 程序生成,它描述了 modprobe(8) 程序使用的模块依赖性.

[注意] 注意

如果你在启动时出现模块加载问题,或者 modprobe(8)时出现模块加载问题, "depmod -a" 可以通过重构"modules.dep"来解决这些问题。

modinfo(8) 程序显示 Linux 内核模块信息。

lsmod(8) 程序以好看的格式展示"/proc/modules"的内容,显示当前内核加载了哪些模块。

[提示] 提示

你能够精确识别你系统上的硬件。 参见第 9.5.3 节 “硬件识别”.

你可以在启动时配置硬件来激活期望的硬件特征。参见 第 9.5.4 节 “硬件配置”.

你可以重新编译内核来增加你的特殊设备的支持。参见 第 9.10 节 “内核”.