Linux Systemd的一些东西

11131 字
56 分钟
Linux Systemd的一些东西

有关 Linux Systemd 的一些东西#

1. 什么是 Systemd#

1.1 Systemd 简介#

Systemd(全称:system daemon)是 Linux 系统中用于启动和管理服务的核心工具,也是其前身 init 的改良版本,

旨在解决传统 init 系统启动时间长、脚本复杂的问题。


Linux 发展初期,系统启动过程中,内核完成初始化以后,由内核第一个启动的程序便是 init 程序,路径为 /sbin/init(为一个软连接,链接到真实的 init 进程),其 PID1,它为系统里所有进程的“祖先”,Linux 中所有的进程都由 init 进程直接或间接进行创建并运行,init 进程以守护进程的方式存在,负责组织与运行系统的相关初始化工作,让系统进入定义好的运行模式,如命令行模式或图形界面模式。

原文链接:https://blog.csdn.net/qq_35995514/article/details/125582824


init 程序的发展,大体上可分为三个阶段:sysvinit->upstart->systemd

阶段描述
sysvinit本质上是一个脚本,通过串行执行shell脚本来启动系统服务,速度缓慢
upstart在前者的基础上,把一些无关联的程序并行启动,提升速度,但是存在依赖关系的服务仍为串行
systemd这个使用了套接字激活的机制,不管程序有无互相依赖全部并行启动,并且仅按照系统启动的需要启动相应的服务,大幅优化开机速度。

1.2 什么是套接字激活机制#

套接字激活(Socket Activation)是 systemd 引入的一种延迟启动机制,核心思想是:先创建监听套接字,等服务真正被请求时再启动对应的服务程序

传统启动: 服务A → 启动完成 → 创建套接字 → 等待连接
服务B (等待A完成才能启动,即使B不依赖A)
套接字激活: systemd 先创建所有套接字(socket单元)
服务并行启动,无需等待彼此
当某个套接字收到请求时,才真正启动对应的服务

用到才启动

**例如 sshd **:

/usr/lib/systemd/system/sshd.socket
[Socket]
ListenStream=22 # systemd 先监听 22 端口
Accept=yes
[Install]
WantedBy=sockets.target
# /usr/lib/systemd/system/sshd@.service
[Service]
ExecStart=-/usr/sbin/sshd -i # 有连接时才启动sshd实例

效果:

  • 开机时 systemd 只占用 22 端口,sshd 进程并未运行

  • 第一次有 SSH 连接进来时,systemd 才按需启动 sshd

  • 如果没人连 SSH,sshd 永远不启动(如果 sshd 使用套接字激活且无人连接,它的状态会显示为 inactive),节省资源

    查看初始状态

    Terminal window
    # 查看 socket 状态(活跃,正在监听22端口)
    $ systemctl status sshd.socket
    sshd.socket - OpenSSH server socket
    Loaded: loaded (/usr/lib/systemd/system/sshd.socket; enabled;)
    Active: active (listening) since Mon 2026-04-13 10:00:00 CST
    # 查看 service 状态(未启动,inactive)
    $ systemctl status sshd.service
    sshd.service - OpenSSH server daemon
    Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled;)
    Active: inactive (dead) since Mon 2026-04-13 10:00:00 CST

    查看连接后状态

    Terminal window
    # 当第一个 SSH 连接进来后
    $ systemctl status sshd@0-192.168.1.100:22-192.168.1.5:54321.service
    sshd@0-192.168.1.100:22-192.168.1.5:54321.service
    Active: active (running) since Mon 2026-04-13 10:05:30 CST
    单元开机时状态有人连接后
    sshd.socketactive (listening)保持 listening
    sshd.serviceinactive (dead)可能仍保持 inactive(取决于配置)
    sshd@xxx.service不存在active (running)

    配置差异

    # 传统方式(Type=simple/forking)- 开机就启动
    [Service]
    Type=simple
    ExecStart=/usr/sbin/sshd -D
    # 套接字激活方式(Type=notify/简单的exec)- 按需启动
    [Service]
    Type=simple
    ExecStart=-/usr/sbin/sshd -i # -i 表示从stdin接受连接
    StandardInput=socket
    StandardOutput=socket

1.3 用户态 和 系统态#

目前主流的系统中,systemd 的守护进程主要分为系统态(system)与用户态(user),可以在 ps -ef中看到 systemd 的守护进程

原文链接:https://blog.csdn.net/qq_35995514/article/details/125582824

1.4 Systemd相关的系统进程#

Terminal window
root@aliyun-worker-01:~# ps -ef | grep systemd
root 277 1 0 Apr01 ? 00:00:06 /usr/lib/systemd/systemd-journald
systemd+ 305 1 0 Apr01 ? 00:00:13 /usr/lib/systemd/systemd-resolved
root 333 1 0 Apr01 ? 00:00:01 /usr/lib/systemd/systemd-udevd
systemd+ 751 1 0 Apr01 ? 00:00:01 /usr/lib/systemd/systemd-networkd
message+ 777 1 0 Apr01 ? 00:00:13 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
root 791 1 0 Apr01 ? 00:00:01 /usr/lib/systemd/systemd-logind
root 832660 1 0 19:47 ? 00:00:00 /usr/lib/systemd/systemd --user
root 832692 832677 0 19:48 pts/0 00:00:00 grep systemd

1.4.1 核心系统组件#

进程作用说明
systemd-journald日志服务收集和管理系统日志,替代传统的 syslog
systemd-resolvedDNS 解析提供本地 DNS 缓存和解析服务
systemd-udevd设备管理处理内核 uevent,管理 /dev 设备节点
systemd-networkd网络管理管理网络配置(你的服务器在用这个而非 NetworkManager)
systemd-logind登录管理管理用户登录会话、电源管理(重启/关机等)

1.4.2 其他相关进程#

进程作用说明
dbus-daemon消息总线系统服务间通信的”邮局”,--systemd-activation 表示支持 systemd 套接字激活
systemd --user用户级 systemd你当前 root 用户的 systemd 用户实例,管理用户级服务

1.5 Systemd 的特点#

1.5.1 兼容性#

systemd 兼容 sysvinit,系统中已经存在的服务无需进行修改,即可迁移到 systemd

1.5.2 启动速度和资源占用#

systemd 依赖于 套接字激活机制,先监听进程的套接字,出现请求时再进行启动。这使其在启动初期只需拉起少量进程,并且允许并行操作,大大加快启动速度。这样不仅能加快启动速度,在一定程度上还可以节省资源。

1.5.3 采用 linux 的 cgroups 跟踪和管理进程的生命周期#

一个服务可能依赖多个进程,systemd 利用了 linux 的内核特性 cgroups 来进行管理和跟踪。当服务停止时,systemd 可以通过查询 cgroups 来获取所有相关进程,从而干净地停止服务。

什么是cgroups:

cgroups 已经出现了很久,它主要用来实现系统资源配额管理。cgroups 提供了类似文件系统的接口,使用方便。当进程创建子进程时,子进程会继承父进程的 cgroups 。因此无论服务如何启动新的子进程,所有的这些相关进程都会属于同一个 cgroups ,systemd 只需要简单地遍历指定的 cgroups 即可正确地找到所有的相关进程,将它们一一停止即可。

原文链接:https://blog.csdn.net/qq_35995514/article/details/125582824

2. Systemd 管理的基本单元 — Unit#

后缀类型作用例子
.service服务后台进程/应用程序nginx.service, sshd.service
.socket套接字监听端口/套接字(用于套接字激活)sshd.socket
.target目标一组 unit 的集合(类似 runlevel)multi-user.target, graphical.target
.timer定时器定时触发任务(替代 cron)apt-daily.timer
.mount挂载点管理文件系统挂载home.mount
.automount自动挂载按需挂载home.automount
.path路径监控文件/目录变化backup.path
.slice切片资源控制组(cgroup)system.slice
.scope范围外部创建的进程组session-1.scope

2.1 .service(服务 - 最常用)#

2.1.1 定义#

后台运行的应用程序或系统服务,是 Systemd 中最常见的 Unit 类型。

2.1.2 配置模板#

