Управление процессами
Лабораторная работа №2
Цель работы
Изучить программные средства создания процессов, получить навыки управления и синхронизации процессов, а также простейшие способы обмена данными между процессами. Ознакомиться со средствами динамического запуска программ в рамках порожденного процесса.
Содержание работы
- Ознакомиться с назначением и синтаксисом системных вызовов fork(), wait(), exit().
- Ознакомиться с системными вызовами getpid(), getppid(), sеtpgrp(), getpgrp().
- Изучить средства динамического запуска программ в ОС UNIX (системные вызовы execl(), execv(),...).
- Ознакомиться с заданием к лабораторной работе.
- Отладить составленную программу, используя инструментарий ОС UNIX.
- Защитить лабораторную работу, ответив на контрольные вопросы.
Указания к лабораторной работе
Для порождения нового процесса (процесс-потомок) используется системный вызов 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();//создаем дочерний процесс от P1if (pid2 == 0) { printf("I'm FIRST_CHILD, my parent is PARENT and my PID is %d\n", getpid()); pid3 = fork();//создаем дочерний процесс от P2if (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 с родительский процесс. В дочернем процессе с помощью системного вызова system() выполнить стандартную команду ps, перенаправив вывод в файл номер 1. Вслед за этим завершить дочерний процесс. В родительском процессе вызвать ps и перенаправить в файл номер 2.
- Приостановить на 1 с родительский процесс. Выполнить в дочернем процессе один из системных вызовов exec(), передав ему в качестве параметра стандартную программу ps. Аналогично выполнить вызов ps в родительском процессе. Результаты работы команд ps в обоих процессах перенаправить в один и тот же файл.
- Определить в программе глобальную переменную var со значением, равным 1. Переопределить стандартный вывод и родительского, и дочернего процессов в один и тот же файл. До выполнения разветвления увеличить на 1 переменную var, причем вывести ее значение, как до увеличения, так и после. В родительском процессе увеличить значение переменной на 3, а в дочернем на 5. Вывести значение переменной до увеличения и после него внутри каждого из процессов. Результат пояснить.
- Приостановить на 1 с дочерний процесс. В дочернем процессе с помощью системного вызова system() выполнить стандартную команду ps, перенаправив вывод в файл номер 1. Вслед за этим завершить дочерний процесс. В родительском процессе вызвать ps и перенаправить в файл номер 2.
- Приостановить на 1 с дочерний процесс. Выполнить в дочернем процессе один из системных вызовов exec(), передав ему в качестве параметра стандартную программу ps. Аналогично выполнить вызов ps в родительском процессе. Результаты работы команд ps в обоих процессах перенаправить в один и тот же файл.
- Программа порождает через каждые 2 секунды 5 новых процессов. Каждый из этих процессов выполняется заданное время и останавливается, сообщая об этом родителю. Программа-родитель выводит на экран все сообщения об изменениях в процессах.
- Программа запускает с помощью функции exec() новый процесс. Завершить процесс-потомок раньше формирования родителем вызова. Повторить запуск программы при условии, что процесс потомок завершается после формирования вызова wait(). Сравнить результаты.
- Программа порождает 5 новых процессов. Каждый из этих процессов выполняется соответственно 5, 1, 2, 4 и 3 с, при этом каждый процесс увеличивает на соответствующее значение переменную var, начальное значение которой равно 0. Программа-родитель выводит на экран значение этой переменной.
Контрольные вопросы
- Каким образом может быть порожден новый процесс? Какова структура нового процесса?
- Если процесс-предок открывает файл, а затем порождает процесс-потомок, а тот, в свою очередь, изменяет положение указателя чтения-записи файла, то изменится ли положение указателя чтения-записи файла процесса-отца?
- Что произойдет, если процесс-потомок завершится раньше, чем процесс-предок осуществит системный вызов wait()?
- Могут ли родственные процессы разделять общую память?
- Каков алгоритм системного вызова fork()?
- Каков алгоритм системного вызова exit()?
- Каков алгоритм системного вызова wait()?
- В чем разница между различными формами системных вызовов типа exec()?
CC-BY-CA Анатольев А.Г., 31.01.2012