Linux,分叉进程立即挂起
Linux, forked process hangs immediately
我遇到了一个偶尔出现的 fork 问题。它基本上一直有效,但在测试系统上偶尔会失败。
我的研究没有发现任何其他人提到类似的问题。
问题发生在嵌入式 Linux 系统上。没有可用的交换分区。
进程 运行 在所有线程中阻塞了所有信号,并在专用线程中通过 sigtimedwait 处理它们。
如果我通过 fork 启动一个 child 进程:
- parent 进程以 return 值 > 0 继续。所以分叉工作。有 no -1 returned - 所以没有错误,不是内存不足!然后 parent 等待 child 进程,并且从不等待 return。
- child 进程从不做任何可观察 的事情。 child 进程 应该 做的第一件事是写一条日志消息。此日志消息永远不会出现。然后它应该产生两个 child
处理一个 time-out 进程和一个辅助进程。这些进程从未出现。
- 如果我在命令行上通过 ps 检查,我可以看到现有的 child 进程。它处于S状态(可中断睡眠(等待事件完成))。它从来没有得到任何 CPU 时间,它没有显示 CPU 用法。
- 如果我kill -9 child 进程,parent 进程完成等待并愉快地继续。
显示问题的伪代码:
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
}
问题:
- 什么会导致这个挂起的 child 进程?
- 如果新进程在导致 syslog 的 LOG_MSG 调用中内存不足,会发生什么情况?这会发出信号吗(由于被阻塞而无法传送)?
我从 Adrien Descamps' link 中获取了示例(另请参阅上面的评论)和 C++ 化并对其进行了一些修改:
#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 所述(参见链接页面)
calling any function that is not async-safe (man 7 signal) in child
process after fork() call in a multi-threaded program has undefined
behaviour
从技术上讲,问题(未定义的行为)是由关键部分中的数据不一致引起的(因为 不是 的线程恰好在它的中间fork) 或 - 就像在这种情况下 - 来自锁定在父级中的互斥锁,然后在子级中永远保持这种方式。
此答案归功于 Adrien Descamps 寻找根本原因 (syslog),也归功于 PSkocik 和 Jan Spurny 检测来源 (LOG_MSG)。
我遇到了一个偶尔出现的 fork 问题。它基本上一直有效,但在测试系统上偶尔会失败。
我的研究没有发现任何其他人提到类似的问题。
问题发生在嵌入式 Linux 系统上。没有可用的交换分区。
进程 运行 在所有线程中阻塞了所有信号,并在专用线程中通过 sigtimedwait 处理它们。
如果我通过 fork 启动一个 child 进程:
- parent 进程以 return 值 > 0 继续。所以分叉工作。有 no -1 returned - 所以没有错误,不是内存不足!然后 parent 等待 child 进程,并且从不等待 return。
- child 进程从不做任何可观察 的事情。 child 进程 应该 做的第一件事是写一条日志消息。此日志消息永远不会出现。然后它应该产生两个 child 处理一个 time-out 进程和一个辅助进程。这些进程从未出现。
- 如果我在命令行上通过 ps 检查,我可以看到现有的 child 进程。它处于S状态(可中断睡眠(等待事件完成))。它从来没有得到任何 CPU 时间,它没有显示 CPU 用法。
- 如果我kill -9 child 进程,parent 进程完成等待并愉快地继续。
显示问题的伪代码:
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
}
问题:
- 什么会导致这个挂起的 child 进程?
- 如果新进程在导致 syslog 的 LOG_MSG 调用中内存不足,会发生什么情况?这会发出信号吗(由于被阻塞而无法传送)?
我从 Adrien Descamps' link 中获取了示例(另请参阅上面的评论)和 C++ 化并对其进行了一些修改:
#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 所述(参见链接页面)
calling any function that is not async-safe (man 7 signal) in child process after fork() call in a multi-threaded program has undefined behaviour
从技术上讲,问题(未定义的行为)是由关键部分中的数据不一致引起的(因为 不是 的线程恰好在它的中间fork) 或 - 就像在这种情况下 - 来自锁定在父级中的互斥锁,然后在子级中永远保持这种方式。
此答案归功于 Adrien Descamps 寻找根本原因 (syslog),也归功于 PSkocik 和 Jan Spurny 检测来源 (LOG_MSG)。