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

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

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

cout << "Fork time: " << cycle2milisec( t ) << " msec. [" << t << " cycles]" << endl;

exit( EXIT_SUCCESS );

};

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

но задается структура mbyte, в которой задается блок данных размером один мегабайт. В главном потоке проверяется условие (argc > 1 && atoi( argv[ 1 ]) > 0 ), где функция atoi() преобразует символьную сстроку соответствующую первому параметру из командной строки в целое число ( argv[ 0 ]

соответствует имени программы – p2-1). Если это значение больше нуля, то с помощью функции new() и структуры mbyte создается блок данных соответ-

ствующего размера. Затем с помощью функции ClockCycles() определяется в переменной t имеющей тип данных uint64_t текущее колисество процессорных циклов, котрое необходимо для определения времни затрачиваемого на создание процесса.

Функция fork() создает дочерний процесс. Для дочернего процесса pid = 0 поэтому условие выполняется и дочерний процесс сразу же после проверки условия завершается функцией exit( EXIT_SUCCESS ). В это время родительский процесс ожидает завершения дочерненого порцесса на функции waitpid( pid, NULL, WEXITED ). Как только дочерний процесс завершится в переменной t = ClockCycles() - t определяется количество процессорных затраченных на порожденние процесса. Затем удаляется блок данных blk и осуществляется вывод на экран через поток вывода cout и с помощью функции cycle2milisec( t ), преобразующей процессорные циклы в секунды, время на создание и уничтожение процесса. На рис.46 показано вы-

полнение программы p2-1.cc.

При запуске программы со значением параметра в Мбайтах от 1 до 100

наблюдается наблюдается близкая к линенной зависимость времени создания процесса от размера его образа в памяти от 17 до 1170 мсек.

121

Рис.46. Выполнение программы p2-1.cc.

Интересны не только затраты на порождение нового процесса, но и то,

насколько «эффективно» сосуществуют параллельные процессы в ОС, на-

сколько быстро происходит переключение контекста с одного процесса на

другой [17]. Для самой грубой оценки этих затрат создадим простейшее при-

ложение (файл р5.сс).

// P5.cc

#include <stdlib.h> #include <inttypes.h> #include <iostream.h> #include <unistd.h> #include <sched.h> #include <sys/neutrino.h>

int main( int argc, char *argv[] ) { unsigned long N = 1000;

122

if( argc > 1 && atoi( argv[ 1 ] ) > 0 ) N = atoi( argv[ 1 ] ); pid_t pid = fork();

if( pid == -1 ) cout << "fork error" << endl, exit( EXIT_FAILURE ); uint64_t t = ClockCycles();

for( unsigned long i = 0; i < N; i++ ) sched_yield(); t = ClockCycles() - t;

delay( 200 );

cout << pid << "\t: cycles - " << t << "; on sched - " << ( t / N ) / 2 << endl; exit( EXIT_SUCCESS );

};

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

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

то с помощью функци atoi() оно преобразуется в новое значение переменной

N . Функция fork() создает дочерний процесс. И в дочернем и в родительском потоках определяется переменных t (своих для каждого процесса) имеющей тип данных uint64_t текущее колисество процессорных циклов, котрое необходимо для определения времни затрачиваемого на создание процесса.

Затем в каждоим из них запускается N раз for-цикл, где выполняется единственная функция sched_yield(). Она переводит вызвавший ее процесс

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

После завершения цила каждый процесс определяет в своей переменной t = ClockCycles() - t количество процессорных затраченных на выполнение циклов. Делает задержку на 0,2 секунды и выводит на экран через поток вывода cout результаты своей работы. На рис.47 показано вы-

полнение программы p5.cc в перспективе System Profiler.

Таким образом, здесь использована симметричная схема, где два одно-

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

что они даже не анализируют PID после выполнения fork(), они только в мак-

симальном темпе «перепасовывают» друг другу активность. Рис. 48 иллюст-

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

123

Рис.47. Выполнение программы p5.cc.

Такая работа возможна при round routing диспетчеризации. На рис. 48

черными стрелками обозначена передача управления от потока к потоку раз-

личных процессов. Результат выполнения программы с различными началь-

ными значениями для процессора с частотой 535 мгц, показывает, что время передачи управления составляет 0,2 миллисекунды (600 циклов).

