Дескриптор файла - это целое число без знака, с помощью которого процесс обращается к открытому файлу. В каждом процессе может быть выделено до двух тысяч дескрипторов. Они создаются при выполнении функций open, pipe, creat и fcntl. Обычно каждый процесс работает с уникальным набором дескрипторов. Однако эти же дескрипторы могут применяться и дочерними процессами, созданными с помощью функции fork. Кроме того, дескрипторы можно скопировать с помощью функций fcntl, dup и dup2.
Дескрипторы файлов выполняют роль индексов таблицы дескрипторов, которая расположена в области u_block и создается ядром для каждого процесса. Чаще всего процесс получает дескрипторы с помощью операций open и creat, а также путем наследования от родительского процесса. При выполнении операции fork таблица дескрипторов копируется для дочернего процесса. В результате дочерний процесс получает право обращаться к файлам родительского процесса.
Структуры данных, содержащие
список открытых файлов и список дескрипторов файлов, позволяют отслеживать
обращения процессов к файлам и гарантировать целостность данных.
Структура | Назначение и содержание |
---|---|
таблица дескрипторов файлов | Преобразует индексы таблицы (дескрипторы файлов) в указатели на открытые
файлы. Для каждого процесса в области u_block создается своя
собственная таблица дескрипторов. Каждая запись такой таблицы содержит
два поля: поле флагов и указатель на файл. Ниже описана структура
таблицы дескрипторов:
struct ufd { struct file *fp; int flags; }u_ufd[OPEN_MAX] Функция fcntl позволяет установить флаг FD_CLOEXEC в записи таблицы дескрипторов. Функция dup копирует запись о дескрипторе файла в другую строку этой же таблицы. При выполнении функции fork для дочернего процесса создается копия всей таблицы дескрипторов файлов. |
Таблица открытых файлов | Содержит записи с информацией обо всех открытых файлах. Два
наиболее важных поля в записях этой таблицы - это текущее положение указателя
в файле, на который ссылаются все операции чтения и записи, а также режим
открытия файла (O_RDONLY, O_WRONLY или
O_RDWR).
Структура данных с информацией об открытом файле содержит смещение указателя в файле. При выполнении операции чтения-записи система выполняет неявный сдвиг указателя. Например, при чтении или записи x байт указатель также будет перемещен на x байт. Для изменения положения указателя в файлах с прямым доступом применяется функция lseek. Для потоковых файлов (например, каналов и сокетов) понятие смещения не поддерживается, так как произвольный доступ к этим файлам невозможен. |
Поскольку с файлами может работать несколько пользователей, необходимо, чтобы связанные процессы работали с общим указателем смещения, а независимые процессы - с собственным указателем смещения в файле. В записи таблицы открытых файлов содержится счетчик обращений к файлу, отражающий число дескрипторов, соответствующих данному файлу.
Несколько обращений к файлу может возникнуть в следующих ситуациях:
Каждая операция открытия создает запись в таблице открытых файлов. Это гарантирует, что каждый процесс будет работать с отдельным указателем смещения в файле. Такой подход позволяет сохранить целостность данных.
При копировании дескриптора два процесса начинают работать с общим указателем смещения. В этом случае оба процесса могут попытаться одновременно изменить положение указателя. Это означает, что чтение и запись не будут выполняться последовательно.
Существует три способа копирования дескрипторов файлов: с помощью функций dup и dup2, а также с помощью функций fork и fcntl.
dup | Создает копию дескриптора файла |
Копия создается в пустой строке пользовательской таблицы дескрипторов,
содержащей исходный дескриптор. При вызове dup увеличивается
значение счетчика обращений к файлу в записи таблицы открытых файлов и
возвращается новый дескриптор файла.
dup2 | Находит запрошенный дескриптор и закрывает связанный с ним файл, если он открыт. |
Функция dup2 позволяет указать конкретную запись таблицы, в которую должен быть скопирован дескриптор.
При запуске программы в оболочке
открывается три дескриптора 0, 1 и 2. По умолчанию с ними связаны
следующие файлы:
0 | Стандартный ввод. |
1 | Стандартный вывод. |
2 | Стандартный вывод сообщений об ошибках. |
Перечисленные дескрипторы файлов связаны с терминалом. Это означает, что при чтении данных из файла с дескриптором 0 программа получает ввод с терминала, а при записи данных в файлы с дескрипторами 1 и 2 они выводятся на терминал. При открытии других файлов дескрипторы присваиваются в порядке возрастания.
Если ввод-вывод перенаправляется с помощью операторов < (знак меньше) или > (знак больше), то стандартные дескрипторы связываются с другими файлами. Например, при выполнении команды:
prog < FileX > FileY
стандартные дескрипторы файлов 0 и 1 связываются с указанными файлами. В данном примере дескриптор 0 будет связан с файлом FileX, а дескриптор 1 - с файлом FileY. Дескриптор 2 не будет изменен. Программе достаточно знать, что дескриптор 0 представляет файл ввода, а дескрипторы 1 и 2 - файлы вывода. Информация о том, с какими конкретно файлами связаны эти дескрипторы, ей не нужна.
В следующем примере программы продемонстрировано перенаправление стандартного вывода:
#include <fcntl.h> #include <stdio.h> void redirect_stdout(char *); main() { printf("Hello world\n"); /* печать в стандартный вывод */ fflush(stdout); redirect_stdout("foo"); /*перенаправление стандартного вывода*/ printf("Hello to you too, foo\n"); /*печать в файл foo */ fflush(stdout); }
void redirect_stdout(char *filename) { int fd; if ((fd = open(filename,O_CREAT|O_WRONLY,0666)) < 0) /*открытие нового файла*/ { perror(filename); exit(1); } close(1); /*закрытие стандартного*/ вывода */ if (dup(fd) !=1) /*присвоение новому дескриптору *значения 1*/ { fprintf(stderr,"Unexpected dup failure\n"); exit(1); } close(fd); /*закрытие ненужного*/ * исходного fd*/ }
Дескриптор 2 также можно присвоить другому файлу, однако это редко применяется на практике.
При получении запроса на дескриптор выделяется первый свободный дескриптор из таблицы дескрипторов (дескриптор с наименьшим номером). Однако с помощью функции dup файлу можно присвоить любой дескриптор.
Максимальное число дескрипторов, которое может использоваться в одном процессе, ограничено. Значение по умолчанию устанавливается в файле /etc/security/limits и обычно равно 2000 (для совместимости с предыдущими выпусками). Для изменения ограничения можно воспользоваться командой ulimit или функцией setrlimit. Максимальное число определяется константой OPEN_MAX.
Глава 5, Файловые системы и каталоги
Функции fcntl, dup, dup2, lseek, open, openx и create