• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

CentOS7:系统初始化Systemd介绍篇(一)

CentOS7 彭东稳 9年前 (2016-06-07) 28247次浏览 已收录 0个评论

近年来,Linux系统的 init 进程经历了两次重大的演进,传统的 sysvinit 已经逐渐淡出历史舞台,新的 UpStart systemd 各有特点,越来越多的 Linux 发行版采纳了 systemd。本文简要介绍了这三种 init 系统的使用和原理,每个 Linux 系统管理员和系统软件开发者都应该了解它们,以便更好地管理系统和开发应用。

关于Linux系统启动

Linux系统从启动到提供服务的过程是这样,先是机器加电,然后通过MBR或者UEFI加载GRUB,再启动内核,内核启动服务,然后开始对外服务。而SysV initUpstart init还是systemd init都是用来解决服务过程引导管理的问题。

1SysV init的优缺点

SysV init是最早的解决方案,依靠划分不同的运行级别,启动不同的服务集,服务依靠脚本控制,并且是顺序执行的。

SysV init优点是:原理简单,易于理解;依靠shell脚本控制,编写服务脚本门槛比较低。

SysV init缺点是:服务顺序启动,启动过程比较慢;不能做到根据需要来启动服务,比如通常希望插入U盘的时候,再启动USB控制的服务,这样可以更好的节省系统资源。

2Upstart的改进

为了解决系统服务的即插即用,UpStart应运而生,在CentOS6系统中,SysV initUpStart是并存的,UpStart主要解决了服务的即插即用。服务顺序启动慢的问题,UpStart的解决办法是把相关的服务分组,组内的服务是顺序启动,组之间是并行启动。但UpsStartCentOS6并不是支持的特别好。

其实对于SysV initUpstart在本博客的“Linux系统管理”中,Linux系统启动过程讲解系列有四篇文章。如下所示:

l  Linux系统启动过程详解(一)

l  Linux系统启动过程详解之SysV init(二)

l  Linux系统启动过程详解之Upstart init(三)

l  Linux系统启动过程详解(四)

其中分别阐述了CentOS5CentOS6各自启动的顺序时对应SysVUpstart就做过解释,其实对systemd也提到过,说了CentOS7会使用。你可以回头再去看看这四篇文章也好对SysVUpstart的原理有一个了解,再看systemd,会发现它有多么强大。

Systemd的简介

SystemdLinux 系统中最新的初始化系统(init),它主要的设计目标是克服 SysV init 固有的缺点,提高系统的启动速度。systemdubuntu upstart 是竞争对手,预计会取代 UpStart,然而在Ubuntu 15.04采用systemd作为默认引导程序,debian8也开始使用systemd。足以看出systemd的流行度。另外CentOS 7使用systemd替换了SysVSystemd目的是要取代Unix时代以来一直在使用的init系统,兼容SysVLSB的启动脚本,而且够在进程启动过程中更有效地引导加载服务。

Systemd的很多概念来源于苹果Mac OS操作系统上的launchd,不过launchd专用于苹果系统,因此长期未能获得应有的广泛关注。Systemd借鉴了很多launchd的思想。

Systemd 的优点是功能强大,使用方便,缺点是体系庞大,非常复杂。事实上,现在还有很多人反对使用 Systemd,理由就是它过于复杂,与操作系统的其他部分强耦合,违反”keep simple, keep stupid”的Unix 哲学

下图是systemd的架构图:

CentOS7:系统初始化Systemd介绍篇(一)

Sytemd的特点

1)更快的启动速度

SysV init服务启动慢,在以前并不是一个问题,尤其是Linux系统以前主要是在服务器系统上,常年也难得重启一次。有的服务器光硬件检测都需要5分钟以上,相对来说系统启动已经很快了。

但是随着移动互联网的到来,SysV init服务启动慢的问题显得越来越突出,许多移动设备都是基于Linux内核,比如安卓。移动设备启动比较频繁,每次启动都要等待服务顺序启动,显然难以接受,systemd就是为了解决这个问题诞生的。Systemd 提供了比 UpStart 更激进的并行启动能力,采用了 socket / D-Bus activation 等技术启动服务。一个显而易见的结果就是更快的启动速度。systemd的设计思路是:

1)尽可能的快速启动服务;

2)尽可能的减少系统资源占用;