/etc/systemd/system/myapp.service
# ============================================
# [Unit] 段:定义服务的元信息、描述和依赖关系
# ============================================
[Unit]
# 服务的简短描述,会显示在 systemctl status 中
Description=My Application
# 文档链接,可以是 man 手册、网页或本地文件路径
# Documentation=man:myapp(8)
# Documentation=https://example.com/docs
# 启动顺序:本服务在 network.target 之后启动
# 注意:After 只控制顺序,不控制依赖
After=network.target
# 强依赖:启动本服务前,必须先启动这些服务
# 如果依赖服务启动失败,本服务也会失败
# Requires=network.target mysql.service
# 弱依赖:尽可能启动这些服务,但不影响本服务启动
# Wants=network.target
# 反向顺序:本服务应该在哪些服务之前启动
# Before=nginx.service
# 冲突服务:不能与这些服务同时运行,会自动停止冲突服务
# Conflicts=another-app.service
# 启动条件判断:条件不满足时跳过启动(软失败)
# ConditionPathExists=/etc/myapp/config.conf
# ConditionDirectoryNotEmpty=/var/lib/myapp
# ConditionUser=root
# 断言判断:条件不满足时启动失败(硬错误)
# AssertPathExists=/usr/local/bin/myapp
# AssertUser=root
# ============================================
# [Service] 段:定义服务的执行方式和生命周期管理
# ============================================
[Service]
# ---- 服务类型 ----
# simple: 默认,ExecStart 启动的进程保持前台运行
# forking: 服务通过 fork 创建守护进程后父进程退出(传统守护进程)
# oneshot: 执行一次性任务,执行完即退出
# notify: 服务启动后会发送 sd_notify 通知 systemd
# dbus: 服务在 D-Bus 上注册后认为启动完成
# idle: 延迟到所有其他任务处理完再启动
Type=simple
# ---- 进程执行配置 ----
# 启动命令(必须),必须使用绝对路径
ExecStart=/usr/local/bin/myapp
# 启动前的准备命令(可多个,按顺序执行)
# ExecStartPre=/usr/local/bin/myapp --check-config
# ExecStartPre=/bin/mkdir -p /var/run/myapp
# 启动后的收尾命令
# ExecStartPost=/bin/echo "MyApp started"
# 停止命令,默认发送 SIGTERM
# ExecStop=/usr/local/bin/myapp --shutdown
# 停止后的收尾命令
# ExecStopPost=/bin/rm -f /var/run/myapp.pid
# 重载配置命令(systemctl reload)
ExecReload=/bin/kill -HUP $MAINPID
# ---- 进程管理 ----
# 主进程 PID 变量,可用于 ExecReload 等
# $MAINPID 由 systemd 自动设置为服务主进程 PID
# 服务运行用户
User=www-data
# 服务运行组(默认与用户主组相同)
# Group=www-data
# 工作目录
# WorkingDirectory=/var/www/myapp
# 根目录(chroot),设置后服务无法访问指定目录外文件
# RootDirectory=/var/chroot/myapp
# 环境变量(可多次指定)
# Environment="NODE_ENV=production"
# Environment="PORT=8080"
# 环境变量文件(每行一个 KEY=VALUE)
# EnvironmentFile=/etc/default/myapp
# EnvironmentFile=-/etc/default/myapp # 减号表示文件不存在也不报错
# ---- 重启策略 ----
# no: 不自动重启(默认)
# on-success: 退出码为 0 时重启
# on-failure: 退出码非 0 时重启
# on-abnormal: 被信号终止或超时重启
# on-abort: 被信号终止时重启(不含 SIGHUP/SIGINT)
# on-watchdog: 看门狗超时重启
# always: 总是重启
Restart=on-failure
# 重启间隔秒数
RestartSec=5
# 启动超时时间(0 表示禁用超时检测)
# TimeoutStartSec=30
# 停止超时时间,超时后发送 SIGKILL
# TimeoutStopSec=30
# 同时设置启动和停止超时
# TimeoutSec=30
# 启动频率限制:在指定时间内最多重启次数
# StartLimitIntervalSec=60
# StartLimitBurst=3
# ---- 标准输入输出 ----
# 标准输出目标:journal/syslog/kern/tty/null/socket/fd/file
# StandardOutput=journal
# 标准错误输出目标
# StandardError=journal
# 标准输入来源:null/tty/tty-force/tty-fail/file/fd/socket
# StandardInput=null
# ---- 资源限制(类似 ulimit) ----
# 文件描述符数量限制
# LimitNOFILE=65535
# 进程数量限制
# LimitNPROC=4096
# core dump 文件大小限制
# LimitCORE=infinity
# ---- 进程终止配置 ----
# 终止模式:
# control-group: 终止控制组内所有进程(默认)
# process: 仅终止主进程
# mixed: 主进程发 SIGTERM,其他进程发 SIGKILL
# none: 不终止任何进程
# KillMode=control-group
# 发送给主进程的终止信号
# KillSignal=SIGTERM
# 最终强制终止信号
# FinalKillSignal=SIGKILL
# 发送 SIGKILL 前的等待时间
# TimeoutAbortSec=30
# ---- 看门狗配置 ----
# 启用看门狗,服务需定期调用 sd_notify("WATCHDOG=1")
# WatchdogSec=30
# ---- 其他配置 ----
# 是否为 oneshot 类型保持激活状态(即使进程已退出)
# RemainAfterExit=yes
# 服务私有的临时文件目录
# RuntimeDirectory=myapp
# RuntimeDirectoryMode=0755
# 状态目录
# StateDirectory=myapp
# 缓存目录
# CacheDirectory=myapp
# 日志目录
# LogsDirectory=myapp
# 配置目录
# ConfigurationDirectory=myapp
# 是否从 /dev、/proc、/sys 挂载私有副本
# PrivateDevices=yes
# 是否启用私有网络命名空间
# PrivateNetwork=yes
# 是否启用私有用户命名空间
# PrivateUsers=yes
# 是否启用私有挂载命名空间
# PrivateMounts=yes
# 能力限制(Linux capabilities)
# CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_DAC_READ_SEARCH
# 安全相关选项
# NoNewPrivileges=yes
# ProtectSystem=strict
# ProtectHome=yes
# ReadWritePaths=/var/lib/myapp
# ============================================
# [Install] 段:定义服务如何安装到系统中
# ============================================
[Install]
# 启用服务时,创建符号链接到该 target 的 .wants 目录
# multi-user.target: 多用户命令行模式(类似运行级别3)
# graphical.target: 图形界面模式(类似运行级别5)
WantedBy=multi-user.target
# 强依赖版本,服务失败会影响 target
# RequiredBy=multi-user.target
# 别名,可用 systemctl enable/disable 操作别名
# Alias=myapp-daemon.service
# 启用/禁用本服务时,同时操作的其他服务
# Also=myapp-socket.socket
# 启用时的默认目标(systemctl enable 时使用)
# DefaultInstance=

2.1.3 常用配置速查表#

配置项作用
[Unit]Description服务描述
[Unit]After/Before启动顺序
[Unit]Requires/Wants依赖关系
[Unit]ConditionPathExists启动条件判断
[Service]Type服务类型 (simple/forking/oneshot/notify)
[Service]ExecStart启动命令
[Service]ExecStop/ExecReload停止/重载命令
[Service]Restart重启策略
[Service]User/Group运行用户/组
[Service]Environment/EnvironmentFile环境变量
[Service]WorkingDirectory工作目录
[Install]WantedBy启用目标

2.1.4 常用命令#

Terminal window
# 重新加载配置
sudo systemctl daemon-reload
# 启动/停止/重启/重载服务
sudo systemctl start myapp
sudo systemctl stop myapp
sudo systemctl restart myapp
sudo systemctl reload myapp
# 设置开机自启/禁用
sudo systemctl enable myapp
sudo systemctl disable myapp
# 查看状态
sudo systemctl status myapp
# 查看日志
sudo journalctl -u myapp -f

2.2 .socket(套接字)#

2.2.1 定义#

监听网络端口或 Unix 套接字,实现按需启动服务。服务未运行时,systemd 先监听端口,有连接时再启动服务。

2.2.2 配置模板#

