Как автоматически уничтожить дочерние процессы в Windows?

В приложении C++ для Windows я запускаю несколько долго работающих дочерних процессов (в настоящее время я использую CreateProcess(...) для этого.

Я хочу, чтобы дочерние процессы автоматически закрывались, если мои основные процессы зависали или закрывались.

Из-за требования, что это должно работать для сбоя "родителя", я считаю, что это должно быть сделано с использованием некоторого API/ функции операционной системы. Так что все "дочерние" процессы очищены.

Как мне это сделать?

8 ответов

Решение

Windows API поддерживает объекты, называемые "Объекты заданий". Следующий код создаст "задание", настроенное на остановку всех процессов, когда основное приложение заканчивается (когда очищаются его дескрипторы). Этот код должен быть запущен только один раз.:

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);
}

ПРИМЕЧАНИЕ: см. AssignProcessToJobObject всегда возвращает "отказ в доступе" в Vista, если у вас возникают проблемы с отказом в доступе с AssignProcessToObject() в vista.

Одним из несколько хакерских решений было бы присоединение родительского процесса к каждому дочернему элементу в качестве отладчика (используйте DebugActiveProcess). Когда отладчик завершает работу, все его процессы отладчика также завершаются.

Лучшее решение (при условии, что вы также написали дочерние процессы) состояло бы в том, чтобы дочерние процессы контролировали родительский процесс и выходили, если он исчезает.

Windows Job Objects - хорошее место для начала. Имя объекта задания должно быть общеизвестным или передаваться дочерним элементам (или наследовать дескриптор). Дети должны будут быть уведомлены, когда родитель умирает, либо из-за неудавшегося "сердцебиения" IPC, либо просто через WFMO/WFSO на дескрипторе процесса родителя. В этот момент любой дочерний процесс может TermianteJobObject обрушить всю группу.

Перед созданием процессов вы можете назначить задание родительскому процессу:

      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. См. Https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags для получения дополнительной информации о CREATE_BREAKAWAY_FROM_JOB.

Вы можете использовать обозреватель процессов от Sysinternals, чтобы убедиться, что все процессы назначены заданию. Именно так:

Вы можете сохранить отдельный процесс сторожевого таймера. Его единственная задача - наблюдать за текущим пространством процесса, чтобы определить ситуации, которые вы описываете. Он может даже перезапустить исходное приложение после сбоя или предоставить пользователю различные параметры, собрать отладочную информацию и т. Д. Просто постарайтесь сделать его достаточно простым, чтобы вам не понадобился второй сторожевой таймер для просмотра первого.

Вы можете инкапсулировать каждый процесс в объекте C++ и хранить их список в глобальной области видимости. Деструкторы могут закрыть каждый процесс. Это будет нормально работать, если программа выйдет нормально, но в случае сбоя все ставки выключены.

Вот грубый пример:

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

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

private:
    HANDLE _hProcess;
};

std::list<myprocess> allprocesses;

Затем всякий раз, когда вы запускаете его, вызывайте allprocessess.push_back(hProcess);

Вам, вероятно, придется вести список запускаемых процессов и уничтожать их один за другим при выходе из программы. Я не уверен в специфике этого в C++, но это не должно быть сложно. Трудной частью, вероятно, будет обеспечение того, чтобы дочерние процессы были закрыты в случае сбоя приложения. .Net имеет возможность добавить функцию, которая вызывается при возникновении необработанного исключения. Я не уверен, что C++ предлагает те же возможности.

Просто с макушки моей головы:

  • Рассматривали ли вы использование потоков вместо процессов?
  • Попробуйте передать дескриптор основного потока / процесса дочерним процессам и заставить их ждать этого дескриптора. Это работает для потоков, так как ожидание на дескрипторе потока ожидает, пока этот поток не завершится и не завершится. Не слишком уверен, что он будет работать для процессов, следует проверить MSDN, чтобы проверить это.
Другие вопросы по тегам