Процесс А

PID = 671779

Поток main:

TID = 1

Процесс B

PID = 671786

Поток main:

TID = 1

Рис. 48. Симметричное взаимодействие потоков

124

Завершение процесса. Завершить процесс можно, направив ему сигнал с помощью утилиты slay или kill в командной строке, или из программы с помощью функций: kill(), sigqueue() и др. Завершение процесса выполняется в две стадии.

На первой – происходит «физическое» уничтожение процесса, т.е. за-

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

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

цесс (главный поток) находится в состояни «зомби».

На второй – уничтожается структура данных о процессе, хранящаяся в оперционной системе. Эта стадия выполняется Администратором процессов внутри самого себя.

Все потоки процесса - «зомби» находятся в состоянии «DEADблоки-

рован».

3.4. Потоки в QNX6

Поток можно понимать, как любой автономный последовательный (ли-

нейный) набор команд процессора. Идентификатором потока (значимым только внутри одного процесса!) является TID (Thread ID), присваиваемый потоку при его создании вызовом pthread_create(). TID позволяет процессу (а

также системе в рамках процесса) однозначно идентифицировать каждый по-

ток. Нумерация TID в QNX начинается с 1 (это всегда главный поток процес-

са, порожденный main()) и последовательно возрастает по мере создания по-

токов (до 32767) [15]. Другим важнейшим атрибутом потока является при-

оритет его выполения. Для каждого из уровней приоритетов, обслуживаемях системой (в QNX 6.3 таких уровней 256), поддерживается циклическая оче-

редь потоков, готовых к исполению (фактически большая часть из таких оче-

редей оказывается пустой).

125

Потоки создаются и уничтожаются динамически, и их количество внут-

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

(pthread_create()) включает в себя выделение и инициализацию необходи-

мых ресурсов внутри адресного пространства процесса (например, стека по-

тока), а также запуск выполнения потока с некоторой функции в данном ад-

ресном пространстве.

Завершение потока (pthread_exit(), pthread_cancel()) включает в себя ос-

танов потока и освобождении ресурсов потока. Когда поток запущен, он мо-

жет находитьсяв одном из двух состояний: «готов» (ready) или «блокирован»

(blocked) [18]. Поток может иметь одно из следующих состояний (рис.49):

CONDVARпоток блокирован на условной переменной (например, при вызове функции pthread_condvar_wait());

DEADпоток завершен и ожидает завершение другого потока;

INTERRUPT-поток блокирован и ожидает прерывания (т.е. поток вы-

звал функцию InterruptWait);

JOIN-поток- блокирован и ожидает завершение другого потока ( на-

пример, при вызове функции pthread_join());

MUTEXпоток блокирован блокировкой взаимного исключения;

NANOSLEEP-поток находиться в режиме ожидания в течение корот-

кого периода времени (например, при вызове функции nanosleep());

NET_REPLY-поток ожидает ответа на сообщение от другого узла сети

(т.е. поток вызвал функцию MsgReply*());

NET_SENDпоток ожидает получения импульса или сигнала от друго-

го узла сети (т.е. поток вызвал функцию MsgSendPulse(), MsgDeliverEvent() или SignalKill());

READYпоток ожидает выполнения, пока процессор занят выполнени-

ем другого потока равного или более высокого приоритета; RECEIVE-

поток блокирован на операции получения сообщения (например, при вызове функции MsgReceive());

RUNNINGпоток выполняется процессором;

126

Рис. 49 Возможные состояния потока в QNX Neutrino

REPLYпоток блокирован при ответе на сообщение (т.е. при вызове функции MsgSend() и получении сообщения сервером);

SEMпоток ожидает освобождения семафора (т.е. поток вызвал функ-

цию SyncSemWait());

SENDпотокблокируется приотправке сообщения (т.е. после вызова функ-

цииMsgSend(),нополучениесообщениясерверомеще непроизошло);

SIGSUSPENDпоток блокирован и ожидает сигнала (т.е поток вызвал функцию sigsuspend());

SIGWAITINFOблокирован и ожидает сигнала (т.е. поток вызвал функцию sigwaitinfo());

STACK-поток ожидает выделения виртуального адресного пространства для своего стека (родительский поток вызывает функцию ThreadCreate());

