ссылается на уже открытый дескриптор, то он закрывается. Если вызов завершен успешно, он возвращает новый файловый дескриптор и
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] .
50
В зависимости от операционной системы, файловые структуры также известны как позиции в таблице файлов или объекты открытых файлов.
51
Файловый дескриптор в каждом процессе ссылается на одну и ту же файловую структуру.
11.6. Создание неименованных каналов
Неименованные каналы подобны именованным, но они в файловой системе не существуют. Они не имеют путевых имен, ассоциированных с ними, и все они и их следы исчезают после того, как последний файловый дескриптор, ссылающийся на них, закрывается. Они почти исключительно используются для межпроцессных коммуникаций между дочерними и родительскими процессами либо между родственными процессами.
Оболочки применяют неименованные каналы для выполнения команд вроде
ls | head
. Процесс
ls
пишет в тот канал, из которого
head
читает свой ввод, выдавая ожидаемый пользователем результат.
Создание неименованного канала выполняется по двум файловым дескрипторам, один из которых доступен только для чтения, а второй — только для записи.
#include <unistd.h>
int pipe(int fds[2]);
Единственный параметр-массив включает два файловых дескриптора —
fd[0]
для чтения и
fd[1]
для записи.
11.7. Добавление перенаправления для ladsh
Теперь, когда мы рассмотрели основные манипуляции с файлами, мы можем научить
ladsh
перенаправлению ввода и вывода через файлы и каналы.
ladsh2.с
, который мы представим здесь, работает с каналами (описанными символом
|
в командах
ladsh
, как это делается в большинстве командных оболочек) и перенаправление ввода и вывода в файловые дескрипторы. Мы покажем только модифицированные части кода здесь — полный исходный текст
ladsh2.с
доступен по упомянутым в начале книги адресам. Изменения
в
parseCommand
— это простое упражнение по разбору строк, поэтому мы не будем надоедать дискуссией об этом.
11.7.1. Структуры данных
Хотя код в
ladsh1.с
поддерживает концепцию задания как множества процессов (предположительно, объединенных вместе каналами), он не предоставляет способа указания того, какие файлы использовать для ввода и вывода. Чтобы позволить это, добавляются новые структуры данных и модифицируются существующие.
24: REDIRECT_APPEND};
25:
26: struct redirectionSpecifier {
27: enum redirectionTypetype; /* тип перенаправления */
28: int fd; /*перенаправляемый файловый дескриптор*/
29: char * filename; /* файл для перенаправления fd */
30: };
31:
32: struct childProgram {
33: pid_t pid; /* 0 если завершен */
34: char **argv; /* имя программы и аргументы */
35: int numRedirections; /* элементы в массиве перенаправлений */
о том, как установить отдельный файловый дескриптор. Она содержит
enum redirectionTypetype
, который указывает, является ли это перенаправление перенаправлением ввода, перенаправлением вывода, который должен быть добавлен к существующему файлу, либо перенаправлением вывода, которое заменяет существующий файл. Она также включает перенаправляемый файловый дескриптор и имя файла. Каждая дочерняя программа (
struct childProgram
) теперь специфицирует нужное ей количество перенаправлений.
Эти новые структуры данных не связаны с установкой каналов между процессами. Поскольку задание определено как множество дочерних процессов с каналами, связывающими их, нет необходимости в более подробной информации, описывающей каналы. На рис. 11.1 показано, как эти новые структуры должны выглядеть для команды
tail < input-file | sort > output-file
.
Рис. 11.1. Структуры данных, описывающие задание для
ladsh2.с
11.7.2. Изменения в коде
Как только в
parseCommand
будут правильно отражены структуры данных, то запуск команд в правильном порядке становится довольно простым при достаточном внимании к деталям. Прежде всего, мы добавляем цикл в
parseCommand
для запуска дочерних процессов, поскольку теперь их может быть множество. Прежде чем войти в цикл, мы устанавливаем
nextin
и
nextout
, которые являются файловыми дескрипторами, используемыми в качестве стандартных потоков ввод и вывода для следующего запускаемого процесса. Для начала мы используем те же stdin и stdout, что и оболочка.
Теперь посмотрим, что происходит внутри цикла. Основная идея описана ниже.
1. Если это финальный процесс в задании, убедиться, что
nextout
указывает на stdout. В противном случае нужно подключить вывод этого задания к входному концу неименованного канала.
2. Породить новый процесс. Внутри дочернего перенаправить stdin и stdout, как указано с помощью
nextin
,
nextout
и всех специфицированных ранее перенаправлений.
3. Вернувшись обратно в родительский процесс, закрыть
nextin
и
nextout
, используемые только что запущенным дочерним процессом (если только они не являются потоками ввода и вывода самой оболочки).