2017-01-13 126 views
1

我有一個Tcl腳本運行進程的情況,即fork()離開分叉進程運行,然後主進程退出。你可以簡單地通過運行任何分支到後臺的程序來嘗試它,例如gvim,只要它被配置爲在執行後在後臺運行:set res [exec gvim]如果進程分叉並退出,Tcl [exec]進程會離開殭屍

理論上主進程立即退出,子進程在後臺運行,但不知何故主進程掛斷,不退出,停留在殭屍狀態(報告爲<defunct>,在ps輸出)。

在我的情況下,我開始打印的東西,我想要的東西,我希望該過程退出,我說完成。問題是,如果我使用open "|gvim" r產生了這個過程,那麼我無法識別過程結束的時刻。由[open]返回的fd從不報告[eof],即使程序變成殭屍。當我嘗試[read]時,只是爲了讀取該進程可能打印的所有內容,完全掛起。

更有趣的是,偶爾主進程和分叉進程都會打印某些內容,當我試圖使用[gets]來讀取它時,我同時得到了這兩個文件。如果我太早關閉描述符,則[close]由於管道損壞而引發異常。可能這就是爲什麼[read]永遠不會結束。

我需要一些方法來識別當進程退出時的時刻,而這個進程可能產生了另一個子進程,但是這個子進程可能完全脫離了,我對它的作用並不感興趣。我想要在退出之前主進程打印的內容,並且腳本應該繼續其工作,而在後臺運行的進程也在運行,並且我不關心它發生了什麼。

我可以控制我開始的過程的來源。是的,我在fork()之前做過signal(SIGCLD, SIG_IGN) - 沒有幫助。

回答

0

好吧,我發現經過長時間的討論這裏的解決方案:

https://groups.google.com/forum/#!topic/comp.lang.tcl/rtaTOC95NJ0

下面的腳本演示了這個問題如何解決:

#!/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." 

唯一存在的問題是仍然沒有可能要知道這個過程已經退出,但是下一個[exec](在這個特殊情況下可能是[exec ps...]命令這樣做)清理了殭屍(沒有通用的方法 - 你可以在POSIX系統上做的最好的方法是[exec /bin/true])。就我而言,我得到一行父級進程必須打印的行就夠了,在這之後我可以簡單地「放手」。

如果[exec]可以以某種方式返回第一個進程的PID,並且有一個標準的[wait]命令可以阻止,直到進程退出或檢查其運行狀態(此命令當前可用於TclX),那將會很不錯。

請注意,[chan pipe]僅適用於Tcl 8.6,您可以使用TclX中的[pipe]

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程序:

#!/usr/bin/tclsh 

puts [exec ./t1] 
puts "exit tcl" 
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是...而不是通常更復雜,因爲它是與用戶交互的應用程序。


您可能會發現更容易地在一個單獨的線程運行一個簡單的exec,提供您的Tcl的版本是支持線程的,並且安裝了Thread包。 (這應該如果你使用8.6是這種情況,但我不知道這是不是真的。)

package require Thread 

set runner [thread::create { 
    proc run {caller targetVariable args} { 
     set res [catch { 
      exec {*}$args 
     } msg opt] 
     set callback [list set $targetVariable [list $res $msg $opt]] 
     thread::send -async $caller $callback 
    } 
    thread::wait 
}] 

proc runInBackground {completionVariable args} { 
    global runner 
    thread::send -async $runner [list run [thread::id] $completionVariable {*}$args] 
} 

runInBackground resultsVar gvim … 
# You can do other things at this point 

# Wait until the variable is set (by callback); alternatively, use a variable trace  
vwait resultsVar 

# Process the results to extract the sense 
lassign $resultsVar res msg opt 
puts "code: $res" 
puts "output: $msg" 
puts "status dictionary: $opt" 

對於這一切,對於喜歡gvim編輯我倒是實際上指望它在前臺運行(不需要太複雜的任何事情),因爲其中只有一個人可以真正與特定的終端進行一次互動。

+0

