lsof: открытые файлы, дескрипторы, кто держит файл и порт

Рейтинг: 67.6% · 8 голосов
Подробный курс по диагностике и производительности Linux для админов и DevOps: с чего начать, методология USE, логи (journalctl, dmesg), процессы, CPU, память и OOM, диск и IO (iostat), сеть (ss, tcpdump), системные вызовы и strace, ltrace, perf, флеймграфы, ftrace, eBPF и bpftrace, lsof, мониторинг. Разбор каждого инструмента с чтением вывода.
Ответить
Аватара пользователя
Dmitry_SRE
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

lsof: открытые файлы, дескрипторы, кто держит файл и порт

Сообщение Dmitry_SRE »

Оглавление курса (47)
  1. С чего начать диагностику Linux: методология вместо паники
  2. Первые 60 секунд: экспресс-диагностика нагруженного сервера
  3. Методологии диагностики: USE, RED и здравый смысл
  4. Логи systemd через journalctl: где искать причину
  5. Где лежат логи Linux: /var/log, dmesg и rsyslog
  6. Источники правды: load average, /proc и /sys
  7. Процессы Linux: ps, pstree и состояния процессов
  8. Интерактивный мониторинг: top и htop
  9. Метрики процессов во времени: pidstat
  10. Приоритеты и ограничения: nice, ionice, cgroups
  11. Сигналы и зависшие процессы: kill, и что делать с D-state
  12. Загрузка CPU: user, system, iowait, steal и контекст-свитчи
  13. Диагностика CPU по ядрам: vmstat и mpstat
  14. Профилирование CPU: perf top и поиск пожирателя
  15. Частоты, троттлинг и NUMA: turbostat и numastat
  16. Память Linux: RSS, VSZ, page cache и миф о нехватке памяти
  17. Сколько памяти занято: free, /proc/meminfo и vmstat
  18. Поиск утечек и пожирателей памяти: smem, pmap, smaps
  19. Swap и подкачка: swapon, swappiness, когда своп - это боль
  20. OOM killer: кто и за что убил процесс
  21. Память ядра и slab: slabtop и куда уходит RAM
  22. Подсистема ввода-вывода: путь запроса от приложения до диска
  23. Диагностика диска: iostat и чтение await, %util
  24. Кто грузит диск: iotop и атрибуция io процессам
  25. Файловые системы: df, du, иноды и куда делось место
  26. Глубокая диагностика блочного слоя: blktrace и biolatency
  27. Кеш страниц и грязные данные: dirty pages, fsync, drop_caches
  28. Сетевой стек Linux: путь пакета и где возникают задержки
  29. Сокеты и соединения: ss и netstat
  30. Захват трафика: tcpdump для диагностики сети
  31. Задержки и потери в сети: ping, mtr, диагностика латентности
  32. Сеть как источник проблем: nftables, conntrack, дропы
  33. Что такое системный вызов и зачем его трассировать
  34. strace: трассировка системных вызовов на практике
  35. strace в бою: почему программа висит, падает или тормозит
  36. ltrace: трассировка вызовов библиотек
  37. Накладные расходы трассировки и безопасные альтернативы
  38. perf: универсальный профайлер Linux
  39. Флеймграфы: визуализация профиля производительности
  40. Трассировка ядра: ftrace и trace-cmd
  41. eBPF: революция в наблюдаемости Linux
  42. Инструменты eBPF на практике: BCC и bpftrace
  43. lsof: открытые файлы, дескрипторы, кто держит файл и порт (вы здесь)
  44. Разбор кейса: приложение тормозит - пошаговая диагностика
  45. Непрерывный мониторинг: sar, node_exporter, Grafana, Zabbix
  46. Диагностика в контейнерах: cgroups, лимиты, Docker и Kubernetes
  47. Карта инструментов и сквозной разбор инцидента производительности
Знакомая боль: пытаешься размонтировать диск, а система отвечает "target is busy". Или сервис не стартует - "address already in use", порт занят, а кем именно - непонятно. Или совсем коварное: df говорит, что раздел забит под завязку, а du по тому же разделу показывает, что данных там в разы меньше. Все три загадки решает одна утилита - lsof (list open files). В этом уроке разберём её руками так, чтобы ты не просто запомнил команды, а понимал каждую колонку вывода и знал, куда смотреть дальше. И сразу пометим: на 2026 год у lsof появился штатный Linux-преемник - lsfd из util-linux, про него тоже расскажем, потому что в свежих дистрибутивах он уже под рукой.

