Управление процессами в Unix
Стратегии управления процессами в Unix представляют собой иную ситуацию, включающую несколько вариантов для выбора. К счастью, эти варианты даже отдаленно по своей сложности не напоминают то, что мы видели в NT. В разговоре об управлении процессами в Unix следует иметь в виду три операции:
Нумерацию списка запущенных на машине процессов.
Для последних двух операций в Perl существуют функции. В случае с первой у нас есть несколько возможностей. Чтобы вывести список запущенных процессов, можно:
Изучить файловую систему /ргос
Использовать модуль Ргос: : ProcessTable
Обсудим каждый из этих подходов. Нетерпеливым читателям могу сказать прямо сейчас, что я сам предпочитаю модуль Ргос : : ProcussTai,-1е, и вы можете пропустить все рассуждения и перейти сразу к рассказу о его использовании. Но рекомендую все же прочитать материал и о других технологиях, т. к. в будущем они могут вам пригодиться.
Вызов внешней программы
Во всех современных вариантах операционной системы Unix есть команда ps, применяемая для получения списка запущенных процессов. Однако в каждом конкретном случае она расположена в различных местах, а аргументы командной строки, которые она принимает, тоже не совпадают. Отсюда и проблема с ее применением: она недостаточно переносима.
Еще более неприятная проблема - это сложность анализа вывода (который тоже отличается в различных версиях). Вот как выглядит вывод команды ps на машине с SunOS:
USER PID %CPU %MEM SZ RSS TT STAT START TIME COMMAND
dnb 385 0.0 0.0 268 0 p4 IW Jul 2 0:00 /bin/zsh
drib 24103 0.0 2.610504 1092 p3 S Aug 10 35:49 emacs
dnb 389 0.0 2.5 3604 1044 p4 S Jul 2 60:16 emacs
remy 15396 0.0 0.0 252 0 p9 IW Jul 7 0:01 -zsn (zsh)
sys 393 0.0 0.0 28 0 7 IW Jul 2 0: 02 in, ider.td
dnb 29488 0.0 0.0 68 0 p5 IW 20:15 0:00 scree:
dnb 29544 0.0 0.4 24 148 p7 R 20:39 0:00 less
dnb 5707 0.0 0.0 260 0 p6 IW Jul 24 0,00 -)
root 28766 0,0 0.0 244 0 "> IW 13:20 0:00 - 0 xd
Обратите внимание на третью строку. Два столбца в ней слились вместе и при анализе вывода разобраться в этом будет непросто. Нет, это возможно, но просто действует на нервы. В некоторых вариантах Unixj дела обстоят лучше, но это обстоятельство следует учитывать. Программа на Perl, применяемая в этом случае, прямолинейна: в ней используются орем() для запуска ps, whilei<KH.-0{...} для чтения вывода
и split(), unpackO или substrQ для анализа данного вывода. Совет по этому поводу можно найти в книге Тома Кристиансена (Tom Christiansen) и Натана Торкингтона (Nathan Torkington) «Perl: Библиотека программиста», изд-во «Питер», 2000 г. («Perl Cookbook», O'Reilly).
Изучение структуры ядра
Я упомянул эту возможность только для полноты картины. Можно написать программу, которая будет открывать устройство, подобное /dev/ kmem, и обращаться к структурам памяти ядра. Таким образом можно добраться до текущей таблицы процессов и прочитать ее. Учитывая, что сделать это трудно (разобраться вручную в сложной двоичной структуре), а полученный результат не будет обладать абсолютно никакой переносимостью (любое изменение даже версии операционной системы сделает, скорее всего, вашу программу неработоспособной), я настоятельно рекомендую не пользоваться такой возможностью.
Тем, кто все же не прислушается к этому совету, придется вспомнить документацию по функциям pack(), unpack() и заголовочным файлам для вашего ядра. Откройте файл памяти ядра (часто это /dev/kmem), затем выполняйте read() и unpack(). Вам может понадобиться изучить исходники таких программ, как top (ищите на ftp://ftp.groupsys.com/ pub/top), выполняющих эти же задачи, используя большое количество кода на С. В следующем разделе мы рассмотрим слегка улучшенную версию этого метода.
Использование файловой системы /ргос
В большинстве современных вариантов Unix существует интересное добавление - файловая система /ргос. Эта загадочная файловая система не имеет ничего общего с хранением данных. Она обеспечивает «файлоподобный» интерфейс к таблице запущенных процессов. Для каждого из них в этой файловой системе существует «каталог», название которого совпадает с идентификатором процесса. В этом каталоге есть целый ряд «файлов», предоставляющих информацию о данном процессе. В один из этих файлов разрешена запись, что и позволяет управлять самим процессом.
Это действительно мудрая идея и это замечательно. Плохо то, что каждый производитель/команда разработчиков, поддержав эту мудрую концепцию, разбежались каждый в своем направлении. В результате файлы, которые можно найти в каталогах /ргос, часто специфичны для различных вариантов операционной системы, отличаясь как по именам, так и по формату. Описание того, какие файлы доступны и что в них хранится, вам придется искать на страницах руководства (обычно в разделах 4, 5 или 8) по ргосfs или
mount jjrocfs.
Единственное переносимое использование файловой системы /ргос -это нумерация запущенных процессов. Если нам нужно только перечислить идентификаторы процессов и их владельцев, мы можем применять операторы Perl по работе с каталогами и Istat ( ) :
opendir(PROC, "/proc") or die
"Невозможно открыть /proc:$!\n";
while (defined($_= readdir(PROC))){
next if ($_ eq " . " or $_ eq "..");
next unless /"\d+$/;
# отфильтровываем все случайные файлы, названия
# которых могут являться идентификаторами процессов
print "$_\t". getpwjid((lstat "/Ргос/$_" )[4] ) , "\n";
> closedir(PROC);
Для того чтобы получить подробную информацию о процессе, следует открыть нужный двоичный файл из каталогов в /ргос и воспользоваться функцией unpack(). Обычно это файл status или
psinfo. На страницах только что упомянутых руководств есть подробная информация о С-структуре, которую можно найти в этом файле, или, по крайней мере, есть ссылка на включаемый (include) файл, в котором эта структура документирована. Поскольку эти форматы зависят от операционной системы (и версии ОС), вы снова столкнетесь с проблемами переносимости программы.
Вероятно, вы уже чувствуете себя растерянным, поскольку все рассмотренные варианты требуют, чтобы в программе были учтены все версии каждой операционной системы, которую нам надо поддерживать. К счастью, в запасе у нас есть еще одна возможность, и она может помочь.
Использование модуля Proc::ProcessTable
Дэниел Дж. Урист (Daniel J. Urist) ( с помощью нескольких добровольцев) написал модуль Proc: :ProcessTable, предоставляющий единый интерфейс к таблице процессов для всех основных вариантов операционной системы Unix. Он скрывает от вас причуды различных реализаций /ргос и kmem, позволяя писать более переносимые программы.
Просто загрузите модуль, создайте объект Ргос: :ProcessTable: :Proces-и используйте методы этого объекта:
use Proc: :ProcessTable;
$tobj = new Proc: : ProcessTable;
Этот объект использует механизм связанных переменных (tied variable) для представления текущего состояния системы. Для обновления! этого объекта не требуется вызывать специальную функцию - он перечитывает таблицу процессов при каждом обращении к нему. Это похоже на хэш %Process, знакомый нам по обсуждению модуля Мае : Р ses ранее в этой главе.
Чтобы получить нужную информацию, следует вызвать метод
: : Sproctable = $tobj->table();
table() возвращает ссылку на массив, элементы которого представляют собой ссылки на объекты процессов. Каждый из этих объектов имеет свой собственный набор методов, возвращающих информацию об этом процессе. Например, вот как можно получить список идентификаторов процессов и их владельцев:
use Proc::ProcessTable:
Stobj = new Proc::ProcessTable:
Sproctable = $tobj->table(); for (ia>$proctable){
print $_->pid."\t". getpwuid($_->uid)."\n";
}
Список методов, доступных в вашей операционной системе, можно получить при помощи метода fields() объекта Proc: : ProcessTable (т.е. $tobj).
В Proc:: ProcessTable также есть три дополнительных метода у каждого объекта процесса: kill(), priorityO и pgrp(), которые являются всего лишь интерфейсом к встроенным функциям, упомянутым в начале этого раздела.
Чтобы опять вернуться к общей задаче, посмотрим на применение способов контроля над процессами. Мы начали изучать управление процессами в контексте действий пользователя, поэтому сейчас рассмотрим несколько маленьких сценариев, посвященных этим действиям. В примерах мы будем использовать Proc:: ProcessTable в Unix, но сами идеи не зависят от операционной системы.
Первый пример из документации по Proc: : ProcessTable: use Proc::ProcessTable;
$t = new Proc::ProcessTable; foreach $p ((5>{$t->table}){
if ($p->pctmem > 95){ $p->kill(9);
}
}
Эта программа «отстреливает» все процессы, занимающие 95% памяти в тех вариантах операционной системы Unix, где поддерживается метод pctmem() (а он поддерживается в большинстве случаев). В таком виде пример, вероятно, слишком «безжалостен», чтобы использовать его в реальной жизни. Было бы благоразумно добавить перед командой kill() что-то подобное:
print "собираемся убрят;. " Sn-^pid. "\t". get. owuid($p->uid).
"Vi": print "выполнять9 (yes'''i'0 " chomp($ans = о):
next unless (Sans eq "yes"):
Здесь может возникнуть состояние перехвата: не исключено, что во время задержки, вызванной вопросом к пользователю, состояние системы изменится. Учитывая, что мы в данном случае работаем только с «большими» процессами, которые вряд ли меняют свое состояние в течение короткого времени, такой вариант, скорее всего, пройдет нормально. Если вы хотите подойти к этому вопросу более педантично, вам, наверное, стоит получить сначала список процессов, которые вы хотите завершить, спросить пользователя, а затем проверить еще раз состояние таблицы процессов и только потом их завершать.
Бывают случаи, когда завершение процесса - это слишком легкая расплата. Иногда важно засечь, что процесс действительно работает, чтобы предпринять необходимые меры (скажем, поставить пользователя на место). Например, политика нашего сайта запрещает применять IRC-роботы. Роботы - это процессы-демоны, которые соединяются с IRC-серверами и выполняют автоматические действия. И хотя роботы могут использоваться в благих целях, в настоящее время они, в основном, играют асоциальную роль в IRC. Кроме того, мы обращали внимание на взлом системы безопасности из-за того, что первое (и часто единственное), что делал взломщик, — это запускал IRC-робота. Короче говоря, нам важно заметить присутствие таких процессов, а не завершать их работу.
Чаще других сейчас используется робот под названием eggdrop. Выяснить, запущен ли в системе процесс с таким именем, можно при помощи следующей программы:
use Proc::ProcessTable;
open(LOG, "»$logf ile") or die
"Невозможно открыть журнал для дозаписи:
$t = new Proc::ProcessTable; foreach $p (@{$t->table})
{ if ($p->fname() =" /eggdrop/i){ print LOG time."\t".
getpwuid($p->uid).
"\t".$p->fname()."\n": >
}
close(LOG);
Тот, кто подумает: «Эта программа не так уж и хороша! Все, что нужно сделать, чтобы ускользнуть от этой проверки, так это всего лишь переименовать исполняемый файл», будет абсолютно прав. Мы попытаемся написать менее простодушный код, ищущий роботов, в самом последнем разделе этой главы.
А пока рассмотрим еще один пример, в котором Perl помогает управлять процессами пользователей. До сих пор все наши примеры касались отрицательных явлений. Рассмотренные программы имели дело со злонамеренными или жадными к ресурсам процессами. Теперь посмотрим на что-нибудь более жизнерадостное.
Существуют ситуации, когда системному администратору необходимо узнать, какие программы применяются пользователями в системе. Иногда это необходимо сделать для программного обеспечения, лицензия которого запрещает его одновременное использование сверхнормативным числом потребителей. В таких случаях обычно применяется специальный механизм. Иногда подобные сведения необходимы, чтобы иметь возможность перейти на другую систему. Если вы переносите пользователей с одной системы на другую, вам необходимо убедиться, что все программы, работающие на старом месте, будут доступны и на новом.
Один подход к решению этой задачи - заменить каждую доступную пользователям исполняемую программу, не входящую в состав операционной системы, на оболочку, которая сначала запишет, какая программа была вызвана, а затем и запустит ее. Это сложно реализовать, если в системе доступно множество программ. Кроме того, есть и побочный эффект - запуск каждой программы требует большего времени.
Если точность не важна и достаточно знать только приблизительную оценку набора работающих программ, можно применить Ргос::Рго-cessTable. Ниже приведена программа, которая активизируется каждые пять минут и проверяет состояние текущих процессов. Она просто ведет учет всех найденных имен процессов, причем те процессы, которые встречались в предыдущий раз, она во второй раз не учитывает. Ежечасно программа записывает результаты и начинает подсчет заново. Пятиминутное ожидание объясняется тем, что обход таблицы процессов является ресурсоемкой операцией (обычно), а мы хотим, чтобы эта программа как можно меньше загружала систему:
use Proc::ProcessTable;
Sinterval = 600;
5 минут перерыва
Spartofhour = 0; (f отмечаем позицию часа, в которой мы находимся
Stop] = new Proc: : ProcessTabie;
создаем човый объект
tt вечный цикл, сбор данных каждые Sintervai секунд
№ и сброс этих данных один раз в час
while(1){
ucollectstats;
&dumpandreset if (Spartofhour >= 3600);
sleep($interval), }
сбор статистики по пролессу sub collectsrars ( my($process);
foreach Sprocess (@{$tobj->table}){
мы должны игнорировать себя next if ($process->pid() == $$);
сохраняем информацию о процессе для следующего запуска
push(@last,Sprocess->pid(),$process->fname());
игнорируем процесс, если мы его уже видели
next if ($last{$process->pid()} eq $process->fname());
если не видели, запоминаем его
$collection{$process->fname()}++; }
и устанавливаем хэш %last, используя текущую таблицу %last = ©last;
Spartofhour += $interval; }
выводим результаты и сбрасываем значения счетчиков
sub dumpandreset{
print scalar localtime(time). C'-"x50),"\n";
for (sort reverse_value_sort keys %collection){ write;
undef %collection; Spartofhour = 0; }
(обратная) сортировка no значениям хэша %collection и по
именам ключей
sub reverse_value_sort{
return $collection{$b} <=> $collection{$a} || $a cmp $b;
}
format STDOUT = @««««« »»
$_, $collection{$._}
format STDOUT_TOP = Name Count
Существует множество способов улучшить эту программу. Она могла бы отслеживать процессы для каждого пользователя (т. е. записывать один экземпляр вызова программы для каждого пользователя), собирать ежедневную статистику, выводить информацию в виде диаграммы и т. д. Все зависит только от того, где вы хотите ее применять.