Как мне написать программу, которая сообщает, когда моя другая программа заканчивается?

Как мне написать программу, которая сообщает, когда моя другая программа заканчивается?

8 ответов

Единственный способ сделать waitpid() или waitid() в программе, которую вы сами не породили, - стать ее родителем, отправив ptrace'ing.

Вот пример того, как использовать ptrace в операционной системе posix, чтобы временно стать другим родительским процессом, а затем дождаться завершения этой программы. В качестве побочного эффекта вы также можете получить код выхода и сигнал, вызвавший выход из этой программы.:

#include <sys/ptrace.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char** argv) {

    int pid = atoi(argv[1]);
    int status;
    siginfo_t si;

    switch (ptrace(PTRACE_ATTACH, pid, NULL)) {
        case 0:
            break;
        case -ESRCH:
        case -EPERM:
            return 0;
        default:
            fprintf(stderr, "Failed to attach child\n");
            return 1;
    }
    if (pid != wait(&status)) {
        fprintf(stderr, "wrong wait signal\n");
        return 1;
    }
    if (!WIFSTOPPED(status) || (WSTOPSIG(status) != SIGSTOP))  {
        /* The pid might not be running */
        if (!kill(pid, 0)) {
            fprintf(stderr, "SIGSTOP didn't stop child\n");
            return 1;
        } else {
            return 0;
        }
    }
    if (ptrace(PTRACE_CONT, pid, 0, 0)) {
        fprintf(stderr, "Failed to restart child\n");
        return 1;
    }

    while (1) {
        if (waitid(P_PID, pid, &si, WSTOPPED | WEXITED)) {
            // an error occurred.
            if (errno == ECHILD)
                return 0;
            return 1;
        }
        errno = 0;

        if (si.si_code & (CLD_STOPPED | CLD_TRAPPED)) {
            /* If the child gets stopped, we have to PTRACE_CONT it
             * this will happen when the child has a child that exits.
             **/
            if (ptrace(PTRACE_CONT, pid, 1, si.si_status)) {
                if (errno == ENOSYS) {
                    /* Wow, we're stuffed. Stop and return */
                    return 0;
                }
            }
            continue;
        }

        if (si.si_code & (CLD_EXITED | CLD_KILLED | CLD_DUMPED)) {
            return si.si_status;
        }
        // Fall through to exiting.
        return 1;
    }
}

Если вы хотите порождать другой процесс, а затем ничего не делать во время его выполнения, то большинство языков более высокого уровня уже имеют встроенные модули для этого. Например, в Perl есть system и обратные пометки для запуска процессов и ожидания их завершения, и такие модули, как IPC:: System:: Simple, для упрощения определения завершения программы, а также того, довольны ли вы или грустны по поводу того, что произошло. Использование языковой функции, которая обрабатывает все для вас, намного проще, чем пытаться сделать это самостоятельно.

Если вы работаете в Unix-системе, то завершение разветвленного вами процесса вызовет сигнал SIGCHLD. Это означает, что ваша программа может выполнять другие действия, выполняемые вашим дочерним процессом.

Поймать сигнал SIGCHLD зависит от вашего языка. В Perl вы устанавливаете обработчик сигнала следующим образом:

use POSIX qw(:sys_wait_h);

sub child_handler {

    while ((my $child = waitpid(-1, WNOHANG)) > 0) {
         # We've caught a process dying, its PID is now in $child.
         # The exit value and other information is in $?
    }

    $SIG{CHLD} \&child_handler;  # SysV systems clear handlers when called,
                                 # so we need to re-instate it.
}

# This establishes our handler.
$SIG{CHLD} = \&child_handler;

В CPAN почти наверняка есть модули, которые работают лучше, чем приведенный выше пример кода. Ты можешь использовать waitpid с определенным идентификатором процесса (вместо -1 для всех) и без WNOHANG если вы хотите, чтобы ваша программа спала, пока другой процесс не завершится.

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

Если вы используете Perl в Windows, то вы можете использовать модуль Win32:: Process для запуска процесса и вызова ->Wait на получившийся объект, чтобы ждать его смерти. Я не знаком со всеми внутренностями Win32::Process, но вы должны быть в состоянии подождать 0 (или же 1 на одну миллисекунду), чтобы проверить, мертв ли ​​процесс.

В других языках и средах ваш пробег может отличаться. Пожалуйста, убедитесь, что, когда ваш другой процесс умирает, вы проверяете, как он умирает. Наличие подпроцесса, потому что пользователь убил его, обычно требует ответа, отличного от его выхода, потому что он успешно завершил свою задачу.

Всего наилучшего,

Павел

В Windows я использовал метод создания глобального именованного объекта (такого как мьютекс с CreateMutex), а затем заставил программу мониторинга открыть этот же мьютекс и дождаться его (с WaitForSingleObject). Как только первая программа завершается, вторая программа получает мьютекс и знает, что первая программа завершилась.

В Unix обычный способ решить эту проблему - заставить первую программу записать свой pid (getpid()) в файл. Вторая программа может отслеживать этот pid (используя kill(pid, 0)), чтобы увидеть, не ушла ли первая программа. Этот метод зависит от условий гонки, и есть, несомненно, лучшие способы его решения.

Вы на Windows? Если это так, то решение проблемы должно решить следующее: вам нужно передать идентификатор процесса:

  bool WaitForProcessExit( DWORD _dwPID )
  { 
     HANDLE hProc = NULL;
     bool bReturn = false;

     hProc = OpenProcess(SYNCHRONIZE, FALSE, _dwPID);

     if(hProc != NULL)
     {
       if ( WAIT_OBJECT_0 == WaitForSingleObject(hProc, INFINITE) )
       {
         bReturn = true;
       }
     }

     CloseHandle(hProc) ;
  }

  return bReturn;
}

Примечание. Это блокирующая функция. Если вы хотите неблокировать, вам нужно изменить значение INFINITE на меньшее значение и вызвать его в цикле (возможно, оставив открытым дескриптор hProc, чтобы избежать повторного открытия другого процесса с тем же PID).

Кроме того, у меня не было времени протестировать этот кусок исходного кода, но я взял его из моего приложения, которое работает.

Большинство операционных систем, как правило, одни и те же вещи....

вы записываете идентификатор процесса рассматриваемой программы и просто отслеживаете его, периодически запрашивая активные процессы

В Windows, по крайней мере, вы можете вызвать события, чтобы сделать это...

Хм, вы не можете, это невыполнимая задача, учитывая природу этого.

Допустим, у вас есть программа foo, которая принимает в качестве входных данных другую программу foo-sub.

Foo {
 func Stops(foo_sub) { run foo_sub; return 1; }
}

Проблема со всем этим, будь то довольно упрощенный дизайн, состоит в том, что если foo-sub - это программа, которая никогда не заканчивается, сама foo никогда не заканчивается. Нет никакого способа узнать извне, является ли foo-sub или foo причиной, по которой программа останавливается, и что определяет, требуется ли вашей программе всего лишь столетие для запуска?

По сути, это один из вопросов, на которые компьютер не может ответить. Для более полного обзора в Википедии есть статья на эту тему.

Если вы хотите проанализировать одну программу без выполнения, то это неразрешимая проблема.

Это называется "проблемой остановки" и не решаемо. Смотрите http://en.wikipedia.org/wiki/Halting_problem

Другие вопросы по тегам