Жизненный цикл процесса в UNIX. Группы процессов

Жизненный цикл процесса

Понятие процесса в UNIX существенно отличается от аналогичного понятия в других популярных ОС. Если в MS-DOS, Windows и других системах новый процесс создается только при запуске программы, то в UNIX создание процесса и запуск программы — это два совершенно разных действия. При этом процесс в некотором роде «более первичен», чем программа. Если по стандартному определению (см. п. 4.2.1) процесс есть работа по выполнению программы, то в UNIX будет более уместно сказать, что программа — это один из ресурсов, используемых процессом.

Существует единственный способ создания процесса в UNIX, и этот способ заключается в вызове функции без параметров fork(). Эта функция создает новый процесс, который является точной копией процесса-родителя: выполняет ту же программу, наследует такие же хэндлы открытых файлов и т.д. При этом содержимое областей памяти процесса копируется. Единственным различием является идентификатор процесса (pid) — целое число, уникальное для каждого процесса в системе. После завершения создания оба процесса, и родитель, и потомок, будут выполнять одну и ту же команду, следующую в программе после вызова fork. Однако при этом функция fork возвращает процессу-родителю значение pid порожденного потомка, а потомку возвращает значение 0. Проверка возвращенного значения — простейший способ для процесса определить, «кто он такой» — родитель или потомок.

Типовой фрагмент программы на C может выглядеть примерно так:

pid = fork(); // Создание нового процесса
if (pid == -1) // Процесс не создан

{ обработка ошибки создания процесса}

else if (pid == 0) // Это порожденный процесс

{ операторы, выполняемые процессом-потомком }

else // Это процесс-родитель

{ операторы, выполняемые процессом-родителем }

В принципе, оба процесса могут в дальнейшем выполнять команды из одного и того же файла программы. Чаще, однако, процесс вскоре после создания начинает выполнять другую программу. Для этого используется одна из функций семейства exec. Несколько функций, имена которых начинаются сexec, различаются деталями — в каком формате передаются параметры командной строки, следует ли использовать поиск файла программы по переменной PATH и т.п. Каждая из этих функций запускает указанный файл программы, однако при этом не порождается новый процесс, просто текущий процесс меняет выполняемую программу. При этом полностью перестраивается заново контекст процесса.

Механизм создания новых процессов один и тот же как для процессов пользователя, так и для системных процессов. Единственным исключением является самый первый процесс, имеющий идентификатор 0. Он порождает второй системный процесс, который называется INIT и имеет идентификатор 1. Все остальные процессы в системе являются потомками INIT.

Для нормального завершения процесса используется функция exit(status). Целое число statusозначает код завершения, заданный программистом, при этом значение 0 означает успешное завершение, а ненулевые значения — разного рода ошибки и нестандартные случаи.

Процесс-потомок полностью независим от родителя, и завершаются эти процессы независимо друг от друга. Тем не менее, процесс-родитель имеет возможность синхронизироваться с моментом завершения потомка (проще говоря, подождать этого завершения). Для этого родитель выполняет вызов функции wait:

pid = wait(&status);

Эта блокирующая функция переводит вызывающий процесс в ожидание до момента завершения любого из потомков, порожденных этим процессов. Так работает, например, интерпретатор командUNIX, который запускает команду, введенную с консоли, и ожидает ее завершения. Функция wait возвращает pidзавершившегося потомка, а в переменной status передает код завершения.

Если процесс-потомок завершает свое выполнение до того, как родитель вызвал функцию wait, то завершившийся процесс переходит в состояние, которое принято называть «зомби». Фактически от процесса остается лишь запись в таблице процессов, содержащая код завершения и информацию о затраченном процессорном времени. Все ресурсы, занимавшиеся процессом, освобождаются. Если в дальнейшем родитель все же вызовет wait, то после передачи кода завершения «зомби» будет вычеркнут из таблицы, на чем и закончится жизненный цикл процесса.

Возможна также ситуация, когда процесс-родитель завершается до того, как будет завершен его потомок. Поскольку процесс не должен оставаться без родителя, «сирота» будет «усыновлен» системным процессом INIT.

Создание нового процесса в системе UNIX является обычным способом решения самых разнообразных проблем. В частности, в UNIX отсутствуют функции асинхронного ввода/вывода, они просто не нужны. Если программист хочет, чтобы операция ввода/вывода выполнялась параллельно с работой основной программы, ему достаточно запустить новый процесс и поручить ему эту операцию. Вместо асинхронного ввода/вывода используется асинхронный параллелизм процессов.

Группы процессов

При входе пользователя в систему для него создается процесс-оболочка, являющийся предком всех других процессов этого пользователя. Этот процесс становится лидером группы порожденных им процессов. В качестве идентификатора группы принимается идентификатор (pid) ее лидера. Тот терминал, с которого пользователь вошел в систему, становится управляющим терминалом группы. Это может быть как локальный терминал компьютера, на котором работает система, так и удаленный терминал, с которого был выполнен вход в систему по сети.

Любой процесс может покинуть свою группу и объявить себя лидером новой группы, к которой будут относиться его потомки. Одна из групп является текущей (foreground), остальные группы —фоновыми (background). Процессы текущей группы могут получать ввод с управляющего терминала.

Понятие группы процессов играет важную роль в ряде ситуаций при работе системы. Например, если пользователь нажимает Ctrl+C, то всем процессам текущей группы посылается сигнал о необходимости завершения. При разрыве соединения с терминалом подобный сигнал посылается всем процессам, для которых этот терминал являлся управляющим.

Процесс может, создав собственную группу, затем «открепиться» от управляющего терминала. Такой процесс, называемый в UNIX «демоном», теряет возможность вести диалог с пользователем, но зато он не будет завершаться, когда пользователь закончит сеанс работы с системой. Демоны в UNIX выполняют обычно общесистемные задачи, такие, как управление печатью, получение и отправка почты, автоматический запуск процессов в заранее заданные моменты времени и т.п.

