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

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

В этом примере я передал 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
}

1 Ответ

0 голосов
/ 21 января 2019

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

Поскольку у вас нет контроля над тем, что делает процесс 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;

и вызов обработчика sig-alarm

  kill(child_pid, SIGKILL)

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

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

...