Работа с дескрипторами файлов
Дескриптор файла - это целое число без знака, с помощью которого процесс обращается к открытому файлу.
Количество дескрипторов файлов, доступных процессу, ограничено параметром /OPEN_MAX, заданным в файле sys/limits.h. Кроме того, количество дескрипторов файлов можно задать с помощью флага -n команды ulimit. Дескрипторы файлов создаются при выполнении функций open, pipe, creat и fcntl. Обычно каждый процесс работает с уникальным набором дескрипторов. Однако эти же дескрипторы могут применяться и дочерними процессами, созданными с помощью функции fork. Кроме того, дескрипторы можно скопировать с помощью функций fcntl, dup и dup2.
Дескрипторы файлов выполняют роль индексов таблицы дескрипторов, которая расположена в области u_block и создается ядром для каждого процесса. Чаще всего процесс получает дескрипторы с помощью операций open и creat, а также путем наследования от родительского процесса. При выполнении операции fork таблица дескрипторов копируется для дочернего процесса. В результате дочерний процесс получает право обращаться к файлам родительского процесса.
Таблицы дескрипторов файлов и системные таблицы открытых файлов
Таблица | Описание |
---|---|
Таблица дескрипторов файлов | Преобразует индексы таблицы (дескрипторы файлов) в указатели на открытые
файлы. Для каждого процесса в области u_block создается своя собственная
таблица дескрипторов.
Каждая запись такой таблицы содержит
следующие поля: поле флагов и указатель на файл. Допустимо не более
OPEN_MAX дескрипторов файлов. Таблица дескрипторов файлов имеет следующую структуру:
|
Таблица открытых файлов | Содержит записи с информацией обо всех открытых файлах. В
записи этой таблицы хранится текущее смещение указателя в файле,
которое используется во всех операциях чтения и записи в файл, а
также режим открытия файла (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 они выводятся на терминал. При открытии других файлов дескрипторы присваиваются в порядке возрастания.
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.