9.1. 系统启动

当启动计算机时,控制台上滚动的大量信息显示许多初始化和配置工作自动正在执行。有时候你可能稍稍的改变这一阶段的操作,就要求你需要很好的理解他们。这正是本章节的目的所在。

首先,BIOS 控制电脑,探测磁盘,加载Master Boot Record,并执行启动加载器。启动程序接手后,找到磁盘上的内核,加载并执行。然后,内核被初始化,并开始寻找和挂载包含根文件系统的分区,最后执行第一个程序-init。根分区和启动程序init常常驻留在仅存在于 RAM 中的虚拟文件系统(正如它的名字,“initramfs”,一般称之为“initrd”初始内存磁盘)。启动加载器将文件系统加载到内存中,文件通常位于硬盘或者源于网络。它包含了内核需要的最少裸信息,以便用来加载“真正”的根文件系统:可能是硬盘上的驱动模块,或者其他系统启动必须的设备,或者更常见的是初始化脚本和模块以组建 RAID 阵列,打开加密分区,激活 LVM ,等等。一旦根分区挂载,initramfs 就会把控制权交到真正的启动程序,机器则回到标准的启动过程。

运行 sytemd 的 Linux 计算机的启动顺序

图 9.1. 运行 sytemd 的 Linux 计算机的启动顺序

9.1.1. Systemd 启动系统

“真正的启动器”当前是由systemd 提供的,本章节讲述该启动系统。

文化systemd 之前

systemd 是相当新的 “启动系统”,虽然在 Wheezy 里已经可以使用到某个程度,直到 Debian Jessie 才纳入默认值。稍最的版本,缺省是 “System V init” (在 sysv-rc 软件包内),算是传统的系统。以下描述的是 System V init。

选择 其它启动系统

This book describes the boot system used by default in Debian Buster (as implemented by the systemd package), as well as the previous default, sysvinit, which is derived and inherited from System V Unix systems; there are others.

file-rc 是一个过程很简单的启动系统。它保留运行等级的原则,但是用配置文件取代了目录和符号链接,来告诉init哪些进程必须启动及其顺序。

The upstart system is still not perfectly tested on Debian. It is event based: init scripts are no longer executed in a sequential order but in response to events such as the completion of another script upon which they are dependent. This system, started by Ubuntu, was present in Debian Jessie, but was not the default; it came, in fact, as a replacement for sysvinit, and one of the tasks launched by upstart was to launch the scripts written for traditional systems, especially those from the sysv-rc package.

也有其他的系统和操作模式,例如:runit 或者 minit,但是他们相对专门且没有那么普遍。

特例 从网络启动

在某些配置中,BIOS 可以配置为不执行 MBR,而是在网络上寻找类似的东西,这样就可以制作不需要硬盘的电脑,在每次启动后可以完全重装。不是所有的硬件都支持该选项,它需要 BIOS 和网卡很好的配合。

从网络启动可以用于执行 debian-installer or FAI (参考 第 4.1 节 “安装方式”)。

回到基础进程,一个程序实例

一个进程代表一个在内存中运行的程序。它包含了需要正确执行软件的所有必要信息(代码本身,内存数据,打开的文件清单,建立的网络连接,等等)。一个程序可能初始化为几个进程,而没必要使用不同的用户 ID 。

安全 使用 shell 作为 init 获取超级用户权限

通常,第一个启动的进程是 init 程序(默认是到 /lib/systemd/systemd 的符号链接)。然而,也可以通过传递 init 选项告诉内核使用其他的程序。

任何可以接近电脑的人都能按下复位 按,并重新启动。然后,在启动提示下,传递init=/bin/sh选项给内核,无需知道密码而获取超级用户权限。

为了防止此类事件,可以给加载器设定密码。你也许会考虑保护 BIOS (密码保护总是可行的),这样可以防止侵入者使用包含自己的 Linux 系统的移动介质启动电脑,使用该系统他们可以读取硬盘上的数据。

最后,应该知道大多数 BIOS 有一个通用的密码。最初用于让那些忘记自己密码的人解决问题,这些密码现在是公开的并且可以在网上找到(通过搜索引擎搜索“通用 BIOS 密码”)。所有这些保护措施都不太可能完全阻止非授权用户操作机器。如果攻击者能直接接触电脑,就没有可靠的方法保护电脑;他们可以拆下硬盘连到自己的电脑上,甚至是偷走整个机器,或者擦除 BIOS 来重置密码…

Systemd executes several processes, in charge of setting up the system: keyboard, drivers, filesystems, network, services. It does this while keeping a global view of the system as a whole, and the requirements of the components. Each component is described by a “unit file” (sometimes more); the general syntax is derived from the widely-used “*.ini files“ syntax, with *key* = *value* pairs grouped between [*section*] headers. Unit files are stored under /lib/systemd/system/ and /etc/systemd/system/; they come in several flavors, but we will focus on “services” and “targets” here.

