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

Text111

.pdf
Скачиваний:
49
Добавлен:
06.02.2018
Размер:
1.16 Mб
Скачать

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

kill(pid, SIGKILL)

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

Пример использования описанных системных функций в Unix задается программами листингов 7.3.1а, 7.3.1b и 7.3.1c.

#include <stdio.h> #include <sys/types.h> #include <signal.h> int main()

{int rc, k;

printf("Parent Proccess\n");

 

rc=fork();

 

if (!rc) {execl("child1.exe",0);

}

printf("For Child Process:\n");

printf("PID=%d \n", rc);

for (k=0; k<10; k++)

{printf("I am Parent (My K=%d)\n", k); if (k= =6)

{kill(rc, SIGKILL); printf("I am killing my child with PID=%d\n",rc);} usleep(2000000);

}

printf("Parent ended\n"); exit(0);

}

Листинг 7.3.1a. Программа procest.c для Unix

#include <stdio.h> int main()

{int rc, k;

printf("First Child Proccess\n"); rc=fork();

if (!rc) {execl("child2.exe",0); }

printf("For Second Child Process:\n"); printf("PID=%d \n", rc); for (k=0; k<30; k++)

{printf("I am First Child (My K=%d)\n", k); usleep(750000); } printf("First Child ended\n");

exit(0);

}

Листинг 7.3.1b. Программа child1.c для Unix

#include <stdio.h> int main()

{int k;

printf("Demonstration processes, Child 2 Proccess\n"); for (k=0; k<30; k++)

{printf(" ------- I am second Child ... (child2=%d)\n", k); sleep(1); } printf("Child 2 ended\n");

exit(0);

}

Листинг 7.3.1c. Программа child2.c для Unix

Для работающего примера в Unix необходимо создать три исполняемых файла procest.exe, child1.exe и child2.exe, изготовляемых из исходных текстов в листингах 7.3.1а, 7.3.1b, 7.3.1c. Программа procest.c описывает создание еще одного процесса из исполняемого файла child1.exe, который будет дочерним процессом для исходного процесса по программе procest.c, а процесс child2.exe, автоматически запускаемый им на основе программы в листинге 7.3.1c, оказывается процес- сом-внуком для родительского. Программа в листинге 7.3.1а – после создания процесса и циклической выдачи сообщений на 6-м шаге – приказывает оператором

kill(rc, SIGKILL)

уничтожить дочерний процесс. В результате этого приказа процесс по программе child1.c прекращается, но все еще продолжается процесс по программе child2.c в листинге 7.3.1с, пока он не завершит все действия, предусмотренные в ней. Заметим, что для использования в программе для Unix системных вызовов kill() необходимо включение в исходную программу на языке Си заголовочных файлов types.h и signal.h, первый из которых размещается в подкаталоге sys основного каталога для хранения заголовочных файлов системы программирования на языке Си.

Для уничтожения процессов в операционных системах Windows предназначена функция TerminateProcess(), которая имеет прототип

BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode)

и в котором аргумент hProcess задает какой именно процесс следует уничтожить, а аргумент uExitCode – код завершения для такого принудительного завершения. Нормальный (не принудительный) код завершения задает функция

VOID ExitProcess(UINT uExitCode),

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

Программы для Windows, выполняющие те же задачи, что и рассмотренные выше программы из листингов 7.3.1а, 7.3.1b и 7.3.1c для Unix, предлагаются читателю в качестве упр. 1 в конце данной главы.

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

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

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

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

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

Наиболее мощные, но и сложные средства ожидания завершения процесса использованы в OS/2. Во-первых, процесс, завершение которого предполагается не только ожидать, но и получить от него значение кода завершения, должен запускаться функцией создания процесса со специальным значением параметра флагов, а именно этот параметр должен задаваться символической константой EXEC_ASYNCRESULT. Если требуется ожидание только самого момента завершения, то достаточно использовать константу EXEC_ASYNC.

Для задания собственно ожидания завершения процесса в этой ОС предназначена функция с прототипом

APIRET DosWaitChild(ULONG action, ULONG option, RESULTCODES *pres, PID *ppid, PID pid),

