浅析Linux初始化init系统

第一部分 sysvinit

什么是sysvinit

sysvinit 就是 system V 风格的 init 系统,顾名思义,它源于 System V 系列 UNIX 。它提供了比 BSD 风格 init 系统更高的灵活性。是已经风行了几十年的 UNIX init 系统,一直被各类 Linux 发行版所采用。

总结

优点:

  • 概念简单。

  • 确定的执行顺序:脚本严格按照启动数字的大小顺序执行,一个执行完毕再执行下一个,这非常有益于错误排查。

缺点:

  • UpStartsystemd 支持并发启动,导致没有人可以确定地了解具体的启动顺序,排错不易。

  • 但是串行地执行脚本导致 sysvinit 运行效率较慢,在新的 IT 环境下,启动快慢成为一个重要问题。此外动态设备加载等 Linux 新特性也暴露出 sysvinit 设计的一些问题。

Upstart 是第一个被广泛应用的新一代 init 系统。我们在接下来的第二部分介绍 UpStart

第二部分 UpStart

什么是 UpStart

假如您使用的 Linux 发行版是 Ubuntu ,很可能会发现在您的计算机上找不到 /etc/inittab 文件了,这是因为 Ubuntu 使用了一种被称为 upstart 的新型 init 系统。

为什么使用 UpStart

随着不断的发展,人们发现经典的 sysvinit 已经不适合笔记本环境。

原因在于:

桌面系统或便携式设备的一个特点是经常重启,而且要频繁地使用硬件热插拔技术。

Sysvinit 没有办法处理这类需求,它必须一次性把所有可能用到的服务都启动起来,即便是需要被初始化的设备并没有连接到系统上,CUPS 服务也必须启动。

UpStart优势

UpStart 解决了之前提到的 sysvinit 的缺点。采用事件驱动模型,UpStart 可以:

  • 更快地启动系统

  • 当新硬件被发现时动态启动服务

  • 硬件被拔除时动态停止服务

UpStart命令

  • 可以用 initctl list 来查看所有工作的概况:

UpStart小结

可以看到,UpStart 的设计比 SysVInit 更加先进。多数 Linux 发行版上已经不再使用 SysVInit ,一部分发行版采用了 UpStart ,比如 Ubuntu(Ubuntu16.04改用systemd);而另外一些比如 Fedora ,采用了一种被称为 systemdinit 系统。接下来我们再介绍 systemd

第三部分 Systemd

什么是 Systemd

SystemdLinux 系统中最新的初始化系统(init),它主要的设计目标是克服 sysvinit 固有的缺点,提高系统的启动速度。

Systemd 的很多概念来源于苹果 Mac OS 操作系统上的 launchd

由于是最新的,所以我们着重了解一下

systemd 特性

  • systemd 提供按需启动能力

  • Systemd 采用 LinuxCgroup 特性跟踪和管理进程的生命周期

  • 启动挂载点和自动挂载的管理

  • 实现事务性依赖关系管理

  • 能够对系统进行快照和恢复

  • 日志服务

