Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Миргородская 7сессия / Операционные системы / %D0%9E%D0%A1_%D0%A1%D0%93%D0%A2%D0%A3%20v5

.pdf
Скачиваний:
86
Добавлен:
12.02.2015
Размер:
4.09 Mб
Скачать

3.3.Процессы в QNX6

ВUNIX выделяют следующие типы процессов: системные, демоны и прикладные (пользовательские) [5].

Системные: они являются частью ядра и всегда расположены в опера-

тивной памяти. Они не имеют соответствующих им программ в виде испол-

няемых файлов и запускаются особым образом при инициализации ядра сис-

темы; выполняемые инструкции и данные этих процессов находятся в ядре системы, таким образом, они могут вызывать функции и обращаться к дан-

ным не доступным для остальных процессов. Системными процессами UNIX

являются: диспетчер свопинга (shed), диспетчер страничного замещения

(vhand), диспетчер буферного КЭША (bdfflush), диспетчер памяти ядра

(kmademon), к ним относятся процесс init, который является прародителем всех остальных процессов ОС и запускающийся при инициализации систе-

мы, хотя он не является частью ядра и запускается из исполняемого файла.

Демоны: это неинтерактивные процессы, которые запускаются обыч-

ным образом, путём загрузки в память соответствующих им программ и вы-

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

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

Прикладные процессы: к ним относятся все остальные процессы, вы-

полняющиеся в системе, как правило, это процесс, порождённые в рамках пользовательского сеанса работы, они могут выполняться как в интерактив-

ном так и в фоновом режиме, но в любом случае время жизни ограничено се-

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

111

В QNX процесс – это выполняющаяся программа. Процесс состоит из образа процесса и метаданных процесса. Образом процесса называется сово-

купность кода (т.е. инструкций для процессора – выполнение этих инструк-

ций и есть выполнение программы) и данных (ими манипулируют с помо-

щью инструкций) [15]. Метаданные процесса – это информация о процессе,

которая хранится в структурах данных ОС и сопровождается ОС. Метадан-

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

Идентификатор процесса (Process ID, PID) – уникальный номер, при-

сваиваемый процессу при его порождении ОС.

Идентификатор родительского процесса (Parent PID, PPID) – PID про-

цесса, породившего данный процесс, т.е. выполнившего запрос к ОС для соз-

дания данного процесса.

Реальные идентификаторы владельца и группы (User ID UID и Group ID, GID) – номер, позволяющий механизмам защиты информации от несанк-

ционированного доступа определять, какому пользователю принадлежит процесс и какой группе пользователей принадлежит этот пользователь. Эти идентификаторы присваиваются при регистрации пользователя в системе или командному интерпретатору (login shell), если выполнялась командно-

строковая регистрация через утилиту login, или графической оболочкой Photon, если регистрация выполнялась в графическом режиме через утилиту phlogin. Процессы, запускаемые пользователем, наследуют UID и GID той программы, из которой они запускались (т.е. родительского процесса).

Эффективные идентификаторы владельца и группы (Effective UID EUID и Effective GID, EGID) – предназначены для повышения гибкости меха-

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

фективные идентификаторы. При этом реальные идентификаторы не меняются.

112

Текущий рабочий каталог – путь (разделенный слэшами список ката-

логов), который будет автоматически добавляться к относительным именам файлов. Выводится на экран командой pwd.

Управляющий терминал (TTY) – терминальная линия (терминал или псевдотерминал) ассоциированный с процессом и, с которым связаны потоки ввода, вывода и ошибок. Если процесс становится процессом – демоном, то он отсоединяется от своей терминальной линии и не имеет ассоциированной терминальной линии. Запуск процесса как фонового – знак «&» в конце ко-

мандной строки - не является достаточным основанием для отсоединения процесса от терминальной линии [17].

Маска создания файлов (umask) – атрибуты доступа, которые будут заданы для файла, созданного процессом.

Приоритет процесса (prio) – с этим приоритетом создается главный поток процесса (порождаемый функцией main()). Процесс – это статическая субстанция, контейнер.

Дисциплина диспетчеризации – также больше относится к потоку.

Использование ресурсов процессора (статистика по времени выпол-

нения программы) – включает: время выполнения программы в прикладном контексте (user time – время выполнения инструкций, написанных програм-

мистом), время выполнения в контексте ядра (system time – время выполне-

ния инструкций ядра по запросу программы, т.е. системных вызовов), сум-

марное время выполнения всех дочерних процессов в прикладном контексте,

суммарное время выполнения всех дочерних процессов в контексте ядра.

Процесс всегда содержит хотя бы один поток. Для процессов исходный код которых подготовлен на языках С/С++, главным потоком процесса яв-

ляется поток, в котором исполняется функция, текстуально описанная под именем main()[15]. Рассмотрим программу, которая получает информацию о значении своих атрибутов (файл process.c):

113

#include <stdlib.h>

// вызов стандартной библиотеки

#include <sys/resourses.h>

// статистическая информация о процессе

int main(int argc, char **argv)

