Учебно-методические материалы для студентов кафедры АСОИУ

Управление процессами

Лабораторная работа №2

Цель работы

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

Содержание работы

  1. Ознакомиться с назначением и синтаксисом системных вызовов fork(), wait(), exit().
  2. Ознакомиться с системными вызовами getpid(), getppid(), sеtpgrp(), getpgrp().
  3. Изучить средства динамического запуска программ в ОС UNIX (системные вызовы execl(), execv(),...).
  4. Ознакомиться с заданием к лабораторной работе.
  5. Отладить составленную программу, используя инструментарий ОС UNIX.
  6. Защитить лабораторную работу, ответив на контрольные вопросы.

Указания к лабораторной работе

Для порождения нового процесса (процесс-потомок) используется системный вызов fork(). Формат вызова:

	int fork()

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

Ожидание завершения процесса-потомка родительским процессом выполняется с помощью системного вызова wait():

	int wait(int *status)

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

Формат системного вызова exit(), предназначенного для завершения функционирования процесса:

	void exit(int status)

Аргумент status является статусом завершения, который передается отцу процесса, если он выполнял системный вызов wait().

Для получения собственного идентификатора процесса используется системный вызов getpid(), а для получения идентификатора процесса-отца - системный вызов getppid():

	int getpid()
int getppid()

Вместе с идентификатором процесса каждому процессу в ОС UNIX ставится в соответствие также идентификатор группы процессов. В группу процессов объединяются все процессы, являющиеся процессами-потомками одного и того же процесса. Организация новой группы процессов выполняется системным вызовом getpgrp(), а получение собственного идентификатора группы процессов - системным вызовом getpgrp(). Их формат:

	int setpgrp()
int getpgrp()

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

	execl(name,arg0,arg1,...,argn,0)
char *name, *arg0, *arg1,...,*argn;
execv(name,argv)
char *name, *argv[];
execle(name,arg0,arg1,...,argn,0,envp)
char *name, *arg0, *arg1,...,*argn,*envp[];
execve(name,argv,envp)

Примеры программ

Листинг 1

	/*-----------------------------------------------------------------------------
Программа выводит в терминал строку сообщения, а затем создает новый процесс. Родительский процесс "засыпает" на 5 с. Порожденный процесс выводит сообщение и свой идентификатор процесса и PPID родительского процесса, затем "просыпается" родительский процесс и выводит свое сообщение.
------------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main(){
int pid;
puts("Who is there?");
pid=fork();
if (pid == 0)	{ // Потомок
printf("I'm, CHILD (PID:%d)\n",getpid());
} else if (pid > 0){ // Родитель
sleep(5);
printf("I'm, PARENT (PID:%d)\n",getpid());
}
else {
perror("Fork error ");
return -1;
}
wait(pid);
return 0;
}

Листинг 2

	/*-----------------------------------------------------------------------------
Программа в результате выполнения порождает три процесса (процесс-предок 1 и процессы-потомки 2 и 3). В ходе выполнения программы будет создан процесс1 (как потомок интерпретатора shell), он сообщит о начале своей работы и породит процесс2. После этого работа процесса1 приостановится и начнет выполняться процесс2 как более приоритетный. Он также сообщит о начале своей работы и породит процесс 3. Далее начнет выполняться процесс3, он сообщит о начале работы и «заснет». После этого возобновит свое выполнение либо процесс1, либо процесс2 в зависимости от величин приоритетов и от того, насколько процессор загружен другими процессами.
------------------------------------------------------------------------------*/
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
int pid2, pid3, st; /* process 1 */
printf("I'm PARENT process, my PID is %d\n", getpid());
pid2 = fork(); 
	//создаем дочерний процесс от P1
if (pid2 == 0) { printf("I'm FIRST_CHILD, my parent is PARENT and my PID is %d\n", getpid()); pid3 = fork();
	//создаем дочерний процесс от P2
if (pid3 == 0) { printf("I'm SECOND_CHILD, my parent is FIRST_CHILD. My PID is %d\n", getpid()); sleep(5); printf("SECOND_CHILD is finished\n"); } if (pid3 < 0) printf("Can't create process 3: error %d\n", pid3); sleep(2); printf("FIRST_CHILD is finished\n"); } else /* if (pid2==0...) */{ printf("PARENT is finished\n"); if (pid2 < 0) printf("Can't create process 2: error %d\n", pid2); } wait(&st); return 0; }

