6月 112013
 

先介绍下背景。我使用 Fvwm 做窗口管理器,用 FvwmButtons 做了一个任务栏,还做了一个类似 Unity 或 GNOME Shell 侧栏的应用程序快捷启动器栏,并且把工作区切换器(一个 FvwmPager)嵌在里面。我用比较多的 GTK 或 GNOME 程序,但不开 gnome-session。为了使 GNOME 控制中心的设置生效,我在 Fvwm 配置中在初始化阶段启动 gnome-settings-daemon。

这样的配置一直工作地很好,但升到 Fedora 18 之后,同样的配置下,进入桌面后会卡死几秒钟,之后 CPU 负荷很快达到并保持 100%,用 htop 查看是两个 FvwmButtons 进程占用了很高的 CPU,不得不用 SIGKILL 信号杀死。当我注释掉一个 FvwmButtons 后,高 CPU 负荷的问题出现的机率大为减少,但还是偶有发生。两个 FvwmButtons 都注释掉,CPU 不会 100%。这时进入桌面后再手动开两个 FvwmButtons,也不会再有 CPU 持续 100% 的问题。这一诡异的问题使我很长一段时间内都要进入桌面后再开启两个工具栏。之前还集中精力调试过一次,以为可能是 race condition 之类的问题,试图通过调整启动过程中两个 FvwmButtons 的启动顺序位次来解决,但后面测试表明这不是有效的解决手段,问题还会不时出现。

随着我升级到 Fedora 18 还有一个有关 Fvwm 的问题出现:使用 Fvwm 的 Restart 命令重启桌面后,通知区域的部分图标(如 Fcitx、NetworkManager)会丢失。尽管相应的主程序还在运行可以使用,但没有这一小图标一些事情就做不了或不方便做了。我的通知区域由 stalonetray 提供,它被嵌在任务栏中。与此同时,经常收到 ABRT 报告说 control-center 遇到问题等。此外,程序的字体大小会变小(恢复默认值),而不是我配置的字体大小。这一问题使得 Restart 重启桌面不再有用,这在使用 Fedora 16 和更早时是没有过的。

近日我又对这些问题有了兴趣。首先我关心的是通知区域图标丢失的问题。通过对 Fvwm 配置中重启时调用的函数 StartFunc 的调试,我发现问题在于其中有一行 killall stalonetray。它是用来杀死已有的 stalonetray 进程的。正常来讲,这里杀死旧进程后,开启 FvwmButtons 任务栏时会启动新的 stalonetray 进程。(根据版本库记录,去年的 4 月份做的这样配置,参考自论坛讨论。)然而在 Fedora 18 上,杀死 stalonetray 进程后,gnome-settings-daemon 进程就挂掉了!事实上,我可以在命令行下启动 gnome-settings-daemon(它在 /usr/libexec/ 目录下)和 stalonetray,然后命令行 killall stalonetray,这时 gnome-settings-daemon 会报一大段错误,退出程序。于是,问题解决需要不杀掉 stalonetray。最终的解决方案是 StartFunc 中删除 killall 这一行,并且在 FvwmButtons 配置中做如下改动(diff 格式):

-*FvwmButtonsTop: (3x1, Frame 0, Left, Swallow(Close,Kill,UseOld) "stalonetray" `Exec exec stalonetray`)
+*FvwmButtonsTop: (3x1, Frame 0, Left, Swallow(NoClose,UseOld) "stalonetray" `Exec exec stalonetray`)

其中关键是 NoClose,它使得每次 Restart 之后 FvwmButtons 被自动杀掉时,stalonetray 会被吐出 (unswallow),之后再启动 FvwmButtons 任务栏后又会吞入 (swallow)。stalonetray 进程一直活着,其中的图标也不会丢失了。事实上,最初我对字体大小变化的问题并不敏感,没有意识到这正是 gnome-settings-daemon 相关的反应。之前一直怀疑 stalonetray 不够可靠,浪费了好些时间。滑稽的是,这和我去年 4 月份做的修改恰好相反,只是那是 Fvwm 是 2.6.4,现在是 2.6.5,而 stalonetray 是许久没有版本更新了。也许这里的 bug 报告使得 NoClose 作用于 stalonetray 变正常了。

这一问题解决之后,我又试图调查进入桌面时卡死数秒的问题。为了找出可疑的进程,我在控制台终端登录,在 GDM 登录 Fvwm 之前,启动一行命令(主要目的是每隔一秒打印出占用 CPU 最高的 10 个进程):

sleep 2; while true; do ps aux --sort=-pcpu | head -n 10; sleep 1; done | tee test/log

很遗憾这样得到的输出没有多大帮助,若干个进程(fvwm, gnome-settings-daemon, nm-applet)都会短时间内占用不少的 CPU,但没有哪个看起来特别突出。最后真正有效的排查方法很土,就是将 Fvwm 的启动函数内容全部注释,然后逐个加入,测试观察。测试结果表明,问题出在 gnome-settings-daemon 上。不启动这个进程时,Fvwm 进入桌面没有可以感觉到的卡顿,很快就加载好自启动程序。而一加入 gnome-settings-daemon,进入桌面时就会遇到数秒内 CPU 负荷很高桌面卡死的现象。在桌面里,手工启动 gnome-settings-daemon 也有类似反应。这使我 Google 了一下 “gnome-settings-daemon high cpu”,结果的确看到了一些 bug 报告,包括 GNOME Bugzilla 的这个。不过这里的问题和我遇到的并不一样,这里的问题是 gnome-settings-daemon 的 CPU 负荷持续居高不下,而且和 NumLock 有关系。我的确试了其中的与 NumLock 相关的一行修复,不过对我的情形没有什么帮助。

定位到问题在于 gnome-settings-daemon 使我意识到,在 Fvwm 的 InitFunc 里启动它是不行的,至少对 Fedora 18 上的 gnome-settings-daemon 3.6.4 是不行的。于是我考虑把启动它提前,让它不在 InitFunc 执行期间和更后面制造麻烦。具体来讲,我在 Fvwm 配置文件中删除了启动 gnome-settings-daemon 这一行。然后把 /usr/share/xsessions/fvwm.desktop 文件中 Exec 一行修改如下:

Exec=/home/alick/bin/startfvwm

这里的 startfvwm 内容如下(记得要给它可执行权限!):

#!/bin/sh
/usr/libexec/gnome-settings-daemon &
sleep 5
fvwm

后续测试表明这样确实解决了进入桌面时卡死的问题,尽管在进入前引入了 5 秒的延时。(我试过设置成 2 秒,结果这时重启机器后第一次进入 Fvwm 时,仍会要一段时间的卡死。)之后 Fvwm Restart 或注销重新登录时都能很快准备好桌面。笔记本睡眠、台式机24小时工作会弱化这个延时的负面影响。

值得一提的是,我起初尝试 Archlinux 维基中提到的 .xprofile,期望 GDM 在启动 xsession 前先执行它。测试发现在我的 Fedora 18 上 GDM 是无视这个文件的。粗略 grep 了一下 /etc/gdm//etc/X11/xinit/ 下的文件,也没有 xprofile 出现的痕迹。