Похоже, что закрытие FILE
в некоторых случаях приводит к поиску базового файлового дескриптора обратно в положение, в котором приложение фактически читает, эффективно устраняя эффект буферизации чтения.Это имеет значение, поскольку файловые дескрипторы уровня операционной системы родительского и дочернего элементов указывают на одно и то же описание файла и, в частности, на одно и то же смещение файла.
POSIX-описание fclose()
имеет следующую фразу:
[CX] [Option Start] Если файл еще не находится в EOF, и файл способен искать, смещение файлалежащее в основе описание открытого файла должно быть установлено в позицию файла потока , если поток является активным дескриптором описания базового файла.
(где CX означает расширение длястандарт ISO C и exit()
, конечно, работает fclose()
на всех потоках.)
Я могу воспроизвести странное поведение этой программы (в Debian 9.8):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[]){
FILE *f;
if ((f = fopen("testfile", "r")) == NULL) {
perror("fopen");
exit(1);
}
int right = 0;
if (argc > 1)
right = 1;
char *line = NULL;
size_t len = 0;
// first line
getline(&line, &len, f);
printf("%s", line);
pid_t p = fork();
if (p == -1) {
perror("fork");
} else if (p == 0) {
if (right)
_exit(0); // exit the child
else
exit(0); // wrong way to exit
} else {
wait(NULL); // parent
}
// rest of the lines
while (getline(&line, &len, f) > 0) {
printf("%s", line);
}
fclose(f);
}
Затем:
$ printf 'a\nb\nc\n' > testfile
$ gcc -Wall -o getline getline.c
$ ./get
getline getline2
$ ./getline
a
b
c
b
c
Запуск его с strace -f ./getline
ясно показывает, что ребенок ищет файловый дескриптор назад:
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f63794e0710) = 25117
strace: Process 25117 attached
[pid 25116] wait4(-1, <unfinished ...>
[pid 25117] lseek(3, -4, SEEK_CUR) = 2
[pid 25117] exit_group(1) = ?
(Я не видел поиск скод, который не предполагал разветвления, но я не знаю почему.)
* 1034Итак, что происходит, так это то, что библиотека C в основной программе считывает блок данных из файла, и приложение выводит первую строку.После разветвления дочерний процесс выходит и ищет fd обратно туда, где находится указатель файла уровня приложения.Затем родитель продолжает работу, обрабатывает оставшуюся часть буфера чтения и, когда он завершает, продолжает чтение из файла.Поскольку дескриптор файла был найден обратно, строки, начинающиеся со второй, снова доступны.
В вашем случае повторное fork()
на каждой итерации, похоже, приводит к бесконечному циклу.
Использование _exit()
вместо exit()
в дочернем узле решает проблему в этом случае , так как _exit()
выходит только из процесса, он не выполняет никаких операций с буферами stdio.
При _exit()
все выходные буферы также не сбрасываются, поэтому вам нужно будет вручную fflush()
вызвать stdout
и любые другие файлы, в которые вы пишете.
Однако, если вы сделали это с другой стороны, когда ребенок читает и буферизует больше, чем обрабатывает, тогда ребенку было бы полезно найти обратно fd, чтобы родитель мог продолжить с того места, где ребенок фактически ушел.
Другим решением было бы не смешивать stdio
с fork()
.