Есть определенная разница в том, как обрабатываются команды и командные файлы в Unix и Windows. Что будет, например, если попробовать установить PATH в командном файле BAT и shell соответственно? Каким будет PATH после того, как закончится работа командного файла?

В Windows PATH будет таким, как он был установлен в командном файле, в Unix же останется тот PATH, что был до запуска командного файла. Почему это происходит? Дело в том, что для обработки командного файла в Unix запускается отдельный процесс; передача переменных окружения происходит всегда только в одну сторону - от отца к сыну. Единственное, что достается отцу после окончания порожденного процесса (кроме, естественно, побочных эффектов в виде изменения каких-либо файлов, разделяемой памяти и т.п.) - код окончания. Что из этого следует? Невозможно (если вы не хакер и не пишете отладчик) написать программу, которая могла бы поменять контекст текущего процесса.

Что такое контекст и что в него входит? Один из компонентов контекста мы уже упомянули - это переменные окружения. Их в принципе можно поменять отладчиком (занятие для большого любителя), т.к. они лежат в памяти, также, например, как и параметры, использованные при запуске процесса. Есть и другие ресурсы, используемые процессом, которые просто так не поменять. Например, открытые файлы (и управляющая консоль), подсоединенные разделяемые области, библиотеки, текущий каталог и т.д.

К контексту процесса относятся и более любопытные вещи, например, корневой каталог данного процесса. Каждый процесс имеет собственный корневой каталог. В общем случае нельзя гарантировать, что все процессы, которые попробуют использовать, например, файл с именем /etc/passwd (имя начинается с / и, формально говоря, должно однозначно определять файл), будут обращаться к одному и тому же файлу. Возможность переназначения корневого каталога используется чаще всего для защиты.

Еще одна часть контекста процесса - привилегии. Для того чтобы иметь возможность определять права конкретного процесса на доступ к тем или иным файлам, система должна знать, кто запустил данный процесс. Система хранит информацию о владельце данного процесса и группе (группах), которой он принадлежит. Кроме того, есть такое понятие как эффективный владелец (группа) данного процесса. Установив соответствующие права на выполняемый файл, можно сделать так, что выполняемый файл будет в процессе работы использовать привилегии владельца или группы файла, а не текущего пользователя. В Unix используются не только права доступа к файлам, но и права на выполнение конкретных системных вызовов. В результате привилегии, которыми обычно обладают только системные администраторы, могут оказаться размазаны по разным группам пользователей. Например, определенной категории пользователей можно разрешить менять приоритеты процессов, блокировать компоненты процессов в памяти, вообще выделить четкую квоту процессорного времени и т.п.

В некоторых диалектах Unix строчка «#!имя» в выполняемом командном файле обрабатывается системой и, как следствие, даже при запуске командного файла возможна установка соответствующих привилегий (например, если командный файл имеет, например,бит установки пользователя, то программа «имя» будет выполняться от имени владельца файла). К чему это может привести? Допустим, в «привилегированном» файле используется печать первого и последнего параметра:

eval echo $1 $$#

Как это будет обработано, например, если первым параметром будет ?`echo uuu`?? Это простейший пример. При работе с shell возможны и более забавные ситуации. Например, в Linux (там такой обработки бит привилегий командных файлов нет) попробуйте несколько раз выдать что-либо типа: echo `cat /bin/tcsh`. Вы получаете одинаковый результат? Можно ли после этого удивляться, что Сеть забита сообщениями о преодолении защиты путем переполнения буферов и т.п.?

Следующая категория в контексте - системные параметры: приоритет, так называемое значение nice, в некоторых диалектах принадлежность очереди (пакету и т.п.), группе с определенными правами на процессорные ресурсы. Эта группа параметров определяет то, как система будет выделять процессорное время конкретному процессу.

Наконец, еще одна категория определяет место процесса в иерархии процессов. Обычно, все вспоминают только одно непосредственное соседство сын-отец. Однако, есть и другие виды отношений. Например, есть ограничение на количество процессов запущенных одним пользователем. Все процессы, запущенные с одного терминала, относятся к одному сеансу. Что это означает? Если «погибает» тот самый процесс, который начал данный сеанс - лидер сеанса (над ним «папочкой» только init), то всем его сыновьям будет послан сигнал SIGHUP (разрыв соединения), и они, скорее всего, окончат работу. Например, при запуске эмулятора терминала появившаяся в окне подсказка будет выдана лидером очередного сеанса. Если выполнить команду xclock &, а затем закрыть окно эмулятора, то xclock прекратит свою работу.

Почему все эти параметры отнесены к контексту процесса? Все они влияют на характер его работы. Есть у контекста еще одно важное свойство - он наследуется при порождении нового процесса. Напомню, что процесс порождается в Unix при помощи системного вызова fork (vfork). И отец и сын имеют одинаковые права на ресурсы. Сын может читать или писать отцовский файл. Именно так фактически и происходит передача стандартного ввода и вывода порожденному процессу. Никто об этом специально не заботится, это получается просто автоматически в результате наследования ресурсов. Для запуска действительно новой программы надо выполнить пару fork-exec. Системный вызов exec подменяет выполняемую в данном контексте программу. Естественно, полностью передать контекст новой программе нельзя. В первую очередь это касается той части контекста, которая связана с памятью. Новая программа может иметь другую карту памяти, поэтому будут разорваны все связи с разделяемыми ресурсами - памятью и библиотеками, удалена блокировка компонентов процесса в памяти. Кроме того, заменяемая программа может потребовать закрытия ряда файлов. Это можно выполнить явно при помощи close после fork, но обычно у открытого файла просто устанавливается флаг необходимости закрытия его при выполнении вызова exec.