同样地,UpStart也试图实现这两个目标。UpStart 采用事件驱动机制,服务可以暂不启动,当需要的时候才通过事件触发其启动,这符合第一个设计目标;此外,不相干的服务可以并行启动,这也实现了第二个目标。

下面的图形演示了UpStart相对于SysVInit在并发启动这个方面的改进:

CentOS7:系统初始化Systemd介绍篇(一)

假设有7个不同的启动项目,比如JobAJob B 等等。在SysVInit 中,每一个启动项目都由一个独立的脚本负责,它们由sysVinit顺序地,串行地调用。因此总的启动时间为 T1+T2+T3+T4+T5+T6+T7。其中一些任务有依赖关系,比如 A,B,C,D

Job E F 却和 A,B,C,D 无关。这种情况下,UpStart 能够并发地运行任务{EF(A,B,C,D)},使得总的启动时间减少为 T1+T2+T3

这无疑增加了系统启动的并行性,从而提高了系统启动速度。但是在UpStart中,有依赖关系的服务还是必须先后启动。比如任务 A,B,(C,D)因为存在依赖关系,所以在这个局部,还是串行执行。

让我们例举一些例子, Avahi服务需要D-Bus 提供的功能,因此Avahi的启动依赖于D-BusUpStart中,Avahi 必须等到D-Bus 启动就绪之后才开始启动。类似的,livirtdX11都需要HAL服务先启动,而所有这些服务都需要 syslog 服务记录日志,因此它们都必须等待syslog 服务先启动起来。然而httpd和他们都没有关系,因此httpd可以和Avahi等服务并发启动。

Systemd能够更进一步提高并发性,即便对于那些UpStart认为存在相互依赖而必须串行的服务,比如AvahiD-Bus也可以并发启动。从而实现如下图所示的并发启动过程:

systemd的并发启动

CentOS7:系统初始化Systemd介绍篇(一)

所有的任务都同时并发执行,总的启动时间被进一步降低为 T1。可见systemdUpStart更进一步提高了并行启动能力,极大地加速了系统启动时间。

systemd使用并行的方法启动服务,不像SysV init是顺序执行的,所以大大节省了系统启动时间。

使用并行启动,最大的难点是要解决服务之间的依赖性,systemd的解决办法是使用类似缓冲池的办法。比如对TCP有依赖的服务,在启动的时候会检查依赖服务的TCP端口,systemd会把对TCP端口的请求先缓存起来,当依赖的服务器启动之后,在将请求传递给服务,使两个服务通讯。同样的进程间通讯的D-BUS也是这样的原理,目录挂载则是先让服务以为目录被挂载了,到真正访问目录的时候,才去真正操作。

2)systemd 提供按需启动能力

sysvinit 系统初始化的时候,它会将所有可能用到的后台服务进程全部启动运行。并且系统必须等待所有的服务都启动就绪之后,才允许用户登录。这种做法有两个缺点:首先是启动时间过长;其次是系统资源浪费。

某些服务很可能在很长一段时间内,甚至整个服务器运行期间都没有被使用过。比如 CUPS,打印服务在多数服务器上很少被真正使用到。您可能没有想到,在很多服务器上 SSHD 也是很少被真正访问到的。花费在启动这些服务上的时间是不必要的;同样,花费在这些服务上的系统资源也是一种浪费。

Systemd 可以提供按需启动的能力,只有在某个服务被真正请求的时候才启动它。当该服务结束,systemd 可以关闭它,等待下次需要时再次启动它。

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

init系统的一个重要职责就是负责跟踪和管理服务进程的生命周期。它不仅可以启动一个服务,也必须也能够停止服务。这看上去没有什么特别的,然而在真正用代码实现的时候,您或许会发现停止服务比一开始想的要困难。

服务进程一般都会作为精灵进程(daemon)在后台运行,为此服务程序有时候会派生(fork)两次。在 UpStart 中,需要在配置文件中正确地配置 expect 小节。这样 UpStart 通过对 fork 系统调用进行计数,从而获知真正的精灵进程的 PID 号。比如下图所示,找到正确 pid

CentOS7:系统初始化Systemd介绍篇(一)

如果UpStart找错了,将p1作为服务进程的Pid,那么停止服务的时候,UpStart 会试图杀死p1进程,而真正的p1进程则继续执行。换句话说该服务就失去控制了。

