如果进程分叉并退出,Tcl[exec]进程将留下僵尸
我有一个例子,当Tcl脚本运行一个进程时,它执行如果进程分叉并退出,Tcl[exec]进程将留下僵尸,tcl,exec,zombie-process,Tcl,Exec,Zombie Process,我有一个例子,当Tcl脚本运行一个进程时,它执行fork(),让fork进程运行,然后主进程退出。您可以通过运行任何分岔到后台的程序来尝试它,例如gvim,前提是它被配置为在执行后在后台运行:set res[exec gvim] 从理论上讲,主进程立即退出,子进程在后台运行,但不知何故,主进程挂起,不退出,保持僵化状态(在psoutput中报告为) 在我的例子中,我启动的流程打印了一些东西,我想要那个东西,我想要流程退出,我声明它完成了。问题是,如果我使用open“|gvim”r生成流程,那么我
fork()
,让fork进程运行,然后主进程退出。您可以通过运行任何分岔到后台的程序来尝试它,例如gvim
,前提是它被配置为在执行后在后台运行:set res[exec gvim]
从理论上讲,主进程立即退出,子进程在后台运行,但不知何故,主进程挂起,不退出,保持僵化状态(在ps
output中报告为
)
在我的例子中,我启动的流程打印了一些东西,我想要那个东西,我想要流程退出,我声明它完成了。问题是,如果我使用open“|gvim”r
生成流程,那么我也无法识别流程完成的时刻。由[open]
返回的fd
从不报告[eof]
,即使程序变成僵尸。当我尝试[read]
时,只是为了读取进程可能打印的所有内容,它完全挂起
更有趣的是,有时主进程和分叉进程都会打印一些内容,当我试图使用[get]
阅读时,我会同时获得这两个内容。如果过早关闭描述符,则[close]
会因管道破裂而引发异常。也许这就是为什么[read]
永远不会结束的原因
我需要一些方法来识别main进程退出的时刻,虽然这个进程可能产生了另一个子进程,但这个子进程可能完全分离,我对它的功能不感兴趣。我希望主进程在退出之前打印一些东西,脚本应该在后台运行的进程也在运行时继续工作,我对它会发生什么不感兴趣
我可以控制我开始的过程的来源。是的,我在
fork()
之前发出了信号(SIGCLD,SIG\u IGN)
但没有帮助。您的守护进程还可以调用setId()
和setpgrp()
来启动新会话并从进程组分离。但是这些也不能解决你的问题
您必须进行一些流程管理:
#!/usr/bin/tclsh
proc waitpid {pid} {
set rc [catch {exec -- kill -0 $pid}]
while { $rc == 0 } {
set ::waitflag 0
after 100 [list set ::waitflag 1]
vwait ::waitflag
set rc [catch {exec -- kill -0 $pid}]
}
}
set pid [exec ./t1 &]
waitpid $pid
puts "exit tcl"
exit
编辑:另一个不合理的答案
如果分叉子进程关闭开放通道,Tcl将不会等待它
测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int
main (int argc, char *argv [])
{
int pid;
FILE *o;
signal (SIGCHLD, SIG_IGN);
pid = fork ();
if (pid == 0) {
/* should also call setsid() and setpgrp() to daemonize */
printf ("child\n");
fclose (stdout);
fclose (stderr);
sleep (10);
o = fopen ("/dev/tty", "w");
fprintf (o, "child exit\n");
fclose (o);
} else {
printf ("parent\n");
sleep (2);
}
printf ("t1 exit %d\n", pid);
return 0;
}
您的守护进程还可以调用
setsid()
和setpgrp()
来启动新会话并从进程组中分离。但是这些也不能解决你的问题
您必须进行一些流程管理:
#!/usr/bin/tclsh
proc waitpid {pid} {
set rc [catch {exec -- kill -0 $pid}]
while { $rc == 0 } {
set ::waitflag 0
after 100 [list set ::waitflag 1]
vwait ::waitflag
set rc [catch {exec -- kill -0 $pid}]
}
}
set pid [exec ./t1 &]
waitpid $pid
puts "exit tcl"
exit
编辑:另一个不合理的答案
如果分叉子进程关闭开放通道,Tcl将不会等待它
测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int
main (int argc, char *argv [])
{
int pid;
FILE *o;
signal (SIGCHLD, SIG_IGN);
pid = fork ();
if (pid == 0) {
/* should also call setsid() and setpgrp() to daemonize */
printf ("child\n");
fclose (stdout);
fclose (stderr);
sleep (10);
o = fopen ("/dev/tty", "w");
fprintf (o, "child exit\n");
fclose (o);
} else {
printf ("parent\n");
sleep (2);
}
printf ("t1 exit %d\n", pid);
return 0;
}
Tcl在下次调用
exec
时清除后台进程调用中的僵尸。由于僵尸实际上不使用太多资源(只是流程表中的一个条目;实际上没有其他资源),因此没有特别的时间来清理它们
管道的问题是没有将其置于非阻塞模式。要检测管道的出口,最好使用
fileevent
,当有字节(或更多字节)要从管道中读取或管道的另一端关闭时,该事件将触发。为了区分这些情况,您必须实际尝试读取,如果您过度读取并且未处于非阻塞模式,则可能会阻塞。但是,Tcl使使用非阻塞模式变得容易
set pipeline [open |… "r"]
fileevent $pipeline readable [list handlePipeReadable $pipeline]
fconfigure $pipeline -blocking false
proc handlePipeReadable {pipe} {
if {[gets $pipe line] >= 0} {
# Managed to actually read a line; stored in $line now
} elseif {[eof $pipe]} {
# Pipeline was closed; get exit code, etc.
if {[catch {close $pipe} msg opt]} {
set exitinfo [dict get $opt -errorcode]
} else {
# Successful termination
set exitinfo ""
}
# Stop the waiting in [vwait], below
set ::donepipe $pipeline
} else {
# Partial read; things will be properly buffered up for now...
}
}
vwait ::donepipe
请注意,在管道中使用gvim
比通常更为复杂,因为它是用户交互的应用程序
如果您的Tcl版本启用了线程,并且安装了
thread
软件包,您可能会发现在单独的线程中运行简单的exec
会更容易。(如果您使用的是8.6,应该是这样的,但我不知道这是不是真的。)
尽管如此,对于像
gvim
这样的编辑器,我实际上希望它在前台运行(不需要那么复杂),因为它们中只有一个可以同时与特定的终端交互。Tcl在下次调用exec
时清除后台进程调用中的僵尸。由于僵尸实际上不使用太多资源(只是流程表中的一个条目;实际上没有其他资源),因此没有特别的时间来清理它们
管道的问题是没有将其置于非阻塞模式。要检测管道的出口,最好使用
fileevent
,当有字节(或更多字节)要从管道中读取或管道的另一端关闭时,该事件将触发。为了区分这些情况,您必须实际尝试读取,如果您过度读取并且未处于非阻塞模式,则可能会阻塞。但是,Tcl使使用非阻塞模式变得容易
set pipeline [open |… "r"]
fileevent $pipeline readable [list handlePipeReadable $pipeline]
fconfigure $pipeline -blocking false
proc handlePipeReadable {pipe} {
if {[gets $pipe line] >= 0} {
# Managed to actually read a line; stored in $line now
} elseif {[eof $pipe]} {
# Pipeline was closed; get exit code, etc.
if {[catch {close $pipe} msg opt]} {
set exitinfo [dict get $opt -errorcode]
} else {
# Successful termination
set exitinfo ""
}
# Stop the waiting in [vwait], below
set ::donepipe $pipeline
} else {
# Partial read; things will be properly buffered up for now...
}
}
vwait ::donepipe
请注意,在管道中使用gvim
比通常更为复杂,因为它是用户交互的应用程序
如果您的Tcl版本启用了线程,并且安装了
thread
软件包,您可能会发现在单独的线程中运行简单的exec
会更容易。(如果您使用的是8.6,应该是这样的,但我不知道这是不是真的。)
尽管如此,对于像gvim
这样的编辑器,我实际上希望它在前台运行(不需要那么复杂),因为它们中只有一个可以同时与特定的终端交互。首先你说:
我需要一些方法来识别主进程退出的时刻,虽然这个进程可能会产生另一个子进程,但这个子进程可能会被完全破坏
[user1@linuxrocks workspace]$ ./test.tcl
cleanup
program with pid 27667 just ended
child
parent
t1 exit 27670
processes that match chuck avahi 936 1 0 2016 ?
00:04:35 avahi-daemon: running [linuxrocks.local] admin 27992 1 0
19:37 pts/0 00:00:00 /tmp/compile/chuck admin 28006 27988 0
19:37 pts/0 00:00:00 grep chuck
child exit
program with pid 27669 just ended
Emacs complete
#!/usr/bin/tclsh
lassign [chan pipe] input output
chan configure $input -blocking no -buffering line ;# just for a case :)
puts "Running $argv..."
set ret [exec {*}$argv 2>@stderr >@$output]
puts "Waiting for finished process..."
set line [gets $input]
puts "FIRST LINE: $line"
puts "DONE. PROCESSES:"
puts [exec ps -ef | grep [lindex $argv 0]]
puts "EXITING."