dup2 / dup - зачем мне дублировать дескриптор файла?

85

Я пытаюсь понять использование dup2и dup.

На странице руководства:

DESCRIPTION

dup and dup2 create a copy of the file descriptor oldfd.
After successful return of dup or dup2, the old and new descriptors may
be used interchangeably. They share locks, file position pointers and
flags; for example, if the file position is modified by using lseek on
one of the descriptors, the position is also changed for the other.

The two descriptors do not share the close-on-exec flag, however.

dup uses the lowest-numbered unused descriptor for the new descriptor.

dup2 makes newfd be the copy of oldfd, closing newfd first if necessary.  

RETURN VALUE

dup and dup2 return the new descriptor, or -1 if an error occurred 
(in which case, errno is set appropriately).  

Зачем мне нужен этот системный вызов? какой толк в дублировании дескриптора файла?

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

Я был бы признателен, если бы вы могли объяснить и привести мне пример, где dup2/ dupнеобходимо.

Благодарность

ЯНВАРЬ
источник
Как бы вы реализовали функциональность оболочек без dupили dup2? Вам нужно позвонить, pipe(2)а затем иметь один из файловых дескрипторов, dupнапример,STDIN_FILENO
Базиль Старынкевич

Ответы:

47

Системный вызов dup дублирует существующий дескриптор файла, возвращая новый, который относится к тому же базовому объекту ввода-вывода.

Dup позволяет оболочкам реализовывать такие команды:

ls existing-file non-existing-file > tmp1  2>&1

2> & 1 указывает оболочке дать команде файловый дескриптор 2, который является дубликатом дескриптора 1. (т.е. stderr и stdout указывают на тот же fd).
Теперь сообщение об ошибке для вызова ls для несуществующего файла и правильный вывод ls для существующего файла отображаются в файле tmp1 .

В следующем примере кода выполняется программа wc со стандартным вводом, подключенным к концу канала для чтения.

int p[2];
char *argv[2];
argv[0] = "wc";
argv[1] = 0;
pipe(p);
if(fork() == 0) {
    close(STDIN); //CHILD CLOSING stdin
    dup(p[STDIN]); // copies the fd of read end of pipe into its fd i.e 0 (STDIN)
    close(p[STDIN]);
    close(p[STDOUT]);
    exec("/bin/wc", argv);
} else {
    write(p[STDOUT], "hello world\n", 12);
    close(p[STDIN]);
    close(p[STDOUT]);
}

Потомок дублирует конец чтения на дескриптор файла 0, закрывает дескрипторы файла в p и выполняет wc. Когда wc читает из стандартного ввода, он читает из канала.
Вот как каналы реализуются с использованием dup, хорошо, что одно использование dup теперь вы используете канал для создания чего-то еще, в этом прелесть системных вызовов, вы строите одну вещь за другой, используя инструменты, которые уже есть, этот инструмент был построен с использованием что-то еще и так далее .. В конце концов, системные вызовы - это самые основные инструменты, которые вы получаете в ядре

Ура :)

Глубокая мысль
источник
1
Так dupполезно для звонящего, а не для самой lsпрограммы? Есть ли польза от dupиспользования в программе, такой как сама ls, если уже есть доступ к файлу? Здесь, например, lsзаписываются ошибки, 2которые жестко запрограммированы, поэтому у меня есть способ переопределить их как потребителя ls. Я думаю, это тонкий момент, нет?
Nishant
2
Ваш пример программы , кажется , имеет ошибку; вы звоните, dup(p[STDIN])но потом отбрасываете результат. Вы хотели использовать dup2(p[STDIN], 0)?
Quuxplusone 08
1
@Quuxplusone dupвозвращает «дескриптор с наименьшим номером, который в настоящее время не используется процессом». Поскольку fd 0 был только что закрыт, он dupдолжен вернуть 0. dup2Ясно, какой fd следует использовать, а не просто использовать наименьший свободный fd, поэтому я бы предпочел это.
Wodin
@Wodin: Ах, держу пари, ты прав насчет того, о чем думал OP. Правильно ли я, однако, что «только что закрытый» является относительным, и код OP может сломаться при наличии, например, параллельных потоков, которые тоже могут открывать файлы?
Quuxplusone
@Quuxplusone Я подозреваю, что вы правы, но не знаю наверняка. Но тогда у вас будут другие проблемы. Если вы закроете stdin, потому что хотите читать откуда-то еще, а затем другой поток откроет файл раньше, чем вы это сделаете, он получит fd 0. Если вы затем используете dup2, он закроет fd, который открыл другой поток, поэтому другой поток теперь будет читать (и писать в?) файл, который вы открываете. В любом случае, если я правильно помню, вы не должны звонить exec*из многопоточного процесса. Но я не эксперт по
нитям
18

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

