如何在Windows中自动销毁子进程?

71
在C++ Windows应用程序中,我启动了多个长时间运行的子进程(目前我使用CreateProcess(...)来做这件事。

如果我的主进程崩溃或关闭,我希望子进程能够自动关闭

由于这需要在“父”进程崩溃时工作,我相信这需要使用操作系统的某些API/功能来完成,以便清理所有“子”进程。

我该怎么做?

7个回答

86

Windows API支持名为“Job Objects”的对象。以下代码将创建一个“作业”,配置为在主应用程序结束时(当其句柄被清理时)关闭所有进程。此代码仅应运行一次。

HANDLE ghJob = CreateJobObject( NULL, NULL); // GLOBAL
if( ghJob == NULL)
{
    ::MessageBox( 0, "Could not create job object", "TEST", MB_OK);
}
else
{
    JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };

    // Configure all child processes associated with the job to terminate when the
    jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    if( 0 == SetInformationJobObject( ghJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)))
    {
        ::MessageBox( 0, "Could not SetInformationJobObject", "TEST", MB_OK);
    }
}

当每个子进程被创建时,执行以下代码来启动每个子进程并将其添加到作业对象中:

STARTUPINFO info={sizeof(info)};
PROCESS_INFORMATION processInfo;

// Launch child process - example is notepad.exe
if (::CreateProcess( NULL, "notepad.exe", NULL, NULL, TRUE, 0, NULL, NULL, &info, &processInfo))
{
    ::MessageBox( 0, "CreateProcess succeeded.", "TEST", MB_OK);
    if(ghJob)
    {
        if(0 == AssignProcessToJobObject( ghJob, processInfo.hProcess))
        {
            ::MessageBox( 0, "Could not AssignProcessToObject", "TEST", MB_OK);
        }
    }

    // Can we free handles now? Not sure about this.
    //CloseHandle(processInfo.hProcess); 
    CloseHandle(processInfo.hThread);
}

VISTA注意事项:如果在VISTA上使用AssignProcessToObject()时遇到访问被拒绝的问题,请参见AssignProcessToObject在VISTA上始终返回“访问被拒绝”


1
不,我不这么认为。JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE会在最后一个“作业”句柄关闭时终止作业,进程句柄不应该有影响。你试过了吗? - Adam Mitz
1
它在Windows 2000上无法工作 :( (但在其他版本的Windows下应该可以) - rook
4
如果新进程足够快地退出,这里存在一个理论上的竞态条件。为了消除它,请使用CREATE_SUSPENDED标志,并在将进程添加到作业后才调用ResumeThread。 - Harry Johnston
1
我在一个可执行文件中使用了上述描述的JobObjects,该文件调用MsBuild和其他内容。我可以随时终止该exe,并且一切都会立即清理干净。超级棒。 :) - Andreas Vergison
此外,我加入了Harry的想法,创建暂停模式下的进程,并在将它们添加到JobObject后才恢复它们。这样做甚至可能是必要的(至少更安全)。我使用了NtResumeProcess而不是ResumeThread - 速度非常快。请参见https://github.com/mridgers/clink/issues/420。 - Andreas Vergison
显示剩余5条评论

5

一种有点粗糙的解决方案是,父进程作为调试器附加到每个子进程上(使用DebugActiveProcess)。当调试器终止时,所有调试目标进程也会终止。

更好的解决方案(假设您也编写了子进程)是让子进程监视父进程,如果父进程消失,则退出。


2
不必调用DebugActiveProcess,只需将DEBUG_PROCESS作为创建标志之一传递给CreateProcess即可。这样可以减少代码量。 - mrduclaw

3

Windows Job Objects听起来是一个不错的开始。Job Object的名称必须是众所周知的,或者传递给子进程(或继承句柄)。子进程需要注意到父进程死亡,可以通过失败的IPC“心跳”或仅仅是在父进程句柄上等待(WFMO/WFSO)来实现。此时,任何子进程都可以使用TerminateJobObject来关闭整个组。


相反,子进程只需在父句柄上进行 WFMO/WFSO 操作并关闭自身。只要每个子进程都这样做,就不需要作业对象。 - dkrikun

1

在创建进程之前,您可以将作业分配给父进程:

static HANDLE hjob_kill_on_job_close=INVALID_HANDLE_VALUE;
void init(){
    hjob_kill_on_job_close = CreateJobObject(NULL, NULL);
    if (hjob_kill_on_job_close){
        JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobli = { 0 };
        jobli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
        SetInformationJobObject(hjob_kill_on_job_close,
            JobObjectExtendedLimitInformation,
            &jobli, sizeof(jobli));
        AssignProcessToJobObject(hjob_kill_on_job_close, GetCurrentProcess());
    }
}
void deinit(){
    if (hjob_kill_on_job_close) {
        CloseHandle(hjob_kill_on_job_close);
    }
}

JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 会在最后一个与作业关联的句柄关闭时终止与该作业相关的所有进程。默认情况下,除非在调用 CreateProcess 时传递了 CREATE_BREAKAWAY_FROM_JOB,否则所有子进程都将自动分配给该作业。有关 CREATE_BREAKAWAY_FROM_JOB 的更多信息,请参见 https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags

您可以使用 Sysinternals 的进程资源管理器确保所有进程都分配给了该作业。就像这样: enter image description here


请注意,此方法仅适用于Windows 8/10,因为一个进程只能与一个作业相关联。作业不能嵌套。嵌套作业的功能是在Windows 8和Windows Server 2012中添加的。 - Kohill Yang

1
你可以保持一个单独的看门狗进程运行。它的唯一任务是监视当前进程空间,以发现像你描述的情况。它甚至可以在崩溃后重新启动原始应用程序或为用户提供不同的选项,收集调试信息等。只需尝试保持足够简单,以便您不需要第二个看门狗来监视第一个。

-2

你可以将每个进程封装在一个C++对象中,并在全局范围内保留它们的列表。析构函数可以关闭每个进程。如果程序正常退出,那么这将很好地工作,但如果程序崩溃,则一切都无法预料。

以下是一个简单的示例:

class myprocess
{
public:
    myprocess(HANDLE hProcess)
        : _hProcess(hProcess)
    { }

    ~myprocess()
    {
        TerminateProcess(_hProcess, 0);
    }

private:
    HANDLE _hProcess;
};

std::list<myprocess> allprocesses;

然后每当你启动一个进程时,调用allprocessess.push_back(hProcess);


3
如果我的主要进程崩溃了。 - jm.

-2
你可能需要维护一个进程列表,并在退出程序时逐个终止它们。我不确定如何在C++中实现这一点,但应该不难。困难的部分可能是确保在应用程序崩溃的情况下关闭子进程。.Net有能力添加一个函数,在未处理的异常发生时调用该函数。我不确定C++是否提供相同的功能。

3
如果我的主要进程崩溃了。 - jm.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接