fcntl(fd, F_SETFD, 1);
11.5.3. Дублирование файловых дескрипторов
Иногда процессам требуется создать новый файловый дескриптор, который ссылается на ранее открытый файл. Командные оболочки используют эту функциональность для перенаправления стандартного ввода, вывода и потока ошибок по запросу пользователя. Если процессу не важно, какой файловый дескриптор будет использован для новой ссылки, он должен использовать dup()
.
#include
int dup(int oldfd);
dup()
возвращает файловый дескриптор, который ссылается на тот же inode, что и oldfd
, или -1
в случае ошибки, oldfd
остается корректным дескриптором, по-прежнему ссылающимся на исходный файл. Новый файловый дескриптор — это всегда наименьший доступный файловый дескриптор. Если процессу нужно получить новый файловый дескриптор с определенным значением (например, 0
, чтобы перенаправить стандартный ввод), то он должен использовать dup2()
.
#include
int dup2(int oldfd, int newfd);
Если newfd
ссылается на уже открытый дескриптор, то он закрывается. Если вызов завершен успешно, он возвращает новый файловый дескриптор и newfd
ссылается на тот же файл, что oldfd
. Системный вызов fcntl()
представляет почти ту же функциональность командой F_DUPFD
. Первый аргумент — fd
— это уже открытый файловый дескриптор. Новый файловый дескриптор — это первый доступный дескриптор, равный или больший, чем значение последнего аргумента fcntl()
. (В этом состоит отличие от работы dup2()
.) Вы можете реализовать dup2()
через fcntl()
следующим образом.
int dup2(int oldfd, int newfd) {
close(newfd); /* ensure new fd is available */
return fcntl(oldfd, F_DUPFD, newfd);
}
Создание двух файловых дескрипторов, ссылающихся на один и тот же файл — это не то же самое, что открытие файла дважды. Почти все атрибуты дублированных дескрипторов разделяются: они разделяют текущую позицию, режим доступа и блокировки. (Все это записывается в файловой структуре [50] В зависимости от операционной системы, файловые структуры также известны как позиции в таблице файлов или объекты открытых файлов.
, которая создается при каждом открытии файла. Файловый дескриптор ссылается на файловую структуру, и дескриптор, возвращенный dup()
, ссылается на одну и ту же структуру.) Единственный атрибут, который может независимо управляться в этих двух дескрипторах — это состояние "закрыть при выполнении". После того, как процесс вызывает fork()
, то файлы, открытые в родительском процессе, наследуются дочерним, и эти пары файловых дескрипторов (один в родительском процессе, другой — в дочернем) ведут себя так, будто файловые дескрипторы были дублированы с текущей позицией и другими разделенными атрибутами [51] Файловый дескриптор в каждом процессе ссылается на одну и ту же файловую структуру.
.
11.6. Создание неименованных каналов
Неименованные каналы подобны именованным, но они в файловой системе не существуют. Они не имеют путевых имен, ассоциированных с ними, и все они и их следы исчезают после того, как последний файловый дескриптор, ссылающийся на них, закрывается. Они почти исключительно используются для межпроцессных коммуникаций между дочерними и родительскими процессами либо между родственными процессами.
Оболочки применяют неименованные каналы для выполнения команд вроде ls | head
. Процесс ls
пишет в тот канал, из которого head
читает свой ввод, выдавая ожидаемый пользователем результат.
Создание неименованного канала выполняется по двум файловым дескрипторам, один из которых доступен только для чтения, а второй — только для записи.
#include
int pipe(int fds[2]);
Единственный параметр-массив включает два файловых дескриптора — fd[0]
для чтения и fd[1]
для записи.
11.7. Добавление перенаправления для ladsh
Теперь, когда мы рассмотрели основные манипуляции с файлами, мы можем научить ladsh
перенаправлению ввода и вывода через файлы и каналы. ladsh2.с
, который мы представим здесь, работает с каналами (описанными символом |
в командах ladsh
, как это делается в большинстве командных оболочек) и перенаправление ввода и вывода в файловые дескрипторы. Мы покажем только модифицированные части кода здесь — полный исходный текст ladsh2.с
доступен по упомянутым в начале книги адресам. Изменения в parseCommand()
— это простое упражнение по разбору строк, поэтому мы не будем надоедать дискуссией об этом.
Читать дальше