超过超时限制后未从 alarm() 触发信号处理程序

Signal handler not being triggered from alarm() after timeout limit exceeded

如果子进程超过超时限制,我想将其终止,超时限制将在几秒钟内作为参数传递给程序。

在这个例子中,我将 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() 处理程序,但现在有一个问题,即 killChild() 中的 getpid() 指的是父 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
}

您为 child 进程安装信号处理程序,然后调用 execve(),它用 execed 程序完全替换当前程序。这样信号处理程序就消失了。

由于您无法控制 exec 进程的行为,因此只有 parent 可以终止其 child。所以你想为 parent 安装信号处理程序并让它发送 SIGKILL 到 child.

实现这个可能会很棘手,因为好像需要信号处理程序知道 child 的 PID。

有几种方法可以做到这一点。

让我们从复杂但便携的开始。这里的信号处理程序并没有杀死 child,只是设置了一个标志,表明它被调用了:

#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() 和其他几个 non-async-signal-save 函数。因此调用 perror() 来指示失败例如是 non-no.