后台进程退出的速度比我添加它的 pid 进行管理的速度要快

Posted

技术标签:

【中文标题】后台进程退出的速度比我添加它的 pid 进行管理的速度要快【英文标题】:Background process is exiting faster than I can add its pid for management 【发布时间】: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& 这样不应该立即退出的命令。

我错过了什么?

谢谢。

【问题讨论】:

【参考方案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)可以使用它。

以上是关于后台进程退出的速度比我添加它的 pid 进行管理的速度要快的主要内容,如果未能解决你的问题,请参考以下文章

守护进程的编写

22. Linux的进程管理

分叉后退出子进程

Linux-进程管理

iterm2 cmd +c无法退出 如何解决

十一实操篇-进程管理