【问题标题】:Background process is exiting faster than I can add its pid for management后台进程退出的速度比我添加它的 pid 进行管理的速度要快
【发布时间】:2010-10-03 00:53:25
【问题描述】:

我正在使用 fork() 在 C 中创建后台进程。

当我创建其中一个进程时,我将它的 pid 添加到一个数组中,这样我就可以跟踪后台进程。

    pid = fork();

    if(pid == -1) 
    {
        printf("error: fork()\n");
    }
    else if(pid == 0) 
    {
        execvp(*args, args);
        exit(0);
    }
    else  
    {
        // add process to tracking array
        addBGroundProcess(pid, args[0]);
    }

我有一个收割僵尸的处理程序

void childHandler(int signum) 
{ 
    pid_t pid; 
    int status; 

    /* loop as long as there are children to process */ 
    while (1) { 

       /* get zombie pids */ 
       pid = waitpid(-1, &status, WNOHANG); 

       if (pid == -1)
       { 
           if (errno == EINTR)
           { 
               continue; 
           } 

           break; 
       } 
       else if (pid == 0)
       { 
           break; 
       } 

       /* Remove this child from tracking array */ 
       if (pid != mainPid)
            cleanUpChild(pid);
    }    
}

当我创建后台进程时,处理程序正在执行并试图在我调用 addBGroundProcess 之前清理子进程。

我正在使用像 emacs& 这样不应该立即退出的命令。

我错过了什么?

谢谢。

【问题讨论】:

    标签: c linux background-process


    【解决方案1】:

    你是对的,那里有一个竞争条件。我建议您使用sigprocmask 函数阻止SIGCHLD 的传递。将新的 PID 添加到数据结构后,再次解除对信号的阻塞。当信号被阻塞时,如果接收到该信号,内核会记住它需要传递该信号,当信号被解除阻塞时,它就会被传递。

    这就是我的意思,具体来说:

    sigset_t mask, prevmask;
    
    //Initialize mask with just the SIGCHLD signal
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    
    sigprocmask(SIG_BLOCK, &mask, &prevmask); /*block SIGCHLD, get previous mask*/
    pid = fork();
    
    if(pid == -1) 
    {
        printf("error: fork()\n");
    }
    else if(pid == 0) 
    {
        execvp(*args, args);
        exit(0);
    }
    else  
    {
        // add process to tracking array
        addBGroundProcess(pid, args[0]);
    
        // Unblock SIGCHLD again
        sigprocmask(SIG_SETMASK, &prevmask, NULL);
    }
    

    另外,我认为execvp 可能会失败。 (一般来说处理这个问题很好,即使在这种情况下没有发生。)这完全取决于它是如何实现的,但我不认为你可以在命令末尾放置一个&让它在后台运行。在这种情况下,运行emacs 本身可能是您想要的,而将& 放在命令行的末尾是shell 提供的一项功能。

    编辑:我看到您的 cmets 关于您不希望 emacs 在当前终端会话中运行的方式。您希望它如何运行,确切地说——也许是在单独的 X11 窗口中?如果是这样,还有其他方法可以实现。

    处理execvp 失败的一个相当简单的方法是这样做:

        execvp(*args, args);
        perror("execvp failed");
        _exit(127);
    

    【讨论】:

      【解决方案2】:

      您的代码只是捕获了它派生的子进程的退出,这并不是说该子进程没有首先派生另一个进程。我猜在你的情况下,emacs 出于某种原因正在对自己进行另一个 fork(),然后允许初始进程退出(这是守护进程会做的一个技巧)。

      setsid() 函数可能也值得一看,虽然我自己没有编写一些代码来检查它,但我不确定这是否相关。

      【讨论】:

      • 说清楚,你是不是说自从我调用 emacs& (带有 &)后,Unix 将产生另一个子进程来执行后台进程 emacs,从而导致我创建的子进程死亡并引发SIGCHLD 标志?
      • 啊……我错过了“&”。这似乎是第二次分叉的可能原因。虽然更具体地说,我认为“&”可能会导致生成一个 shell,它执行 emacs 并立即退出。请记住,这不是我的想法,所以我还没有做太多的测试。
      • 如果我不运行 emacs&,而是运行 emacs,emacs 会在当前终端会话中打开。这是我不想要的行为。我实际上想在后台执行命令(emacs、ls 等)并跟踪这些进程并在僵尸出现时清理它们。
      【解决方案3】:

      您不应该使用带有& 的shell 来运行后台进程。如果你这样做,他们就会成为你无法追踪和等待的孙辈。相反,您需要模仿 shell 在您自己的代码中运行后台进程的操作,或者关闭终端(或者更确切地说是 stdin/out/err)并在其位置打开 /dev/null子进程,因此它们不会尝试写入终端或控制它。

      【讨论】:

      • 您能否进一步解释或提供示例?我同意使用 & 执行是不正确的,但我不希望命令使用终端并且我不想关闭终端。
      • 如果你不想让命令使用终端,为什么不想关闭终端呢?您将在 child 进程中关闭它,而不是在父进程中。
      • 只是沟通不畅...我确实想关闭子进程的终端,以便只有前台进程(我的 shell)可以使用它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-14
      • 1970-01-01
      • 1970-01-01
      • 2015-11-26
      • 2016-04-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多