STOPPEDпоток блокирован и ожидает сигнала SIGCONT;

127

WAITCTX-поток ожидает доступности нецелочисленного контекста

(например, с плавающей запятой);

WAITPAGEпоток ожидает выделения физической памяти для вирту-

ального адреса;

WAITTHREADпоток ожидает завершения создания дочернего потока

(т.е. поток вызвал функцию ThreadCreate()).

Управление потоком заключается в его создании, изменении его пара-

метров в процессе выполнения и завершения потока. Процесс состоит мини-

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

званы. Остальные потоки создаются с помощью функции: pthread_create(&tid, &attr, &func, &arg).

Для вызова функции pthread_create() необходимо использовать библио-

теку pthead.h. Функция имеет 4 параметра [19]. Обязательным параметром является только func – точка входа в поток, т.е. имя функции ,с которой нач-

нет выполнение поток, своего рода «потоковая функция» main().

Первым параметром является возвращаемое значение идетификатора

TID созданного потока. Его бывает полезно знать, например, для уничтоже-

ния созданного потока функцией: pthread_cancel( tid ).

Второй параметр содержит адрес структура атрибутов attr, на основе которой задаются атрибуты создаваемого потока. содержит ряд весьма по-

лезных полей. Если вместо этой структуры при создании потока указать

NULL, то поток будет создан с атрибутами по умолчанию (при этом пара-

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

тов полезно задать: pthread_attr_init(&attr);

после инициализации структуры можно читать/модифицировать ее поля, ис-

пользуя набор функций :

128

pthread_attr_get *()-для того, чтобы узнать значение атрибута;

pthread_attr_set *()-для того, чтобы задать значение атрибута.

Вкоде реальных приложений очень часто можно видеть простейшую форму вызова, порождающего новый поток, в следующем виде:

pthread_create( NULL, NULL, &thread_func, NULL );

И для многих целей такого вызова достаточно, так как созданный поток будет обладать свойствами, предусмотренными по умолчанию (преимущест-

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

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

Атрибутная запись потока должна создаваться и обязательно инициа-

лизироваться вызовом pthread_attr_init() до точки порождения потока. Любые последующие изменения атрибутной записи создания потока не производят никаких изменений в поведении потока (хотя некоторые из параметров пото-

ка, определяемых атрибутной записью при его создании, могут быть измене-

ны позже, уже в ходе выполнения потока, вызовом соответствующих функ-

ций). Таким образом, атрибутная запись потока является чисто инициализи-

рующей структурой и может быть даже уничтожена следующим оператором после порождения этого потока.

Эффект повторной инициализации атрибутной записи не определен.

Для ее повторного использования (если требуется переопределение значений параметров) должен быть предварительно выполнен вызов pthread_attr_destroy() с последующей повторной инициализацией структуры

(он разрушает атрибутную запись, но без освобождения ее памяти).

Список функций управления атрибутами выглядит довольно большим

(18 функций), но они сгруппированы по парам «get» — « set», т e. в каждой паре есть функция как получения параметров (get), так и их установки (set) .

Флаги (булевы характеристики)

129

pthread attr~getdetachstate(); pthread attr_setdetachstate(); pthread attr~getinheritsched(); pthread_attr setinheritsched(); pthread attr~getscope(); pthread attr setscope();

Параметры стека

pthread attrgetguardsize(); pthread attr_setguardsize() pthread attrgetstackaddr(); pthread attr_setstackaddr() pthread attr._getstacksize(); pthread attr setstacksize()

Параметры диспетчеризации

pthread alt rgetschedparar(); pthread_attr_setschedparain() pthread alt r_getschedpolicy(); pthread attr setschedpolicy()

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

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

зова: int pthread_join( pthread_t thread, void** value_ptr ), где thread - иденти-

фикатор TID ожидаемого потока, который возвращался как первый параметр вызова pthread_create( pthread_t* thread, …) при его создании или был им же получен после своего создания вызовом pth read_self (); value_ptг - NULL или указатель на область данных (результата выполнения), которую завершаю-

щийся поток, возможно, захочет сделать доступной для внешнего мира после своего завершения. Этот указатель функция потока возвращает оператором return или вызовом pthread_exit().

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

#include <stdlib.h> #include <stdio.h> #include <iostream.h>

130