{

struct rusage r_usage; // задается структура последних четырех переметров

printf(“\nProcess Information:\n); // \n – переход на новую строку printf(“Process name = \t\t%\n”,argv[0]);

// \t – строка, argv[0] – выведем нулевой аргумент (процесс) printf(“User ID = \t\t<%d>\n”,getuid());

// по аналогии вывод остальной информации printf(“Effective User ID = \t<%d>\n”,geteuid()); printf(“Group ID = \t\t<%d>\n”,getgid()); printf(“Process Group ID = \t<%d>\n”,getpgid()); printf(“Process ID (PID) = \t<%d>\n”,getpid()); printf(“Parent PID (PPID) = \t<%d>\n”,getppid()); printf(“Process priority = \t<%d>\n”,getprio()); getrusage(RUSAGE_SELF, &r_usage);

printf(‘\t<user time=%d sec, %d microsec >\n’,r_usage, ru_utime,tv_sec, r_usage, ru_utime,tv_usec); printf(‘\t<system time=%d sec, %d microsec >\n’,r_usage, ru_stime,tv_sec, r_usage, ru_stime,tv_usec);

return EXIT_SUCCESS; // завершение работы программы

}

С помощью директивы #include подключаются две библиотки stdlib.h

(стандартная библиотека) и sys/resourses.h (статистическая информация о

процессе). Программа начинает выполняться с точки входа - функции main().

Параметрами этой функции являются argc – целое значение количества

аргументов командной строки при запуске программы и argv – массив, в ко-

тором находятся значения этих аргументов. Директива struct rusage задает

структуру r_usage для получения информации о статистике выполнения про-

граммы. Функция printf() подключаемая через библиотеку stdlib.h обеспечи-

вает вывод на экран сообщений. Управляющий символ \n переводит курсор

на новую строку экрана. Переменная argv[0] всегда содержит имя исполняе-

мой программы. Далее в функциях printf() происходит вызов функций для

получения соответствующих атрибутов процесса. Функция

getrusage(RUSAGE_SELF, &r_usage) возвращает в структуру r_usage инфор-

мацию о статистике выполнения программы. Оператором return программа

114

завершается и возвращает в создавший ее процесс сообщение

EXIT_SUCCESS (имеет значение 0) – успешное завершение.

Жизненный цикл процесса можно разделить на четыре этапа: создание,

загрузка образа процесса, выполнение процесса и завершение процесса.

Предком всех процессов QNX является администратор процессов

(процесс procnto) (рис. 39), идентификатор PID которого равен 1. Остальные процессы порождаются в результате вызова соответствующей функции дру-

гим процессом, именуемым родительским [15].

Первой из них является функция fork() – создает дочерний процесс пу-

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

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

(locks), установленные в родительском процессе, наследуются дочерним процессом. При клонировании родительский и дочерний процессы различа-

ются только идентификаторами PID и PPID. Функцию fork() рекомендуется использовать только в однопоточных программах.

Рассмотрим применение функции fork() на примере программы fork.c.

Функция fork() возвращает целое число, которое в родительском процессе равно идентификатору дочернего процесса, а в дочернем – нулю.

// fork.c

#include <stdlib.h> #include <sys/syspage.h>

int main(int argc, char **argv, char **env) // функция возвращает целое значение.

//argc – количство параметров передаваемых функции аргументов, //argv – сами аргументы, env – сообщения.

{

pid_t pid; // тип данных для хранения идентификатора процесса char *prefix;

115

prefix=(char*)malloc(sizeof(char));

// выделение памяти

pid=fork(); // новый процесс, дочерний к родительскому

if (pid==0) sprintf(prefix, ‘child’);

// был ли создан дочерний процесс

else sprintf(prefix,”parent’);

printf(“%s Process name =%s\n”,prefix,argv[0]); // вывод информации printf(“%s PID=%d\n”,prefix,getpid(0));

printf(“%s PPID=%d\n”,prefix,getppid(0));

return EXIT_SUCCESS; // завершение работы программы

}

На рис.44 показано выполнение программы fork.c.

Рис.44. Выполнение программы fork.c.

Функция vfork() (виртуальный fork()) – используется как «облегченная»

альтернатива паре вызовов fork()-exec(). В отличии от стандартной функции

fork(), она не выполняет реального копирования данных, а просто блокирует

родительский процесс, пока дочерний не вызовет exec().

116

Семейство функций exec() – заменяют образ вызвавшего процесса ука-

занным исполняемым файлом (execl(), execle(), execlp(), execlpe(), execv(),

execve(), execvp(), execvpe()). В функциях exec и spawn используются суффик-

сы: l – список аргументов (определяется через список данных непосредст-

венно в самом вызове); e – список аргументов указывается посредством оп-

ределения массива переменных; р – относительный путь, если не указан пол-

ный путь к файлу программы; v – список аргументов определяется через ука-

затель на массив аргументов. Функции семейства ехес() подменяют испол-

няемый код текущего процесса (не изменяя его идентификатор PID, права

доступа, внешние ресурсы процесса, а также находящийся в том же адресном

пространстве) исполняемым кодом из другого файла. Поэтому используются

эти вызовы непосредственно после fork() для замены копии вызывающего

процесса новым.

