Дескриптор 2 процесса А и дескриптор 2 процесса Б ссылаются на один и тот же файловый дескриптор (73). Этот сценарий может сложиться после вызова fork() (то есть процесс А является родительским по отношению к процессу Б или наоборот) либо при условии, что один процесс передал открытый дескриптор другому процессу, используя доменный сокет UNIX (см. подраздел 57.13.3).
И наконец, можно увидеть, что дескриптор 0 процесса А и дескриптор 3 процесса Б ссылаются на различные дескрипторы открытых файлов, но эти дескрипции ссылаются на одну и ту же запись в таблице индексных дескрипторов (1976), то есть на один и тот же файл. Дело в том, что каждый процесс независимо вызвал open() для одного и того же файла. Похожая ситуация может возникнуть, если один и тот же процесс дважды откроет один и тот же файл.
В результате можно прийти к следующим заключениям.
• Два различных файловых дескриптора, ссылающихся на одну и ту же дескрипцию открытого файла, совместно используют значение файлового смещения. Поэтому, если файловое смещение изменяется в связи с работой с одним файловым дескриптором (в результате вызовов read(), write() или lseek()), это изменение прослеживается через другой файловый дескриптор. Это применимо как к случаю, когда оба файловых дескриптора принадлежат одному и тому же процессу, так и к случаю, когда они принадлежат разным процессам.
• Аналогичные правила видимости применяются и к извлечению и изменению флагов состояния открытых файлов (например, O_APPEND, O_NONBLOCK и O_ASYNC) при использовании в системном вызове fcntl() операций F_GETFL и F_SETFL.
• В отличие от этого, флаги файлового дескриптора (то есть флаг закрытия при исполнении — close-on-exec) находятся в исключительном владении процесса и файлового дескриптора. Изменение этих флагов не влияет на другие файловые дескрипторы в одном и том же или в разных процессах.
Использование синтаксиса перенаправления ввода-вывода (присущего Bourne shell) 2>&1 информирует оболочку о необходимости перенаправления стандартной ошибки (файловый дескриптор 2) в то же место, в которое выдается стандартный вывод (дескриптор файла 1). Таким образом, следующая команда станет (поскольку оболочка вычисляет направление ввода-вывода слева направо) отправлять и стандартный вывод, и стандартную ошибку в файл results.log:
$ ./myscript > results.log 2>&1
Рис. 5.2. Связь между дескрипторами файлов, дескрипцией открытых файлов и индексными дескрипторами
Оболочка перенаправляет стандартную ошибку, создавая дескриптор файла 2 дубликата дескриптора файла 1, так что он ссылается на ту же дескрипцию открытого файла, что и файловый дескриптор 1 (точно так же, как дескрипторы 1 и 20 процесса А ссылаются на одну и ту же дескрипцию открытого файла на рис. 5.2). Этого эффекта можно достичь, используя системные вызовы dup() и dup2().
Заметьте, что для оболочки недостаточно просто дважды открыть файл results.log: один раз с дескриптором 1 и один раз с дескриптором 2. Одна из причин состоит в том, что два файловых дескриптора не смогут совместно использовать указатель файлового смещения и это приведет к перезаписи вывода друг друга. Другая причина заключается в том, что файл может не быть дисковым. Рассмотрим следующую команду, отправляющую стандартную ошибку по тому же конвейеру, что и стандартный вывод:
$ ./myscript 2>&1 | less
Вызов dup() на основании аргумента oldfd открывает файловый дескриптор, возвращая новый дескриптор, ссылающийся на ту же самую дескрипцию открытого файла. Новый дескриптор гарантированно будет наименьшим неиспользованным файловым дескриптором.
#include <unistd.h>
int dup(int oldfd);
При успешном завершении возвращает новый файловый дескриптор, а при ошибке выдает –1
Предположим, что осуществляется следующий вызов:
newfd = dup(1);
Если предположить, что сложилась обычная ситуация, при которой оболочка открыла от имени программы файловые дескрипторы 0, 1 и 2, и не используются никакие другие дескрипторы, dup() откроет дубликат дескриптора 1, используя файловый дескриптор 3.
Если нужно, чтобы дубликатом стал дескриптор 2, можно воспользоваться следующей технологией:
close(2); /* Высвобождение файлового дескриптора 2 */
newfd = dup(1); /* Повторное использование файлового дескриптора 2 */
Этот код работает, только если был открыт дескриптор 0. Чтобы упростить показанный выше код и обеспечить неизменное получение нужного нам файлового дескриптора, можно воспользоваться системным вызовом dup2().