优麒麟的程序员小哥在研究如何优化系统资源模块时,查阅了许许多多的资料,发现没有一篇能详细把 systemd 的优势与原理说得很清楚的中文介绍文章,于是自己动手下载了 systemd 的源码,对照资料汇总了一份有关 systemd 的详解文章,希望能对研究 systemd 的优客有所帮助。
systemd 介绍
1 systemd 的起源
关于 systemd 的起源,首先要从 Linux 的 init 程序说起。Linux 系统在启动过程中,内核完成初始化以后,由内核第一个启动的程序便是 init 程序,路径为 /sbin/init(为一个软连接,链接到真实的 init 进程),其 PID 为1,它为系统里所有进程的“祖先”,Linux 中所有的进程都由 init 进程直接或间接进行创建并运行,init 进程以守护进程的方式存在,负责组织与运行系统的相关初始化工作,让系统进入定义好的运行模式,如命令行模式或图形界面模式。
init 程序的发展,大体上可分为三个阶段:sysvinit->upstart->systemd,根据 init 进程的发展特性,可以简单理解为如下:
sysvinit:init 系统通过 shell 脚本以串行的方式启动系统服务,下一个进程必须等待上一个进程启动完成后才能开始启动,因此系统启动的过程比较慢。
upstart:在 sysvinit 的基础上,把一些没有关联的程序并行启动,以提高启动的速度,但是存在依赖关系的程序仍然为串行启动。
systemd:通过套接字激活的机制,让所有无论有无依赖关系的程序全部并行启动,并且仅按照系统启动的需要启动相应的服务,最大化提高开机启动速度。
目前优麒麟操作系统使用的就为 systemd。systemd 的意思为 system daemon,意为系统守护进程,由 Lennart Poettering 带头开发,采用更加优秀的服务框架,并且与老的 sysvinit 兼容,其设计目的就是克服 sysvinit 与 upstart 的缺点,进一步地提高启动速度。目前主流的系统中,systemd 的守护进程主要分为系统态(system)与用户态(user),可以在 ps -ef 中看到 systemd 的守护进程,如下:
PID 为1的进程/sbin/init 即是 system 态的 systemd,它为一个软链接,指向真实的 systemd 路径,在优麒麟操作系统中一般放在/lib/systemd/目录:
systemd 为进程服务集合的总称,它包含许多的进程,负责控制、管理系统的资源,其中包括 systemd-login,负责用户登录相关信息的创建、修改与删除;systemd-sleep 控制系统的休眠、睡眠状态切换等等。在优麒麟操作系统下,它们主要集中在/lib/systemd/文件目录:
每个进程的主要用途可以阅读 freedesktop systemd 手册:https://www.freedesktop.org/software/systemd/man/
目前 systemd 占据 init 程序的主导,有统一天下的趋势。
2 systemd 的主要功能
3 systemd 如何实现服务的并行
systemd 的设计理念就是希望让所有的服务并行的启动,以最大化利用硬件资源,提高启动的时间。但是我们知道服务之间存在依赖关系,客户端需要等待服务端的启动才可以建立连接,例如前面提到的,在优麒麟操作系统中,所有的服务都需要等待 syslog 服务的启动,那 systemd 是如何摆脱这同步和序列化过程的呢?
systemd 的设计师认为,对于传统的守护进程,他们真正实际等待另一个守护进程提供的是套接字的准备,需要的是一个文件系统的 socket 套接字描述符,这是它们唯一等待的,因此是否可以设法让这些套接字描述符可以更早的创建用于连接,从而不用等待整个守护进程完整的启动?答案是可以的。
在 C 语言中,一个进程启动另一个进程时,一般是执行系统调用 exec(),systemd 在调用 exec()来启动服务之前,先创建与该服务关联的监听套接字并激活,然后在 exec()启动服务期间把套接字传递给它,因此在服务还在启动的时候,套接字就已经处于可用的状态。通过这一方式,systemd 可以在第一步中为所有的服务创建套接字,然后第二步一次运行所有的服务,如果一个服务需要依赖于另一个服务,由于套接字已经准备好,服务之间可以直接进行连接并继续执行启动,如果遇到了需要同步的请求,不得不等待阻塞的情况,那阻塞的也将只会是一个服务,并且只是一个服务的一个请求,不会影响其他服务的启动,由此实现服务之间不需要再进行序列化的启动。Linux 内核提供了套接字缓冲区功能,帮助 systemd 实现了最大的并行化,还是拿 syslog 服务来说,优麒麟操作系统上大多数服务在启动初期都会先进行日志相关的初始化配置,如果同时启动 syslog 服务与各种 syslog 的客户端服务,由于 syslog 相关套接字在 systemd 执行 exec()启动 syslog 之前已经创建并准备好,客户端可以直接连接到 syslog 的套接字上,如果遇到 syslog 启动比较慢,客户端向 syslog 发送请求消息,syslog 还无法处理的情况,通过内核 socket 缓冲区的机制,请求的消息将会传到 syslog 套接字的缓冲区之中,只要缓冲区未满,客户端就不需要等待并继续往下执行;一旦 syslog 服务完全启动,它就会使所有消息出列并处理他们;当出现另一种情况,缓冲区已满,或者需要同步消息请求的情况,虽然这个时候客户端不得不阻塞等待,但是也只有一个客户端的一个请求被阻塞,并且直到服务端赶上并处理为止。
因此 systemd 先进行套接字的激活,然后开始服务的创建,使得所有的服务可以并行启动,依赖的管理也变得多余,至少可以说是次要的,因为从服务的角度看,只要套接字是激活的,另一个服务有没有启动都没有区别,这样一种方式也使得程序更加地健壮,因为不论服务可用或不可用,甚至是崩溃,套接字都处于可用的状态,不会让客户端注意到丢失连接。
4 systemd 执行单元–Unit 介绍
Unit 是 systemd 管理服务与资源的基本单元,可以简单理解为 systemd 启动后每次需要执行的服务,每次需要处理的资源,都被抽象为一个配置单元 Unit,保存在一个 Unit 文件里面。例如,当用户登录到优麒麟操作系统时,systemd 会执行 systemd-login.service 这个 Unit 文件来启动 login 服务,持续跟踪用户的会话、进程、空闲状态,为用户分配一个 slice 单元;当用户执行睡眠操作时,systemd 会执行 systemd-suspend.service 文件的 Unit,来启动 systemd-sleep 服务执行系统睡眠操作。Unit 文件可以根据其后缀名分为12种不同的类型,systemd 内部给这12种类型的 Unit 定义了不同的全局模板,因此 systemd 的执行流程为:
首先找到对应的 Unit 文件,然后根据 Unit 文件的类型匹配对应的全局模板,再然后根据这个模板解析 Unit 文件,最后执行 Unit 文件里的操作。接下来简单介绍一下12种 Unit 文件类型:
(1)service:这是最明显的单元类型,代表一个后台守护进程,可以启动、停止、重新启动、重新加载守护进程,是最常用的一类 Unit 文件。
(2)socket:这个单元在文件系统或互联网上封装了一个套接字。目前 systemd 支持流、数据报和顺序包类型的 AF_INET、AF_INET6、AF_UNIX 套接字。还支持经典的 FIFO 作为传输。每个套接字单元都有一个匹配的服务单元,相应的服务在第一个连接进入套接字时就会启动,例如:nscd.socket 在传入连接上启动 nscd.service。
(3)device:这个单元封装了 Linux 设备树中的一个设备。如果设备通过 udev 规则为此标记,它将在 systemd 中作为设备单元公开。使用 udev 设置的属性可用作配置源来设置设备单元的依赖关系。
(4)mount:这个单元封装了文件系统层次结构中的一个挂载点。systemd 监控所有挂载点,也可用于挂载或卸载挂载点。systemd 会将/etc/fstab 中的条目都转换为挂载点,并在开机时处理。
(5)automount:这个单元类型在文件系统层次结构中封装了一个自动挂载点。每个自动挂载单元都有一个匹配的挂载单元,当该自动挂载点被访问时,systemd 就会执行挂载点中定义的挂载行为。
(6)target:这种单元类型用于单元的逻辑分组:它本身并不做任何事情,它只是引用其他单元,从而可以一起控制。比如:想让系统进入图形化模式,需要运行许多服务和配置命令,这些操作都由一个个的配置单元表示,将所有这些配置单元组合为一个目标(target),就表示需要将这些配置单元全部执行一遍以便进入目标所代表的系统运行状态。
(7)snapshot:类似于 target 单元,snapshot 本身实际上不做任何事情,它们的唯一目的是引用其他单元。快照可用于保存或回滚 init 系统的所有服务和单元的状态。比如允许用户临时进入特定状态,例如“紧急外壳”,终止当前服务,并提供一种简单的方法返回之前的状态。
(8)swap:和挂载配置单元类似,交换配置单元用来管理交换分区。用户可以使用交换配置单元来定义系统中的交换分区,可以让这些交换分区在启动时被激活。
(9)timer:定时器配置单元用来定时触发用户定义的服务操作,是一种基于定时器的服务激活,这类配置单元取代了 atd、crond 等传统的定时服务。
(10)path:这类配置单元用来监控指定目录或者文件的变化,根据变化触发其他配置单元服务的运行。
(11)scope:这个单元主要表示从 systemd 外部创建的进程。
(12)slice:此单元主要用于封装管理一组进程资源占用的控制组的 slice 单元,也就是主要用于 cgroup,它通过在 cgroup 中创建一个节点实现资源的控制,一般包含 scope 与 service 单元。
下面通过蓝牙服务 bluetooth.service 介绍一下 Unit 文件的结构。
Unit 文件主要分为以下三个大的部分:
相关字段更详细的描述可以参考 freedesktop 的 man 手册: https://www.freedesktop.org/software/systemd/man/systemd.unit.html https://www.freedesktop.org/software/systemd/man/systemd.service.html
以优麒麟操作系统为例,Unit 文件主要的存储路径如下:
system:
/etc/systemd/system/*
/run/systemd/system/*
/lib/systemd/system/*
user:
~/.config/systemd/user/*
/etc/systemd/user/*
$XDG_RUNTIME_DIR/systemd/user/*
/run/systemd/user/*
~/.local/share/systemd/user/*
/usr/lib/systemd/user/*
考虑篇幅问题,本期先带来 systemd 的基础介绍,下期将带大家详细了解开机启动的过程中 systemd 的作用机制。
投稿作者 | 作者网站 |
---|---|
微信捐赠 | 支付宝捐赠 |
---|---|
评论功能已经关闭!