Хаос, когда fork () встречает fopen () - PullRequest
0 голосов
/ 28 сентября 2018

Я обнаружил, что открытый файловый поток испортится, если мы сделаем fork () перед его закрытием.Хорошо известно, что параллелизм, то есть условия гонки, могут возникать, когда родительский и дочерний процессы хотят изменить файловый поток.Тем не менее, даже когда дочерний процесс никогда не касается файлового потока, он по-прежнему имеет неопределенное поведение.Мне было интересно, может ли кто-нибудь объяснить это, возможно, из-за того, как ядро ​​работает с файловым потоком на этапах, когда дочерний процесс разветвляется и завершается.

Ниже приведен небольшой фрагмент странного поведения:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> 
#include <sys/wait.h> 

int main() {
    // Open file
    FILE* fp = fopen("test.txt", "r");

    int count = 0;
    char* buffer = NULL;
    size_t capacity = 0;
    ssize_t line = 0;
    while ( (line = getline(&buffer, &capacity, fp)) != -1 ) {
        if (line > 0 && buffer[line - 1] == '\n') // remove the end '\n'
            buffer[line - 1] = 0;

        pid_t pid = fork();
        if (pid == 0) {
            // fclose(fp); // Magic line here: when you add this, everything is fine
            if (*buffer == '2')
                execlp("xyz", "xyz", NULL);
            else
                execlp("pwd", "pwd", NULL);
            exit(1);
        } else {
            waitpid(pid, NULL, 0);
        }
        count++;
    }
    printf("Loops: %d\n", count);
    return 0;
}

Просто скопируйте код в новый файл (например, test.c).И создайте .txt файл test.txt с простым содержимым

1
2
3
4

и запустите

$ gcc test.c && ./a.out

. В файле 4 строки.Ожидается, что циклы прочитают каждую строку и выполнятся ровно 4 раза (1 2 3 4).И я решил позволить ему выполнить недопустимую команду "xyz", когда он находится во 2-м цикле.Затем вы обнаружите, что цикл фактически выполняется 6 раз (1 2 3 4 3 4)!Дело в том, что, когда все четыре выполненные команды являются действительными, ничто не пойдет не так.Но если выполняется недопустимая команда, каждая команда после нее будет выполнена дважды.(Обратите внимание, что это странное поведение возникает только на Linux-машине, с моей Mac OS все в порядке, не уверен насчет Windows. Так что проблема зависит от платформы?)

Похоже, что когда я выполняю fork (),Поток файла в parent больше не обещает быть старым fp (недетерминированное поведение), даже когда мой дочерний процесс его не трогает.

Временное решение, которое я нашел, это: fclose (fp) в дочернем процессе,Это заставит замолчать вышеупомянутое странное поведение, но в более сложных условиях могут наблюдаться и другие вещи.Было бы полезно, если бы кто-нибудь дал мне некоторое представление об этой проблеме.Спасибо

1 Ответ

0 голосов
/ 29 сентября 2018

Как уже говорилось в комментариях, вам нужно закрыть дескрипторы открытых файлов перед вызовом exec.

В этом блоге (раздел 4) есть пример аккуратного кода, который вы можете использовать, чтобы убедиться, чтовсе fds закрыты даже в сложных приложениях, где вы не всегда знаете, какие файлы открыты в данный момент:

for ( i=getdtablesize(); i>2; --i) 
close(i); /* close all descriptors */

(слегка изменено, чтобы держать stdin, stdout, stderr открытым)

Это отчасти хакерски, но это работает.Если вы хотите избежать этого, вы также можете установить флаг O_CLOEXEC для каждого дескриптора файла, который вы открываете.Поскольку при использовании fopen вы напрямую не вызываете open(), вы можете сделать это, добавив к нему флаг 'e' (при использовании glibc> = 2.7):

FILE* fp = fopen("test.txt", "er");

При вызове exec*() всего файладескрипторы с этим флагом автоматически закрываются.

...