web-dev-qa-db-ja.com

Linuxfork中のファイル記述子の継承を防ぐ

ファイル記述子がfork()システムコール間でコピー継承されるのをどのように防止しますか(もちろん、それを閉じることなく)?

単一のファイル記述子fork()の子によって(コピー)継承されないとしてマークする方法を探しています。これはFD_CLOEXECのようなハックのようなものですが、フォーク用(必要に応じてFD_DONTINHERIT機能)。誰かこれをしましたか?またはこれを調べて、私が始めるためのヒントがありますか?

ありがとうございました

更新:

libcの__register_atfork を使用できます

 __register_atfork(NULL, NULL, fdcleaner, NULL)

fork()が戻る直前に子のfdsを閉じます。ただし、fdsはまだコピーされているので、これは私にはばかげたハックのように聞こえます。質問は、不要なfdsの子でdup()-ingをスキップする方法です。

Fcntl(fd、F_SETFL、F_DONTINHERIT)が必要になるいくつかのシナリオを考えています。

  • fork()はイベントfd(epollなど)をコピーします。たとえば、FreeBSDはkqueue()イベントfdをKQUEUE_TYPEとしてマークしており、これらのタイプのfdはフォーク間でコピーされません(必要に応じて、kqueue fdsはコピーから明示的にスキップされます)。共有fdテーブルでフォークする必要がある子から使用する)

  • fork()は、CPUを集中的に使用するタスクを実行するために子をフォークするために100kの不要なfdsをコピーします(fork()の必要性は確率的に非常に低く、プログラマーは通常はしない何かのために子のプールを維持したくないと仮定します。 tが起こる)

コピーしたい記述子(0,1,2)もあれば、コピーしない記述子(ほとんど?)もあります。歴史的な理由から、完全なfdtableの複製がここにあると思いますが、おそらく間違っています。

この音はどれほどばかげていますか:

  • ファイル記述子でdontinheritフラグをサポートするようにfcntlにパッチを適用します(フラグをfdごとに保持するか、fdtable fd_setに保持するかは不明です。たとえば、close-on-execフラグが維持
  • カーネルのdup_fd()を変更して、dontinheritfdsのコピーをスキップします。これは、freebsdがkqfdsに対して行うのと同じです。

プログラムを検討する

#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は、Dell Inspiron 1520 Intel(R)Core(TM)2 Duo CPU T7500 @ 2.20GHz、4GBRAMで動作しました。 Average_load = 0.00

26
user237419

いいえ。閉じる必要があるものがわかっているので、自分で閉じてください。

fork関数を呼び出す目的でexecを実行する場合は、fcntlFD_CLOEXECを使用して、execを実行すると、ファイル記述子を閉じることができます。

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

このようなファイル記述子はforkを存続させますが、execファミリーの関数は存続しません。

8
zneak

私の知る限り、これを行う標準的な方法はありません。

適切に実装しようとしている場合、おそらく最善の方法は、システムコールを追加してファイル記述子をフォークに近いものとしてマークし、_sys_fork_システムコール(syscall番号)をインターセプトすることです。 2)元の_sys_fork_を呼び出した後、これらのフラグに基づいて動作します。

新しいシステムコールを追加したくない場合は、_sys_ioctl_(syscall番号54)をインターセプトし、ファイルの説明をフォークでマークするための新しいコマンドを追加するだけで解決できる場合があります。 。

もちろん、アプリケーションの実行内容を制御できる場合は、フォークで閉じたいすべてのファイル記述子のユーザーレベルのテーブルを維持し、代わりに独自のmyforkを呼び出す方がよい場合があります。これはフォークし、ユーザーレベルのテーブルを通過して、そのようにマークされたファイル記述子を閉じます。

Linuxカーネルをいじくり回す必要はありません。おそらく、フォークプロセスを制御できない場合にのみ必要なソリューションです(たとえば、サードパーティのライブラリがfork()呼び出しを実行している場合などです。 )。

3
paxdiablo