Подтвердить что ты не робот

Предотвращение наследования файловых дескрипторов во время форка Linux

Как вы предотвращаете копирование наследования файлов через fork() syscalls (без его закрытия, конечно)?

Я ищу способ отметить один дескриптор файла как НЕ, чтобы быть (copy-) унаследовал детей в fork(), что-то вроде взлома FD_CLOEXEC, но для forks (так что функция FD_DONTINHERIT, если хотите). Кто-нибудь это сделал? Или посмотрел на это и дал мне подсказку для начала?

Спасибо

ОБНОВЛЕНИЕ:

Я мог бы использовать libc __register_atfork

 __register_atfork(NULL, NULL, fdcleaner, NULL)

чтобы закрыть fds в дочернем элементе только перед тем, как fork() вернется. Тем не менее, fds все еще копируются, так что это звучит как глупый хак для меня. Вопрос в том, как пропустить dup() в дочернем объекте ненужных fds

Я думаю о некоторых сценариях, когда нужны fcntl (fd, F_SETFL, F_DONTINHERIT):

  • fork() скопирует событие fd (например, epoll); иногда это не требуется, например, FreeBSD отмечает событие fk kqueue() как имеющее значение KQUEUE_TYPE, и эти типы fds не будут скопированы через forks (kqueue fds пропускается явно от копирования, если вы хотите использовать его у ребенка, который он должен использовать fork с общей таблицей fd)

  • fork() скопирует 100k ненужных fds, чтобы развить ребенка для выполнения некоторых интенсивных задач (предположим, что необходимость fork() вероятностно очень низкая, и программист не захочет поддерживать пул детей для чего-то, чего обычно не будет)

Некоторые дескрипторы, которые мы хотим скопировать (0,1,2), некоторые (большинство из них?) нет. Я думаю, что полное обманывание fdtable здесь по историческим причинам, но я, вероятно, ошибаюсь.

Как глупо это звучит:

  • patch fcntl для поддержки флага dontinherit в дескрипторах файлов (не уверен, что флаг должен храниться в 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;
}

конечно, не может считать это настоящей скамейкой, но так или иначе:

[email protected]:/home/cia/dev/kqueue# time ./forkit
fork_cost(100 fds)=0.000000ticks(0.000000 seconds)

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

real    0m0.287s
user    0m0.010s
sys     0m0.240s
[email protected]:/home/cia/dev/kqueue# gcc -DNUMFDS=100 -o forkit forkit.c
[email protected]:/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 с 4 ГБ оперативной памяти; average_load = 0.00

4b9b3361

Ответ 1

Нет. Закройте их самостоятельно, так как вы знаете, какие из них нужно закрыть.

Ответ 2

Если вы fork с целью вызова функции exec, вы можете использовать fcntl с FD_CLOEXEC, чтобы дескриптор файла был закрыт после exec:

int fd = open(...);
fcntl(fd, F_SETFD, FD_CLOEXEC);

Такой файловый дескриптор выдержит fork, но не функции семейства exec.

Ответ 3

Нет стандартного способа сделать это, насколько мне известно.

Если вы хотите правильно его реализовать, вероятно, лучший способ сделать это - добавить системный вызов, чтобы пометить дескриптор файла как закрытую-fork, и перехватить системный вызов sys_fork (syscall номер 2), чтобы действовать на эти флаги после вызова оригинала sys_fork.

Если вы не хотите добавлять новый системный вызов, вы можете уйти с перехватом sys_ioctl (syscall номер 54) и просто добавить к нему новую команду для маркировки описания файла close- вилка.

Конечно, если вы можете контролировать то, что делает ваше приложение, лучше было бы поддерживать таблицы пользовательского уровня всех дескрипторов файлов, которые вы хотите закрыть на fork, и вместо этого вызвать свой собственный myfork. Это будет fork, а затем просмотрите таблицу пользовательского уровня, закрыв эти дескрипторы файлов таким образом.

В Linux-ядре вам не пришлось бы работать, это решение, которое, вероятно, необходимо только в том случае, если у вас нет контроля над процессом fork (скажем, если сторонняя библиотека выполняет вызовы fork()),.