我正在编写一个C++守护进程,将在Linux嵌入式系统上运行并执行一个shell命令。我想获取该shell命令的输出(stdout)以及退出代码。我按照以下SO问题中的方法获取管道中shell命令的返回值或退出代码:
How to execute a command and get return code stdout and stderr of command in C++
这很好用,但当我将进程变成守护进程时,无论命令成功还是失败,
我参考了这个SO问题,学习如何创建一个Linux守护进程:"Creating a daemon in Linux",以及一本名为"Linux-UNIX-Programmierung"的德语书籍,学习如何创建守护进程。到目前为止,这些方法都能正常工作,但会破坏pclose()函数的行为。
以下是重现此问题的示例:
将守护进程版本切换为常规版本,请将第22行的
我在我的主开发机上使用
我还使用我的arm-cross-compile工具链编译了相同的代码,以测试错误是否可以在另一种架构上重现。我的arm-cross-compile工具链的GCC版本为7.3.0,运行程序的操作系统是32位ARM的定制版本Yocto Linux。
两者的行为相同,并产生相同的输出-在非守护进程版本中产生预期的输出,在守护进程程序中产生意外的“-1”输出。
以下是启用守护进程的嵌入式“Yocto Linux”系统的系统日志文件输出(
这是禁用后的效果:
供比较,这是我的 x64 Linux(systemd 和 journalctl)守护进程代码:
"和普通的代码:"
如您所见,在四种情况下,命令结果都可以正确地读取为“test”,而在非守护程序中,
我试图研究一下是否有关于守护进程化进程与
供参考,我也尝试使用
非常感谢您提前的任何帮助,并请让我知道是否需要进一步的信息或更多的细节。
pclose()
始终返回“-1”。预期结果应该是“0”代表成功执行,而整数“>0”代表shell命令的错误代码。
命令的输出 - stdout - 可以正确地读取和解释,但尝试关闭管道以获取退出代码会失败,并显示错误代码“-1”(请参阅 pclose 的手册以了解如何解释返回值)。我参考了这个SO问题,学习如何创建一个Linux守护进程:"Creating a daemon in Linux",以及一本名为"Linux-UNIX-Programmierung"的德语书籍,学习如何创建守护进程。到目前为止,这些方法都能正常工作,但会破坏pclose()函数的行为。
以下是重现此问题的示例:
#include <array> // For std::array
#include <memory> // For std::unique_ptr
#include <string>
#include <sys/stat.h>
#include <sys/syslog.h> // For all syslog things
#include <sys/wait.h>
#include <unistd.h>
// Just a helper method for signal handling
void signalHandler(int sig) {
switch (sig) {
case SIGINT:
case SIGTERM:
break;
}
}
int main(int argc, char *argv[]) {
/* Open log file to be able to use syslog */
setlogmask(LOG_UPTO(LOG_DEBUG));
openlog("MyDemoProg", LOG_PID, LOG_DAEMON);
#if 1 // Set to 0 to disable the daemonizing
/* Fork off the parent process */
pid_t pid = fork();
/* An error occurred */
if (pid < 0)
exit(EXIT_FAILURE);
/* Success: Let the parent terminate */
if (pid > 0)
exit(EXIT_SUCCESS);
/* On success: The child process becomes session leader */
if (setsid() < 0)
exit(EXIT_FAILURE);
/* Catch, ignore and handle signals */
signal(SIGCHLD, SIG_IGN);
/* Set up a signal handler */
struct sigaction newSigAction;
newSigAction.sa_handler = signalHandler;
sigemptyset(&newSigAction.sa_mask);
newSigAction.sa_flags = 0;
/* Signals to handle */
sigaction(SIGHUP, &newSigAction, NULL); /* catch hangup signal */
sigaction(SIGTERM, &newSigAction, NULL); /* catch term signal */
sigaction(SIGINT, &newSigAction, NULL); /* catch interrupt signal */
/* Fork off for the second time*/
pid = fork();
/* An error occurred */
if (pid < 0)
exit(EXIT_FAILURE);
/* Success: Let the parent terminate */
if (pid > 0)
exit(EXIT_SUCCESS);
/* Set new file permissions */
umask(0);
/* Change the working directory to the root directory */
/* or another appropriate directory */
chdir("/");
/* Close all open file descriptors */
int x;
for (x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
close(x);
}
#endif // end of daemonizing
std::string command = "ls /var/bla/; sleep 2; echo test";
syslog(LOG_DEBUG, "Command is: %s", command.c_str());
int rc = -999; // the return code variable, set to some value to see if it's
// truly changed
std::array<char, 16> buffer;
std::string commandResult;
// A wrapper function to be able to get the return code while still using the
// automatic close function wizzardy of unique_ptr
auto pclose_wrapper = [&rc](FILE *cmd) { rc = pclose(cmd); };
{
const std::unique_ptr<FILE, decltype(pclose_wrapper)> pipe(
popen(command.c_str(), "r"), pclose_wrapper);
if (!pipe) {
syslog(LOG_ERR, "Could not open pipe! Exiting");
return EXIT_FAILURE;
}
/* Read in the pipe and save the content to a buffer */
while (::fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
commandResult += buffer.data();
}
}
syslog(LOG_DEBUG, "Command result is: %s", commandResult.c_str());
syslog(LOG_DEBUG, "Return code is: %d", rc);
return EXIT_SUCCESS;
}
将守护进程版本切换为常规版本,请将第22行的
#if
标志更改为“0”。这将禁用所有与守护进程相关的代码。我在我的主开发机上使用
g++ main.cpp -o pcloseTest -Wall -Werror
编译了这段代码,该机器运行Arch Linux x64,使用的是g++(GCC)版本11.2.0。然后,我直接从终端运行程序,并使用journalctl(journalctl -f
)监视日志输出。我还使用我的arm-cross-compile工具链编译了相同的代码,以测试错误是否可以在另一种架构上重现。我的arm-cross-compile工具链的GCC版本为7.3.0,运行程序的操作系统是32位ARM的定制版本Yocto Linux。
两者的行为相同,并产生相同的输出-在非守护进程版本中产生预期的输出,在守护进程程序中产生意外的“-1”输出。
以下是启用守护进程的嵌入式“Yocto Linux”系统的系统日志文件输出(
/var/log/message
)。May 9 15:43:08 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[10255]: Command is: ls /var/bla/; sleep 2; echo test
May 9 15:43:10 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[10255]: Command result is: test
May 9 15:43:10 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[10255]: Return code is: -1
这是禁用后的效果:
May 9 15:43:26 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[10262]: Command is: ls /var/bla/; sleep 2; echo test
May 9 15:43:28 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[10262]: Command result is: test
May 9 15:43:28 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[10262]: Return code is: 0
供比较,这是我的 x64 Linux(systemd 和 journalctl)守护进程代码:
May 09 16:06:02 aero15 MyDemoProg[76950]: Command is: ls /var/bla/; sleep 2; echo test
May 09 16:06:04 aero15 MyDemoProg[76950]: Command result is: test
May 09 16:06:04 aero15 MyDemoProg[76950]: Return code is: -1
"和普通的代码:"
May 09 16:05:34 aero15 MyDemoProg[76805]: Command is: ls /var/bla/; sleep 2; echo test
May 09 16:05:36 aero15 MyDemoProg[76805]: Command result is: test
May 09 16:05:36 aero15 MyDemoProg[76805]: Return code is: 0
如您所见,在四种情况下,命令结果都可以正确地读取为“test”,而在非守护程序中,
pclose()
的返回代码为“0”,如预期一样(表示命令成功执行),而在守护版本中为“-1”,表明pclose()
发生了一些意外情况。我试图研究一下是否有关于守护进程化进程与
popen()
或pclose()
之间的某些已知奇怪行为,但我没有找到任何具体信息。 我自己的怀疑是,可能守护进程未处理某个信号,该信号对于pclose()
的工作是必需的,或者分叉两次将进程守护化,然后再使用popen
执行管道时分叉会导致某些问题。 但我不确定哪个信号可能未被正确处理或可能丢失。供参考,我也尝试使用
waitpid()
函数实现了自己的pclose()
版本如man页面RATIONALE所建议的,但是我放弃了,因为它的行为与pclose()
类似,而且我认为潜在的问题是相同的。非常感谢您提前的任何帮助,并请让我知道是否需要进一步的信息或更多的细节。
pclose
返回-1
时,errno
的值是多少?例如,perror(“pclose”)
会输出什么信息? - Some programmer dudeauto pclose_wrapper = [&rc](FILE *cmd) { rc = pclose(cmd); if (rc < 0) { syslog(LOG_ERR, "rc is negativ - %s", strerror(errno)); } };
将errno记录到syslog中似乎可以解决问题,并返回“无子进程”或根据errno man页面的说法是“ECHILD”。我认为通过将“SIGCHLD”添加到程序处理的信号中,我解决了这个问题。非常感谢!我应该编辑原始消息以显示已解决吗? - N0x