где параметр action задает чего именно требуется ожидать – завершения лишь одного процесса (задается константой DCWA_PROCESS) или завершения всей совокупности процессов-потомков, начиная с указанного процесса (задается константой DCWA_PROCESSTREE). Параметр option определяет режим выполнения данной функции и может задаваться константами DCWW_WAIT и DCWW_NOWAIT. Первая из них задает действительное ожидание, а вторая требует опросить состояние завершения и немедленно вернуть информацию об этом, не блокируя выполнение текущего процесса. Последний параметр pid задает идентификатор процес-

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

Значение кода возврата, возвращаемое от завершенного процесса после выполнения рассматриваемой функции, находится в поле codeResult возвращаемого экземпляра pres структуры RESULTCODES. При этом поле codeTerminate этого возвращаемого параметра дает информацию о качественной причине завершения процесса. Эти причины описываются символическими константами и отображают как нормальное завершение, так и завершение по причине уничтожения другим процессом.

В операционной системе Unix имеется ранний вариант функции ожидания wait() с прототипом

pid_t wait(int *status).

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

pid_t waitpid(pid_t pid, int *status, int option),

которая позволяет задать с помощью значения pid завершение какого именно процесса будет ждать данный процесс, выполняющий функцию waitpid(), а параметром option - блокирующим или нет будет вызов функции. Использование этой функции требует включение заголовочного файла директивой #include <sys/wait.h>. Использование константы WNOHANG из этого файла в качестве параметра option обеспечивает немедленное возвращение функцией waitpid значения 0, если запращиваемый процесс еще не завершен. При нулевом значении этого параметра обеспечивается ожидание завершения процесса.

Следующая программа, приведенная в листинге 7.4.1, является исходным текстом для исполняемого файла procesw.exe, предназначенного для Unix. Для ее использования необходимо в автоматически доступном каталоге (в частности текущем) иметь исполняемый файл child1.exe, созданной из исходной программы, приведенной в листинге 7.3.1b.

#include <stdio.h> #include <sys/types.h> #include <signal.h> int main()

{int rc, k;

printf("Parent Proccess\n"); rc=fork();

if (!rc) {execl("child1.exe",0); } printf("For Child Process: PID=%d \n", rc); wait(&rc);

printf("I have waited my child with retcode =%d\n",rc); for (k=0; k<10; k++)

{printf("I am Parent (My K=%d)\n", k); usleep(2000000);

}

printf("Parent ended\n"); exit(0);

}

Листинг 7.4.1. Программа procesw.c для Unix

Вэтой программе – после создания дочернего процесса и выдачи учетной информации о нем (идентификатора процесса PID) – вызывается функция wait() для ожидания завершения дочернего процесса. Наблюдение за поведением процессов, образующихся при запуске этой программы, позволяет явно видеть, что после выполнения wait() и до завершения дочернего процесса родительский приостановлен

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

Воперационных системах Windows для ожидания завершения процесса применяется универсальная функция ожидания WaitForSingleObject, имеющая прототип

DWORD WaitForSingleObject(HANDLE hProcess, DWORD Timeout).

Здесь первый параметр задает хэндл того процесса, завершения которого следует ждать, а второй параметр – максимальное время ожидания в миллисекундах. Если величину Timeout задать символической константой INFINITE, то ожидание неограниченно. В других случаях после истечения времени, заданного в Timeout, функция возвращает управление со значением самой функции, равным символической константе WAIT_TIMEOUT, по которой программа может определить, что ожидание было неудачным.

При удачном завершении ожидания можно получить код завершения дочернего процесса. Для его получения служит функция GetExitCodeProcess() с прототипом

BOOL GetExitCodeProcess(HANDLE hProcess, LPDWORD pExitCode). Следующая программа, приведенная в листинге 7.4.2, является исходным тек-

стом для исполняемого файла Procesw.exe, предназначенного для Windows. Для ее использования необходимо в автоматически доступном каталоге (в частности текущем) иметь исполняемый файл Child1.exe, действия которого аналогичны программе из листинга 7.3.1b, но предназначеного для Windows.

#include <windows.h> #include <stdio.h>