/etc/systemd/system/myapp.socket
# ============================================
# [Unit] 段:定义套接字单元的元信息和依赖关系
# ============================================
[Unit]
# 套接字单元的简短描述
Description=My Application Socket
# 文档链接
# Documentation=man:systemd.socket(5)
# 启动顺序:在 network.target 之后启动
After=network.target
# 启动顺序:在 sockets.target 之前启动
# sockets.target 是系统启动时统一启动所有套接字的目标
Before=sockets.target
# 弱依赖:启动本套接字前尽可能启动这些服务
# Wants=network.target
# 强依赖:启动本套接字前必须先启动这些服务
# Requires=network.target
# 冲突服务:不能与这些服务同时运行
# Conflicts=shutdown.target
# Before=shutdown.target
# ============================================
# [Socket] 段:定义套接字的监听配置
# ============================================
[Socket]
# ---- 监听地址与类型(核心配置)----
# 流式套接字(SOCK_STREAM)- 通常用于 TCP
# 格式:IP:PORT 或 [IPv6]:PORT 或 /path/to/socket
ListenStream=0.0.0.0:8080
# ListenStream=127.0.0.1:8080
# ListenStream=/run/myapp.sock
# 数据报套接字(SOCK_DGRAM)- 通常用于 UDP
# ListenDatagram=53
# 顺序包套接字(SOCK_SEQPACKET)- 用于可靠有序数据传输
# ListenSequentialPacket=/run/myapp-seq.sock
# FIFO 命名管道
# ListenFIFO=/run/myapp.fifo
# Netlink 套接字(用于内核通信)
# ListenNetlink=audit 1
# POSIX 消息队列
# ListenMessageQueue=/myapp-queue
# USB 功能端点(用于 USB gadget 模式)
# ListenUSBFunction=ffs.myapp
# 特殊值:留空表示重置所有已定义的监听地址
# ListenStream=
# ---- 连接处理模式 ----
# 连接处理方式:
# no(默认):将监听套接字传递给服务,服务自己 accept() 连接
# yes:为每个连接创建独立的 [name@.service] 实例(inetd 风格)
Accept=no
# 当 Accept=yes 时,指定要实例化的服务模板
# 例如:Accept=yes 且 Service=myapp@.service 时,
# 每个连接会创建 myapp@<连接ID>.service 实例
# Service=myapp.service
# ---- 套接字选项 ----
# 绑定 IPv6 专用模式:
# default:根据系统默认(/proc/sys/net/ipv6/bindv6only)
# yes:仅绑定 IPv6
# no:同时绑定 IPv4 和 IPv6(双栈)
BindIPv6Only=default
# 重用端口(允许多个进程绑定同一端口,用于负载均衡)
# ReusePort=yes
# 监听队列长度(backlog)
# Backlog=128
# 是否延迟激活(接收到数据时才激活服务,而非连接建立时)
# DeferAcceptSec=5
# 是否禁用 Nagle 算法(TCP_NODELAY)
# NoDelay=yes
# 是否启用 TCP keepalive
# KeepAlive=yes
# Keepalive 时间设置(秒)
# KeepAliveTimeSec=7200
# Keepalive 探测间隔(秒)
# KeepAliveIntervalSec=75
# Keepalive 探测次数
# KeepAliveProbes=9
# 标记套接字(用于 SO_MARK)
# Mark=1234
# 最大连接数
# MaxConnections=64
# 每个连接的超时时间(秒)
# MaxConnectionsPerSource=256
# 消息队列最大大小(字节)
# MessageQueueMaxMessages=10
# MessageQueueMessageSize=8192
# ---- 文件系统与权限(用于 AF_UNIX/FIFO)----
# 套接字文件的所有者用户
SocketUser=www-data
# 套接字文件的所属组
SocketGroup=www-data
# 套接字文件的权限模式(八进制)
SocketMode=0660
# 自动创建父目录时的权限模式
DirectoryMode=0755
# 停止套接字单元时是否删除文件系统节点
# 注意:通常不推荐使用,因为服务可能仍在使用
# RemoveOnStop=no
# 创建指向套接字的符号链接(别名)
# Symlinks=/run/myapp-alias.sock
# ---- 文件描述符传递 ----
# 为传递的文件描述符命名,便于服务识别
# FileDescriptorName=myapp-socket
# 是否传递套接字的 credentials(SCM_CREDENTIALS)
PassCredentials=yes
# 是否传递套接字的 security context(SELinux)
PassSecurity=yes
# 接收缓冲区大小(字节)
# ReceiveBuffer=8M
# 发送缓冲区大小(字节)
# SendBuffer=8M
# 是否移除传递的套接字上的 close-on-exec 标志
# PassEnvironment=yes
# ---- 触发限制(防雪崩保护)----
# 触发间隔时间(秒)
# TriggerLimitIntervalSec=2
# 在触发间隔内允许的最大触发次数
# TriggerLimitBurst=200
# ---- 执行命令(可选)----
# 启动套接字前的准备命令
# ExecStartPre=/bin/mkdir -p /run/myapp
# 启动套接字后的命令
# ExecStartPost=/bin/chown www-data:www-data /run/myapp.sock
# 停止套接字前的命令
# ExecStopPre=
# 停止套接字后的命令
# ExecStopPost=/bin/rm -f /run/myapp.sock
# ============================================
# [Install] 段:定义套接字如何安装到系统
# ============================================
[Install]
# 启用时链接到 sockets.target
# 这是所有套接字单元的标准目标
WantedBy=sockets.target
# 强依赖版本
# RequiredBy=sockets.target
# 别名
# Alias=myapp-daemon.socket
# 启用/禁用本单元时同时操作的其他单元
# Also=myapp.service

2.2.3 配套的.service模板#

/etc/systemd/system/myapp.service
# ============================================
# Socket Activation 配套的服务单元
# ============================================
[Unit]
Description=My Application Service
# 关键:确保服务在套接字之后启动,且套接字存在
Requires=myapp.socket
After=myapp.socket
[Service]
Type=simple
User=www-data
ExecStart=/usr/local/bin/myapp
# 重启策略
Restart=on-failure
RestartSec=5
# 标准输入输出(如果使用 inetd 风格的 Accept=yes)
# StandardInput=socket
# StandardOutput=socket
[Install]
# 注意:socket 激活的服务通常不需要 WantedBy=multi-user.target
# 因为服务是由套接字按需启动的
# 但如果需要开机启动,可以启用此配置
WantedBy=multi-user.target

2.2.4 两种工作模式对比#

模式Accept=no(默认)Accept=yes
套接字传递传递监听套接字传递已连接的套接字
服务单元myapp.servicemyapp@.service(模板)
连接处理服务自己 accept()每个连接一个独立进程
适用场景高性能服务器(nginx/redis)简单服务、inetd 风格服务
代码要求需支持 sd_listen_fds()可从 stdin 读取(StandardInput=socket)

2.2.5 常用命令#

Terminal window
# 重新加载配置
sudo systemctl daemon-reload
# 启用并启动套接字(服务会在第一个连接时自动启动)
sudo systemctl enable myapp.socket
sudo systemctl start myapp.socket
# 查看套接字状态
sudo systemctl status myapp.socket
# 查看监听中的套接字
sudo systemctl list-sockets
# 手动启动服务(非 socket 激活方式)
sudo systemctl start myapp.service
# 停止套接字(会同时停止关联的服务)
sudo systemctl stop myapp.socket
# 查看日志
sudo journalctl -u myapp.socket -u myapp.service -f

2.2.6 典型应用场景#

  1. 按需启动:服务平时不运行,有请求时才启动(节省资源)
  2. 并行启动:系统启动时并行创建所有套接字,无需等待服务启动完成
  3. 故障恢复:服务崩溃后,新请求会自动重启服务,连接不会丢失(systemd 保持套接字监听)
  4. 无缝升级:可以停止旧服务、启动新服务,而不中断客户端连接

2.2.7 套接字激活机制流程图#

flowchart TD subgraph Stage1["🚀 Systemd 启动阶段"] A1[解析 myapp.socket] --> A2[创建并监听套接字<br/>bind & listen] A2 --> A3[myapp.service<br/>尚未启动] A3 --> A4[继续启动其他服务<br/>实现并行启动] end A4 -->|"等待请求"| Stage2 subgraph Stage2["📡 请求到达时"] B1[客户端连接到套接字] --> B2[Systemd 检测到连接/数据] B2 --> B3[启动 myapp.service] B3 --> B4[传递套接字 FD<br/>sd_listen_fds] B4 --> B5[myapp 直接使用套接字<br/>无需 bind/listen] B5 --> B6[处理请求] end style Stage1 fill:#e1f5fe,stroke:#01579b style Stage2 fill:#e8f5e9,stroke:#2e7d32 style A2 fill:#fff3e0,stroke:#ef6c00 style B4 fill:#fff3e0,stroke:#ef6c00

2.2.8 代码中对套接字激活机制的检测#

// 服务代码中检测 socket activation
#include <systemd/sd-daemon.h>
int main() {
int n = sd_listen_fds(0); // 获取 systemd 传递的 FD 数量
if (n > 0) {
// 使用 FD 3(SD_LISTEN_FDS_START)作为监听套接字
int sock = SD_LISTEN_FDS_START;
accept(sock, ...); // 直接 accept,无需 bind/listen
} else {
// 普通启动,需要自行 bind/listen
}
}

2.3 .target (目标 - 运行级别)#

2.3.1 定义#

一组 Unit 的集合,用于组织启动顺序和系统状态,替代传统的 SysV runlevel。

