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关闭的程序表现如下:
使用pclose关闭的效果如下:
通过对比,看出了差异,如果通过close来关闭popen打开的FILE描述符的话,有些负面的作用【cpu高了,且有部分内存泄露】。
查了下pclose和close的差异,pclose除了会关闭文件描述符之外,还具有pclose会调用waitpid为popen时fork的子进程收尸,而fclose不会;另外pclose还具有在关闭文件时冲刷缓冲区的功能。
将若垂天之云: C/C++插件之前好像整理插件的时候不小心卸载了,很难想象自己居然被这么傻杯的问题困扰了2天。十分感谢答主
CSDN-Ada助手: CS入门 技能树或许可以帮到你:https://edu.csdn.net/skill/gml?utm_source=AI_act_gml
2401_83010091: 哥,打不开了,请问可以再分享一下这本书么?祝您2024新年快乐,万事胜意🌹 [格林斯潘回忆录.动荡年代].Alan.Greenspan.-.The.Age.of.Turbulence.pdf
AtItAgain: 感谢,禁用再启用就好了
ViodMian: 确实有用,点赞