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

时间:2015-02-03 08:02:47

标签: c++ linux fork raii socketpair

现在我尝试了解子进程的 stdin / out / err 的分支/重新绑定,并正确管理资源(文件句柄,套接字)而不泄漏任何内容资源。

还有一些问题: 在我创建 socketpair 和fork之后,我在父5个文件描述符和子文件中( stdin / out / err / socket1 / socket2 )。在子进程中,我需要关闭socketpair的“父”端。我在fork之后关闭() stdin / out / err ,然后将 dup()套接字的“客户端”关闭三次。在 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");   
}

1 个答案:

答案 0 :(得分:0)

我想,我找到了解决方案。对于socketpair()来电中每个创建的套接字,我设置了FD_CLOEXEC。这样,我可以确定内核关闭所有文件描述符。由我的代码处理的所有其他套接字将被fdhandle类调用close()关闭。重新绑定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);
}