Too many open files: чей это лимит, почему он не общий и что считается дескриптором
Ошибку «Too many open files» (пер.: «слишком много открытых файлов») чинят перезапуском и правкой конфига, редко разбираясь, чей именно лимит кончился. Урок показывает механизм целиком: два лимита вместо одного, наследование при запуске, и то, что дескриптор — это не файл, а любой открытый объект, включая каждое сетевое соединение.
Полное техническое изложение
TL;DR
Дескриптор — это номер, которым процесс ссылается на что-то открытое, и лимит ограничивает число таких номеров у одного процесса. Лимитов два, и заняты они разным: мягкий ядро проверяет в тот момент, когда процесс открывает очередной дескриптор, а жёсткий ограничивает то, насколько высоко процесс может поднять себе мягкий. Оба принадлежат процессу и достаются его потомкам.
Отсюда главное следствие: «я поднял лимит» и «сервису его хватило» — разные
утверждения. Мягкий процесс поднимает себе сам, без перезапуска и без нового
конфига, но стартует он с тем лимитом, который стоял у запустившего: измерено —
родитель поставил себе 128, потомок стартовал со 128. Поэтому ulimit -n в
одной оболочке ничего не меняет для сервиса, поднятого из другого места. И
считает лимит не файлы, а все занятые номера: при лимите 64 открыть удалось
61, потому что три номера процесс получил при рождении.
Дальше — то, что отличает знающего от читавшего. Ошибка называется
EMFILE и говорит о лимите процесса; измерено: имя EMFILE, текст
Too many open files
(слишком много открытых файлов). У машины лимит
свой, и его исчерпание даёт ENFILE — другую ошибку, которая лечится в другом
месте. Дескриптором считается не только файл: при том же лимите сокет дал 61,
канал 60 (у канала два конца — два номера), epoll — 61, так что каждое
соединение клиента базы или HTTP-пула тоже занимает номер. А жёсткий лимит
понижается необратимо: непривилегированный процесс может опустить свой потолок,
но поднять обратно — уже нет; getrlimit(2) называет понижение необратимым, а
в прогоне отвергнута попытка поднять жёсткий выше текущего значения.
- программа открывает файлы и устанавливает сетевые соединения;
- у сервиса под нагрузкой этих соединений много и живут они одновременно;
- сервис кто-то запускает: оболочка, менеджер служб или контейнер.
RLIMIT_NOFILE,EMFILE,ENFILE,setrlimit;- что такое мягкий и жёсткий лимит и чем они отличаются;
epoll,inotify, состояниеCLOSE_WAIT.
Что здесь на самом деле спрашивают
Лестница обычно такая:
- «Что означает
Too many open files
(слишком много открытых файлов)?» — разминка. - «Чей это лимит — процесса, пользователя или машины?» — здесь начинается содержание.
- «Мягкий и жёсткий лимит — в чём разница?» — знаете ли вы, что один из них процесс может поменять себе сам.
- «Что считается дескриптором?» — назовёте ли вы сокеты и
epoll, а не только файлы. - «Почему поднятие лимита в консоли не помогло?» — вопрос про наследование.
- «Как отличить утечку дескрипторов от честной нехватки?» — вопрос про то, что смотреть.
Числа получены прогоном bench/rlimits/descriptors.py и
bench/rlimits/practice.py. Здесь нет ни одного замера времени — только счёт
дескрипторов и имена ошибок, — поэтому на любой машине с Linux повторяется
всё, кроме значений самих лимитов: они у каждой машины свои.
База: дескриптор — это номер, а не файл
Прежде чем говорить о лимитах, стоит назвать обычными словами, что именно считают.
Дескриптор — маленькое целое число. Когда процесс открывает файл, подключается к базе или создаёт канал между собой и подпроцессом, ядро заводит у себя нужный объект, а процессу отдаёт номер. Дальше процесс работает не с объектом, а с этим номером: читать, писать и закрывать он просит по нему.
Нумерация своя у каждого процесса. Таблица номеров принадлежит процессу: номер 7 у одного процесса и номер 7 у другого указывают на совершенно разные вещи. Поэтому и счёт открытого ведут по процессам, а не по машине целиком.
Три номера заняты ещё до первой строки вашего кода. Это 0, 1 и 2 — ввод, вывод и поток ошибок; их процесс получает при рождении. Открывали их не вы, но заняты они точно так же, как всё остальное.
И вот вопрос, на который отвечает остальной урок: сколько номеров процессу разрешено держать одновременно и откуда берётся это разрешение? Разрешение есть всегда — бесконечных таблиц не бывает. У каждого процесса оно своё, и приходит оно не из его собственного кода, а от того, кто его запустил. Когда номера кончаются, ядро отказывается открыть следующий, и на этом месте в логе появляется знакомая строка про слишком много открытых файлов.
Этого уже достаточно, чтобы ответить на базовый вопрос собеседования. Дальше — про то, почему разрешений на самом деле два, что ещё, кроме файлов, тратит номера, и почему поднятый в консоли лимит часто не доходит до сервиса.
Механизм 1: лимитов два, и это не дублирование
getrlimit(2) определяет пару так:
The soft limit is the value that the kernel enforces for the corresponding
resource. The hard limit acts as a ceiling for the soft limit: an unprivileged
process may set only its soft limit to a value in the range from 0 up to the
hard limit, and (irreversibly) lower its hard limit.
Мягкий лимит — это значение, которое ядро проводит в жизнь для соответствующего ресурса. Жёсткий лимит служит потолком для мягкого: непривилегированный процесс может лишь выставить свой мягкий лимит в диапазоне от 0 до жёсткого и (необратимо) понизить свой жёсткий.
Три следствия, и все три спрашивают.
Проверяется мягкий. Ядро сверяется с ним в тот момент, когда процесс открывает очередной дескриптор: не хватило — отказ. Когда говорят «лимит дескрипторов равен тысяче», почти всегда имеют в виду именно его.
Жёсткий ограничивает не открытие, а подъём. Сколько дескрипторов процесс откроет, решает мягкий лимит; насколько высоко процесс может поднять себе мягкий — жёсткий. И поднять мягкий до жёсткого процесс может сам, без перезапуска. Это самый недооценённый способ починки: сервису, который упёрся в мягкий лимит, часто не нужен ни новый конфиг, ни перезапуск — достаточно одного вызова при старте.
Жёсткий понижается необратимо. Опустил — обратно не поднимешь, если у
процесса нет CAP_SYS_RESOURCE; это свойство процесса, а не настройки.
Вот всё это в прогоне:
1. TWO LIMITS, AND ONLY ONE OF THEM IS ENFORCED
-----------------------------------------------
soft limit (the one the kernel enforces) 20000
hard limit (the ceiling for the soft one) 20000
descriptors this process holds right now 3
after lowering the soft limit to 256 256
raised back to the hard limit, no restart 20000
raising the hard limit refused: ValueError
Обратите внимание на третью строку: у процесса уже три дескриптора — ввод, вывод и поток ошибок. Они тоже считаются — и именно из-за них в следующем разделе получится 61, а не 64.
Механизм 2: EMFILE — про процесс, а не про машину
Что происходит при исчерпании, getrlimit(2) говорит прямо:
This specifies a value one greater than the maximum file descriptor number that
can be opened by this process. Attempts (open(2), pipe(2), dup(2), etc.) to
exceed this limit yield the error EMFILE.
Задаёт значение на единицу больше максимального номера файлового дескриптора, который может открыть этот процесс. Попытки (open(2), pipe(2), dup(2) и т. д.) превысить этот лимит дают ошибку EMFILE.
Ключевое слово — this process. Есть и общесистемный лимит, но он даёт
другую ошибку, и open(2) разводит их явно: EMFILE — the per-process limit
on the number of open file descriptors has been reached
(достигнут лимит числа открытых файловых дескрипторов на процесс), ENFILE — the
system-wide limit on the total number of open files has been reached
(достигнут общесистемный лимит на общее число открытых файлов).
Практически это первое, что стоит выяснять по логу: какая из двух букв.
EMFILE — виноват один процесс и лечится он на уровне процесса. ENFILE —
кончился общий ресурс машины: proc(5) описывает его как /proc/sys/fs/file-max,
файл, который defines a system-wide limit on the number of open files for
all processes
(задаёт общесистемный лимит на число открытых файлов для всех процессов). Правка лимита сервиса тут не поможет.
Смотрим на исчерпание. Потомок ставит себе мягкий лимит 64 и открывает файлы, пока ядро не откажет:
2. WHAT HITTING THE LIMIT LOOKS LIKE
------------------------------------
soft limit set by the child 64
descriptors the child already had 3
files it managed to open 61
error name EMFILE
error message Too many open files
61, а не 64. Три дескриптора у процесса уже были, и в лимит они входят. Разница выглядит мелочью на числе 64 и перестаёт быть мелочью, когда речь про пул соединений: лимит считает всё, что открыто, а не то, что открыли вы.
Механизм 3: дескриптор — это не файл
Слово «файл» в названии сбивает. Считается любой объект, у которого есть номер в таблице дескрипторов процесса, и проверить это можно, открывая разные вещи до отказа:
3. IT IS NOT ONLY FILES THAT COUNT
----------------------------------
file: opened before the refusal 61 — EMFILE: Too many open files
socket: opened before the refusal 61 — EMFILE: Too many open files
pipe: opened before the refusal 60 — EMFILE: Too many open files
epoll: opened before the refusal 61 — EMFILE: Too many open files
Сокет, конец канала, экземпляр epoll — каждый занимает номер. Отсюда
практический список того, что съедает лимит в реальном сервисе: соединения с
базой, соединения HTTP-пула, слушающие сокеты, принятые соединения,
inotify-наблюдатели, таймеры, открытые логи.
И отдельная строка про каналы: 60 вместо 61. Свободных номеров 61 — число нечётное, а канал берёт сразу два: тридцать каналов занимают шестьдесят номеров, и на тридцать первый одного оставшегося номера уже не хватает. Мелочь, но она объясняет, почему счёт открытых объектов и счёт занятых номеров расходятся: процесс, который порождает подпроцессы с перехватом вывода, тратит номера парами.
Механизм 4: лимит наследуется, и поэтому «я поднял, а не помогло»
Практическая ошибка, из-за которой лимит «не работает», обычно одна: его
подняли не тому процессу. Лимит принадлежит процессу, но достаётся
потомку, и execve(2) формулирует это от обратного — All process attributes
are preserved during an execve(), except the following
(При execve() сохраняются все атрибуты процесса, кроме следующих), — а ограничений
ресурсов в списке исключений нет.
4. THE LIMIT IS PER PROCESS, AND IT IS INHERITED
------------------------------------------------
soft limit set in the parent 128
soft limit the child starts with 128
soft limit restored in the parent 20000
Отсюда правило, которым стоит отвечать: лимит выставляют тому, кто
запускает. Если сервис поднимает менеджер служб, лимит задаётся в его
описании службы; если контейнер — в его настройках; ulimit -n в вашей
оболочке меняет лимит только для того, что вы запустите из этой оболочки, и
ничего не значит для процесса, который стартовал иначе.
Проверять надо там же, где ядро его и держит: файл /proc/<pid>/limits
показывает действующие лимиты конкретного процесса. Это единственный
ответ, который не зависит от того, что написано в конфигах.
Глубже: утечка или честная нехватка
Обе выглядят одинаково — EMFILE в логе, — а лечатся противоположно. Различие
видно по одному наблюдению: растёт ли число открытых дескрипторов
монотонно.
Считается оно там же, где всё в этом уроке: в /proc/<pid>/fd лежит по записи
на каждый открытый дескриптор, и их количество — это текущее потребление.
Дальше два случая.
Число растёт и не падает при спаде нагрузки — это утечка: где-то не закрывается ответ, соединение, файл. Лимит её только отсрочит.
Число держится на полке, близкой к лимиту, и падает вместе с нагрузкой — это честная нехватка: столько соединений сервису и нужно. Тогда лимит поднимают, и поднимают тому, кто запускает.
Есть и третий случай, который путают с утечкой: сокеты в состоянии
CLOSE_WAIT. Собеседник закрыл соединение, а приложение — нет, и дескриптор
остаётся занятым, пока close не будет вызван. Сам он не освободится: это
утечка, просто её причина видна снаружи.
Как отвечать на собеседовании
Короткий ответ: EMFILE — это лимит на число дескрипторов конкретного
процесса, и считаются в нём не файлы, а все открытые объекты — сокеты, каналы,
epoll, плюс три, которые процесс получил при рождении. Лимитов два: мягкий
ядро проверяет при открытии очередного дескриптора, а жёсткий ограничивает то,
насколько высоко процесс может поднять себе мягкий, — и поднять мягкий он может
сам, без перезапуска.
Этого достаточно, чтобы ответить верно. Дальше — то, что добавляют, если собеседник копает.
Если интервьюер копает глубже
Хороший ответ отличают три вещи. Первая — вы различаете EMFILE и ENFILE:
первый про процесс, второй про машину, и лечатся они в разных местах. Вторая —
вы говорите про наследование и знаете, почему ulimit -n в чужой оболочке
ничего не изменил: лимит достаётся от того, кто запускает. Третья — вы
называете способ отличить утечку от нехватки: смотреть на счёт в
/proc/<pid>/fd во времени, а не на факт ошибки.
Дальше спросят
Почему приложение упёрлось в лимит, хотя открывает всего десяток файлов?
Потому что дескрипторы тратят не только файлы. Измерено: сокет, конец канала и
экземпляр epoll расходуют номер так же, как файл. В сервисе основная масса
дескрипторов — это соединения: с базой, с соседними сервисами, принятые от
клиентов.
Поэтому счёт стоит вести не по коду приложения, а по /proc/<pid>/fd. Там
видно и то, чего в коде нет: пулы внутри клиентских библиотек, наблюдатели за
файлами, таймеры.
Можно ли поднять лимит без перезапуска сервиса?
Мягкий — да, и это ровно то, что показывает первый блок прогона: процесс опустил себе мягкий лимит и поднял обратно до жёсткого, ничего не перезапуская. Если в программе есть такой вызов при старте, «поднять лимит» перестаёт быть операцией с простоем.
Выше жёсткого — нет: попытка в прогоне отвергнута. И если жёсткий кто-то понизил, поднять его обратно в том же процессе тоже нельзя — это описано как необратимое действие.
Чем EMFILE отличается от ENFILE и почему это важно?
EMFILE — лимит этого процесса; ENFILE — общесистемный лимит на число
открытых файлов. Разные ошибки, разные потолки, разное лечение: в первом
случае правят лимит процесса, во втором — общесистемную настройку, и правка не
той стороны не даёт ничего.
Практическая ценность различения в том, что оно доступно сразу: имя ошибки уже
есть в логе. Если там ENFILE, виноват может быть вовсе не ваш сервис — он
просто оказался тем, кому не хватило.
Помогает ли поднять лимит до максимума и забыть?
Лимит — это не только защита от вас, но и защита от соседей: он ограничивает
цену чужой ошибки. Подняв его до потолка, вы меняете EMFILE у одного сервиса
на ENFILE у всей машины: общесистемный лимит из /proc/sys/fs/file-max
никуда не делся, и упрётся в него уже не тот, кто виноват.
Полезнее другое: поднять лимит осознанно до величины, которая соответствует
ожидаемому числу соединений, и следить за счётом в /proc/<pid>/fd. Тогда
EMFILE перестаёт быть сюрпризом, а рост счёта становится сигналом заранее.
Частые заблуждения
сообщение Too many open files означает, что дескрипторы кончились на машине
Это лимит процесса: EMFILE — the per-process limit on the number of open file descriptors has been reached
(достигнут лимит числа открытых файловых дескрипторов на процесс). Общесистемное исчерпание даёт другую ошибку — ENFILE, — и правится в другом месте. Первое, что стоит выяснить по логу, — какая из двух букв.
лимит один, и его меняет администратор
Лимитов два, и заняты они разным. Мягкий ядро проверяет в тот момент, когда процесс открывает очередной дескриптор; жёсткий ограничивает то, насколько высоко процесс может поднять себе мягкий. Поднять мягкий до жёсткого процесс может сам и без перезапуска — измерено в первом блоке прогона. Администратор нужен, только когда не хватает жёсткого.
при лимите 64 можно открыть 64 файла
Измерено: 61. Три дескриптора процесс получает при рождении — ввод, вывод, поток ошибок, — и они входят в лимит. Лимит считает не «файлы, которые открыл я», а все занятые номера.
дескрипторы тратятся только на файлы
Измерено при том же лимите: сокет — 61, канал — 60, epoll — 61. В сервисе основная масса дескрипторов приходится на соединения, а канал вообще берёт по два номера сразу, потому что у него два конца.
я поднял лимит через ulimit -n, значит сервису его хватит
Только если сервис запущен из этой самой оболочки: лимит наследуется от того, кто запускает. Измерено: родитель поставил себе 128 — потомок стартовал со 128. Сервису, поднятому менеджером служб, ваш ulimit неизвестен; проверять надо в /proc/<pid>/limits.
если поднять лимит повыше, проблема решена
Решена, если это была честная нехватка: число открытых дескрипторов держится на полке и падает вместе с нагрузкой. Если же оно растёт монотонно и не возвращается — это утечка, и лимит её только отсрочит. Различаются они не по тексту ошибки, а по счёту в /proc/<pid>/fd во времени.
жёсткий лимит можно опустить, а потом вернуть
Нельзя: понижение жёсткого лимита описано как необратимое для непривилегированного процесса. В прогоне попытка поднять его выше текущего значения отвергнута. Вернуть потолок можно только новым процессом, который унаследует его от родителя.
Практика
Две задачи. Сначала ответьте, потом сверьтесь с настоящим выводом: в обеих правильный ответ взят из прогона скрипта, а не назначен.
Практика · что напечатает
name, message, opened, held = probe(SOFT) print(name) print(message) print(inherited_soft(128))
Практика · оцените
Проверка знаний
В логе сервиса — сообщение Too many open files. Что это говорит о лимите?
Это не пересказ и не отдельный текст: всё ниже взято из самой статьи — её выжимка, заголовки разборов, колонка «на самом деле» и таблица версий. Поэтому разойтись со статьёй эти тезисы не могут.
Суть
- Дескриптор — это номер, которым процесс ссылается на что-то открытое, и лимит ограничивает число таких номеров у одного процесса. Лимитов два, и заняты они разным: мягкий ядро проверяет в тот момент, когда процесс открывает очередной дескриптор, а жёсткий ограничивает то, насколько высоко процесс может поднять себе мягкий. Оба принадлежат процессу и достаются его потомкам.
- Отсюда главное следствие: «я поднял лимит» и «сервису его хватило» — разные утверждения. Мягкий процесс поднимает себе сам, без перезапуска и без нового конфига, но стартует он с тем лимитом, который стоял у запустившего: измерено — родитель поставил себе 128, потомок стартовал со 128. Поэтому
ulimit -nв одной оболочке ничего не меняет для сервиса, поднятого из другого места. И считает лимит не файлы, а все занятые номера: при лимите 64 открыть удалось 61, потому что три номера процесс получил при рождении. - Дальше — то, что отличает знающего от читавшего. Ошибка называется
EMFILEи говорит о лимите процесса; измерено: имяEMFILE, текст Too many open files. У машины лимит свой, и его исчерпание даётENFILE— другую ошибку, которая лечится в другом месте. Дескриптором считается не только файл: при том же лимите сокет дал 61, канал 60 (у канала два конца — два номера),epoll— 61, так что каждое соединение клиента базы или HTTP-пула тоже занимает номер. А жёсткий лимит понижается необратимо: непривилегированный процесс может опустить свой потолок, но поднять обратно — уже нет;getrlimit(2)называет понижение необратимым, а в прогоне отвергнута попытка поднять жёсткий выше текущего значения.
На самом деле
- Это лимит процесса:
EMFILE— the per-process limit on the number of open file descriptors has been reached. Общесистемное исчерпание даёт другую ошибку —ENFILE, — и правится в другом месте. Первое, что стоит выяснить по логу, — какая из двух букв. - Лимитов два, и заняты они разным. Мягкий ядро проверяет в тот момент, когда процесс открывает очередной дескриптор; жёсткий ограничивает то, насколько высоко процесс может поднять себе мягкий. Поднять мягкий до жёсткого процесс может сам и без перезапуска — измерено в первом блоке прогона. Администратор нужен, только когда не хватает жёсткого.
- Измерено: 61. Три дескриптора процесс получает при рождении — ввод, вывод, поток ошибок, — и они входят в лимит. Лимит считает не «файлы, которые открыл я», а все занятые номера.
- Измерено при том же лимите: сокет — 61, канал — 60,
epoll— 61. В сервисе основная масса дескрипторов приходится на соединения, а канал вообще берёт по два номера сразу, потому что у него два конца. - Только если сервис запущен из этой самой оболочки: лимит наследуется от того, кто запускает. Измерено: родитель поставил себе 128 — потомок стартовал со 128. Сервису, поднятому менеджером служб, ваш
ulimitнеизвестен; проверять надо в/proc/<pid>/limits. - Решена, если это была честная нехватка: число открытых дескрипторов держится на полке и падает вместе с нагрузкой. Если же оно растёт монотонно и не возвращается — это утечка, и лимит её только отсрочит. Различаются они не по тексту ошибки, а по счёту в
/proc/<pid>/fdво времени. - Нельзя: понижение жёсткого лимита описано как необратимое для непривилегированного процесса. В прогоне попытка поднять его выше текущего значения отвергнута. Вернуть потолок можно только новым процессом, который унаследует его от родителя.
Что разобрано
- Что здесь на самом деле спрашивают
- База: дескриптор — это номер, а не файл
- Механизм 1: лимитов два, и это не дублирование
- Механизм 2: EMFILE — про процесс, а не про машину
- Механизм 3: дескриптор — это не файл
- Механизм 4: лимит наследуется, и поэтому «я поднял, а не помогло»
- Глубже: утечка или честная нехватка
- Как отвечать на собеседовании
- Дальше спросят
- Частые заблуждения
- Практика
- Проверка знаний
Источники и что читать дальше
4 ИСТОЧНИКА
- getrlimit(2), Linux man-pages 6.7Официальная документация. Откуда берутся два лимита и кто может их менять: «The soft limit is the value that the kernel enforces for the corresponding resource. The hard limit acts as a ceiling for the soft limit: an unprivileged process may set only its soft limit to a value in the range from 0 up to the hard limit, and (irreversibly) lower its hard limit» (Мягкий лимит — это значение, которое ядро проводит в жизнь для соответствующего ресурса. Жёсткий лимит служит потолком для мягкого: непривилегированный процесс может лишь выставить свой мягкий лимит в диапазоне от 0 до жёсткого и (необратимо) понизить свой жёсткий). И что именно ограничивает RLIMIT_NOFILE: «This specifies a value one greater than the maximum file descriptor number that can be opened by this process. Attempts (open(2), pipe(2), dup(2), etc.) to exceed this limit yield the error EMFILE» (Задаёт значение на единицу больше максимального номера файлового дескриптора, который может открыть этот процесс. Попытки (open(2), pipe(2), dup(2) и т. д.) превысить этот лимит дают ошибку EMFILE).https://man7.org/linux/man-pages/man2/getrlimit.2.html
- open(2), Linux man-pages 6.7Официальная документация. Различение, на котором строится половина урока. Про лимит процесса: «The per-process limit on the number of open file descriptors has been reached» (Достигнут лимит числа открытых файловых дескрипторов на процесс). Про лимит машины: «The system-wide limit on the total number of open files has been reached» (Достигнут общесистемный лимит на общее число открытых файлов). Первое — это EMFILE, второе — ENFILE: разные ошибки, разные лимиты и разное лечение.https://man7.org/linux/man-pages/man2/open.2.html
- execve(2), Linux man-pages 6.7Официальная документация. Почему лимит достаётся потомку — правило сформулировано от обратного: «All process attributes are preserved during an execve(), except the following» (При execve() сохраняются все атрибуты процесса, кроме следующих), и ограничений ресурсов в списке исключений нет. Отсюда практический вывод урока: лимит выставляют тому, кто запускает, а не тому, кто работает.https://man7.org/linux/man-pages/man2/execve.2.html
- proc(5), Linux man-pages 6.7Официальная документация. Где смотреть, а не гадать. Счёт открытых дескрипторов: «This is a subdirectory containing one entry for each file which the process has open, named by its file descriptor» (Это подкаталог, содержащий по одной записи на каждый открытый процессом файл, названной по его файловому дескриптору). Действующие лимиты процесса: «This file displays the soft limit, hard limit, and units of measurement for each of the process's resource limits» (Этот файл показывает мягкий лимит, жёсткий лимит и единицы измерения для каждого из ограничений ресурсов процесса). И общесистемный потолок, из-за которого возникает ENFILE: «This file defines a system-wide limit on the number of open files for all processes. System calls that fail when encountering this limit fail with the error ENFILE» (Этот файл задаёт общесистемный лимит на число открытых файлов для всех процессов. Системные вызовы, упирающиеся в этот лимит, завершаются ошибкой ENFILE).https://man7.org/linux/man-pages/man5/proc.5.html