代码之家  ›  专栏  ›  技术社区  ›  Ville Laurikari

获取分配的最高文件描述符

  •  44
  • Ville Laurikari  · 技术社区  · 15 年前

    是否有一种可移植的方法(posix)为当前进程获取分配的最高文件描述符编号?

    例如,我知道有一种很好的方法可以在AIX上获取数字,但我正在寻找一种可移植的方法。

    我问的原因是我想关闭所有打开的文件描述符。我的程序是一个服务器,它作为根和分叉运行,并为非根用户执行子程序。在子进程中保持特权文件描述符打开是一个安全问题。某些文件描述符可能是由我无法控制的代码(C库、第三方库等)打开的,因此我不能依赖 FD_CLOEXEC 要么。

    5 回复  |  直到 8 年前
        1
  •  64
  •   Jonathon Reinhart    9 年前

    当可移植时,关闭所有文件描述符 sysconf(_SC_OPEN_MAX) 不可靠,因为在大多数系统上,此调用返回当前文件描述符软限制,该限制可能已降低到使用率最高的文件描述符以下。另一个问题是在许多系统上 sysconf(_sc_open_max) 可能返回 INT_MAX 这可能会导致这种方法慢得无法接受。不幸的是,没有可靠的、可移植的替代方法不涉及对每个可能的非负int文件描述符进行迭代。

    尽管不可移植,但目前大多数常用的操作系统都提供了以下一种或多种解决方案:

    1. 一个库函数 关闭所有文件描述符 = 峡湾 . 对于关闭所有文件描述符的常见情况,这是最简单的解决方案,尽管它不能用于其他许多情况。要关闭除特定集合之外的所有文件描述符, dup2 可用于事先将它们移到低端,必要时可在事后将它们移回低端。

      • closefrom(fd) (Solaris 9或更高版本、FreeBSD 7.3或8.0及更高版本、NetBSD 3.0或更高版本、OpenBSD 3.5或更高版本。)

      • fcntl(fd, F_CLOSEM, 0) (AIX、IRIX、NetBSD)

    2. 一个库函数,用于提供 最大文件描述符 进程当前正在使用。要关闭某个数字以上的所有文件描述符,要么将它们全部关闭到这个最大值,要么继续获取并关闭循环中的最高文件描述符,直到达到下限。哪个更有效取决于文件描述符的密度。

      • fcntl(0, F_MAXFD) (NETBSD)

      • pstat_getproc(&ps, sizeof(struct pst_status), (size_t)0, (int)getpid())
        返回有关进程的信息,包括当前在中打开的最高文件描述符 ps.pst_highestfd . (H-UX)

    3. 包含每个打开文件描述符的条目的目录 .这是最灵活的方法,因为它允许关闭所有文件描述符,查找最高的文件描述符,或者对每个打开的文件描述符执行任何其他操作,甚至对其他进程(在大多数系统上)执行任何操作。然而,这可能比其他常用方法更复杂。此外,它也可能由于各种原因而失败,例如proc/fdescfs未装入、chroot环境或没有可用于打开目录的文件描述符(进程或系统限制)。因此,这种方法的使用通常与回退机制相结合。 Example (OpenSSH) , another example (glib) .

      • /proc/ PID /fd/ /proc/self/fd/ (Linux、Solaris、AIX、Cygwin、NetBSD)
        (AIX不支持“ self “”

      • /dev/fd/ (Freebsd、达尔文、OS X)

      用这种方法很难可靠地处理所有的角箱。例如,考虑所有文件描述符= 峡湾 将关闭,但所有文件描述符< 峡湾 已使用,当前进程资源限制为 峡湾 ,还有文件描述符>。= 峡湾 正在使用中。由于已达到进程资源限制,无法打开目录。如果关闭中的每个文件描述符 峡湾 通过资源限制或 sysconf(_sc_open_max) 用作回退,不会关闭任何内容。

        2
  •  13
  •   Jonathon Reinhart    9 年前

    POSIX方式是:

    int maxfd=sysconf(_SC_OPEN_MAX);
    for(int fd=3; fd<maxfd; fd++)
        close(fd);
    

    (注意,从3开始关闭,以保持stdin/stdout/stderr打开)

    close()如果文件描述符未打开,则无害地返回ebadf。没有必要浪费另一个系统调用检查。

    一些unix支持closeFrom()。这可以避免根据最大可能的文件描述符数调用close()的次数过多。虽然我知道最好的解决方案,但它是完全不可移植的。

        3
  •  5
  •   Hongli    9 年前

    我已经编写了处理所有平台特定功能的代码。所有功能都是异步信号安全的。以为人们会觉得这很有用。目前仅在OS X上测试,请随时改进/修复。

    // Async-signal safe way to get the current process's hard file descriptor limit.
    static int
    getFileDescriptorLimit() {
        long long sysconfResult = sysconf(_SC_OPEN_MAX);
    
        struct rlimit rl;
        long long rlimitResult;
        if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
            rlimitResult = 0;
        } else {
            rlimitResult = (long long) rl.rlim_max;
        }
    
        long result;
        if (sysconfResult > rlimitResult) {
            result = sysconfResult;
        } else {
            result = rlimitResult;
        }
        if (result < 0) {
            // Both calls returned errors.
            result = 9999;
        } else if (result < 2) {
            // The calls reported broken values.
            result = 2;
        }
        return result;
    }
    
    // Async-signal safe function to get the highest file
    // descriptor that the process is currently using.
    // See also http://stackoverflow.com/questions/899038/getting-the-highest-allocated-file-descriptor
    static int
    getHighestFileDescriptor() {
    #if defined(F_MAXFD)
        int ret;
    
        do {
            ret = fcntl(0, F_MAXFD);
        } while (ret == -1 && errno == EINTR);
        if (ret == -1) {
            ret = getFileDescriptorLimit();
        }
        return ret;
    
    #else
        int p[2], ret, flags;
        pid_t pid = -1;
        int result = -1;
    
        /* Since opendir() may not be async signal safe and thus may lock up
         * or crash, we use it in a child process which we kill if we notice
         * that things are going wrong.
         */
    
        // Make a pipe.
        p[0] = p[1] = -1;
        do {
            ret = pipe(p);
        } while (ret == -1 && errno == EINTR);
        if (ret == -1) {
            goto done;
        }
    
        // Make the read side non-blocking.
        do {
            flags = fcntl(p[0], F_GETFL);
        } while (flags == -1 && errno == EINTR);
        if (flags == -1) {
            goto done;
        }
        do {
            fcntl(p[0], F_SETFL, flags | O_NONBLOCK);
        } while (ret == -1 && errno == EINTR);
        if (ret == -1) {
            goto done;
        }
    
        do {
            pid = fork();
        } while (pid == -1 && errno == EINTR);
    
        if (pid == 0) {
            // Don't close p[0] here or it might affect the result.
    
            resetSignalHandlersAndMask();
    
            struct sigaction action;
            action.sa_handler = _exit;
            action.sa_flags   = SA_RESTART;
            sigemptyset(&action.sa_mask);
            sigaction(SIGSEGV, &action, NULL);
            sigaction(SIGPIPE, &action, NULL);
            sigaction(SIGBUS, &action, NULL);
            sigaction(SIGILL, &action, NULL);
            sigaction(SIGFPE, &action, NULL);
            sigaction(SIGABRT, &action, NULL);
    
            DIR *dir = NULL;
            #ifdef __APPLE__
                /* /dev/fd can always be trusted on OS X. */
                dir = opendir("/dev/fd");
            #else
                /* On FreeBSD and possibly other operating systems, /dev/fd only
                 * works if fdescfs is mounted. If it isn't mounted then /dev/fd
                 * still exists but always returns [0, 1, 2] and thus can't be
                 * trusted. If /dev and /dev/fd are on different filesystems
                 * then that probably means fdescfs is mounted.
                 */
                struct stat dirbuf1, dirbuf2;
                if (stat("/dev", &dirbuf1) == -1
                 || stat("/dev/fd", &dirbuf2) == -1) {
                    _exit(1);
                }
                if (dirbuf1.st_dev != dirbuf2.st_dev) {
                    dir = opendir("/dev/fd");
                }
            #endif
            if (dir == NULL) {
                dir = opendir("/proc/self/fd");
                if (dir == NULL) {
                    _exit(1);
                }
            }
    
            struct dirent *ent;
            union {
                int highest;
                char data[sizeof(int)];
            } u;
            u.highest = -1;
    
            while ((ent = readdir(dir)) != NULL) {
                if (ent->d_name[0] != '.') {
                    int number = atoi(ent->d_name);
                    if (number > u.highest) {
                        u.highest = number;
                    }
                }
            }
            if (u.highest != -1) {
                ssize_t ret, written = 0;
                do {
                    ret = write(p[1], u.data + written, sizeof(int) - written);
                    if (ret == -1) {
                        _exit(1);
                    }
                    written += ret;
                } while (written < (ssize_t) sizeof(int));
            }
            closedir(dir);
            _exit(0);
    
        } else if (pid == -1) {
            goto done;
    
        } else {
            do {
                ret = close(p[1]);
            } while (ret == -1 && errno == EINTR);
            p[1] = -1;
    
            union {
                int highest;
                char data[sizeof(int)];
            } u;
            ssize_t ret, bytesRead = 0;
            struct pollfd pfd;
            pfd.fd = p[0];
            pfd.events = POLLIN;
    
            do {
                do {
                    // The child process must finish within 30 ms, otherwise
                    // we might as well query sysconf.
                    ret = poll(&pfd, 1, 30);
                } while (ret == -1 && errno == EINTR);
                if (ret <= 0) {
                    goto done;
                }
    
                do {
                    ret = read(p[0], u.data + bytesRead, sizeof(int) - bytesRead);
                } while (ret == -1 && ret == EINTR);
                if (ret == -1) {
                    if (errno != EAGAIN) {
                        goto done;
                    }
                } else if (ret == 0) {
                    goto done;
                } else {
                    bytesRead += ret;
                }
            } while (bytesRead < (ssize_t) sizeof(int));
    
            result = u.highest;
            goto done;
        }
    
    done:
        if (p[0] != -1) {
            do {
                ret = close(p[0]);
            } while (ret == -1 && errno == EINTR);
        }
        if (p[1] != -1) {
            do {
                close(p[1]);
            } while (ret == -1 && errno == EINTR);
        }
        if (pid != -1) {
            do {
                ret = kill(pid, SIGKILL);
            } while (ret == -1 && errno == EINTR);
            do {
                ret = waitpid(pid, NULL, 0);
            } while (ret == -1 && errno == EINTR);
        }
    
        if (result == -1) {
            result = getFileDescriptorLimit();
        }
        return result;
    #endif
    }
    
    void
    closeAllFileDescriptors(int lastToKeepOpen) {
        #if defined(F_CLOSEM)
            int ret;
            do {
                ret = fcntl(lastToKeepOpen + 1, F_CLOSEM);
            } while (ret == -1 && errno == EINTR);
            if (ret != -1) {
                return;
            }
        #elif defined(HAS_CLOSEFROM)
            closefrom(lastToKeepOpen + 1);
            return;
        #endif
    
        for (int i = getHighestFileDescriptor(); i > lastToKeepOpen; i--) {
            int ret;
            do {
                ret = close(i);
            } while (ret == -1 && errno == EINTR);
        }
    }
    
        4
  •  0
  •   over_optimistic    8 年前

    就在你的程序启动但没有打开任何东西的时候。例如,像main()的开头。pipe和fork立即启动执行服务器。这样,它的内存和其他细节都是干净的,您只需将其交给fork&exec即可。

    #include <unistd.h>
    #include <stdio.h>
    #include <memory.h>
    #include <stdlib.h>
    
    struct PipeStreamHandles {
        /** Write to this */
        int output;
        /** Read from this */
        int input;
    
        /** true if this process is the child after a fork */
        bool isChild;
        pid_t childProcessId;
    };
    
    PipeStreamHandles forkFullDuplex(){
        int childInput[2];
        int childOutput[2];
    
        pipe(childInput);
        pipe(childOutput);
    
        pid_t pid = fork();
        PipeStreamHandles streams;
        if(pid == 0){
            // child
            close(childInput[1]);
            close(childOutput[0]);
    
            streams.output = childOutput[1];
            streams.input = childInput[0];
            streams.isChild = true;
            streams.childProcessId = getpid();
        } else {
            close(childInput[0]);
            close(childOutput[1]);
    
            streams.output = childInput[1];
            streams.input = childOutput[0];
            streams.isChild = false;
            streams.childProcessId = pid;
        }
    
        return streams;
    }
    
    
    struct ExecuteData {
        char command[2048];
        bool shouldExit;
    };
    
    ExecuteData getCommand() {
        // maybe use json or semething to read what to execute
        // environment if any and etc..        
        // you can read via stdin because of the dup setup we did
        // in setupExecutor
        ExecuteData data;
        memset(&data, 0, sizeof(data));
        data.shouldExit = fgets(data.command, 2047, stdin) == NULL;
        return data;
    }
    
    void executorServer(){
    
        while(true){
            printf("executor server waiting for command\n");
            // maybe use json or semething to read what to execute
            // environment if any and etc..        
            ExecuteData command = getCommand();
            // one way is for getCommand() to check if stdin is gone
            // that way you can set shouldExit to true
            if(command.shouldExit){
                break;
            }
            printf("executor server doing command %s", command.command);
            system(command.command);
            // free command resources.
        }
    }
    
    static PipeStreamHandles executorStreams;
    void setupExecutor(){
        PipeStreamHandles handles = forkFullDuplex();
    
        if(handles.isChild){
            // This simplifies so we can just use standard IO 
            dup2(handles.input, 0);
            // we comment this out so we see output.
            // dup2(handles.output, 1);
            close(handles.input);
            // we uncomment this one so we can see hello world
            // if you want to capture the output you will want this.
            //close(handles.output);
            handles.input = 0;
            handles.output = 1;
            printf("started child\n");
            executorServer();
            printf("exiting executor\n");
            exit(0);
        }
    
        executorStreams = handles;
    }
    
    /** Only has 0, 1, 2 file descriptiors open */
    pid_t cleanForkAndExecute(const char *command) {
        // You can do json and use a json parser might be better
        // so you can pass other data like environment perhaps.
        // and also be able to return details like new proccess id so you can
        // wait if it's done and ask other relevant questions.
        write(executorStreams.output, command, strlen(command));
        write(executorStreams.output, "\n", 1);
    }
    
    int main () {
        // needs to be done early so future fds do not get open
        setupExecutor();
    
        // run your program as usual.
        cleanForkAndExecute("echo hello world");
        sleep(3);
    }
    

    如果要在执行的程序上执行IO,执行服务器必须执行套接字重定向,并且可以使用Unix套接字。

        5
  •  -2
  •   alamar    15 年前

    为什么不关闭从0到10000的所有描述符呢?

    这将是相当快,最糟糕的事情将发生是ebadf。