int main() {int k; DWORD rc;

DWORD CreationFlags; STARTUPINFO si; PROCESS_INFORMATION pi;

printf("Demonstration processes, Main Proccess\n"); memset(&si, 0, sizeof(STARTUPINFO)); si.cb=sizeof(si); CreationFlags = NORMAL_PRIORITY_CLASS; rc=CreateProcess(NULL, "child1.exe", NULL, NULL, FALSE,

CreationFlags, NULL, NULL, &si, &pi);

if (!rc)

{printf("Error create Process, codeError = %ld\n", GetLastError()); getchar(); return 0; }

printf("For Child Process:\n");

printf("hProcess=%d hThread=%d ProcessId=%lu ThreadId=%lu\n", pi.hProcess, pi.hThread, pi.dwProcessId, pi.dwThreadId);

WaitForSingleObject(pi.hProcess, INFINITE);

printf("It terminated child with hProcess\n", pi.hProcess); GetExitCodeProcess(pi.hProcess, &rc); printf("ExitCodeProcess=%ld\n", rc);

for (k=0; k<10; k++)

{printf("I am Parent... (my K=%d)\n", k); Sleep(2000); } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return 0;

}

Листинг 7.4.2. Программа Parentw.c для Windows

В этой программе – после создания дочернего процесса и выдачи учетной информации о нем (идентификатора в pi.hProcess) – вызывается функцию WaitForSingleObject(pi.hProcess, INFINITE) для ожидания завершения дочернего процесса, сколько бы долго он не длился. Получение информации о завершении процесса выполняется функцией GetExitCodeProcess(pi.hProcess, &rc).

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

Упражнения

1. Разработать программы для Windows, выполняющие те же задачи, что и рассмотренные выше программы в листингах 7.3.1а, 7.3.1b, 7.3.1c для Unix. Аналогично рассмотренным примерам, при выполнении упражнения необходимо создать три исполняемых файла Procest.exe, Child1.exe и Child2.exe. Программа Child1.c должна описывать создание (из исполняемого файла child2.exe) процесса, который будет процессом-внуком для исходного процесса.

8.МНОГОПОТОЧНОЕ ФУНКЦИОНИРОВАНИЕ ОС

8.1.Понятие нити и связь ее с процессом

Всовременных операционных системах широко используются нити (thread), называемые несколько неточно в русском переводе также потоками. Это понятие возникло как результат развития понятия абстрактного процесса. Оказалось, что иногда целесообразно процесс – как некоторую общность программного выполнения, имеющую прикладную цель, – разбить на части, которые выполнялись бы параллельно, конкурируя за главный ресурс – процессор, но в остальном выполняли бы общую работу. Можно подойти к осознанию понятия процесса с другой стороны. Абстрактные процессы теоретически разделяют на конкурирующие и кооперативные. Конкурирующие процессы по существу мешают друг другу, но в совокупности выполняют много работ одновременно. Кооперативные процессы выполняют по частям общую работу совместно. Организационно-техническое объединение (под одной "крышей" обобщенного процесса) аналогов кооперативных процессов и составляет существо объединения нитей в одном процессе.

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

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

Чем же владеет отдельная нить? Нити принадлежит только текущая совокупность значений в аппаратных узлах хранения информации (регистрах) и локальные переменные подпрограмм. Текущую совокупность значений в аппаратных узлах хранения информации называют контекстом задачи. Когда происходит переключение между задачами (нитями), запоминается контекст приостанавливаемой задачи и восстанавливается из места сохранения контекст запускаемой на процессор задачи. В современной архитектуре процессоров много внимания уделяется аппаратной реализации сохранения контекста, его восстановлению и переключению между задачами. Зачем необходимо запоминать и восстанавливать контекст задачи? По той же причине, по которой сохраняется и восстанавливается содержимое аппаратных узлов при возникновении прерывания.

Обычно нить своей работой реализует действия одной из процедур программы. Теоретически любой нити процесса доступны все части программы процесса, в частности, все его процедуры, но реально работа организуется так, чтобы нити отвечала отдельная процедура. Учитывая, что процедуре для нормальной работы необходимы локальные переменные, становится понятным закрепление области этих переменных за нитью. Объект хранения локальных переменных (вместе со служебной информацией при вызове подпрограмм) называют стеком. (Более точное понятие стека программы формируется только с помощью архитектуры процессора.) Этот стек в действительности является частью оперативной памяти, он используется не только программно, но и аппаратно, в частности, при реализации прерываний. Стек процедуры является неотъемлемой частью ресурсов, принадлежащих процедуре. (Более точным термином является кадр стека для процедуры или фрейм стека.)

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

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

