对 system() 的调用不会在我的 Windows 进程之间创建父子关系

Posted

技术标签:

【中文标题】对 system() 的调用不会在我的 Windows 进程之间创建父子关系【英文标题】:A call to system() doesn't create a parent-child relation between my processes in Windows 【发布时间】:2012-04-20 09:45:41 【问题描述】:

我正在使用 Perl 创建 Windows 服务。为此,我使用Win32::Daemon

处理服务(接受启动和停止回调等)的 Perl 脚本使用 system() 命令调用 .bat 文件,最终调用我的最终 Perl 程序。

问题是当我停止服务时,system()启动的进程没有关闭,最终进程也没有关闭(由system()生成的进程启动)。

就像进程之间没有“父子”关系(停止Windows服务通常会导致所有相关进程同时关闭)。

编辑:我添加了上面的代码。我刚刚展示了注册服务回调和调用 StartService 的主函数,以及三个主要回调:启动、运行、停止。

sub main 
        #registering service callbacks
        Win32::Daemon::RegisterCallbacks( 
            start       =>  \&Callback_Start,
            running     =>  \&Callback_Running,
            stop        =>  \&Callback_Stop,
            pause       =>  \&Callback_Pause,
            continue    =>  \&Callback_Continue,
          );
        my %Context = (
            last_state => SERVICE_STOPPED,
            start_time => time(),
      );

      Win32::Daemon::StartService( \%Context, 2000 );

      # Here the service has stopped
      close STDERR; close STDOUT;



#function called after the start one
sub Callback_Running

   my( $Event, $Context ) = @_;

#Here I had to make an infinite loop to make the service "persistant". Otherwise it stops automatically (maybe there's something important I missed here?

   if( SERVICE_RUNNING == Win32::Daemon::State() )
   
       Win32::Daemon::State( SERVICE_RUNNING );

   
   

#function first called by StartService
sub Callback_Start


#above is stated the system() call where I trigger the .bat script

       my( $Event, $Context ) = @_;

        my $cmd = "START \"\" /Dc:\\path\\to\\script\\dir\\ \"script.bat\"";

    print $cmd."\n";
    system($cmd);

   $Context->last_state = SERVICE_RUNNING;
   Win32::Daemon::State( SERVICE_RUNNING );


sub Callback_Stop

   my( $Event, $Context ) = @_;

   #Things I should do to stop the service, like closing the generated processes (if I knew their pid)

   $Context->last_state = SERVICE_STOPPED;
   Win32::Daemon::State( SERVICE_STOPPED );

   # We need to notify the Daemon that we want to stop callbacks and the service.
   Win32::Daemon::StopService();

【问题讨论】:

Add code 到 exhibit the problem。 【参考方案1】:

在 Windows 中,进程之间没有父子关系。 Windows 将所有进程视为对等进程,尤其是杀死“父进程”永远不会杀死“子进程”。

当服务收到停止请求时,它负责关闭/杀死它可能已经创建的任何进程,如果这样做合适的话。

您说“停止 [原文如此] Windows 服务通常会导致所有相关进程关闭”,但通常情况并非如此;也许 Win32::Daemon 正在为您执行此操作,但它当然无法知道从您的批处理文件启动的任何进程。

如果可能的话,正确的解决方案是在单个进程中实现您的服务。由于在这种情况下,两个进程都是用 Perl 编写的,因此将它们组合成一个程序应该相对简单。

【讨论】:

好的,我认为当一个进程创建其他进程时,windows 就像 linux 一样。我认为在一个唯一的过程中执行整个脚本是不可能的,因为 StartService 函数期望返回正确结束。如果没有,服务被“阻塞”,不能再处理回调。 是的。有两个模块实现了线程,但据我所知,它们都不是很健壮。我要试一试。我将用我的结果更新这个问题,并将你的答案标记为解决问题,如果它成功了。同时我将发布我用来解决问题的方式。非常感谢! 好的,经过很长时间我回到任务并设法不通过 .bat 文件来运行我的程序。但是,由于我的程序打算在没有真正结束的情况下运行(它是一个 http 服务器),我在输入“停止”回调时遇到了问题。但是您的回答使我得到了很好的影响并解决了最初的问题。【参考方案2】:

Harry Johnston 的回答基本上是正确的。如果很难跟踪“孩子”,您可以做的一件事是将您创建的所有进程放入Windows Job 对象中,而不是通过system 启动它们,然后终止您的工作进程的服务关闭代码。

我不确定从 Perl 做这件事有多容易,但从 C 做起并不太难。看起来确实有一些 CPAN 模块可以帮助您解决这个问题,例如 Win32::Job 和Win32::JobAdd,但我自己没有任何经验。

【讨论】:

我明天要去看看,如果我无法轻松检索生成的进程的 pid,这似乎是一个有趣的解决方案。在一个线程中实现该过程可能是一个更好的解决方案,但目前我找不到一种方法来做到这一点(请参阅我对 Harry 的回答的评论) 好吧,你可以在同一个进程中生成另一个线程,或者也许只是生成一个新进程并在另一个进程中运行所有内容,然后只跟踪那个 pid,如果这在你的设计中是可能的.【参考方案3】:

如果批处理文件在您的最终 Perl 程序之前终止,那么最终 Perl 程序将没有父程序,因为它的父程序(批处理文件的命令处理器(如 cmd.exe))已死亡。

(已更新以包含可能发生这种情况的特定场景)

一个场景:

    批处理文件 B 启动 Perl 程序 P1。 Perl 程序 P1 启动 Perl 程序 PFINAL。 Perl 程序 P1 无需等待 PFINAL 终止就终止。 批处理文件 B 继续执行下一个命令,因为 P1 已终止。 如果 Batch 文件 B 在 PFINAL 终止之前终止,则 PFINAL 没有父级,因为它的父级(Perl 程序 P1)和它的祖父级(批处理文件 B)都已终止。

创建守护进程时的一个常见习惯用法是分叉,然后让父进程死掉,这样子进程(实际的守护进程)就没有父进程了。这避免了守护进程仍然与控制终端连接的问题。

【讨论】:

是什么让你这么认为? cmd 将等待 perl 退出,然后再执行批处理文件的下一条语句(或退出,如果它是最后一条语句)。 但是 cmd 正在等待 P1,而不是 PFINAL。如果 P1 在 PFINAL 之前终止,那么 cmd(批处理文件 B)将继续执行下一条语句,并且 PFINAL 失去其父进程。【参考方案4】:

我遇到了同样的问题。

我的 perl 脚本调用 java 应用程序,同时应用程序应该被杀死。

我用Win32::Process模块

Win32::Process::Create($ProcessObj,
    "C:/Java/bin/java.exe",
    "java.exe -cp bin/blablabla.jar",
    0,
    CREATE_NEW_CONSOLE,
    ".")

$ProcessObj->GetExitCode($retCode);         
$ProcessObj->Kill($retCode);         

希望对你有帮助

【讨论】:

这个模块看起来很有用,但我会先看看 fork() 命令来管理一个并发线程。

以上是关于对 system() 的调用不会在我的 Windows 进程之间创建父子关系的主要内容,如果未能解决你的问题,请参考以下文章

开始调用未运行

调用 finish() 不会清除对 Activity 的内存引用

WPF线程调用UI元素

C语言中调用system()函数弹出dos窗口如何隐藏?

当应用程序处于活动状态时,不会在我的视图控制器中调用 viewWillAppear

matlab 调用dos命令和文件操作