Когда процесс открывает файл (open), ядро создаёт запись об открытом ресурсе и возвращает процессу индекс в его таблице — файловый дескриптор. Дальше все операции идут по номеру: read(fd, ...), write(fd, ...), close(fd).
«Всё есть файл». Через тот же интерфейс в Unix работают сокеты, каналы (| в shell), терминалы, устройства, таймеры (timerfd), события (eventfd). Поэтому одним epoll можно ждать готовности и сетевых соединений, и каналов, а перенаправление > в shell просто подменяет дескриптор 1.
Стандартные: 0 — stdin, 1 — stdout, 2 — stderr. 2>&1 — направить stderr туда же, куда stdout.
Лимиты. На процесс есть ограничение числа открытых дескрипторов — ulimit -n, по умолчанию часто 1024 (мягкий лимит). Для сервера с десятками тысяч соединений его поднимают (LimitNOFILE в systemd, nofile в limits.conf). Есть и общесистемный fs.file-max.
Too many open files (EMFILE) — обычно не «мало лимита», а утечка: файлы, сокеты или HTTP-соединения открываются и не закрываются. Ищут через ls /proc/<pid>/fd | wc -l и lsof -p <pid> — видно, чего накопилось. Лечение — закрывать ресурсы гарантированно: try-with-resources в Java, with в Python, defer в Go; переиспользовать HTTP-клиент с пулом вместо нового клиента на каждый запрос.
Наследование. После fork дочерний процесс получает копии дескрипторов; чтобы не утекали в запускаемые программы, их открывают с флагом O_CLOEXEC. Удалённый, но открытый файл продолжает занимать место на диске, пока дескриптор не закрыт — классическая загадка «диск полон, а файлов нет» с лог-файлом, который удалили у работающего процесса.