Для задания многопоточного приложения как результата разработки в системах программирования Borland Inc. (Inprise Inc.) при использовании командного вызова компилятора следует обязательно использовать опцию -tWM, так что вы-

зов компилятора для построения исполняемой программы из файла prog.c будет записываться в виде

BCC32 -tWM prog.c

Для компиляции исходного файла в системе MS Visual C++ с помощью компилятора CL.EXE этого пакета следует использовать опцию /MT, так что аналогичный вызов компилятора будет иметь вид

CL /MT prog.c

При использовании интегрированных систем разработки нужно учитывать необходимость установки флажка многопоточного приложения (флажка Multithread) в соответствующем диалоговом окне окна цели (target) разработки. В частности, в среде разработки Borland C++ этот флажок необходимо установить в окне TargetExpert с заголовком New Target, возникающим после выбора подпункта New в пункте Project меню, выпадающего из пункта File главного меню.

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

gcc prog.c /usr/lib/libpthread

а вторая задает эту же библиотеку неявно и записывается в виде gcc prog.c -lpthread

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

8.2. Создание нитей (thread) в программе

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

В операционной системе OS/2 для создания нити служит функция DosCreateThread, которая имеет прототип

APIRET DosCreateThread(PTID ptid, PFNTHREAD pfn,

ULONG param, ULONG flag, ULONG cbStack),

где ptid – указатель на возвращаемый номер (идентификатор) нити, pfn – имя подпрограммы (описанной выше в программе прототипом), param – передаваемый в процедуру параметр, cbStack - размер стека для нити. Аргумент flag задает, запускается ли в работу нить сразу после создания или создается приостановленной. В первом случае значение flag = 0, во втором flag = 1. Эти значения могут быть указаны символическими константами CREATE_READY и CREATE_SUSPENDED. Тип PFNTHREAD определяет процедуру нити как имеющую тип VOID с

единственным аргументом типа ULONG. Используя явное преобразование типов, можно задать при создании нити использование любого вида процедуры.

В операционной системе Unix многопоточное программирование появилось достаточно поздно. К настоящему времени эта возможность входит в стандарт POSIX для Unix и поддерживается во всех современных ОС. Использование нитей при этом требует подключения заголовочного файла pthread.h. Системная функция создания нити в Unix по указанному стандарту имеет прототип

int pthread_create(pthread_t* tid, const pthread_attr_t* att, void*(*fun)(void*), void* argp).

Первый параметр этой функции возвращает идентификатор созданной нити. (В качестве дополнительных возможностей предлагается задавать в этот параметр исходное значение NULL, тогда идентификатор не возвращается). Важнейшим и обязательным параметром является адрес процедуры нити, этот параметр стоит третьим в списке и ему должна отвечать процедура с возвращаемым типом void* и единственным аргументом типа void*. При ином построении процедуры (что практически не имеет смысла, если эта процедура используется только для работы нити) требуется указывать явное преобразование типа. Последний, четвертый параметр, предназначен для передачи конкретного аргумента при создании нити. Второй параметр вызова attr – для задания атрибутов, отличных от атрибутов по умолчанию, причем параметр является указателем на место объекта, содержащего эти атрибуты. В простейшем и многих иных случаях этот параметр задается как NULL, что информирует ОС о необходимости использовать атрибуты по умолчанию. (О более сложном использовании этого параметра здесь рассказываться не будет.) Если функция создания нити возвращает значение >0, то в ходе выполнения системной функции произошла ошибка, и нить не была создана. Эту ситуацию рекомендуется анализировать в программе. Нулевое возвращаемое значение соответствует безошибочному выполнению, а положительное значение выдает непосредственно числовое значение кода ошибки.

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

int pthread_exit(void *status),

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

Соседние файлы в предмете Операционные системы