Почему lsof в Linux видит почти всё

Главная философия Unix - "всё есть файл". Обычный файл на диске - файл. Каталог - файл. Сетевой сокет, через который веб-сервер слушает порт 443 - тоже файл (точнее, файловый дескриптор). Пайп между процессами, символьное устройство /dev/null, загруженная в память разделяемая библиотека, eventfd, inotify-наблюдатель - всё это для ядра однотипные объекты, на которые процесс ссылается через файловый дескриптор (file descriptor, сокращённо fd).

Дескриптор - это просто целое неотрицательное число, индекс в таблице открытых файлов конкретного процесса. Когда программа делает системный вызов open(), socket() или accept(), ядро возвращает ей наименьший свободный номер: 3, 4, 5 и так далее (0, 1, 2 заняты под stdin, stdout, stderr). Дальше процесс работает с объектом по этому номеру и обязан вызвать close(), когда закончил. Забыл закрыть - дескриптор "течёт".

lsof проходит по всем процессам и показывает, какие объекты каждый из них держит открытыми. Под капотом она читает псевдо-файловую систему /proc: в каталоге /proc/PID/fd лежат символические ссылки на всё, что открыл процесс с этим PID. lsof собирает это в удобную таблицу. Глянуть можно и без lsof, голыми руками:

Код: Выделить всё

ls -l /proc/$$/fd
# lrwx------ 1 user user 64 ... 0 -> /dev/pts/0
# lrwx------ 1 user user 64 ... 1 -> /dev/pts/0
# lrwx------ 1 user user 64 ... 2 -> /dev/pts/0
Здесь $$ - PID твоей текущей оболочки. Видишь дескрипторы 0, 1, 2, указывающие на терминал. lsof делает то же самое, только для всех процессов сразу и аккуратно. На большинстве систем lsof уже стоит; если нет - ставится из штатных репозиториев (apt install lsof, dnf install lsof). На Astra Linux и RED OS пакет тоже называется lsof.

Изображение

Читаем вывод lsof по колонкам

Самый частый запуск - посмотреть, что открыл конкретный процесс по его PID:

Код: Выделить всё

sudo lsof -p 774
Вывод выглядит так (сокращённо):

Код: Выделить всё

COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
nginx   774 root  cwd    DIR  259,2     4096      2 /
nginx   774 root  rtd    DIR  259,2     4096      2 /
nginx   774 root  txt    REG  259,2  1264424 131841 /usr/sbin/nginx
nginx   774 root  mem    REG  259,2  2030928 131720 /usr/lib/x86_64-linux-gnu/libc.so.6
nginx   774 root    0u   CHR    1,3      0t0      6 /dev/null
nginx   774 root    6u  IPv4  28931      0t0    TCP *:443 (LISTEN)
nginx   774 root    9w   REG  259,2    51234 131900 /var/log/nginx/access.log
Разберём колонки - это самое важное, без понимания полей команда бесполезна:
  • COMMAND - имя процесса (по умолчанию обрезано до 9 символов; флаг +c 0 или +c 15 расширяет ширину, если имя длинное).
  • PID - его номер. По нему ты находишь и при необходимости прибиваешь процесс.
  • USER - от какого пользователя процесс запущен.
  • FD - тот самый дескриптор. Тут либо число с буквой режима (0u, 6u, 9w), либо специальное слово. Буквы: r - открыт на чтение, w - на запись, u - и чтение, и запись. Спецслова: cwd - текущий рабочий каталог процесса, rtd - корневой каталог (root), txt - исполняемый код самой программы (text), mem - файл, отображённый в память (обычно библиотеки через mmap). Если видишь DEL - файл удалён, но mmap-отображение ещё держится в памяти (запомни, к этому вернёмся).
  • TYPE - тип объекта: REG - обычный файл, DIR - каталог, CHR - символьное устройство, IPv4/IPv6 - сетевой сокет, unix - локальный сокет, FIFO - именованный пайп, a_inode - анонимный inode (epoll, eventfd, inotify).
  • DEVICE - номера устройства (мажор, минор) или адрес объекта в ядре.
  • SIZE/OFF - размер файла либо текущее смещение чтения/записи (0t0 значит "смещение 0", запись 0t показывает десятичное смещение).
  • NODE - номер inode для файла, либо протокол (TCP/UDP) для сокета.
  • NAME - путь к файлу или описание соединения, например *:443 (LISTEN).
