如果进程分叉并退出,Tcl[exec]进程将留下僵尸

如果进程分叉并退出,Tcl[exec]进程将留下僵尸,tcl,exec,zombie-process,Tcl,Exec,Zombie Process,我有一个例子,当Tcl脚本运行一个进程时,它执行fork(),让fork进程运行,然后主进程退出。您可以通过运行任何分岔到后台的程序来尝试它,例如gvim,前提是它被配置为在执行后在后台运行:set res[exec gvim] 从理论上讲,主进程立即退出,子进程在后台运行,但不知何故,主进程挂起,不退出,保持僵化状态(在psoutput中报告为) 在我的例子中,我启动的流程打印了一些东西,我想要那个东西,我想要流程退出,我声明它完成了。问题是,如果我使用open“|gvim”r生成流程,那么我

我有一个例子,当Tcl脚本运行一个进程时,它执行
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."