现在,我尝试理解子进程的stdin / out / err的派生/重新绑定,并正确地管理资源(文件句柄,套接字),而不会泄漏任何资源。
还有一些问题:
创建套接字对和派生后,在父级5个文件描述符和子级(stdin / out / err / socket1 / socket2)中。在子进程中,我需要关闭套接字对的“父”侧。在fork和dup()套接字的“客户端”之后,我关闭了三次stdin / out / err。 dup()之后,是否需要关闭dup的“源”?我想是的...但是我是对的吗?
当我以这种方式创建(参见下文)第二个孩子时,资源处理正确吗?我试图严重依赖RAII来不泄漏任何fds,但这是对的吗?我想念一件大事吗?
再见,谢谢!
格奥尔格
编辑:我修复了rebind_and_exec_child中的错误。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory>
#include <cassert>
// this handle takes a fd, behaves like an int and makes sure the fd is closed again
class fdhandle {
public:
explicit fdhandle(int fd) {
mp_fd = std::shared_ptr<int>(new int, [=](int* pfd) {
close(*pfd);
delete pfd;
});
assert(mp_fd);
*mp_fd = fd;
}
operator int() {
assert(mp_fd);
return *mp_fd;
}
private:
std::shared_ptr<int> mp_fd;
};
void rebind_and_exec_child(fdhandle fd, std::string exe) {
// now close the std fds and connect them to the given fd
close(0); close(1); close(2);
// dup the fd three times and recreate stdin/stdout/stderr with fd as the target
if (dup(fd) != 0 || dup(fd) != 1 || dup(fd) != 2) {
perror("error duplicating socket for stdin/stdout/stderr");
exit(EXIT_FAILURE);
}
// now we can exec the new sub process and talk to it through
// stdin/stdout/stderr
char *arguments[4] = { exe.c_str(), exe.c_str(), "/usr/bin", NULL };
execv(exe.c_str(), arguments);
// this could should never be reached
perror("error: executing the binary");
exit(EXIT_FAILURE);
}
fdhandle fork_connected_child(std::string exe) {
// create the socketpair
int fd[2];
if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)) {
perror("error, could not create socket pair");
exit(EXIT_FAILURE);
}
fdhandle fdparent(fd[0]); fdhandle fdchild(fd[1]);
// now create the child
pid_t pid = fork();
switch (pid) {
case -1: // could not fork
perror("error forking the child");
exit(EXIT_FAILURE);
break;
case 0: // child
rebind_and_exec_child(fdchild);
break;
default: // parent
return fdparent;
break;
}
}
int main(int argc, const char** argv) {
// create 2 childs
fdhandle fdparent1 = fork_connected_child("/bin/ls");
fdhandle fdparent2 = fork_connected_child("/bin/ls");
}
最佳答案
我想,我找到了解决方案。对于在socketpair()
调用中创建的每个套接字,我设置FD_CLOEXEC
。这样,我可以确定内核会关闭所有文件描述符。我的代码处理的所有其他套接字将通过对close()
的fdhandle类调用来关闭。重新绑定stdin / stdout / stderr后,我将dup()
替换为dup2()
,因为它确实关闭并自动复制了。
提示是此页面:
http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
在调用过程映像中打开的文件描述符应保持打开状态
在新的过程映像中,但那些具有close-on-exec标志的
设置为FD_CLOEXEC
。对于那些保持打开状态的文件描述符,所有
打开文件描述的属性保持不变。对于任何文件
由于这个原因而关闭的描述符,文件锁作为
关闭的结果,如close()
中所述。未取下的锁
通过关闭文件描述符保持不变。
现在是我调整后的代码:
编辑:调整后的结构
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <memory>
#include <cassert>
#include <iostream>
// this handle takes a fd, behaves like an int and makes sure the fd is closed again
class fdhandle {
public:
fdhandle() {}
explicit fdhandle(int fd) {
mp_fd = std::shared_ptr<int>(new int, [=](int* pfd) {
close(*pfd);
delete pfd;
});
assert(mp_fd);
*mp_fd = fd;
// set FD_CLOEXEC on fd
int flags;
flags = fcntl(fd, F_GETFD);
if (-1 == flags) {
perror("error, could not get flags from filedescriptor");
exit(EXIT_FAILURE);
}
flags |= FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) == -1) {
perror("error, could not set FD_CLOEXEC");
exit(EXIT_FAILURE);
}
}
operator int() {
assert(mp_fd);
return *mp_fd;
}
void show_fd_status() {
if (!mp_fd)
return;
int fd = *mp_fd;
using namespace std;
char buf[256];
int fd_flags = fcntl(fd, F_GETFD);
if (fd_flags == -1)
return;
int fl_flags = fcntl(fd, F_GETFL);
if (fl_flags == -1)
return;
char path[256];
sprintf(path, "/proc/self/fd/%d", fd);
memset(&buf[0], 0, 256);
ssize_t s = readlink(path, &buf[0], 256);
if (s == -1) {
cerr << " (" << path << "): " << "not available";
return;
}
cerr << fd << " (" << buf << "): ";
// file status
if (fd_flags & FD_CLOEXEC) cerr << "cloexec ";
if (fl_flags & O_APPEND) cerr << "append ";
if (fl_flags & O_NONBLOCK) cerr << "nonblock ";
// acc mode
if (fl_flags & O_RDONLY) cerr << "read-only ";
if (fl_flags & O_RDWR) cerr << "read-write ";
if (fl_flags & O_WRONLY) cerr << "write-only ";
if (fl_flags & O_DSYNC) cerr << "dsync ";
if (fl_flags & O_RSYNC) cerr << "rsync ";
if (fl_flags & O_SYNC) cerr << "sync ";
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_whence = 0;
fl.l_start = 0;
fl.l_len = 0;
fcntl(fd, F_GETLK, &fl);
if (fl.l_type != F_UNLCK)
{
if (fl.l_type == F_WRLCK)
cerr << "write-locked";
else
cerr << "read-locked";
cerr << "(pid:" << fl.l_pid << ") ";
}
}
private:
std::shared_ptr<int> mp_fd;
};
struct child
{
pid_t pid;
fdhandle fd;
};
void rebind_and_exec_child(fdhandle fd, std::string exe) {
// unset FD_CLOEXEC
int flags, oflags;
flags = oflags = fcntl(fd, F_GETFD);
if (-1 == flags) {
perror("error, could not get flags from filedescriptor");
exit(EXIT_FAILURE);
}
flags &= ~FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) == -1) {
perror("error, could not unset FD_CLOEXEC");
exit(EXIT_FAILURE);
}
// close and rebind the stdin/stdout/stderr
// dup the fd three times and recreate stdin/stdout/stderr with fd as the target
if (dup2(fd, STDIN_FILENO) != 0 || dup2(fd, STDOUT_FILENO) != 1 || dup2(fd, STDERR_FILENO) != 2) {
perror("error duplicating socket for stdin/stdout/stderr");
exit(EXIT_FAILURE);
}
// restore the old flags
if (fcntl(fd, F_SETFD, oflags) == -1) {
perror("error, could not set FD_CLOEXEC");
exit(EXIT_FAILURE);
}
// now we can exec the new sub process and talk to it through
// stdin/stdout/stderr
char path[256];
char argv[256];
sprintf(path,"%s",exe.c_str());
sprintf(argv,"%d",30);
execlp(path, path, argv, 0);
// this should never be reached
perror("error: executing the binary");
exit(EXIT_FAILURE);
}
child fork_connected_child(std::string exe) {
// create the socketpair
int fd[2];
if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)) {
perror("error, could not create socket pair");
exit(EXIT_FAILURE);
}
fdhandle fdparent(fd[0]); fdhandle fdchild(fd[1]);
// now create the child
pid_t pid = fork();
switch (pid) {
case -1: // could not fork
perror("error forking the child");
exit(EXIT_FAILURE);
break;
case 0: // child
rebind_and_exec_child(fdchild, exe);
break;
default: // parent
std::cout << "forked " << exe << std::endl;
return child { pid, fdparent };
break;
}
}
int main(int argc, const char** argv) {
// setup the signal handler prior to forking
sleep(20);
// create 2 childs
{
child child1 = fork_connected_child("/usr/bin/sleep");
child child2 = fork_connected_child("/usr/bin/sleep");
int status;
waitpid(child1.pid, &status, 0);
waitpid(child2.pid, &status, 0);
}
sleep(20);
}
关于c++ - linux:fork/socketpair/close和多个子进程,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28294010/