Уже из этого вывода видно, что nginx с PID 774 слушает порт 443 (строка с дескриптором 6u, TCP *:443 LISTEN) и пишет в access.log (дескриптор 9w, открыт на запись). Это и есть навык: не вызвать команду, а прочитать таблицу.

Практика: кто держит файл и кто держит порт

Кто держит файл в Linux. Классика - не можешь размонтировать или удалить раздел, потому что файл "занят". Передаём lsof путь:

Код: Выделить всё

sudo lsof /var/log/nginx/access.log
Утилита покажет все процессы, у которых этот файл открыт - с их PID и дескрипторами. Хочешь проверить целый каталог или точку монтирования перед размонтированием? Варианты:

Код: Выделить всё

sudo lsof +D /var/log/nginx     # рекурсивно по всему дереву, полно, но медленно
sudo lsof +d /var/log/nginx     # только сам каталог, без спуска вглубь
sudo lsof /mnt/disk             # быстрая проверка точки монтирования целиком
Если нужен один ответ "кто и можно ли убить" - проще fuser: sudo fuser -vm /mnt/disk покажет процессы на файловой системе, а fuser -km /mnt/disk их прибьёт (осторожно). lsof же даёт развёрнутую картину - что именно за дескрипторы держатся.

Кто держит порт. Сервис ругается "address already in use". Узнаём владельца порта:

Код: Выделить всё

sudo lsof -i :443 -nP
# COMMAND PID USER FD TYPE DEVICE NODE NAME
# nginx   774 root  6u IPv4  28931  TCP *:443 (LISTEN)
Видим: порт 443 держит nginx, PID 774. Можно фильтровать по протоколу: lsof -i TCP, lsof -i UDP, по версии lsof -i 4 / -i 6, или конкретное соединение lsof -i TCP:443. Полезные флаги: -n не резолвит IP в имена хостов (быстрее, не виснет на DNS), -P не превращает номера портов в имена служб (показывает 443, а не https). Запомни связку -nP - на проде она спасает от подвисаний.

Важная оговорка на 2026: для сетевых вопросов lsof давно не первый выбор. Современный штатный инструмент - ss из пакета iproute2: ss -tlnp покажет все слушающие TCP-сокеты с процессами быстрее и точнее, чем lsof, и без обхода всех /proc. lsof -i удобен, когда нужно одной командой увидеть и сетевые сокеты, и файлы процесса вместе. Держи в голове обе: ss -tulpn для сети, lsof -p PID для полной картины процесса.

Утечка дескрипторов и ошибка too many open files

Теперь та самая прод-ошибка. У каждого процесса есть лимит на число открытых дескрипторов. Упёрся в него - получаешь в логах "Too many open files" (errno EMFILE), и приложение начинает отбрасывать соединения и не может открывать файлы. Смотрим лимит:

Код: Выделить всё

ulimit -n                                  # мягкий лимит текущей сессии
cat /proc/774/limits | grep "open files"
# Max open files   1024   524288   files
Тут soft-лимит 1024, hard 524288. На 2026 многие дистрибутивы с systemd ставят дефолтный soft уже выше (часто 1024 для сессии, но DefaultLimitNOFILE у сервисов поднят) - всегда сверяйся с /proc/PID/limits самого процесса, а не с ulimit в своей оболочке: у демона под systemd лимит свой. Считаем, сколько дескрипторов реально занято:

Код: Выделить всё

ls /proc/774/fd | wc -l        # быстрый способ, прямо из /proc
sudo lsof -p 774 | wc -l       # то же через lsof (чуть больше из-за cwd/rtd/txt/mem)
Если число близко к лимиту и со временем только растёт - это утечка дескрипторов (fd leak): код открывает файлы или сокеты и забывает их закрывать. Найти источник помогает группировка по типу:

Код: Выделить всё

