linux fork:防止文件描述符继承

use*_*419 24 linux fork

你如何阻止文件描述符跨fork()系统调用继承(当然没有关闭它)?

我正在寻找一种方法来将单个文件描述符标记 为不被(copy)在fork()中继承,类似于FD_CLOEXEC类似的hack但是for forks(如果你愿意,那么就是FD_DONTINHERIT功能).有人这样做过吗?或者看看这个,并提示我开始?

谢谢

更新:

我可以使用libc的__register_atfork

 __register_atfork(NULL, NULL, fdcleaner, NULL)
Run Code Online (Sandbox Code Playgroud)

在fork()返回之前关闭子中的fds.然而,fds仍然被复制,所以这对我来说听起来像是一个愚蠢的黑客.问题是如何跳过不需要的fds的孩子中的dup()

我想到了一些需要fcntl(fd,F_SETFL,F_DONTINHERIT)的场景:

  • fork()将复制事件fd(例如epoll); 有时这不是必需的,例如FreeBSD将kqueue()事件fd标记为KQUEUE_TYPE,并且这些类型的fds不会被复制到forks(如果有人想要,则会明确跳过kqueue fds被复制)从一个孩子使用它必须与共享fd表分叉)

  • fork()将复制100k不需要的fds来分叉一个孩子做一些cpu密集型任务(假设需要fork()概率非常低,程序员不希望维护一个孩子池,因为通常不会发生)

我们想要复制一些描述符(0,1,2),有些(大多数?)不是.我认为完整的fdtable重复是出于历史原因,但我可能错了.

这听起来多么愚蠢:

  • 修补fcntl以支持文件描述符上的dontinherit标志(不确定标志是应该保持per-fd还是保存在fdtable fd_set中,就像保持close-on-exec标志一样
  • 修改内核中的dup_fd()以跳过dontinherit fds的复制,就像freebsd 对kq fds一样

考虑该计划

#include <stdio.h>
#include <unistd.h>
#include <err.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>

static int fds[NUMFDS];
clock_t t1;

static void cleanup(int i)
{
    while(i-- >= 0) close(fds[i]);
}
void clk_start(void)
{
    t1 = clock();
}
void clk_end(void)
{  

    double tix = (double)clock() - t1;
    double sex = tix/CLOCKS_PER_SEC;
    printf("fork_cost(%d fds)=%fticks(%f seconds)\n",
        NUMFDS,tix,sex);
}
int main(int argc, char **argv)
{
    pid_t pid;
    int i;
    __register_atfork(clk_start,clk_end,NULL,NULL);
    for (i = 0; i < NUMFDS; i++) {
        fds[i] = open("/dev/null",O_RDONLY);
        if (fds[i] == -1) {
            cleanup(i);
            errx(EXIT_FAILURE,"open_fds:");
        }
    }
    t1 = clock();
    pid = fork();
    if (pid < 0) {
        errx(EXIT_FAILURE,"fork:");
    }
    if (pid == 0) {
        cleanup(NUMFDS);
        exit(0);
    } else {
        wait(&i);
        cleanup(NUMFDS);
    }
    exit(0);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

当然,无论如何都不能认为这是一个真正的替补席:

root@pinkpony:/home/cia/dev/kqueue# time ./forkit
fork_cost(100 fds)=0.000000ticks(0.000000 seconds)

real    0m0.004s
user    0m0.000s
sys     0m0.000s
root@pinkpony:/home/cia/dev/kqueue# gcc -DNUMFDS=100000 -o forkit forkit.c
root@pinkpony:/home/cia/dev/kqueue# time ./forkit
fork_cost(100000 fds)=10000.000000ticks(0.010000 seconds)

real    0m0.287s
user    0m0.010s
sys     0m0.240s
root@pinkpony:/home/cia/dev/kqueue# gcc -DNUMFDS=100 -o forkit forkit.c
root@pinkpony:/home/cia/dev/kqueue# time ./forkit
fork_cost(100 fds)=0.000000ticks(0.000000 seconds)

real    0m0.004s
user    0m0.000s
sys     0m0.000s
Run Code Online (Sandbox Code Playgroud)

forkit在Dell Inspiron 1520 Intel(R)Core(TM)2 Duo CPU T7500 @ 2.20GHz上运行,配备4GB内存; average_load = 0.00

zne*_*eak 8

如果您fork的目的是调用exec函数,则可以使用fcntlwith FD_CLOEXEC在以下情况下关闭文件描述符exec:

int fd = open(...);
fcntl(fd, F_SETFD, FD_CLOEXEC);
Run Code Online (Sandbox Code Playgroud)

这样的文件描述符将存在fork但不是该exec族的功能.

  • 问题中提到了`FD_CLOEXEC`,这也解释了为什么它不适用于这种情况(`exec`没有被调用).此外,`fcntl`不是设置close-on-exec标志的最佳方法. (4认同)
  • @Akronix:如果另一个线程在调用“open”和“fcntl”的线程之间调用“exec()”(比照“fork()”)会发生什么?两阶段初始化无法解决这个问题。这是在“fcntl”和“open”手册页中特别提到的一个问题,解决方案是“open()”的“O_CLOEXEC”标志。 (2认同)
  • @Akronix:或者进行一个带有额外参数的`fork`调用 - 无论标志是什么,都要显式继承fds列表.但是不要设计一些你无法控制另一个线程上的`fork()`是否看到标志的东西. (2认同)

Ign*_*ams 6

不.自己关闭它们,因为你知道哪些需要关闭.

  • "没有." 是问题的答案,但"自己关闭"不是.在多种情况下,这会产生竞争条件,如原始问题的评论中所述. (7认同)
  • 如果您将访问 Oracle DB 嵌入到您的软件中,oracle 本身会为您的进程创建一个分支,该进程继承您打开的文件和套接字。特别是 TCP 服务器套接字保持活动状态并防止在主应用程序中关闭和重新打开它们。没有地方可以关闭自己的 fh,只有描述的 fcntl 可能会有所帮助。 (2认同)

pax*_*blo 6

据我所知,没有标准的方法可以做到这一点。

如果您希望正确实现它,最好的方法可能是添加一个系统调用来将文件描述符标记为 close-on-fork,并拦截系统sys_fork调用(系统调用号 2)以执行操作调用原始后的那些标志sys_fork

如果您不想添加新的系统调用,则可以通过拦截sys_ioctl(系统调用号 54)并仅向其添加新命令来标记文件描述 close-on-fork 来完成。

当然,如果您可以控制应用程序正在执行的操作,那么最好维护要在 fork 上关闭的所有文件描述符的用户级表并调用您自己的表myfork。这将分叉,然后遍历用户级表,关闭如此标记的文件描述符。

那么您就不必在 Linux 内核中摆弄,这个解决方案可能只有在您无法控制 fork 进程时才需要(例如,如果第三方库正在执行调用fork())。

  • 考虑一个更通用的解决方案,添加一个新的 fcntl 标志并修改内核中的 dup_fd() (补丁似乎很容易应用)来测试它......这听起来太侵入性了吗?它比 syscall/ioctl 方式工作量少,至少乍一看是这样。dup_fd 是 fdcopy 在 fork 处发生的地方,似乎该函数仅受 fork() 系统调用的使用限制 (2认同)