popen返回NULL

当程序写得越来越大,进程占用的内存也就越来越多,调用popen时会返回空的FILE指针,网上说原因是system或popen这样的系统函数,其内部实现是调用fork函数创建子进程,创建过程中会复制父进程堆、栈等资源,这样就容易造成创建失败,返回NULL。

以下是我写的用vfork替换fork调用的Vpopen类。

Vpopen.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#pragma once
 
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
class Vpopen
{
        FILE *fp_;
        pid_t pid_;
        int pipeFd_[2];
public:
        Vpopen();
        ~Vpopen();
        FILE *open(const char *cmd, const char *flags);
        void close();
};


Vpopen.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include "Vpopen.h"
 
Vpopen::Vpopen()
{
	fp_ = NULL;
	pid_ = -1;
	pipeFd_[0] = pipeFd_[1] = -1;
}
 
Vpopen::~Vpopen()
{
	close();
}
 
FILE *Vpopen::open(const char *cmd, const char *flags)
{
	if (pipe(pipeFd_) != 0)
		goto failOut;
 
	if ((pid_ = vfork()) < 0)
		goto failOut;
 
	if (pid_ == 0)
	{
		if (strcmp(flags, "r") == 0)
		{
			::close(pipeFd_[0]);
			if (pipeFd_[1] != STDOUT_FILENO)
			{
				dup2(pipeFd_[1], STDOUT_FILENO);
				::close(pipeFd_[1]);
			}
		}
		else if (strcmp(flags, "w") == 0)
		{
			::close(pipeFd_[1]);
			if (pipeFd_[0] != STDIN_FILENO)
			{
				dup2(pipeFd_[0], STDIN_FILENO);
				::close(pipeFd_[0]);
			}
		}
 
		execl("/bin/sh", "sh", "-c", cmd, NULL);
		_exit(127);
	}
 
	if (strcmp(flags, "r") == 0)
	{  
		::close(pipeFd_[1]);
		pipeFd_[1] = -1;
		fp_ = fdopen(pipeFd_[0], flags);
	}
	else if (strcmp(flags, "w") == 0)
	{  
		::close(pipeFd_[0]);
		pipeFd_[0] = -1;
		fp_ = fdopen(pipeFd_[1], flags);
	}
	return fp_;
 
failOut:
	close();
	return NULL;
}
 
void Vpopen::close()
{
	if (pid_ > 0)
	{
		int status;
		while (1)
		{
			if (waitpid(pid_, &status, 0) < 0)
			{
				perror("waitpid failed");
				break;
			}
			if (WIFEXITED(status))
				break;
		}
		pid_ = -1;
	}
 
	for (int i = 0; i < 2; i++)
	{
		if (pipeFd_[i] > 0)
		{
			::close(pipeFd_[i]);
			pipeFd_[i] = -1;
		}
	}
	if (NULL != fp_)
	{
		fclose(fp_);
		fp_ = NULL;
	}
}

test.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdlib.h>
#include "Vpopen.h"
 
#define USING_VPOPEN 1
 
int main()
{
	const char *cmd = "ls /etc";
#if USING_VPOPEN
	Vpopen vp;
	FILE *fp = vp.open(cmd, "r");
#else
	FILE *fp = popen(cmd, "r");
#endif
	char line[1024];
	int i = 0;
	while (NULL != fgets(line, 1023, fp))
		printf("%d: %s", i++, line);
#if USING_VPOPEN
	vp.close();
#else
	pclose(fp);
#endif
	return 1;
}

refer to:
1, bingqingsuimeng, https://www.it610.com/article/4096653.htm
2, litingli, https://blog.csdn.net/litingli/article/details/5891726