症状
我频繁遇到 Pipewire 音频框架突然停止运行的情况:
- 问题通常出现在我的笔记本电脑连接/断开电源的时候,此时我的电脑会因为切换性能模式卡顿一小段时间;
systemctl --user status pipewire.service
只能看到 Pipewire 进程被SIGKILL
信号终止,没有其它有用的日志信息;coredumpctl
和dmesg
里也找不到 Coredump 内存转储事件的记录。
原因
Pipewire 进程运行时具有实时优先级,其调度需求被最优先满足,以便及时处理音频数据,避免音频卡顿。Pipewire 提高进程优先级是通过它的 libpipewire-module-rt
模块请求系统中以 root
权限运行的 RTKit
(Realtime Kit)服务,然后 RTKit
以特权修改进程优先级来达成的。
但是,如果一个具有实时优先级的进程出了 Bug,进入了死循环,那么它会占用所有的 CPU 资源。系统上绝大部分其它进程(包括但不限于 SSH 服务端,Xorg,还有你的 Shell)由于优先级更低,就无法得到任何 CPU 时间片,无法处理任何任务,包括你尝试修复系统时输入的命令。
为了避免这个问题,Linux 内核默认对实时进程的运行时长做了限制。在默认设置下,实时进程必须在 200 毫秒内完成这一次的计算(例如 Pipewire 的音频处理),调用
sched_yield
系统调用把 CPU 时间片交还给其它进程。之后这个进程就可以在后台等待下一次事件触发(例如声卡的音频缓冲区即将耗尽),Linux 内核再次调度这个实时进程。如果实时进程在 200 毫秒后仍未完成计算,Linux 内核会直接发送 SIGKILL 信号结束进程。
由于我的电脑在切换性能模式时发生卡顿,Pipewire 处理音频数据的耗时超过了 200 毫秒,就被 Linux 内核直接结束了进程。
解决方法
由于我没法解决电脑切换性能模式时卡顿的问题,我选择把 Pipewire 的运行时长限制提升到 5 秒,足够电脑卡顿时 Pipewire 处理音频数据。
首先需要修改 Pipewire libpipewire-module-rt
模块的参数,让 Pipewire 申请更长的时间限制:
{
"context.modules": [
{
"args": {
"nice.level": -11,
"rt.prio": 88,
"rt.time.hard": 5000000,
"rt.time.soft": 5000000
},
"flags": ["ifexists", "nofail"],
"name": "libpipewire-module-rt"
}
]
}
其中 5000000 的单位是微秒,换算成秒数为 5 秒整。
然后,由于 RTKit
还有一层运行时长限制,我们还需要给 RTKit
添加启动参数,提高它的限制。运行 systemctl edit rtkit-daemon.service
,然后输入以下内容:
[Service]
# 先清除掉原先的 ExecStart 命令
ExecStart=
# 然后换成我们的加了参数的命令,如果你的发行版 rtkit-daemon 路径不同,请自行修改
ExecStart=/usr/lib/rtkit-daemon --rttime-usec-max=5000000
如果你用的是 NixOS 系统,可以直接使用下面的配置:
let
# 时间限制,单位是微秒
realtimeLimitUS = 5000000;
in {
security.rtkit.enable = true;
systemd.services.rtkit-daemon.serviceConfig.ExecStart = [
"" # 清除掉原先的 ExecStart 命令
"${pkgs.rtkit}/libexec/rtkit-daemon --rttime-usec-max=${builtins.toString realtimeLimitUS}"
];
services.pipewire.enable = true;
environment.etc = {
"pipewire/pipewire.conf.d/rtprio.conf".text = builtins.toJSON {
"context.modules" = [
{
name = "libpipewire-module-rt";
args = {
"nice.level" = -11;
"rt.prio" = 88;
"rt.time.soft" = realtimeLimitUS;
"rt.time.hard" = realtimeLimitUS;
};
flags = ["ifexists" "nofail"];
}
];
};
};
}