R .. GitHub НЕ ПОМОГАЕТ ICE
источник
fdopen()похоже, не дублирует файловый дескриптор, он просто создает буфер в пользовательском пространстве.
Эрик Ван
3
Вы неправильно прочитали мой ответ. Дело в том, что вы можете захотеть dupиспользовать fd, прежде чем передавать его, fdopenпоскольку fcloseзакроет его.
R .. GitHub НЕ ПОМОГАЕТ ICE
1
@ theferrit32: если вы выделяете FILEдескриптор для доступа к уже существующему открытому файлу через интерфейсы stdio, вам нужно вызвать fcloseдля освобождения этого FILEдескриптора. Если вы хотите продолжать использовать лежащий в основе открытый файл или ваша программная архитектура такова, что это будет исходный код «владельца» для файлового дескриптора close, то факт, что он fcloseтакже закрывает нижележащий файловый дескриптор, который вы передали, fdopenявляется проблемой. Вы можете избежать этой проблемы, используя dupдля создания нового файлового дескриптора для того же открытого файла, к fdopenкоторому нужно перейти , чтобы fcloseне закрыть исходный файл .
R .. GitHub НЕ ПОМОГАЕТ ICE 01
1
Дело в том , что fdopen () перемещает право собственности на ФД к FILE, а не копировать его. Это то, о чем пользователи должны знать. Потребители, которым необходимо сохранить полезный fdдескриптор в дополнение к FILEобъекту, должны дублировать fd. Вот и все.
Конрад Мейер,
1
@ConradMeyer: Да, это очень хороший способ выразить это с примечанием, что не существует операции по «перемещению владения» от того момента, FILEкогда вы передаете ему владение.
R .. GitHub НЕ ПОМОГАЕТ ICE
4

dup используется для перенаправления вывода процесса.

Например, если вы хотите сохранить вывод процесса, вы дублируете вывод (fd = 1), перенаправляете дублированный fd в файл, затем разветвляете и выполняете процесс, а когда процесс завершается, вы снова перенаправляете сохранил fd для вывода.

Alinsoar
источник
4

Обратите внимание на некоторые моменты, связанные с dup / dup2.

dup / dup2 - Технически цель состоит в том, чтобы разделить одну запись таблицы файлов внутри одного процесса с помощью разных дескрипторов. (Если мы выполняем разветвление, дескриптор по умолчанию дублируется в дочернем процессе, и запись в таблице файлов также является общей).

Это означает, что с помощью функции dup / dup2 у нас может быть более одного дескриптора файла с возможно разными атрибутами для одной записи открытой таблицы файлов.

(Хотя, похоже, в настоящее время только флаг FD_CLOEXEC является единственным атрибутом для файлового дескриптора).

http://www.gnu.org/software/libc/manual/html_node/Descriptor-Flags.html

dup(fd) is equivalent to fcntl(fd, F_DUPFD, 0);

dup2(fildes, fildes2); is equivalent to 

   close(fildes2);
   fcntl(fildes, F_DUPFD, fildes2);

Различия (для последнего) - помимо некоторого значения errno между dup2 и fcntl close, за которым следует fcntl, могут возникать условия гонки, поскольку задействованы два вызова функций.

Подробности можно узнать на http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html.

Пример использования -

Один интересный пример реализации управления заданиями в оболочке, где можно увидеть использование dup / dup2 .. по ссылке ниже

http://www.gnu.org/software/libc/manual/html_node/Launching-Jobs.html#Launching-Jobs

Танмой Bandyopadhyay
источник