Программа exec.c иллюстрирует прохождение нового процесса с по-

мощью комбинации вызовов vfork() и exec():

#include <stdlib.h> #include <sys/syspage.h>

int main(int argc, char **argv, char **env)

//функция возвращает целое значение.

//argc – количство параметров передаваемых функции аргументов,

//argv – сами аргументы, env – сообщения.

{

pid_t pid; // тип данных

pid=fork(); // результат выполнения функции, определенной ранее, If (pid==0) //если pid=0, выполняется фнкция execlp

{execlp(“process”,”process”, NULL);

//программа завершается неудачей

Perror(“Child”);

Exit(EXIT_FAILURE);

}

Waitpid(0,NULL,0);

Printf(“Parants’s PID =%d\n”,getpid(0)); Printf(“Parants’s PPID =%d\n”,getppid(0)); Return EXIT_SUCCESS;

}

На рис.45 показано выполнение программы exec.c.

117

Рис.45. Выполнение программы exec.c.

Для запуска программы exec.c из интерпретатора команд sh необходи-

мо набрать имя программы exec и нажать клавишу Enter. После этого интер-

претатор команд sh запрашивает администратор процессов Procnto возмож-

ность создания нового процесса exec. Если процесс exec успешно создан, то интерпретатор команд sh блокируется до момента завершения процесса exec.

Родительский процесс exec создает дочерний процесс, вызывая функцию vfork(). В родительском процессе условие pid==0 не выполняется, так как pid

равен PID дочернего процесса, а он не может быть равен 0. И затем ожидает завершения дочернего процесса с помощью функции waitpid().

В дочернем процессе выполняется условие pid==0 и с помощью функ-

ции execlp(“process”,”process”, NULL) делается попытка загрузить програм-

118

му process, но путь к ней не указан и функция execlp возвращает ошибку, ко-

торая выводится в поток ошибок функцией perror(“Child”). Затем дочерний процесс завершается функцией Exit(EXIT_FAILURE) и возвращает в функцию waitpid() родительского процесса код возврата дочернего процесса. Роди-

тельский процесс выводит на экран свои идентификаторы PID и PPID и за-

вершается оператором return.

Семейство функций spawn() – сразу порождает дочерний процесс, за-

грузив указанный исполняемый файл (spawn(), spawnl(), spawnle(), spawnlp(), spawnlpe(), spawnp(), spawnv(), spawnve(), spawnvp(), spawnvpe()) [17]. Суф-

фиксы имеют аналогичное значение, что для описанной выше функции exec(). Это наиболее эффективный способ порождения процессов в QNX Neutrino. Функции семейства spawn () порождают новый процесс с новым идентификатором PID и в новом адресном пространстве. В файле spawn.c

представлен пример наиболее простого и быстрого способа порождения но-

вого процесса:

// spawn.c #include <stdlib.h>

#include <sys/syspage.h>

int main(int argc, char **argv, char **env)

{

spawnl(P_WAIT, “process”,”process” ,NULL); printf(“Parants’s PID =%d\n”,getpid(0)); printf(“Parants’s PPID =%d\n”,getppid(0)); return EXIT_SUCCESS;

}

Самый простой способ создания процесса - запустить из программного кода дочернюю копию командного интерпретатора, которому затем передать команду запуска процесса. Для этого используется вызов:

int system(const char * command),

где command - текстовая строка, содержащая команду, которую пред-

полагается выполнить ровно в том виде, в котором мы вводим ее командному интерпретатору с консоли.

119

Процесс завершается, если программа выполняет вызов exit () или вы-

полнение просто доходит до точки завершения функции main(), будь то с яв-

ным указанием оператора return или без него. Другой путь - посылка процес-

су извне (из другого процесса) сигнала, реакцией на который (предопреде-

ленной или установленной) является завершение процесса.

Рассмотрим временные затраты на создание процесса с помощью функ-

ции fork() на примере программы p2-1.cc [17]:

//p2-1.cc

#include <stdlib.h> #include <stdio.h> #include <inttypes.h> #include <iostream.h> #include <sys/neutrino.h> #include <process.h> #include <sys/syspage.h>

static double cycle2milisec ( uint64_t ccl ) { const static double s2m = 1.E+3;

const static uint64_t cps = SYSPAGE_ENTRY( qtime )->cycles_per_sec; // частота процессора:

return (double)ccl * s2m / (double)cps;

};

struct mbyte { #pragma pack( 1 )

uint8_t data[ 1024 * 1024 ]; #pragma pack( 4 )

};

int main( int argc, char *argv[] ) { mbyte *blk = NULL;

if( argc > 1 && atoi( argv[ 1 ] ) > 0 ) { blk = new mbyte[ atoi( argv[ 1 ] ) ]; };

uint64_t t = ClockCycles(); pid_t pid = fork();

if( pid == -1 ) { perror( "fork" ); exit( EXIT_FAILURE );} if( pid == 0 ) exit( EXIT_SUCCESS );

if( pid > 0 ) {

waitpid( pid, NULL, WEXITED ); t = ClockCycles() - t;

};

if( blk != NULL ) delete blk;

120