对 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 的内存引用