flowchart LR A[客户端请求] --> B[systemd 监听端口] B --> C[启动服务] C --> D[传递 socket 给服务] D --> E[服务处理请求] style A fill:#e1f5fe style E fill:#c8e6c9 style B fill:#fff3e0 style C fill:#fff3e0 style D fill:#fff3e0

2.3.2 配置模板#

/etc/systemd/system/myapp.target
# ============================================
# [Unit] 段:定义 Target 的元信息和依赖关系
# ============================================
[Unit]
# Target 的简短描述,显示在 systemctl status 中
Description=My Application Target
# 文档链接,可以是 man 手册或网页
Documentation=man:systemd.special(7)
# Documentation=https://example.com/docs/myapp-target
# ---- 依赖关系配置 ----
# 强依赖:启动本 Target 前必须先启动这些 Unit
# 如果依赖的 Unit 启动失败,本 Target 也会失败
Requires=basic.target
# Requires=network.target mysql.service
# 弱依赖:尽可能启动这些 Unit,但不影响本 Target 启动
# Wants=myapp-database.service myapp-cache.service
# 启动顺序:本 Target 在这些 Unit 之后启动
# 注意:After 只控制顺序,不控制依赖
After=basic.target network.target
# After=myapp-database.service
# 启动顺序:本 Target 在这些 Unit 之前启动
# Before=multi-user.target
# 冲突 Unit:不能与这些 Unit 同时运行
# 当切换到本 Target 时,会自动停止冲突的 Unit
Conflicts=rescue.service rescue.target
# Conflicts=shutdown.target
# 反向依赖:哪些 Unit 依赖于本 Target(用于 systemctl enable)
# 通常不需要手动设置,由其他 Unit 的 WantedBy/RequiredBy 决定
# WantedBy=
# RequiredBy=
# ---- 启动条件与断言 ----
# 条件判断:条件不满足时跳过启动(软失败)
# ConditionPathExists=/etc/myapp/enabled
# ConditionDirectoryNotEmpty=/var/lib/myapp
# 断言判断:条件不满足时启动失败(硬错误)
# AssertPathExists=/usr/bin/myapp
# ---- 隔离模式配置 ----
# 允许使用 systemctl isolate 命令切换到此 Target
# 为 yes 时,切换到本 Target 会停止不属于本 Target 的其他 Unit
# 为 no(默认)时,isolate 命令会拒绝执行
AllowIsolate=yes
# 忽略隔离请求(当其他 Target 执行 isolate 时,本 Target 保持运行)
# IgnoreOnIsolate=no
# ---- 停止行为配置 ----
# 停止本 Target 时,是否同时停止依赖它的 Unit
# StopWhenUnneeded=no
# 忽略停止信号(systemctl stop 时保持运行)
# RefuseManualStop=no
# 忽略启动信号(systemctl start 时拒绝启动)
# RefuseManualStart=no
# ---- 其他元信息 ----
# 启动失败时的默认依赖行为
# DefaultDependencies=yes
# 任务超时时间
# JobTimeoutSec=0
# JobRunningTimeoutSec=0
# 超时后的动作:fail/replace/ignore/isolate
# JobTimeoutAction=fail
# JobTimeoutRebootArgument=
# ============================================
# [Install] 段:定义 Target 如何安装到系统
# ============================================
[Install]
# 启用时创建符号链接到该 Target 的 .wants 目录
# 通常不需要设置,因为 Target 一般直接由其他 Unit 引用
# WantedBy=multi-user.target
# 强依赖版本
# RequiredBy=
# 别名
# Alias=myapp-group.target
# 启用/禁用本 Target 时同时操作的其他 Unit
# Also=myapp.service myapp.socket

2.3.3 常用命令#

Terminal window
# 查看当前默认 Target
systemctl get-default
# 设置默认启动 Target
sudo systemctl set-default multi-user.target
sudo systemctl set-default graphical.target
# 切换到指定 Target(会停止不属于该 Target 的 Unit)
sudo systemctl isolate multi-user.target
# 查看 Target 包含的所有 Unit
systemctl list-dependencies multi-user.target
# 查看当前激活的所有 Target
systemctl list-units --type=target
# 查看所有可用的 Target
systemctl list-unit-files --type=target
# 启动/停止 Target
sudo systemctl start myapp.target
sudo systemctl stop myapp.target
# 启用/禁用 Target(通常不需要,因为 Target 由依赖关系驱动)
sudo systemctl enable myapp.target
sudo systemctl disable myapp.target

2.3.4 自定义应用示例#

2.3.4.1 自定义应用组 Target#
/etc/systemd/system/myapp-stack.target
[Unit]
Description=My Application Stack
Documentation=https://docs.example.com
Requires=basic.target network.target
After=basic.target network.target
Wants=myapp-db.service myapp-redis.service myapp-api.service myapp-web.service
AllowIsolate=yes
[Install]
WantedBy=multi-user.target
2.3.4.2 配合 Service 使用#
/etc/systemd/system/myapp-api.service
[Unit]
Description=My Application API Server
PartOf=myapp-stack.target # 当 myapp-stack.target 停止时,本服务也停止
[Service]
ExecStart=/usr/bin/myapp-api
Restart=on-failure
[Install]
WantedBy=myapp-stack.target # 启用时加入 myapp-stack.target 组

2.3.5 Target 与 Service 的关键区别#

特性TargetService
功能组织和管理一组 Unit运行具体的守护进程
进程不启动任何进程启动并管理实际进程
配置段只有 [Unit][Install][Unit][Service][Install]
启动命令ExecStart 等执行命令必须有 ExecStart
状态激活表示依赖的 Unit 已启动激活表示进程正在运行
用途系统状态/运行级别分组具体服务管理

2.4 .timer(定时器 - 替代 cron)#

2.4.1 定义#

在特定时间或周期触发服务,功能比 cron 更强大,支持依赖管理和日志集中。

2.4.2 配置模板#

/etc/systemd/system/myapp.timer
# ============================================
# [Unit] 段:定义 Timer 的元信息和依赖关系
# ============================================
[Unit]
# Timer 单元的简短描述
Description=My Application Timer
# 文档链接
Documentation=man:systemd.timer(5)
# Documentation=https://example.com/docs/myapp-timer
# 启动顺序:在这些 Unit 之后启动
After=network.target
# 弱依赖:尽可能启动这些 Unit
# Wants=network.target
# 强依赖:必须先启动这些 Unit
# Requires=network.target
# 冲突 Unit:不能与这些同时运行
# Conflicts=shutdown.target
# 启动条件
# ConditionPathExists=/etc/myapp/enabled
# ============================================
# [Timer] 段:定义定时触发规则(核心配置)
# ============================================
[Timer]
# ---- 基础定时触发(OnCalendar)----
# 日历时间触发(类似 cron,但语法更强大)
# 格式:DayOfWeek Year-Month-Day Hour:Minute:Second
# 支持通配符 * 和范围
OnCalendar=*-*-* *:*:00 # 每分钟
# OnCalendar=*-*-* 02:00:00 # 每天凌晨 2 点
# OnCalendar=Mon *-*-* 09:00:00 # 每周一 9 点
# OnCalendar=*-*-01 00:00:00 # 每月 1 号
# OnCalendar=*-01-01 00:00:00 # 每年 1 月 1 号
# OnCalendar=Mon..Fri 09:00:00 # 工作日 9 点
# OnCalendar=*-*-* 09:00,15:00:00 # 每天 9 点和 15 点
# 使用具体日期
# OnCalendar=2024-12-25 00:00:00
# 相对时间语法
# OnCalendar=*:0/10 # 每 10 分钟
# OnCalendar=*-01/3-01 00:00:00 # 每 3 个月的 1 号
# 时区指定(默认系统时区)
# OnCalendar=*-*-* 09:00:00 Asia/Shanghai
# ---- 单调时钟触发(Monotonic Timers)----
# 系统启动后触发(开机后)
# OnBootSec=5min # 开机 5 分钟后
# OnBootSec=10s
# 系统首次激活后触发(systemd 第一次启动)
# OnStartupSec=1min
# 本 Timer 单元激活后触发
# OnActiveSec=30min # Timer 启动 30 分钟后
# 关联 Unit 激活后触发
# OnUnitActiveSec=1h # 上次运行后 1 小时
# OnUnitInactiveSec=30min # 上次停止后 30 分钟
# ---- 持久化配置 ----
# 持久化模式:如果系统关机时错过了触发,开机后补执行
# 适用于定时备份、日志轮转等不能错过的任务
Persistent=true
# 持久化精度(秒),控制补执行的精度范围
# AccuracySec=1min
# ---- 随机延迟 ----
# 随机延迟时间,防止多个 Timer 同时触发造成负载高峰
# 格式:秒、分钟、小时等
RandomizedDelaySec=30 # 随机延迟 0-30 秒
# RandomizedDelaySec=5min
# 是否固定随机种子(相同配置产生相同随机数)
# FixedRandomDelay=no
# ---- 触发精度 ----
# 触发精度,用于合并相近时间的触发以节省资源
# AccuracySec=1min # 默认 1 分钟
# AccuracySec=1us # 微秒级(高精度但费电)
# ---- 触发关联配置 ----
# 要触发的 Unit(默认是同名的 .service)
# 例如:myapp.timer 默认触发 myapp.service
Unit=myapp.service
# 触发时传递给 Unit 的属性
# 可以覆盖被触发 Unit 的某些设置
# PropagatesReloadTo=myapp.service
# ---- 触发限制 ----
# 在日历事件触发后,延迟多久才执行
# OnCalendarSec=5min
# 两次触发之间的最小间隔(防抖动)
# OnClockChange=yes # 系统时间调整时是否触发
# ---- 其他配置 ----
# 是否保持 Timer 运行(即使关联的 Unit 失败)
# RemainAfterElapse=yes
# 是否唤醒休眠的系统
# WakeSystem=no # 需要硬件支持 RTC 唤醒
# 硬件时钟使用 UTC 还是本地时间
# UTC=no
# ============================================
# [Install] 段:定义 Timer 如何安装到系统
# ============================================
[Install]
# 启用时链接到 timers.target(所有 Timer 的默认目标)
WantedBy=timers.target
# 强依赖版本
# RequiredBy=timers.target
# 别名
# Alias=myapp-cron.timer
# 启用/禁用时同时操作的其他 Unit
# Also=myapp.service

