В жизни каждого пользователя Linux наступает момент, когда в ответ на
команду "umount /mnt/cdrom/" выдается безжалостное "umount:
/mnt/cdrom: device is busy". После этого начинается изнурительная
(особенно в случае сильно загруженной системы) процедура убиения
процессов "по одному" с целью найти источник блокировки. Однако,
проблему можно решить более изящным способом...
Виктор Костромин (kos AT rus-linux.net)
Во время работы в Linux мне иногда приходилось сталкиваться с одной
проблемкой. Установил я, скажем, диск CD-ROM в привод, смонтировал
его, потом в процессе работы запустил кучу разных приложений, про CD в
приводе уже и забыл. Затем мне потребовалось что-то прочитать с
другого диска, я жму на кнопочку извлечения диска на панели CD-ROM,
однако реакции никакой не следует. А в ответ на команду
[user]$ umount /mnt/cdrom/
выдается такое сообщение:
umount: /mnt/cdrom: device is busy
Приложений у меня открыто много, так что я уже и не помню, какое из
них открыло файлы на CD и тем самым препятствует размонтированию
диска. И иногда даже последовательный перебор всех открытых окон не
позволял понять, что же надо закрыть. Я помнил, правда, что есть
статья С.Лапшанского с описанием решения аналогичной проблемы, да все
руки не доходили заняться этим. Но вот надоело мне в конце концов все
время наступать на одни и те же грабли, и решил я разобраться с CD-ROM
и его размонтированием. Вначале просмотрел я свой каталог на сайте
http://rus-linux.net и статью Лапшанского [2] разыскал, но
оказалось, что рассматривается в ней аналогичная проблема во FreeBSD,
и решается она с помощью утилиты fstat. Я, конечно, попытался вызвать
эту команду в своей системе на основе дистрибутива ASP Linux 7.3, но
такой команды в моей системе не обнаружилось. Команда man fstat
сообщила, что есть такой системный вызов fstat, который выдает
информацию об указанном файле, но писать собственную утилиту на основе
этого системного вызова мне, прямо признаюсь, не по силам.
Сейчас я даже уже и не вспомню, откуда я это знал, но где-то в
глубинах подсознания мелькнула мысль, что для решения этой проблемы
можно использовать утилиту lsof. Кстати, один из читателей моей книги
[3] в своем отзыве на книгу в качестве недостатка указал на то, что
эта утилита в книге не описана. Я и решил попытаться применить lsof
для решения своей задачи, и начал, естественно, с изучения справки,
выдаваемой командой man lsof. А кроме того, поискал информацию в
Интернет и нашел статьи [[[http://www.linuxcenter.ru/lib/system/lsof.phtml#links 4-8].]] Все эти материалы и послужили
основой для настоящей статьи.
Для чего служит команда lsof
Относительно lsof справка man как раз сообщает, что lsof есть
сокращение от LiSt of Open Files, и что утилита эта служит для вывода
информации о том, какие файлы используются теми или иными процессами.
Причем утилита эта имеется в очень многих версиях и диалектах UNIX,
включая Linux версии 2.1.72 и выше, а также в HP-UX, AIX, NextStep,
Apple Darwin для Power Macintosh, SCO UnixWare, Solaris, FreeBSD,
NetBDS, OpenBSD и так далее.
Создателем программа lsof является Victor A. Abell, его домашняя
страничка расположена по адресу http://people.freebsd.org/~abe/ ,
где вы можете найти исходные коды программы. Их можно также скачать с
FTP-сайта ftp://vic.cc.purdue.edu/pub/tools/unix/lsof. О том, как
скомпилировать и установить программу, вы можете прочитать в статье .
Если запустить эту утилиту без параметров, выдается информация о всех
работающих процессах и открытых ими файлах. Даже в моей системе с
одним пользователем эта команда выдала 2344 строки текста. Попробуй
проанализируй эту массу информации! Но давайте взглянем хотя бы на
несколько строк ее вывода, чтобы на этом примере понять, какую же
информацию она выдает.
Кстати, если вы будете экспериментировать, имейте в виду, что будучи
запущенной простым пользователем, эта команда выдает информацию только
о процессах, запущенных этим пользователем. Поэтому во многих случаях
вы можете не получить от нее никакого ответа. Если вы хотите получить
вразумительный ответ на любой свой запрос, ее необходимо запускать от
имени root-а (можно воспользоваться следующей формой запуска sudo
lsof). Кроме того, команда отрабатывает достаточно медленно, так что
будьте терпеливы и не щелкайте раньше времени <Ctrl>+<C> - программа
не зависла, просто идет поиск информации.
А теперь смотрим листинг 1 (или результат работы программы на вашем
экране).
Листинг 1. Вывод команды lsof.
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
init 1 root cwd DIR 3,3 4096 2 /
init 1 root txt REG 3,3 27036 161176 /sbin/init
init 1 root mem REG 3,3 103044 160493 /lib/ld-2.3.2.so
init 1 root 10u FIFO 3,3 71954 /dev/initctl
portmap 1528 rpc 3u IPv4 1656 UDP *:sunrpc
xinetd 1649 root 0r CHR 1,3 65970 /dev/null
rpc.mount 1682 root 5u unix 0xc34cea80 10311 socket
nmbd 1790 root 6u IPv4 2456 UDP *:netbios-ns
sh 23252 kos 4u CHR 5,0 70758 /dev/tty
sh 23252 kos 6u CHR 136,3 5 /dev/pts/3
sh 23252 kos 8w FIFO 0,5 840800 pipe
sh 23253 kos cwd DIR 3,3 4096 160487 /usr/share/m
an
sh 23253 kos rtd DIR 3,3 4096 2 /
sh 23253 kos txt REG 3,3 626028 160664 /bin/bash
Как видите, один и тот же процесс открывает много файлов, каждому из
которых соответствует строка в выводе lsof . В начале строки
указывается имя процесса, его идентификатор, имя пользователя,
запустившего процесс. В столбце FD стоит номер файлового дескриптора
или одна из следующих буквенных комбинаций (я не берусь корректно
перевести на русский язык некоторых из расшифровок этих сокращений,
так что привожу их в том виде, как они выдаются справочной системой):
* cwd - текущий рабочий каталог;
* ltx - текст разделяемой библиотеки;
* mxx - hex memory-mapped type number xx.
* m86 - DOS Merge mapped file;
* mem - файл, загруженный в память (memory-mapped file), чаще всего - библиотека,
* mmap - memory-mapped device;
* pd - родительский каталог;
* rtd - корневой каталог;
* txt - текст программы (код и данные);
* v86 - VP/ix mapped file.
Номер файлового дескриптора сопровождается символом, указывающим
режим, в котором файл был открыт:
* r - файл открыт для чтения;
* w - файл открыт для записи;
* u - файл открыт для чтения и для записи;
* пробел - режим доступа неизвестен и файл не блокирован;
* `-' - режим доступа неизвестен, но на файл установлена блокировка.
В последнем случае за дефисом следует еще один символ, определяющий
тип блокировки (подробнее смотри man lsof).
В следующем столбце указан тип файла. Чаще других встречаются файлы
одного из следующих типов: REG - обычный файл, DIR - каталог, BLK -
файл блочного устройства, CHR - файл символьного устройства, LINK -
файл символической ссылки, INET - Internet-сокет, UNIX - доменный
сокет UNIX. Но на справочной странице список типов гораздо обширнее.
Вслед за типом указывается устройство, на котором расположен файл,
размер файла, номер индексного дескриптора и имя файла.
Опции команды
Поскольку просматривать вывод команды lsof, запущенной без параметров,
достаточно неудобно, можно сократить объем выдаваемых данных,
конкретизировав ваш запрос, что достигается за счет использования
опций. Опций у этой утилиты довольно много и большинство из них
действует по принципу ограничения вывода. Если вы указываете,
например, опцию -U, то будут выведены только данные о сокетах UNIX, а
все другие файлы игнорируются. Если указать две или более опций, то их
действие определяется правилом ИЛИ, то есть выводится информация,
определяемая каждой из опций. Например, команда
/usr/sbin/lsof -U -u kos
выдаст данные о всех открытых UNIX-сокетах и всех файлах,
принадлежащих процессам, запущенным пользователем "kos".
Если же необходимо скомбинировать действие опций по принципу
логического И, то это делается путем специальной опции -a. Например,
команда
/usr/sbin/lsof -a -U -u kos
выдаст список только сокетов UNIX, принадлежащих процессам, владельцем
которых является пользователь "kos"'. Обратите внимание на то, что
опция -a стоит не между объединяемыми ею другими опциями. Впрочем, ее
можно ставить где угодно, все равно все перечисленные в командной
строке опции будут работать по принципу "логическое И".
Но если указывается, например, несколько однотипных опций, то вначале
к ним применяется операция логического ИЛИ, а затем уже работает опция
-a. Пример: по команде
/usr/sbin/lsof -i@aaa.bbb -i@ccc.ddd -a -ufff,ggg
будет выведен список файлов, принадлежащих ЛИБО пользователю "fff",
ЛИБО пользователю "ggg" И имеющих сетевые соединения ЛИБО к хосту
aaa.bbb ЛИБО к хосту ccc.ddd.
Опции команды lsof можно записывать одну за другой без пробелов,
предваряя это список всего одним дефисом, то есть вместо
/usr/sbin/lsof -a -b -C
можно указать просто
/usr/sbin/lsof -abC
Однако при этом надо побеспокоиться о том, чтобы не было
неоднозначности в интерпретации опций. Но я не буду детально
рассматривать возможные причины неоднозначности, если у вас возникает
желание использовать эту возможность, смотрите соответствующую
man-страницу.
Отмечу только, что существует ряд опций, которые позволяют изменить
вывод программы lsof. Например, опция -R заставляет lsof дополнительно
выводить данные об идентификаторе родительского процесса. Очень
интересной может оказаться опция -F. Запущенная с этой опцией утилита
lsof выдает информацию не в виде таблицы, а в виде последовательности
отдельных строк. В таком случае вывод можно перенаправить на вход
другой программы, которая будет каким-либо образом обрабатывать
полученную информацию. После опции -F можно указать, какие именно поля
будут присутствовать в выводе. Например, если командная строка имела
вид
[user]$ /usr/sbin/lsof -F pcfn
в выводе lsof будет присутствовать идентификатор процесса (p), имя
команды (c), файловый дескриптор (f) и ися файла.
Применение команды lsof
Не имеет смысла повторять в статье описание всех опций этой команды,
имеющееся на страничке man. Давайте ограничимся тем, что рассмотрим ее
применение и использование наиболее употребительных опций на примерах
конкретных ситуаций.
Пример 1. Кто работает с файлом или каталогом?
Начнем с решения той проблемы, которая была описана в начале статьи, и
которая рассматривалась в упомянутой выше статье С.Лапшанского .
Итак, вы попытались размонтировать CD-ROM (захотели сменить диск). А в
ответ получили сообщение "Device is busy". Это означает, что какой-то
процесс открыл файл, расположенный на этом устройстве. А чтобы узнать,
какие файлы открыты в том или ином каталоге, служит опция +d команды
lsof. Поэтому, чтобы узнать, кем занят диск, дайте команду следующего
вида:
[user]$ /usr/sbin/lsof +d /mnt/cdrom
(у меня CD-ROM монтируется в каталог /mnt/cdrom). В ответ я получил
такое сообщение:
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
mc 1547 kos cwd DIR 11,0 2048 53248 /mnt/cdrom
bash 1556 kos cwd DIR 11,0 2048 53248 /mnt/cdrom
Как видите, сразу становится ясно, что дисковод используется
пользователем kos, причем, видимо, какой-то подкаталог каталога
/mnt/cdrom открыт в одной из панелей файлового менеджера Mifnight
Commander (mc).
Надо отметить, что при использовании опции +d dir выдается информация
только о тех файлах и подкаталогах, которые содержатся в самом
каталоге dir, а не в его подкаталогах. Для решения рассматриваемой
проблемы этого вполне достаточно, потому что если обычно в выводе
команды указан и файл данного каталога. Но, если вы все же захотите
получить список всех открытых файлов во всех вложенных подкаталогах
каталога dir, надо воспользоваться опцией +D. Попробуем, например,
посмотреть, какие файлы открыты в каталоге /var/ и всех его
подкаталогах. В работающей системе всегда найдутся открытые файлы из
этого каталога, поэтому на этом примере очень наглядно демонстрируется
разница между опциями +d и +D:
[root]# /usr/sbin/lsof +D /var/
(Упомяну для точности, что символические ссылки игнорируются и поэтому
в выводе этой команды не перечисляются.)
Если вы почему либо хотите указать не имя каталога или файла, а номер
файлового дескриптора, вы можете воспользоваться командой lsof -f fd,
где fd есть список файловых дескрипторов, разделенных запятыми. В
списке можно использовать диапазоны: 1-3,5 и те буквенные сочетания,
которые были упомянуты выше, например, cwd,ltx,mem.
Между прочим, когда я экспериментировал с командой lsof, выяснилось,
что файловый менеджер Konqueror не "держит" устройство: несмотря на
то, что в Konqueror открыт каталог, расположенный на примонтированном
CD, операция размонтирования устройства выполняется без проблем.
По-видимому, Konqueror (как, вероятно, и другие браузеры) скачивает
файл и закрывает соединение (файл).
Пример 2. Какой процесс использует данный файл?
[root]# /usr/sbin/lsof /etc/passwd
Приведенный пример очень похож на предыдущий, только вы получаете
сведения о процессах, использующих конкретный файл.
Пример 3. Какие ресурсы использует процесс?
Вы уже видели в листинге 1, что каждый процесс открывает несколько
файлов. Но когда я выполнил команду
[root]# /usr/sbin/lsof -c mc
я был поражен тем, как много файлов открыто этим процессом. Впрочем, я
привел этот пример не для того, чтобы сообщить о своем удивлении. Это
пример просто иллюстрирует применение опции - с string в команде lsof.
Запущенная с этой опцией lsof выводит список файлов, отрытых
процессами, выполняющими команды, название которых начинается строкой
string (один или несколько символов).
Но строка string может соответствовать нескольким запущенным в данный
момент командам. Если вы хотите конкретизировать ваш запрос, вы можете
воспользоваться другой опцией: -p pid. Например:
[root]# /usr/sbin/lsof -p 409
Команда lsof -p pid выводит список файлов, открытых процессами, чьи
идентификаторы (PID) находятся в списке pid, например, "3593" или
"823,451" (отдельные идентификаторы разделяются запятыми, пробелы в
списке недопустимы). С помощью этой команды вы сможете увидеть,
например, какие библиотеки используются процессом. Номер нужного PID
можно извлечь из результатов выполнения команды /usr/sbin/lsof -c
string.
Аналогичным образом можно вывести список файлов, открытых процессами,
чьи идентификаторы группы (PGID) находятся в разделенном запятыми
списке gid. Это позволяет сделать команда lsof -g gid.
Пример 4. С какими файлами работает пользователь?
Следующий вариант запуска той же команды:
[root]# /usr/sbin/lsof -u names
позволяет вывести список файлов, открытых всеми процессами,
запущенными пользователями, чье имя или идентификатор содержится в
списке names. Отдельные элементы списка разделяются запятыми,
например, "548,root" (пробелов быть не должно).
Пример 5. Кто подключился к вашему компьютеру?
Команда lsof очень полезна в том случае, когда вы хотите обеспечить
безопасность работы вашего компьютера в сети. С ее помощью вы можете
определить, какие порты открыты для приема входящих соединений и какая
программа прослушивает каждый порт. После это можно закрыть ненужные
порты, чтобы минимизировать риск проникновения в систему
злоумышленника. Для того, чтобы узнать, какие процессы в данный момент
прослушивают сетевые соединения, воспользуйтесь командой lsof с опцией -i :
[root]# /usr/sbin/lsof -i
На листинге 2 показан результат выполнения этой команды на компьютере
trend в моей домашней сети, состоящей всего из двух компьютеров.
(Три последних строки были разбиты для повышения
удобочитаемости,-прим.ред.)
Порты, которые открыты для входящих соединений, обозначены меткой
LISTEN. Метка ESTABLISHED показывает, что по данному порту соединение
установлено. В листинге 2 вы можете видеть пример двух SSH-сессий.
Первая сессия установлена хостом old-lin.home.kos с компьютером
trend.home.kos. Она обслуживается процессом с PID 2192, который был
порожден основным демоном sshd, PID 727. Процесс с PID 2464, наоборот,
соответствует соединению, установленному клиентской программой ssh на
компьютере trend.
Поскольку и в этом случае количество информации, выдаваемой командой
lsof с опцией -i может оказаться очень велико, можно конкретизировать
запрос. Для этого нужно использовать команду в следующем формате:
[root]# /usr/sbin/lsof -i adress
где после опции -i указывается еще параметр - интересующий вас порт,
сервис или имя хоста. В общем случае параметр adress задается в
следующем виде (квадратные скобки обозначают необязательность каждой
части параметра):
Здесь "46" обозначает версию протокола IP. Цифру 6 можно указывать
только в том случае, если ваше ядро поддерживает IPv6. Если не указано
ни 4, ни 6, будут рассматриваться соединения по тому и другому
варианту протокола. Задавать версию протокола можно указывая опцию в
форме -i4 -i6 (это то же самое, что и указать просто -i). Если
указывается только версия IP-протокола без указания других частей
параметра address, отображаются только данные о тех процессах и
файлах, которые работают по соответствующему протоколу.
Вместо "protocol" в параметре может стоять либо TCP, либо UDP (либо
ничего).
"hostname" - имя хоста, а "hostaddr" - числовой адрес. В случае
протокола IPv4 эта часть задается в форме десятично-точечной записи, в
случае IPv6 - в форме чисел, разделенных двоеточиями и заключенных в
скобки.
"service" - это название одного из сервисов, например, smtp, или
список таких сервисов.
"port" - это номер порта или список таких номеров.
Чтобы сказанное стало понятнее, приведем несколько простых примеров.
[root]# lsof -i :80
В этом случае будет выдан список всех процессов, прослушивающих или
установивших соединение через порт 80.
[root]# lsof -i :smtp
В этом примере будут перечислены все соединения, ассоциированные с
почтовым протоколом SMTP.
[root]# lsof -i @rus-linux.net
В третьем случае будет выдан список всех входящих и исходящих
соединений с хостом rus-linux.net.
[root]# lsof -i UDP@rus-linux.net:123
Эта команда покажет, какие процессы установили UDP-соединения с хостом
rus-linux.net через порт 123 (ntp).
Очевидно, что указание параметра adress полезно только в том случае,
когда вы заранее знаете, что именно вам нужно искать. Вы можете таким
образом проверить, например, что какой-то конкретный сервис фактически
работает и какой порт он прослушивает. Вы можете увидеть, подключился
ли кто-то извне к вашему компьютеру по таким протоколам как SSH,
Telnet, FTP или другим возможным способом.
Пример 6. Кто открыл файлы по NFS?
Если вы хотите узнать, открыты ли какие-то файлы, предоставленные
удаленным пользователям по протоколу NFS, используйте опцию -N.
Пример 7. Наблюдаем за процессом копирования
Предположим, что вы решили переместить из одного каталога в другой
большое количество файлов (или большой каталог целиком). И вы хотите
видеть, какой именно файл перемещается в данный момент. Тут вам и
может помочь утилита lsof. Для этого достаточно дать команду примерно
такого вида (в этом примере предполагается, что перемещаются куда-то
файлы из каталога Photo):
[user]$ /usr/sbin/lsof | grep Photo
При этом нужно иметь в виду, что команда lsof делает как-бы
"мгновенный снимок", отображающий только лишь состояние процессов и
соответствующих файлов в момент ее выполнения. Так что для "наблюдения
за процессом" надо повторять ее запуск многократно. И в таком случае
может оказаться полезной опция +r, которая служит для периодического
повторения запуска команды через заданный промежуток времени.
Заключение
В конце статьи можно сформулировать следующий вывод: поскольку файл и
процесс - два ключевых понятия в операционной системе, утилита lsof
может оказаться незаменимым помощником администратора в тех случаях,
когда надо разобраться, как работает система, а особенно в тех
случаях, когда система скомпрометирована. И она тем полезнее, чем
больше ваша система и чем больше вы с ней работаете, чем лучше вы
понимаете, что она может. Когда вы освоите ее возможности, вы сможете
создать отдельные скрипты для обработки тех огромных массивов
информации, которые выдает эта программа. Можно, например, запускать
команду lsof -i через определенные промежутки времени, чтобы
сравнивать полученные данные. Если будут выявлены отличия в числе
открытых процессов или другие отличия, удовлетворяющие заданному
критерию, скрипт может сформировать сообщение администратору.
Мы рассмотрели только малую часть возможностей этой утилиты. Более
подробную информацию ищите на соответствующей man-страничке.
Можно еще отметить, что для lsof разработана графическая оболочка,
называемая GLSOF. Ее можно получить с сайта http://glsof.sourceforge.net/.
Ссылки:
1. http://rus-linux.net
2. С.Лашанский, "Кто использует эти файлы", перевод статьи Майкла
Лукаса (URL: http://www.computerra.ru/softerra/freeos/22483/).
3. В.А.Костромин, "Linux для пользователя", БХВ-Петербург, 2002 г.
4. "Meet the Amazing Mr. Lsof" (URL:
http://www.netadmintools.com/art176.html)
5. Indiana University, Unix Workstation Support Group, "Unix for
Advanced Users". 15.9. List Open Files: lsof and fuser (URL:
http://www.uwsg.iu.edu/UAU/uau.html).
6. Thomas Nooning, "Track network connections with LSOF on Linux"
(URL:
http://www.builderau.com.au/program/unix/0,39024638,20268166,0
0.htm).
7. Martin Zahn, "Introduction to lsof", (URL:
http://www.akadia.com/services/lsof_intro.html).
8. "Installing, configuring and using lsof 4.50 to list open files on
systems running Solaris 2.x" (URL:
http://www.cert.org/security-improvement/implementations/i042.05.html)