1. systemd的历史背景
1号进程init
关于 systemd 的起源,首先要从 Linux 的 init 程序说起。Linux 系统在启动过程中,内核完成初始化以后,由内核第一个启动的程序便是 init 程序,路径为 /sbin/init(为一个软连接,链接到真实的 init 进程),其 PID 为1,它为系统里所有进程的“祖先”,Linux 中所有的进程都由 init 进程直接或间接进行创建并运行,init 进程以守护进程的方式存在,负责组织与运行系统的相关初始化工作,让系统进入定义好的运行模式,如命令行模式或图形界面模式。
init发展的三个阶段
init 程序的发展,大体上可分为三个阶段:sysvinit->upstart->systemd,根据 init 进程的发展特性,可以简单理解为如下:
sysvinit
sysvinit:init 系统通过 shell 脚本以串行的方式启动系统服务,下一个进程必须等待上一个进程启动完成后才能开始启动,因此系统启动的过程比较慢。
upstart
upstart:在 sysvinit 的基础上,把一些没有关联的程序并行启动,以提高启动的速度,但是存在依赖关系的程序仍然为串行启动。
systemd
通过套接字激活的机制,让所有无论有无依赖关系的程序全部并行启动,并且仅按照系统启动的需要启动相应的服务,最大化提高开机启动速度。
systemd的守护进程
systemd 的意思为 system daemon,意为系统守护进程,由 Lennart Poettering 带头开发,采用更加优秀的服务框架,并且与老的 sysvinit 兼容,其设计目的就是克服 sysvinit 与 upstart 的缺点,进一步地提高启动速度。目前主流的系统中,systemd 的守护进程主要分为系统态(system)与用户态(user),可以在 ps -ef 中看到 systemd 的守护进程,如下:
system态systemd
PID 为1的进程/sbin/init 即是 system 态的 systemd,它为一个软链接,指向真实的 systemd 路径,在操作系统中一般放在/lib/systemd/目录:
systemd 为进程服务集合的总称,它包含许多的进程,负责控制、管理系统的资源,其中包括 systemd-login,负责用户登录相关信息的创建、修改与删除;systemd-sleep
每个进程的主要用途可以阅读 freedesktop systemd 手册:https://www.freedesktop.org/s…
目前 systemd 占据 init 程序的主导,有统一天下的趋势。
2.systemd的主要功能
systemd的启动方式
systemd 采用并行的启动方式,并提供按需启动的方式:systemd 在设计之初最关注两件事情:更少的开始,更多的并行。更少的开始,意味着在开机启动阶段,systemd 仅启动系统启动时必要的一些服务,更多其他的服务推迟启动,直到真正需要它的时候,例如蓝牙 bluetooth 与截图相关的服务,开机的时候系统不会用到它; U 盘启动器相关的服务,可以等到接入 U 盘的时候再启动;如果系统未连接到网络,那那些需要用到网络的相关服务也可以无需启动,直到网络连通后的第一次连接再启动即可。更多的并行,意味着服务的启动不需要像 sysvinit 一样序列化启动,而是同时运行所有需要的服务,以便系统 cpu 资源利用的最大化,因此总的启动时间最小化,后面会介绍 systemd 是如何实现所有服务并行启动。
systemd的生命周期管理
采用 cgroup 跟踪管理进程的生命周期:cgroup 为控制组,是一个层级结构,类似与文件管理系统的结构。当一个进程创建了子进程,子进程会继承父进程的 cgroup,就好比子进程创建在父进程的目录下,当子进程又创建一个子进程时,这个子进程会继承上一个子进程的 cgroup,也就相当与继承了父进程的 cgroup,好比这个子进程创建在上一个子进程的目录下,也就在父进程的目录下,通过这一机制就可以把父进程与所有的子进程关联起来并进行跟踪,当停止父进程时,可以通过查询 cgroup 找到所有关联的子进程,从而确保干净的停止所有相关服务。
启动挂载与自动挂载
启动挂载与自动挂载:在系统启动过程中,systemd 在初始化时会自身进行一些挂载,如/sys 目录与/run 目录的挂载,这些都是系统启动时至关重要的文件系统。systemd 还能实现动态挂载点的自动挂载,例如不需要经常使用的光盘、U 盘的挂载,只在这些设备接入时,systemd 启动相应的服务并对其进行临时的挂载以便访问其中的内容,当这些设备拔出时,这些挂载点将被取消以便节约资源。
服务依赖关系管理
事务的依赖关系管理:系统有很多的服务存在依赖关系,例如麒麟软件商店需要等待网络服务的启动,lightdm 与 systemd 交互需要等待 D-Bus 的启动,大多数服务也需要等待 syslog 的完全启动与初始化。systemd 采用 Unit(配置单元)管理这些服务的依赖关系,维护一个事务的一致性,并保证所有的相关服务不会出现相互依赖而产生死锁的情况,后面会对 Unit 进行详细介绍。
日志
日志:systemd 自带 journalctl 命令来查看系统保存的所有日志信息,并且可以支持通过一些参数来对日志进行过滤,方便用户进行日志分析。
其它
其他:systemd 经过几代的更新,实现的功能已经十分的多了,甚至有人觉得 systemd 管得太多了,导致一些服务都没有了存在的必要。例如 systemd 添加了许多 systemctl 的命令,可以实现系统电源的管理;systemd 还添加了看门狗机制,其他守护进程需要定期 ping systemd 进程,否则会视为失败而重启它等等。详情可以去阅读设计师的博客 http://0pointer.de/blog/projects。
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的分类
Unit 文件可以根据其后缀名分为12种不同的类型,systemd 内部给这12种类型的 Unit 定义了不同的全局模板,因此 systemd 的执行流程为:
分类解析流程
首先找到对应的 Unit 文件,然后根据 Unit 文件的类型匹配对应的全局模板,再然后根据这个模板解析 Unit 文件,最后执行 Unit 文件里的操作。接下来简单介绍一下12种 Unit 文件类型:
service
(1)service:这是最明显的单元类型,代表一个后台守护进程,可以启动、停止、重新启动、重新加载守护进程,是最常用的一类 Unit 文件。
socket
(2)socket:这个单元在文件系统或互联网上封装了一个套接字。目前 systemd 支持流、数据报和顺序包类型的 AF_INET、AF_INET6、AF_UNIX 套接字。还支持经典的 FIFO 作为传输。每个套接字单元都有一个匹配的服务单元,相应的服务在第一个连接进入套接字时就会启动,例如:nscd.socket 在传入连接上启动 nscd.service。
device
(3)device:这个单元封装了 Linux 设备树中的一个设备。如果设备通过 udev 规则为此标记,它将在 systemd 中作为设备单元公开。使用 udev 设置的属性可用作配置源来设置设备单元的依赖关系。
mount
(4)mount:这个单元封装了文件系统层次结构中的一个挂载点。systemd 监控所有挂载点,也可用于挂载或卸载挂载点。systemd 会将/etc/fstab 中的条目都转换为挂载点,并在开机时处理。。
automount
(5)automount:这个单元类型在文件系统层次结构中封装了一个自动挂载点。每个自动挂载单元都有一个匹配的挂载单元,当该自动挂载点被访问时,systemd 就会执行挂载点中定义的挂载行为。。
target
(6)target:这种单元类型用于单元的逻辑分组:它本身并不做任何事情,它只是引用其他单元,从而可以一起控制。比如:想让系统进入图形化模式,需要运行许多服务和配置命令,这些操作都由一个个的配置单元表示,将所有这些配置单元组合为一个目标(target),就表示需要将这些配置单元全部执行一遍以便进入目标所代表的系统运行状态。。
snapshot
(7)snapshot:类似于 target 单元,snapshot 本身实际上不做任何事情,它们的唯一目的是引用其他单元。快照可用于保存或回滚 init 系统的所有服务和单元的状态。比如允许用户临时进入特定状态,例如“紧急外壳”,终止当前服务,并提供一种简单的方法返回之前的状态。。
swap
(8)swap:和挂载配置单元类似,交换配置单元用来管理交换分区。用户可以使用交换配置单元来定义系统中的交换分区,可以让这些交换分区在启动时被激活。。
timer
(9)timer:定时器配置单元用来定时触发用户定义的服务操作,是一种基于定时器的服务激活,这类配置单元取代了 atd、crond 等传统的定时服务。。
path
(10)path:这类配置单元用来监控指定目录或者文件的变化,根据变化触发其他配置单元服务的运行。。
scope
(11)scope:这个单元主要表示从 systemd 外部创建的进程。。
slice
(12)slice:此单元主要用于封装管理一组进程资源占用的控制组的 slice 单元,也就是主要用于 cgroup,它通过在 cgroup 中创建一个节点实现资源的控制,一般包含 scope 与 service 单元。
unit文件介绍
Unit 文件主要分为以下三个大的部分:
Unit
Unit 段:此部分所有 Unit 文件通用,用来定义 Unit 的元数据、配置以及与其他 Unit 的关系,Description 描述 Unit 文件信息,Documentation 表示指定服务的文档,Condition 表示服务启动的条件,有些 Unit 还包含 wants、before、after、require 字段,这些表示服务的一个依赖关系。
unit字段及含义
Description:当前服务的简单描述
Documentation:指定 man 文档位置
After:如果 network.target 或 sshd-keygen.service 需要启动,那么 sshd.service 应该在它们之后启动
Before:定义 sshd 应该在哪些服务之前启动
注意:After 和 Before 字段只涉及启动顺序,不涉及依赖关系。
Wants:表示 sshd.service 与 sshd-keygen.service 之间存在"弱依赖"关系,即如果"sshd-keygen.service"启动失败或停止运行,不影响 sshd.service 继续执行
Requires:表示"强依赖"关系,即如果该服务启动失败或异常退出,那么sshd.service 也必须退出
注意:Wants 字段与 Requires 字段只涉及依赖关系,与启动顺序无关,默认情况下是同时启动。
service
service 段:服务(Service)类型的 Unit 文件特有的字段,用于定义服务的具体管理和执行动作。其中包括 Type 字段,定义进程的行为,例如使用 fork()启动,使用 dbus 启动等等;ExecStart、ExecStartPre、ExecStartPos、ExecReload、ExecStop 分别表示启动当前服务执行的命令、启动当前服务之前执行的命令、启动当前服务之后启动的命令、重启当前服务时执行的命令、停止当前服务时执行的命令。
service字段及含义
EnvironmentFile:许多软件都有自己的环境参数文件,该字段指定文件路径
注意:/etc/profile 或者 /etc/profile.d/ 这些文件中配置的环境变量仅对通过 pam 登录的用户生效,而 systemd 是不读这些配置的。
systemd 是所有进程的父进程或祖先进程,它的环境变量会被所有的子进程所继承,如果需要给 systemd 配置默认参数可以在 /etc/systemd/system.conf 和 /etc/systemd/user.conf 中设置。
加载优先级 system.conf 最低,可能会被其他的覆盖。
Type:定义启动类型。可设置:simple,exec,forking,oneshot,dbus,notify,idle
simple(设置了 ExecStart= 但未设置 BusName= 时的默认值):ExecStart 字段启动的进程为该服务的主进程
forking:ExecStart 字段的命令将以 fork() 方式启动,此时父进程将会退出,子进程将成为主进程
ExecStart:定义启动进程时执行的命令
上面的例子中,启动 sshd 执行的命令是 /usr/sbin/sshd -D $OPTIONS,其中的变量 $OPTIONS 就来自 EnvironmentFile 字段指定的环境参数文件。类似的,还有如下字段:
ExecReload:重启服务时执行的命令
ExecStop:停止服务时执行的命令
ExecStartPre:启动服务之前执行的命令
ExecStartPost:启动服务之后执行的命令
ExecStopPost:停止服务之后执行的命令
RemainAfterExit:设为yes,表示进程退出以后,服务仍然保持执行
KillMode:定义 Systemd 如何停止服务,可以设置的值如下:
control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
process:只杀主进程
mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号
none:没有进程会被杀掉,只是执行服务的 stop 命令
Restart:定义了退出后,Systemd 的重启方式。可以设置的值如下:
no(默认值):退出后不会重启
on-success:只有正常退出时(退出状态码为0),才会重启
on-failure:非正常退出时(退出状态码非0),包括被信号终止和超时,才会重启
on-abnormal:只有被信号终止和超时,才会重启
on-abort:只有在收到没有捕捉到的信号终止时,才会重启
on-watchdog:超时退出,才会重启
always:不管是什么退出原因,总是重启
StandardOutput=file:/data/pcie.log #指定标准输出文件路径
RestartSec:表示 Systemd 重启服务之前,需要等待的秒数
配置中多个相同配置会选择最后一个
抑制错误
所有的启动设置之前,都可以加上一个连词号(-),表示"抑制错误",即发生错误的时候,不影响其他命令的执行
EnvironmentFile=-/etc/sysconfig/sshd,表示即使 /etc/sysconfig/sshd 文件不存在,也不会抛出错误
Install
Install 段:此部分所有 Unit 文件通用,通常指定运行目标的 target,使得服务在系统启动时自动运行。Wantedby、RequiredBy 与 Unit 段 Wants 字段类似,表示依赖关系,Alias 字段表示启动运行时使用的别名。
Install字段及含义
WantedBy:表示该服务所在的 Target(服务组)
相关字段更详细的描述可以参考 freedesktop 的 man 手册: https://www.freedesktop.org/s… https://www.freedesktop.org/software/systemd/man/systemd.service.html
5. 自定义服务
- 在 /usr/lib/systemd/system 下新建服务脚本
vim /usr/lib/systemd/system/zdy.service
[Unit]
Description=描述
Environment=环境变量或参数(系统环境变量此时无法使用)
After=network.target
[Service]
Type=forking
EnvironmentFile=所需环境变量文件或参数文件
ExecStart=启动命令(需指定全路径)
ExecStop=停止命令(需指定全路径)
User=以什么用户执行命令
[Install]
WantedBy=multi-user.target
添加或修改配置文件后,需要重新加载。执行如下指令:
systemctl daemon-reload
设置自启动,实质就是在 /etc/systemd/system/multi-user.target.wants/ 添加服务文件的链接。执行如下指令:
systemctl enable zdy
6. 常用指令
开机启动
systemctl enable mysqld
关闭开机启动
systemctl disable mysqld
启动服务
systemctl start mysqld
停止服务
systemctl stop mysqld
重启服务
systemctl restart mysqld
查看服务状态
systemctl status mysqld
systemctl is-active sshd.service
$ sudo systemctl status httpd
httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled)
Active: active (running) since 金 2014-12-05 12:18:22 JST; 7min ago
Main PID: 4349 (httpd)
Status: "Total requests: 1; Current requests/sec: 0; Current traffic: 0 B/sec"
CGroup: /system.slice/httpd.service
├─4349 /usr/sbin/httpd -DFOREGROUND
├─4350 /usr/sbin/httpd -DFOREGROUND
├─4351 /usr/sbin/httpd -DFOREGROUND
├─4352 /usr/sbin/httpd -DFOREGROUND
├─4353 /usr/sbin/httpd -DFOREGROUND
└─4354 /usr/sbin/httpd -DFOREGROUND
12月 05 12:18:22 localhost.localdomain systemd[1]: Starting The Apache HTTP Server...
12月 05 12:18:22 localhost.localdomain systemd[1]: Started The Apache HTTP Server.
12月 05 12:22:40 localhost.localdomain systemd[1]: Started The Apache HTTP Server.
Loaded
行:配置文件的位置,是否设为开机启动Active
行:表示正在运行Main PID
行:主进程IDStatus
行:由应用本身(这里是 httpd )提供的软件当前状态CGroup
块:应用的所有子进程- 日志块:应用的日志
结束服务进程(服务无法停止时)
systemctl kill mysqld
列出正在运行的 Unit
$ systemctl list-units
查看启动耗时 | systemd-analyze |
---|---|
查看每个服务的启动耗时 | systemd-analyze blame |
显示瀑布状的启动过程流 | systemd-analyze critical-chain |
显示指定服务的启动流 | systemd-analyze critical-chain 单元 |
SVG图像输出 | systemd-analyze plot [> file.svg] |
列出所有Unit,包括没有找到配置文件的或者启动失败的
$ systemctl list-units --all
列出所有没有运行的 Unit
$ systemctl list-units --all --state=inactive
列出所有加载失败的 Unit
$ systemctl list-units --failed
列出所有正在运行的、类型为 service 的 Unit
$ systemctl list-units --type=service
显示系统状态
$ systemctl status
显示单个 Unit 的状态
$ sysystemctl status bluetooth.service
显示远程主机的某个 Unit 的状态
$ systemctl -H 用户名@ip status httpd.service