防止 Linux fork 期间文件描述符继承
- 2024-11-13 08:36:00
- admin 原创
- 181
问题描述:
如何防止文件描述符在fork()
系统调用之间被复制继承(当然,不关闭它)?
我正在寻找一种方法来将单个文件描述符标记 为不被子进程 (复制) 继承fork()
,类似于 FD_CLOEXEC 之类的 hack,但适用于 fork(因此如果您愿意,可以使用 FD_DONTINHERIT 功能)。有人这样做过吗?或者有人研究过这个并给我一些提示?
谢谢
更新:
我可以使用libc 的 __register_atfork
__register_atfork(NULL, NULL, fdcleaner, NULL)
在返回之前关闭 child 中的 fds fork()
。但是,FD 仍在被复制,所以这对我来说听起来像是一个愚蠢的 hack。问题是如何跳过dup()
child 中不需要的 FD 的 -ing。
我正在考虑一些fcntl(fd, F_SETFL, F_DONTINHERIT)
需要的情况:
fork()
将复制事件 FD(例如epoll()
);有时这并不是想要的,例如 FreeBSD 将 kqueue() 事件 FD 标记为 KQUEUE_TYPE,并且这些类型的 FD 不会在分叉之间复制(明确跳过 kqueue FD 的复制,如果想从子进程使用它,则必须使用共享 FD 表进行分叉)fork()
将复制 100k 个不需要的 FD,以便派生一个子进程来执行一些 CPU 密集型任务(假设对 a 的需求fork()
概率很低,并且程序员不想为通常不会发生的事情维护一个子进程池)
有些描述符我们希望被复制(0、1、2),有些(大多数?)则不希望被复制。我认为完整的 FD 表复制是出于历史原因,但我可能错了。
这听起来有多傻呢:
补丁程序
fcntl()
支持文件描述符上的dontinherit标志(不确定该标志是否应保留在每个 FD 中或保留在 FD 表 fd_set 中,就像保留 close-on-exec 标志一样在内核中进行修改
dup_fd()
以跳过dontinherit FD 的复制,就像 FreeBSD 对 kq FD 所做的一样
考虑一下这个程序
#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)
",
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;
}
当然,不能认为这是一个真正的长凳,但无论如何:
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
forkit 在 Dell Inspiron 1520 Intel(R) Core(TM)2 Duo CPU T7500 @ 2.20GHz 和 4GB RAM 上运行;average_load=0.00
解决方案 1:
如果您的fork
目的是调用一个exec
函数,那么您可以使用fcntl
withFD_CLOEXEC
来关闭文件描述符exec
:
int fd = open(...);
fcntl(fd, F_SETFD, FD_CLOEXEC);
这样的文件描述符将保留下来fork
,但不会保留该exec
系列的功能。
解决方案 2:
不。你自己关闭它们,因为你知道哪些需要关闭。
解决方案 3:
据我所知,没有标准的方法可以做到这一点。
如果您希望正确实现它,那么最好的方法可能是添加一个系统调用来将文件描述符标记为 close-on-fork,并sys_fork
在调用原始系统调用后拦截系统调用(系统调用编号 2)以对这些标志采取行动sys_fork
。
如果您不想添加新的系统调用,您可能能够拦截sys_ioctl
(系统调用编号 54)并只向其添加一个新命令来标记文件描述 close-on-fork。
当然,如果您可以控制应用程序正在做什么,那么最好维护您想要在 fork 时关闭的所有文件描述符的用户级表,然后调用您自己的表myfork
。这将 fork,然后通过用户级表关闭那些标记的文件描述符。
那么您不必在 Linux 内核中摆弄,这个解决方案可能仅在您无法控制 fork 进程时才是必要的(例如,如果第三方库正在进行调用fork()
)。