1. 背景介绍
Unix/Linux环境下在C/C++中调用Native脚本命令通常使用system或者popen,二者都是先fork子进程然后execl加载代码镜像开始执行。system相比较popen较为简单,但是可能会出现"no child process"
的情况,即调用的父进程没有等待子进程返回就退出了,通常的办法是使用system前置换信号量,如调用pox_system函数:
typedef void (*sighandler_t)(int);
static int pox_system(const char* cmd_line)
{
int ret = 0;
sighandler_t old_handler = std::signal(SIGCHLD, SIG_DFL);
ret = std::system(cmd_line);
signal(SIGCHLD, old_handler);
return ret;
}
这种方式的处理在单线程工作的系统中通常OK;但是如果使用多线程并发,仍然会出现"no child process"
的情况,例如在执行比较重的任务时,A线程pox_system置换了信号后system出子进程开始执行任务,恰巧此时B线程置换信号,等到A线程恢复原信号后,B线程才system产生进程,这种情况下执行环境正是A执行pox_system之前的状态,如果此时是SIG_IGN,那就瞎了...总结一下,如果pox_system的操作不是原子的,那就会概率的出现"no child process"
的情况。如下图所示,先后两个pox_system调用A和B(在切换signal前后分别设为A0、A1和B0、B1):
那有什么好办法吗?加锁互斥?不嫌麻烦么?用popen吧!
2. 使用单例模式对popen
工具封装
头文件声明
/* popen_util.h
#ifndef _POPEN_UTIL_H__
#define _POPEN_UTIL_H__
#define POPEN_OPEN_FAILED (-1)
#define POPEN_FORK_FAILED (-2)
#define POPEN_EXEC_CMD_FAILED (-3)
#define MAXBUFFSIZE 1024
#include <string>
class PopenUtil
{
public:
static PopenUtil& GetInstance();
static int DoPopen(const std::string &cmd, std::string &result);
static int DoPopenSpecial(const std::string &cmd, std::string &result);
private:
PopenUtil();
~PopenUtil();
};
#endif
源文件定义
#include <cstdio>
#include "popen_util.h"
PopenUtil::PopenUtil()
{
}
PopenUtil::~PopenUtil()
{
}
PopenUtil& PopenUtil::GetInstance()
{
static PopenUtil popenUtil;
return popenUtil;
}
int PopenUtil::DoPopen(const std::string &cmd, std::string &result)
{
FILE* fd = popen(cmd.c_str(), "r");
if(fd == NULL)
{
return POPEN_OPEN_FAILED;
}
char buf[MAXBUFFSIZE]={'\0'};
int ret = fread(buf, sizeof(buf), 1, fd);
if(ret >= 0)
{
result = buf;
}
if (0 != pclose(fd))
{
result += "pclose failed";
}
return ret >= 0 ? 0 : -1;
}
int PopenUtil::DoPopenSpecial(const std::string &cmd, std::string &result)
{
std::string appendStr = cmd + std::string("; echo \"Ret code:\"$?");
FILE* fd = popen(appendStr.c_str(), "r");
if(fd == NULL)
{
return POPEN_FORK_FAILED;
}
char buf[MAXBUFFSIZE]={'\0'};
while(fgets(buf, MAXBUFFSIZE, fd) != NULL)
{
result += std::string(buf);
}
if (0 != pclose(fd))
{
result += "pclose failed";
}
int ret = 0;
if (std::string::npos == result.find("Ret code:0"))
{
ret = POPEN_EXEC_CMD_FAILED;
}
return ret;
}
使用示例
#include "popen_util.h"
#include <iostream>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <cerrno>
int main(int argc, char *argv[])
{
if (argc == 1)
{
std::cout << "usage: " << argv[0] << " comands ..." << std::endl;
return -1;
}
std::string cmd;
for (int i = 1; i < argc; i++)
{
cmd += argv[i];
if (i != argc - 1)
{
cmd += " ";
}
}
std::string result;
int status = PopenUtil::GetInstance().DoPopenSpecial(cmd, result);
if (POPEN_OPEN_FAILED == status || POPEN_FORK_FAILED == status || POPEN_EXEC_CMD_FAILED == status)
{
perror("popen failed");
}
std::string output = "popen execute command: " + cmd +", "
+ "error: " + strerror(errno) + ", "
+ "result: " + result;
std::cout << std::endl << "--> " << output << std::endl << std::endl;
return status;
}