процесса возвращается в stat_loc, а его идентификатор – как значение функции. Переменную stat_loc можно проанализировать с помощью следующих макросов, приведённых в таблице 12.
Таблица 12. Макросы для анализа статуса завершения процесса
Макрос |
Значение |
WIFEXITED(status) |
Возвращает значение «истина» (не нуль), если про- |
|
цесс завершился нормально (вернул 0). |
WEXITSTATUS(status) |
Если WIFEXITED(status) не равно нулю, определяет |
|
код возврата завершившегося процесса (аргумент |
|
функции exit). |
WIFSIGNALLED(status) |
Возвращает значение «истина», если процесс завер- |
|
шился по сигналу. |
WTERMSIG(status) |
Если WIFSIGNALLED(status) не равно нулю, опре- |
|
деляет номер сигнала, вызвавшего завершение вы- |
|
полнения процесса. |
WCOREDUMP(status) |
Если WIFSIGNALLED(status) не равно нулю, макрос |
|
возвращает истину в случае создания файла core. |
Пример использования вызова wait приведён в листинге 13.
Листинг 13. Использованием вызова wait
1.cpid = fork();
2.if(cpid != 0){
3.cpid = wait (&status)
4.if (WIFEXITED(status))
printf (“Процесс %d завершился успешно\
со значением”, cpid, WEXITSTATUS(status));
5. }
Системный вызов waitid позволяет указать, завершение какого процесса необходимо ожидать, для чего используются аргументы idtype и id
(см. табл. 13).
Таблица 13. Значения аргумента idtype.
Значение |
Описание idtype |
аргумента |
|
P_PID Необходимо ожидать изменений в состоянии дочернего процесса с идентификатором, заданным в id.
P_PGID Требуется ждать изменения в состоянии одного из дочерних процессов, принадлежащих к группе, указанной в id.
P_ALL Означает, что ожидать нужно изменения состояния любого из дочерних процессов. При этом значение параметра id игнорируется.
86
Ожидаемые изменения состояния дочернего процесса задаются в параметре options путём объединения логическим ИЛИ следующих макросов:
Таблица 14. Макросы для задания ожидаемых изменений состояния процесса.
Макросы |
Что ожидается |
WEXITED |
Завершение процесса. |
WTRAPPED |
Ловушки (англ. trap) или точки останова (англ. breakpoint) |
|
для трассируемых процессов. |
WSTOPPED |
Остановки процесса из-за получения сигнала. |
WCONTINUED |
Ожидать продолжения выполнения процесса после его ос- |
|
тановки. |
В дополнение к перечисленным макросам можно задать дополнительные параметры поведения функции waitid:
Таблица 15. Макросы, задающие дополнительные параметры поведения waitid.
Макрос |
Поведение функции waitid |
WNOHANG |
Сразу же завершить свою работу, если в системе отсутсвует |
|
процесс, изменение состояния которого требуется ожидать. |
WNOWAIT |
Предписывает получить статусную информацию, но не |
|
уничтожать её, оставив дочерний процесс в состоянии |
|
ожидания. |
6.2.2. Ожидание завершения нити. Синхронизация выполнения нитей
Чтобы узнать код завершения нити (какое значение будет возвращено её оператором return) используется функция pthread_join, которая имеет следующий заголовок:
int pthread_join(pthread_t thread, void** value_ptr);
Эта функция аналогично вызову waitpid дожидается завершения нити с идентификатором thread и записывает возвращаемое ею значение в переменную, на которую указывает value_ptr.
Для организации взаимоисключения стандарт POSIX предлагает использовать так называемый mutex (от англ. mutual exclusion – взаимное исключение).
В программах mutex представляется переменными типа pthread_mutex_t. Прежде чем начать использовать объект типа pthread_mutex_t, необходимо провести его инициализацию. Это можно сделать при помощи функции:
#include <pthread.h>
int pthread_mutex_init (pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
87
В случае если мы хотим создать mutex с атрибутами по умолчанию, мы можем воспользоваться макросом PTHREAD_MUTEX_INITIALIZER.
После того как мы проинициализировали mutex, мы можем «захватить» его при помощи одной из функций:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex);
Первая из двух функций просто «захватывает» mutex, при этом, если на данный момент mutex уже «захвачен» другой нитью, эта функция дождётся его освобождения. Вторая же функция попытается «захватить» mutex, если он свободен, а если он окажется занят, то немедленно возвратит специальный код ошибки EBUSY. То есть pthread_mutex_trylock фактически является неблокирующим вариантом вызова pthread_mutex_lock.
При этом надо заметить, что нить, которая уже владеет mutex, не должна повторно пытаться захватить mutex, так как при этом либо будет возвращена ошибка, либо может произойти то, что в англоязычной литературе называется self-deadlock (самотупиковая ситуация). В этом случае нить будет ждать освобождения mutex до тех пор, пока сама не освободит его, то есть фактически до бесконечности.
Для освобождения захваченного mutex'а предназначена функция:
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
Стоит ещё раз подчеркнуть, что нить может освобождать только тот mutex, который был ранее захвачен ею. Результат работы функции pthread_mutex_unlock при попытке освободить захваченный другой нитью или вообще свободный mutex не определён. Другими словами вызов этой функции корректен, если данная нить перед этим успешно выполнила либо pthread_mutex_lock, либо pthread_mutex_trylock и не успела выполнить комплиментарный им pthread_mutex_unlock.
Мы обязаны рано или поздно (но в любом случае до повторного использования памяти, в которой располагается объект типа pthread_mutex_t) уничтожить mutex, освободив связанные с ним ресурсы. Сделать это можно, вызвав функцию:
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
88
При этом на момент вызова этой операции уничтожаемый mutex должен быть свободен, то есть не захвачен ни одной из нитей, в том числе вызывающей данную операцию. В случае если мы инициализировали mutex при помощи макроса PTHREAD_MUTEX_INITIALIZER, то необходимость в его явном уничтожении отсутствует.
6.2.3. Неименованные (программные) каналы
Вспомните синтаксис организации программных каналов при работе в командной строке shell:
$cat myfile | sort
При этом (стандартный) вывод программы cat, которая выводит содержимое файла myfile, передаётся на (стандартный) ввод программы sort, которая, в свою очередь, отсортирует предложенный материал.
Таким образом, два процесса обменялись данными. При этом использовался программный канал, обеспечивающий однонаправленную передачу данных между двумя задачами.
Для создания канала в программе используется системный вызов pipe.
#include <unistd.h>
int pipe (int *filedes);
Этот вызов возвращает два файловых дескриптора: filedes[1] для записи в канал и filedes[0] для чтения из канала. Теперь, если один процесс записывает данные в filedes[1], то другой сможет получить эти данные из filedes[0]. Вопрос только в том, как другой процесс сможет получить сам файловый дескриптор filedes[1]? Вспомним наследуемые атрибуты при создании процесса. Дочерний процесс наследует и разделяет все назначенные файловые дескрипторы родительского. То есть доступ к дескрипторам filedes канала может получить сам процесс, вызвавший pipe, и его дочерние процессы. В этом заключается серьёзный недостаток каналов, поскольку они могут быть использованы для передачи данных только между родственными процессами. Каналы не могут использоваться в качестве средства межпроцессного взаимодействия между независимыми процессами.
Хотя в приведённом примере может показаться, что процессы cat и sort независимы, на самом деле оба этих процесса создаются процессом shell и являются родственными.
6.3.Взаимодействие «неродственных» процессов
6.3.1.Идентификаторы ресурсов в межпроцессном взаимодействии
Средства межпроцессного взаимодействия первого типа требуют наличия средств идентификации совместно используемых объектов. Множество
89
возможных имён объектов конкретного типа межпроцессного взаимодействия называется пространством имён (англ. name space). Имена являются важным компонентом системы межпроцессного взаимодействия (англ. InterProcess Communications, IPC) для всех объектов, поскольку позволяют различным процессам получить доступ к общему объекту.
Имя для объектов IPC называется ключом (англ. key) и генерируется функцией ftok, исходя из: имени некоторого доступного для всех процессовучастников взаимодействий файла и односимвольного идентификатора:
#include <sys/types.h> #include <sys/ipc.h>
key_t ftok(char *filename, char proj);
В качестве первого параметра используется имя некоторого файла (полное или относительное), известное всем взаимодействующим процессам. Например, это может быть имя программы-сервера. Важно, чтобы этот файл существовал на момент создания ключа, при этом содержание файла значения не имеет. Также нежелательно использовать файл, который создаётся, изменяется и/или удаляется в процессе взаимодействия процессов.
Одному идентификатору (ключу) может соответсвовать несколько разнотипных общих ресурсов. Например, набор семафоров и разделяемая память.
Исключением из правила является использование «именованного канала», в котором общий файл непосредственно используется для организации взаимодействий.
6.3.2. Именованные каналы. FIFO
Название каналов FIFO происходит от выражения First In First Out (первый вошёл, первый вышел). FIFO очень похожи на программные каналы, поскольку являются однонаправленным средством передачи данных, причём чтение данных происходит в порядке их записи. Однако в отличие от программных каналов, FIFO имеют имена, которые позволяют независимым процессам получить к этим объектам доступ. Поэтому иногда FIFO также называют именованными каналами. FIFO являются средством UNIX System V и не используются в BSD. Впервые FIFO были представлены в System III, однако они до сих пор не документированы и поэтому мало используются. FIFO является отдельным типом файла в файловой системе UNIX (команда ls -l покажет символ «р» в первой позиции). Для создания FIFO используется системный вызов:
#include <sys/type.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>
90