我是老吳,一個愛學(xué)習(xí)的,嵌入式 Linux 驅(qū)動工程師。關(guān)注我,一起變得更加優(yōu)秀吧!
哈嘍,我是老吳。
最近產(chǎn)品上有用到一個叫?supervisord?的開源軟件。
https://github.com/ochinchina/supervisord
supervisord?是一個進(jìn)程管理軟件,golang?寫的,?3.5K?star。
我們的產(chǎn)品上有多個服務(wù)程序要在后臺長期運(yùn)行,所以使用 supervisord 來守護(hù)并管理這些進(jìn)程。
supervisord 非常適合嵌入式 Linux 平臺。
一方面,supervisord 可以幫我們在開機(jī)時后臺啟動多個服務(wù)程序,另一方面,當(dāng)有服務(wù)異常退出時,supervisord 也能幫我們重新將服務(wù)拉起來,從而保證產(chǎn)品能長期正常工作。
supervisord 的代碼非常值得我們閱讀學(xué)習(xí),不過今天不研究它。
我們來研究另外一個核心功能跟它類似,但是代碼更加簡單的開源軟件:pman。
https://github.com/matsune/pman
pman 也是一個進(jìn)程管理軟件,不過它是 C++ 寫的,代碼僅 1420 行,麻雀雖小,五臟俱全,功能上也完全滿足我們產(chǎn)品的需求。
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
這個配置文件指定了一個需要運(yùn)行的程序:ls,并且指定了它的標(biāo)準(zhǔn)輸出輸出,以及是否要自動重啟等屬性。
運(yùn)行效果:
$?./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
內(nèi)部實現(xiàn)
pman?源碼一共才?1420?行,非常適合我們用來學(xué)習(xí)進(jìn)程管理類工具的實現(xiàn)原理。
$?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)?{
????????//?守護(hù)模式,并啟動?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 自己本身就是一個守護(hù)服務(wù),當(dāng)沒有傳遞 status、stop、start 等參數(shù)時,pman 會以守護(hù)進(jìn)程的方式運(yùn)行:
int?runServer(...)
{
????//?將自己編程守護(hù)進(jìn)程
????Daemon?daemon(pmanConf,?programConfs);
????daemon.setup();
????//?創(chuàng)建?server?線程
????thread(rungRPCServer,?pmanConf.port(),?ref(daemon)).detach();
????//?啟動配置文件中指定的所有程序
????return?daemon.runLoop();
}
server 線程會建立一個 RPCServer,使用到了 Google 的 gRPC 組件:
https://github.com/grpc/grpc
簡單來說,就是為了實現(xiàn) status/stop/start 等命令以 RPC client 的方式去調(diào)用到 pman 守護(hù)進(jìn)程內(nèi)的函數(shù) API,這里就不詳細(xì)展開了。
接著看 runLoop()
int?Daemon::runLoop()
{
????//?啟動所有的用戶程序
????for?(auto?program?=?this->programs_.begin();?program?!=?this->programs_.end();?++program)?{
????????if?(program->autostart())?startProgram(*program);
????}
????//?循環(huán)處理用戶的命令行?的操作
????while?(!abrt_status)?{
????????//?取出用戶指令
????????while?(!tasks_.empty())?{
????????????Task?task?=?tasks_.front();
????????????//?Start?/?Stop?程序
????????}
????}
}
上面的代碼只是 pman 的主邏輯,其他很多功能,例如命令行參數(shù)的解析、配置文件的解析、程序的管理等功能,都被良好地封裝起來了,所以我們看起來才會這么清晰明了。
總結(jié)
對于嵌入式?Linux?的產(chǎn)品,如果需要長期守護(hù)住應(yīng)用程序的運(yùn)行,可以考慮部署?supervisord?或者?pman?等進(jìn)程管理工具。
建議優(yōu)先考慮考慮 Go 版本 supervisord,一方面是因為 Go 應(yīng)用部署非常簡單,只要拷貝一個可執(zhí)行文件即可,另一方面也是因為 supervisord 的用戶更多、活躍度更高,功能更完善。
如果你開發(fā)的產(chǎn)品上沒有 Go 環(huán)境,性能也有限,類似路由器 openwrt 系統(tǒng),那就考慮采用 pman,對其加以定制以滿足最終的需求。
最后,這兩個開源工具的代碼可讀性都非常的好,很適合用來鍛煉和提升?Go?/?C++?的編程能力,喜歡研究技術(shù)的小伙伴們,可以品讀一下源碼,肯定會有所收獲。