【问题标题】:Linux, forked process hangs immediatelyLinux,分叉的进程立即挂起
【发布时间】:2015-04-28 12:19:58
【问题描述】:

我遇到了只偶尔出现的 fork 问题。它基本上一直有效,但在测试系统上每隔一段时间就会失败。

我的研究没有发现其他人提到类似的问题。

问题出现在嵌入式 Linux 系统上。没有可用的交换分区。

正在运行的进程在所有线程中阻塞了所有信号,并在专用线程中通过 sigtimedwait 处理它们。

如果我通过 fork 启动子进程:

  • 父进程继续返回值 > 0。因此,fork 工作。返回 no -1 - 所以没有错误,没有内存不足!然后父进程等待子进程并且永远不会从等待中返回。
  • 子进程从不做任何事情可观察。子进程应该做的第一件事就是写一条日志消息。此日志消息永远不会出现。然后它应该产生两个孩子 处理一个超时进程和一个工作进程。这些进程永远不会出现。
  • 如果我在命令行上通过 ps 检查,我可以看到现有的子进程。它处于状态 S(可中断睡眠(等待事件完成))。它永远不会获得任何 CPU 时间,它不会显示任何 CPU 使用率。
  • 如果我kill -9子进程,父进程完成等待并愉快地继续。

显示问题的伪代码:

const pid_t childPid = fork();
if(0 == childPid) {
    // child process
    LOG_MSG("Child process started."); // <- This never shows up in the syslog.

    // do some stuff

} else if(-1 == childPid) {
    // error
    LOG_MSG("Parent process: Error starting child process!");
    result = false;
} else {
    // parent process
    LOG_MSG("Parent process: Child process started. PID: %.", childPid); // <- This shows up in the syslog.

    // do some stuff
    int status = 0;
    const int options = 0;
    const auto waitResult = waitpid(childPid, &status, options);
    // more stuff
}

问题:

  1. 什么可能导致这个挂起的子进程?
  2. 如果新进程在导致 syslog 的 LOG_MSG 调用中内存不足,会发生什么情况?这会引发一个信号(因为它被阻止而无法传递)?

【问题讨论】:

  • LOG_MSG的定义是什么?
  • 你能用“strace”运行你的程序,使用“-ff”选项来跟随分叉吗?例如“strace -o output.txt -ff your_program”。您应该看到父进程及其产生的任何子进程的跟踪文件。这些文件中的信息可能很有启发性。
  • 我认为LOG_MSG 是这里的关键。如果父母和孩子都在争夺LOG_MSGs 资源,我不会感到惊讶,这就是问题所在。
  • 您的问题可能与此有关:bugs.debian.org/cgi-bin/bugreport.cgi?bug=454183
  • 您说它处于可中断睡眠状态,等待事件完成。看起来它正在等待一个锁(到日志文件)并且父级持有该锁。

标签: c linux fork


【解决方案1】:

我从Adrien Descamps' link(另见上面的cmets)和C++-ified中提取样本并对其进行了一些修改:

#include <thread>
#include <iostream>
#include <atomic>

#include <unistd.h>
#include <syslog.h>
#include <sys/wait.h>


std::atomic<bool> go(true);


void syslogBlaster() {
   int j = 0;
   while(go) {
      for(int i = 0; i < 100; ++i) {
         syslog(LOG_INFO, "syslogBlaster: %d@%d", i, j);
      }
      ++j;

      std::this_thread::sleep_for(std::chrono::milliseconds(30));
   }
}

int main() {
   std::thread blaster(syslogBlaster);

   for(int i = 0; i < 1000; ++i) {
      const auto forkResult = fork();
      if(0 == forkResult) {
          syslog(LOG_INFO, "Child process: '%d'.", static_cast<int>(getpid()));
          exit(0);
      } else if(forkResult < 0) {
         std::cout << "fork() failed!" << std::endl;
      } else {
         syslog(LOG_INFO, "Parent process.");
         std::cout << "Waiting #" << i << "!" << std::endl;
         int status = 0;
         const int options = 0;
         const auto waitResult = waitpid(forkResult, &status, options);
         if(-1 == waitResult) {
             std::cout << "waitpid() failed!";
         } else {
             std::cout << "Bye zombie #" << i << "!" << std::endl;
         }
      }

      std::this_thread::sleep_for(std::chrono::milliseconds(28));
   }

   go = false;
   blaster.join();

   std::cout << "Wow, we survived!" << std::endl;
}

运行此示例,进程在第一次和第五次尝试之间卡住(在我的设备上)。

说明

系统日志是问题!

一般来说:非异步信号安全函数是问题所在!

如 Damian Pietras 所述(见链接页面)

在子进程中调用任何非异步安全的函数(man 7 信号) 多线程程序中 fork() 调用后的进程未定义 行为

从技术上讲,问题(未定义的行为)是由不一致的关键部分中的数据引起的(因为 不是 一个线程在分叉期间正好位于它的中间)或 - 像在这种情况下 - 来自锁定在父级中的互斥体,然后永远保持这种方式在子级中。

此答案归功于 Adrien Descamps 找到根本原因 (syslog),还归功于 PSkocik 和 Jan Spurny 检测源 (LOG_MSG)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-20
    相关资源
    最近更新 更多