sudo lsof -p 774 | awk '{print $5}' | sort | uniq -c | sort -rn
# 8901 IPv4
#  120 REG
#  ...
8901 открытых TCP-сокетов - явный признак, что приложение не закрывает соединения (типичная утечка: HTTP-клиент без пула или без закрытия тела ответа). Можно сузить ещё: посмотреть состояния сокетов (много CLOSE_WAIT - точно забытый close на нашей стороне). Под systemd мягкий лимит правильно поднимать через LimitNOFILE= в unit-файле (или dr=in override), а не через ulimit в скрипте запуска - ulimit на уже запущенный сервис не действует.

Удалённый, но открытый файл - почему df и du расходятся

Третья загадка, и она же самая частая причина внезапно "забитого" диска. В Linux, если процесс держит файл открытым, а ты его rm-нул, место не освобождается до закрытия дескриптора. Файл исчезает из каталога, du его не видит (ему нечего обходить - имени больше нет), но блоки на диске заняты, и df это честно показывает. Найти таких "призраков" умеет lsof:

Код: Выделить всё

sudo lsof +L1
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
# java   2210 app  35w  REG  259,2 9.2G        0  4521 /var/log/app.log (deleted)
Флаг +L1 показывает файлы, у которых число жёстких ссылок (NLINK) меньше 1, то есть нет ни одной директории-записи, но дескриптор открыт. У удалённого открытого файла NLINK равен 0 и в NAME видна пометка (deleted). Здесь java занял 9.2 ГБ удалённым логом. Лечение по приоритету:
  • Самое чистое - корректно перезапустить или сигнализировать процессу переоткрыть файлы (для логов часто помогает logrotate с copytruncate или kill -HUP/-USR1, если сервис умеет переоткрывать лог).
  • Если ронять прод нельзя и файл - именно лог в режиме append, можно обнулить его через дескриптор, не убивая процесс:

Код: Выделить всё

sudo truncate -s 0 /proc/2210/fd/35
# или эквивалент через перенаправление:
sudo sh -c ': > /proc/2210/fd/35'
После этого место вернётся сразу. Но это острый инструмент, и вот честное предупреждение (актуально и в 2026): трюк безопасен только для файлов, в которые пишут в режиме append (логи). Если процесс держит смещение записи и пишет по позиции (например, пишет не в конец, а по абсолютному offset), после truncate ты получишь sparse-файл: смещение останется большим, диск снова "распухнет" при следующей записи, а данные между нулём и старым offset станут дырой. Поэтому: truncate-через-fd - для append-логов да, для файлов БД, образов, рабочих данных - нет, там только рестарт. Это любимый трюк дежурного, но применять его надо понимая, что внутри.

lsfd - современная замена lsof (актуально на 2026)

С версии util-linux 2.38 в дистрибутивах появился lsfd (list file descriptors) - Linux-специфичная замена lsof. Он не drop-in (другой CLI и формат), зато заточен ровно под Linux: понимает cgroups, сетевые namespace, отдаёт JSON, и колонки задаёшь сам. Базовый запуск:

Код: Выделить всё

lsfd                                   # все дескрипторы системы
lsfd -p 774                            # как lsof -p
lsfd --list-columns                    # все доступные колонки
lsfd -Q 'TYPE == "IPv4"' -o PID,COMMAND,NAME   # фильтр-запрос
lsfd -J -o PID,FD,NAME                 # JSON для скриптов
Главное отличие в подходе: у lsfd есть язык фильтров (-Q / --filter) и явный выбор колонок (-o / --output), поэтому в скриптах он надёжнее lsof, где парсят пробелы awk-ом. В руководстве прямо советуют: в скриптах всегда задавай --output, не полагайся на дефолтный вывод. Для интерактивной отладки lsof пока удобнее и привычнее, но lsfd стоит знать - в новых системах он уже есть, и за ним будущее этой задачи. В одну линейку поставь и /proc напрямую (ls /proc/PID/fd), ss для сети, fuser для быстрого "кто держит" - lsof и lsfd это самые подробные из них.

