Оглавление Вперед


      

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

Создание и завершение процессов

Цель работы: овладение системными программными средствами порождения и завершения процессов в ОС Linux.

Процессы - общие сведения

Процесс в Unix/Linux представляет собой единицу работы вычислительной системы, которой операционная система выделяет ресурсы. С некоторой степенью приближения можно определить процесс как выполняющуюся программу. Каждый процесс в системе имеет свой уникальный идентификатор процесса (PID), представляемый целым числом.

Каждому процессу в операционной системе соответствует запись в таблице процессов и адресное пространство процесса. Запись в таблице процессов (и ее расширение в адресном пространстве процесса) содержит управляющую информацию о ресурсах, выделенных процессу, и о состоянии процесса. Адресное пространство содержит коды и данные процесса.

Процесс может порождать другой процесс. Порождение нового процесса в Unix/Linux реализовано копированием записи таблицы процессов, таким образом, что процесс-потомок в момент своего порождения представляет собой точную копию процесса-предка. Процесс-предок и процесс-потомок далее выполняются параллельно, но процесс-предок может и ожидать завершения процесса-потомка.

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

Порождение процессов

Новый процесс порождается системным вызовом fork, который создает дочерний процесс - копию родительского. В дочернем процессе выполняется та же программа, что и в родительском, и когда дочерний процесс начинает выполняться, он выполняется с точки возврата из системного вызова fork. Системный вызов fork возвращает родительскому процессу PID дочернего процесса, а дочернему процессу - 0. По коду возврата вызова fork дочерний процесс может "осознать" себя как дочерний. Свой PID процесс может получить при помощи системного вызова getpid, а PID родительского процесса - при помощи системного вызова getppid. Если требуется, чтобы в дочернем процессе выполнялась программа, отличная от программы родительского процесса, процесс может сменить выполняемую в нем программу при помощи одного из системных вызовов семейства exec. Все вызовы этого семейства загружают для выполнения в процессе программу из заданного в вызове файла и отличаются друг от друга способом передачи параметров этой программе. Таким образом, наиболее распространенный контекст применения системного вызова fork выглядит примерно так:

    /* порождение дочернего процесса и запоминание его PID */
    if (!(ch_pid=fork()) 
        /* загрузка другой программы в дочернем процессе */
        exec(программа);
    else
        продолжение родительского процесса

Приоритет процесса

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

Это обосновано тем, что в ядре Linux используется два класса приоритетов:

  1. Unreal-time priority.
  2. Real-time priority

Unreal-time priority делятся на два подкласса:

  1. Статический приоритет или nice-приоритет, Значение nice-приоритета может лежать в диапазоне от -20 до 19, по умолчанию используется значение 0. Значение –20 соответствует наиболее высокому приоритету. nice-приоритет не изменяется планировщиком, он наследуется от родителя или его указывает пользователь.
  2. Динамический приоритет, на основании которого работает планировщик. Динамический приоритет вычисляется исходя из значения параметра пicе для данной задачи путем вычисления надбавки или штрафа в диапазоне от -5 до 5, в зависимости от интерактивности задачи. Для определения интерактивности в ядре Linux предусмотрен изменяемый показатель того, как соотносится время, которое процесс проводит в приостановленном состоянии, со временем, которое процесс проводит в состоянии готовности к выполнению.

Real-time priority лежат в диапазоне от 1 до (MAX_RT_PRIO-1), По умолчанию диапазон значений приоритетов реального времени составляет от 1 до 99.

Для стратегии планирования в режиме не реального времени (SCHED_OTHER) пространство приоритетов реального времени объединяется с пространством значений параметра nice, что соответствует диапазону приоритетов от значения MAX_RT_PRIO до значения (MAX_RT_PRIO+40). По умолчанию это означает, что диапазон значений параметра nice от -20 до +19 взаимно однозначно отображается в диапазон значений приоритетов от 100 до 139. И, таким образом, планирование выполняется по уровням приоритетов от 1 до 139.

Процесс может изменить свой приоритет при помощи системного вызова nice, а приоритет другого процесса может быть изменен системным вызовом setpriority.. Системный вызов getpriority позволяет узнать приоритет процесса.

Завершение процесса

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

Принудительное завершение процесса извне может быть выполнено при помощи системного вызова kill, посылающего процессу сигнал. Подробное рассмотрение сигналов мы проведем в следующей лабораторной работе, пока же только отметим, что гарантированно "убить" процесс, имеющий PID = p, можно системным вызовом:

    kill(p,SIGKILL)

Ожидание завершения процесса

Процесс-предок может ожидать завершения процесса-потомка (или процессов-потомков) при помощи системных вызовов wait или waitpid. Если процесс-потомок еще не завершился, процесс-предок переводится таким системным вызовом в состояние ожидания до завершения процесса-потомка (впрочем, процесс может и не ожидать завершения потомка, а только проверить, завершился ли он). Эти системные вызовы позволяют также процессу предку узнать код завершения потомка.

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

Каждый процесс-потомок при завершении работы посылает своему процессу-предку специальный сигнал SIGCHLD, на который у всех процессов по умолчанию установлена реакция "игнорировать сигнал". Наличие такого сигнала совместно с системным вызовом waitpid() позволяет организовать асинхронный сбор информации о статусе завершившихся порожденных процессов процессом-предком.

Помощь при разработке программ

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

Форматированный ввод

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

Математические вычисления

Большинство функций для выполнения математических вычислений имеет прототипы, объявленные в файле math.h. См. Описание.

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

` gcc -o sin sin.c -lm

Работа с файлами

Для открытия файла можно использовать функции fopen / open. Запись в файл - fprintf / write. Чтение файла - fgets / read. Закрытие файла - fclose / close. Для удаления файла можно воспользоваться функцией unlink.

Например (фрагменты программ):

Родитель

		 ....
		 char str[255];
		 FILE *fl;
		 .... 
		 unlink("./file_v5");
		  ...
		 if ((fl=fopen("./file_v5", "r"))==NULL)
		  {
			printf("Error open  file!\n");
			exit(1);
		  }
		  fgets(str, 256, fl);
		  printf("Father read file! str=%s\n", str);
		  fclose(fl);
		  ...
		 

Потомок

		...
		 long fk=1;
		  FILE *fl;
		 ...
		if ((fl=fopen("./file_v5", "a+"))==NULL)
		{
		      printf("Error open  file!\n");
		      exit(1);
		}
		fprintf(fl, "%s%d ","k=",fk);
		fclose(fl);
		...
		

Родитель и потомок (один исполняемый файл)

		 ....
		 char str[255];
		 .... 
		 unlink("./file_v3");
		  ...
		   fd=open("./file_v3", O_CREAT|O_WRONLY|O_APPEND, 0x1b6);
		   sprintf(str, "i=%d My pid=%d Father pid=%d! ",i, my_pid, p_pid );
		   if ((wr_str=write(fd, str, strlen(str)+1))<=0)
		   {
		      printf("Error write\n");
		      exit(1);
		   }
		   close(fd);
		    ...
		  fd=open("./file_v3", O_RDONLY, 0);
		  if ((rd_str=read(fd, str1, /*strlen(str1)+1*/ 256))<=0)
		  {
		  printf("Error read\n");
		  exit(1);
		  }
		  ...
		  close(fd);
		  ...
		 

Или см. справочник библиотечных функция языка С: часть 1, часть 2 (кодировка ibm866).

Постановка задачи

I. Проанализировать результат работы программы-примера.

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

  1. Для нечетных вариантов все действия, относящиеся как к родительскому процессу, так и к порожденным процессам, выполнить в рамках одного исполняемого файла.
  2. Для четных вариантов действия родительского и дочерних процессов выполнить с помощью разных исполняемых файлов.
  3. Все исходные данные задать в родительском процессе с клавиатуры.
  4. Работа процессов-потомков должна выполняться параллельно.
  5. Сигналом об окончании работы процесса-потомка считать его завершение.
  6. Результаты своей работы процессы-потомки передают процессу-родителю путем записи структурированной информации в файлы, индивидуальные для каждого процесса (Например: Потомок=1, х=5, х!=120).
  7. Окончательный результат индивидуального задания получить в родительском процессе.
  8. Ход работы процессов отобразить на экране.
  9. Вариант индивидуального задания выбрать в соответствии с номером по списку в журнале группы.

Индивидуальные задания

Отчет по лабораторной работе

Отчет по лабораторной работе оформить на украинском языке в соответсвии со следующими пунктами:

Пример выполнения основной части отчета работы №9 приведен здесь.


Справочный материал

Избранные системные вызовы Linux/Unix. Краткое описание.

Cправочник библиотечных функция языка С: часть 1, часть 2 (кодировка кириллица ibm866).


Оглавление Вперед