systemd v262 升级笔记:协议错误、静默失效与回滚不兼容
systemd v262 于 2026-09-22 发布。这批破坏性变更大多不会在升级那一刻报错,出问题的形式大致是四类:
- 服务在下次启动时才失败。
Type=notify-reload的 unit 若没有在发送READY=1前捕获或屏蔽ReloadSignal=,会直接以协议错误启动失败。以前这类服务能起来,只是后续 reload 可能触发信号的默认动作把进程杀掉。 - 行为静默改变。
journalctl -F配合过滤器现在被拒绝(以前过滤器被悄悄忽略);UnsetEnvironment=的生效顺序反了;速率限制计时器换了时钟源;DNS-SD 的一次性注册在客户端断开后自动撤销。 - 跨版本不对称。TPM 封装凭据现在钉到 TPM 的 SRK,v262 之后铸造的凭据旧版 systemd 不认;反向兼容只有一个方向——新版仍接受变更前创建的凭据。
- 路径与接口改名。sysupdate 的 unit、udevd 的控制 socket、几个 JSON 输出结构都动了。
unit 与 service manager 行为
Type=notify-reload 是这次最容易踩的一条。修改方式是在进程里为 ReloadSignal= 指定的信号安装 handler 或 sigprocmask() 屏蔽,然后再发 READY=1。
UnsetEnvironment= 现在在 ExecStart= 等命令行完成变量展开之后才应用。变量仍可在命令行里使用,但不会出现在被执行进程的环境里。依赖“整词 $VAR 引用被丢弃”这一旧行为的 unit 需要改写。
sd-event 的速率限制计时器,以及 StartLimitIntervalSec= 这类 manager 计时器,从 CLOCK_MONOTONIC 换成了 CLOCK_BOOTTIME。系统挂起的时间现在计入速率限制的过期计算;升级或 reexec 后,序列化下来的限制状态可能比预期更早过期。
manager 现在内置了一小组 unit 文件(basic.target、sysinit.target、multi-user.target、reboot.target、shutdown.target、systemd-poweroff.service),磁盘加载失败时用它们兜底,磁盘上的文件和 mask 仍然优先。
新增能力里值得记的:RestartRandomizedDelaySec= 在 RestartSec= 和可选指数退避之上再加一段均匀分布的延迟,用来打散同步重启风暴;slice 单元的 ActivatingConcurrencyMax= 限制一个 slice 层级里同时处于 activating 状态的单元数;manager 新增 EnqueueUnitJobMany(),在单个事务里排队多个 unit 的 start/stop/restart/reload;StartTransient() 接受更多临时单元属性;SecureBits=、NUMAPolicy=(preferred-many 需 Linux 5.15+,weighted-interleave 需 Linux 6.9+)、ConditionCPUFeature=(支持 arm64 ELF hwcaps 和 arm64.bti 这类 ARCH.FEATURE 前缀名)都有扩充。PID 1 暴露 KExecCount 与前几次 kexec 周期的关机时间戳。主机名配置新增 $ 通配,按哈希从词表文件里取词,与已有的 ? 并存。
systemd 也可构建为单一静态链接的 PID 1/executor 二进制,参数是 --default-library=static --prefer-static -Dbuild-static=true -Dsystemd-multicall-binary=true。
另外一条容易被忽略的:通过 resolved 的 RegisterService() 注册的 DNS-SD 服务,现在会在注册它的客户端断开总线连接时自动注销,行为对齐 Avahi。用 busctl、gdbus 做的一次性注册,工具退出即失效。要脱离客户端生命周期,改用 *.dnssd 文件。
journalctl 与日志
journalctl -F/--field 现在拒绝与 unit、boot、时间、cursor、grep 这些过滤器同时使用。旧实现走 sd_journal_query_unique(),表达不了这些过滤器,所以一直是静默忽略——脚本里 journalctl -u foo -F _PID 这种写法过去只是“看起来生效”,现在会直接报错。需要字段列表就先不带过滤器地列,再对条目单独过滤。
journald 侧:对于高频写入期间被非正常关机截断 header/tail 的活动日志文件,读取端会尝试抢救其中完整的条目;可写打开仍然严格,状态不适合追加的文件会被 rotate 掉。
sealing/FSS 支持从 libgcrypt 迁移到 OpenSSL,libsystemd 不再链接 libgcrypt。已封存的日志文件在没有 sealing 支持的环境下仍可读,只是跳过 seal tag 校验。
io.systemd.JournalAccess.GetEntries 这个 Varlink 方法增加了 follow 模式,follow=true 时像 journalctl -f 一样流式推送新条目。
networkd / udev / boot
udevd 的旧控制 socket 已移除,udevadm 无条件走 Varlink IPC;/run/udev/control 变成指向 Varlink socket 的符号链接,该路径上不再支持旧协议。udevd 的 worker 在可能时运行于同级的 workers cgroup,配合 cgroup.kill 原子清理残留 worker。sd-device 和 udevd 现在忽略 uevent 中无效的普通内核属性,而不是拒收整条事件。
networkd:[Match] 新增 MachineTag=;IPv6ProxyNDPAddress= 改名为 ProxyNeighbor=(旧名保留为废弃别名),且可同时代理 IPv4 和 IPv6;networkctl reload 新增 --no-reconfigure;IPoIB=、IPVLAN=、IPVTAP=、MACsec=、MACVLAN=、MACVTAP=、Tunnel=、VLAN=、VXLAN=、Xfrm= 接受空白分隔的 netdev 名列表。
boot/stub/bootctl:随机种子的处理尊重只读 ESP/vFAT 标志;boot entry 文件只读时禁用 boot counting;systemd-stub 会初始化所有检测到的控制台;bootctl 新增 link-auto。
coredump 支持内核 coredump socket 协议(Linux 6.17+),systemd-coredumpd.service 监听 /run/systemd/coredumpd/kernel;旧内核仍走每个 coredump 一个的 systemd-coredump@.service。改动 coredump.conf 需要 reload systemd-coredumpd.service;自定义了 kernel.core_pattern 的部署要停掉并 disable 该服务。
其余改名与格式变化:systemd-sysupdate.service/.timer 改名为 systemd-sysupdate-update.service/.timer(提供兼容符号链接),systemd-sysupdate 二进制从 bindir 移回 libexecdir(重新标记为实验性);systemd-sysext status --json= 和 systemd-confext status --json= 的 extensions 字段恒为数组,未 merge 的层级报空数组而非 "none",表格输出显示 -;systemd-sysupdated 的 DescribeFeature() JSON 中 name 键改为 id,documentation 改为字符串数组;ukify inspect --json= 对含重复 PE section 和 profile 的 UKI 改了输出,重复 section 输出为数组,多 profile UKI 在新增的 _profiles 数组下描述各 profile 的 section;systemd-repart 的 MakeSymlinks= 现在也会展开符号链接目标里的 % 说明符(以前只展开源);.nspawn 中 PrivateUsers= 为 pick 或 managed 时,PrivateUsersOwnership= 默认值变为 auto;homed 新建的 fscrypt 家目录默认使用 fscrypt v2 策略。
凭据与 TPM
TPM 封装的凭据现在钉到 TPM 的 SRK,用于阻断中间人 interposer 攻击。这一改动让新铸凭据与旧版 systemd 之间单向不通:v262 之后创建的、绑定 TPM 的凭据在旧版上无法识别,而 v262 仍接受变更前创建的凭据。混合版本环境或需要回滚的机器,要在升级前确认凭据来源。
systemd-cryptsetup 的 crypttab 选项 tpm2-measure-bank= 已废弃且不再有任何效果。卷密钥度量改为通过 varlink 调用 systemd-pcrextend,由它选择所有合适的 TPM2 PCR bank。
OpenPGP keyring 的默认查找现在把 /usr/ 下的厂商 keyring 与 /etc/ 下的本地 keyring 合并。原先只靠一个 /etc/ keyring 来排除厂商信任的部署,需要显式设置 $SYSTEMD_OPENPGP_KEYRING。
新增内核命令行选项 systemd.credentials_boot_policy=,控制何时接受用 null 密钥加密的 boot 凭据,取值 strict / tofu / relaxed(默认)/ off。systemd 也会把 initrd 中 /.extra/system_credentials/ 下的凭据作为可信系统凭据导入。
升级前自查
# 1. 找出所有 notify-reload 单元,逐个确认 ReloadSignal 是否被处理
grep -rl 'Type=notify-reload' /etc/systemd/system /usr/lib/systemd/system
# 2. 看单个 unit 的关键属性与限制参数
systemctl show | grep -Ei 'Type|ReloadSignal|NotifyAccess|UnsetEnvironment|Restart|StartLimit'
# 3. 找出依赖 UnsetEnvironment 整词丢弃语义的单元
grep -rn '^UnsetEnvironment=' /etc/systemd/system
# 4. unit 文件层面的语法与依赖检查(协议错误需在运行时才暴露)
systemd-analyze verify /etc/systemd/system/*.service
# 5. journalctl 字段列表使用法差异:不带过滤器列字段,再对条目过滤
journalctl -F _PID
journalctl -u -n 50 -o verbose
# 6. udev 控制路径是否已切到 Varlink
ls -l /run/udev/control
udevadm info /dev/sda
# 7. networkd 配置中的已改名/待废弃键
grep -rn 'IPv6ProxyNDPAddress=\|ProxyNeighbor=' /etc/systemd/network
networkctl status
networkctl reload --no-reconfigure
# 8. boot 与 coredump 相关状态
bootctl status
sysctl kernel.core_pattern
systemctl status systemd-coredumpd.service
# 9. 凭据侧:crypttab 中已失效的选项、sysupdate 单元改名后的实际单元名
grep -rn 'tpm2-measure-bank' /etc/crypttab
systemctl list-unit-files | grep sysupdate
# 10. JSON 输出结构变更影响的调用
systemd-sysext status --json=short