【问题标题】:Tracking the death of a child process跟踪子进程的死亡
【发布时间】:2011-01-23 14:00:44
【问题描述】:

如何在不让父进程等到子进程被杀死的情况下追踪子进程的死亡?

我正在尝试一个客户端-服务器方案,其中服务器接受来自客户端的连接,并为它接受的每个连接分叉一个新进程。

我忽略了 SIGCHLD 信号以防止僵尸创建。

signal(SIGCHLD, SIG_IGN);
while(1)
{
  accept();
  clients++;
  if(fork() ==0)
  {
     childfunction();
     clients--;
  }
  else
  {
  }
}

上述场景的问题是,如果子进程在childfunction()函数中被杀死,全局变量clients并没有递减。

注意:我正在寻找不使用 SIGCHLD 信号的解决方案...如果可能的话

【问题讨论】:

  • 你可以在 SIGCHLD 的信号处理器中做一些事情
  • 我已经提到... SIGCHLD 信号被忽略.. ?
  • +1 表示非常戏剧性的标题和开头句。 D:

标签: c linux client-server


【解决方案1】:

通常您为SIGCHLD 编写一个处理程序,它在pid -1 上调用waitpid()。您可以使用其中的返回值来确定哪个 pid 死了。例如:

void my_sigchld_handler(int sig)
{
    pid_t p;
    int status;

    while ((p=waitpid(-1, &status, WNOHANG)) != -1)
    {
       /* Handle the death of pid p */
    }
}

/* It's better to use sigaction() over signal().  You won't run into the
 * issue where BSD signal() acts one way and Linux or SysV acts another. */

struct sigaction sa;

memset(&sa, 0, sizeof(sa));
sa.sa_handler = my_sigchld_handler;

sigaction(SIGCHLD, &sa, NULL);

您也可以调用waitpid(pid, &status, 0) 并指定子进程ID,并同步等待它终止。或者使用WNOHANG 来检查它的状态而不被阻塞。

【讨论】:

  • 但是在我的例子中 SIGCHLD 被忽略了??
  • @codingfreak 我建议您可能需要重新评估。你不需要忽略它来避免僵尸。当你waitpid() 时,“僵尸”就会消失。
  • @asveikau - 我正在尝试创建一个守护进程.. 并且使用 SIGCHLD 处理程序破坏了应用程序的稳定性.. 父进程应该一直在 SIGCHLD 处理程序中等待..跨度>
  • 正是 SIGCHLD 信号的用途。
  • @codingfreak:chld_handler 中的if 必须是while ((p = waitpid(-1, &status, WNOHANG)) > 0)(因为如果两个或更多孩子连续快速退出,您可能只会收到一个信号)。
【解决方案2】:

到目前为止,没有任何解决方案提供不使用 SIGCHLD 作为问题请求的方法。这是使用this answer 中概述的poll 的替代方法的实现(这也解释了为什么在这种情况下应避免使用SIGCHLD):

确保您有一个通往/来自您创建的每个子进程的管道。它可以是他们的标准输入/标准输出/标准错误,也可以只是一个额外的虚拟 fd。当子进程终止时,它的管道末端将被关闭,您的主事件循环将检测该文件描述符上的活动。从它关闭的事实可以看出,子进程已经死亡,并调用 waitpid 来收割僵尸。

(注意:为简洁起见,我省略了一些最佳实践,例如错误检查和清理文件描述符)

/**
 * Specifies the maximum number of clients to keep track of.
 */
#define MAX_CLIENT_COUNT 1000

/**
 * Tracks clients by storing their process IDs and pipe file descriptors.
 */
struct process_table {
    pid_t clientpids[MAX_CLIENT_COUNT];
    struct pollfd clientfds[MAX_CLIENT_COUNT];
} PT;

/**
 * Initializes the process table. -1 means the entry in the table is available.
 */
void initialize_table() {
    for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
        PT.clientfds[i].fd = -1;
    }
}

/**
 * Returns the index of the next available entry in the process table.
 */
int get_next_available_entry() {
    for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
        if (PT.clientfds[i].fd == -1) {
            return i;
        }
    }
    return -1;
}