Листинг 3

	/* ----------------------------------------------------------------------------
Программа перенаправляет вывод со стандартного устройства в указанный файл.
Использованные функции: dup() - дублирует дескриптор файла (здесь - дескриптор TTY)
close() - закрывает файл с указанным дескриптором (1 - дескриптор TTY)
open() - открывает заданный файл для записи, предварительно обрезая его содержимое (здесь этот файл будет использован как стандартное устройство вывода (TTY))
execl() - выполняет внешнюю программу (здесь - выполняет команду ps, записывая результаты не в std_out, а в файл) ------------------------------------------------------------------------------*/
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main() {
int outfile;
if ((outfile = dup(1))==-1) return -1;
close(1); // закрытие стандартного устройства вывода (TTY)
if ((outfile=open("1.dat", O_WRONLY|O_TRUNC|O_CREAT,00644))>=0) {
execl("/bin/ps",NULL);
}
return 0;
}

Задания к лабораторной работе

Разработать программу, выполняющую "разветвление" посредством системного вызова fork(). Вывести на экран идентификаторы PID и PPID для родительского и дочернего процессов. Сохранить результаты работы программы в файл.

Варианты

  1. Приостановить на 1 с родительский процесс. В дочернем процессе с помощью системного вызова system() выполнить стандартную команду ps, перенаправив вывод в файл номер 1. Вслед за этим завершить дочерний процесс. В родительском процессе вызвать ps и перенаправить в файл номер 2.
  2. Приостановить на 1 с родительский процесс. Выполнить в дочернем процессе один из системных вызовов exec(), передав ему в качестве параметра стандартную программу ps. Аналогично выполнить вызов ps в родительском процессе. Результаты работы команд ps в обоих процессах перенаправить в один и тот же файл.
  3. Определить в программе глобальную переменную var со значением, равным 1. Переопределить стандартный вывод и родительского, и дочернего процессов в один и тот же файл. До выполнения разветвления увеличить на 1 переменную var, причем вывести ее значение, как до увеличения, так и после. В родительском процессе увеличить значение переменной на 3, а в дочернем на 5. Вывести значение переменной до увеличения и после него внутри каждого из процессов. Результат пояснить.
  4. Приостановить на 1 с дочерний процесс. В дочернем процессе с помощью системного вызова system() выполнить стандартную команду ps, перенаправив вывод в файл номер 1. Вслед за этим завершить дочерний процесс. В родительском процессе вызвать ps и перенаправить в файл номер 2.
  5. Приостановить на 1 с дочерний процесс. Выполнить в дочернем процессе один из системных вызовов exec(), передав ему в качестве параметра стандартную программу ps. Аналогично выполнить вызов ps в родительском процессе. Результаты работы команд ps в обоих процессах перенаправить в один и тот же файл.
  6. Программа порождает через каждые 2 секунды 5 новых процессов. Каждый из этих процессов выполняется заданное время и останавливается, сообщая об этом родителю. Программа-родитель выводит на экран все сообщения об изменениях в процессах.
  7. Программа запускает с помощью функции exec() новый процесс. Завершить процесс-потомок раньше формирования родителем вызова. Повторить запуск программы при условии, что процесс потомок завершается после формирования вызова wait(). Сравнить результаты.
  8. Программа порождает 5 новых процессов. Каждый из этих процессов выполняется соответственно 5, 1, 2, 4 и 3 с, при этом каждый процесс увеличивает на соответствующее значение переменную var, начальное значение которой равно 0. Программа-родитель выводит на экран значение этой переменной.

Контрольные вопросы

  1. Каким образом может быть порожден новый процесс? Какова структура нового процесса?
  2. Если процесс-предок открывает файл, а затем порождает процесс-потомок, а тот, в свою очередь, изменяет положение указателя чтения-записи файла, то изменится ли положение указателя чтения-записи файла процесса-отца?
  3. Что произойдет, если процесс-потомок завершится раньше, чем процесс-предок осуществит системный вызов wait()?
  4. Могут ли родственные процессы разделять общую память?
  5. Каков алгоритм системного вызова fork()?
  6. Каков алгоритм системного вызова exit()?
  7. Каков алгоритм системного вызова wait()?
  8. В чем разница между различными формами системных вызовов типа exec()?

CC-BY-CA Анатольев А.Г., 31.01.2012