systemd “服务文件” 描述被 systemd 管理的进程。包括与旧型的 init-scripts 相同的数据,但以声明 (同时较为简洁) 的方式表述。Systemd 处理大量重复的工作 (启动与终止进程、检查其状态、日注记录、去除特权等),以及只供特定进程使用的服务文件。例如,以下是 SSH 用到的服务档:

  1. [Unit]
  2. Description=OpenBSD Secure Shell server
  3. After=network.target auditd.service
  4. ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
  5.  
  6. [Service]
  7. EnvironmentFile=-/etc/default/ssh
  8. ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
  9. ExecReload=/bin/kill -HUP $MAINPID
  10. KillMode=process
  11. Restart=on-failure
  12.  
  13. [Install]
  14. WantedBy=multi-user.target
  15. Alias=sshd.service

如上文所示,代码极少,只有声明。Systemd 管理显示进度报表、追踪进程、以及必要的重启。

systemd 的 “目标文件” 描述系统的现状,包括可操作的服务。不妨视为相当于旧型的运行阶段作业。其中一个目标是 local-fs.target;进入之后,系统的其他部分假设所有的本地文件系统均己挂载并可近用。其他的目标包括 network-online.targetsound.target。目标的相依性可以列在目标文件内 (于 Requires= 列) 或使用符号链接至在 /lib/systemd/system/*targetname*.target.wants/ 文件夹内的服务文件。例如,/etc/systemd/system/printer.target.wants/ 包括一个链接至 /lib/systemd/system/cups.service;systemd 将确保 CUPS 已运行至 printer.target

单元文件是声明性的而不是脚本或程序,不能直接运行,只能被 systemd 解译;因些有些工具允许管理者与 systemd 交互且控制系统的状态与其组件。

第一种这类工具是 systemctl。未使用参数运行时,它列出 systemd 已知的所有单元档 (除了已经停用的),及其现况。systemctl status 则以更佳的角度查看服务,以及相关的进程。若提供服务的名称 (如 systemctl status ntp.service),则送回更多详细的数据,以及与该服务有关的最后几个日志档 (还有更多的)。

运行 systemctl start *servicename*.service 就能以人工方式启动服务。同样的,运行 systemctl stop *servicename*.service 就能停止已完成的服务;其他的次命令包括 reloadrestart

systemctl enable *servicename*.service (或 disable) 控制启动服务 (即开机后自动启动)。is-enabled 可以检查服务的状态。

An interesting feature of systemd is that it includes a logging component named journald. It comes as a complement to more traditional logging systems such as syslogd, but it adds interesting features such as a formal link between a service and the messages it generates, and the ability to capture error messages generated by its initialization sequence. The messages can be displayed later on, with a little help from the journalctl command. Without any arguments, it simply spews all log messages that occurred since system boot; it will rarely be used in such a manner. Most of the time, it will be used with a service identifier:

  1. #

另个有用的命令行旗标是 -f,用于指示 journalctl 继续显示溢出的添加消息 (大部分是在 tail -f *file* 之内)。

若服务状况不如预共,第一个步骤是以 systemctl status 检查该服务是否真的已启动;若没有,则第一个命令给的消息就不足以诊断问题之所在,检查 journald 产生的日志档。例如,假设 SSH 服务器未启动时:

  1. #

检查服务的状态 (失败) 后,再检查日志档;它们会指出配置的错误。编辑配置档并修正错误后,重启服务,确认运行中。

下一步 其他类型的单元档

本区只描述 systemd 最基本的功能。其他的功能只能在此列出若干:

  • 启用插口:“插口” 单元文件可用于描述 systemd 管理的网络或 Unix 插口;也就是由 systemd 创建的插口,可以在需要的时候再启动实际的服务。通常重制 inetd 的功能。见 systemd.socket(5)。

  • 定时器:“定时器” 单元文件描述定时或在指定时间发生的事件;与定时器链接的服务,其映射的工作将在定时器的要求下才运行。 允许重制 cron 的部分功能。见 systemd.timer(5)。

  • 网络:“网络“ 单元文件描述网络接口,允许配置该等接口以及表述在特定接口的服务。

9.1.2. System V 初始系统

System V 初始系统 (简称初始) 运行若干进程,根据 /etc/inittab 文件的指令做事。第一个运行的程序 (映射于 sysinit 步骤) 是 /etc/init.d/rcS,一个运行在 /etc/rcS.d/ 文件夹内所有程序的脚本。

其中,你会发现相继的程序会负责:

  • 配置控制台键盘;

  • 加载驱动:当探测到硬件,大部分内核模块通过内核自身加载;然后,自动加载 /etc/modules 中列出的模块;

  • 检查文件系统的完整性;

  • 挂载本地分区;

  • 配置网络;

  • 挂载网络文件系统(NFS)。

回到基础 内核模块和选项

内核模块也有一些选项,可以通过在 /etc/modprobe.d/ 中放置一些文件配置。这些选项通过诸如此类的语法定义:选项 *模块名字* *选项名字*=*选项值*。如果有必要,一些选项可以通过单独的定向命令指示。

这些配置档系供 modprobe 使用 — 这个程序加载核心模块及其相依者 (才能直正的调用其他模块)。这个程序由 kmod 软件包提供。

到了这个地步,init 接手并启动运行阶段缺省的程序 (通常是运行阶段 2)。它运行 /etc/init.d/rc 2,一个启动列在 /etc/rc2.d/ 之内的所有服务并命名为 “S” 字母开头。接着的两位数,曾经做为服务启动的顺序,不过现在的缺省启动系统使用 insserv,根据脚本的相依性自动决定其先后顺序。每个启动脚本声明的情况必须符合启动或停止服务 (例如,必须在另个服务之前或之后启动);init 再依此情况启动它们。不再考虑静态的脚本编号 (但仍需按相依性使用 “S” 及两个数字与实际的脚本名称)。通常,基本的服务 (诸如以 rsyslog 登录,或以 portmap 指定端口口) 先列出来,然后才是标准服务与图形接口 (gdm3)。

这种以依赖为基础的启动系统使自动排序成为可能,这样的排序如果要手工完成则显得冗长乏味。由于调度根据明确给出的参数进行,这样就避免了人为错误。另一个好处是,如果两个服务彼此独立,则可以并行启动,进而加速启动过程。

init分几个运行等级,它可以通过telinit *new-level* 命令,从一个等级切换到另一个等级。马上就会在新等级下重新执行init executes /etc/init.d/rc。这个脚本会启动漏掉的服务并中止不再需要的服务。为了做到这一点,它读取/etc/rc*X*.d 文件的内容(此处 X 代表新的运行等级)。以“S”(Start的首字母)开头的服务脚本要启动;以“K”(Kill的首字母)开头的服务要停止。脚本不会启动在之前运行等级已经生效的服务。

缺省,Debian 的 System V init 使用四个不同的运行阶层:

  • 等级0仅作电脑关机时的临时应用。这样,它只包含许多“K”脚本。

  • 等级1,也被称为单用户模式,对应于降级的系统模式;它仅包含基本服务,用于维护,此时不需要与一般用户交互。

  • 等级2用于正常运行,包含网络服务,图形界面,用户登陆,等等。

  • 等级6和等级0类似,不同在于它用于系统重启之前的关机。

也存在其他等级,从3到5。默认情况,他们配置为和等级2相同,但是管理员可以修改(通过添加和删除对应/etc/rc*X*.d目录下的脚本)它们来适应不同的需求。

以 System V init 运行 Linux 的启动进程

图 9.2. 以 System V init 运行 Linux 的启动进程

所有包含在/etc/rc*X*.d目录下的脚本都只是符号联接-有update-rc.d程序在安装时创建-指向存储在/etc/init.d/中的实际脚本。管理员可以使用调整后的参数重新运行 update-rc.d 来微调每个运行等级的服务。update-rc.d(1)手册详细介绍了语法。请注意,使用remove 参数移除所有的符号连接不是停用服务的好办法。取而代之的方法是,你可以在特定的运行等级将其配置为不启动(而保留先前等级对应事件的停止调用)。由于update-rc.d的接口有些绕,可以考虑使用rcconf(出自rcconf 软件包),它提供了更加友好的界面。

DEBIAN 策略 重启服务

Debian 软件包的维护者脚本将不时重新启动特定服务以确保其有效性或取得特定的选项。控制服务的命令 — service *service* *operation* — 未考量运行阶层,假设 (错误地) 该服务仍在使用中,且可能启动错误的作业 (启动应该停止的服务,或停止已经停止的服务等)。因此,Debian 有个 invoke-rc.d 程序:必须由维护者的脚本启动,运行服务的初启脚本且只运行必要的命令。注意,不同于常见的用法,在程序名之前使用 .d 前置,且不能在文件夹内。

最后,init启动各种虚拟控制台的控制程序(getty)。显示提示符,等待输入用户名,然后执行login *user*发起会话。

词汇 控制台和终端

早期的计算机通常被分成几个很大的部分:存储箱和中央处理单元与外围操作员的控制设备相分离。这些部件是单独的装置,即“控制台”。该术语被保留下来,但是其意义改变了。或多或少它已经和“终端”同义,代表键盘和屏幕。

随着计算机的发展,操作系统提供了许多虚拟终端,从而允许同时存在几个独立的会话,即使是只有一套键盘和屏幕。大部分 GNU/Linux 系统提供六个虚拟终端(在文本模式),通过组合键 Control+Alt+F1Control+Alt+F6 开启。

推而广之,术语“控制台”和“终端”也代表在X11图形会话中的终端模拟器(类似xtermgnome-terminal 或者 konsole)。