dup() и сброс кеша

Я новичок в C, пытаюсь использовать dup(), я написал программу для проверки этой функции, результат немного отличается от того, что я ожидал.

Код:

// unistd.h, dup() test

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern void dup_test();

int main() {
    dup_test();
}

// dup()test
void dup_test() {
    // open a file
    FILE *f = fopen("/tmp/a.txt", "w+");
    int fd = fileno(f);
    printf("original file descriptor:\t%d\n",fd);

    // duplicate file descriptor of an opened file,
    int fd_dup = dup(fd);
    printf("duplicated file descriptor:\t%d\n",fd_dup);
    FILE *f_dup = fdopen(fd_dup, "w+");

    // write to file, use the duplicated file descriptor,
    fputs("hello\n", f_dup);
    fflush(f_dup);
    // close duplicated file descriptor,
    fclose(f_dup);
    close(fd_dup);

    // allocate memory
    int maxSize = 1024; // 1 kb
    char *buf = malloc(maxSize);

    // move to beginning of file,
    rewind(f);
    // read from file, use the original file descriptor,
    fgets(buf, maxSize, f);
    printf("%s", buf);

    // close original file descriptor,
    fclose(f);

    // free memory
    free(buf);
}

Программа пытается записать через дублированный fd, затем закрыть дублированный fd, затем попытаться прочитать через исходный fd.

Я ожидал, что когда я закрою дублированный fd, кэш ввода-вывода будет очищен автоматически, но это не так, если я удалю функцию fflush() в коде, оригинальный fd не сможет прочитать содержимое, написанное дублированным fd, который уже закрыт.

Мой вопрос:

Означает ли это, что при закрытии дублированного файла fd он не будет автоматически сбрасываться?


@Редактировать:

Прошу прощения, моя ошибка, я нашел причину, в моей исходной программе это:

close(fd_dup);

но не имеют:

fclose(f_dup);

после использования fclose(f_dup); для замены close(f_dup); все работает.

Таким образом, дублированный fd автоматически очищается, если он правильно закрыт, write() и close() - это пара, fwrite() и fclose() - это пара, их не следует смешивать.

На самом деле, в коде я мог бы использовать дублированный fd_dup напрямую с write() и close(), и вообще нет необходимости создавать новый FILE.

Итак, код может быть просто таким:

// unistd.h, dup() test

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define BUF_SIZE 1024 // 1 kb

extern void dup_test();

int main() {
    dup_test();
}

// dup()test
void dup_test() {
    // open a file
    FILE *f = fopen("/tmp/a.txt", "w+");
    int fd = fileno(f);
    printf("original file descriptor:\t%d\n",fd);

    // duplicate file descriptor of an opened file,
    int fd_dup = dup(fd);
    printf("duplicated file descriptor:\t%d\n",fd_dup);

    // write to file, use the duplicated file descriptor,
    write(fd_dup, "hello\n", BUF_SIZE);
    // close duplicated file descriptor,
    close(fd_dup);

    // allocate memory
    char *buf = malloc(BUF_SIZE);

    // move to beginning of file,
    rewind(f);
    // read from file, use the original file descriptor,
    fgets(buf, BUF_SIZE, f);
    printf("%s", buf);

    // close original file descriptor,
    fclose(f);

    // free memory
    free(buf);
}

person user218867    schedule 03.11.2014    source источник
comment
Возможно, вы захотите strace свою программу проверить, какие системные вызовы она на самом деле выполняет. В моей системе ваша программа всегда печатает hello. Я думаю, что может происходить то, что f_dup сбрасывается, но f просматривает ваш файл до этого (в fopen).   -  person zch    schedule 03.11.2014
comment
поскольку у вас есть fdopen ваш fd, объект, с которым вы должны в основном работать, это FILE*. по сути, это одно и то же, не закрывайте одно и то же дважды. и вы немного запутались с FILE* и int, которые должны быть fd. FILE * — это файловый поток, это стандартное представление, поэтому оно кроссплатформенное. fd на самом деле int, порядковый номер таблицы файловых дескрипторов, это особенность системы *nix.   -  person Jason Hu    schedule 03.11.2014
comment
@zch Спасибо, strace - отличный инструмент!   -  person user218867    schedule 03.11.2014


Ответы (2)


Я не могу понять вашу проблему. Я тестировал его под Microsoft VC2008 (пришлось заменить unistd.h на io.h) и gcc 4.2.1.

Я закомментировал fflush(f_dup), потому что это бесполезно перед закрытием, и close(fd_dup);, потому что дескриптор файла уже был закрыт, поэтому фрагмент кода теперь выглядит так:

// write to file, use the duplicated file descriptor,
fputs("hello\n", f_dup);
// fflush(f_dup);
// close duplicated file descriptor,
fclose(f_dup);
// close(fd_dup);

И работает корректно. Я получаю в обеих системах:

original file descriptor:       3
duplicated file descriptor:     4
hello
person Serge Ballesta    schedule 03.11.2014
comment
Спасибо, моя ошибка, причина описана в обновленном вопросе. - person user218867; 03.11.2014

Из dup справочных страниц:

После успешного возврата из одного из этих системных вызовов старый и новый файловые дескрипторы могут использоваться взаимозаменяемо. Они ссылаются на одно и то же описание открытого файла (см. open(2)) и, таким образом, имеют общие флаги смещения файла и состояния файла; например, если смещение в файле изменяется с помощью lseek(2) для одного из дескрипторов, смещение также изменяется для другого.

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

Вы используете fdopen для создания отдельных seek_ptr и end_ptr дублированного потока, таким образом, fd_dup перестает быть дублированием. Вот почему вы можете читать данные после сброса и закрытия потока.

Я не смог найти убедительных фактов о том, почему вы не можете читать, если не сбросите второй файловый дескриптор. Могу добавить, что это может быть связано с системным вызовом sync.

В конце концов, если вам нужен буфер ввода-вывода, вы можете использовать неправильный механизм, проверьте именованные каналы и другие механизмы буферизации ОС.

person Felipe Lavratti    schedule 03.11.2014
comment
Я сделал перемотку() после записи перед чтением, поэтому поиск указателя не проблема. В любом случае, спасибо за ваше предложение, код просто пытается понять функцию dup(), в реальной программе может использоваться другой подход. - person user218867; 03.11.2014