Systemd 的基本概念

  • 单元的概念
    系统初始化需要做的事情非常多。需要启动后台服务,比如启动 SSHD 服务;需要做配置工作,比如挂载文件系统。这个过程中的每一步都被 systemd 抽象为一个配置单元,即 unit 。可以认为一个服务是一个配置单元;一个挂载点是一个配置单元;一个交换分区的配置是一个配置单元;等等。

  • service :代表一个后台服务进程,比如 mysqld 。这是最常用的一类。

  • socket :此类配置单元封装系统和互联网中的一个 套接字 。当下,systemd 支持流式、数据报和连续包的 AF_INET、AF_INET6、AF_UNIX socket 。每一个套接字配置单元都有一个相应的服务配置单元 。相应的服务在第一个”连接”进入套接字时就会启动(例如:nscd.socket 在有新连接后便启动 nscd.service)。

  • device :此类配置单元封装一个存在于 Linux 设备树中的设备。每一个使用 udev 规则标记的设备都将会在 systemd 中作为一个设备配置单元出现。

  • mount :此类配置单元封装文件系统结构层次中的一个挂载点。 Systemd 将对这个挂载点进行监控和管理。比如可以在启动时自动将其挂载;可以在某些条件下自动卸载。Systemd 会将 /etc/fstab 中的条目都转换为挂载点,并在开机时处理。

  • automount :此类配置单元封装系统结构层次中的一个自挂载点。每一个自挂载配置单元对应一个挂载配置单元 ,当该自动挂载点被访问时,systemd 执行挂载点中定义的挂载行为。

  • swap: 和挂载配置单元类似,交换配置单元用来管理交换分区。用户可以用交换配置单元来定义系统中的交换分区,可以让这些交换分区在启动时被激活。

  • target :此类配置单元为其他配置单元进行逻辑分组。它们本身实际上并不做什么,只是引用其他配置单元而已。这样便可以对配置单元做一个统一的控制。这样就可以实现大家都已经非常熟悉的运行级别概念。

  • timer:定时器配置单元用来定时触发用户定义的操作,这类配置单元取代了 atdcrond 等传统的定时服务。

  • snapshot :与 target 配置单元相似,快照是一组配置单元。它保存了系统当前的运行状态。

Systemd 的并发启动原理

如前所述,在 Systemd 中,所有的服务都并发启动,比如 AvahiD-BuslivirtdX11HAL 可以同时启动。乍一看,这似乎有点儿问题,比如 Avahi 需要 syslog 的服务, Avahisyslog 同时启动,假设 Avahi 的启动比较快,所以 syslog 还没有准备好,可是 Avahi 又需要记录日志,这岂不是会出现问题?
Systemd 的开发人员仔细研究了服务之间相互依赖的本质问题,发现所谓依赖可以分为三个具体的类型,而每一个类型实际上都可以通过相应的技术解除依赖关系。

并发启动原理之一:解决 socket 依赖

绝大多数的服务依赖是套接字依赖。比如服务 A 通过一个套接字端口 S1 提供自己的服务,其他的服务如果需要服务 A,则需要连接 S1。因此如果服务 A 尚未启动,S1 就不存在,其他的服务就会得到启动错误。所以传统地,人们需要先启动服务 A,等待它进入就绪状态,再启动其他需要它的服务。Systemd 认为,只要我们预先把 S1 建立好,那么其他所有的服务就可以同时启动而无需等待服务 A 来创建 S1 了。如果服务 A 尚未启动,那么其他进程向 S1 发送的服务请求实际上会被 Linux 操作系统缓存,其他进程会在这个请求的地方等待。一旦服务 A 启动就绪,就可以立即处理缓存的请求,一切都开始正常运行。
那么服务如何使用由 init 进程创建的套接字呢?
Linux 操作系统有一个特性,当进程调用 fork 或者 exec 创建子进程之后,所有在父进程中被打开的文件句柄 (file descriptor) 都被子进程所继承。套接字也是一种文件句柄,进程 A 可以创建一个套接字,此后当进程 A 调用 exec 启动一个新的子进程时,只要确保该套接字的 close_on_exec 标志位被清空,那么新的子进程就可以继承这个套接字。子进程看到的套接字和父进程创建的套接字是同一个系统套接字,就仿佛这个套接字是子进程自己创建的一样,没有任何区别。
这个特性以前被一个叫做 inetd 的系统服务所利用。Inetd 进程会负责监控一些常用套接字端口,比如 Telnet,当该端口有连接请求时,inetd 才启动 telnetd 进程,并把有连接的套接字传递给新的 telnetd 进程进行处理。这样,当系统没有 telnet 客户端连接时,就不需要启动 telnetd 进程。Inetd 可以代理很多的网络服务,这样就可以节约很多的系统负载和内存资源,只有当有真正的连接请求时才启动相应服务,并把套接字传递给相应的服务进程。
和 inetd 类似,systemd 是所有其他进程的父进程,它可以先建立所有需要的套接字,然后在调用 exec 的时候将该套接字传递给新的服务进程,而新进程直接使用该套接字进行服务即可。

并发启动原理之二:解决 D-Bus 依赖