/**
 * Adds information about a new client to the process table.
 */
void add_process_to_table(int i, pid_t pid, int fd) {
    PT.clientpids[i] = pid;
    PT.clientfds[i].fd = fd;
}

/**
 * Removes information about a client from the process table.
 */
void remove_process_from_table(int i) {
    PT.clientfds[i].fd = -1;
}

/**
 * Cleans up any dead child processes from the process table.
 */
void reap_zombie_processes() {
    int p = poll(PT.clientfds, MAX_CLIENT_COUNT, 0);

    if (p > 0) {
        for (int i = 0; i < MAX_CLIENT_COUNT; i++) {
            /* Has the pipe closed? */
            if ((PT.clientfds[i].revents & POLLHUP) != 0) {
                // printf("[%d] done\n", PT.clientpids[i]);
                waitpid(PT.clientpids[i], NULL, 0);
                remove_process_from_table(i);
            }
        }
    }
}

/**
 * Simulates waiting for a new client to connect.
 */
void accept() {
    sleep((rand() % 4) + 1);
}

/**
 * Simulates useful work being done by the child process, then exiting.
 */
void childfunction() {
    sleep((rand() % 10) + 1);
    exit(0);
}

/**
 * Main program
 */
int main() {
    /* Initialize the process table */
    initialize_table();

    while (1) {
        accept();

        /* Create the pipe */
        int p[2];
        pipe(p);

        /* Fork off a child process. */
        pid_t cpid = fork();

        if (cpid == 0) {
            /* Child process */
            close(p[0]);
            childfunction();
        }
        else {
            /* Parent process */
            close(p[1]);
            int i = get_next_available_entry();
            add_process_to_table(i, cpid, p[0]);
            // printf("[%d] started\n", cpid);
            reap_zombie_processes();
        }
    }

    return 0;
}

以下是在未注释 printf 语句的情况下运行程序的一些示例输出:

[31066] started
[31067] started
[31068] started
[31069] started
[31066] done
[31070] started
[31067] done
[31068] done
[31071] started
[31069] done
[31072] started
[31070] done
[31073] started
[31074] started
[31072] done
[31075] started
[31071] done
[31074] done
[31081] started
[31075] done

【讨论】:

    【解决方案3】:

    你不想要僵尸。如果子进程死亡并且父进程仍在运行但从未发出wait()/waitpid() 调用来获取状态,则系统不会释放与子进程关联的资源,并且进程中会留下僵尸/已失效进程表。

    尝试将您的 SIGCHLD 处理程序更改为更接近以下内容:

    
    void chld_handler(int sig) {
        pid_t p;
        int status;
    
        /* loop as long as there are children to process */
        while (1) {
    
           /* retrieve child process ID (if any) */
           p = waitpid(-1, &status, WNOHANG);
    
           /* check for conditions causing the loop to terminate */
           if (p == -1) {
               /* continue on interruption (EINTR) */
               if (errno == EINTR) {
                   continue;
               }
               /* break on anything else (EINVAL or ECHILD according to manpage) */
               break;
           }
           else if (p == 0) {
               /* no more children to process, so break */
               break;
           }
    
           /* valid child process ID retrieved, process accordingly */
           ...
        }   
    }
    

    您可以选择在使用sigprocmask() 执行信号处理程序期间屏蔽/阻止其他SIGCHLD 信号。信号处理例程完成后,必须将阻塞掩码返回到其原始值。

    如果您真的不想使用SIGCHLD 处理程序,您可以尝试将子处理循环添加到会定期调用的地方并轮询终止的子处理。

    【讨论】:

    • 按照您所说的进行更改后..我没有看到任何僵尸创建..让我看看它在 HTTP 服务器守护程序中的行为...
    • 我预计在您的守护进程中使用此代码不会出现任何重大问题。
    【解决方案4】:

    变量“clients”在 fork() 之后位于不同的进程地址空间中,当您在子进程中递减变量时,这不会影响父进程中的值。我认为您需要处理 SIGCHLD 才能正确处理计数。

    【讨论】:

      猜你喜欢
      • 2018-02-22
      • 1970-01-01
      • 1970-01-01
      • 2011-12-09
      • 2014-06-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多