我不知何故知道你是唯一合理回答的人:)。不幸的是,你的解決方案沒有一個能夠工作:第一個解決方案掛在'gets'上,它是'exec'上的線程化解決方案。這可以通過'gvim'很容易地證明(我給它只是爲了可以很容易地測試它)。我需要一個解決方案:接受套接字連接,獲取命令,在bg中運行一個進程,忘記它。另一個請求可能會殺死它,但該進程將被cmdline args識別。我也不必自己製作流程叉,我只是覺得它會更容易。也可以執行'exec ...&''slongs我可以退出並保持它運行 – Ethouris

+0

我認爲如果你對這個主題有點興趣,它會很好。 'bash'中不存在這個問題 - 你運行一個進程分叉到後臺並且它正在運行,CLI返回。在Tcl中做同樣的事情,'[exec]'命令不會返回(永遠掛起,退出的過程像殭屍一樣)。它是否真的按照設計工作?正常情況下,殭屍將退出由父母清理的CHILD進程。不過,不管怎樣,相同的CHILD進程不會分叉任何東西,沒有任何殭屍就退出。你能解釋一下嗎? – Ethouris

0

起初,你說:

我需要一些方法來識別的那一刻,當主進程退出,而這一過程可能催生另一個孩子的過程,但這個孩子過程可以完全分離和我對它的作用不感興趣。

後來你說:

如果派生的子進程關閉開放的渠道,TCL不會等待就可以了。

這些是兩個相互矛盾的陳述。一方面,你只對父母過程感興趣,另一方面不管孩子是否完成,甚至認爲你也表示你對已經分離的子過程不感興趣。最後,我聽到分手和關閉孩子的父母副本stdin,stdout和stderr正在分離(即將孩子進程變成了守護進程)。我編寫了這個快速程序來運行上面包含的簡單c程序,並且正如所料,tcl對子進程一無所知。我調用了編譯版本的程序/ tmp/compile/chuck。我沒有gvim,所以我使用emacs,但由於emacs不生成文本,我將exec包裝在自己的tcl腳本中並執行它。在這兩種情況下,都會等待父進程並檢測到eof。當父對象退出時,Runner :: getData將運行並對清理進行評估。


#!/bin/sh 
exec /opt/usr8.6.3/bin/tclsh8.6 "$0" ${1+"[email protected]"} 

namespace eval Runner { 
    variable close 
    variable watch 
    variable lastpid "" 
    array set close {} 
    array set watch {} 


    proc run { program { message "" } } { 
     variable watch 
     variable close 
     variable lastpid 
     if { $message ne "" } { 
      set fname "/tmp/[lindex $program 0 ]-[pid].tcl" 
      set out [ open $fname "w" ] 
      puts $out "#![info nameofexecutable]" 
      puts $out " catch { exec $program } err " 
      puts $out "puts \"\$err\n$message\"" 
      close $out 
      file attributes $fname -permissions 00777 
      set fd [ open "|$fname " "r" ] 
      set close([pid $fd]) "file delete -force $fname " 
     } else { 
      set fd [ open "|$program" "r" ] 
      set close([pid $fd]) "puts \"cleanup\"" 
     } 
     fconfigure $fd -blocking 0 -buffering none 
     fileevent $fd readable [ list Runner::getData [ pid $fd ] $fd ] 
    } 

    proc getData { pid chan } { 
     variable watch 
     variable close 
     variable lastpid 
     set data [read $chan] 
     append watch($pid) "$data" 
     if {[eof $chan]} { 
      catch { close $chan } 
      eval $close($pid) ; # cleanup 
      set lastpid $pid 
     } 
    } 
} 
Runner::run /tmp/compile/chuck "" 
Runner::run emacs " Emacs complete" 

while { 1 } { 
    vwait Runner::lastpid 
    set p $Runner::lastpid 
    catch { exec ps -ef | grep chuck } output 
    puts "program with pid $p just ended" 
    puts "$Runner::watch($p)" 
    puts " processes that match chuck " 
    puts "$output" 
} 

輸出: 注意到我退出了emacs的後孩子報告說,它正在退出。

[[email protected] 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 
+0

Tcl腳本自動生成?那麼,我寧願嘗試'[chan pipe]'(或者來自TclX的管道<8.6)。 – Ethouris