Оглавление | Вперед |
![]() |
Лабораторная работа №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 используется два класса приоритетов:
Unreal-time priority делятся на два подкласса:
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. Разработать программу/комплекс программ, с помощью которых демонстрируется работа механизма порождения дочерних процессов в соответствии с индивидуальным заданием с учетом следующих требований:
Отчет по лабораторной работе оформить на украинском языке в соответсвии со следующими пунктами:
Пример выполнения основной части отчета работы №9 приведен здесь.
Избранные системные вызовы Linux/Unix. Краткое описание.
Cправочник библиотечных функция языка С: часть 1, часть 2 (кодировка кириллица ibm866).
Оглавление | Вперед |