execvp新进程图像和_exit

时间:2017-03-15 17:01:43

标签: c fork exec

我有这段代码:

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

    for (int i = 1; i < argc; i++) {
        pid_t pid = fork();

        if ( !pid ) {
            char *aux[] = {argv[i], NULL};
            execvp(aux[0], aux);
            printf("\nChild %d executed %s with exit code %d\n", getpid(), aux[0], i); 
            _exit(i);
        }

    }

    for (int i = 1; i < argc; i++) {
        printf("Child dead\n");
        wait(NULL);
    }

    return 0;
}

运行时,我发现在子进程的execvp之后永远不会运行printf(...)。我检查了man exec我找到的地方:

  

&#34; exec()系列函数用新的过程映像替换当前过程映像。&#34;

这是否意味着除了printf之外,_exit(i)也没有被执行?如果是这样,我怎么能杀死孩子的过程? execvp是否会处理新的过程映像?

编辑:假设我想在execvp行之后执行printf(或任何其他指令),如代码所示,有什么办法吗?

编辑[2]:使其更清晰。如果我有这个块:

int main(void) {

    for (int i = 0; i < 10; i++) {
        pid_t pid = fork();

        if (!pid) {
            printf("child %d\n", getpid());
            //_exit(1);
        }

        else {
            wait(NULL);
            printf("parent here\n");
        }
    }

    return 0;
}

评论exit(1)时,事情变得疯狂。所以,我的疑问是:是否存在execvp正确执行的任何情况,而原始子进程(调用execvp的进程)不会像_exit(n)调用那样退出,从而产生如上面的块中的结果。

3 个答案:

答案 0 :(得分:2)

  

这是否意味着除了printf之外,_exit(i)也没有被执行?

是。除非execvp()(以及其他exec系列函数)失败,否则它将返回,并且将执行printf()_exit()语句。

  

如果是这样,我怎么能杀死孩子的过程? execvp是否会处理新的过程映像?

execvp()不会杀死子进程 - 它甚至不知道它替换的进程是子进程。

在大多数实际情况中,您不希望“杀死”子进程。您希望等待以使用wait(2)来完成子进程 - 您已经这样做了。子进程是独立的,就像你的“主要”进程一样;它们会在main()返回时退出,或者通过调用exit()/ _ exit等退出,或者它们异常终止(例如被SIGTERM / SIGKILL信号杀死)。

答案 1 :(得分:2)

  

这是否意味着除了printf之外,_exit(i)也没有被执行?

正确。 execvp仅在失败时返回。

  

如果是这样,我该如何杀死子进程? execvp是否会处理新的过程映像?

我认为您实际上已经在询问您需要做些什么来确保流程退出,因为您无法自己致电exit

你不需要做任何事情。现在正在执行的程序将在它想要退出时调用exit

  

如果是这样,我该如何杀死子进程?

如果你真的在问如何杀死(过早终止)这个过程,可以通过kill发送信号来完成。发送信号的进程的pid由fork返回。

  

我想在execvp之后执行printf(或任何其他指令),如代码所示,有什么办法吗?

没有。您的程序不再在该过程中运行。您的所有代码和变量都已替换为新程序。如果您希望该过程输出某些内容,则必须由您执行的程序完成。

父级可以通过创建一个设置了exec标志的管道(在fork之前)来检查子级是否已成功调用FD_CLOEXEC

int pipefd[2];
pid_t pid;
int status;

if (pipe2(pipefd, O_CLOEXEC) == -1) {
   perror("Can't create pipe");
   exit(1);
}

pid = fork();
if (pid == -1) {
   perror("Can't create pipe");
   exit(1);
}

if (!pid) {
    char* child_argv[] = { argv[i], NULL };

    close(pipefd[0]);

    execvp(child_argv[0], child_argv);
    write(pipefd[1], &errno, sizeof(errno));
    _exit(1);
}

{
   char buf[sizeof(errno)];
   ssize_t bytes_read;

   close(pipefd[1]);

   bytes_read = read(pipefd[0], buf, sizeof(buf));
   if (bytes_read == -1) {
      /* Ignoring read errors */
   }
   else if (bytes_read > 0) {
      /* An error occurred in the child */
      if (bytes_read >= sizeof(errno)) {
         memmove(&errno, buf, sizeof(errno));
         perror("Can't exec");
      } else {
         fprintf(stderr, "Can't exec: Unknown error\n");
      }

      waitpid(pid, &status, 0);
      exit(1);
   }
}

/* The child is now running the executed program */
waitpid(pid, &status, 0);
if      ( status & 0x7F ) { fprintf(stderr, "Child killed by signal %d\n", status & 0x7F); }
else if ( status >> 8   ) { fprintf(stderr, "Child exited with error %d\n", status >> 8); }
else                      { fprintf(stdout, "Child completed successfully\n"); }

答案 2 :(得分:0)

如果execvp调用成功,则表示您是正确的,printf_exit调用都不会执行。但一个好的做法是检查execvp调用是否成功,如果不成功则进行故障排除。

正如评论中所述,该计划有望终止,并通过wait来电正确收到。

只有在出于任何原因想要在终止之前中止它之前,才有必要杀死该过程。为此,您必须使用kill函数,使用fork返回的进程的pid以及您要发送的信号。

回答编辑

  

假设我想在execvp行之后执行printf(或任何其他指令),如代码所示,有什么办法吗?

不,除非execvp调用失败。在(内部,实际上......)之后execvp,新程序的“文本”将被执行,它将main()运行等...父程序的“文本”将被丢弃(手册报价中的替换部分)。

回答编辑[2]

  

是否存在execvp正确执行的任何情况,而原始子进程(调用execvp的进程)不会像_exit(n)调用中那样退出

我不确定我完全理解你的新问题 execvp执行的程序可以通过exit()_exit()以多种方式退出,从main()返回,被信号杀死......但是{{1}例如,调用将出现在新程序的“文本”中,而不是父程序的“文本”中 它也可以“永远”活着,在这种情况下,你必须采取一些预防措施,以避免在父母的_exit电话中被阻止。大多数情况下,父母会等待wait信号检测到孩子必须SIGCHLD - 并且当他们希望终止时可能wait

至于你问题的这一部分:

  

...产生的结果就像上面的块一样。

关键是您的第二个代码段不会调用kill()

fork()调用后,两个进程(父进程和子进程)继续执行相同的程序,具有相同的状态。所以在分叉后,两者都将继续执行循环。因此父进程将尝试fork execvp子进程,子进程将执行相同的操作。所以你最终会有超过11个进程,就像我想象的那样。

注意:也许使用gdb跟踪您的程序将有助于您更准确地了解工作原理。您必须在每次(10 - i)来电之前使用set follow-fork-mode [child|parent]。这有点乏味但可行。