П Р О Ц Е С С Ы

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

Системные данные, используемые ядром для идентификации процесса, которые существуют в течении всего времени жизни процесса, образуют дескриптор (описатель) процесса. Множество дескрипторов образуют таблицу процессов. Размер таблицы процессов, хотя и имеет допустимые ограничения, но в современных версиях UNIX позволяет создавать до нескольких сотен процессов. Дескриптор процесса содержит его параметры. Информация о состоянии включает расположение (адрес в памяти), размер выгружаемой части образа процесса, идентификаторы процесса и запустившего его пользователя.

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

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

Любой процесс в UNIX порождается с помощью системного вызова fork. После выполнения fork оба процесса продолжают выполнение с одной и той же точки. Имеется процедура опознания порождающего и порождаемого процессов. Например, чтобы узнать, является процесс "отцом" или "сыном", достаточно воспользоваться следующей конструкцией:

      main()
      {
            int childPID, ParentPID;
            if ((childPID = fork()) == -1 {
               perror ("Can't fork");
               exit(1);
            } else if (childPID == 0){
               /* процесс-сын */
               printf("child: childPID=%d, ParentPID=%d\n",getpid(),
                     getppid());
               exit(0);
            } else {
               /* процесс-отец */
               printf ("parent: childPID=%d, ParentPID=%d\n",childPID,
               getpid());
               exit(0);
            }
      }

Породивший процесс может остановить свое выполнение до завершения одного из "процессов-сыновей" с помощью системного вызова

wait(&status)

Значение, которое будет возвращено в переменную status, содержит в младшем байте идентификатор завершившегося процесса, а в старшем байте - статус завершения. Любой процесс может завершиться по собственной инициативе с помощью системного вызова exit(status). Аргумент status передается "процессу-отцу", если тот ожидает завершения "процесса-сына".

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

Идентификаторы процесса

Каждому созданному процессу UNIX назначает уникальный идентификатор, известный как процесс ID (или аббревиатура PID). Этот PID идентифицирует процесс и для самой ОС, и для множества команд и системных вызовов. В добавление к этому каждый процесс имеет также parent process ID (PPID), который есть не что иное, как PID его "процесса-отца".

Используя команду ps, вы можете всегда увидеть идентификаторы текущих процессов в системе. К примеру, если ваше пользовательское имя terry, вы можете увидеть на экране после выполнения команды ps:

UID PID PPID C STIME TTY TIME COMMAND terry 3865 3699 2 13:35:43 ttyp3 0:00 ps -f terry 3699 3699 0 12:58:21 ttyp3 0:00 ksh

Этот фрагмент показывает, что пользователь terry имеет два процесса: команду ps -f и ksh (Korn Shell).

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

Техника идентификаторов используется также при группировании процессов. Каждый процесс (за исключением системных процессов, таких как init и swapper) является членом группы (process group). Когда вы создаете задание, Shell назначает всем процессам в задании идентификатор группы процесов (GID). Сигналы (специальные системные вызовы UNIX) могут распространяться по всем процессам в данной группе. Это очень важная особенность в управлении заданиями, когда все родственные процессы в задании могут получать посылаемые им сигналы.

Еще один способ группирования - объединение процессов в терминальную группу. Члены группы получают в качестве атрибута идентификатор терминальной группы TGID, который равен значению PID лидера этой группы. В качестве лидера выступает процесс, который открыл терминал. Управляющий терминал используется для передачи через управляющий процесс сигналов для всех процессов - членов группы. Сигналы вырабатываются при нажатии определенных клавиш на клавиатуре. Следует отметить, что каждый пользователь, входящий в систему по login, обязательно порождает новые процессы. При этом каждый процесс в контексте пользователя имеет один и тот же идентификатор пользователя UID. Соответсвие между UID и login-именем пользователя фиксируется в файле /etc/passwd. Процесс суперпользователя имеет UID, равный 0, а остальные - целые положительные, отличные от 0.

Взаимодействие процессов

Сразу после обработки системного вызова fork() происходит установка дввусторонней связи между "процессом-отцом" и "процессом-сыном". "Сын" при этом наследует все свойства "отца". Однако существование нового, порожденного процесса как точной копии другого не имеет никакого смысла. Поэтому почти всегда "сын" заменяет программный код "отца" на свой собственный программный код. Это возможно с помощью системного вызова exec.

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

1. Вариант нормального окончания. "Процесс-отец" выдает wait и ждет, когда "процесс-сын" завершит свое выполнение по exit. Однако, если у "сына" свои "процессы-дети", чтобы они не остались "сиротами", происходит их "усыновление" прародителем всех процессов init. Главная работа ядра UNIX при этом заключается в переустановлении PPID у "процессов-сыновей". Он становится для них равным 1. Кроме того, всем процессам - членам группы рассылается соответсвующий сигнал.

2. Вариант "ненормального" окончания. "Процесс-сын" выдает exit, когда его "отец" существует и не находится в состоянии ожидания wait. В этом случае связь "отца" с "сыном" продолжает существовать, несмотря на то, что "сын" "умер". Слот "процесса-сына" в таблице процессов сохраняется и поэтому такой не до конца уничтоженный процесс называют "зомби".

3. Вариант преждевременного выхода. "Процесс-отец" заканчивается ранее своих "сыновей". В этом случае происходит реконфигурация генеалогического дерева и все "процессы-сироты" перенаправляются постоянно действующему процессу init.

Процессы-демоны"

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

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

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

1. В процессе старта системы из файла /etc/rc. Такие "демоны" будут работать
в статусе суперпользователя во время работы ОС.
2. Используя либо системный файл /usr/lib/crontab, либо пользовательский
crontab. Обыкновенно стандартный системный процесс cron в течении дня
выполняет определенные задачи, периодически выбирая свои команды для таких
исполнений из файла /usr/lib/crontab.
3. С помощью команды at "демон" ждет наступления заданного времени и выдает
определенное задание.
4. Запуск фонового процесса с помощью пользовательского терминала.