2.4.3 配套的 .service 文件#

/etc/systemd/system/myapp.service
[Unit]
Description=My Application Service
# 不需要 [Install] 段,因为由 Timer 触发
[Service]
Type=oneshot # 一次性任务,执行完即退出
ExecStart=/usr/local/bin/myapp --run-task
# 如果任务可能运行时间较长,可使用 simple 类型
# Type=simple
# ExecStart=/usr/local/bin/myapp-daemon
# 环境变量
Environment="TASK_TYPE=scheduled"
# 工作目录
WorkingDirectory=/var/lib/myapp
[Install]
# 通常不需要 WantedBy,因为由 Timer 管理
# 但如果需要手动启动,可以保留
# WantedBy=multi-user.target

2.4.4 OnCalendar 语法详解#

DayOfWeek Year-Month-Day Hour:Minute:Second
│ │ │
│ │ └── 时:分:秒
│ └───────────────── 年-月-日
└─────────────────────────── 星期几(英文缩写或数字)
特殊值:
* 匹配任意值
, 列表分隔(如 1,3,5)
.. 范围(如 Mon..Fri)
/ 步长(如 */10 或 0/10)
示例:
*-*-* 00:00:00 每天午夜
Mon *-*-* 09:00:00 每周一 9 点
*-01-01 00:00:00 每年元旦
*-*-01 03:00:00 每月 1 号 3 点
*-*-* 09:00..17:00:00 每天 9 点到 17 点每小时
*:0/5 每 5 分钟
*-01/3-01 00:00:00 每季度第一天
Mon..Fri 08:00:00 工作日早上 8 点
Sat,Sun 10:00:00 周末早上 10 点

2.4.5 常用命令#

Terminal window
# 查看所有 Timer 状态
systemctl list-timers
# 查看所有 Timer(包括未激活的)
systemctl list-timers --all
# 查看特定 Timer 状态
systemctl status myapp.timer
# 启动/停止/重启 Timer
sudo systemctl start myapp.timer
sudo systemctl stop myapp.timer
sudo systemctl restart myapp.timer
# 启用/禁用开机启动
sudo systemctl enable myapp.timer
sudo systemctl disable myapp.timer
# 查看下次触发时间
systemctl list-timers myapp.timer
# 手动触发关联的 Service
sudo systemctl start myapp.service
# 查看 Timer 日志
sudo journalctl -u myapp.timer -f
# 查看 Service 日志
sudo journalctl -u myapp.service -f
# 验证 OnCalendar 语法(测试下次触发时间)
systemd-analyze calendar "*-*-* 02:00:00"
systemd-analyze calendar "Mon..Fri 09:00:00"
# 验证 Timer 配置
systemd-analyze verify /etc/systemd/system/myapp.timer

2.4.6 数据库备份示例#

/etc/systemd/system/db-backup.timer
[Unit]
Description=Daily Database Backup Timer
[Timer]
OnCalendar=*-*-* 02:00:00 # 每天凌晨 2 点
RandomizedDelaySec=10min # 随机延迟 0-10 分钟,避免所有服务器同时备份
Persistent=true # 如果错过(如关机),开机后补执行
[Install]
WantedBy=timers.target
/etc/systemd/system/db-backup.service
[Unit]
Description=Daily Database Backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-db.sh
User=backup
Group=backup

2.5 .mount / .automount(挂载管理)#

2.5.1 定义#

管理文件系统挂载,替代 /etc/fstab 中的条目,支持依赖和自动挂载。

2.5.2 配置模板#

/etc/systemd/system/mnt-data.mount
# ============================================
# [Unit] 段:定义 Mount 的元信息和依赖关系
# ============================================
[Unit]
# Mount 单元的描述
Description=Data Partition Mount
# 文档链接
Documentation=man:systemd.mount(5)
# ---- 依赖关系 ----
# 强依赖:挂载前必须先启动这些 Unit
# 通常需要 local-fs-pre.target 和块设备
Requires=local-fs-pre.target
Requires=dev-disk-by\x2duuid-12345678\x2d1234\x2d1234\x2d1234\x2d123456789abc.device
# 弱依赖
# Wants=
# 启动顺序:在这些 Unit 之后挂载
After=local-fs-pre.target
After=dev-disk-by\x2duuid-12345678\x2d1234\x2d1234\x2d1234\x2d123456789abc.device
# After=network.target # 网络文件系统需要
# 启动顺序:在这些 Unit 之前挂载
Before=local-fs.target
# 冲突 Unit
Conflicts=umount.target
# Conflicts=shutdown.target
# ---- 启动条件 ----
# 条件判断
# ConditionPathExists=/dev/sdb1
# ConditionDirectoryNotEmpty=/mnt/data
# ============================================
# [Mount] 段:定义挂载参数(核心配置)
# ============================================
[Mount]
# ---- 挂载源和目标(必需)----
# 要挂载的设备或远程路径
What=/dev/disk/by-uuid/12345678-1234-1234-1234-123456789abc
# What=/dev/sdb1
# What=192.168.1.100:/nfs/export # NFS 远程路径
# What=//192.168.1.100/share # SMB/CIFS 路径
# 挂载点(必须是绝对路径,且单元文件名必须匹配)
# 例如:mnt-data.mount 对应 /mnt/data
Where=/mnt/data
# ---- 文件系统类型 ----
# 文件系统类型
Type=ext4
# Type=xfs
# Type=btrfs
# Type=nfs
# Type=cifs
# Type=vfat
# Type=auto # 自动检测
# ---- 挂载选项 ----
# 挂载选项(逗号分隔,无空格)
Options=defaults,noatime,discard
# Options=rw,sync,noexec,nosuid,nodev
# Options=username=user,password=pass,uid=1000 # CIFS 认证
# 额外的挂载标志
# SloppyOptions=no # 是否忽略不支持的挂载选项
# ---- 挂载模式 ----
# 是否以只读方式挂载
# ReadOnlyOnly=no # 只尝试只读挂载
# 是否强制作为绑定挂载
# BindPaths=
# BindReadOnlyPaths=
# 是否作为移动挂载(mount --move)
# MoveDirectory=no
# ---- 挂载前准备 ----
# 挂载前执行的命令
# ExecMount=/bin/mount ...
# 挂载后执行的命令
# ExecUnmount=/bin/umount ...
# 挂载前创建挂载点目录
# DirectoryMode=0755
# ---- 超时与重试 ----
# 挂载超时时间
TimeoutSec=30
# TimeoutSec=0 # 0 表示无超时
# 挂载失败时的重试次数
# Retry=0
# ============================================
# [Install] 段:定义 Mount 如何安装到系统
# ============================================
[Install]
# 启用时链接到 local-fs.target(本地文件系统目标)
WantedBy=local-fs.target
# 网络文件系统应使用 remote-fs.target
# WantedBy=remote-fs.target
# 强依赖版本
# RequiredBy=local-fs.target
# 别名
# Alias=data.mount

2.5.3 命名规则(重要)#