还有更加特殊的情况。比如,一个CGI 程序会派生两次,从而脱离了和Apache 的父子关系。当 Apache 进程被停止后,该CGI 程序还在继续运行。而我们希望服务停止后,所有由它所启动的相关进程也被停止。

为了处理这类问题,UpStart 通过strace 来跟踪forkexit等系统调用,但是这种方法很笨拙,且缺乏可扩展性。systemd 则利用了Linux内核的特性即CGroup 来完成跟踪的任务。当停止服务时,通过查询CGroupsystemd可以确保找到所有的相关进程,从而干净地停止服务。

cgroups,或者叫控制组,在Linux内核里已经出现好几年了,但直到systemd的出现才被真正使用起来。它主要用来实现系统资源配额管理。CGroup 提供了类似文件系统的接口,使用方便。当进程创建子进程时,子进程会继承父进程的 CGroup。因此无论服务如何启动新的子进程,所有的这些相关进程都会属于同一个 CGroupsystemd 只需要简单地遍历指定的 CGroup 即可正确地找到所有的相关进程,将它们一一停止即可。(如果你了解Docker容器就会知道CgroupsDocker正式基于Cgroups管理做资源控制)

4)启动挂载点和自动挂载的管理

传统的Linux系统中,用户可以用/etc/fstab 文件来维护固定的文件系统挂载点。这些挂载点在系统启动过程中被自动挂载,一旦启动过程结束,这些挂载点就会确保存在。这些挂载点都是对系统运行至关重要的文件系统,比如 HOME 目录。和sysvinit 一样,Systemd 管理这些挂载点,以便能够在系统启动时自动挂载它们。Systemd 还兼容/etc/fstab 文件,您可以继续使用该文件管理挂载点。

有时候用户还需要动态挂载点,比如打算访问DVD 内容时,才临时执行挂载以便访问其中的内容,而不访问光盘时该挂载点被取消(umount),以便节约资源。传统地,人们依赖 autofs 服务来实现这种功能。

Systemd内建了自动挂载服务,无需另外安装autofs 服务,可以直接使用systemd 提供的自动挂载管理能力来实现autofs的功能。

5)实现事务性依赖关系管理

系统启动过程是由很多的独立工作共同组成的,这些工作之间可能存在依赖关系,比如挂载一个 NFS 文件系统必须依赖网络能够正常工作。Systemd 虽然能够最大限度地并发执行很多有依赖关系的工作,但是类似挂载 NFS”启动网络这样的工作还是存在天生的先后依赖关系,无法并发执行。对于这些任务,systemd 维护一个事务一致性的概念,保证所有相关的服务都可以正常启动而不会出现互相依赖,以至于死锁的情况。

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

systemd 支持按需启动,因此系统的运行状态是动态变化的,人们无法准确地知道系统当前运行了哪些服务。Systemd 快照提供了一种将当前系统运行状态保存并恢复的能力。

比如系统当前正运行服务 A B,可以用 systemd 命令行对当前系统运行状况创建快照。然后将进程 A 停止,或者做其他的任意的对系统的改变,比如启动新的进程 C。在这些改变之后,运行 systemd 的快照恢复命令,就可立即将系统恢复到快照时刻的状态,即只有服务 AB 在运行。一个可能的应用场景是调试:比如服务器出现一些异常,为了调试用户将当前状态保存为快照,然后可以进行任意的操作,比如停止服务等等。等调试结束,恢复快照即可。

这个快照功能目前在 systemd 中并不完善,似乎开发人员也没有特别关注它,因此有报告指出它还存在一些使用上的问题,使用时尚需慎重。

7)日志服务

systemd 自带日志服务 journald,该日志服务的设计初衷是克服现有的 syslog 服务的缺点。比如:

syslog不安全,消息的内容无法验证。每一个本地进程都可以声称自己是 Apache PID 4711,而 syslog 也就相信并保存到磁盘上。数据没有严格的格式,非常随意。自动化的日志分析器需要分析人类语言字符串来识别消息。一方面此类分析困难低效;此外日志格式的变化会导致分析代码需要更新甚至重写。Systemd Journal 用二进制格式保存所有日志信息,用户使用 journalctl 命令来查看日志信息。无需自己编写复杂脆弱的字符串分析处理程序。

Systemd Journal的优点如下:

l  简单性:代码少,依赖少,抽象开销最小。