Теперь вернемся к разного рода фокусам процессов.

Открытые файлы могут порождать проблемы. Когда файл удаляется из системы? Когда удаляется последняя ссылка на него (естественно, речь не идет о символьных ссылках). Но когда файл открыт, на него есть еще одна ссылка. Поэтому, даже если при помощи команды rm мы удалим все ссылки на открытый файл, то не освободим ни байта пространства. Наибольшие проблемы возникают с разного рода журналами. Если удалить файл, в который идет сброс диагностики, молчаливый отбор дискового пространства продолжается, а в файловой системе возникает раздувающийся невидимый мыльный пузырь. Мне довелось это наблюдать в следующем сюжете, который повторялся несколько недель: компьютер запускается, работает несколько дней, персонал замечает резкое уменьшение свободного пространства, принимает «меры» и через день система агонизирует. Как оказалось, меры состояли в «удалении» файла с диагностикой. Как корректно избавиться от такого файла? Наиболее правильный подход состоит в переоткрытии его на запись, например: >a.log После подобной команды файл будет иметь нулевую длину и начнет заполняться сначала (если, конечно пишущий процесс не выполняет явного позиционирования); очень важно, что не меняются права на этот файл.

Обсудим теперь вывод программы ps, которая позволяет увидеть часть информации из контекста процесса, в частности, пользователя, запустившего процесс, текущий приоритет, значение nice, массу флагов, выполняемый файл и параметры, использованные при запуске. Вот тут, однако, и кроется стандартная ошибка. Почему-то многие убеждены, что ps показывает все параметры процесса. Это не так. Реальные параметры хранятся в памяти процесса и могут занимать несколько килобайт. ps показывает лишь вырезку из параметров размером в строку терминала, которую система хранит у себя именно для этих целей. Что из этого следует? Программа может иметь параметров более, чем на одну строку, и конец мы не увидим. Некоторые фирмы так и поступают, для сокрытия реальных параметров (переменных им мало) - в качестве первого параметра передается строка, например, из одних символов «-». Если же вы, кроме того, как все настоящие программисты используете для именования своих программ постфикс sh (типа sh, ksh, bash, csh и т.п., вы разве пользуетесь другими именами?), то .... ох, много вы поймете из вывода ps. Но знать это полезно. Периодически попадаются программы, которым необходимо передать что-то на параметрах. Самое неприятное, если это «что-то» должно содержать в той или иной форме пароль. Его надо как-то запрятать от любителей ps. Надеюсь, понятно как.

Как уже было сказано, shell порождает процесс для выполнения командного файла, поэтому при помощи командного файла нельзя установить переменные окружения, текущий каталог и т.п. Что делать? Специально для этих целей есть команда «.», например: . setup. Командный файл setup будет выполнен непосредственно, без порождения новых процессов и с игнорированием #! в начале файла. Воспользоваться для этих целей (более точно: воспользоваться именно таким образом) двоичным файлом не удастся - все установки, увы, сгинут в недрах процесса.

Но не всегда все проходит гладко даже в пределах одного командного файла. Тривиальный пример:

        i=0
	ps  | while read a
	do
	   i=`expr $i + 1`
	done
	echo $i

Зануды, которые считают, что «wc -l» проще, могут переделать данный пример по своему, например, так:

        i=0
	(i=`ps|wc -l`)
	echo $i

Что выдаст последняя команда? Если вы думаете, что что-то отличное от нуля, то заблуждаетесь. В обоих случаях в процессе работы порождаются дополнительные процессы командных интерпретаторов, внедрах которых и пропадает переменная i. Можно добавить еще один оператор echo после «i=`...`», чтобы убедиться, что i, в принципе, меняется, но только внутри цикла или круглых скобок.

Что будет, если использовать конструкцию с большим числом скобок и т.п.? Вас (если вы обычный пользователь) просто схватят за руку, поскольку процессов окажется слишком много, а их количество, отпущенное на одного пользователя, лимитировано. Из этого есть еще одно забавное следствие. Предположим, в среди ваших коллег появился пользователь с привычкой запускать мелкопакостные процессы в стиле эстафеты: один процесс «нагадил», запустил другой процесс и тут же умер (это желание у многих возникает сразу после изучения команды nohup). Как с этим бороться суперпользователю? Борьба при помощи ps не особенно эффективна - kill достанется несуществующему процессу. Одно из решений состоит в том, что надо «помочь товарищу», запустив достаточное количество процессов от имени пакостника.

ОБ АВТОРЕ

Игорь Облаков, присылайте свои вопросы и пожелания по адресу oblakov@bigfoot.com