fclose () / pclose () может блокировать некоторые файловые указатели - PullRequest
3 голосов
/ 15 ноября 2009

вызов fclose() здесь после dup() блокирования его дескриптора файла до тех пор, пока дочерний процесс не завершится (предположительно, потому что поток закончился).

FILE *f = popen("./output", "r");
int d = dup(fileno(f));
fclose(f);

Однако, вручную выполнив pipe(), fork(), execvp() popen() и затем dup(), используя дескриптор файла чтения канала, закрытие оригинала не блокируется.

int p[2];
pipe(p);
switch (fork()) {
    case 0: {
        char *argv[] = {"./output", NULL};
        close(p[0]);
        dup2(p[1], 1);
        execvp(*argv, argv);
    }
    default: {
        close(p[1]);
        int d = dup(p[0]);
        close(p[0]);
    }
}

Почему это происходит, и как я могу закрыть FILE *, возвращенный из popen(), и использовать вместо него дескриптор файла?

Обновление:

Мне известно, что в документации сказано, что нужно использовать pclose(), а также fclose() блоков. Кроме того, я ковырялся в коде glibc, и pclose() просто звонит fclose(). Поведение такое же, используется ли fclose() или pclose().

Ответы [ 3 ]

9 голосов
/ 16 ноября 2009

http://linux.die.net/man/3/popen

Функция pclose () ожидает завершения связанного процесса и возвращает состояние завершения команды, возвращенное функцией wait4 ().

Так как pclose () хочет вернуть статус выхода, ему нужно дождаться, чтобы дочерний процесс завершил и сгенерировал его. Поскольку fclose () вызывает pclose (), то же самое относится и к fclose () для вас.

Если вы выполняете форк и exec и делаете все остальное самостоятельно, то в итоге вы не вызовете pclose () (прямо или косвенно), поэтому в ближайшее время ждать не придется. Однако обратите внимание, что если ваша программа не настроена на игнорирование SIGCHLD, ваш процесс не завершится (вместо этого он станет зомби), пока ребенок не завершит. Но по крайней мере ваша цена побежит, чтобы выйти первым.

8 голосов
/ 20 ноября 2009

Разочаровавшись в общности ответов (я могу RTFM , tyvm), я тщательно исследовал это, пройдя и прочитав источник glibc .

В glibc pclose() напрямую звонит fclose() без дополнительного эффекта, поэтому 2 вызова одинаковы. На самом деле вы можете использовать pclose() и fclose() взаимозаменяемо. Я уверен, что это просто совпадение в развитой реализации, и все еще рекомендуется использовать pclose() для закрытия FILE *, возвращенного из popen().

Магия в popen(). FILE * s в glibc содержит таблицу переходов с указателями на соответствующие функции для обработки таких вызовов, как fseek(), fread() и релевантность fclose(). При вызове popen() используется таблица переходов, отличная от используемой fopen(). Элемент close в этой таблице переходов указывает на специальную функцию _IO_new_proc_close, которая вызывает waitpid() для pid, хранящегося в области, на которую указывает FILE *.

Вот соответствующий стек вызовов в моей версии glibc, который я отметил примечаниями о том, что происходит:

// linux waitpid system call interface
#0  0x00f9a422 in __kernel_vsyscall ()
#1  0x00c38513 in __waitpid_nocancel () from /lib/tls/i686/cmov/libc.so.6

// removes fp from a chain of proc files
// and waits for the process of the stored pid to terminate
#2  0x00bff248 in _IO_new_proc_close (fp=0x804b008) at iopopen.c:357

// flushes the stream and calls close in its jump table
#3  0x00c09ff3 in _IO_new_file_close_it (fp=0x804b008) at fileops.c:175

// destroys the FILEs buffers
#4  0x00bfd548 in _IO_new_fclose (fp=0x804b008) at iofclose.c:62

// calls fclose
#5  0x00c017fd in __new_pclose (fp=0x804b008) at pclose.c:43

// calls pclose
#6  0x08048788 in main () at opener.c:34

Таким образом, используя popen(), возвращаемый FILE * не должен закрываться, даже если вы dup() его файловый дескриптор, потому что он будет блокироваться до завершения дочернего процесса. Конечно, после этого у вас останется дескриптор файла для канала, который будет содержать все, что дочернему процессу удалось записать () в него до завершения.

Если не fread() с указателем файла, возвращаемым из popen(), нижележащий канал не будет затронут, можно безопасно использовать дескриптор файла с помощью fileno() и завершить, вызвав pclose().

1 голос
/ 15 ноября 2009

FILE*, возвращаемое popen(), должно быть закрыто pclose(), а не fclose(). Затем в документации для pclose() указывается:

Функция pclose () ожидает связанный процесс прекратить и возвращает статус выхода команды как возвращено wait4 ().

Таким образом, ожидание - это одна из двух вещей, которые pclose() делает, помимо закрытия дескриптора файла. close() делает только одно: закрывает дескриптор.

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

...