37. Программные каналы и сигналы в UNIX.

Программные каналы

Одним из «фирменных» изобретений UNIX, впоследствии позаимствованных другими ОС, является понятие программного канала или «трубопровода» (pipe), позволяющего выполнять обмен данными между процессами с помощью тех же системных вызовов, которые используются для чтения/записи данных при работе с файлами и с периферийными устройствами.

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

Данные, записываемые в канал, буферизуются системой в памяти и затем могут быть прочитаны функциями чтения из канала. Если в канале нет данных, то функция чтения блокирует вызвавший ее процесс, пока другой процесс не запишет данные в канал.

Если все процессы закрыли хэндлы записи в канал (то есть, нет шансов, что в канал будут помещены еще какие-нибудь данные), то процесс-читатель, выбрав все данные, которые еще оставались в канале, прочтет затем признак конца файла.

Хуже, если закрыты все хэндлы чтения, а какой-нибудь процесс пытается выполнить запись данных, которые некому будет прочитать. В этом случае система посылает процессу сигнал об ошибке работы с каналом.

Использование безымянных каналов имеет одно ограничение: все процессы, работающие с каналом, должны быть потомками процесса, создавшего канал. Для передачи данных между неродственными процессами можно использовать именованные каналы, называемые также каналами FIFO. Такой канал создается системным вызовом mknod, при этом указывается путь и имя канала, как при создании файла. Имена каналов хранятся в каталогах файловой системы UNIX наравне с именами обычных и специальных файлов. Чтобы открыть канал для чтения или для записи, используется обычный системный вызов open с указанием требуемого режима доступа, как при открытии файла.

Сигналы

Сигнал представляет собой уведомление процесса о некотором событии, посылаемое системой или другим процессом.

Основными характеристиками сигнала являются его номер и адресат сигнала. В разных версиях UNIX используется от 16 до 32 сигналов. Номер сигнала означает причину его посылки. В программах номер сигнала обычно задается одной из стандартных констант. Отметим следующие сигналы:

· SIGKILL — процесс должен быть немедленно прекращен;

· SIGTERM — более «вежливая» форма: система предлагает процессу прекратиться, но он может и не послушаться;

· SIGILL — процесс выполнил недопустимую команду;

· SIGSEGV — процесс обратился к неверному адресу памяти;

· SIGHUP — разорвана связь процесса с управляющим терминалом (например, модем «повесил трубку»);

· SIGPIPE — процесс попытался записать данные в канал, к другому концу которого не присоединен ни один процесс;

· SIGSTOP — процесс должен быть немедленно приостановлен;

· SIGCONT — приостановленный процесс возобновляет работу;

· SIGINT — пользователь нажал Ctrl+C, чтобы прервать процесс;

· SIGALRM — поступил сигнал от ранее запущенного таймера;

· SIGCHLD — завершился один из потомков процесса;

· SIGPWR — возникла угроза потери электропитания, компьютер переключился на автономный источник (т.е. пора срочно спасать данные);

· SIGUSR1, SIGUSR2 — номера сигналов, предоставленные в распоряжение прикладных программ для использования в произвольных целях.

При возникновении соответствующего события система посылает сигнал процессу или группе процессов (например, SIGHUP посылается всем процессам, связанным с данным терминалом).

Любой процесс также может послать любой сигнал другому процессу или группе процессов. Для этого используется системный вызов со страшным именем kill. Параметрами функции служат номер посылаемого сигнала и получатель сигнала. Сигнал может быть послан конкретному процессу (указывается pidполучателя), а также всем процессам группы отправителя или другой указанной группы, или всем процессам, запущенным данным пользователем. Привилегированный пользователь может даже послать сигнал всем процессам в системе, за исключением корневых системных процессов 0 и 1.

Для каждого процесса система хранит маску, задающую его реакцию на каждый из сигналов. Всего возможно три вида реакции.

· По умолчанию — выполняются действия, предусмотренные системой для данного номера сигнала. Для большинства сигналов действия по умолчанию предусматривают завершение процесса.

· Игнорировать — процесс никак не реагирует на получение сигнала.

· Обработать — в этом случае процесс должен задать адрес функции, которая будет вызвана при получении сигнала.

Для сигналов SIGKILL и SIGSTOP всегда используется реакция по умолчанию. Для остальных сигналов процесс может установить требуемую реакцию. Традиционным средством для этого является системный вызов signal. Одним из параметров этой функции указывается номер сигнала, другим — либо адрес функции, обрабатывающей сигнал, либо одно из специальных значений, означающих «По умолчанию» и «Игнорировать».

Установка функции-обработчика для сигнала действует только на первый полученный сигнал с данным номером, сразу же при вызове этой функции система автоматически восстанавливает обработку по умолчанию. Вероятно, это было сделано для того, чтобы избежать повторного вызова обработчика, если следующий сигнал будет получен до завершения обработки первого. Однако в результате может случиться другая неприятность: если процесс не успеет снова установить обработчик сигнала, то при получении следующего сигнала процесс может быть прекращен в порядке обработки сигнала по умолчанию.

Еще один недостаток традиционной модели обработки сигналов является невозможность временно заблокировать обработку сигналов на период выполнения каких-либо критически важных действий. Приходится выбирать: либо сигнал должен быть обработан немедленно при получении, либо он будет проигнорирован и потерян.

В современных версиях UNIX, наряду с традиционными средствами обработки сигналов, может также использоваться аппарат «надежных сигналов», позволяющий преодолеть названные трудности.

38. Интерпретатор команд shell в UNIX.