单元文件路径挂载点路径
mnt-data.mount/mnt/data
home-user-data.mount/home/user/data
mnt-usb\x2dbackup.mount/mnt/usb-backup-\x2d 转义)
dev-sdb1.mount/dev/sdb1(不推荐直接挂载设备)

转义规则:

  • -\x2d

  • /-(路径分隔符)

  • 其他特殊字符使用 \xHH 十六进制编码

2.5.4 Mount vs Automount 对比#

特性.mount.automount
触发时机系统启动时立即挂载首次访问挂载点时挂载
卸载行为系统关机时才卸载空闲超时时自动卸载
资源占用始终占用资源按需占用,节省资源
适用场景系统必需分区移动设备、网络共享、备份盘
依赖关系需等待设备就绪设备就绪后仍延迟挂载
用户体验启动时可能变慢首次访问可能有延迟

2.5.5 常用命令#

Terminal window
# 查看所有挂载点
systemctl list-units --type=mount
# 查看所有自动挂载点
systemctl list-units --type=automount
# 启动/停止挂载
sudo systemctl start mnt-data.mount
sudo systemctl stop mnt-data.mount
# 重新挂载
sudo systemctl restart mnt-data.mount
# 启用/禁用开机挂载
sudo systemctl enable mnt-data.mount
sudo systemctl disable mnt-data.mount
# 启用/禁用自动挂载
sudo systemctl enable mnt-data.automount
sudo systemctl disable mnt-data.automount
# 查看挂载状态
systemctl status mnt-data.mount
systemctl status mnt-data.automount
# 查看日志
sudo journalctl -u mnt-data.mount -f
# 手动触发自动挂载(通过访问挂载点)
ls /mnt/data
# 查看当前挂载的文件系统
findmnt
mount | grep mnt-data
# 验证配置
systemd-analyze verify /etc/systemd/system/mnt-data.mount

2.5.6 自动挂在本地硬盘示例#

/etc/systemd/system/mnt-data.automount
[Unit]
Description=Data Partition Automount
[Automount]
Where=/mnt/data
TimeoutIdleSec=300 # 5分钟无访问自动卸载
[Install]
WantedBy=local-fs.target
/etc/systemd/system/mnt-data.mount
[Unit]
Description=Data Partition Mount
[Mount]
What=/dev/disk/by-uuid/a1b2c3d4-e5f6-7890-abcd-ef1234567890
Where=/mnt/data
Type=ext4
Options=defaults,noatime
[Install]
WantedBy=local-fs.target

2.5.7 挂载NFS示例#

/etc/systemd/system/mnt-nfs-share.mount
[Unit]
Description=NFS Share Mount
Requires=network-online.target
After=network-online.target
Before=remote-fs.target
[Mount]
What=192.168.1.100:/export/share
Where=/mnt/nfs-share
Type=nfs
Options=defaults,_netdev,soft,intr
[Install]
WantedBy=remote-fs.target

2.5.8 USB 移动设备自动挂载示例#

/etc/systemd/system/mnt-usb.automount
[Unit]
Description=USB Drive Automount
After=local-fs-pre.target
[Automount]
Where=/mnt/usb
TimeoutIdleSec=60 # 1分钟无访问自动卸载
[Install]
WantedBy=local-fs.target
/etc/systemd/system/mnt-usb.mount
[Unit]
Description=USB Drive Mount
[Mount]
What=/dev/disk/by-label/MYUSB
Where=/mnt/usb
Type=auto
Options=defaults,noatime,uid=1000,gid=1000
[Install]
WantedBy=local-fs.target

2.6 .path(路径监控)#

2.6.1 定义#

监控文件或目录变化,触发对应的服务执行。适用于文件上传处理、日志轮转等场景。

2.6.2 配置模板#

/etc/systemd/system/myapp-monitor.path
# ============================================
# [Unit] 段:定义 Path 单元的元信息和依赖关系
# ============================================
[Unit]
# Path 单元的描述
Description=Monitor /var/lib/myapp for changes
# 文档链接
Documentation=man:systemd.path(5)
# ---- 依赖关系 ----
# 启动顺序:在这些 Unit 之后启动
After=basic.target
# 弱依赖
# Wants=
# 强依赖
# Requires=
# 冲突 Unit
Conflicts=shutdown.target
# ============================================
# [Path] 段:定义路径监控规则(核心配置)
# ============================================
[Path]
# ---- 监控路径类型(四种触发条件)----
# 1. 监控目录存在性:当指定目录存在时触发
PathExists=/var/lib/myapp/ready
# 2. 监控目录非空:当指定目录存在且非空时触发
# PathExistsGlob=/var/lib/myapp/uploads/*
# 3. 监控目录内容变化:当目录内文件被创建、修改、删除时触发
PathChanged=/var/lib/myapp/data/
# 4. 监控目录修改时间变化:当目录本身被修改(权限、时间戳等)时触发
# PathModified=/var/lib/myapp/config/
# ---- 触发关联配置 ----
# 要触发的 Unit(默认是同名的 .service)
# 例如:myapp-monitor.path 默认触发 myapp-monitor.service
Unit=myapp-processor.service
# 触发时传递给 Unit 的属性
# MakeDirectory=yes # 如果路径不存在,是否创建目录
# DirectoryMode=0755 # 创建目录时的权限
# ---- 触发行为配置 ----
# 是否触发一次后停止监控(默认 no,持续监控)
# TriggerLimitIntervalSec=0
# TriggerLimitBurst=0
# 触发间隔限制(防雪崩)
# TriggerLimitIntervalSec=2
# TriggerLimitBurst=200
# ============================================
# [Install] 段:定义 Path 单元如何安装到系统
# ============================================
[Install]
# 启用时链接到默认目标
WantedBy=multi-user.target
# 或者使用 paths.target(所有 path 单元的标准目标)
# WantedBy=paths.target

2.6.3 配套的 .service 文件#

/etc/systemd/system/myapp-processor.service
[Unit]
Description=Process myapp data changes
# 不需要 [Install] 段,由 Path 单元触发
[Service]
Type=oneshot # 一次性任务,执行完即退出
ExecStart=/usr/local/bin/myapp-processor
# 如果处理可能并发,使用 simple 类型并处理并发
# Type=simple
# ExecStart=/usr/local/bin/myapp-daemon --process
# 环境变量
Environment="WATCH_PATH=/var/lib/myapp/data"
# 工作目录
WorkingDirectory=/var/lib/myapp

2.6.4 种 Path 触发类型详解#

