概述
Boost.Process提供了一个灵活的C++ 进程管理框架。它允许C++ developer可以像Java和.Net程序developer那样管理进程。它还提供了管理当前执行进程上下文、创建子进程、用C++ 流和异步I/O进行通信的能力。
该库以完全透明的方式将所有进程管理的抽象细节呈现给使用者,且该库是跨平台的。
特点
进程管理
Boost.Process的长期目标是提供一个抽象于操作系统之上的,可以管理任何运行的进程的框架。由于提供这样的API比较困难,所以现在只专注于管理。Boost.Process的最重要的特征就是启动一个外部应用、控制它们并与它们交互。传统上讲,对于C和C++ 来说,就比较困难了,因为它们要启动新进程、执行外部程序、建立匿名管道来交互、等待进程结束、检查进程退出码等。更糟糕的是不同操作系统,相关的进程模块和API是不同的。所以,Boost.Process的出现就提供了便利条件。
输入输出重定向
一般来说一个应用启动了子进程,它们可能会通过传输数据来交流。这种进程间通信是文件句柄层面的,通常涉及stdin、stdout、stderr。如果操作系统支持,那么就需要可重定向的流。不过这对C++ 来说是很容易的。
不同操作模式
支持同步、异步、分离
管道管理
这样就可以实现一个进程的输出可以作为另一个进程的输入。
库的设计图
最重要的类就是Context和Process。Context提供了进程运行的上下文。pistream和postream是为了交互。父进程还可以等待子进程退出,并检查进程退出码。如果有例如包含管道的shell命令要执行,那么pipeline_entry就应运而生了,它可以实现前一个子进程的输出是下一个子进程的输入。
使用步骤
1、创建上下文Context
2、创建子进程,获得子进程对象
3、如果有重定向,可以访问到stdin、stdout、stderr
4、进程结束,检查进程退出码
教程
一个最简单的例子
#include <boost/filesystem.hpp> #include <boost/process.hpp> #include <string> #include <vector> namespace bp = ::boost::process;
bp::child start_child() { std::string exec = "bjam" ;
std::vector<std::string> args;
args.push_back( "bjam" );
args.push_back( "--version" );
bp::context ctx;
ctx.stdout_behavior = bp::capture_stream();
return bp::launch(exec, args, ctx);
} int main()
{ bp::child c = start_child();
bp::pistream &is = c.get_stdout();
std::string line;
while (std::getline(is, line))
std::cout << line << std::endl;
bp::status s = c.wait();
return s.exited() ? s.exit_status() : EXIT_FAILURE;
} |
下面再看一个异步的例子
#include <boost/filesystem.hpp> #include <boost/asio.hpp> #include <boost/process.hpp> #include <boost/array.hpp> #include <boost/bind.hpp> #include <string> #include <vector> #include <iostream> namespace bp = ::boost::process;
namespace ba = ::boost::asio;
ba::io_service io_service; boost::array< char , 4096> buffer;
ba::posix::stream_descriptor in(io_service); bp::child start_child() { std::string exec = "bjam" ;
std::vector<std::string> args;
args.push_back( "bjam" );
args.push_back( "--version" );
bp::context ctx;
ctx.stdout_behavior = bp::capture_stream();
ctx.environment = bp::self::get_environment();
return bp::launch(exec, args, ctx);
} void end_read( const boost:: system ::error_code &ec, std:: size_t bytes_transferred);
void begin_read()
{ in.async_read_some(boost::asio::buffer(buffer),
boost::bind(&end_read, ba::placeholders::error, ba::placeholders::bytes_transferred));
} void end_read( const boost:: system ::error_code &ec, std:: size_t bytes_transferred)
{ if (!ec)
{
std::cout << std::string(buffer.data(), bytes_transferred) << std::flush;
begin_read();
}
} int main()
{ bp::child c = start_child();
bp::pistream &is = c.get_stdout();
in.assign(is.handle().release());
begin_read();
io_service.run();
c.wait();
} |
这个例子中用到了asio库,涉及到许多回调函数。关于异步(asio)暂时不做讲解,写这个例子是为了展示该库的异步功能。对异步感兴趣的同学可以看一下《Boost.Asio C++ Network Programming》
部分文件和类
stream_behaviour.hpp文件
对于流的描述,可分为六种类型
序号 | 流描述 | 含义 |
---|---|---|
1 | capture | 父子进程之间通过无名管道相互接收数据 |
2 | close | 启动时关闭 |
3 | inherit | 父子进程共用一个,也即继承 |
4 | redirect_to_stdout | 主要用在stderr时,重定向到stdout |
5 | silence | 输出重定向到/dev/null |
6 | posix_redirect | 将输出重定向到指定的文件描符,是对redirect_to_stdout的扩展 |
以下是等价的
boost::process::child::get_stdin() <==> boost::process::posix_child::get_input(STDIN_FILENO)
boost::process::child::get_stdout() <==> boost::process::posix_child::get_output(STDOUT_FILENO)
boost::process::child::get_stderr() <==> boost::process::posix_child::get_output(STDERR_FILENO)
#include <boost/process.hpp> #include <boost/filesystem.hpp> #include <string> #include <vector> #include <iostream> #include <cstdlib> #include <unistd.h> namespace bp = ::boost::process;
bp::posix_child start_child() { std::string exec = bp::find_executable_in_path( "dbus-daemon" );
std::vector<std::string> args;
args.push_back( "dbus-daemon" );
args.push_back( "--fork" );
args.push_back( "--session" );
args.push_back( "--print-address=3" );
args.push_back( "--print-pid=4" );
bp::posix_context ctx;
ctx.output_behavior.insert(bp::behavior_map::value_type(STDOUT_FILENO, bp::inherit_stream()));
ctx.output_behavior.insert(bp::behavior_map::value_type(STDERR_FILENO, bp::inherit_stream()));
ctx.output_behavior.insert(bp::behavior_map::value_type(3, bp::capture_stream()));
ctx.output_behavior.insert(bp::behavior_map::value_type(4, bp::capture_stream()));
return bp::posix_launch(exec, args, ctx);
} int main()
{ try
{
bp::posix_child c = start_child();
std::string address;
pid_t pid;
c.get_output(3) >> address;
c.get_output(4) >> pid;
bp::status s = c.wait();
if (s.exited())
{
if (s.exit_status() == EXIT_SUCCESS)
{
std::cout << "D-BUS daemon's address is: " << address << std::endl;
std::cout << "D-BUS daemon's PID is: " << pid << std::endl;
}
else
std::cout << "D-BUS daemon returned error condition: " << s.exit_status() << std::endl;
}
else
std::cout << "D-BUS daemon terminated abnormally" << std::endl;
return s.exited() ? s.exit_status() : EXIT_FAILURE;
}
catch (boost::filesystem::filesystem_error &ex)
{
std::cout << ex.what() << std::endl;
return EXIT_FAILURE;
}
} |
boost::process::context类
template < class Path>
class basic_context : public basic_work_directory_context<Path>, public environment_context
{ public :
/**
* Child's stdin behavior.
*/
stream_behavior stdin_behavior;
/**
* Child's stdout behavior.
*/
stream_behavior stdout_behavior;
/**
* Child's stderr behavior.
*/
stream_behavior stderr_behavior;
}; typedef basic_context<std::string> context;
|
而basic_work_directory_context是用来设置工作目录的;environment_context实质上是个包装了boost::process::environment的类,boost::process::environment是一个map<string, string>,用以保存环境变量。
boost::process::posix_context类
typedef std::map< int , stream_behavior> behavior_map;
template < class Path>
class posix_basic_context : public basic_work_directory_context<Path>, public environment_context
{ public :
/**
* Constructs a new POSIX-specific context.
*
* Constructs a new context. It is configured as follows:
* * All communcation channels with the child process are closed.
* * There are no channel mergings.
* * The initial work directory of the child processes is set to the
* current working directory.
* * The environment variables table is empty.
* * The credentials are the same as those of the current process.
*/
posix_basic_context()
: uid(::getuid()),
euid(::geteuid()),
gid(::getgid()),
egid(::getegid())
{
}
/**
* List of input streams that will be redirected.
*/
behavior_map input_behavior;
/**
* List of output streams that will be redirected.
*/
behavior_map output_behavior;
/**
* The user credentials.
*
* UID that specifies the user credentials to use to run the %child
* process. Defaults to the current UID.
*/
uid_t uid;
/**
* The effective user credentials.
*
* EUID that specifies the effective user credentials to use to run
* the %child process. Defaults to the current EUID.
*/
uid_t euid;
/**
* The group credentials.
*
* GID that specifies the group credentials to use to run the %child
* process. Defaults to the current GID.
*/
gid_t gid;
/**
* The effective group credentials.
*
* EGID that specifies the effective group credentials to use to run
* the %child process. Defaults to the current EGID.
*/
gid_t egid;
/**
* The chroot directory, if any.
*
* Specifies the directory in which the %child process is chrooted
* before execution. Empty if this feature is not desired.
*/
Path chroot;
}; /** * Default instantiation of posix_basic_context.
*/
typedef posix_basic_context<std::string> posix_context;
|
函数boost::process::self::get_environment()可以得到当前进程的环境变量。
我们可以对环境变量进行修改,如
boost::process::environment_context env;
env.insert(boost::process::environment::valuetype(“A”, “a”));
进程结束码类信息
class status
{ friend class child;
public :
/**
* 进程是否正常退出
*/
bool exited() const ;
/**
* 进程返回值
*/
int exit_status() const ;
protected :
status( int flags);
...
}; class posix_status : public status
{ public :
posix_status( const status &s);
/**
* 进程是否因为信号终止
*/
bool signaled() const ;
/**
* 如果因为信号终止,那么是因为哪个信号终止的
*/
int term_signal() const ;
/**
* 是否core dump了
*/
bool dumped_core() const ;
/**
* 进程是否因为收到信号停止
*/
bool stopped() const ;
/**
* 如果进程因为收到信号停止,那么信号是哪个
*/
int stop_signal() const ;
} |
进程类对象信息
class process
{ public :
typedef pid_t id_type;
process(id_type id);
/**
* Returns the process' identifier.
*/
id_type get_id() const ;
/**
* 强制终止一个进程,force为真则用SIGKILL杀死,否则用SIGTERM杀死
*/
void terminate( bool force = false ) const ;
private :
...
}; class child : public process
{ public :
/**
* 获得标准输出
*/
postream &get_stdin() const ;
/**
* 获得标准输入
*/
pistream &get_stdout() const ;
/**
* 获得标准错误输入
*/
pistream &get_stderr() const ;
/**
* 阻塞等待进程退出,返回状态码对象
*/
status wait();
/**
* 创建一个子进程对象
*/
child(id_type id, detail::file_handle fhstdin, detail::file_handle fhstdout, detail::file_handle fhstderr, detail::file_handle fhprocess = detail::file_handle());
private :
...
}; class posix_child : public child
{ public :
/**
* 从指定描述符获得一个输出流
*/
postream &get_input( int desc) const ;
/**
* 从指定描述符获得一个输入流
*/
pistream &get_output( int desc) const ;
/**
*构造函数
*/
posix_child(id_type id, detail::info_map &infoin, detail::info_map &infoout);
private :
...
}; |
children类
children类实际上std::vector<child>。children的启动方式是一个输出流被链接到下一个子进程的输入流上。
#include <boost/process.hpp> #include <string> #include <vector> #include <iostream> #include <fstream> #include <cstdlib> namespace bp = ::boost::process;
bp::children start_children() { bp::context ctxin;
ctxin.stdin_behavior = bp::capture_stream();
bp::context ctxout;
ctxout.stdout_behavior = bp::inherit_stream();
ctxout.stderr_behavior = bp::redirect_stream_to_stdout();
std::string exec1 = bp::find_executable_in_path( "cut" );
std::vector<std::string> args1;
args1.push_back( "cut" );
args1.push_back( "-d " );
args1.push_back( "-f2-5" );
std::string exec2 = bp::find_executable_in_path( "sed" );
std::vector<std::string> args2;
args2.push_back( "sed" );
args2.push_back( "s,^,line: >>>," );
std::string exec3 = bp::find_executable_in_path( "sed" );
std::vector<std::string> args3;
args3.push_back( "sed" );
args3.push_back( "s,$,<<<," );
std::vector<bp::pipeline_entry> entries;
entries.push_back(bp::pipeline_entry(exec1, args1, ctxin));
entries.push_back(bp::pipeline_entry(exec2, args2, ctxout));
entries.push_back(bp::pipeline_entry(exec3, args3, ctxout));
return bp::launch_pipeline(entries);
} int main( int argc, char *argv[])
{ try
{
if (argc < 2)
{
std::cerr << "Please specify a file name" << std::endl;
return EXIT_FAILURE;
}
std::ifstream file(argv[1]);
if (!file)
{
std::cerr << "Cannot open file" << std::endl;
return EXIT_FAILURE;
}
bp::children cs = start_children();
bp::postream &os = cs.front().get_stdin();
std::string line;
while (std::getline(file, line))
os << line << std::endl;
os.close();
bp::status s = bp::wait_children(cs);
return s.exited() ? s.exit_status() : EXIT_FAILURE;
}
catch (boost::filesystem::filesystem_error &ex)
{
std::cout << ex.what() << std::endl;
return EXIT_FAILURE;
}
} |
需要注意的是,wait_children出错时,返回第一个子进程的退出码,所有子进程都正常退出时,返回最后一个子进程的退出码。
master3中大量用到进程管理这个库。这个Boost.Process库可以在这里获得点这里。