gpt4 book ai didi

c++ - linux:fork/socketpair/close 和多个子进程

转载 作者:太空宇宙 更新时间:2023-11-04 03:45:45 26 4
gpt4 key购买 nike

现在我尝试理解子进程的stdin/out/err的 fork /重新绑定(bind),并在不泄漏任何资源的情况下正确管理资源(文件句柄、套接字)。

还有一些问题:创建套接字对和分支后,我在父级和子级中拥有 5 个文件描述符 (stdin/out/err/socket1/socket2)。在子进程中,我需要关闭套接字对的“父”端。我在 fork 后 close() stdin/out/errdup() 套接字的“客户端”三次。在dup()之后,我需要关闭dup的“源”吗?我想是的......但我是对的吗?

当我以这种方式(见下文)创建第二个子项时,资源处理正确吗?我试图严重依赖 RAII 来不泄漏任何 fd,但是这样吗?我错过了一件大事吗?

再见,提前致谢!

乔治

编辑:我修复了 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 。这样,我可以确定内核关闭了所有文件描述符。由我的代码处理的所有其他套接字将通过 fdhandle 类调用 close() 来关闭。重新绑定(bind)stdin/stdout/stderr,我替换了dup()对于 dup2()因为它确实以原子方式关闭和重复。

提示是这个页面: http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html

File descriptors open in the calling process image shall remain open in the new process image, except for those whose close-on-exec flag FD_CLOEXEC is set. For those file descriptors that remain open, all attributes of the open file description remain unchanged. For any file descriptor that is closed for this reason, file locks are removed as a result of the close as described in close(). Locks that are not removed by closing of file descriptors remain unchanged.

这是我调整后的代码:

编辑:调整结构

#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/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com