popen和system函數的區別 以及 popen打開的FILE指針能否用close替代fclose關閉

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關閉的程序表現如下:
使用close關閉的效果

使用pclose關閉的效果如下:
使用pclose關閉的效果
通過對比,看出了差異,如果通過close來關閉popen打開的FILE描述符的話,有些負面的作用【cpu高了,且有部分內存泄露】。

查了下pclose和close的差異,pclose除了會關閉文件描述符之外,還具有pclose會調用waitpid爲popen時fork的子進程收屍,而fclose不會;另外pclose還具有在關閉文件時沖刷緩衝區的功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章