D-Bus 是 desktop-bus 的简称,是一个低延迟、低开销、高可用性的进程间通信机制。它越来越多地用于应用程序之间通信,也用于应用程序和操作系统内核之间的通信。很多现代的服务进程都使用D-Bus 取代套接字作为进程间通信机制,对外提供服务。比如简化 Linux 网络配置的 NetworkManager 服务就使用 D-Bus 和其他的应用程序或者服务进行交互:邮件客户端软件 evolution 可以通过 D-Bus 从 NetworkManager 服务获取网络状态的改变,以便做出相应的处理。
D-Bus 支持所谓”bus activation”功能。如果服务 A 需要使用服务 B 的 D-Bus 服务,而服务 B 并没有运行,则 D-Bus 可以在服务 A 请求服务 B 的 D-Bus 时自动启动服务 B。而服务 A 发出的请求会被 D-Bus 缓存,服务 A 会等待服务 B 启动就绪。利用这个特性,依赖 D-Bus 的服务就可以实现并行启动。

并发启动原理之三:解决文件系统依赖

系统启动过程中,文件系统相关的活动是最耗时的,比如挂载文件系统,对文件系统进行磁盘检查(fsck),磁盘配额检查等都是非常耗时的操作。在等待这些工作完成的同时,系统处于空闲状态。那些想使用文件系统的服务似乎必须等待文件系统初始化完成才可以启动。但是 systemd 发现这种依赖也是可以避免的。
Systemd 参考了 autofs 的设计思路,使得依赖文件系统的服务和文件系统本身初始化两者可以并发工作。autofs 可以监测到某个文件系统挂载点真正被访问到的时候才触发挂载操作,这是通过内核 automounter 模块的支持而实现的。比如一个 open()系统调用作用在”/misc/cd/file1”的时候,/misc/cd 尚未执行挂载操作,此时 open()调用被挂起等待,Linux 内核通知 autofs,autofs 执行挂载。这时候,控制权返回给 open()系统调用,并正常打开文件。
Systemd 集成了 autofs 的实现,对于系统中的挂载点,比如/home,当系统启动的时候,systemd 为其创建一个临时的自动挂载点。在这个时刻/home 真正的挂载设备尚未启动好,真正的挂载操作还没有执行,文件系统检测也还没有完成。可是那些依赖该目录的进程已经可以并发启动,他们的 open()操作被内建在 systemd 中的 autofs 捕获,将该 open()调用挂起(可中断睡眠状态)。然后等待真正的挂载操作完成,文件系统检测也完成后,systemd 将该自动挂载点替换为真正的挂载点,并让 open()调用返回。由此,实现了那些依赖于文件系统的服务和文件系统本身同时并发启动。
当然对于”/“根目录的依赖实际上一定还是要串行执行,因为 systemd 自己也存放在/之下,必须等待系统根目录挂载检查好。
不过对于类似/home 等挂载点,这种并发可以提高系统的启动速度,尤其是当/home 是远程的 NFS 节点,或者是加密盘等,需要耗费较长的时间才可以准备就绪的情况下,因为并发启动,这段时间内,系统并不是完全无事可做,而是可以利用这段空余时间做更多的启动进程的事情,总的来说就缩短了系统启动时间。

Systemd优点

  • 令人惊奇的激进的并发启动能力,极大地提高了系统启动速度

  • CGroup 统计跟踪子进程,干净可靠

此外,和其前任不同的地方在于,systemd 已经不仅仅是一个初始化系统了。
Systemd 出色地替代了 sysvinit 的所有功能,但它并未就此自满。因为 init 进程是系统所有进程的父进程这样的特殊性,systemd 非常适合提供曾经由其他服务提供的功能,比如定时任务 (以前由 crond 完成) ;会话管理 (以前由 ConsoleKit/PolKit 等管理) 。仅仅从本文皮毛一样的介绍来看,Systemd 已经管得很多了,可它还在不断发展。它将逐渐成为一个多功能的系统环境,能够处理非常多的系统管理任务,有人甚至将它看作一个操作系统。

扩展话题

如何让 ubuntu 系统休眠,可以使用底层的/sys/power/state 接口,也可以使用诸如 pm-utility 等高层接口。

参考

浅析 Linux 初始化 init系统