Обработчик сигнала не запускается из alarm() после превышения лимита времени ожидания

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

В этом примере я передал 3 в качестве предела времени ожидания. Программа здесь /bin/cat без каких-либо дополнительных аргументов, поэтому она должна зависнуть и должна сработать SIGALRM, но почему-то не срабатывает функция killChild().

void killChild(int sig) {
    printf("PID: %d\n", getpid());
    kill(getpid(), SIGKILL);
}

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

    // Parse timeout arg
    int timeout = 0;
    if (argv[1] != NULL) {
        timeout = atoi(argv[1]);
    }

    char program[] = "/bin/cat";

    // Create child process
    pid_t child = fork();


    if (child == 0) { // Child

        signal(SIGALRM, killChild);
        alarm(timeout); 

        printf("I'm the child %d, my parent is %d\n", getpid(), getppid());
        char* av[] = { program, NULL };

        execve(program, av, NULL);   
    } else {          // Parent

        printf("I'm the parent %d, my child is %d\n", getpid(), child);
        wait(NULL);
        alarm(0);    // Reset alarm if program executes within timeout limit
    }
    return 0;
}

РЕДАКТИРОВАТЬ: согласно предложению @alk, сигнал заменяется, поэтому мой единственный вариант - сохранить его в родительском процессе, поэтому я изменил код, чтобы вызовы alarm() и signal() находились вне дочернего блока.

Теперь вызывается обработчик killChild(), но теперь есть одна проблема, заключающаяся в том, что getpid() в killChild() относится к родительскому PID — как мне передать дочерний PID в killChild()?

signal(SIGALRM, killChild);
alarm(timeout);

if (child == 0) { // Child

    printf("I'm the child %d, my parent is %d\n", getpid(), getppid());
    char* av[] = { program, NULL };

    execve(program, av, NULL);   
} else {          // Parent

    printf("I'm the parent %d, my child is %d\n", getpid(), child);
    wait(NULL);
    alarm(0);    // Reset alarm if program executes within timeout limit
}

person doctopus    schedule 21.01.2019    source источник
comment
Возможно, какой-то код отсутствует, но я не вижу, где вызывается killChild(). И kill(getpid(),9) просто совершает самоубийство. Извините, я пропустил обработчик.   -  person Alain Merigot    schedule 21.01.2019
comment
@AlainMerigot killChild() используется как обработчик сигналов   -  person woz    schedule 21.01.2019
comment
Обратите внимание, что использование execve() с нулевым указателем для среды не является общепринятым. лучший — я думаю, что это приводит к неопределенному поведению. Однако это не связано с вашей текущей проблемой.   -  person Jonathan Leffler    schedule 21.01.2019
comment
@woz: ... и вызывается ОС при приеме сигнала процессом.   -  person alk    schedule 21.01.2019


Ответы (1)


Вы устанавливаете обработчик сигнала для дочернего процесса, а затем вызываете execve(), который полностью заменяет текущую программу программой execed. При этом обработчик сигнала исчез.

Поскольку у вас нет контроля над тем, что делает процесс exec, только родитель может убить своего потомка. Итак, вы хотите установить обработчик сигнала для родителя и отправить SIGKILL дочернему.

Реализация этого может быть сложной, поскольку обработчику сигнала потребуется знать PID дочернего элемента.

Есть несколько способов сделать это.

Начнем со сложного, но портативного. Здесь обработчик сигнала не уничтожает дочерний элемент, а просто устанавливает флаг, указывающий, что он был вызван:

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


volatile sig_atomic_t f = 0;

/* to be set as handler for SIGALRM */
void sig_alarm(int unused)
{
  f = 1;
}

int main(void)
{
  pid_t child_pid;

  /* install signal handler here */
  ...

  /* fork/exec and set child_pid here */
  ...

  /* assuming to be in the parent from here */
  ...

  /* set alarm here */
  ...

  while (!f)
  {
    int status;        
    int result = waitpid(child_pid, &status, WNOHANG);
    if (-1 == result)
    {
      if (errno != EINTR)
      {
        perror("waitpid() failed");
        exit(EXIT_FAILURE);
      }

      continue;
    }
    else if (0 != result) /* child ended. */
    {
      /* Analyse status here to learn in detail if the child
         ended abnormally or normally and if the latter which
         exit code it returned (see W* marcos on man waitpid). */
      break; 
    }        

    sleep(1); /* busy waiting is not nice so sleep a bit */
  }

  if (f) /* sig-alarm handler was called */
  {
    if (-1 == kill(child_pid, SIGKILL))
    {
      perror("kill() failed");
      exit(EXIT_FAILURE);
    }
  }

  exit(EXIT_SUCCESS);
}

Быстрое и грязное решение, которое может не работать ни в одной системе, состояло бы в том, чтобы определить child_pid глобально.

volatile sig_atomic_t child_pid = 0;

и вызвать обработчик сигнальной тревоги

  kill(child_pid, SIGKILL)

Это может не сработать, поскольку неясно, подходит ли pid_t к sig_atomic_t на платформе, для которой создается код.

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

person alk    schedule 21.01.2019
comment
Теперь это имеет смысл. Я сделал ваши предложения и добавил дополнение. Сейчас вызывается обработчик killChild(), но проблема в том, что я не уверен, как передать ему дочерний PID. Любые идеи? - person doctopus; 21.01.2019