Типичные грабли и заблуждения
  • Запуск без sudo. lsof покажет только твои процессы и молча умолчит о чужих - отсюда классика "lsof -i :443 ничего не выводит, хотя порт занят". Чужие порты и файлы root видны только под sudo.
  • Пустой вывод lsof -i при занятом порте бывает и из-за сетевых namespace: сокет живёт внутри контейнера (Docker, Podman, systemd-nspawn). Тогда смотри изнутри неймспейса (nsenter -t PID -n ss -tlnp) или используй lsof/ss с правами на хосте.
  • +D на большом каталоге может работать минуты: он обходит дерево рекурсивно и stat-ит каждый файл. Для точки монтирования хватит lsof /mnt/disk.
  • Имя в COMMAND обрезано до 9 символов - не пугайся куцых названий, ориентируйся на PID или добавь +c 0.
  • ulimit -n меняет лимит только для текущей оболочки и её потомков. На уже запущенный сервис он не влияет - правь LimitNOFILE= в unit-файле и делай systemctl daemon-reload + рестарт.
  • Заблуждение "удалил большой файл - место сразу вернулось". Нет: пока хоть один процесс держит fd, блоки заняты. Проверяй lsof +L1.
Мини-лаба: повтори руками сейчас
  • Выполни ls -l /proc/$$/fd и найди дескрипторы 0, 1, 2 - это твой терминал.
  • Запусти sleep 600 & запиши PID, в другом окне sudo lsof -p PID - посмотри cwd, rtd, txt.
  • Сделай так: echo test > /tmp/test.log; tail -f /tmp/test.log & ; rm /tmp/test.log; потом sudo lsof +L1 | grep test - найди (deleted) файл, посмотри NLINK 0.
  • Освободи его, не убивая tail: найди fd через ls -l /proc/PID/fd и сделай sudo truncate -s 0 /proc/PID/fd/N (для append-лога безопасно).
  • Проверь, кто держит порт: sudo lsof -i :22 -nP, а затем сравни с sudo ss -tlnp 'sport = :22'.
  • Если есть util-linux 2.38+: повтори lsof -p PID через lsfd -p PID, сравни вывод.
Контрольные вопросы
  • Что означают значения cwd, txt, mem и DEL в колонке FD?
  • Каким одним флагом lsof найти удалённые, но всё ещё открытые файлы, съевшие место, и какое значение NLINK у них?
  • Как узнать лимит дескрипторов процесса и сколько он занял сейчас, и почему ulimit -n в твоей оболочке тут может врать?
  • Чем lsof -i :443 удобнее и в чём проигрывает команде ss -tlnp? И что такое lsfd?
Что запомнить

lsof - твой рентген файловых дескрипторов. Колонки FD и NAME - главные: по ним видно, держит процесс файл, сокет или библиотеку. lsof файл, lsof +D и fuser отвечают "кто держит файл linux" перед размонтированием. lsof -i :порт -nP находит владельца порта, но для сети на 2026 первым бери ss -tlnp. ulimit/limits плюс счёт через /proc/PID/fd ловят утечку и ошибку too many open files. lsof +L1 - способ увидеть удалённые открытые файлы (NLINK 0), из-за которых расходятся df и du; чинить рестартом, а truncate-через-fd только для append-логов. А в свежих системах присматривайся к lsfd - штатной Linux-замене lsof с фильтрами и JSON.
👍2 ❤️3 🔥1 😄 🤔5
Аватара пользователя
golangpro
Сообщения: 1
Зарегистрирован: 04 июн 2026, 12:01

Re: lsof: открытые файлы, дескрипторы, кто держит файл и порт

Сообщение golangpro »

Спасибо, наконец дошло почему df показывает забитый диск, а du - пусто. Сегодня поймал java с удаленным логом на 9 гигов через lsof +L1, truncate спас прод без рестарта. Хорошо что предупредили про append - у меня как раз лог, обошлось.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
robwees
Сообщения: 1
Зарегистрирован: 16 май 2026, 07:44

Re: lsof: открытые файлы, дескрипторы, кто держит файл и порт

Сообщение robwees »

Про namespace - в точку. У меня lsof -i :443 пусто, а порт занят nginx в докере. Пришлось nsenter в неймспейс контейнера. Раньше думал что просто sudo забыл. И lsfd попробовал, json реально удобнее парсить чем awk по lsof.
👍 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Инструменты eBPF на практике: BCC и bpftrace
Следующая глава →
Разбор кейса: приложение тормозит - пошаговая диагностика

Все главы курса «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Поделиться темой: ✈ Telegram VK
Похожие запросы: lsof кто держит файл и порт в linux.gitignore: как игнорировать файлыGit LFS: большие файлы в репозитории

Вернуться в «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость