0

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

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

1 回答 1

0

我想,我找到了解决方案。对于socketpair()通话中创建的每个套接字,我设置了FD_CLOEXEC. 这样,我可以确定内核关闭了所有文件描述符。我的代码处理的所有其他套接字将被 fdhandle 类调用关闭close()。stdin/stdout/stderr 的重新绑定,我替换了dup()for,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);
}
于 2015-02-03T13:04:28.747 回答