Работа с дескрипторами файлов

Дескриптор файла - это целое число без знака, с помощью которого процесс обращается к открытому файлу.

Количество дескрипторов файлов, доступных процессу, ограничено параметром /OPEN_MAX, заданным в файле sys/limits.h. Кроме того, количество дескрипторов файлов можно задать с помощью флага -n команды ulimit. Дескрипторы файлов создаются при выполнении функций open, pipe, creat и fcntl. Обычно каждый процесс работает с уникальным набором дескрипторов. Однако эти же дескрипторы могут применяться и дочерними процессами, созданными с помощью функции fork. Кроме того, дескрипторы можно скопировать с помощью функций fcntl, dup и dup2.

Дескрипторы файлов выполняют роль индексов таблицы дескрипторов, которая расположена в области u_block и создается ядром для каждого процесса. Чаще всего процесс получает дескрипторы с помощью операций open и creat, а также путем наследования от родительского процесса. При выполнении операции fork таблица дескрипторов копируется для дочернего процесса. В результате дочерний процесс получает право обращаться к файлам родительского процесса.

Таблицы дескрипторов файлов и системные таблицы открытых файлов

Структуры данных, содержащие список открытых файлов и список дескрипторов файлов, позволяют отслеживать обращения процессов к файлам и гарантировать целостность данных.
Таблица Описание
Таблица дескрипторов файлов Преобразует индексы таблицы (дескрипторы файлов) в указатели на открытые файлы. Для каждого процесса в области u_block создается своя собственная таблица дескрипторов. Каждая запись такой таблицы содержит следующие поля: поле флагов и указатель на файл. Допустимо не более OPEN_MAX дескрипторов файлов. Таблица дескрипторов файлов имеет следующую структуру:
struct ufd
{
        struct file *fp;
        int flags;
} *u_ufd
Таблица открытых файлов Содержит записи с информацией обо всех открытых файлах. В записи этой таблицы хранится текущее смещение указателя в файле, которое используется во всех операциях чтения и записи в файл, а также режим открытия файла (O_RDONLY, O_WRONLY или O_RDWR).

В структуре таблицы открытых файлов хранится смещение указателя в файле. При выполнении операции чтения-записи система выполняет неявный сдвиг указателя. Например, при чтении или записи x байт указатель также будет перемещен на x байт. Для изменения положения указателя в файлах с прямым доступом применяется функция lseek. Для потоковых файлов (например, каналов и сокетов) понятие смещения не поддерживается, так как произвольный доступ к этим файлам невозможен.

Управление дескрипторами файлов

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

Несколько обращений к файлу может потребоваться в следующих случаях:
  • Файл открыт еще одним процессом
  • Дочерний процесс унаследовал дескрипторы файлов, открытых родительским процессом
  • Дескриптор файла скопирован с помощью функции fcntl или dup

Совместная работа с открытыми файлами

При выполнении каждой операции открытия в таблицу открытых файлов добавляется запись. Это гарантирует, что каждый процесс будет работать со своим указателем в файле. Такой подход позволяет сохранить целостность данных.

При копировании дескриптора два процесса начинают работать с одним и тем же указателем. В этом случае оба процесса могут попытаться одновременно обратиться к файлу, при этом данные будут считаны или записаны не последовательно.

Копирование дескрипторов файлов

Существуют следующие способы копирования дескрипторов файлов: функция dup или dup2, функция fork и функция fcntl.

Функции dup и dup2

Функция dup создает копию дескриптора файла. Копия создается в пустой строке пользовательской таблицы дескрипторов, содержащей исходный дескриптор. При вызове dup увеличивается значение счетчика обращений к файлу в записи таблицы открытых файлов и возвращается новый дескриптор файла.

Функция dup2 находит запрошенный дескриптор и закрывает связанный с ним файл, если он открыт. С ее помощью можно указать конкретную запись таблицы, в которую должен быть скопирован дескриптор.

fork, функция
Функция fork создает дочерний процесс, который наследует все дескрипторы файлов родительского процесса. После этого дочерний процесс запускает новый процесс. Унаследованные дескрипторы с флагом Закрыть при exec, установленным с помощью fcntl, будут закрыты.

 

Функция fcntl
Функция fcntl позволяет работать со структурой данных о файле и с дескрипторами открытых файлов. Она позволяет выполнять следующие операции над дескрипторами:
  • Копировать дескриптор файла (аналогично функции dup).
  • Получать или устанавливать значение флага Закрыть при exec.
  • Выключать режим объединения дескрипторов в блоки.
  • Включать режим добавления данных в конец файла (O_APPEND).
  • Включать отправку процессам сигнала о разрешении ввода-вывода.
  • Устанавливать и получать ИД процесса или группы процессов для отправки SIGIO.
  • Закрывать все дескрипторы файлов.

Стандартные дескрипторы файлов

При запуске программы в оболочке открывается три дескриптора 0, 1 и 2. По умолчанию с ними связаны следующие файлы:

Дескриптор Описание
0 Стандартный ввод.
1 Стандартный вывод.
2 Стандартный вывод сообщений об ошибках.

Перечисленные дескрипторы файлов связаны с терминалом. Это означает, что при чтении данных из файла с дескриптором 0 программа получает ввод с терминала, а при записи данных в файлы с дескрипторами 1 и 2 они выводятся на терминал. При открытии других файлов дескрипторы присваиваются в порядке возрастания.

Если ввод-вывод перенаправляется с помощью операторов < (знак меньше) или > (знак больше), то стандартные дескрипторы связываются с другими файлами. Например, следующая команда связывает дескрипторы файлов 0 и 1 с необходимыми файлами (по умолчанию эти дескрипторы связаны с терминалом).
prog < FileX > FileY

В данном примере дескриптор 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*/
}

При получении запроса на дескриптор выделяется первый свободный дескриптор из таблицы дескрипторов (дескриптор с наименьшим номером). Однако с помощью функции dup файлу можно присвоить любой дескриптор.

Ограничение на число дескрипторов файлов

Максимальное число дескрипторов, которое может использоваться в одном процессе, ограничено. Значение по умолчанию указывается в файле /etc/security/limits и обычно равно 2000. Для изменения ограничения можно воспользоваться командой ulimit или функцией setrlimit. Максимальное число определяется константой OPEN_MAX.