В рамках решаемой стандартом POSIX общей задачи обеспечения
мобильности приложений на уровне исходных текстов можно выделить
подзадачу
В качестве средств для решения этой подзадачи в стандарте POSIX-2001 присутствует целый ряд идей, понятий, объектов и функций, которые можно сгруппировать в следующие предметные области:
) основано на применении
функций posix_spawn() и posix_spawnp(). Оно в любом случае менее
тяжеловесно (хотя и тяжеловеснее порождения потоков управления), но
особенно актуально для аппаратных конфигураций, в которых отсутствует
поддержка виртуальной памяти и, как следствие, реализация функции проблематична
Основные понятия, ассоциированные с
Чтобы время доступа к объекту было по возможности минимальным и не превышало заданной величины, этот объект делают резидентным в физической памяти. Применительно к процессам подобный подход реализуется посредством удержания в памяти страниц, принадлежащих прикладному процессу.
Достижению той же цели – минимизации времени (а также унификации)
доступа – служит механизм
С помощью функции mmap() строится
Отметим, что
Средства
При стандартизации средств
В качестве дополнительных целей объявлены:
Еще одна дополнительная цель – простота интерфейса. Семейство exec*()
насчитывает шесть членов; для posix_spawn*() хватило двух с единым
списком аргументов в духе и небольшими отличиями в трактовке
имени файла с образом нового процесса (напоминающими разницу между и ).
Более точно: для posix_spawn() и posix_spawnp() (см. листинг 3.1).
#include <spawn.h>
int posix_spawn (pid_t *restrict pid,
const char *restrict path,
const posix_spawn_file_actions_t
*file_actions,
const posix_spawnattr_t *restrict attrp,
char *const argv [restrict],
char *const envp [restrict]);
int posix_spawnp (pid_t *restrict pid,
const char *restrict file,
const posix_spawn_file_actions_t
*file_actions,
const posix_spawnattr_t *restrict attrp,
char *const argv [restrict],
char *const envp [restrict]);
Аргументами, отличающими posix_spawn() и posix_spawnp() от функций
семейства exec*() являются pid, file_actions, attrp. Опишем их
назначение.
По указателю pid (если он отличен от NULL ) возвращается идентификатор
успешно
Аргументы file_actions и attrp отвечают за контроль сущностей,
наследуемых при posix_spawn() и posix_spawnp()
контролируют шесть видов наследуемых сущностей:
Контроль file_actions, позволяет порожденному процессу получить доступ к
потокам данных, открытым или даже порожденным main() конкретных имен файлов или номеров дескрипторов.
Как правило, все открытые дескрипторы FD_CLOEXEC. Кроме того, если значение аргумента file_actions
отлично от NULL, до обработки флагов FD_CLOEXEC принимается во
внимание указуемый объект типа posix_spawn_file_actions_t, который
содержит действия по закрытию, открытию и/или дублированию posix_spawn_file_actions_t служат функции posix_spawn_file_actions_init(), posix_spawn_file_actions_addclose(), posix_spawn_file_actions_addopen() и posix_spawn_file_actions_adddup2() ; функция posix_spawn_file_actions_destroy() ликвидирует подобный объект (см.
листинг 3.2).
#include <spawn.h>
int posix_spawn_file_actions_init
(posix_spawn_file_actions_t *file_actions);
int posix_spawn_file_actions_destroy
(posix_spawn_file_actions_t *file_actions);
int posix_spawn_file_actions_addclose
(posix_spawn_file_actions_t *file_actions,
int fildes);
int posix_spawn_file_actions_addopen
( posix_spawn_file_actions_t *restrict file_actions,
int fildes, const char *restrict path, int oflag,
mode_t mode);
int posix_spawn_file_actions_adddup2
(posix_spawn_file_actions_t *file_actions, int fildes,
int newfildes);
Функция posix_spawn_file_actions_addclose() добавляет дескриптор fildes к числу закрываемых перед началом выполнения порожденного
процесса. Функция posix_spawn_file_actions_addopen() предписывает
открыть дескриптор fildes, как если бы был выполнен вызов open (path,
oflag, mode). Наконец, функция posix_spawn_file_actions_adddup2()
специфицирует дублирование дескриптора fildes в newfildes (close
(newfildes); fcntl (fildes, F_DUPFD, newfildes)). Таким образом,
функции posix_spawn() и posix_spawnp(), отправляясь от набора
открытых дескрипторов file_actions, и получают набор дескрипторов,
открытых в порождаемом процессе, то есть родительский процесс берет
на себя согласование по
Отметим, что с помощью функции posix_spawn_file_actions_addopen()
удобно перенаправлять ввод/вывод
За контроль других сущностей, наследуемых при attrp. Для
формирования и опроса подобных объектов служат функции, показанные на
листингах 3.3, 3.4 и 3.5.
#include <spawn.h>
int posix_spawnattr_init (
posix_spawnattr_t *attr);
int posix_spawnattr_destroy (
posix_spawnattr_t *attr);
int posix_spawnattr_getflags (
const posix_spawnattr_t
*restrict attr,
short *restrict flags);
int posix_spawnattr_setflags (
posix_spawnattr_t *attr,
short flags);
int posix_spawnattr_getpgroup (
const posix_spawnattr_t
*restrict attr,
pid_t *restrict pgroup);
int posix_spawnattr_setpgroup (
posix_spawnattr_t *attr,
pid_t pgroup);
#include <spawn.h>
#include <sched.h>
int posix_spawnattr_getschedparam (
const posix_spawnattr_t *restrict attr,
struct sched_param *restrict schedparam);
int posix_spawnattr_setschedparam (
posix_spawnattr_t *restrict attr,
const struct sched_param
*restrict schedparam);
int posix_spawnattr_getschedpolicy (
const posix_spawnattr_t *restrict attr,
int *restrict schedpolicy);
int posix_spawnattr_setschedpolicy (
posix_spawnattr_t *attr, int schedpolicy);
#include <spawn.h>
#include <signal.h>
int posix_spawnattr_getsigdefault (
const posix_spawnattr_t *restrict attr,
sigset_t *restrict sigdefault);
int posix_spawnattr_setsigdefault (
posix_spawnattr_t *restrict attr,
const sigset_t *restrict sigdefault);
int posix_spawnattr_getsigmask (
const posix_spawnattr_t *restrict attr,
sigset_t *restrict sigmask);
int posix_spawnattr_setsigmask (
posix_spawnattr_t *restrict attr,
const sigset_t *restrict sigmask);
Флаговые атрибуты определяют, какие из контролируемых наследуемых сущностей в порождаемом процессе должны быть изменены. Допустимы следующие флаги.
POSIX_SPAWN_RESETIDS
Установить POSIX_SPAWN_RESETIDS,
POSIX_SPAWN_SETPGROUP
Установить attrp. При нулевом
значении этого атрибута
POSIX_SPAWN_SETSIGDEF
Установить
POSIX_SPAWN_SETSIGMASK
Установить начальную
POSIX_SPAWN_SETSCHEDPARAM
Установить
POSIX_SPAWN_SETSCHEDULER
Установить POSIX_SPAWN_SETSCHEDPARAM ).
Если значение аргумента attrp равно NULL, используются
подразумеваемые значения атрибутов.
Все характеристики нового процесса, на которые не воздействуют
аргументы attrp и file_actions, устанавливаются так, как если бы
применялось двухшаговое порождение . Будут ли при atfork(), зависит от
реализации.
При библиотечной реализации функций posix_spawn() и posix_spawnp()
некоторые ошибки могут быть выявлены только после порождения
процесса. В таком случае родительский процесс может узнать о них,
анализируя с помощью макросов WIFEXITED, WEXITSTATUS (см. курс [1])
значение stat_val, возвращаемое функциями wait() и/или waitpid().
Предлагается, чтобы статус "аварийного завершения до начала реального
выполнения" равнялся 127. Это не очень естественно и удобно, но иного
выхода не видно.
В целом средства
Чтобы лучше понять семантику posix_spawn(), представленной в четвертой, информационной части
стандарта POSIX-2001 (см. листинги 3.6 и 3.7).
typedef struct {
short posix_attr_flags;
pid_t posix_attr_pgroup;
sigset_t posix_attr_sigmask;
sigset_t posix_attr_sigdefault;
int posix_attr_schedpolicy;
struct sched_param posix_attr_schedparam;
} posix_spawnattr_t;
typedef char *posix_spawn_file_actions_t;
int posix_spawn (pid_t *pid, const char *path,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp, char *const argv [],
char *const envp []);
int posix_spawn (pid_t *pid,
const char *path,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const argv [],
char *const envp []) {
/* Создадим новый процесс */
if ((*pid = fork()) == (pid_t) 0) {
/* Порожденный процесс */
/* Позаботимся о группе процессов */
if (attrp->posix_attr_flags POSIX_SPAWN_SETPGROUP) {
/* Изменим унаследованную группу */
if (setpgid (0, attrp->posix_attr_pgroup) != 0) {
/* Неудача */
exit (127);
}
}
/* Позаботимся о действующих идентификаторах */
/* пользователя и группы */
if (attrp->posix_attr_flags POSIX_SPAWN_RESETIDS) {
/* В данном случае неудачи быть не может */
setuid (getuid ());
setgid (getgid ());
}
/* Позаботимся о подразумеваемом способе */
/* обработки сигналов */
if (attrp->posix_attr_flags POSIX_SPAWN_SETSIGDEF) {
struct sigaction deflt;
sigset_t all_signals;
int s;
deflt.sa_handler = SIG_DFL;
deflt.sa_flags = 0;
sigfillset (all_signals);
/* Цикл по всем сигналам */
for (s = 0; sigismember (all_signals, s); s++) {
if (sigismember (attrp->posix_attr_sigdefault,
s)) {
if (sigaction (s, deflt, NULL) == -1) {
exit (127);
}
}
}
}
/* Проконтролируем остальные атрибуты */
/* . . . */
/* Подменим образ процесса */
execve (path, argv, envp);
exit (127);
} else {
/* Родительский (вызывающий) процесс */
if (*pid == (pid_t) (-1))
return errno;
return 0;
}
}
Если для posix_spawn() сохранить и
переустановить ее, а затем восстановить прежнее значение (см. листинг
3.8). Правда, при этом необходимо, чтобы все потоки управления,
выполняющиеся в рамках
/* Запуск процесса с произвольным идентификатором */ /* пользователя */ uid_t old_uid; uid_t new_uid = ...; old_uid = getuid (); setuid (new_uid); posix_spawn (...); setuid (old_uid);
На листинге 3.9 показан пример posix_spawn_file_actions_t. В данном случае стандартный
вывод (дескриптор 1) направляется в файл outfile, а стандартный ввод
(дескриптор 0) отождествляется с открытым ранее дескриптором socket_pair [1]. Попутно обеспечивается закрытие в новом процессе
дескрипторов socket_pair [0] и socket_pair [1].
posix_spawn_file_actions_t file_actions;
posix_spawn_file_actions_init (
file_actions);
posix_spawn_file_actions_addopen (
file_actions, 1, "outfile", ...);
posix_spawn_file_actions_adddup2 (
file_actions, socket_pair [1], 0);
posix_spawn_file_actions_addclose (
file_actions, socket_pair [0]);
posix_spawn_file_actions_addclose (
file_actions, socket_pair [1]);
posix_spawn (..., file_actions, ...);
posix_spawn_file_actions_destroy (
file_actions);
Любопытно сопоставить реальные posix_spawn() практически пустые процессы, показана на
листинге 3.10. На листинге 3.11 приведены данные о времени ее работы,
полученные с помощью команды time -p. Полученные результаты
практически не отличаются от измеренного ранее времени двухшагового
порождения. Это означает, что в используемой нами версии ОС Linux
функции posix_spawn() и posix_spawnp() реализованы как библиотечные,
а их применение выигрыша в эффективности в данном случае не дает (но
по соображениям "мобильной потенциальной эффективности" их все равно
есть смысл использовать).
#include <spawn.h>
#include <stdio.h>
#include <sys/wait.h>
#include <errno.h>
#define N 10000
int main (void) {
char *s_argv [] = {"dummy", NULL};
char *s_env [] = {NULL};
int i;
for (i = 0; i < N; i++) {
if ((errno = posix_spawn (
NULL, "./dummy", NULL, NULL, s_argv,
s_env)) != 0) {
perror ("POSIX_SPAWN");
return (errno);
}
(void) wait (NULL);
}
return 0;
}
real 34.37 user 12.01 sys 22.07
Чтобы устранить перечисленные недостатки, механизм
Номера SIGRTMIN до SIGRTMAX. Всего таких RTSIG_MAX.
Обеспечивается ли поведение в
"Жизненный цикл"
sigqueue() и
т.д.
На фазе генерации <signal.h> структура типа sigevent, которая,
согласно стандарту, должна содержать по крайней мере следующие поля.
int sigev_notify; /* Способ уведомления */
int sigev_signo; /* Номер сигнала */
union sigval sigev_value;
/* Значение сигнала */
void (*) (union sigval)
sigev_notify_function;
/* Функция уведомления */
(pthread_attr_t *)sigev_notify_attributes;
/* Атрибуты уведомления */
Значение поля sigev_notify определяет
Константа SIGEV_NONE означает отсутствие
Константа SIGEV_SIGNAL предписывает сгенерировать sigev_signo. Если для этого SA_SIGINFO, он
(
Константа SIGEV_THREAD задает в качестве механизма уведомления вызов
функции.
Чтобы обеспечить sigev_value структуры-аргумента типа sigevent.
sigval должно определяться со
следующими полями:
int sigval_int;
/* Значение сигнала – целое число */
void *sigval_ptr;
/* Значение сигнала – указатель */
Если для многопотоковой программы в качестве SIGEV_THREAD в
поле sigev_notify ), то указатель на эту функцию извлекается из поля sigev_notify_function, а ее аргументом служит *sigev_notify_attributes.
При подобном
После того, как sigqueue() или иным
способом, позволяющим задать определяемое приложением значение,
наступает фаза ожидания.
kill() или в
результате наступления такого
При наличии нескольких неблокированных ждущих
На фазе обработки уведомления об асинхронном siginfo_t. Для
union sigval si_value;
/* Значение сигнала */
в который переносится значение из поля sigev_value структуры типа sigevent.
SIG_DFL ) состоят в аварийном завершении процесса.
Простейший способ сгенерировать sigqueue() (см. листинг 3.12).
#include <signal.h>
int sigqueue (pid_t pid, int signo,
const union sigval value);
Функция sigqueue() посылает signo и значением value
процессу, идентификатор которого задан аргументом pid. Права на
посылку kill() ;
аналогично kill(), при нулевом значении signo pid.
Функция sigqueue() завершается немедленно, без какого-либо ожидания.
Если для signo установлен флаг SA_SIGINFO и в наличии
достаточно ресурсов, pid. Если
флаг SA_SIGINFO не установлен,
Если процесс посылает sigqueue().
Дождаться доставки sigwaitinfo() и sigtimedwait() (см. листинг 3.13), являющихся
аналогами рассмотренной в курсе [1] функции sigwait().
#include <signal.h>
int sigwaitinfo (
const sigset_t *restrict set,
siginfo_t *restrict info);
int sigtimedwait (
const sigset_t *restrict set,
siginfo_t *restrict info,
const struct timespec *restrict timeout);
Данные функции возвращают в качестве нормального результата номер
полученного set. Кроме того, если значение
аргумента info отлично от NULL, заполняются поля si_signo (номер si_code (источник si_value (значение,
если оно ассоциировано с siginfo_t. Разумеется, полученный
Функция sigtimedwait() отличается тем, что ограничивает время
ожидания timeout является
пустым указателем, поведение функции не специфицировано. Если
реализация поддерживает CLOCK_MONOTONIC ), они и
будут использоваться для контроля времени ожидания.
Если несколько потоков управления ждут один
Для SA_ONSTACK, установленного в поле sa_flags структуры типа sigaction при обращении к функции sigaction(). Функция sigaltstack()
(см. листинг 3.14) позволяет установить и/или опросить характеристики
#include <signal.h>
int sigaltstack (const stack_t *restrict ss,
stack_t *restrict oss);
Согласно стандарту POSIX-2001, структурный тип stack_t должен
содержать по крайней мере следующие поля.
void *ss_sp; /* Адрес стека */ size_t ss_size; /* Размер стека */ int ss_flags; /* Флаги */
Если значение аргумента ss отлично от NULL, то заданные им
характеристики sigaltstack(). Поле ss_flags определяет состояние нового
стека. Флаг SS_DISABLE в этом поле по сути означает отказ от ss_sp и ss_size
игнорируются.
ss_sp до (но
не включая) ss_sp + ss_size. Стандарт не специфицирует, с какого
конца и в каком направлении растет стек
Если значением аргумента служит непустой указатель, то после
возврата из функции sigaltstack() в указуемую структуру типа stack_t
будут помещены прежние значения характеристик ss_flags будет отражено состояние стека, которое
могут характеризовать по крайней мере следующие флаги.
SS_ONSTACK
Этот флаг означает, что в данный момент процесс выполняется на
SS_DISABLE
Этот флаг означает, что в данный момент
Константа SIGSTKSZ задает подразумеваемый, а MINSIGSTKSZ – минимально
допустимый размер MINSIGSTKSZ.
Естественно, все заботы по контролю за переполнением и исчерпанием
На листинге 3.15 показана типичная схема определения
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
. . .
stack_t sighstk;
. . .
if ((sighstk.ss_sp = malloc(
SIGSTKSZ)) == NULL) {
perror ("malloc (SIGSTKSZ)");
/* Аварийное завершение */
}
sighstk.ss_size = SIGSTKSZ;
sighstk.ss_flags = 0;
if (sigaltstack (sighstk,
(stack_t *) NULL) != 0) {
perror ("SIGALTSTACK");
. . .
}
. . .
Обработка sigsetjmp() и siglongjmp() (см. листинг 3.16). Они
аналогичны функциям setjmp() и longjmp() с единственным
содержательным отличием: если значение аргумента savemask функции sigsetjmp() отлично от нуля, siglongjmp().
#include <setjmp.h>
int sigsetjmp (sigjmp_buf env,
int savemask);
void siglongjmp (sigjmp_buf env, int val);
В качестве примера использования
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Многопотоковый вариант обеда философов */
/* с использованием сигналов реального времени */
/* * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
/* Число обедающих философов */
#define QPH 5
/* Время (в секундах) на обед */
#define FO 15
/* Длительность еды */
#define ernd (rand () % 3 + 1)
/* Длительность разговора */
#define trnd (rand () % 5 + 1)
/* Номер сигнала, используемого для захвата и освобождения вилок */
#define SIG_FORK SIGRTMIN
/* Номер сигнала, используемого для информирования философа */
#define SIG_PHIL SIGINT
static pthread_t pt_id [QPH]; /* Массив идентификаторов */
/* потоков - философов */
static int fork_busy [QPH] = {0, }; /* Состояние вилок */
static int phil_req [QPH] = {0, }; /* Невыполненные */
/* заявки на вилки */
static sigjmp_buf phil_env [QPH]; /* Массив буферов для */
/* нелокальных переходов */
static pid_t pid_wt; /* Идентификатор процесса,*/
/* контролирующего вилки */
static pthread_key_t phil_key; /* Ключ индивидуальных */
/* данных потоков-философов */
/* * * * * * * * * * * * * * * * * * * */
/* Функция обработки сигнала SIG_PHIL */
/* * * * * * * * * * * * * * * * * * * */
static void phil_eat (int signo) {
int no; /* Номер философа, которому достался сигнал */
no = (int) pthread_getspecific (phil_key);
if ((no > 0) (no <= QPH)) {
siglongjmp (phil_env [no – 1], signo);
}
}
/* * * * * * * * * * * * * * * * * * * * * * */
/* Попытка выполнить заявку на захват вилок */
/* от философа номер no, если она есть */
/* * * * * * * * * * * * * * * * * * * * * * */
static void fork_lock (int no) {
if (phil_req [no – 1] != 0) {
/* Заявка есть. */
/* Вилки свободны? */
if ((fork_busy [no – 1] == 0) (fork_busy
[no % QPH] == 0)) {
/* Выполним заявку */
fork_busy [no – 1] = fork_busy [no % QPH] = 1;
phil_req [no – 1] = 0;
(void) pthread_kill (pt_id [no – 1], SIG_PHIL);
}
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока, обслуживающего заявки на */
/* захват и освобождение вилок. */
/* Заявка передается в виде значения, ассоциированного */
/* с сигналом signo. */
/* Значение no > 0 запрашивает захват вилок для философа */
/* с номером no, no < 0 – освобождение вилок философа -no */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *start_waiter (void *signo) {
siginfo_t sinfo; /* Структура для получения данных */
/* о сигнале */
int no; /* Номер философа, приславшего заявку */
sigset_t s_sgno; /* Маска ожидаемых сигналов */
pid_wt = getpid ();
/* Сформируем маску ожидаемых сигналов */
if ((sigemptyset (s_sgno) != 0) || (sigaddset (s_sgno,
(int) signo) != 0)) {
perror ("SIGEMPTYSET/SIGADDSET");
return (NULL);
}
while (1) {
if (sigwaitinfo (s_sgno, sinfo) != (int) signo) {
return (NULL);
} else {
/* Поступила заявка. */
/* Посмотрим, что от нас хотят */
if ((no = sinfo.si_value.sival_int) > 0) {
/* Заявка на захват вилок. */
/* Запомним ее ... */
phil_req [no – 1] = 1;
/* ... и попробуем выполнить */
fork_lock (no);
} else {
/* Освобождение вилок */
no = -no;
fork_busy [no – 1] = fork_busy [no % QPH] = 0;
/* Попробуем выполнить заявки от соседей */
fork_lock (no % QPH + 1);
fork_lock (no == 1 ? QPH : (no – 1));
}
} /* Другие сигналы нас не интересуют */
} /* while (1) */
}
/* * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока-философа. */
/* Аргумент – номер философа */
/* * * * * * * * * * * * * * * * * * */
void *start_phil (void *no) {
int fo; /* Время до конца обеда */
int t; /* Время очередного отрезка еды или беседы */
time_t tbe; /* Время, когда философу понадобились вилки */
union sigval sval; /* Значение посылаемого сигнала: */
/* (int) no – заказ вилок */
/* -(int) no – освобождение вилок */
/* Запомним значение аргумента в качестве */
/* индивидуальных данных потока */
(void) pthread_setspecific (phil_key, no);
/* Подготовка к обеду */
fo = FO;
if (sigsetjmp (phil_env [(int) no – 1], 1) != 0) {
/* Сюда придем после нелокального перехода */
/* из обработчика сигнала SIG_PHIL. */
/* Философ просил вилки и получил их */
printf ("Философ %d ест\n", (int) no);
t = ernd; sleep (t);
/* Нужно вычесть времена еды и ожидания вилок */
fo -= (int) (time ((time_t *) NULL) – tbe) + t;
/* Отдает вилки */
sval.sigval_int = -((int) no);
(void) sigqueue (pid_wt, SIG_FORK, sval);
}
while (fo > 0) {
printf ("Философ %d беседует\n", (int) no);
t = trnd; sleep (t); fo -= t;
/* Пытается взять вилки */
tbe = time ((time_t *) NULL);
sval.sival_int = (int) no;
(void) sigqueue (pid_wt, SIG_FORK, sval);
/* Пока вилки заняты, приходится беседовать... */
printf ("Философ %d беседует в ожидании вилок\n",
(int) no);
sleep (fo);
fo = 0;
} /* while */
printf ("Философ %d закончил обед\n", (int) no);
return (NULL);
}
/* * * * * * * * * * */
/* Организация обеда */
/* * * * * * * * * * */
int main (void) {
int no; /* Номер философа */
struct sigaction sact; /* Структура для обработки */
/* обычных сигналов */
pthread_t pt_wt; /* Идентификатор потока, */
/* управляющего вилками */
/* Блокируем сигнал SIG_FORK */
if ((sigemptyset (sact.sa_mask) == 0)
(sigaddset (sact.sa_mask, SIG_FORK) == 0)) {
(void) pthread_sigmask (SIG_BLOCK, sact.sa_mask,
(sigset_t *) NULL);
}
/* Установим для сигнала SIG_FORK флаг SA_SIGINFO */
sact.sa_flags = SA_SIGINFO;
sact.sa_sigaction = (void (*) (int, siginfo_t *,
void *)) SIG_DFL;
(void) sigaction (SIG_FORK, sact,
(struct sigaction *) NULL);
/* Установим реакцию на сигнал SIG_PHIL */
sact.sa_handler = phil_eat;
sigemptyset (sact.sa_mask);
sact.sa_flags = 0;
(void) sigaction (SIG_PHIL, sact,
(struct sigaction *) NULL);
/* Создадим поток, захватывающий и освобождающий вилки */
if ((errno = pthread_create (pt_wt, NULL,
start_waiter, (void *) SIG_FORK)) != 0) {
perror ("PTHREAD_CREATE-1");
return (errno);
}
/* Создадим ключ индивидуальных данных */
if ((errno = pthread_key_create (phil_key,
(void (*) (void *)) NULL)) != 0) {
perror ("PTHREAD_KEY_CREATE");
return (errno);
}
/* Все – к столу */
for (no = 1; no <= QPH; no++) {
if ((errno = pthread_create (pt_id [no – 1], NULL,
start_phil, (void *) no)) != 0) {
perror ("PTHREAD_CREATE");
return (no);
}
}
/* Ожидание завершения обеда */
for (no = 1; no <= QPH; no++) {
(void) pthread_join (pt_id [no – 1], NULL);
}
(void) pthread_key_delete (phil_key);
/* Завершим поток, контролирующий вилки */
(void) pthread_cancel (pt_wt);
(void) pthread_join (pt_wt, NULL);
return 0;
}
Здесь sigwaitinfo(),
в рамках специально выделенного потока управления (что, конечно,
нечестно). Разумеется, ожидаемые таким способом
Для информирования философов о том, что нужные вилки захвачены и
можно приступать к еде, применяются обычные
В качестве небольшой тонкости обратим внимание на значение второго
аргумента (оно отлично от нуля) в вызове функции sigsetjmp(). Таким
образом обеспечивается SIG_PHIL. Если
этого не сделать, SIG_PHIL останется блокированным и второй
раз философ вилок уже не получит (точнее, он не узнает о том, что
вилки для него захвачены).
Возможные результаты выполнения приведенной программы показаны на листинге 3.18.
Философ 1 беседует Философ 2 беседует Философ 3 беседует Философ 4 беседует Философ 5 беседует Философ 4 беседует в ожидании вилок Философ 4 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 4 беседует Философ 1 беседует в ожидании вилок Философ 5 беседует в ожидании вилок Философ 5 ест Философ 2 беседует Философ 3 ест Философ 5 беседует Философ 1 ест Философ 2 беседует в ожидании вилок Философ 4 беседует в ожидании вилок Философ 3 беседует Философ 4 ест Философ 5 беседует в ожидании вилок Философ 1 беседует Философ 2 ест Философ 2 беседует Философ 4 закончил обед Философ 1 беседует в ожидании вилок Философ 5 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 3 закончил обед Философ 1 закончил обед Философ 5 закончил обед Философ 2 закончил обед
Стандартом POSIX-2001 предусмотрены средства CLOCK_REALTIME и CLOCK_MONOTONIC,
а также основанных на них сервисов времени должна быть не хуже, чем
заданное конфигурационной константой _POSIX_CLOCKRES_MIN значение 20
мсек (1/50 секунды). Естественно, реализация может обеспечивать более
высокую
Если определена _POSIX_CPUTIME, значит,
реализация дополнительно поддерживает для процессов clockid_t и значением CLOCK_PROCESS_CPUTIME_ID.
Если определена константа _POSIX_THREAD_CPUTIME, то аналогичные CLOCK_THREAD_CPUTIME_ID поддерживаются и для
потоков управления.
Функция clock_getcpuclockid() позволяет выяснить pthread_getcpuclockid() – аналогичный
идентификатор для заданного потока управления текущего процесса. В
принципе, это позволяет мобильным образом строить системы мониторинга
выполнения независимо разработанных приложений, выявляя узкие места и
ситуации перерасхода процессорного времени, что очень важно по
крайней мере для выполнения требований мягкого
Для nanosleep() – clock_nanosleep()
(см. листинг 3.19).
#include <time.h>
int clock_nanosleep (clockid_t clock_id,
int flags, const struct timespec *rqtp,
struct timespec *rmtp);
Аргумент rqtp задает момент времени (по clock_id ), до наступления которого приостанавливается выполнение
текущего потока управления. flags установлен флаг TIMER_ABSTIME, этот момент трактуется как
Разумеется, "наносон" может быть прерван доставкой обрабатываемого rmtp отлично от NULL, а
момент возобновления выполнения задан как относительный, то в
указуемую структуру типа timespec помещается "недоспанное" время.
Отметим, что функция clock_nanosleep() полностью аналогична nanosleep(), если не устанавливать флаг TIMER_ABSTIME, а в качестве CLOCK_REALTIME.
Возможность nanosleep() в таком случае
необходимо узнать текущее время и вычесть его из расчетного времени
возобновления выполнения; clock_nanosleep() с флагом TIMER_ABSTIME
позволяет сразу задать время возобновления. Более сложное вычисление
аргумента функции nanosleep() плохо не столько само по себе, сколько
из-за возможного nanosleep() ; в результате поток позже заснет и,
соответственно, проснется позже запланированного момента времени.
(Нетрудно видеть, что это общая проблема относительных
Понятно также, почему "недоспанное" время возвращается, только если
момент возобновления выполнения задан как относительный. Чтобы
"доспать" после обработки rmtp.
Естественно, попытка использования в clock_nanosleep()
Следующая программа (см. листинг 3.20) иллюстрирует типичную схему
применения функции clock_nanosleep() для организации
/* * * * * * * * * * * * * * * * * * * */
/* Организация периодических процессов */
/* с помощью функции clock_nanosleep() */
/* * * * * * * * * * * * * * * * * * * */
#define _XOPEN_SOURCE 600
#include <time.h>
#include <stdio.h>
#include <unistd.h>
/* * * * * * * * * * * * * * * * * * * * */
/* Сложение двух структур типа timespec */
/* * * * * * * * * * * * * * * * * * * * */
static void tmspc_add (struct timespec *a1,
struct timespec *a2, struct timespec *res) {
res->tv_sec = a1->tv_sec + a2->tv_sec +
(a1->tv_nsec + a2->tv_nsec) / 1000000000;
res->tv_nsec = (a1->tv_nsec + a2->tv_nsec) % 1000000000;
}
/* * * * * * * * * * * * * * * * * * * */
/* Организация периодического процесса */
/* * * * * * * * * * * * * * * * * * * */
int main (void) {
struct timespec t_bp; /* Время начала очередного */
/ *периода выполнения */
struct timespec prd = {1, 250000000}; /* Период */
/* выполнения: 1.25 сек */
clockid_t clk_id = CLOCK_REALTIME; /* Идентификатор */
/* используемых часов */
struct timespec t_tmp;
int i;
/* Запомним время начала выполнения */
(void) clock_gettime (clk_id, t_bp);
printf ("Начало выполнения: %ld сек %ld нсек\n",
t_bp.tv_sec, t_bp.tv_nsec);
for (i = 0; i < 8; i++) {
/* Содержательные действия. */
/* Предполагается, что они укладываются в период */
sleep (1);
/* Доспим до конца периода */
tmspc_add (t_bp, prd, t_bp);
(void) clock_nanosleep (clk_id, TIMER_ABSTIME,
t_bp, NULL);
(void) clock_gettime (clk_id, t_tmp);
printf ("Конец периода: %ld сек %ld нсек\n",
t_tmp.tv_sec, t_tmp.tv_nsec);
}
return 0;
}
На листинге 3.21 показаны возможные результаты выполнения приведенной программы.
Начало выполнения: 1079080828 сек 194254000 нсек Конец периода: 1079080829 сек 460021000 нсек Конец периода: 1079080830 сек 710023000 нсек Конец периода: 1079080831 сек 960020000 нсек Конец периода: 1079080833 сек 210021000 нсек Конец периода: 1079080834 сек 460023000 нсек Конец периода: 1079080835 сек 710021000 нсек Конец периода: 1079080836 сек 960094000 нсек Конец периода: 1079080838 сек 210022000 нсек
В качестве несложного упражнения читателю предлагается объяснить,
почему в функции tmspc_add() наносекунды суммируются после секунд, а
также почему длительность первой итерации оказывается несколько
больше, чем у последующих.
Для организации timer_create() (см. листинг 3.22).
#include <signal.h>
#include <time.h>
int timer_create (clockid_t clockid,
struct sigevent *restrict evp,
timer_t *restrict timerid);
clockid ; timerid. Разумеется, сразу после
Аргумент , указывающий на структуру типа sigevent, определяет
характер уведомлений о NULL, то в качестве SIGEV_SIGNAL, при
срабатывании генерируется
Если реализация поддерживает
timer_delete() (см. листинг
3.23)
#include <time.h> int timer_delete (timer_t timerid);
Для выполнения содержательных действий с timer_gettime(), timer_settime() и timer_getoverrun() (см. листинг
3.24).
#include <time.h>
int timer_gettime (timer_t timerid,
struct itimerspec *value);
int timer_settime (timer_t timerid,
int flags,
const struct itimerspec *restrict value,
struct itimerspec *restrict ovalue);
int timer_getoverrun (timer_t timerid);
Согласно стандарту POSIX-2001, структура типа itimerspec содержит по крайней мере следующие поля.
struct timespec it_interval;
/* Интервал таймера */
struct timespec it_value;
/* Текущие показания таймера */
/* (ведется обратный отсчет) */
Значение поля it_value (если оно отлично от нуля) показывает время,
оставшееся до it_value
свидетельствует о том, что it_value, равным it_interval, если последнее отлично от нуля; в таком случае мы имеем
периодический
Функция timer_gettime() запоминает текущие характеристики value.
Функция timer_settime() взводит или снимает value, и сохраняет старые по указателю ovalue (если он отличен от NULL ).
Если в аргументе flags установлен флаг TIMER_ABSTIME,
Если при обращении к timer_settime() заданный
Округление заданного времени с учетом
В любой момент времени в очереди к процессу может находиться не более
одного timer_getoverrun(). Верхней
границей упомянутого числа является DELAYTIMER_MAX.
Отметим, что наличие функции timer_getoverrun() позволяет избавиться
от неопределенно большого расхода ресурсов на постановку в очередь
Применим таймер для контроля длительности
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Многопотоковый вариант обеда философов */
/* с использованием сигналов реального времени */
/* и таймера для контроля длительности обеда */
/* * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
/* Число обедающих философов */
#define QPH 5
/* Время (в секундах) на обед */
#define FO 15
/* Длительность еды */
#define ernd (rand () % 3 + 1)
/* Длительность разговора */
#define trnd (rand () % 5 + 1)
/* Номер сигнала, используемого для захвата */
/* и освобождения вилок */
#define SIG_FORK SIGRTMIN
/* Номер сигнала, используемого таймером */
/* для окончания обеда */
#define SIG_DEND (SIGRTMIN + 1)
/* Номер сигнала, используемого */
/* для информирования философа */
#define SIG_PHIL SIGINT
static pthread_t pt_id [QPH]; /* Массив идентификаторов */
/* потоков-философов */
static int fork_busy [QPH] = {0, }; /* Состояние вилок */
static int phil_req [QPH] = {0, }; /* Невыполненные */
/* заявки на вилки */
static sigjmp_buf phil_env [QPH]; /* Массив буферов для */
/* нелокальных переходов */
static pid_t pid_wt; /* Идентификатор процесса, */
/* контролирующего вилки */
static pthread_key_t phil_key; /* Ключ индивидуальных */
/* данных потоков-философов */
/* * * * * * * * * * * * * * * * * * */
/* Функция обработки сигнала SIG_PHIL */
/* * * * * * * * * * * * * * * * * * */
static void phil_eat (int signo) {
int no; /* Номер философа, которому достался сигнал */
no = (int) pthread_getspecific (phil_key);
if ((no > 0) (no <= QPH)) {
siglongjmp (phil_env [no – 1], signo);
}
}
/* * * * * * * * * * * * * * * * * * */
/* Деструктор индивидуальных данных */
/* потоков-философов */
/* * * * * * * * * * * * * * * * * * */
static void phil_destructor (void *no) {
printf ("Философ %d закончил обед\n", (int) no);
}
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Функция, вызываемая при срабатывании таймера, */
/* контролирующего длительность обеда */
/* * * * * * * * * * * * * * * * * * * * * * ** */
static void end_ph_dinner (union sigval dummy) {
int i;
for (i = 0; i < QPH; i++) {
(void) pthread_cancel (pt_id [i]);
}
}
/* * * * * * * * * * * * * * * * * * * * * */
/* Попытка выполнить заявку на захват вилок */
/* от философа номер no, если она есть */
/* * * * * * * * * * * * * * * * * * * * * */
static void fork_lock (int no) {
if (phil_req [no – 1] != 0) {
/* Заявка есть. */
/* Вилки свободны? */
if ((fork_busy [no – 1] == 0) (fork_busy
[no % QPH] == 0)) {
/* Выполним заявку */
fork_busy [no – 1] = fork_busy [no % QPH] = 1;
phil_req [no – 1] = 0;
(void) pthread_kill (pt_id [no – 1], SIG_PHIL);
}
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока, обслуживающего заявки на */
/* захват и освобождение вилок. */
/* Заявка передается в виде значения, ассоциированного */
/* с сигналом signo. */
/* Значение no > 0 запрашивает захват вилок для философа */
/* с номером no, no < 0 – освобождение вилок философа -no */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *start_waiter (void *signo) {
siginfo_t sinfo; /* Структура для получения данных */
/* о сигнале */
int no; /* Номер философа, приславшего */
/* заявку */
sigset_t s_sgno; /* Маска ожидаемых сигналов */
pid_wt = getpid ();
/* Сформируем маску ожидаемых сигналов */
if ((sigemptyset (s_sgno) != 0) ||
(sigaddset (s_sgno, (int) signo) != 0)) {
perror ("SIGEMPTYSET/SIGADDSET");
return (NULL);
}
while (1) {
if (sigwaitinfo (s_sgno, sinfo) != (int) signo) {
return (NULL);
} else {
/* Поступила заявка. */
/* Посмотрим, что от нас хотят */
if ((no = sinfo.si_value.sigval_int) > 0) {
/* Заявка на захват вилок. */
/* Запомним ее ... */
phil_req [no – 1] = 1;
/* ... и попробуем выполнить */
fork_lock (no);
} else {
/* Освобождение вилок */
no = -no;
fork_busy [no – 1] = fork_busy [no % QPH] = 0;
/* Попробуем выполнить заявки от соседей */
fork_lock (no % QPH + 1);
fork_lock (no == 1 ? QPH : (no – 1));
}
} /* Другие сигналы нас не интересуют */
} /* while (1) */
}
/* * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока-философа. */
/* Аргумент – номер философа */
/* * * * * * * * * * * * * * * * * * */
void *start_phil (void *no) {
union sigval sval; /* Значение посылаемого сигнала: */
/* (int) no – заказ вилок */
/* -(int) no – освобождение вилок */
/* Запомним значение аргумента в качестве */
/* индивидуальных данных потока */
(void) pthread_setspecific (phil_key, no);
if (sigsetjmp (phil_env [(int) no – 1], 1) != 0) {
/* Сюда придем после нелокального перехода */
/* из обработчика сигнала SIG_PHIL. */
/* Философ просил вилки и получил их */
printf ("Философ %d ест\n", (int) no);
if (sleep (ernd) != 0) {
return (NULL);
}
/* Отдает вилки */
sval.sival_int = -((int) no);
(void) sigqueue (pid_wt, SIG_FORK, sval);
}
{ /* Обед */
printf ("Философ %d беседует\n", (int) no);
if (sleep (trnd) != 0) {
return (NULL);
}
/* Пытается взять вилки */
sval.sigval_int = (int) no;
(void) sigqueue (pid_wt, SIG_FORK, sval);
/* Пока вилки заняты, приходится беседовать... */
printf ("Философ %d беседует в ожидании вилок\n",
(int) no);
sleep (FO);
} /* Конец обеда */
return (NULL);
}
/* * * * * * * * * * */
/* Организация обеда */
/* * * * * * * * * * */
int main (void) {
int no; /* Номер философа */
struct sigaction sact; /* Структура для обработки */
/* обычных сигналов */
struct sigevent dend; /* Структура для генерации */
/* сигнала таймером */
timer_t dend_tmrid; /* Идентификатор таймера, */
/* контролирующего длительность */
/* обеда */
struct itimerspec dend_tmrsp = {{0, 0}, {FO, 0}};
/* Структура для взведения таймера, */
/* контролирующего длительность обеда */
pthread_t pt_wt; /* Идентификатор потока, управляющего */
/* вилками */
/* Блокируем сигнал SIG_FORK */
if ((sigemptyset (sact.sa_mask) == 0)
(sigaddset (sact.sa_mask, SIG_FORK) == 0)) {
(void) pthread_sigmask (SIG_BLOCK, sact.sa_mask,
(sigset_t *) NULL);
}
/* Установим для сигнала SIG_FORK флаг SA_SIGINFO */
sact.sa_flags = SA_SIGINFO;
sact.sa_sigaction = (void (*) (int, siginfo_t *,
void *)) SIG_DFL;
(void) sigaction (SIG_FORK, sact,
(struct sigaction *) NULL);
/* Установим реакцию на сигнал SIG_PHIL */
sact.sa_handler = phil_eat;
sigemptyset (sact.sa_mask);
sact.sa_flags = 0;
(void) sigaction (SIG_PHIL, sact,
(struct sigaction *) NULL);
/* Создадим поток, захватывающий и освобождающий вилки */
if ((errno = pthread_create (pt_wt, NULL,
start_waiter, (void *) SIG_FORK)) != 0) {
perror ("PTHREAD_CREATE-1");
return (errno);
}
/* Создадим ключ индивидуальных данных */
if ((errno = pthread_key_create (phil_key,
phil_destructor)) != 0) {
perror ("PTHREAD_KEY_CREATE");
return (errno);
}
/* Создадим и взведем таймер, */
/* контролирующий длительность обеда */
dend.sigev_notify = SIGEV_THREAD;
dend.sigev_signo = SIG_DEND;
dend.sigev_notify_function = end_ph_dinner;
dend.sigev_notify_attributes = NULL;
if (timer_create (CLOCK_REALTIME, dend,
dend_tmrid) != 0) {
perror ("TIMER_CREATE");
return (-1);
}
if (timer_settime (dend_tmrid, 0, dend_tmrsp,
NULL) != 0) {
perror ("TIMER_SETTIME");
return (-1);
}
/* Все – к столу */
for (no = 1; no <= QPH; no++) {
if ((errno = pthread_create (pt_id [no – 1], NULL,
start_phil, (void *) no)) != 0) {
perror ("PTHREAD_CREATE");
return (no);
}
}
/* Ожидание завершения обеда */
for (no = 1; no <= QPH; no++) {
(void) pthread_join (pt_id [no – 1], NULL);
}
(void) pthread_key_delete (phil_key);
(void) timer_delete (dend_tmrid);
/* Поток, контролирующий вилки, */
/* можно не терминировать и не ждать */
return 0;
}
Обратим внимание на применение вызова функции end_ph_dinner() в
качестве
Отметим также использование phil_key. По сути они играют роль
обработчиков завершения потоков-философов; возможно, так их и
следовало оформить, но тогда могло возникнуть нежелательное
взаимодействие с
Читателю предлагается самостоятельно объяснить, почему важно, что философы нумеруются с единицы, а не с нуля.
Довольно тонким вопросом является sleep() ), его
"будят" и он приступает к обработке запроса. Если, однако, между
пробуждением и планируемым началом обработки происходит errno, равным EINTR. Чтобы сделать приложение мобильным по отношению
к подобным вариациям, в программе проверяется sleep() и sigwaitinfo() с соответствующей реакцией на ошибки.
Возможные результаты выполнения приведенной программы показаны на
листинге 3.26. Если сравнить их с результатами варианта без
Философ 1 беседует Философ 2 беседует Философ 3 беседует Философ 4 беседует Философ 5 беседует Философ 4 беседует в ожидании вилок Философ 4 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 4 беседует Философ 1 беседует в ожидании вилок Философ 5 беседует в ожидании вилок Философ 5 ест Философ 2 беседует Философ 3 ест Философ 5 беседует Философ 1 ест Философ 2 беседует в ожидании вилок Философ 4 беседует в ожидании вилок Философ 3 беседует Философ 4 ест Философ 5 беседует в ожидании вилок Философ 1 беседует Философ 2 ест Философ 2 беседует Философ 1 беседует в ожидании вилок Философ 4 беседует Философ 1 ест Философ 2 беседует в ожидании вилок Философ 3 беседует в ожидании вилок Философ 3 ест Философ 1 беседует Философ 5 ест Философ 4 беседует в ожидании вилок Философ 5 беседует Философ 1 закончил обед Философ 2 закончил обед Философ 3 беседует Философ 4 закончил обед Философ 5 закончил обед Философ 3 беседует в ожидании вилок Философ 3 закончил обед
В рамках решаемой стандартом POSIX общей задачи обеспечения
мобильности приложений на уровне исходных текстов можно выделить
подзадачу
В качестве средств для решения этой подзадачи в стандарте POSIX-2001 присутствует целый ряд идей, понятий, объектов и функций, которые можно сгруппировать в следующие предметные области:
) основано на применении
функций posix_spawn() и posix_spawnp(). Оно в любом случае менее
тяжеловесно (хотя и тяжеловеснее порождения потоков управления), но
особенно актуально для аппаратных конфигураций, в которых отсутствует
поддержка виртуальной памяти и, как следствие, реализация функции проблематична
Основные понятия, ассоциированные с
Чтобы время доступа к объекту было по возможности минимальным и не превышало заданной величины, этот объект делают резидентным в физической памяти. Применительно к процессам подобный подход реализуется посредством удержания в памяти страниц, принадлежащих прикладному процессу.
Достижению той же цели – минимизации времени (а также унификации)
доступа – служит механизм
С помощью функции mmap() строится
Отметим, что
Средства
При стандартизации средств
В качестве дополнительных целей объявлены:
Еще одна дополнительная цель – простота интерфейса. Семейство exec*()
насчитывает шесть членов; для posix_spawn*() хватило двух с единым
списком аргументов в духе и небольшими отличиями в трактовке
имени файла с образом нового процесса (напоминающими разницу между и ).
Более точно: для posix_spawn() и posix_spawnp() (см. листинг 3.1).
#include <spawn.h>
int posix_spawn (pid_t *restrict pid,
const char *restrict path,
const posix_spawn_file_actions_t
*file_actions,
const posix_spawnattr_t *restrict attrp,
char *const argv [restrict],
char *const envp [restrict]);
int posix_spawnp (pid_t *restrict pid,
const char *restrict file,
const posix_spawn_file_actions_t
*file_actions,
const posix_spawnattr_t *restrict attrp,
char *const argv [restrict],
char *const envp [restrict]);
Аргументами, отличающими posix_spawn() и posix_spawnp() от функций
семейства exec*() являются pid, file_actions, attrp. Опишем их
назначение.
По указателю pid (если он отличен от NULL ) возвращается идентификатор
успешно
Аргументы file_actions и attrp отвечают за контроль сущностей,
наследуемых при posix_spawn() и posix_spawnp()
контролируют шесть видов наследуемых сущностей:
Контроль file_actions, позволяет порожденному процессу получить доступ к
потокам данных, открытым или даже порожденным main() конкретных имен файлов или номеров дескрипторов.
Как правило, все открытые дескрипторы FD_CLOEXEC. Кроме того, если значение аргумента file_actions
отлично от NULL, до обработки флагов FD_CLOEXEC принимается во
внимание указуемый объект типа posix_spawn_file_actions_t, который
содержит действия по закрытию, открытию и/или дублированию posix_spawn_file_actions_t служат функции posix_spawn_file_actions_init(), posix_spawn_file_actions_addclose(), posix_spawn_file_actions_addopen() и posix_spawn_file_actions_adddup2() ; функция posix_spawn_file_actions_destroy() ликвидирует подобный объект (см.
листинг 3.2).
#include <spawn.h>
int posix_spawn_file_actions_init
(posix_spawn_file_actions_t *file_actions);
int posix_spawn_file_actions_destroy
(posix_spawn_file_actions_t *file_actions);
int posix_spawn_file_actions_addclose
(posix_spawn_file_actions_t *file_actions,
int fildes);
int posix_spawn_file_actions_addopen
( posix_spawn_file_actions_t *restrict file_actions,
int fildes, const char *restrict path, int oflag,
mode_t mode);
int posix_spawn_file_actions_adddup2
(posix_spawn_file_actions_t *file_actions, int fildes,
int newfildes);
Функция posix_spawn_file_actions_addclose() добавляет дескриптор fildes к числу закрываемых перед началом выполнения порожденного
процесса. Функция posix_spawn_file_actions_addopen() предписывает
открыть дескриптор fildes, как если бы был выполнен вызов open (path,
oflag, mode). Наконец, функция posix_spawn_file_actions_adddup2()
специфицирует дублирование дескриптора fildes в newfildes (close
(newfildes); fcntl (fildes, F_DUPFD, newfildes)). Таким образом,
функции posix_spawn() и posix_spawnp(), отправляясь от набора
открытых дескрипторов file_actions, и получают набор дескрипторов,
открытых в порождаемом процессе, то есть родительский процесс берет
на себя согласование по
Отметим, что с помощью функции posix_spawn_file_actions_addopen()
удобно перенаправлять ввод/вывод
За контроль других сущностей, наследуемых при attrp. Для
формирования и опроса подобных объектов служат функции, показанные на
листингах 3.3, 3.4 и 3.5.
#include <spawn.h>
int posix_spawnattr_init (
posix_spawnattr_t *attr);
int posix_spawnattr_destroy (
posix_spawnattr_t *attr);
int posix_spawnattr_getflags (
const posix_spawnattr_t
*restrict attr,
short *restrict flags);
int posix_spawnattr_setflags (
posix_spawnattr_t *attr,
short flags);
int posix_spawnattr_getpgroup (
const posix_spawnattr_t
*restrict attr,
pid_t *restrict pgroup);
int posix_spawnattr_setpgroup (
posix_spawnattr_t *attr,
pid_t pgroup);
#include <spawn.h>
#include <sched.h>
int posix_spawnattr_getschedparam (
const posix_spawnattr_t *restrict attr,
struct sched_param *restrict schedparam);
int posix_spawnattr_setschedparam (
posix_spawnattr_t *restrict attr,
const struct sched_param
*restrict schedparam);
int posix_spawnattr_getschedpolicy (
const posix_spawnattr_t *restrict attr,
int *restrict schedpolicy);
int posix_spawnattr_setschedpolicy (
posix_spawnattr_t *attr, int schedpolicy);
#include <spawn.h>
#include <signal.h>
int posix_spawnattr_getsigdefault (
const posix_spawnattr_t *restrict attr,
sigset_t *restrict sigdefault);
int posix_spawnattr_setsigdefault (
posix_spawnattr_t *restrict attr,
const sigset_t *restrict sigdefault);
int posix_spawnattr_getsigmask (
const posix_spawnattr_t *restrict attr,
sigset_t *restrict sigmask);
int posix_spawnattr_setsigmask (
posix_spawnattr_t *restrict attr,
const sigset_t *restrict sigmask);
Флаговые атрибуты определяют, какие из контролируемых наследуемых сущностей в порождаемом процессе должны быть изменены. Допустимы следующие флаги.
POSIX_SPAWN_RESETIDS
Установить POSIX_SPAWN_RESETIDS,
POSIX_SPAWN_SETPGROUP
Установить attrp. При нулевом
значении этого атрибута
POSIX_SPAWN_SETSIGDEF
Установить
POSIX_SPAWN_SETSIGMASK
Установить начальную
POSIX_SPAWN_SETSCHEDPARAM
Установить
POSIX_SPAWN_SETSCHEDULER
Установить POSIX_SPAWN_SETSCHEDPARAM ).
Если значение аргумента attrp равно NULL, используются
подразумеваемые значения атрибутов.
Все характеристики нового процесса, на которые не воздействуют
аргументы attrp и file_actions, устанавливаются так, как если бы
применялось двухшаговое порождение . Будут ли при atfork(), зависит от
реализации.
При библиотечной реализации функций posix_spawn() и posix_spawnp()
некоторые ошибки могут быть выявлены только после порождения
процесса. В таком случае родительский процесс может узнать о них,
анализируя с помощью макросов WIFEXITED, WEXITSTATUS (см. курс [1])
значение stat_val, возвращаемое функциями wait() и/или waitpid().
Предлагается, чтобы статус "аварийного завершения до начала реального
выполнения" равнялся 127. Это не очень естественно и удобно, но иного
выхода не видно.
В целом средства
Чтобы лучше понять семантику posix_spawn(), представленной в четвертой, информационной части
стандарта POSIX-2001 (см. листинги 3.6 и 3.7).
typedef struct {
short posix_attr_flags;
pid_t posix_attr_pgroup;
sigset_t posix_attr_sigmask;
sigset_t posix_attr_sigdefault;
int posix_attr_schedpolicy;
struct sched_param posix_attr_schedparam;
} posix_spawnattr_t;
typedef char *posix_spawn_file_actions_t;
int posix_spawn (pid_t *pid, const char *path,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp, char *const argv [],
char *const envp []);
int posix_spawn (pid_t *pid,
const char *path,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const argv [],
char *const envp []) {
/* Создадим новый процесс */
if ((*pid = fork()) == (pid_t) 0) {
/* Порожденный процесс */
/* Позаботимся о группе процессов */
if (attrp->posix_attr_flags POSIX_SPAWN_SETPGROUP) {
/* Изменим унаследованную группу */
if (setpgid (0, attrp->posix_attr_pgroup) != 0) {
/* Неудача */
exit (127);
}
}
/* Позаботимся о действующих идентификаторах */
/* пользователя и группы */
if (attrp->posix_attr_flags POSIX_SPAWN_RESETIDS) {
/* В данном случае неудачи быть не может */
setuid (getuid ());
setgid (getgid ());
}
/* Позаботимся о подразумеваемом способе */
/* обработки сигналов */
if (attrp->posix_attr_flags POSIX_SPAWN_SETSIGDEF) {
struct sigaction deflt;
sigset_t all_signals;
int s;
deflt.sa_handler = SIG_DFL;
deflt.sa_flags = 0;
sigfillset (all_signals);
/* Цикл по всем сигналам */
for (s = 0; sigismember (all_signals, s); s++) {
if (sigismember (attrp->posix_attr_sigdefault,
s)) {
if (sigaction (s, deflt, NULL) == -1) {
exit (127);
}
}
}
}
/* Проконтролируем остальные атрибуты */
/* . . . */
/* Подменим образ процесса */
execve (path, argv, envp);
exit (127);
} else {
/* Родительский (вызывающий) процесс */
if (*pid == (pid_t) (-1))
return errno;
return 0;
}
}
Если для posix_spawn() сохранить и
переустановить ее, а затем восстановить прежнее значение (см. листинг
3.8). Правда, при этом необходимо, чтобы все потоки управления,
выполняющиеся в рамках
/* Запуск процесса с произвольным идентификатором */ /* пользователя */ uid_t old_uid; uid_t new_uid = ...; old_uid = getuid (); setuid (new_uid); posix_spawn (...); setuid (old_uid);
На листинге 3.9 показан пример posix_spawn_file_actions_t. В данном случае стандартный
вывод (дескриптор 1) направляется в файл outfile, а стандартный ввод
(дескриптор 0) отождествляется с открытым ранее дескриптором socket_pair [1]. Попутно обеспечивается закрытие в новом процессе
дескрипторов socket_pair [0] и socket_pair [1].
posix_spawn_file_actions_t file_actions;
posix_spawn_file_actions_init (
file_actions);
posix_spawn_file_actions_addopen (
file_actions, 1, "outfile", ...);
posix_spawn_file_actions_adddup2 (
file_actions, socket_pair [1], 0);
posix_spawn_file_actions_addclose (
file_actions, socket_pair [0]);
posix_spawn_file_actions_addclose (
file_actions, socket_pair [1]);
posix_spawn (..., file_actions, ...);
posix_spawn_file_actions_destroy (
file_actions);
Любопытно сопоставить реальные posix_spawn() практически пустые процессы, показана на
листинге 3.10. На листинге 3.11 приведены данные о времени ее работы,
полученные с помощью команды time -p. Полученные результаты
практически не отличаются от измеренного ранее времени двухшагового
порождения. Это означает, что в используемой нами версии ОС Linux
функции posix_spawn() и posix_spawnp() реализованы как библиотечные,
а их применение выигрыша в эффективности в данном случае не дает (но
по соображениям "мобильной потенциальной эффективности" их все равно
есть смысл использовать).
#include <spawn.h>
#include <stdio.h>
#include <sys/wait.h>
#include <errno.h>
#define N 10000
int main (void) {
char *s_argv [] = {"dummy", NULL};
char *s_env [] = {NULL};
int i;
for (i = 0; i < N; i++) {
if ((errno = posix_spawn (
NULL, "./dummy", NULL, NULL, s_argv,
s_env)) != 0) {
perror ("POSIX_SPAWN");
return (errno);
}
(void) wait (NULL);
}
return 0;
}
real 34.37 user 12.01 sys 22.07
Чтобы устранить перечисленные недостатки, механизм
Номера SIGRTMIN до SIGRTMAX. Всего таких RTSIG_MAX.
Обеспечивается ли поведение в
"Жизненный цикл"
sigqueue() и
т.д.
На фазе генерации <signal.h> структура типа sigevent, которая,
согласно стандарту, должна содержать по крайней мере следующие поля.
int sigev_notify; /* Способ уведомления */
int sigev_signo; /* Номер сигнала */
union sigval sigev_value;
/* Значение сигнала */
void (*) (union sigval)
sigev_notify_function;
/* Функция уведомления */
(pthread_attr_t *)sigev_notify_attributes;
/* Атрибуты уведомления */
Значение поля sigev_notify определяет
Константа SIGEV_NONE означает отсутствие
Константа SIGEV_SIGNAL предписывает сгенерировать sigev_signo. Если для этого SA_SIGINFO, он
(
Константа SIGEV_THREAD задает в качестве механизма уведомления вызов
функции.
Чтобы обеспечить sigev_value структуры-аргумента типа sigevent.
sigval должно определяться со
следующими полями:
int sigval_int;
/* Значение сигнала – целое число */
void *sigval_ptr;
/* Значение сигнала – указатель */
Если для многопотоковой программы в качестве SIGEV_THREAD в
поле sigev_notify ), то указатель на эту функцию извлекается из поля sigev_notify_function, а ее аргументом служит *sigev_notify_attributes.
При подобном
После того, как sigqueue() или иным
способом, позволяющим задать определяемое приложением значение,
наступает фаза ожидания.
kill() или в
результате наступления такого
При наличии нескольких неблокированных ждущих
На фазе обработки уведомления об асинхронном siginfo_t. Для
union sigval si_value;
/* Значение сигнала */
в который переносится значение из поля sigev_value структуры типа sigevent.
SIG_DFL ) состоят в аварийном завершении процесса.
Простейший способ сгенерировать sigqueue() (см. листинг 3.12).
#include <signal.h>
int sigqueue (pid_t pid, int signo,
const union sigval value);
Функция sigqueue() посылает signo и значением value
процессу, идентификатор которого задан аргументом pid. Права на
посылку kill() ;
аналогично kill(), при нулевом значении signo pid.
Функция sigqueue() завершается немедленно, без какого-либо ожидания.
Если для signo установлен флаг SA_SIGINFO и в наличии
достаточно ресурсов, pid. Если
флаг SA_SIGINFO не установлен,
Если процесс посылает sigqueue().
Дождаться доставки sigwaitinfo() и sigtimedwait() (см. листинг 3.13), являющихся
аналогами рассмотренной в курсе [1] функции sigwait().
#include <signal.h>
int sigwaitinfo (
const sigset_t *restrict set,
siginfo_t *restrict info);
int sigtimedwait (
const sigset_t *restrict set,
siginfo_t *restrict info,
const struct timespec *restrict timeout);
Данные функции возвращают в качестве нормального результата номер
полученного set. Кроме того, если значение
аргумента info отлично от NULL, заполняются поля si_signo (номер si_code (источник si_value (значение,
если оно ассоциировано с siginfo_t. Разумеется, полученный
Функция sigtimedwait() отличается тем, что ограничивает время
ожидания timeout является
пустым указателем, поведение функции не специфицировано. Если
реализация поддерживает CLOCK_MONOTONIC ), они и
будут использоваться для контроля времени ожидания.
Если несколько потоков управления ждут один
Для SA_ONSTACK, установленного в поле sa_flags структуры типа sigaction при обращении к функции sigaction(). Функция sigaltstack()
(см. листинг 3.14) позволяет установить и/или опросить характеристики
#include <signal.h>
int sigaltstack (const stack_t *restrict ss,
stack_t *restrict oss);
Согласно стандарту POSIX-2001, структурный тип stack_t должен
содержать по крайней мере следующие поля.
void *ss_sp; /* Адрес стека */ size_t ss_size; /* Размер стека */ int ss_flags; /* Флаги */
Если значение аргумента ss отлично от NULL, то заданные им
характеристики sigaltstack(). Поле ss_flags определяет состояние нового
стека. Флаг SS_DISABLE в этом поле по сути означает отказ от ss_sp и ss_size
игнорируются.
ss_sp до (но
не включая) ss_sp + ss_size. Стандарт не специфицирует, с какого
конца и в каком направлении растет стек
Если значением аргумента служит непустой указатель, то после
возврата из функции sigaltstack() в указуемую структуру типа stack_t
будут помещены прежние значения характеристик ss_flags будет отражено состояние стека, которое
могут характеризовать по крайней мере следующие флаги.
SS_ONSTACK
Этот флаг означает, что в данный момент процесс выполняется на
SS_DISABLE
Этот флаг означает, что в данный момент
Константа SIGSTKSZ задает подразумеваемый, а MINSIGSTKSZ – минимально
допустимый размер MINSIGSTKSZ.
Естественно, все заботы по контролю за переполнением и исчерпанием
На листинге 3.15 показана типичная схема определения
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
. . .
stack_t sighstk;
. . .
if ((sighstk.ss_sp = malloc(
SIGSTKSZ)) == NULL) {
perror ("malloc (SIGSTKSZ)");
/* Аварийное завершение */
}
sighstk.ss_size = SIGSTKSZ;
sighstk.ss_flags = 0;
if (sigaltstack (sighstk,
(stack_t *) NULL) != 0) {
perror ("SIGALTSTACK");
. . .
}
. . .
Обработка sigsetjmp() и siglongjmp() (см. листинг 3.16). Они
аналогичны функциям setjmp() и longjmp() с единственным
содержательным отличием: если значение аргумента savemask функции sigsetjmp() отлично от нуля, siglongjmp().
#include <setjmp.h>
int sigsetjmp (sigjmp_buf env,
int savemask);
void siglongjmp (sigjmp_buf env, int val);
В качестве примера использования
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Многопотоковый вариант обеда философов */
/* с использованием сигналов реального времени */
/* * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
/* Число обедающих философов */
#define QPH 5
/* Время (в секундах) на обед */
#define FO 15
/* Длительность еды */
#define ernd (rand () % 3 + 1)
/* Длительность разговора */
#define trnd (rand () % 5 + 1)
/* Номер сигнала, используемого для захвата и освобождения вилок */
#define SIG_FORK SIGRTMIN
/* Номер сигнала, используемого для информирования философа */
#define SIG_PHIL SIGINT
static pthread_t pt_id [QPH]; /* Массив идентификаторов */
/* потоков - философов */
static int fork_busy [QPH] = {0, }; /* Состояние вилок */
static int phil_req [QPH] = {0, }; /* Невыполненные */
/* заявки на вилки */
static sigjmp_buf phil_env [QPH]; /* Массив буферов для */
/* нелокальных переходов */
static pid_t pid_wt; /* Идентификатор процесса,*/
/* контролирующего вилки */
static pthread_key_t phil_key; /* Ключ индивидуальных */
/* данных потоков-философов */
/* * * * * * * * * * * * * * * * * * * */
/* Функция обработки сигнала SIG_PHIL */
/* * * * * * * * * * * * * * * * * * * */
static void phil_eat (int signo) {
int no; /* Номер философа, которому достался сигнал */
no = (int) pthread_getspecific (phil_key);
if ((no > 0) (no <= QPH)) {
siglongjmp (phil_env [no – 1], signo);
}
}
/* * * * * * * * * * * * * * * * * * * * * * */
/* Попытка выполнить заявку на захват вилок */
/* от философа номер no, если она есть */
/* * * * * * * * * * * * * * * * * * * * * * */
static void fork_lock (int no) {
if (phil_req [no – 1] != 0) {
/* Заявка есть. */
/* Вилки свободны? */
if ((fork_busy [no – 1] == 0) (fork_busy
[no % QPH] == 0)) {
/* Выполним заявку */
fork_busy [no – 1] = fork_busy [no % QPH] = 1;
phil_req [no – 1] = 0;
(void) pthread_kill (pt_id [no – 1], SIG_PHIL);
}
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока, обслуживающего заявки на */
/* захват и освобождение вилок. */
/* Заявка передается в виде значения, ассоциированного */
/* с сигналом signo. */
/* Значение no > 0 запрашивает захват вилок для философа */
/* с номером no, no < 0 – освобождение вилок философа -no */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *start_waiter (void *signo) {
siginfo_t sinfo; /* Структура для получения данных */
/* о сигнале */
int no; /* Номер философа, приславшего заявку */
sigset_t s_sgno; /* Маска ожидаемых сигналов */
pid_wt = getpid ();
/* Сформируем маску ожидаемых сигналов */
if ((sigemptyset (s_sgno) != 0) || (sigaddset (s_sgno,
(int) signo) != 0)) {
perror ("SIGEMPTYSET/SIGADDSET");
return (NULL);
}
while (1) {
if (sigwaitinfo (s_sgno, sinfo) != (int) signo) {
return (NULL);
} else {
/* Поступила заявка. */
/* Посмотрим, что от нас хотят */
if ((no = sinfo.si_value.sival_int) > 0) {
/* Заявка на захват вилок. */
/* Запомним ее ... */
phil_req [no – 1] = 1;
/* ... и попробуем выполнить */
fork_lock (no);
} else {
/* Освобождение вилок */
no = -no;
fork_busy [no – 1] = fork_busy [no % QPH] = 0;
/* Попробуем выполнить заявки от соседей */
fork_lock (no % QPH + 1);
fork_lock (no == 1 ? QPH : (no – 1));
}
} /* Другие сигналы нас не интересуют */
} /* while (1) */
}
/* * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока-философа. */
/* Аргумент – номер философа */
/* * * * * * * * * * * * * * * * * * */
void *start_phil (void *no) {
int fo; /* Время до конца обеда */
int t; /* Время очередного отрезка еды или беседы */
time_t tbe; /* Время, когда философу понадобились вилки */
union sigval sval; /* Значение посылаемого сигнала: */
/* (int) no – заказ вилок */
/* -(int) no – освобождение вилок */
/* Запомним значение аргумента в качестве */
/* индивидуальных данных потока */
(void) pthread_setspecific (phil_key, no);
/* Подготовка к обеду */
fo = FO;
if (sigsetjmp (phil_env [(int) no – 1], 1) != 0) {
/* Сюда придем после нелокального перехода */
/* из обработчика сигнала SIG_PHIL. */
/* Философ просил вилки и получил их */
printf ("Философ %d ест\n", (int) no);
t = ernd; sleep (t);
/* Нужно вычесть времена еды и ожидания вилок */
fo -= (int) (time ((time_t *) NULL) – tbe) + t;
/* Отдает вилки */
sval.sigval_int = -((int) no);
(void) sigqueue (pid_wt, SIG_FORK, sval);
}
while (fo > 0) {
printf ("Философ %d беседует\n", (int) no);
t = trnd; sleep (t); fo -= t;
/* Пытается взять вилки */
tbe = time ((time_t *) NULL);
sval.sival_int = (int) no;
(void) sigqueue (pid_wt, SIG_FORK, sval);
/* Пока вилки заняты, приходится беседовать... */
printf ("Философ %d беседует в ожидании вилок\n",
(int) no);
sleep (fo);
fo = 0;
} /* while */
printf ("Философ %d закончил обед\n", (int) no);
return (NULL);
}
/* * * * * * * * * * */
/* Организация обеда */
/* * * * * * * * * * */
int main (void) {
int no; /* Номер философа */
struct sigaction sact; /* Структура для обработки */
/* обычных сигналов */
pthread_t pt_wt; /* Идентификатор потока, */
/* управляющего вилками */
/* Блокируем сигнал SIG_FORK */
if ((sigemptyset (sact.sa_mask) == 0)
(sigaddset (sact.sa_mask, SIG_FORK) == 0)) {
(void) pthread_sigmask (SIG_BLOCK, sact.sa_mask,
(sigset_t *) NULL);
}
/* Установим для сигнала SIG_FORK флаг SA_SIGINFO */
sact.sa_flags = SA_SIGINFO;
sact.sa_sigaction = (void (*) (int, siginfo_t *,
void *)) SIG_DFL;
(void) sigaction (SIG_FORK, sact,
(struct sigaction *) NULL);
/* Установим реакцию на сигнал SIG_PHIL */
sact.sa_handler = phil_eat;
sigemptyset (sact.sa_mask);
sact.sa_flags = 0;
(void) sigaction (SIG_PHIL, sact,
(struct sigaction *) NULL);
/* Создадим поток, захватывающий и освобождающий вилки */
if ((errno = pthread_create (pt_wt, NULL,
start_waiter, (void *) SIG_FORK)) != 0) {
perror ("PTHREAD_CREATE-1");
return (errno);
}
/* Создадим ключ индивидуальных данных */
if ((errno = pthread_key_create (phil_key,
(void (*) (void *)) NULL)) != 0) {
perror ("PTHREAD_KEY_CREATE");
return (errno);
}
/* Все – к столу */
for (no = 1; no <= QPH; no++) {
if ((errno = pthread_create (pt_id [no – 1], NULL,
start_phil, (void *) no)) != 0) {
perror ("PTHREAD_CREATE");
return (no);
}
}
/* Ожидание завершения обеда */
for (no = 1; no <= QPH; no++) {
(void) pthread_join (pt_id [no – 1], NULL);
}
(void) pthread_key_delete (phil_key);
/* Завершим поток, контролирующий вилки */
(void) pthread_cancel (pt_wt);
(void) pthread_join (pt_wt, NULL);
return 0;
}
Здесь sigwaitinfo(),
в рамках специально выделенного потока управления (что, конечно,
нечестно). Разумеется, ожидаемые таким способом
Для информирования философов о том, что нужные вилки захвачены и
можно приступать к еде, применяются обычные
В качестве небольшой тонкости обратим внимание на значение второго
аргумента (оно отлично от нуля) в вызове функции sigsetjmp(). Таким
образом обеспечивается SIG_PHIL. Если
этого не сделать, SIG_PHIL останется блокированным и второй
раз философ вилок уже не получит (точнее, он не узнает о том, что
вилки для него захвачены).
Возможные результаты выполнения приведенной программы показаны на листинге 3.18.
Философ 1 беседует Философ 2 беседует Философ 3 беседует Философ 4 беседует Философ 5 беседует Философ 4 беседует в ожидании вилок Философ 4 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 4 беседует Философ 1 беседует в ожидании вилок Философ 5 беседует в ожидании вилок Философ 5 ест Философ 2 беседует Философ 3 ест Философ 5 беседует Философ 1 ест Философ 2 беседует в ожидании вилок Философ 4 беседует в ожидании вилок Философ 3 беседует Философ 4 ест Философ 5 беседует в ожидании вилок Философ 1 беседует Философ 2 ест Философ 2 беседует Философ 4 закончил обед Философ 1 беседует в ожидании вилок Философ 5 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 3 закончил обед Философ 1 закончил обед Философ 5 закончил обед Философ 2 закончил обед
Стандартом POSIX-2001 предусмотрены средства CLOCK_REALTIME и CLOCK_MONOTONIC,
а также основанных на них сервисов времени должна быть не хуже, чем
заданное конфигурационной константой _POSIX_CLOCKRES_MIN значение 20
мсек (1/50 секунды). Естественно, реализация может обеспечивать более
высокую
Если определена _POSIX_CPUTIME, значит,
реализация дополнительно поддерживает для процессов clockid_t и значением CLOCK_PROCESS_CPUTIME_ID.
Если определена константа _POSIX_THREAD_CPUTIME, то аналогичные CLOCK_THREAD_CPUTIME_ID поддерживаются и для
потоков управления.
Функция clock_getcpuclockid() позволяет выяснить pthread_getcpuclockid() – аналогичный
идентификатор для заданного потока управления текущего процесса. В
принципе, это позволяет мобильным образом строить системы мониторинга
выполнения независимо разработанных приложений, выявляя узкие места и
ситуации перерасхода процессорного времени, что очень важно по
крайней мере для выполнения требований мягкого
Для nanosleep() – clock_nanosleep()
(см. листинг 3.19).
#include <time.h>
int clock_nanosleep (clockid_t clock_id,
int flags, const struct timespec *rqtp,
struct timespec *rmtp);
Аргумент rqtp задает момент времени (по clock_id ), до наступления которого приостанавливается выполнение
текущего потока управления. flags установлен флаг TIMER_ABSTIME, этот момент трактуется как
Разумеется, "наносон" может быть прерван доставкой обрабатываемого rmtp отлично от NULL, а
момент возобновления выполнения задан как относительный, то в
указуемую структуру типа timespec помещается "недоспанное" время.
Отметим, что функция clock_nanosleep() полностью аналогична nanosleep(), если не устанавливать флаг TIMER_ABSTIME, а в качестве CLOCK_REALTIME.
Возможность nanosleep() в таком случае
необходимо узнать текущее время и вычесть его из расчетного времени
возобновления выполнения; clock_nanosleep() с флагом TIMER_ABSTIME
позволяет сразу задать время возобновления. Более сложное вычисление
аргумента функции nanosleep() плохо не столько само по себе, сколько
из-за возможного nanosleep() ; в результате поток позже заснет и,
соответственно, проснется позже запланированного момента времени.
(Нетрудно видеть, что это общая проблема относительных
Понятно также, почему "недоспанное" время возвращается, только если
момент возобновления выполнения задан как относительный. Чтобы
"доспать" после обработки rmtp.
Естественно, попытка использования в clock_nanosleep()
Следующая программа (см. листинг 3.20) иллюстрирует типичную схему
применения функции clock_nanosleep() для организации
/* * * * * * * * * * * * * * * * * * * */
/* Организация периодических процессов */
/* с помощью функции clock_nanosleep() */
/* * * * * * * * * * * * * * * * * * * */
#define _XOPEN_SOURCE 600
#include <time.h>
#include <stdio.h>
#include <unistd.h>
/* * * * * * * * * * * * * * * * * * * * */
/* Сложение двух структур типа timespec */
/* * * * * * * * * * * * * * * * * * * * */
static void tmspc_add (struct timespec *a1,
struct timespec *a2, struct timespec *res) {
res->tv_sec = a1->tv_sec + a2->tv_sec +
(a1->tv_nsec + a2->tv_nsec) / 1000000000;
res->tv_nsec = (a1->tv_nsec + a2->tv_nsec) % 1000000000;
}
/* * * * * * * * * * * * * * * * * * * */
/* Организация периодического процесса */
/* * * * * * * * * * * * * * * * * * * */
int main (void) {
struct timespec t_bp; /* Время начала очередного */
/ *периода выполнения */
struct timespec prd = {1, 250000000}; /* Период */
/* выполнения: 1.25 сек */
clockid_t clk_id = CLOCK_REALTIME; /* Идентификатор */
/* используемых часов */
struct timespec t_tmp;
int i;
/* Запомним время начала выполнения */
(void) clock_gettime (clk_id, t_bp);
printf ("Начало выполнения: %ld сек %ld нсек\n",
t_bp.tv_sec, t_bp.tv_nsec);
for (i = 0; i < 8; i++) {
/* Содержательные действия. */
/* Предполагается, что они укладываются в период */
sleep (1);
/* Доспим до конца периода */
tmspc_add (t_bp, prd, t_bp);
(void) clock_nanosleep (clk_id, TIMER_ABSTIME,
t_bp, NULL);
(void) clock_gettime (clk_id, t_tmp);
printf ("Конец периода: %ld сек %ld нсек\n",
t_tmp.tv_sec, t_tmp.tv_nsec);
}
return 0;
}
На листинге 3.21 показаны возможные результаты выполнения приведенной программы.
Начало выполнения: 1079080828 сек 194254000 нсек Конец периода: 1079080829 сек 460021000 нсек Конец периода: 1079080830 сек 710023000 нсек Конец периода: 1079080831 сек 960020000 нсек Конец периода: 1079080833 сек 210021000 нсек Конец периода: 1079080834 сек 460023000 нсек Конец периода: 1079080835 сек 710021000 нсек Конец периода: 1079080836 сек 960094000 нсек Конец периода: 1079080838 сек 210022000 нсек
В качестве несложного упражнения читателю предлагается объяснить,
почему в функции tmspc_add() наносекунды суммируются после секунд, а
также почему длительность первой итерации оказывается несколько
больше, чем у последующих.
Для организации timer_create() (см. листинг 3.22).
#include <signal.h>
#include <time.h>
int timer_create (clockid_t clockid,
struct sigevent *restrict evp,
timer_t *restrict timerid);
clockid ; timerid. Разумеется, сразу после
Аргумент , указывающий на структуру типа sigevent, определяет
характер уведомлений о NULL, то в качестве SIGEV_SIGNAL, при
срабатывании генерируется
Если реализация поддерживает
timer_delete() (см. листинг
3.23)
#include <time.h> int timer_delete (timer_t timerid);
Для выполнения содержательных действий с timer_gettime(), timer_settime() и timer_getoverrun() (см. листинг
3.24).
#include <time.h>
int timer_gettime (timer_t timerid,
struct itimerspec *value);
int timer_settime (timer_t timerid,
int flags,
const struct itimerspec *restrict value,
struct itimerspec *restrict ovalue);
int timer_getoverrun (timer_t timerid);
Согласно стандарту POSIX-2001, структура типа itimerspec содержит по крайней мере следующие поля.
struct timespec it_interval;
/* Интервал таймера */
struct timespec it_value;
/* Текущие показания таймера */
/* (ведется обратный отсчет) */
Значение поля it_value (если оно отлично от нуля) показывает время,
оставшееся до it_value
свидетельствует о том, что it_value, равным it_interval, если последнее отлично от нуля; в таком случае мы имеем
периодический
Функция timer_gettime() запоминает текущие характеристики value.
Функция timer_settime() взводит или снимает value, и сохраняет старые по указателю ovalue (если он отличен от NULL ).
Если в аргументе flags установлен флаг TIMER_ABSTIME,
Если при обращении к timer_settime() заданный
Округление заданного времени с учетом
В любой момент времени в очереди к процессу может находиться не более
одного timer_getoverrun(). Верхней
границей упомянутого числа является DELAYTIMER_MAX.
Отметим, что наличие функции timer_getoverrun() позволяет избавиться
от неопределенно большого расхода ресурсов на постановку в очередь
Применим таймер для контроля длительности
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Многопотоковый вариант обеда философов */
/* с использованием сигналов реального времени */
/* и таймера для контроля длительности обеда */
/* * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
/* Число обедающих философов */
#define QPH 5
/* Время (в секундах) на обед */
#define FO 15
/* Длительность еды */
#define ernd (rand () % 3 + 1)
/* Длительность разговора */
#define trnd (rand () % 5 + 1)
/* Номер сигнала, используемого для захвата */
/* и освобождения вилок */
#define SIG_FORK SIGRTMIN
/* Номер сигнала, используемого таймером */
/* для окончания обеда */
#define SIG_DEND (SIGRTMIN + 1)
/* Номер сигнала, используемого */
/* для информирования философа */
#define SIG_PHIL SIGINT
static pthread_t pt_id [QPH]; /* Массив идентификаторов */
/* потоков-философов */
static int fork_busy [QPH] = {0, }; /* Состояние вилок */
static int phil_req [QPH] = {0, }; /* Невыполненные */
/* заявки на вилки */
static sigjmp_buf phil_env [QPH]; /* Массив буферов для */
/* нелокальных переходов */
static pid_t pid_wt; /* Идентификатор процесса, */
/* контролирующего вилки */
static pthread_key_t phil_key; /* Ключ индивидуальных */
/* данных потоков-философов */
/* * * * * * * * * * * * * * * * * * */
/* Функция обработки сигнала SIG_PHIL */
/* * * * * * * * * * * * * * * * * * */
static void phil_eat (int signo) {
int no; /* Номер философа, которому достался сигнал */
no = (int) pthread_getspecific (phil_key);
if ((no > 0) (no <= QPH)) {
siglongjmp (phil_env [no – 1], signo);
}
}
/* * * * * * * * * * * * * * * * * * */
/* Деструктор индивидуальных данных */
/* потоков-философов */
/* * * * * * * * * * * * * * * * * * */
static void phil_destructor (void *no) {
printf ("Философ %d закончил обед\n", (int) no);
}
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Функция, вызываемая при срабатывании таймера, */
/* контролирующего длительность обеда */
/* * * * * * * * * * * * * * * * * * * * * * ** */
static void end_ph_dinner (union sigval dummy) {
int i;
for (i = 0; i < QPH; i++) {
(void) pthread_cancel (pt_id [i]);
}
}
/* * * * * * * * * * * * * * * * * * * * * */
/* Попытка выполнить заявку на захват вилок */
/* от философа номер no, если она есть */
/* * * * * * * * * * * * * * * * * * * * * */
static void fork_lock (int no) {
if (phil_req [no – 1] != 0) {
/* Заявка есть. */
/* Вилки свободны? */
if ((fork_busy [no – 1] == 0) (fork_busy
[no % QPH] == 0)) {
/* Выполним заявку */
fork_busy [no – 1] = fork_busy [no % QPH] = 1;
phil_req [no – 1] = 0;
(void) pthread_kill (pt_id [no – 1], SIG_PHIL);
}
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока, обслуживающего заявки на */
/* захват и освобождение вилок. */
/* Заявка передается в виде значения, ассоциированного */
/* с сигналом signo. */
/* Значение no > 0 запрашивает захват вилок для философа */
/* с номером no, no < 0 – освобождение вилок философа -no */
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *start_waiter (void *signo) {
siginfo_t sinfo; /* Структура для получения данных */
/* о сигнале */
int no; /* Номер философа, приславшего */
/* заявку */
sigset_t s_sgno; /* Маска ожидаемых сигналов */
pid_wt = getpid ();
/* Сформируем маску ожидаемых сигналов */
if ((sigemptyset (s_sgno) != 0) ||
(sigaddset (s_sgno, (int) signo) != 0)) {
perror ("SIGEMPTYSET/SIGADDSET");
return (NULL);
}
while (1) {
if (sigwaitinfo (s_sgno, sinfo) != (int) signo) {
return (NULL);
} else {
/* Поступила заявка. */
/* Посмотрим, что от нас хотят */
if ((no = sinfo.si_value.sigval_int) > 0) {
/* Заявка на захват вилок. */
/* Запомним ее ... */
phil_req [no – 1] = 1;
/* ... и попробуем выполнить */
fork_lock (no);
} else {
/* Освобождение вилок */
no = -no;
fork_busy [no – 1] = fork_busy [no % QPH] = 0;
/* Попробуем выполнить заявки от соседей */
fork_lock (no % QPH + 1);
fork_lock (no == 1 ? QPH : (no – 1));
}
} /* Другие сигналы нас не интересуют */
} /* while (1) */
}
/* * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока-философа. */
/* Аргумент – номер философа */
/* * * * * * * * * * * * * * * * * * */
void *start_phil (void *no) {
union sigval sval; /* Значение посылаемого сигнала: */
/* (int) no – заказ вилок */
/* -(int) no – освобождение вилок */
/* Запомним значение аргумента в качестве */
/* индивидуальных данных потока */
(void) pthread_setspecific (phil_key, no);
if (sigsetjmp (phil_env [(int) no – 1], 1) != 0) {
/* Сюда придем после нелокального перехода */
/* из обработчика сигнала SIG_PHIL. */
/* Философ просил вилки и получил их */
printf ("Философ %d ест\n", (int) no);
if (sleep (ernd) != 0) {
return (NULL);
}
/* Отдает вилки */
sval.sival_int = -((int) no);
(void) sigqueue (pid_wt, SIG_FORK, sval);
}
{ /* Обед */
printf ("Философ %d беседует\n", (int) no);
if (sleep (trnd) != 0) {
return (NULL);
}
/* Пытается взять вилки */
sval.sigval_int = (int) no;
(void) sigqueue (pid_wt, SIG_FORK, sval);
/* Пока вилки заняты, приходится беседовать... */
printf ("Философ %d беседует в ожидании вилок\n",
(int) no);
sleep (FO);
} /* Конец обеда */
return (NULL);
}
/* * * * * * * * * * */
/* Организация обеда */
/* * * * * * * * * * */
int main (void) {
int no; /* Номер философа */
struct sigaction sact; /* Структура для обработки */
/* обычных сигналов */
struct sigevent dend; /* Структура для генерации */
/* сигнала таймером */
timer_t dend_tmrid; /* Идентификатор таймера, */
/* контролирующего длительность */
/* обеда */
struct itimerspec dend_tmrsp = {{0, 0}, {FO, 0}};
/* Структура для взведения таймера, */
/* контролирующего длительность обеда */
pthread_t pt_wt; /* Идентификатор потока, управляющего */
/* вилками */
/* Блокируем сигнал SIG_FORK */
if ((sigemptyset (sact.sa_mask) == 0)
(sigaddset (sact.sa_mask, SIG_FORK) == 0)) {
(void) pthread_sigmask (SIG_BLOCK, sact.sa_mask,
(sigset_t *) NULL);
}
/* Установим для сигнала SIG_FORK флаг SA_SIGINFO */
sact.sa_flags = SA_SIGINFO;
sact.sa_sigaction = (void (*) (int, siginfo_t *,
void *)) SIG_DFL;
(void) sigaction (SIG_FORK, sact,
(struct sigaction *) NULL);
/* Установим реакцию на сигнал SIG_PHIL */
sact.sa_handler = phil_eat;
sigemptyset (sact.sa_mask);
sact.sa_flags = 0;
(void) sigaction (SIG_PHIL, sact,
(struct sigaction *) NULL);
/* Создадим поток, захватывающий и освобождающий вилки */
if ((errno = pthread_create (pt_wt, NULL,
start_waiter, (void *) SIG_FORK)) != 0) {
perror ("PTHREAD_CREATE-1");
return (errno);
}
/* Создадим ключ индивидуальных данных */
if ((errno = pthread_key_create (phil_key,
phil_destructor)) != 0) {
perror ("PTHREAD_KEY_CREATE");
return (errno);
}
/* Создадим и взведем таймер, */
/* контролирующий длительность обеда */
dend.sigev_notify = SIGEV_THREAD;
dend.sigev_signo = SIG_DEND;
dend.sigev_notify_function = end_ph_dinner;
dend.sigev_notify_attributes = NULL;
if (timer_create (CLOCK_REALTIME, dend,
dend_tmrid) != 0) {
perror ("TIMER_CREATE");
return (-1);
}
if (timer_settime (dend_tmrid, 0, dend_tmrsp,
NULL) != 0) {
perror ("TIMER_SETTIME");
return (-1);
}
/* Все – к столу */
for (no = 1; no <= QPH; no++) {
if ((errno = pthread_create (pt_id [no – 1], NULL,
start_phil, (void *) no)) != 0) {
perror ("PTHREAD_CREATE");
return (no);
}
}
/* Ожидание завершения обеда */
for (no = 1; no <= QPH; no++) {
(void) pthread_join (pt_id [no – 1], NULL);
}
(void) pthread_key_delete (phil_key);
(void) timer_delete (dend_tmrid);
/* Поток, контролирующий вилки, */
/* можно не терминировать и не ждать */
return 0;
}
Обратим внимание на применение вызова функции end_ph_dinner() в
качестве
Отметим также использование phil_key. По сути они играют роль
обработчиков завершения потоков-философов; возможно, так их и
следовало оформить, но тогда могло возникнуть нежелательное
взаимодействие с
Читателю предлагается самостоятельно объяснить, почему важно, что философы нумеруются с единицы, а не с нуля.
Довольно тонким вопросом является sleep() ), его
"будят" и он приступает к обработке запроса. Если, однако, между
пробуждением и планируемым началом обработки происходит errno, равным EINTR. Чтобы сделать приложение мобильным по отношению
к подобным вариациям, в программе проверяется sleep() и sigwaitinfo() с соответствующей реакцией на ошибки.
Возможные результаты выполнения приведенной программы показаны на
листинге 3.26. Если сравнить их с результатами варианта без
Философ 1 беседует Философ 2 беседует Философ 3 беседует Философ 4 беседует Философ 5 беседует Философ 4 беседует в ожидании вилок Философ 4 ест Философ 2 беседует в ожидании вилок Философ 2 ест Философ 3 беседует в ожидании вилок Философ 4 беседует Философ 1 беседует в ожидании вилок Философ 5 беседует в ожидании вилок Философ 5 ест Философ 2 беседует Философ 3 ест Философ 5 беседует Философ 1 ест Философ 2 беседует в ожидании вилок Философ 4 беседует в ожидании вилок Философ 3 беседует Философ 4 ест Философ 5 беседует в ожидании вилок Философ 1 беседует Философ 2 ест Философ 2 беседует Философ 1 беседует в ожидании вилок Философ 4 беседует Философ 1 ест Философ 2 беседует в ожидании вилок Философ 3 беседует в ожидании вилок Философ 3 ест Философ 1 беседует Философ 5 ест Философ 4 беседует в ожидании вилок Философ 5 беседует Философ 1 закончил обед Философ 2 закончил обед Философ 3 беседует Философ 4 закончил обед Философ 5 закончил обед Философ 3 беседует в ожидании вилок Философ 3 закончил обед
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.