我是老吴,一个爱学习的,嵌入式 Linux 驱动工程师。关注我,一起变得更加优秀吧!
哈喽,我是老吴。
最近产品上有用到一个叫 supervisord 的开源软件。
https://github.com/ochinchina/supervisord
supervisord 是一个进程管理软件,golang 写的, 3.5K star。
我们的产品上有多个服务程序要在后台长期运行,所以使用 supervisord 来守护并管理这些进程。
supervisord 非常适合嵌入式 Linux 平台。
一方面,supervisord 可以帮我们在开机时后台启动多个服务程序,另一方面,当有服务异常退出时,supervisord 也能帮我们重新将服务拉起来,从而保证产品能长期正常工作。
supervisord 的代码非常值得我们阅读学习,不过今天不研究它。
我们来研究另外一个核心功能跟它类似,但是代码更加简单的开源软件:pman。
https://github.com/matsune/pman
pman 也是一个进程管理软件,不过它是 C++ 写的,代码仅 1420 行,麻雀虽小,五脏俱全,功能上也完全满足我们产品的需求。
pman 的用法
举个例子:
$ cat /etc/pman.conf
[pman]
pidfile=/tmp/pman.pid ; pman daemon's pidfile
logfile=/tmp/pman.log ; pman daemon's logfile
port=127.0.0.1:50010 ; gRPC server port
directory=/tmp ; default is current dir
[program:ls]
command=/bin/ls ; program command
stdout=/tmp/sample_stdout.log ; program stdout logfile (default: /tmp/${program name}_stdout.log)
stderr=/tmp/sample_stderr.log ; program stderr logfile (default: /tmp/${program name}_stderr.log)
autorestart=true ; automatically restart if exited unexpectedly
autostart=true ; start program on daemon's startup
这个配置文件指定了一个需要运行的程序:ls,并且指定了它的标准输出输出,以及是否要自动重启等属性。
运行效果:
$ ./pman status -c pman.conf
[sleep] RUNNING pid: 15283 uptime: 00:00:04
$ ./pman stop sleep -c pman.conf
[sleep] STOPPING
$ ./pman status -c pman.conf
[sleep] STOPPING
$ ./pman start sleep -c pman.conf
[sleep] RUNNING pid: 15370 uptime: 00:00:00
内部实现
pman 源码一共才 1420 行,非常适合我们用来学习进程管理类工具的实现原理。
$ wc -l *
222 cmd_parser.cpp
45 cmd_parser.hpp
32 conf.cpp
47 conf.hpp
80 conf_parser.cpp
23 conf_parser.hpp
222 daemon.cpp
41 daemon.hpp
29 defines.h
126 main.cpp
56 pid_file.cpp
15 pid_file.hpp
88 pman_client.cpp
21 pman_client.hpp
188 pman_service_impl.cpp
39 pman_service_impl.hpp
35 program.cpp
27 program.hpp
21 task.hpp
54 util.cpp
9 util.hpp
1420 total
简单过一下源码,抓主干:
int main()
{
[...]
if (cmdParser.command() == DAEMON) {
// 守护模式,并启动 server
return runServer(confParser.pmanConf(), confParser.programConfs());
} else if (cmdParser.command() == KILL) {
return killServer(confParser.pmanConf();
} else {
// client 模式,会去访问 server
return runClient(confParser.pmanConf().port(), cmdParser.command(), cmdParser.program());
}
}
pman 自己本身就是一个守护服务,当没有传递 status、stop、start 等参数时,pman 会以守护进程的方式运行:
int runServer(...)
{
// 将自己编程守护进程
Daemon daemon(pmanConf, programConfs);
daemon.setup();
// 创建 server 线程
thread(rungRPCServer, pmanConf.port(), ref(daemon)).detach();
// 启动配置文件中指定的所有程序
return daemon.runLoop();
}
server 线程会建立一个 RPCServer,使用到了 Google 的 gRPC 组件:
https://github.com/grpc/grpc
简单来说,就是为了实现 status/stop/start 等命令以 RPC client 的方式去调用到 pman 守护进程内的函数 API,这里就不详细展开了。
接着看 runLoop()
int Daemon::runLoop()
{
// 启动所有的用户程序
for (auto program = this->programs_.begin(); program != this->programs_.end(); ++program) {
if (program->autostart()) startProgram(*program);
}
// 循环处理用户的命令行 的操作
while (!abrt_status) {
// 取出用户指令
while (!tasks_.empty()) {
Task task = tasks_.front();
// Start / Stop 程序
}
}
}
上面的代码只是 pman 的主逻辑,其他很多功能,例如命令行参数的解析、配置文件的解析、程序的管理等功能,都被良好地封装起来了,所以我们看起来才会这么清晰明了。
总结
对于嵌入式 Linux 的产品,如果需要长期守护住应用程序的运行,可以考虑部署 supervisord 或者 pman 等进程管理工具。
建议优先考虑考虑 Go 版本 supervisord,一方面是因为 Go 应用部署非常简单,只要拷贝一个可执行文件即可,另一方面也是因为 supervisord 的用户更多、活跃度更高,功能更完善。
如果你开发的产品上没有 Go 环境,性能也有限,类似路由器 openwrt 系统,那就考虑采用 pman,对其加以定制以满足最终的需求。
最后,这两个开源工具的代码可读性都非常的好,很适合用来锻炼和提升 Go / C++ 的编程能力,喜欢研究技术的小伙伴们,可以品读一下源码,肯定会有所收获。