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

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

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

我问的原因是我想closures所有打开的文件描述符。 我的程序是一个以root身份运行的服务器,为非root用户分配和执行子程序。 在subprocess中保留打开特权文件描述符是一个安全问题。 有些文件描述符可能是由我无法控制的代码(C库,第三方库等)打开的,所以我也不能依靠FD_CLOEXEC

在可移植的情况下,closures所有文件描述符到sysconf(_SC_OPEN_MAX)是不可靠的,因为在大多数系统中,这个调用返回当前文件描述符软限制,该限制可能低于最高使用的文件描述符。 另一个问题是,在许多系统上sysconf(_SC_OPEN_MAX)可能返回INT_MAX ,这可能会导致这种方法慢得令人无法接受。 不幸的是,没有可靠的,可移植的替代scheme,不涉及迭代每个可能的非负int文件描述符。

尽pipe不是可移植的,但是现在大多数常用的操作系统提供了一个或多个以下解决scheme来解决这个问题:

  1. 一个库函数closures所有文件描述符 > = fd 。 这是closures所有文件描述符的常见情况的最简单的解决scheme,虽然它不能用于其他许多。 要closures除特定集外的所有文件描述符,可以使用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. 库函数提供进程正在使用的最大文件描述符 。 要closures超过一定数量的所有文件描述符,可以closures所有文件描述符直到达到这个最大值,或者不断地获取并closures一个循环中的最高文件描述符,直到达到下限。 哪个更有效率取决于文件描述符密度。

    • fcntl(0, F_MAXFD) (NetBSD)

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

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

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

    • /dev/fd/ (FreeBSD,Darwin,OS X)

    采用这种方法可以很好地处理所有的angular落案例。 例如,考虑所有文件描述符> = fd将被closures的情况,但所有文件描述符都是< fd ,当前进程资源限制是fd ,并且使用的文件描述符> = fd 。 由于已达到进程资源限制,因此目录无法打开。 如果通过资源限制或sysconf(_SC_OPEN_MAX)closuresfd中的每个文件描述符作为回退,则不会closures任何内容。

POSIX的方式是:

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

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

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

一些Unix支持closefrom()。 这可以避免调用close()的次数过多,具体取决于最大可能的文件描述符编号。 虽然我知道最好的解决scheme,但它是完全不可移植的。

我已经编写了代码来处理所有平台特定的function。 所有function都是asynchronous信号安全的。 思想的人可能会觉得这很有用。 现在只在OS X上进行testing,随时可以改进/修复。

 // 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); } } 

当你的程序开始,并没有打开任何东西。 例如像main()的开始。 pipe道和叉立即启动执行器服务器。 这样它的内存和其他细节是干净的,你可以给它的东西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,执行程序服务器必须执行套接字redirect,你可以使用unix套接字。

你为什么不closures所有的描述符,从0到10000。

这将是非常快,最糟糕的事情会发生是EBADF。