我已经找到了解决这个问题的方法。
基本上,被叫方(wallpaper
)本身并没有退出,因为它正在等待另一个进程完成。
在52天的过程中,这个有问题的副作用已经滚雪球,直到10,000多个挥之不去的进程消耗了10+ GB的RAM,几乎使我的系统崩溃。
违规过程原来是从一个名为log
的函数调用printf,这个函数已经发送到后台并被遗忘了,因为它正在写入管道并挂起。
事实证明,写入命名管道的进程将阻塞,直到另一个进程出现并从中读取。
反过来,这又改变了问题的要求,我需要一种方法来阻止这些过程的建立"到了#34;我需要一种更好的方法来解决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-run
init由startx
直接调用,并在运行wallpaper-run之前启动dwm(以及其他"模块")
现在问题出现了,出于某种原因,无论是壁纸还是壁纸,都会徘徊在"在记忆中。也就是说,在循环的每次迭代之后,创建两个新的壁纸和壁纸运行实例,而#34; old"那些没有得到清理并陷入睡眠状态的人。它就像是内存泄漏,但是由于流程延迟而不是糟糕的内存管理。
我发现了这个"过程泄漏"在我的系统运行52天之后,当一切都崩溃时(类似bash: cannot fork: resource temporarily unavailable
,当我试图运行命令时,终端发送垃圾邮件),因为系统内存不足。我不得不杀死超过10,000个壁纸/运行实例,以使我的系统恢复正常工作。
我完全不知道为什么会这样。我认为这些脚本没有理由留在内存中,因为退出的脚本应该意味着它的进程被清理了。
为什么他们挥之不去,吃掉了资源?
在评论的帮助下(非常感谢我' L'我),我已将问题追溯到函数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行被删节到了#34; ...",因为它们相当复杂并且与手头的问题无关(而且我有40分钟的时间可以做更好的事情)而不是与Android的垃圾文本输入界面战斗)。特别是,根据我们正在谈论的printf,打印诸如当前时间,调用进程的名称和传递的消息之类的内容。第一个是最详细的,因为它被保存到一个文件,其中立即上下文丢失,而notify-send行具有最少的细节,因为它将在桌面上显示。
整个管道崩溃是通过我为它写的基本外壳直接与init连接。
第三个版本是有意的;它打印到我在会话开始时登录的tty。这样,如果init突然崩溃,我可以看到出错的日志。或者至少在崩溃之前发生的事情
我在问题中包括这个,因为这是"泄漏的根本原因"。如果我可以修复此功能,问题将得到解决。
该函数需要将消息记录到各自的源并停止,直到每次调用printf结束,但它也必须及时完成;无限期挂起和/或无法记录消息是不可接受的行为。
将log
函数(请参阅更新1)隔离到测试脚本并设置模拟环境后,我将其归结为printf。
重定向到管道的printf调用,
printf "..." > $pipe
如果没有任何内容正在侦听它,则会挂起,因为它正在等待第二个进程获取管道的读取端并使用数据。这可能是我最初强迫他们进入后台的原因,这样一个过程可以在某个时刻从管道读取数据,而在这种情况下,系统可以继续前进并做其他事情。
然后,对睡眠的呼唤是一个经过深思熟虑的黑客,可以解决数据竞争问题,因为一个读者试图同时从多个作者中读取数据。理论上说,如果每个作者必须等待0.001秒(尽管背景中的printf
与跟随它的sleep
无关),不知何故,这会使数据出现按顺序修复bug。当然,回头看,这确实没什么用处。
最终结果是几个后台进程挂在管道上,等待从中读取内容。
" Prevent hanging of "echo STRING > fifo" when nothing..."的答案提出相同的解决方案"这导致了产生这个问题的bug。显然是不正确的。然而,用户R..
的一个有趣的评论提到了有关包含状态的fifos的内容,其中包括诸如正在读取管道的进程等信息。
存储状态?你的意思是读者的缺席/存在?这是fifo状态的一部分;任何将其存放在外面的企图都是伪造的,并且会受到竞争条件的限制。
如果没有读者,获取这些信息并拒绝写信是解决这个问题的关键。
然而,无论我在Google上搜索什么,我似乎都无法找到关于读取管道状态的任何信息,即使在C语言中也是如此。如果需要,我非常愿意使用C语言解决方案(或现有的核心工具)将是首选。
所以现在问题变成了:我怎样才能读取FIFO的状态信息,特别是那些(有)管道打开以进行读取和/或写入的过程?
答案 0 :(得分:1)
https://stackoverflow.com/a/20694422
以上链接的答案显示了一个C程序试图open
一个O_NONBLOCK
的文件。所以我尝试编写一个程序,如果open返回有效的文件描述符,则返回0(成功),如果open返回-1
则返回1(失败)。
#include <fcntl.h>
#include <unistd.h>
int
main(int argc, char **argv)
{
int fd = open(argv[1], O_WRONLY | O_NONBLOCK);
if(fd == -1)
return 1;
close(fd);
return 0;
}
我没有费心检查argv[1]
是否为空,或者因为文件不存在而打开失败,因为我只打算从shell脚本中使用这个程序,保证给它正确的参数
那就是说,该计划完成了它的工作
$ gcc pipe-open.c
$ ./a.out ./pipe && echo "pipe has a reader" || echo "pipe has no reader"
$ ./a.out ./pipe && echo "pipe has a reader" || echo "pipe has no reader"
假设存在pipe
以及第一次和第二次调用之间的另一个进程打开管道(cat pipe
),输出如下所示:
管道没有读者
pipe有一个阅读器
如果管道有第二个写入程序(即,它将因为没有读取器而失败),该程序也可以工作。
唯一的问题是关闭文件后,阅读器也会关闭管道的末端。并且取消对close的调用将没有任何好处,因为所有打开的文件描述符在主返回后自动关闭(控制进入退出,它将遍历打开的文件描述符列表并逐个关闭它们)。不好!</ p>
这意味着实际写入管道的唯一窗口是在它关闭之前,即I.e.来自C程序本身。
#include <fcntl.h>
#include <unistd.h>
int
write_to_pipe(int fd)
{
char buf[1024];
ssize_t nread;
int nsuccess = 0;
while((nread = read(0, buf, 1024)) > 0 && ++nsuccess)
write(fd, buf, nread);
close(fd);
return nsuccess > 0 ? 0 : 2;
}
int
main(int argc, char **argv)
{
int fd = open(argv[1], O_WRONLY | O_NONBLOCK);
if(fd == -1)
return 1;
return write_to_pipe(fd);
}
调用:
$ echo hello world | ./a.out pipe
$ ret=$?
$ if [[ $ret == 1 ]]; then echo no reader
> elif [[ $ret == 2 ]]; then echo an error occurred trying to write to the pipe
> else echo success
> fi
输出条件与之前相同(第一次通话没有读卡器;第二次通话没有):
没有读者
成功
此外,在阅读管道的终端中可以看到文本“Hello World”
最后,问题解决了。我有一个程序作为编写器和管道之间的中间人,如果在调用时没有读取器连接到管道,则会立即退出故障代码,或者如果有,则尝试写入管道和如果没有写任何内容,则传达失败。
最后一部分是新的。我认为将来知道是否有任何内容可能会有用。
我可能会在将来添加更多的错误检测,但由于日志在尝试写入之前检查管道是否存在,现在这很好
答案 1 :(得分:0)
问题是您正在启动壁纸过程而不检查上一次运行是否结束。因此,在52天内,可能有4 * 24 * 52 = ~5000
个实例正在运行(不知道你是如何找到10000的)!是否可以使用flock
来确保一次只运行一个wallpaper
个实例?
请参阅此帖子:Quick-and-dirty way to ensure only one instance of a shell script is running at a time