l  零维护:日志是除错和监控系统的核心功能,因此它自己不能再产生问题。举例说,自动管理磁盘空间,避免由于日志的不断产生而将磁盘空间耗尽。

l  移植性:日志 文件应该在所有类型的 Linux 系统上可用,无论它使用的何种 CPU 或者字节序。

l  性能:添加和浏览 日志 非常快。

l  最小资源占用:日志 数据文件需要较小。

l  统一化:各种不同的日志存储技术应该统一起来,将所有的可记录事件保存在同一个数据存储中。所以日志内容的全局上下文都会被保存并且可供日后查询。例如一条固件记录后通常会跟随一条内核记录,最终还会有一条用户态记录。重要的是当保存到硬盘上时这三者之间的关系不会丢失。Syslog 将不同的信息保存到不同的文件中,分析的时候很难确定哪些条目是相关的。

l  扩展性:日志的适用范围很广,从嵌入式设备到超级计算机集群都可以满足需求。

l  安全性:日志 文件是可以验证的,让无法检测的修改不再可能。

8)systemd向后兼容SysV init

systemd完全支持SysV init Linux标准的基础核心规范脚本,这样的脚本易于升级到systemd服务单元。

9)Systemd对依赖关系处理

虽然 systemd 将大量的启动工作解除了依赖,使得它们可以并发启动。但还是存在有些任务,它们之间存在天生的依赖,不能用套接字激活“(socket activation)D-Bus activation autofs 三大方法来解除依赖(三大方法详情见后续描述)。比如:挂载必须等待挂载点在文件系统中被创建;挂载也必须等待相应的物理设备就绪。为了解决这类依赖问题,systemd 的配置单元之间可以彼此定义依赖关系。

Systemd 用配置单元定义文件中的关键字来描述配置单元之间的依赖关系。比如:unit A依赖unit B,可以在unit B的定义中用“require A”来表示。这样systemd 就会保证先启动A再启动B

Systemd 事务

Systemd能保证事务完整性。Systemd的事务概念和数据库中的有所不同,主要是为了保证多个依赖的配置单元之间没有环形引用。比如unit ABC假如它们的依赖关系为:

Unit 的循环依赖

CentOS7:系统初始化Systemd介绍篇(一)

存在循环依赖,那么systemd 将无法启动任意一个服务。此时systemd 将会尝试解决这个问题,因为配置单元之间的依赖关系有两种:required 是强依赖;want 则是弱依赖,systemd 将去掉wants 关键字指定的依赖看看是否能打破循环。如果无法修复,systemd 会报错。

Systemd能够自动检测和修复这类配置错误,极大地减轻了管理员的排错负担。

Systemd 的并发启动原理

如前所述,在 Systemd 中,所有的服务都并发启动,比如 AvahiD-BuslivirtdX11HAL 可以同时启动。乍一看,这似乎有点儿问题,比如 Avahi 需要 syslog 的服务,Avahi syslog 同时启动,假设 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 内核通知 autofsautofs 执行挂载。这时候,控制权返回给 open()系统调用,并正常打开文件。

Systemd 集成了 autofs 的实现,对于系统中的挂载点,比如/home,当系统启动的时候,systemd 为其创建一个临时的自动挂载点。在这个时刻/home 真正的挂载设备尚未启动好,真正的挂载操作还没有执行,文件系统检测也还没有完成。可是那些依赖该目录的进程已经可以并发启动,他们的 open()操作被内建在 systemd 中的 autofs 捕获,将该 open()调用挂起(可中断睡眠状态)。然后等待真正的挂载操作完成,文件系统检测也完成后,systemd 将该自动挂载点替换为真正的挂载点,并让 open()调用返回。由此,实现了那些依赖于文件系统的服务和文件系统本身同时并发启动。

当然对于“/”根目录的依赖实际上一定还是要串行执行,因为 systemd 自己也存放在/之下,必须等待系统根目录挂载检查好。

不过对于类似/home 等挂载点,这种并发可以提高系统的启动速度,尤其是当/home 是远程的 NFS 节点,或者是加密盘等,需要耗费较长的时间才可以准备就绪的情况下,因为并发启动,这段时间内,系统并不是完全无事可做,而是可以利用这段空余时间做更多的启动进程的事情,总的来说就缩短了系统启动时间。


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (0)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!