popen和system函數的區別
在c/cpp程序中執行shell命令,通常有兩種方式,一種是使用popen函數,一種是使用system函數;兩者會調用fork函數從父進程中fork出一個子進程,然後在子進程中執行shell命令,其主要的區別如下:
- popen
popen會先fork一個子進程,然後子進程去執行shell命令,函數同時返回一個FILE指針給調用者,調用者可以根據FILE指針來獲取函數的執行結果。popen是非阻塞的,即執行後可立即返回。 - system
system會fork一個子進程,然後父進程等待子進程結束,也就是執行system函數是阻塞的,如果調用system("sleep 1")
,將會延遲1s再執行system後面的代碼。
popen打開的FILE句柄能否用close替代fclose關閉?
急的話直接看答案就行:不行,會有一些副作用
實際中遇到的問題
最近遇到一個這樣的問題:
場景:爲了完成相關功能,需要在程序調用shell命令,用到了popen;爲了支持多個命令併發,
在獲取shell命令的結果時使用reactor模式(藉助了epoll的多路複用能力,epoll監聽對應的fd是否有事件過來),以提升處理效率。其僞代碼大概如下:
//註冊信號處理函數,防止產生僵死進程
signal(SIGCHLD, SIG_IGN);
//將popen打開的文件描述符加入epoll
FILE * file = popen("shell cmd", "r"); //這邊shell cmd爲具體的shell腳本
//將FILE轉int,然後加入到epoll中
int fd = fileno(file);
//將描述符設置爲非阻塞模式
set_no_block(fd);
struct epoll_event event;
event.events = EPOLLIN | EPOLL UT;
event.data = ...; //epoll保存的data
epoll_ctl(epfd, EPOLL_CTL_ADD, fd,&event);
在epoll主循環中,
int num = epoll_wait(timeout);
for(int i = 0; i < num; i++)
{
//處理epoll事件
processEvent(...)
}
其中processEvent的大體邏輯如下:
void processEvent(struct epoll_event* event)
{
//事件處理,比如讀取結果等
//收尾工作,如刪除epoll監聽的fd,關閉文件描述符
...
close(fd); //這邊fd爲第一部分添加儘量的描述符fd,可以從event中獲取
}
功能開發完成後,自測ok,然後就放着讓他跑,看看穩定性。這時程序的cpu使用率有時會飆升,外部程序調用該服務存在間斷性的失敗。這時用top或者ps看一下進程,會發現有時候出現多個此進程的子進程;且從失敗的時間點看,週期和popen執行的週期基本能對上。由於子進程是fork出來的,popen內部調用了fork,就懷疑是popen帶來的問題。
排查程序發現可能是使用close替代pclose來關閉用popen打開的描述符導致的問題,然後改了下代碼後,發現該問題消失。
demo測試分析
爲了對比使用close和fclose關閉popen打開的描述符的差別,這邊進行了下面的實驗,測試代碼如下:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <iostream>
using namespace std;
int main()
{
signal(SIGCHLD, SIG_IGN);
while(1)
{
char szCommand[128] = {0};
snprintf(szCommand, sizeof(szCommand) - 1, "cat testfile"); //這邊testfile爲一個文本文件
FILE* fp = popen(szCommand, "r");
if(NULL == fp)
{
std::cout << "popen failed" << endl;
}
usleep(1000);
//1
int fd = fileno(fp);
close(fd);
//2
//fclose(fp);
}
return 0;
}
分別使用close和fclose,編譯出兩個testpopen文件,然後運行,過一陣後,使用close關閉的程序表現如下:
使用pclose關閉的效果如下:
通過對比,看出了差異,如果通過close來關閉popen打開的FILE描述符的話,有些負面的作用【cpu高了,且有部分內存泄露】。
查了下pclose和close的差異,pclose除了會關閉文件描述符之外,還具有pclose會調用waitpid爲popen時fork的子進程收屍,而fclose不會;另外pclose還具有在關閉文件時沖刷緩衝區的功能。