Файловый дескриптор: что это простыми словами

Читать в полной версии →

Файловый дескриптор (file descriptor) — это уникальный числовой идентификатор, который операционная система назначает каждому открытому файлу или ресурсу (сокету, каналу, устройству). Файловые дескрипторы в Linux и других UNIX-подобных операционных системах (ОС) очень важны, хотя используются и в Windows. Они помогают управлять вводом-выводом данных, позволяя программам обращаться к файлам и другим объектам через системные вызовы.

Для чего нужен файловый дескриптор?

Файловый дескриптор можно сравнить с номерком в гардеробе: вместо сданного пальто посетитель получает номер, по которому забирает одежду. Аналогично операционная система дает программе дескриптор, который используется для работы с конкретным файлом. Файловые дескрипторы в Linux и других ОС решают несколько задач.

1. Обеспечивают взаимодействие между программами и ОС

Когда программа открывает файл, она запрашивает у операционной системы доступ к нему. В ответ ОС выделяет файловый дескриптор, который программа использует для выполнения операций с этим файлом.

int fd = open("example.txt", O_RDONLY); // Открытие файла только для чтения
if (fd == -1) {
    perror("Ошибка открытия файла");
    return 1;
}

Это код на C/C++ для открытия файла, типичный в UNIX-подобных системах.

Важно: файловый дескриптор может быть только положительным числом. Если число отрицательное, как в примере, появится сообщение об ошибке.

2. Управляют открытыми файлами и ресурсами

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

3. Унифицируют работу с разными типами данных

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

int sockfd = socket(AF_INET, SOCK_STREAM, 0); // Создание TCP-сокета
if (sockfd == -1) {
    perror("Ошибка создания сокета");
    return 1;
}

Это код на C/C++ для создания сетевого TCP-сокета:

Читайте также: Компьютерная сеть: что это такое, основные принципы

4. Перенаправляют стандартных потоков (stdin, stdout, stderr)

В UNIX-подобных системах три стандартных потока ввода-вывода:

Файловые дескрипторы позволяют изменять стандартные потоки ввода и вывода. Например, можно перенаправить вывод программы в файл:

ls > output.txt 2> errors.txt

Здесь stdout (1) перенаправляется в output.txt, а stderr (2) — в errors.txt.

5. Работают с многопоточной обработкой данных

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

fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
select(sockfd + 1, &readfds, NULL, NULL, NULL); // Ожидание активности на сокете

Это код для мониторинга активности на сокете через select(), где fd_set readfds — это набор файловых дескрипторов для отслеживания. Это классический способ ожидания данных на сокете без постоянного опроса (polling).

6. Контролируют утечки файловых дескрипторов

В системе можно установить ограничение на количество файловых дескрипторов. Это ограничение проверяется командой:

ulimit -n

Если программа открывает файлы, но не закрывает их, возникает утечка дескрипторов, что может привести к нехватке ресурсов. Отладить утечки можно с помощью следующих команд:

Также интересно: Что такое DFS и для чего он используется?

Файловый дескриптор в программировании: работа с разными ОС

Файловые дескрипторы используются в UNIX-подобных системах и в OS Windows по-разному. Многие современные языки программирования, такие как Python, Java и другие, абстрагируют эти различия, предоставляя единый кросс-платформенный интерфейс для работы с файлами. Работа с дескрипторами часто скрыта за высокоуровневыми абстракциями.

Python:

with open('file.txt') as f:    # Объект файла скрывает дескриптор
    data = f.read()

Java:

try (FileInputStream fis = new FileInputStream("file.txt")) {
    // Класс FileInputStream управляет дескриптором
}

Node.js:

const fs = require('fs');
fs.readFile('file.txt', (err, data) => {
    // Асинхронное API скрывает работу с дескрипторами
});

Go:

file, err := os.Open("file.txt")
defer file.Close()
// os.File инкапсулирует дескриптор

Во всех примерах можно получить доступ к нативному дескриптору, если нужно работать с низкоуровневой операцией или системным вызовом. Вот как это сделать:

Заключение

Файловые дескрипторы лежат в основе взаимодействия программ с файлами и устройствами ввода-вывода. Хотя современные языки программирования скрывают работу с дескрипторами за удобными абстракциями, разработчикам стоит разобраться в их работе, особенно при создании системных программ, сетевых приложений или при оптимизации производительности. Детальнее узнать о том, как используются файловые дескрипторы в программировании, можно на курсе Хекслет «Python: основы текстового ввода-вывода». Студенты учатся взаимодействовать с файлами и файловой системой, используя разные режимы и менеджеры контекста. Этот материал также пригодится тем, кто изучает Python.