类型语法触发条件适用场景
PathExistsPathExists=/path/to/file路径存在时触发一次等待配置文件就绪、设备就绪
PathExistsGlobPathExistsGlob=/path/*匹配 glob 模式的文件存在时触发等待特定类型文件出现
PathChangedPathChanged=/path/to/dir/目录内容变化时触发(创建/删除/修改文件)监控上传目录、数据目录
PathModifiedPathModified=/path/to/file文件/目录元数据变化时触发(权限、时间戳、内容)监控配置文件修改

2.6.5 触发行为对比#

PathExists: 文件/目录出现 ──→ 触发(只触发一次,除非删除后重新出现)
文件/目录删除 ──→ 无操作
PathExistsGlob: 匹配的文件出现 ──→ 触发
匹配的文件全部删除 ──→ 无操作
PathChanged: 目录内文件创建 ──→ 触发
目录内文件删除 ──→ 触发
目录内文件修改 ──→ 触发
子目录创建/删除 ──→ 触发
PathModified: 文件内容修改 ──→ 触发
文件权限修改 ──→ 触发
文件时间戳修改 ──→ 触发
目录属性修改 ──→ 触发

2.6.6 常用命令#

Terminal window
# 查看所有 Path 单元状态
systemctl list-units --type=path
# 查看所有 Path 单元(包括未激活的)
systemctl list-units --type=path --all
# 启动/停止 Path 监控
sudo systemctl start myapp-monitor.path
sudo systemctl stop myapp-monitor.path
# 启用/禁用开机监控
sudo systemctl enable myapp-monitor.path
sudo systemctl disable myapp-monitor.path
# 查看 Path 单元状态
systemctl status myapp-monitor.path
# 查看日志
sudo journalctl -u myapp-monitor.path -f
sudo journalctl -u myapp-processor.service -f
# 手动触发关联的 Service
sudo systemctl start myapp-processor.service
# 验证配置
systemd-analyze verify /etc/systemd/system/myapp-monitor.path

2.6.7 配置文件热重载#

/etc/systemd/system/myapp-config.path
[Unit]
Description=Monitor myapp config changes
[Path]
PathModified=/etc/myapp/config.yaml
Unit=myapp-reload.service
[Install]
WantedBy=multi-user.target
/etc/systemd/system/myapp-reload.service
[Unit]
Description=Reload myapp configuration
[Service]
Type=oneshot
ExecStart=/bin/kill -HUP $(cat /run/myapp.pid)

2.7 .slice(资源控制 - cgroup)#

2.7.1 定义#

组织进程树,进行资源限制(CPU、内存、IO)。是 Linux cgroup 的 systemd 封装。

2.7.2 配置模板#

/etc/systemd/system/myapp.slice
# ============================================
# [Unit] 段:定义 Slice 的元信息
# ============================================
[Unit]
# Slice 的描述
Description=My Application Resource Slice
# 文档链接
Documentation=man:systemd.slice(5)
# ---- 依赖关系 ----
# 启动顺序
After=system.slice
# 弱依赖
# Wants=
# 强依赖
# Requires=system.slice
# 冲突
Conflicts=umount.target
# 停止时是否同时停止子单元
# StopWhenUnneeded=no
# ============================================
# [Slice] 段:定义资源限制(核心配置)
# ============================================
[Slice]
# ---- CPU 资源控制 ----
# CPU 权重(相对份额),用于 CPU 调度
# 范围:1-10000,默认 100
# 与其他 Slice 竞争 CPU 时按权重分配
CPUWeight=500
# CPUShares=500 # 旧版 cgroup v1 语法(已弃用)
# CPU 配额:绝对限制,百分比形式
# 100% = 1 个核心,200% = 2 个核心
CPUQuota=80%
# CPUQuotaPeriodSec=100ms # 配额计算周期
# CPU 亲和性:绑定到特定 CPU 核心
# AllowedCPUs=0-3 # 允许使用 0-3 号核心
# AllowedCPUs=0,2,4-6 # 混合指定
# NUMA 节点亲和性
# AllowedMemoryNodes=0-1
# ---- 内存资源控制 ----
# 内存限制(硬限制,超过会被 OOM killer 终止)
MemoryMax=512M
# MemoryMax=1G
# MemoryMax=infinity # 无限制(默认)
# 内存高水位(软限制,超过时内核优先回收该组内存)
MemoryHigh=400M
# 内存交换限制
# MemorySwapMax=1G # 最大交换使用量
# MemorySwapMax=0 # 禁用交换
# 内存最小保证(预留内存,不被其他组借用)
# MemoryMin=256M
# MemoryLow=256M # 尽力保证的内存量
# 内存回收压力控制
# MemoryPressureWatch=auto
# MemoryPressureThresholdSec=100ms
# OOM 控制
# OOMPolicy=continue # OOM 时继续运行(默认)
# OOMPolicy=stop # OOM 时停止
# OOMPolicy=kill # OOM 时杀死进程
# ---- IO 资源控制 ----
# IO 权重(相对份额),用于块设备调度
# 范围:1-10000,默认 100
IOWeight=500
# BlockIOWeight=500 # 旧版语法
# 特定设备的 IO 限制
# IOReadBandwidthMax=/dev/sda 10M
# IOWriteBandwidthMax=/dev/sda 10M
# IOReadIOPSMax=/dev/sda 100
# IOWriteIOPSMax=/dev/sda 100
# IO 延迟目标(控制 IO 延迟)
# IODeviceLatencyTargetSec=/dev/sda 10ms
# ---- 网络资源控制 ----
# 网络带宽限制(需要内核支持,通常通过 tc 实现)
# NetworkIngressMax=100M
# NetworkEgressMax=100M
# IP 防火墙规则(通过 BPF/cgroup 实现)
# IPAddressAllow=192.168.0.0/16
# IPAddressDeny=any
# ---- 任务/进程数量控制 ----
# 最大任务数(线程/进程)
TasksMax=1000
# TasksMax=infinity
# 最大进程数
# TasksAccounting=yes # 启用任务统计
# ---- 设备访问控制 ----
# 允许访问的设备
DeviceAllow=/dev/null rw
DeviceAllow=/dev/zero rw
DeviceAllow=/dev/random r
DeviceAllow=/dev/urandom r
# DeviceAllow=char-pts rw # 允许伪终端
# 拒绝访问的设备(默认策略)
DevicePolicy=closed
# DevicePolicy=auto # 自动(根据类型)
# DevicePolicy=strict # 严格(只允许明确允许的)
# DevicePolicy=closed # 关闭(只允许白名单)
# ---- 控制组属性 ----
# 是否委托控制组给子进程管理
# Delegate=yes
# Delegate=cpu cpuset io memory # 委托特定控制器
# 控制组内存回收策略
# MemoryAccounting=yes # 启用内存统计(默认启用)
# 控制组 CPU 统计
# CPUAccounting=yes # 启用 CPU 统计(默认启用)
# IO 统计
# IOAccounting=yes # 启用 IO 统计(默认启用)
# IP 统计
# IPAccounting=yes # 启用网络统计
# 控制组层级中的权重
# Slice=machine.slice # 指定父 Slice
# ---- 其他资源控制 ----
# 文件描述符限制(继承到子单元)
# LimitNOFILE=65535
# 进程数限制(继承到子单元)
# LimitNPROC=1024
# 核心转储限制
# LimitCORE=0
# 文件大小限制
# LimitFSIZE=1G
# 虚拟内存限制
# LimitAS=2G
# 栈大小限制
# LimitSTACK=8M
# ============================================
# [Install] 段:通常不需要,Slice 由依赖关系驱动
# ============================================
[Install]
# Slice 通常不需要 [Install] 段
# 因为它们被其他单元引用而非直接启用
# WantedBy=

2.7.3 Slice 层级结构#

system.slice (系统默认 Slice)
├── user.slice (用户会话)
│ ├── user-1000.slice (UID 1000 用户)
│ │ ├── session-1.scope (会话 1)
│ │ └── session-2.scope (会话 2)
│ └── user-1001.slice (UID 1001 用户)
├── machine.slice (容器/虚拟机)
│ ├── libvirt-lxc\x2dvm1.scope
│ └── docker-abc123.scope
└── myapp.slice (自定义应用组)
├── myapp-web.service
├── myapp-api.service
└── myapp-db.service

2.7.4 命名规则#

单元文件路径层级位置
myapp.slice直接位于根层级(与 system.slice 同级)
myapp-frontend.slicemyapp.slice 的子 Slice
myapp-backend.slicemyapp.slice 的子 Slice
system-myapp.slice位于 system.slice 下

命名约定:

  • 使用 - 连接父子层级

  • 顶层 Slice 建议放在 system.slice 下(system-xxx.slice

  • 用户相关放在 user.slice 下

2.7.5 常用命令#

Terminal window
# 查看所有 Slice
systemctl list-units --type=slice
# 查看 Slice 树状结构
systemd-cgls
# 查看资源使用情况
systemd-cgtop
# 查看特定 Slice 状态
systemctl status myapp.slice
# 启动/停止 Slice
sudo systemctl start myapp.slice
sudo systemctl stop myapp.slice
# 查看 cgroup 文件系统
ls /sys/fs/cgroup/system.slice/myapp.slice/
# 实时查看资源使用
cat /sys/fs/cgroup/system.slice/myapp.slice/memory.current
cat /sys/fs/cgroup/system.slice/myapp.slice/cpu.stat
# 查看资源限制
cat /sys/fs/cgroup/system.slice/myapp.slice/memory.max
cat /sys/fs/cgroup/system.slice/myapp.slice/cpu.max

2.7.6 数据库资源隔离示例#

/etc/systemd/system/myapp-db.slice
[Unit]
Description=MyApp Database Tier Resources
[Slice]
# 数据库需要更多 CPU
CPUWeight=800
# 内存:2GB 限制,1.5GB 高水位
MemoryHigh=1536M
MemoryMax=2G
# 禁用交换(数据库性能考虑)
MemorySwapMax=0
# IO:最高优先级
IOWeight=1000
# 允许访问块设备(用于原始设备访问)
DeviceAllow=/dev/sda rw
DeviceAllow=block-blkext rw

2.8 .scope(外部进程组)#

2.8.1 定义#

外部程序创建的进程组,systemd 只负责管理生命周期,不启动进程。主要用于用户会话和容器。

2.8.2 与 Service 的区别#

特性ServiceScope
启动方式systemd 启动外部程序创建
主进程systemd 管理外部管理
使用场景守护进程用户会话、容器
配置完整配置主要是资源限制

2.8.3 配置模板#

/run/systemd/transient/session-123.scope
# 通过 systemd-run 创建的临时 scope 示例
# ============================================
# [Unit] 段:定义 Scope 的元信息
# ============================================
[Unit]
# Scope 的描述
Description=User Session 123
# 文档链接
Documentation=man:systemd.scope(5)
# ---- 依赖关系 ----
# 启动顺序
After=systemd-logind.service
# 弱依赖
# Wants=
# 强依赖
# Requires=
# 冲突
Conflicts=shutdown.target
# 停止时是否同时停止
# StopWhenUnneeded=no
# 忽略停止信号
# RefuseManualStop=no
# ============================================
# [Scope] 段:定义进程组管理参数(核心配置)
# ============================================
[Scope]
# ---- 进程管理 ----
# 要管理的初始 PID(创建时指定)
# PIDs=1234 # 主进程 PID
# PIDs=1234 1235 1236 # 多个初始 PID
# 运行时目录
# RuntimeDirectory=session-123
# 运行时目录权限
# RuntimeDirectoryMode=0755
# 状态目录
# StateDirectory=
# 缓存目录
# CacheDirectory=
# 日志目录
# LogsDirectory=
# 配置目录
# ConfigurationDirectory=
# ---- 资源控制(与 Slice 相同)----
# CPU 权重
CPUWeight=100
# CPU 配额
# CPUQuota=
# 内存限制
MemoryMax=1G
# MemoryHigh=800M
# 交换限制
# MemorySwapMax=
# IO 权重
IOWeight=100
# 任务数限制
TasksMax=1000
# 设备访问控制
DevicePolicy=auto
# DeviceAllow=
# ---- 控制组委托 ----
# 是否委托 cgroup 给子进程
# Delegate=yes # 委托所有控制器
# Delegate=cpu memory io # 委托特定控制器
# 委托属性
# DelegateSubgroup=
# ---- _KILL 模式_ ----
# 终止模式
KillMode=mixed # mixed: 主进程 SIGTERM,其他 SIGKILL
# KillMode=control-group # 终止整个控制组
# KillMode=process # 仅终止主进程
# KillMode=none # 不终止任何进程
# 终止信号
KillSignal=SIGTERM
# 最终终止信号
FinalKillSignal=SIGKILL
# 发送终止信号前的等待时间
TimeoutStopSec=30
# 是否发送 SIGHUP
SendSIGHUP=yes
# 是否发送 SIGKILL
SendSIGKILL=yes
# ---- 标准输入输出 ----
# 标准输入
StandardInput=null
# StandardInput=tty
# StandardInput=tty-force
# 标准输出
StandardOutput=journal
# StandardOutput=inherit # 继承调用者的输出
# StandardOutput=null
# StandardOutput=tty
# 标准错误
StandardError=journal
# StandardError=inherit
# StandardError=null
# StandardError=tty
# 终端设置
# TTYPath=/dev/tty1
# TTYReset=yes
# TTYVHangup=yes
# TTYVTDisallocate=yes
# ---- 环境变量 ----
# 环境变量
# Environment=VAR=value
# 环境变量文件
# EnvironmentFile=
# 是否传递环境变量给子进程
# PassEnvironment=
# ---- 其他控制组设置 ----
# 控制组属性
# Slice=session.slice
# 内存压力监控
# MemoryPressureWatch=auto
# MemoryPressureThresholdSec=100ms
# OOM 策略
OOMPolicy=continue
# OOMPolicy=stop
# OOMPolicy=kill
# ============================================
# [Install] 段:通常不需要,Scope 是动态的
# ============================================
[Install]
# Scope 通常是动态创建的,不需要 [Install]
# WantedBy=

2.8.4 创建 Scope 的方式#

2.8.4.1 使用 systemd-run 🌟#
Terminal window
# 创建临时 scope 运行命令
systemd-run --scope --unit=myjob --slice=myapp.slice --property=MemoryMax=512M ./my-script.sh
# 创建交互式 scope
systemd-run --scope --unit=interactive-session --uid=$(id -u) --gid=$(id -g) /bin/bash
# 指定更多资源限制
systemd-run --scope \
--unit=heavy-job \
--property=CPUWeight=200 \
--property=MemoryMax=2G \
--property=IOWeight=50 \
./heavy-processing.sh
2.8.4.2 使用 D-Bus API(程序化)#
package main
import (
"context"
"fmt"
"os"
"os/exec"
"syscall"
"github.com/godbus/dbus/v5"
)
// SystemdManager 封装 systemd D-Bus 调用
type SystemdManager struct {
conn *dbus.Conn
object dbus.BusObject
}
// NewSystemdManager 连接到 systemd D-Bus
func NewSystemdManager() (*SystemdManager, error) {
conn, err := dbus.SystemBus()
if err != nil {
// 尝试用户 bus(用于用户级 systemd)
conn, err = dbus.UserBus()
if err != nil {
return nil, fmt.Errorf("连接 D-Bus 失败: %w", err)
}
}
object := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
return &SystemdManager{
conn: conn,
object: object,
}, nil
}
// RunInScope 在指定 scope 中运行命令
func (m *SystemdManager) RunInScope(
ctx context.Context,
unitName string,
slice string,
properties map[string]interface{},
cmd string,
args ...string,
) error {
// 准备 transient unit 的属性
props := []dbus.Property{
{
Name: "Description",
Value: dbus.MakeVariant(fmt.Sprintf("Scope for %s", unitName)),
},
{
Name: "PIDs",
Value: dbus.MakeVariant([]uint32{uint32(os.Getpid())}),
},
}
// 指定 slice
if slice != "" {
props = append(props, dbus.Property{
Name: "Slice",
Value: dbus.MakeVariant(slice),
})
}
// 添加自定义属性(资源限制等)
for name, value := range properties {
props = append(props, dbus.Property{
Name: name,
Value: dbus.MakeVariant(value),
})
}
// 辅助单元(无)
aux := []dbus.UnitAux{}
// 调用 StartTransientUnit
var jobPath dbus.ObjectPath
err := m.object.CallWithContext(
ctx,
"org.freedesktop.systemd1.Manager.StartTransientUnit",
0,
unitName,
"fail", // 模式: fail/replace/ignore-dependencies/isolate
props,
aux,
).Store(&jobPath)
if err != nil {
return fmt.Errorf("创建 scope 失败: %w", err)
}
fmt.Printf("Scope %s created, job: %s\n", unitName, jobPath)
// 执行实际命令
command := exec.CommandContext(ctx, cmd, args...)
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
// 设置进程组,确保信号正确传递
command.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
return command.Run()
}
func main() {
manager, err := NewSystemdManager()
if err != nil {
fmt.Fprintf(os.Stderr, "错误: %v\n", err)
os.Exit(1)
}
// 资源限制属性
properties := map[string]interface{}{
"MemoryMax": uint64(512 * 1024 * 1024), // 512M
}
// 运行命令
err = manager.RunInScope(
context.Background(),
"myjob.scope",
"myapp.slice",
properties,
"./my-script.sh",
)
if err != nil {
fmt.Fprintf(os.Stderr, "执行失败: %v\n", err)
os.Exit(1)
}
}

2.8.5 常用命令#

Terminal window
# 查看所有 Scope
systemctl list-units --type=scope
# 查看特定 Scope 状态
systemctl status session-1.scope
# 停止 Scope(会终止其中所有进程)
sudo systemctl stop myscope.scope
# 查看 Scope 中的进程
systemd-cgls /user.slice/user-1000.slice/session-1.scope
# 查看资源使用
systemd-cgtop --batch | grep scope
# 查看 cgroup 文件
ls /sys/fs/cgroup/user.slice/user-1000.slice/session-1.scope/
# 将现有进程移入 Scope(通过 busctl)
busctl call org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager AttachProcessesToUnit 'sau' myscope.scope 0 1234 1235
# 创建临时 scope 并立即执行
systemd-run --scope --unit=temporary --collect /path/to/command

2.8.6 容器/沙箱环境示例#

/run/systemd/transient/container-web.scope
# 通过 systemd-nspawn 创建的容器 scope
[Unit]
Description=Container web
[Scope]
Slice=machine.slice
CPUWeight=200
MemoryMax=4G
MemorySwapMax=0
TasksMax=10000
DevicePolicy=closed
DeviceAllow=/dev/null rw
DeviceAllow=/dev/zero rw
DeviceAllow=/dev/random r
DeviceAllow=/dev/urandom r
DeviceAllow=eth0 rw
# 网络命名空间
PrivateNetwork=no
# 用户命名空间
PrivateUsers=yes
# 挂载命名空间
PrivateMounts=yes

3. 结尾#

没了喵,谢谢欣赏

gopher
gopher

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Linux Systemd的一些东西
https://blog.tuf3i.cc/posts/linux-systemd/
作者
TuF3i
发布于
2026-04-13
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
TuF3i
一只区,什么也不想写
分类
标签
站点统计
文章
5
分类
4
标签
8
总字数
9,842
运行时长
0
最后活动
0 天前

目录