【发布时间】:2017-06-08 09:25:41
【问题描述】:
总结
我已经为这个问题制定了解决方案。
基本上,被调用者 (wallpaper) 本身并没有退出,因为它正在等待另一个进程完成。
在 52 天的时间里,这种有问题的副作用越来越大,直到 10,000 多个延迟进程消耗了 10 多 GB 的 RAM,几乎使我的系统崩溃。
问题进程原来是从一个名为 log 的函数调用 printf,我已将它发送到后台并忘记了,因为它正在写入管道并挂起。
事实证明,写入命名管道的进程将阻塞,直到另一个进程出现并从中读取。
这反过来又将问题的要求从“我需要一种方法来阻止这些进程建立”改为“我需要一种更好的方法来绕过 FIFO I/O,而不是把它扔到后台”。
请注意,虽然问题已经解决,但我很乐意接受技术层面的详细回答。例如,为什么调用者脚本的 (wallpaper-run) 进程也会被复制,即使它只被调用一次,或者如何正确读取管道的状态信息,而不是依赖于 open 的未解之谜使用O_NONBLOCK 调用时失败。
原始问题如下。
问题
我有两个要循环运行的 bash 脚本。第一个 wallpaper-run 在无限循环中运行并调用第二个 wallpaper。
它们是我的“桌面”的一部分,它是一堆组合在一起的 shell 脚本,用于扩充 dwm 窗口管理器。
壁纸运行:
log "starting wallpaper runner"
while true; do
log "..."
$scr/wallpaper
sleep 900 # 15 minutes
done &
壁纸:
log "changing wallpaper"
# several utility functions ...
if [[ $1 ]]; then
parse_arg $1
else
load_random
fi
一些注意事项:
log是从init导出的函数,顾名思义,它记录一条消息。init在其前台调用wallpaper-run(除其他外)(因此 while 循环在后台)$scr也是由init定义的;它是所谓的“init-scripts”所在的目录parse_arg和load_random是wallpaper的本地对象尤其是图片通过
feh程序加载到后台壁纸运行的加载方式如下:
$mod/wallpaper-runinit由
startx直接调用,并在运行wallpaper-run(和其他“模块”)之前启动dwm
现在问题是,由于某种原因,壁纸运行和壁纸都“徘徊”在内存中。也就是说,在循环的每次迭代之后,都会创建两个新的壁纸实例和壁纸运行实例,而“旧”实例不会被清理并陷入睡眠状态。这就像内存泄漏,但有拖延的进程而不是糟糕的内存管理。
在我的系统运行 52 天后,我发现了这个“进程泄漏”,因为系统内存不足。我必须杀死超过 10,000 个壁纸/运行实例才能使我的系统恢复正常工作。
我完全不知道为什么会这样。我认为这些脚本没有理由留在内存中,因为脚本退出应该意味着它的进程被清理了。
他们为什么徘徊和消耗资源?
更新 1
在 cmets 的一些帮助下(非常感谢 I'L'I),我将问题追溯到函数 log,它对 printf 进行后台调用(尽管我为什么选择这样做,但我不不记得了)。这是 init 中出现的函数:
log(){
local pipe=$pipe_front
if ! [[ -p $pipe ]]; then
mkfifo $pipe
fi
printf ... >> $initlog
printf ... > $pipe &
printf ... &
[[ $2 == "-g" ]] && notify-send "[DWM Init] $1"
sleep 0.001
}
如你所见,这个函数写得很糟糕。我将它组合在一起是为了使其工作,而不是让它变得健壮。
第二个和第三个 printf 被发送到后台。我不记得我为什么这样做了,但大概是因为第一个 printf 一定是让日志挂起。
printf 行已被删节为“...”,因为它们相当复杂且与手头的问题无关(而且我用 40 分钟的时间做的事情比处理 Android 的垃圾文本更好输入接口)。特别是,诸如当前时间、调用进程的名称和传递的消息之类的东西会被打印出来,这取决于我们谈论的是哪个 printf。第一个具有最详细的信息,因为它被保存到一个丢失即时上下文的文件中,而 notify-send 行的详细信息最少,因为它将显示在桌面上。
整个管道崩溃是为了通过我为它编写的基本 shell 直接与 init 交互。
第三个 printf 是故意的;它打印到我在会话开始时登录的 tty。这样,如果 init 突然在我身上崩溃,我可以看到出错的日志。或者至少在它崩溃之前发生了什么
我将其包含在问题中,因为这是“泄漏”的根本原因。如果我能修复此功能,问题将得到解决。
该函数需要将消息记录到各自的来源并暂停,直到每次调用 printf 完成,但它也必须及时完成;无限期挂起和/或未能记录消息是不可接受的行为。
更新 2
在将log 函数(参见更新1)分离到测试脚本并设置模拟环境后,我将其归结为printf。
重定向到管道的 printf 调用,
printf "..." > $pipe
如果没有人在监听它,则挂起,因为它正在等待第二个进程获取管道的读取端并使用数据。这可能是我最初强制它们进入后台的原因,以便某个进程可以在某个时候从管道中读取数据,而在当前情况下,系统可以继续执行其他操作。
因此,调用 sleep 是一种未经过深思熟虑的黑客攻击,用于解决由于一个读取器试图同时读取多个写入器而导致的数据竞争问题。理论是,如果每个作者都必须等待 0.001 秒(尽管后台的 printf 与它后面的 sleep 无关),不知何故,这将使数据按顺序显示并修复错误。当然,回过头来看,这确实没什么用。
最终结果是几个后台进程挂在管道上,等待从中读取内容。
“Prevent hanging of "echo STRING > fifo" when nothing...”的答案提供了导致产生此问题的错误的相同“解决方案”。显然不正确。然而,用户R.. 的一个有趣评论提到了一些关于 fifos 包含状态的内容,其中包括诸如哪些进程正在读取管道等信息。
存储状态?你的意思是读者的缺席/在场?这是fifo状态的一部分;任何将其存储在外面的尝试都是虚假的,并且会受到竞争条件的影响。
获取这些信息,如果没有读者就拒绝写是解决这个问题的关键。
但是,无论我在 Google 上搜索什么,我似乎都找不到任何关于读取管道状态的信息,即使在 C 中也是如此。如果需要,我非常愿意使用 C,但是一个 bash 解决方案(或现有的核心实用程序)将是首选。
所以现在问题变成了:我到底如何读取 FIFO 的状态信息,尤其是已经打开管道以进行读取和/或写入的进程?
【问题讨论】:
-
注意:我是用手机输入的,我真的希望没有任何由自动更正引起的拼写错误
-
你说
log调用wallpaper-run。是这样吗? -
@我刚刚注意到这种模棱两可。现在修复它
-
关于更新 2:我不确定是否应该更改问题的名称、创建一个新问题并从问题中链接到它,或者保留问题原样。我的判断告诉我保持原样,因为可以解决更新 2 中问题的答案将是有效解决问题所提出问题的答案。如果这违背了更频繁的用户会做的事情,我们深表歉意。
-
@hansaplast 我以简短的摘要作为问题的开头,并将问题的根本原因添加到问题标题中
标签: bash memory-leaks