如何防止文件描述符在fork()
系统调用中被复制继承(当然不能关闭它)?
我正在寻找一种方法标记单个文件描述符,在fork()
时不被子进程(复制)继承,类似于FD_CLOEXEC的黑科技,但适用于forks(因此如果您喜欢,可以使用FD_DONTINHERIT功能)。有人做过这个吗?或者研究过这个并给我一个提示开始?
谢谢
更新:
我可以使用libc的__register_atfork。
__register_atfork(NULL, NULL, fdcleaner, NULL)
在fork()
返回之前关闭子进程中的fds。然而,FD仍在被复制,所以这听起来像是一个愚蠢的hack。问题是如何跳过不需要的FD的子进程中的dup()
。
我正在考虑一些情况下需要使用fcntl(fd, F_SETFL, F_DONTINHERIT)
:
fork()
将复制事件FD(例如epoll()
);有时这是不想要的,例如FreeBSD将kqueue()事件FD标记为KQUEUE_TYPE,这些类型的FD不会被复制到fork(kqueue FD明确地从被复制中跳过,如果想要从子进程中使用它,则必须使用共享FD表进行fork)fork()
将复制100k个不需要的FDs以fork子进程执行某些CPU密集型任务(假设需要fork()
的概率非常低,程序员不想为通常不会发生的事情维护一个子进程池)
我们希望复制一些描述符(0、1、2),但大多数描述符可能都不需要复制。我认为完整的FD表复制是出于历史原因,但我可能是错误的。
这听起来多么愚蠢:
- 修改
fcntl()
以支持文件描述符上的dontinherit标志(不确定该标志应该是每个FD保留还是在FD表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;
}
当然,不能把这个视为真正的工作台,但无论如何:
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 运行在戴尔Inspiron 1520上,使用Intel(R) Core(TM)2 Duo CPU T7500 @ 2.20GHz和4GB RAM;平均负载为0.00。