现在,我尝试理解子进程的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/

10-11 06:22