Часы и таймеры
Стандартом 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() – аналогичный идентификатор для заданного потока управления текущего процесса. В принципе, это позволяет мобильным образом строить системы мониторинга выполнения независимо разработанных приложений, выявляя узкие места и ситуации перерасхода процессорного времени, что очень важно по крайней мере для выполнения требований мягкого реального времени. Правда, возможность создания таймеров на основе полученных идентификаторов объявлена в стандарте POSIX-2001 как зависящая от реализации.
Для приложений реального времени важна возможность использования не только процессорных, но и монотонных часов. В основном из этих соображений в стандарте POSIX-2001 присутствует расширенный аналог рассмотренной в курсе [1] функции nanosleep() – clock_nanosleep() (см. листинг 3.19).
#include <time.h> int clock_nanosleep (clockid_t clock_id, int flags, const struct timespec *rqtp, struct timespec *rmtp);
Листинг 3.19. Описание функции clock_nanosleep(). (html, txt)
Аргумент 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() для организации периодических процессов.
Листинг 3.20. Пример применения функции clock_nanosleep() для организации периодического процесса. (html, txt)
На листинге 3.21 показаны возможные результаты выполнения приведенной программы.
Листинг 3.21. Возможные результаты выполнения программы, использующей функцию clock_nanosleep() для организации периодического процесса. (html, txt)
В качестве несложного упражнения читателю предлагается объяснить, почему в функции 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);
Листинг 3.22. Описание функции timer_create(). (html, txt)
Таймер создается на основе часов с идентификатором clockid; идентификатор таймера (уникальный в пределах вызывающего процесса) записывается по указателю timerid. Разумеется, сразу после создания таймер оказывается в невзведенном состоянии.
Аргумент evp, указывающий на структуру типа sigevent, определяет характер уведомлений о срабатывании таймера. Если его значение равно NULL, то в качестве способа уведомления принимается SIGEV_SIGNAL, при срабатывании генерируется сигнал реального времени с подразумеваемым номером и значением, равным идентификатору таймера.
Если реализация поддерживает часы процессорного времени, то подобные часы для вызывающего процесса (потока управления) могут обслуживать создаваемый таймер.
Одношаговое порождение процессов
При стандартизации средств одношагового порождения процессов преследовались две основные цели:
возможность использования на аппаратных конфигурациях без устройства управления памятью и без какого-либо специфического оборудования;совместимость с существующими POSIX-стандартами.
В качестве дополнительных целей объявлены:
возможность эффективной реализации;способность заменить двухшаговое порождение по крайней мере в половине типичных случаев;системы, где имеется только одношаговое порождение процессов, должны быть практически полезны, по крайней мере для приложений реального времени;на системах с двухшаговым порождением процессов должна быть возможна библиотечная реализация одношагового порождения.
Еще одна дополнительная цель – простота интерфейса. Семейство exec*() насчитывает шесть членов; для posix_spawn*() хватило двух с единым списком аргументов в духе execve() и небольшими отличиями в трактовке имени файла с образом нового процесса (напоминающими разницу между execve() и execvp()).
Более точно: для одношагового порождения процессов служат функции 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]);
Листинг 3.1. Описание функций одношагового порождения процессов. (html, txt)
Аргументами, отличающими 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);
Листинг 3.2.
Описание функций формирования и ликвидации объектов типа posix_spawn_file_actions_t. (html, txt)
Функция 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);
Листинг 3.3. Описание функций формирования и опроса атрибутных объектов порождаемых процессов. (html, txt)
#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);
Листинг 3.4. Описание функций опроса и установки параметров и политики планирования в атрибутных объектах порождаемых процессов. (html, txt)
#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);
Листинг 3.5. Описание функций опроса и установки подразумеваемой обработки и маски сигналов в атрибутных объектах порождаемых процессов. (html, txt)
POSIX_SPAWN_SETSIGMASK
Установить начальную маску сигналов по атрибутному объекту.
POSIX_SPAWN_SETSCHEDPARAM
Установить параметры планирования по атрибутному объекту.
POSIX_SPAWN_SETSCHEDULER
Установить политику и параметры планирования по атрибутному объекту (независимо от состояния флага POSIX_SPAWN_SETSCHEDPARAM).
Если значение аргумента attrp равно NULL, используются подразумеваемые значения атрибутов.
Все характеристики нового процесса, на которые не воздействуют аргументы attrp и file_actions, устанавливаются так, как если бы применялось двухшаговое порождение fork()/exec(). Будут ли при одношаговом порождении выполняться обработчики разветвления процессов, зарегистрированные с помощью функции 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 []);
Листинг 3.6. Фрагмент возможного содержимого файла spawn.h.
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; } }
Листинг 3.7. Фрагмент возможной библиотечной реализации функции posix_spawn().
Если для порожденного процесса нужно особым образом установить характеристику, не принадлежащую к числу описанных выше контролируемых сущностей (например, значение переменной окружения), приходится перед обращением к posix_spawn() сохранить и переустановить ее, а затем восстановить прежнее значение (см. листинг 3.8). Правда, при этом необходимо, чтобы все потоки управления, выполняющиеся в рамках родительского процесса, были устойчивы к подобным манипуляциям.
/* Запуск процесса с произвольным идентификатором */ /* пользователя */
uid_t old_uid; uid_t new_uid = ...;
old_uid = getuid (); setuid (new_uid);
posix_spawn (...);
setuid (old_uid);
Листинг 3.8. Пример установки характеристики порожденного процесса, не принадлежащей к числу контролируемых стандартными средствами.
На листинге 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);
Листинг 3.9. Пример перенаправления стандартных ввода и вывода порождаемого процесса.
Любопытно сопоставить реальные накладные расходы на одношаговое и двухшаговое порождение процессов. Программа, порождающая с помощью функции 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; }
Листинг 3.10. Пример программы, порождающей в цикле практически пустые процессы с помощью функции posix_spawn().
real 34.37 user 12.01 sys 22.07
Листинг 3.11. Возможные результаты измерения времени работы программы, порождающей в цикле практически пустые процессы с помощью функции posix_spawn().
Основные идеи, понятия и объекты
В рамках решаемой стандартом POSIX общей задачи обеспечения мобильности приложений на уровне исходных текстов можно выделить подзадачу мобильного программирования систем, к которым предъявляются требования работы в реальном масштабе времени. (Разумеется, стандарт не может и не должен преодолевать зависимость числовых параметров реализации от свойств аппаратной платформы.)
В качестве средств для решения этой подзадачи в стандарте POSIX-2001 присутствует целый ряд идей, понятий, объектов и функций, которые можно сгруппировать в следующие предметные области:
одношаговое порождение процессов;сигналы реального времени;часы и таймеры;средства межпроцессного взаимодействия;объекты в памяти;удержание процессов в памяти;приоритетное планирование;асинхронный ввод/вывод;рекомендательные интерфейсы.
Одношаговое порождение процессов (в противоположность традиционной для ОС Unix двухшаговой модели fork()/exec()) основано на применении функций posix_spawn() и posix_spawnp(). Оно в любом случае менее тяжеловесно (хотя и тяжеловеснее порождения потоков управления), но особенно актуально для аппаратных конфигураций, в которых отсутствует поддержка виртуальной памяти и, как следствие, реализация функции fork() проблематична.
Под сигналами реального времени понимается не нарушающее совместимости расширение общего механизма сигналов, повышающее детерминированность за счет постановки сигналов, асинхронно доставленных приложению, в очередь.
Основные понятия, ассоциированные с часами и таймерами, были рассмотрены в курсе [1]. В настоящем курсе мы сосредоточимся на необязательных элементах стандарта POSIX-2001, специфичных для реального времени.
В качестве средств межпроцессного взаимодействия в реальном времени в стандарт POSIX-2001 включены очереди сообщений, семафоры и разделяемые сегменты памяти (см. также курс [1], где детально анализируется другой класс средств межпроцессного взаимодействия с аналогичными названиями).
Чтобы время доступа к объекту было по возможности минимальным и не превышало заданной величины, этот объект делают резидентным в физической памяти.
Применительно к процессам подобный подход реализуется посредством удержания в памяти страниц, принадлежащих прикладному процессу.
Достижению той же цели – минимизации времени (а также унификации) доступа – служит механизм объектов в памяти. Стандартом POSIX-2001 предусмотрено три вида таких объектов:
файлы, отображенные в память;объекты в разделяемой памяти (они же – упомянутые выше как средства межпроцессного взаимодействия разделяемые сегменты памяти);объекты в типизированной памяти.
С помощью функции mmap() строится отображение объекта в памяти на группу страниц из адресного пространства вызывающего процесса, так что доступ к адресам из заданного диапазона выливается в обращение к ассоциированному объекту (например, к файлу).
Объекты в разделяемой памяти характеризуются тем, что их можно параллельно отобразить в адресное пространство нескольких процессов.
Объект в типизированной памяти представляет собой комбинацию типизированных пула и порта памяти. Типизированный пул – это область памяти с однородными операционными характеристиками. Пулы могут быть вложенными. Аппаратный доступ к содержимому пула осуществляется через порт. Объект в типизированной памяти идентифицируется именем из пространства имен типизированной памяти.
Отметим, что объект в памяти не обязательно должен быть резидентным в физической памяти.
Согласно стандарту POSIX-2001, приоритет – это неотрицательная целочисленная величина, ассоциированная с процессом или потоком управления. Допустимый диапазон приоритетов определяется применяемой политикой планирования. Большие величины представляют более высокие приоритеты.
Приоритетное планирование (или планирование, основанное на приоритетах) – это средство усиления детерминированности, позволяющее приложениям определять порядок, в соответствии с которым потоки управления, готовые к выполнению, получают доступ к процессорным ресурсам.
Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных и получать асинхронные уведомления о завершении выполнения этих команд.
Рекомендательные интерфейсы – это средство проинформировать операционную систему о поведении мобильного приложения, чтобы ОС могла принять меры для оптимизации его обслуживания. Например, если прогнозируется последовательный доступ к файлу, ОС может осуществлять опережающее чтение, чтобы совместить во времени ввод и обработку данных.
Применительно к процессам подобный подход реализуется посредством удержания в памяти страниц, принадлежащих прикладному процессу.
Достижению той же цели – минимизации времени (а также унификации) доступа – служит механизм объектов в памяти. Стандартом POSIX-2001 предусмотрено три вида таких объектов:
файлы, отображенные в память;объекты в разделяемой памяти (они же – упомянутые выше как средства межпроцессного взаимодействия разделяемые сегменты памяти);объекты в типизированной памяти.
С помощью функции mmap() строится отображение объекта в памяти на группу страниц из адресного пространства вызывающего процесса, так что доступ к адресам из заданного диапазона выливается в обращение к ассоциированному объекту (например, к файлу).
Объекты в разделяемой памяти характеризуются тем, что их можно параллельно отобразить в адресное пространство нескольких процессов.
Объект в типизированной памяти представляет собой комбинацию типизированных пула и порта памяти. Типизированный пул – это область памяти с однородными операционными характеристиками. Пулы могут быть вложенными. Аппаратный доступ к содержимому пула осуществляется через порт. Объект в типизированной памяти идентифицируется именем из пространства имен типизированной памяти.
Отметим, что объект в памяти не обязательно должен быть резидентным в физической памяти.
Согласно стандарту POSIX-2001, приоритет – это неотрицательная целочисленная величина, ассоциированная с процессом или потоком управления. Допустимый диапазон приоритетов определяется применяемой политикой планирования. Большие величины представляют более высокие приоритеты.
Приоритетное планирование (или планирование, основанное на приоритетах) – это средство усиления детерминированности, позволяющее приложениям определять порядок, в соответствии с которым потоки управления, готовые к выполнению, получают доступ к процессорным ресурсам.
Средства асинхронного ввода/вывода позволяют прикладным процессам ставить в очередь команды ввода/вывода данных и получать асинхронные уведомления о завершении выполнения этих команд.
Рекомендательные интерфейсы – это средство проинформировать операционную систему о поведении мобильного приложения, чтобы ОС могла принять меры для оптимизации его обслуживания. Например, если прогнозируется последовательный доступ к файлу, ОС может осуществлять опережающее чтение, чтобы совместить во времени ввод и обработку данных.
restrict pid, const char
#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]); |
Листинг 3.1. Описание функций одношагового порождения процессов. |
Закрыть окно |
#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); |
Листинг 3.2. Описание функций формирования и ликвидации объектов типа posix_spawn_file_actions_t. |
Закрыть окно |
#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); |
Листинг 3.3. Описание функций формирования и опроса атрибутных объектов порождаемых процессов. |
Закрыть окно |
#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); |
Листинг 3.4. Описание функций опроса и установки параметров и политики планирования в атрибутных объектах порождаемых процессов. |
Закрыть окно |
#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); |
Листинг 3.5. Описание функций опроса и установки подразумеваемой обработки и маски сигналов в атрибутных объектах порождаемых процессов. |
Закрыть окно |
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 []); |
Листинг 3.6. Фрагмент возможного содержимого файла spawn.h. |
Закрыть окно |
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; } } |
Листинг 3.7. Фрагмент возможной библиотечной реализации функции posix_spawn(). |
Закрыть окно |
/* Запуск процесса с произвольным идентификатором */ /* пользователя */ uid_t old_uid; uid_t new_uid = ...; old_uid = getuid (); setuid (new_uid); posix_spawn (...); setuid (old_uid); |
Листинг 3.8. Пример установки характеристики порожденного процесса, не принадлежащей к числу контролируемых стандартными средствами. |
Закрыть окно |
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); |
Листинг 3.9. Пример перенаправления стандартных ввода и вывода порождаемого процесса. |
Закрыть окно |
#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; } |
Листинг 3.10. Пример программы, порождающей в цикле практически пустые процессы с помощью функции posix_spawn(). |
Закрыть окно |
real 34.37 user 12.01 sys 22.07 |
Листинг 3.11. Возможные результаты измерения времени работы программы, порождающей в цикле практически пустые процессы с помощью функции posix_spawn(). |
Закрыть окно |
#include <signal.h> int sigqueue ( pid_t pid, int signo, const union sigval value); |
Листинг 3.12. Описание функции sigqueue(). |
Закрыть окно |
#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); |
Листинг 3.13. Описание функций sigwaitinfo() и sigtimedwait(). |
Закрыть окно |
#include <signal.h> int sigaltstack (const stack_t * restrict ss, stack_t *restrict oss); |
Листинг 3.14. Описание функции sigaltstack(). |
Закрыть окно |
#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"); . . . } . . . |
Листинг 3.15. Типичная схема определения альтернативного стека. |
Закрыть окно |
#include <setjmp.h> int sigsetjmp ( sigjmp_buf env, int savemask); void siglongjmp (sigjmp_buf env, int val); |
Листинг 3.16. Описание функций sigsetjmp() и siglongjmp(). |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Многопотоковый вариант обеда философов */ /* с использованием сигналов реального времени */ /* * * * * * * * * * * * * * * * * * * * * * * */ #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.sival_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; } |
Листинг 3.17. Пример реализации обеда философов с использованием сигналов реального времени. |
Закрыть окно |
Философ 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 закончил обед |
Листинг 3.18. Возможные результаты выполнения программы, реализующей обед философов с использованием сигналов реального времени. |
Закрыть окно |
#include <time.h> int clock_nanosleep ( clockid_t clock_id, int flags, const struct timespec *rqtp, struct timespec *rmtp); |
Листинг 3.19. Описание функции 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.20. Пример применения функции clock_nanosleep() для организации периодического процесса. |
Закрыть окно |
Начало выполнения: 1079080828 сек 194254000 нсек Конец периода: 1079080829 сек 460021000 нсек Конец периода: 1079080830 сек 710023000 нсек Конец периода: 1079080831 сек 960020000 нсек Конец периода: 1079080833 сек 210021000 нсек Конец периода: 1079080834 сек 460023000 нсек Конец периода: 1079080835 сек 710021000 нсек Конец периода: 1079080836 сек 960094000 нсек Конец периода: 1079080838 сек 210022000 нсек |
Листинг 3.21. Возможные результаты выполнения программы, использующей функцию clock_nanosleep() для организации периодического процесса. |
Закрыть окно |
#include <signal.h> #include <time.h> int timer_create ( clockid_t clockid, struct sigevent *restrict evp, timer_t *restrict timerid); |
Листинг 3.22. Описание функции timer_create(). |
Закрыть окно |
#include <time.h> int timer_delete (timer_t timerid); |
Листинг 3.23. Описание функции timer_delete(). |
Закрыть окно |
#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); |
Листинг 3.24. Описание функций timer_gettime(), timer_settime() и 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.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) { 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.sival_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; } |
Листинг 3.25. Пример реализации обеда философов с использованием сигналов реального времени и таймера. |
Закрыть окно |
Философ 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 закончил обед |
Листинг 3.26. Возможные результаты выполнения программы, реализующей обед философов с использованием сигналов реального времени и таймера. |
Закрыть окно |
Сигналы реального времени
Приложениям реального времени требуются средства надежного, детерминированного, асинхронного извещения (уведомления) о событиях. Теоретически для этого можно было бы разработать совершенно новый механизм, допускающий исключительно эффективную реализацию, однако авторы стандарта POSIX-2001 не пошли по такому пути по той простой причине, что уже имеется механизм сигналов, который обладает почти всеми необходимыми свойствами. "Почти", потому что он не решает следующих проблем.
Не обеспечивается надежная доставка уведомлений о событиях. Если имеется ждущий сигнал, то при последующем появлении таких же сигналов их доставка не гарантируется.Не поддерживается ранжированная по приоритетам доставка уведомлений о событиях . Если имеется несколько ждущих неблокированных сигналов, порядок их доставки не определен.Не поддерживается дифференциация между сигналами одного типа.
Чтобы устранить перечисленные недостатки, механизм сигналов был расширен, а в стандарт была введена необязательная часть, получившая название "сигналы реального времени" (Realtime Signals Extension, RTS).
Номера сигналов реального времени лежат в диапазоне от 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 sival_int; /* Значение сигнала – целое число */ void *sival_ptr; /* Значение сигнала – указатель */
Если для многопотоковой программы в качестве способа уведомления о наступлении события избран вызов функции (константа SIGEV_THREAD в поле sigev_notify), то указатель на эту функцию извлекается из поля sigev_notify_function, а ее аргументом служит значение сигнала. Эта функция выполняется как стартовая для вновь созданного обособленного потока управления с атрибутным объектом *sigev_notify_attributes.
При подобном способе уведомления сигналы как таковые не генерируются, а уведомляющую функцию не следует путать с функцией обработки сигнала, которая вызывается в ином контексте и с другими аргументами.
После того, как сигнал сгенерирован функцией sigqueue() или иным способом, позволяющим задать определяемое приложением значение, наступает фаза ожидания. Сигналы одного типа ставятся в очередь к процессу в порядке генерации.
Сигналы, сгенерированные с помощью вызова функции kill() или в результате наступления такого события, как аппаратное прерывание, срабатывание будильника или ввод управляющего символа с терминала, для которых реализация не обеспечивает постановку в очередь, никак на эту очередь не влияют.
При наличии нескольких неблокированных ждущих сигналов реального времени, их доставка процессу производится в порядке возрастания номеров. Тем самым поддерживается ранжированная по приоритетам доставка уведомлений, а роль приоритета играет номер сигнала.
На фазе обработки уведомления об асинхронном событии используется структура типа siginfo_t. Для сигналов реального времени она включает, помимо описанных в курсе [1], дополнительный элемент
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);
Листинг 3.12. Описание функции sigqueue(). (html, txt)
Функция 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);
Листинг 3.13. Описание функций sigwaitinfo() и sigtimedwait(). (html, txt)
Данные функции возвращают в качестве нормального результата номер полученного сигнала, входящего в набор set. Кроме того, если значение аргумента info отлично от NULL, заполняются поля si_signo (номер сигнала), si_code (источник сигнала) и, возможно, si_value (значение, если оно ассоциировано с сигналом) указуемого объекта структурного типа siginfo_t. Разумеется, полученный сигнал изымается из очереди ждущих, а соответствующие ресурсы освобождаются.
Функция sigtimedwait() отличается тем, что ограничивает время ожидания сигнала заданным интервалом. Если аргумент timeout является пустым указателем, поведение функции не специфицировано. Если реализация поддерживает монотонные часы (CLOCK_MONOTONIC), они и будут использоваться для контроля времени ожидания.
Если несколько потоков управления ждут один сигнал, он достанется кому-то одному из них.
Поле ss_flags определяет состояние нового стека. Флаг SS_DISABLE в этом поле по сути означает отказ от альтернативного стека; в таком случае значения ss_sp и ss_size игнорируются.
Альтернативный стек располагается в диапазоне адресов от ss_sp до (но не включая) ss_sp + ss_size. Стандарт не специфицирует, с какого конца и в каком направлении растет стек.
Если значением аргумента oss служит непустой указатель, то после возврата из функции 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"); . . . } . . .
Листинг 3.15. Типичная схема определения альтернативного стека.
Обработка сигналов (не обязательно реального времени) нередко сочетается с нелокальными переходами. В таких случаях могут оказаться полезными функции 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);
Листинг 3.16. Описание функций sigsetjmp() и siglongjmp().
В качестве примера использования сигналов реального времени приведем еще один вариант реализации обеда философов (см. листинг 3.17).
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Многопотоковый вариант обеда философов */ /* с использованием сигналов реального времени */ /* * * * * * * * * * * * * * * * * * * * * * * */
#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.sival_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; }
Листинг 3.17. Пример реализации обеда философов с использованием сигналов реального времени.
Здесь сигналы реального времени служат средством межпотокового взаимодействия. В качестве значений сигналов передаются данные для захвата и освобождения вилок. В данном случае сигналы реального времени обрабатываются синхронно, посредством функции 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 закончил обед
Листинг 3.18. Возможные результаты выполнения программы, реализующей обед философов с использованием сигналов реального времени.
Передача и прием сообщений в реальном времени
Средства локальной передачи и приема сообщений присутствуют, вероятно, во всех или почти во всех операционных системах. Во многих версиях ОС Unix представлено по нескольку разновидностей подобных средств. Следуя за исторически сложившимися реализациями, стандарт POSIX-2001 предусматривает два вида очередей сообщений и обслуживающих их функций. Один из них был рассмотрен нами в курсе [1]. Здесь мы опишем вторую разновидность, предназначенную для использования в системах реального времени.
Описываемые очереди сообщений являются общесистемными. Они доступны по именам, которые могут быть маршрутными именами файлов.
Над очередями сообщений определены следующие группы функций:
открытие очереди;отправка сообщения в очередь;прием (синхронный или асинхронный) сообщения из очереди;изменение атрибутов очереди;регистрация на получение уведомления о появлении сообщения в очереди;закрытие очереди;удаление очереди.
Одну очередь могут открыть несколько посылающих и/или принимающих сообщения процессов. При открытии может производиться контроль прав доступа.
Для каждой очереди задается фиксированная верхняя граница размера сообщений, которые могут быть в эту очередь отправлены.
На порядок приема влияет имеющийся механизм приоритетов сообщений.
Процесс может получать асинхронные уведомления о том, что в очереди появилось сообщение.
Для открытия очереди служит функция mq_open() (см. листинг 4.1), которая, по аналогии с файлами, создает описание открытой очереди и ссылающийся на него дескриптор типа mqd_t, возвращаемый в качестве нормального результата.
#include <mqueue.h> mqd_t mq_open ( const char *name, int oflag, ...);
Листинг 4.1. Описание функции mq_open(). (html, txt)
Трактовка имени очереди (аргумент name) зависит от реализации. Оговаривается только, что оно должна подчиняться ограничениям, налагаемым на маршрутные имена, и что если его первым символом является '/', то процессы, вызывающие mq_open() с одинаковыми значениями name, ссылаются на одну и ту же очередь (если, конечно, ее не удаляли).
При реализации дескрипторов очередей сообщений могут применяться файловые дескрипторы. В таком случае приложение может открыть не менее OPEN_MAX файлов и очередей.
Аргумент oflag специфицирует запрашиваемые виды доступа к очереди: на прием (чтение) и отправку (запись). Контроль прав доступа для очередей сообщений производится так же, как и для файлов.
В целом набор флагов и их трактовка для очередей сообщений те же, что и для файлов: O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NONBLOCK.
Если установлен флаг O_CREAT, то при вызове функции mq_open() необходимо задать два дополнительных аргумента: режим доступа (тип mode_t) и указатель на атрибутный объект (тип struct mq_attr *) создаваемой очереди сообщений.
Согласно стандарту POSIX-2001, структура типа mq_attr, описанная в заголовочном файле <mqueue.h>, содержит по крайней мере следующие поля.
long mq_flags; /* Флаги очереди сообщений */ long mq_maxmsg; /* Максимальное число сообщений в очереди */ long mq_msgsize; /* Максимальный размер сообщения в очереди */ long mq_curmsgs; /* Текущее число сообщений в очереди */
Для опроса и/или установки атрибутов очереди служат функции mq_getattr() и mq_setattr() (см. листинг 4.2). Впрочем, про установку атрибутов сказано, пожалуй слишком сильно: посредством вызова 2 можно изменить лишь состояние флага 2 (и, возможно, некоторых других флагов, зависящих от реализации).
#include <mqueue.h>
int mq_getattr ( mqd_t mqdes, struct mq_attr *mqstat);
int mq_setattr (mqd_t mqdes, const struct mq_attr *restrict mqstat, struct mq_attr *restrict omqstat);
Листинг 4.2. Описание функций mq_getattr() и mq_setattr(). (html, txt)
После того, как процесс завершил работу с очередью сообщений, соответствующий дескриптор следует закрыть, воспользовавшись функцией mq_close() (см. листинг 4.3).
#include <mqueue.h> int mq_close (mqd_t mqdes);
Листинг 4.3. Описание функции mq_close(). (html, txt)
Если очередь сообщений стала совсем ненужной, ее можно удалить с помощью функции mq_unlink() (см.листинг 4.4).
#include <mqueue.h> int mq_unlink (const char *name);
Листинг 4.4. Описание функции mq_unlink(). (html, txt)
При реализации дескрипторов очередей сообщений могут применяться файловые дескрипторы. В таком случае приложение может открыть не менее OPEN_MAX файлов и очередей.
Аргумент oflag специфицирует запрашиваемые виды доступа к очереди: на прием (чтение) и отправку (запись). Контроль прав доступа для очередей сообщений производится так же, как и для файлов.
В целом набор флагов и их трактовка для очередей сообщений те же, что и для файлов: O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NONBLOCK.
Если установлен флаг O_CREAT, то при вызове функции mq_open() необходимо задать два дополнительных аргумента: режим доступа (тип mode_t) и указатель на атрибутный объект (тип struct mq_attr *) создаваемой очереди сообщений.
Согласно стандарту POSIX-2001, структура типа mq_attr, описанная в заголовочном файле <mqueue.h>, содержит по крайней мере следующие поля.
long mq_flags; /* Флаги очереди сообщений */ long mq_maxmsg; /* Максимальное число сообщений в очереди */ long mq_msgsize; /* Максимальный размер сообщения в очереди */ long mq_curmsgs; /* Текущее число сообщений в очереди */
Для опроса и/или установки атрибутов очереди служат функции mq_getattr() и mq_setattr() (см. листинг 4.2). Впрочем, про установку атрибутов сказано, пожалуй слишком сильно: посредством вызова 2 можно изменить лишь состояние флага 2 (и, возможно, некоторых других флагов, зависящих от реализации).
#include <mqueue.h>
int mq_getattr ( mqd_t mqdes, struct mq_attr *mqstat);
int mq_setattr (mqd_t mqdes, const struct mq_attr *restrict mqstat, struct mq_attr *restrict omqstat);
Листинг 4.2. Описание функций mq_getattr() и mq_setattr().
После того, как процесс завершил работу с очередью сообщений, соответствующий дескриптор следует закрыть, воспользовавшись функцией mq_close() (см. листинг 4.3).
#include <mqueue.h> int mq_close (mqd_t mqdes);
Листинг 4.3. Описание функции mq_close().
Если очередь сообщений стала совсем ненужной, ее можно удалить с помощью функции mq_unlink() (см. листинг 4.4).
#include <mqueue.h> int mq_unlink (const char *name);
Листинг 4.4. Описание функции mq_unlink().
Переходя к описанию содержательных действий с очередями сообщений, укажем, что отправка осуществляется функциями mq_send() и mq_timedsend() (см. листинги 4.5 и 4.6).
#include <mqueue.h> int mq_send (mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio);
Листинг 4.5. Описание функции mq_send(). #include <mqueue.h> #include <time.h> int mq_timedsend ( mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio, const struct timespec *abstime);
Листинг 4.6. Описание функции mq_timedsend().
Более точно: функции mq_send() и mq_timedsend() помещают сообщение из msg_len байт, на которое указывает аргумент msg_ptr, в очередь, заданную дескриптором mqdes (если она не полна), в соответствии с приоритетом msg_prio (большим значениям msg_prio соответствует более высокий приоритет сообщения; допустимый диапазон - от 0 до MQ_PRIO_MAX - 1).
Если очередь полна, а флаг O_NONBLOCK не установлен, вызов mq_send() блокируется до появления свободного места. Функция mq_timedsend() в таких случаях контролирует длительность ожидания (до заданного аргументом abstime абсолютного момента времени по часам CLOCK_REALTIME).
Для извлечения (разумеется, с удалением) сообщений из очереди служат функции mq_receive() и mq_timedreceive() (см. листинги 4.7 и 4.8). Извлекается самое старое из сообщений с самым высоким приоритетом и помещается в буфер, на который указывает аргумент msg_ptr. Если размер буфера (значение аргумента msg_len) меньше атрибута очереди mq_msgsize, вызов завершается неудачей. Если значение msg_prio_ptr отлично от NULL, в указуемый объект помещается приоритет принятого сообщения.
#include <mqueue.h> ssize_t mq_receive ( mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio_ptr);
Листинг 4.7. Описание функции mq_receive().
#include <mqueue.h> #include <time.h> ssize_t mq_timedreceive ( mqd_t mqdes, char *restrict msg_ptr, size_t msg_len, unsigned *restrict msg_prio_ptr, const struct timespec *restrict abstime);
Листинг 4.8. Описание функции mq_timedreceive().
Если очередь пуста, а флаг O_NONBLOCK не установлен, вызов mq_receive() блокируется до появления сообщения. Функция mq_timedreceive() в таких случаях контролирует длительность ожидания.
Нормальным результатом обеих функций является размер в байтах извлеченного из очереди сообщения. Как всегда, признаком ошибки служит -1.
Посредством функции mq_notify() (см. листинг 4.9) процесс может зарегистрироваться на получение уведомления о том, что в очередь, бывшую до этого пустой, поступило сообщение.
#include <mqueue.h> int mq_notify (mqd_t mqdes, const struct sigevent *notification);
Листинг 4.9. Описание функции mq_notify().
Для уведомлений используется механизм сигналов реального времени. В каждый момент времени только один процесс может быть зарегистрированным, а сама регистрация является одноразовой: после поступления уведомления она отменяется. Процесс может добровольно отменить регистрацию, если передаст NULL в качестве значения аргумента notification.
Если, наряду с зарегистрированным процессом, имеется поток управления, ожидающий сообщения в вызове mq_receive() или mq_timedreceive(), поступившее сообщение достанется потоку, а процесс не получит никакого уведомления, как если бы очередь осталась пустой. Это очень по-человечески: живое стояние в очереди всегда ценилось выше всяких списков и уведомлений.
На наш взгляд, возможность получать уведомления о том, что очередь сообщений стала непустой, является скорее заплатой, призванной скрыть неполноту функциональности poll() и select(), чем полноценным, практически полезным средством. В стандартизованном интерфейсе отсутствует концептуальная целостность - из соображений симметрии необходимы уведомления о том, что очередь стала неполной и в нее можно отправлять сообщения. Далее, состояние очереди может меняться параллельно с регистрацией и/или получением уведомления, и нет никаких средств, чтобы сделать соответствующую транзакцию атомарной. В результате остается неясным, когда процесс получит уведомление (и получит ли он его вообще), какое сообщение он примет после получения уведомления (и будет ли что принимать) и т.п., хотя, с другой стороны, устраивать конкуренцию за сообщения тоже не обязательно.
Поучительно сопоставить два вида очередей сообщений - только что описанные и те, что были представлены в курсе [1]. Сразу видно, что первые устроены проще и, следовательно, могут быть реализованы эффективнее. Во-первых, структура типа mq_attr гораздо компактнее, чем msqid_ds. Нет и речи о хранении времени последних операций и идентификаторов процессов, их выполнивших. Во-вторых, упрощены производимые проверки. Контролируется максимальный размер одного сообщения, а не суммарный размер сообщений в очереди. Права доступа проверяются при открытии, а не при каждой операции. Только наличие приоритетов вносит элемент сложности, сопоставимый с обработкой типов сообщений, но приоритеты так или иначе должны быть реализованы - либо на уровне ОС, либо в приложении, поэтому лучше этот аспект стандартизовать и поручить операционной системе.
Примером применения очередей сообщений послужит программа, вычисляющая взвешенную сумму целых чисел, генерируемых и отправляемых множеством потоков управления (см. листинг 4.10).
/* * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа принимает сообщения */ /* и суммирует содержащиеся в них целые числа. */ /* Массивы случайных целых чисел */ /* генерируют несколько потоков управления. */ /* Каждый поток использует свою очередь сообщений.*/ /* Используется режим без блокировки, */ /* с уведомлениями о появлении сообщений в очереди*/ /* * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdlib.h> #include <stdio.h> #include <limits.h> #include <fcntl.h> #include <mqueue.h> #include <signal.h> #include <pthread.h> #include <errno.h>
/* Число потоков управления, порождающих случайные числа */ #define PT_N MQ_OPEN_MAX
/* Число сообщений, генерируемых каждым потоком управления */ #define MSG_N 128
/* Длина имени очереди сообщений */ #define MQ_NAME_LENGTH PATH_MAX
/* Количество целых чисел в одном сообщении */ #define MSG_INT_SIZE 32
/* Максимальное число сообщений в очереди */ #define MQ_MSGS_MAX 16
/* Приоритет порождаемого сообщения */ #define prio_rnd (rand () % MQ_PRIO_MAX)
/* Номер сигнала, используемого для уведомлений */ #define SIG_MQ_NOTIFY SIGRTMIN
/* Массив идентификаторов очередей сообщений */ static mqd_t mq_des [PT_N];
/* Массив структур для задания уведомлений */ /* о поступлении сообщений в очереди */ static struct sigevent evnt_mq_notify [PT_N];
/* Мьютекс, используемый для синхронизации */ /* доступа к переменной sum */ static pthread_mutex_t sm_mutex = PTHREAD_MUTEX_INITIALIZER;
/* Общий результат суммирования */ static int sum = 0;
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Функция, вызываемая при получении уведомления*/ /* о том, что очередь сообщений стала непустой. */ /* Номер очереди передается как аргумент */ /* * * * * * * * * * * * * * * * * * * * * * * */ static void msg_arrvd (union sigval pt_nm) { int msg_buf [MSG_INT_SIZE]; mqd_t mqdes; unsigned int msg_prio; int msg_sum = 0; ssize_t msg_size; int i;
mqdes = mq_des [pt_nm.sival_int];
/* Примем и обработаем имеющиеся сообщения, */ /* а затем снова зарегистрируемся на получение */ /* такого же уведомления */ while ((msg_size = mq_receive (mqdes, (char *) msg_buf, MSG_INT_SIZE * sizeof (int), &msg_prio)) > 0) { for (i = 0; i < (msg_size / (signed int) sizeof (int)); i++) { msg_sum += msg_buf [i]; } msg_sum *= msg_prio; }
if ((errno = pthread_mutex_lock (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_LOCK"); } sum += msg_sum; if ((errno = pthread_mutex_unlock (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_UNLOCK"); }
if (mq_notify (mqdes, &evnt_mq_notify [pt_nm.sival_int]) != 0) { perror ("MQ_NOTIFY"); } }
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, генерирующего числа и */ /* посылающего сообщения. */ /* Аргумент - номер очереди сообщений */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_sender (void *pt_nm) {
int msg_buf [MSG_INT_SIZE]; int i, j;
/* Сформируем и пошлем заданное число сообщений */ /* (проверяя, не переполнилась ли очередь)*/ for (j = 0; j < MSG_N; j++) { for (i = 0; i < MSG_INT_SIZE; i++) { msg_buf [i] = rand (); } if (mq_send (mq_des [(int) pt_nm], (char *) msg_buf, MSG_INT_SIZE * sizeof (int), prio_rnd) != 0) { perror ("MQ_SEND"); return (NULL); } }
return (NULL); }
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Создание очереди сообщений, */ /* регистрация на получение уведомлений, */ /* создание и ожидание завершения потоков управления */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { /* Массив идентификаторов порождаемых */ /* потоков. Эти потоки будут */ pthread_t pt_mqs [PT_N]; /* генерировать сообщения*/ /* Массив для генерации и хранения */ /* имен очередей сообщений */ char mq_name [PT_N] [MQ_NAME_LENGTH]; struct mq_attr mqattrs; /* Атрибуты создаваемых очередей */ int i;
for (i = 0; i < PT_N; i++) { /* Создадим очереди сообщений */ /* и зарегистрируемся на получение уведомлений */ sprintf (mq_name [i], "g%d", i);
mqattrs.mq_flags = O_NONBLOCK; mqattrs.mq_maxmsg = MQ_MSGS_MAX; mqattrs.mq_msgsize = MSG_INT_SIZE * sizeof (int); mqattrs.mq_curmsgs = 0; if ((mq_des [i] = mq_open (mq_name [i], O_RDWR | O_CREAT | O_NONBLOCK, 0777, &mqattrs)) == (mqd_t) (-1)) { perror ("MQ_OPEN"); return (-1); }
/* Сформируем структуру evnt_mq_notify */ evnt_mq_notify [i].sigev_notify = SIGEV_THREAD; evnt_mq_notify [i].sigev_signo = SIG_MQ_NOTIFY; evnt_mq_notify [i].sigev_value.sival_int = i; evnt_mq_notify [i].sigev_notify_function = msg_arrvd; evnt_mq_notify [i].sigev_notify_attributes = NULL; if (mq_notify (mq_des [i], &evnt_mq_notify [i]) != 0) { perror ("MQ_NOTIFY_MAIN"); return (-1); }
/* Создадим потоки управления */ if ((errno = pthread_create (&pt_mqs [i], NULL, start_sender, (void *) i)) != 0) { perror ("PTHREAD_CREATE"); return (i); } } /* for */
/* Ожидание завершения */ for (i = 0; i < PT_N; i++) { (void) pthread_join (pt_mqs [i], NULL); }
/* Закроем дескрипторы и удалим очереди */ for (i = 0; i < PT_N; i++) { (void) mq_close (mq_des [i]); (void) mq_unlink (mq_name [i]); }
printf ("Общая сумма: %d\n", sum);
if ((errno = pthread_mutex_destroy (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_DESTROY"); return (errno); }
return 0; }
Листинг 4.10. Пример программы, использующей очереди сообщений.
Обратим внимание на то, что если в программе для отправки и приема сообщений используются буфера одного размера, он должен равняться значению атрибута mq_msgsize, которое задается при создании очереди. Отметим также применение режима без блокировки, что важно для приложений реального времени.
mqd_t mq_open
#include <mqueue.h> mqd_t mq_open ( const char *name, int oflag, ...); |
Листинг 4.1. Описание функции mq_open(). |
Закрыть окно |
#include <mqueue.h> int mq_getattr ( mqd_t mqdes, struct mq_attr *mqstat); int mq_setattr ( mqd_t mqdes, const struct mq_attr *restrict mqstat, struct mq_attr *restrict omqstat); |
Листинг 4.2. Описание функций mq_getattr() и mq_setattr(). |
Закрыть окно |
#include <mqueue.h> int mq_close (mqd_t mqdes); |
Листинг 4.3. Описание функции mq_close(). |
Закрыть окно |
#include <mqueue.h> int mq_unlink (const char *name); |
Листинг 4.4. Описание функции mq_unlink(). |
Закрыть окно |
#include <mqueue.h> int mq_send ( mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio); |
Листинг 4.5. Описание функции mq_send(). |
Закрыть окно |
#include <mqueue.h> #include <time.h> int mq_timedsend ( mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio, const struct timespec *abstime); |
Листинг 4.6. Описание функции mq_timedsend(). |
Закрыть окно |
#include <mqueue.h> ssize_t mq_receive ( mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio_ptr); |
Листинг 4.7. Описание функции mq_receive(). |
Закрыть окно |
#include <mqueue.h> #include <time.h> ssize_t mq_timedreceive ( mqd_t mqdes, char * restrict msg_ptr, size_t msg_len, unsigned *restrict msg_prio_ptr, const struct timespec *restrict abstime); |
Листинг 4.8. Описание функции mq_timedreceive(). |
Закрыть окно |
#include <mqueue.h> int mq_notify ( mqd_t mqdes, const struct sigevent *notification); |
Листинг 4.9. Описание функции mq_notify(). |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа принимает сообщения */ /* и суммирует содержащиеся в них целые числа. */ /* Массивы случайных целых чисел */ /* генерируют несколько потоков управления. */ /* Каждый поток использует свою очередь сообщений.*/ /* Используется режим без блокировки, */ /* с уведомлениями о появлении сообщений в очереди*/ /* * * * * * * * * * * * * * * * * * * * * * * * */ #include <stdlib.h> #include <stdio.h> #include <limits.h> #include <fcntl.h> #include <mqueue.h> #include <signal.h> #include <pthread.h> #include <errno.h> /* Число потоков управления, порождающих случайные числа */ #define PT_N MQ_OPEN_MAX /* Число сообщений, генерируемых каждым потоком управления */ #define MSG_N 128 /* Длина имени очереди сообщений */ #define MQ_NAME_LENGTH PATH_MAX /* Количество целых чисел в одном сообщении */ #define MSG_INT_SIZE 32 /* Максимальное число сообщений в очереди */ #define MQ_MSGS_MAX 16 /* Приоритет порождаемого сообщения */ #define prio_rnd (rand () % MQ_PRIO_MAX) /* Номер сигнала, используемого для уведомлений */ #define SIG_MQ_NOTIFY SIGRTMIN /* Массив идентификаторов очередей сообщений */ static mqd_t mq_des [PT_N]; /* Массив структур для задания уведомлений */ /* о поступлении сообщений в очереди */ static struct sigevent evnt_mq_notify [PT_N]; /* Мьютекс, используемый для синхронизации */ /* доступа к переменной sum */ static pthread_mutex_t sm_mutex = PTHREAD_MUTEX_INITIALIZER; /* Общий результат суммирования */ static int sum = 0; /* * * * * * * * * * * * * * * * * * * * * * * */ /* Функция, вызываемая при получении уведомления*/ /* о том, что очередь сообщений стала непустой. */ /* Номер очереди передается как аргумент */ /* * * * * * * * * * * * * * * * * * * * * * * */ static void msg_arrvd (union sigval pt_nm) { int msg_buf [MSG_INT_SIZE]; mqd_t mqdes; unsigned int msg_prio; int msg_sum = 0; ssize_t msg_size; int i; mqdes = mq_des [pt_nm.sival_int]; /* Примем и обработаем имеющиеся сообщения, */ /* а затем снова зарегистрируемся на получение */ /* такого же уведомления */ while ((msg_size = mq_receive (mqdes, (char *) msg_buf, MSG_INT_SIZE * sizeof (int), &msg_prio)) > 0) { for (i = 0; i < (msg_size / (signed int) sizeof (int)); i++) { msg_sum += msg_buf [i]; } msg_sum *= msg_prio; } if ((errno = pthread_mutex_lock (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_LOCK"); } sum += msg_sum; if ((errno = pthread_mutex_unlock (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_UNLOCK"); } if (mq_notify (mqdes, &evnt_mq_notify [pt_nm.sival_int]) != 0) { perror ("MQ_NOTIFY"); } } /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, генерирующего числа и */ /* посылающего сообщения. */ /* Аргумент - номер очереди сообщений */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_sender (void *pt_nm) { int msg_buf [MSG_INT_SIZE]; int i, j; /* Сформируем и пошлем заданное число сообщений */ /* (проверяя, не переполнилась ли очередь)*/ for (j = 0; j < MSG_N; j++) { for (i = 0; i < MSG_INT_SIZE; i++) { msg_buf [i] = rand (); } if (mq_send (mq_des [(int) pt_nm], (char *) msg_buf, MSG_INT_SIZE * sizeof (int), prio_rnd) != 0) { perror ("MQ_SEND"); return (NULL); } } return (NULL); } /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Создание очереди сообщений, */ /* регистрация на получение уведомлений, */ /* создание и ожидание завершения потоков управления */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { /* Массив идентификаторов порождаемых */ /* потоков. Эти потоки будут */ pthread_t pt_mqs [PT_N]; /* генерировать сообщения*/ /* Массив для генерации и хранения */ /* имен очередей сообщений */ char mq_name [PT_N] [MQ_NAME_LENGTH]; struct mq_attr mqattrs; /* Атрибуты создаваемых очередей */ int i; for (i = 0; i < PT_N; i++) { /* Создадим очереди сообщений */ /* и зарегистрируемся на получение уведомлений */ sprintf (mq_name [i], "g%d", i); mqattrs.mq_flags = O_NONBLOCK; mqattrs.mq_maxmsg = MQ_MSGS_MAX; mqattrs.mq_msgsize = MSG_INT_SIZE * sizeof (int); mqattrs.mq_curmsgs = 0; if ((mq_des [i] = mq_open (mq_name [i], O_RDWR | O_CREAT | O_NONBLOCK, 0777, &mqattrs)) == (mqd_t) (-1)) { perror ("MQ_OPEN"); return (-1); } /* Сформируем структуру evnt_mq_notify */ evnt_mq_notify [i].sigev_notify = SIGEV_THREAD; evnt_mq_notify [i].sigev_signo = SIG_MQ_NOTIFY; evnt_mq_notify [i].sigev_value.sival_int = i; evnt_mq_notify [i].sigev_notify_function = msg_arrvd; evnt_mq_notify [i].sigev_notify_attributes = NULL; if (mq_notify (mq_des [i], &evnt_mq_notify [i]) != 0) { perror ("MQ_NOTIFY_MAIN"); return (-1); } /* Создадим потоки управления */ if ((errno = pthread_create (&pt_mqs [i], NULL, start_sender, (void *) i)) != 0) { perror ("PTHREAD_CREATE"); return (i); } } /* for */ /* Ожидание завершения */ for (i = 0; i < PT_N; i++) { (void) pthread_join (pt_mqs [i], NULL); } /* Закроем дескрипторы и удалим очереди */ for (i = 0; i < PT_N; i++) { (void) mq_close (mq_des [i]); (void) mq_unlink (mq_name [i]); } printf ("Общая сумма: %d\n", sum); if ((errno = pthread_mutex_destroy (&sm_mutex)) != 0) { perror ("PTHREAD_MUTEX_DESTROY"); return (errno); } return 0; } |
Листинг 4.10. Пример программы, использующей очереди сообщений. |
Закрыть окно |
#include <semaphore.h> sem_t *sem_open (const char *name, int oflag, ...); int sem_init (sem_t *sem, int pshared, unsigned value); |
Листинг 4.11. Описание функций sem_open() и sem_init(). |
Закрыть окно |
#include <semaphore.h> int sem_close (sem_t *sem); int sem_unlink (const char *name); int sem_destroy (sem_t *sem); |
Листинг 4.12. Описание функций закрытия и ликвидации семафоров. |
Закрыть окно |
#include <semaphore.h> int sem_wait (sem_t *sem); int sem_trywait (sem_t *sem); |
Листинг 4.13. Описание функций захвата семафоров. |
Закрыть окно |
#include <semaphore.h> #include <time.h> int sem_timedwait (sem_t * restrict sem, const struct timespec *restrict abstime); |
Листинг 4.14. Описание функции захвата семафора с контролем времени ожидания. |
Закрыть окно |
#include <semaphore.h> int sem_post (sem_t *sem); |
Листинг 4.15. Описание функции sem_post(). |
Закрыть окно |
#include <semaphore.h> int sem_getvalue (sem_t * restrict sem, int *restrict sval); |
Листинг 4.16. Описание функции sem_getvalue(). |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует взаимодействие */ /* поставщик/потребитель (писатель/читатель). */ /* Поставщик генерирует случайные целые числа и помещает их буфер */ /* на один элемент, потребитель извлекает их оттуда и суммирует. */ /* Для синхронизации используются неименованные семафоры*/ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <semaphore.h> #include <pthread.h> #include <errno.h> /* Буфер для хранения генерируемых данных */ static int my_buf; /* Семафор, разрешающий записывать в буфер новые данные */ static sem_t w_sem; /* Семафор, разрешающий читать данные из буфера */ static sem_t r_sem; /* Общий результат суммирования */ static int sum = 0; /* * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, генерирующего числа */ /* * * * * * * * * * * * * * * * * * * * * * * */ void *start_writer (void *dummy) { while (sem_wait (&w_sem) == 0) { my_buf = rand (); if (sem_post (&r_sem) != 0) { perror ("SEM_POST-R"); return (NULL); } } return (NULL); } /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, читающего и суммирующего числа */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ void *start_reader (void *dummy) { while (sem_wait (&r_sem) == 0) { sum += my_buf; if (sem_post (&w_sem) != 0) { perror ("SEM_POST-W"); return (NULL); } } return (NULL); } /* * * * * * * * * * * * * * * * * * * * * * * */ /* Инициализация семафоров, */ /* создание и терминирование потоков управления*/ /* * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_t w_ptid;/* Идентификатор потока-писателя */ pthread_t r_ptid;/* Идентификатор потока-читателя */ /* Инициализируем семафоры. */ /* Семафор записи будет свободен,*/ /* семафор чтения - захвачен */ if (sem_init (&w_sem, 0, 1) == -1) { perror ("SEM_INIT-W"); return (-1); } if (sem_init (&r_sem, 0, 0) == -1) { perror ("SEM_INIT-R"); return (-1); } /* Создадим потоки управления - писателя и читателя */ if ((errno = pthread_create (&w_ptid, NULL, start_writer, NULL)) != 0) { perror ("PTHREAD_CREATE-W"); return (errno); } if ((errno = pthread_create (&r_ptid, NULL, start_reader, NULL)) != 0) { perror ("PTHREAD_CREATE-R"); return (errno); } /* Дадим потокам повыполняться */ sleep (10); /* Терминируем потоки */ (void) pthread_cancel (w_ptid); (void) pthread_cancel (r_ptid); /* Дождемся завершения потоков */ (void) pthread_join (w_ptid, NULL); (void) pthread_join (r_ptid, NULL); /* Ликвидируем семафоры */ if (sem_destroy (&w_sem) != 0) { perror ("SEM_DESTROY-W"); return (-1); } if (sem_destroy (&r_sem) != 0) { perror ("SEM_DESTROY-R"); return (-1); } printf ("Сумма сгенерированных чисел: %d\n", sum); return 0; } |
Листинг 4.17. Пример программы, реализующей взаимодействие поставщик/потребитель. |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует взаимодействие */ /* поставщик/потребитель (писатель/читатель). */ /* Поставщик генерирует случайные целые числа */ /* и помещает их в кольцевой буфер, */ /* потребитель извлекает их оттуда и суммирует.*/ /* Для синхронизации используются */ /* неименованные семафоры */ /* * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <semaphore.h> #include <pthread.h> #include <errno.h> /* Буфер для хранения генерируемых данных */ static int my_buf [BUFSIZ]; /* Индекс, по которому можно записать очередной элемент */ static int w_ind = 0; /* Индекс, по которому можно прочитать очередной элемент */ static int r_ind = 0; /* Семафор, разрешающий записывать в буфер новые данные */ static sem_t w_sem; /* Семафор, разрешающий читать данные из буфера */ static sem_t r_sem; /* Общий результат суммирования */ static int sum = 0; /* * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, генерирующего числа*/ /* * * * * * * * * * * * * * * * * * * * * * * */ void *start_writer (void *dummy) { while (sem_wait (&w_sem) == 0) { my_buf [w_ind] = rand (); w_ind = (w_ind + 1) % BUFSIZ; if (sem_post (&r_sem) != 0) { perror ("SEM_POST-R"); return (NULL); } } return (NULL); } /* * * * * * * * * * * * * * * */ /* Стартовая функция потока, */ /* читающего и суммирующего числа*/ /* * * * * * * * * * * * * * * */ void *start_reader (void *dummy) { while (sem_wait (&r_sem) == 0) { sum += my_buf [r_ind]; r_ind = (r_ind + 1) % BUFSIZ; if (sem_post (&w_sem) != 0) { perror ("SEM_POST-W"); return (NULL); } } return (NULL); } /* * * * * * * * * * * * * * * * * * * * * * * */ /* Инициализация семафоров, */ /* создание и терминирование потоков управления*/ /* * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_t w_ptid;/* Идентификатор потока-писателя */ pthread_t r_ptid;/* Идентификатор потока-читателя */ /* Инициализируем семафоры. */ /* Семафор записи будет свободен,*/ /* разрешая заполнить весь буфер,*/ /* семафор чтения - захвачен */ if (sem_init (&w_sem, 0, BUFSIZ) == -1) { perror ("SEM_INIT-W"); return (-1); } if (sem_init (&r_sem, 0, 0) == -1) { perror ("SEM_INIT-R"); return (-1); } /* Создадим потоки управления - писателя и читателя */ if ((errno = pthread_create (&w_ptid, NULL, start_writer, NULL)) != 0) { perror ("PTHREAD_CREATE-W"); return (errno); } if ((errno = pthread_create (&r_ptid, NULL, start_reader, NULL)) != 0) { perror ("PTHREAD_CREATE-R"); return (errno); } /* Дадим потокам повыполняться */ sleep (10); /* Терминируем потоки */ (void) pthread_cancel (w_ptid); (void) pthread_cancel (r_ptid); /* Дождемся завершения потоков */ (void) pthread_join (w_ptid, NULL); (void) pthread_join (r_ptid, NULL); /* Ликвидируем семафоры */ if (sem_destroy (&w_sem) != 0) { perror ("SEM_DESTROY-W"); return (-1); } if (sem_destroy (&r_sem) != 0) { perror ("SEM_DESTROY-R"); return (-1); } printf ("Сумма сгенерированных чисел: %d\n", sum); return 0; } |
Листинг 4.18. Пример программы, реализующей взаимодействие поставщик/потребитель с помощью целочисленных семафоров. |
Закрыть окно |
/* Обедающие философы. Многопотоковая реализация с помощью семафоров. Запуск: mudrecSem [-a | -p | -I] [-t число_секунд] имя_философа ... Опции: -t число_секунд - сколько секунд моделируется Стратегии захвата вилок: -a - сначала захватывается вилка с меньшим номером; -p - сначала захватывается нечетная вилка; -I - некорректная (но эффективная) интеллигентная стратегия: во время ожидания уже захваченная вилка кладется. Пример запуска: mudrecSem -p -t 300 A B C D E F G H I J K L M N\ O P Q R S T U V W X Y Z */ static char rcsid[] __attribute__((unused)) = \ "$Id: mudrecSem.c,v 1.2 2004/03/18 10:28:38 sambor Exp $"; #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <signal.h> #include <string.h> #include <fcntl.h> #include <limits.h> #include <errno.h> #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)>(b)?(b):(a)) struct mudrec { char *name; int left_fork, right_fork; int eat_time, wait_time, think_time, max_wait_time; int count; pthread_t thread; int private_pFdIn; } *kafedra; /* Глобальные счетчики и логические переменные */ int Stop = 0; /* Признак конца обеда */ /* Различные дескрипторы */ int protokol [2] = {-1, -1}; #define pFdIn (protokol [1]) #define pFdOut (protokol [0]) /* Массив семафоров для синхронизации доступа к вилкам */ sem_t *semFork; /* Разные алгоритмы захвата вилок */ static void get_forks_simple (struct mudrec *this); static void get_forks_odd (struct mudrec *this); static void get_forks_maybe_infinit_time (struct mudrec *this); /* Используемый метод захвата вилок */ void (*get_forks) (struct mudrec *this) = get_forks_simple; /* Возвращение вилок */ static void put_forks (struct mudrec *this); /* * Потоки-философы */ void *filosof (void *arg) { struct mudrec *this = arg; char buffer [LINE_MAX]; int bytes; int private_pFdIn = this->private_pFdIn; while (!Stop) { /* Пора подкрепиться */ { int wait_time, tm = time (NULL); sprintf (buffer, "%s: хочет есть\n", this->name); bytes = write (private_pFdIn, buffer, strlen (buffer)); (*get_forks) (this); wait_time = time (NULL) - tm; this->wait_time += wait_time; this->max_wait_time = max (wait_time, this->max_wait_time); sprintf (buffer,"%s: ждал вилок %d сек\n", this->name, wait_time); bytes = write (private_pFdIn, buffer, strlen (buffer)); } /* Может, обед уже закончился? */ if (Stop) { put_forks (this); break; } /* Ест */ { int eat_time = rand () % 20 + 1; sleep (eat_time); this->eat_time += eat_time; this->count++; sprintf (buffer,"%s: ел %d сек\n", this->name, eat_time); bytes = write (private_pFdIn, buffer, strlen (buffer)); } /* Отдает вилки */ put_forks (this); if (Stop) break; /* Размышляет */ { int think_time = rand () % 10 + 1; sleep (think_time); this->think_time += think_time; } } /* while (!Stop) */ sprintf (buffer,"%s: уходит\n", this->name); bytes = write (private_pFdIn, buffer, strlen (buffer)); close (private_pFdIn); return (NULL); } /* Поток-философ */ /* Кладет вилки одну за другой */ static void put_forks (struct mudrec *this) { sem_post (&semFork [this->left_fork - 1]); sem_post (&semFork [this->right_fork - 1]); } /* Берет вилки по очереди в порядке номеров */ static void get_forks_simple (struct mudrec *this) { int first = min (this->left_fork, this->right_fork); int last = max (this->left_fork, this->right_fork); sem_wait (&semFork [first - 1]); sem_wait (&semFork [last - 1]); } /* Берем сначала нечетную вилку */ /* (если обе нечетные - то с большим номером) */ static void get_forks_odd (struct mudrec *this) { int left = this->left_fork, right = this->right_fork; int first; int last; if ((left & 1) > (right & 1)) { first = left; last = right; } else if ((left & 1) < (right & 1)) { first = right; last = left; } else { first = max (left, right); last = min (left, right); } sem_wait (&semFork [first - 1]); sem_wait (&semFork [last - 1]); } /* Берем вилки по очереди, в произвольном порядке. * Но если вторая вилка не берется сразу, то кладем первую. * То есть философ не расходует вилочное время впустую. */ static void get_forks_maybe_infinit_time (struct mudrec *this) { int left = this->left_fork, right = this->right_fork; for (;;) { sem_wait (&semFork [left - 1]); if (0 == sem_trywait (&semFork [right - 1])) return; sem_post (&semFork [left - 1]); sem_wait (&semFork [right - 1]); if (0 == sem_trywait (&semFork [left - 1])) return; sem_post (&semFork [right - 1]); } } /* Мелкие служебные функции */ static void stop (int dummy) { Stop = 1; } static void usage (char name []) { fprintf (stderr, "Использование: %s [-a | -p | -I] [-t число_секунд] " "имя_философа ...\n", name); exit (1); } /* Точка входа демонстрационной программы */ int main (int argc, char *argv []) { char buffer [LINE_MAX], *p; int i, n, c; int open_room_time = 300; int nMudr; struct sigaction sact; while ((c = getopt (argc, argv, "apIt:")) != -1) { switch (c) { case 'a': get_forks = get_forks_simple; break; case 'p': get_forks = get_forks_odd; break; case 'I': get_forks = get_forks_maybe_infinit_time; break; case 't': open_room_time = strtol (optarg, &p, 0); if (optarg [0] == 0 || *p != 0) usage (argv [0]); break; default : usage (argv [0]); } } nMudr = argc - optind; if (nMudr < 2) usage (argv [0]); /* Меньше двух */ /* философов неинтересно ... */ /* Создание канала для протокола обработки событий */ pipe (protokol); kafedra = calloc (sizeof (struct mudrec), nMudr); /* Зачисление на кафедру */ for (i = 0; i < nMudr; i++, optind++) { kafedra [i].name = argv [optind]; /* Выдадим телефон */ kafedra [i].private_pFdIn = fcntl (pFdIn, F_DUPFD, 0); /* Укажем новичку, какими вилками пользоваться */ kafedra [i].left_fork = i + 1; kafedra [i].right_fork = i + 2; } kafedra [nMudr - 1].right_fork = 1; /* Последний*/ /* пользуется вилкой первого */ /* Зададим реакцию на сигналы и установим будильник */ /* на конец обеда */ sact.sa_handler = stop; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGINT, &sact, ( struct sigaction *) NULL); (void) sigaction (SIGALRM, &sact, (struct sigaction *) NULL); alarm (open_room_time); /* Создадим семафоры для охраны вилок */ semFork = calloc (sizeof (sem_t), nMudr); for (i = 0; i < nMudr; i++) { sem_init (&semFork [i], 0, 1 /* На каждое место */ /* по одной вилке */); } /* Философы входят в столовую */ for (i = 0; i < nMudr; i++) pthread_create (&kafedra [i].thread, NULL, &filosof, (void *) &kafedra [i]); /* Выдача сообщений на стандартный вывод и выход */ /* после окончания всех задач */ close (pFdIn); while (1) { n = read (pFdOut, buffer, LINE_MAX); if (n == 0 || (n < 0 && errno != EINTR)) break; for (i = 0; i < n; i++) putchar (buffer [i]); } close (pFdOut); /* Уничтожение семафоров */ for (i = 0; i < nMudr; i++) { sem_destroy (&semFork [i]); } /* Выдача сводной информации */ { int full_eating_time = 0; int full_waiting_time = 0; int full_thinking_time = 0; for (i = 1; i <= nMudr; i++) { struct mudrec *this = &kafedra [i - 1]; full_eating_time += this->eat_time; full_waiting_time += this->wait_time; full_thinking_time += this->think_time; if (this->count > 0) { float count = this->count; float think_time = this->think_time / count; float eat_time = this->eat_time / count; float wait_time = this->wait_time / count; printf ("%s: ел %d раз в среднем: думал=%.1f " "ел=%.1f ждал=%.1f (максимум %d)\n", this->name, this->count, think_time, eat_time, wait_time, this->max_wait_time); } else printf ("%s: не поел\n", this->name); } /* for */ { float total_time = (full_eating_time + full_waiting_time + full_thinking_time) / (float) nMudr; printf("Среднее число одновременно едящих = %.3f\n", full_eating_time / total_time); printf("Среднее число одновременно ждущих = %.3f\n", full_waiting_time / total_time); } } /* Выдача сводной информации */ free (semFork); free (kafedra); /* Сообщим об окончании работы. */ printf ("Конец обеда\n"); return 0; } |
Листинг 4.19. Многопотоковый вариант решения задачи об обедающих философах с использованием семафоров реального времени. |
Закрыть окно |
#include <sys/mman.h> int shm_open (const char *name, int oflag, mode_t mode); int shm_unlink (const char *name); |
Листинг 4.20. Описание функций shm_open() и shm_unlink(). |
Закрыть окно |
#ifndef g_SHM #define g_SHM /* Имя объекта в разделяемой памяти */ #define O_SHM_NAME "/g_o.shm" /* Используемый номер сигнала реального времени */ #define SIG_SHM SIGRTMIN /* Используемые значения сигнала реального времени */ #define SIGVAL_LINE 0 #define SIGVAL_EOF EOF #endif |
Листинг 4.21. Заголовочный файл "g_shm.h" программы, копирующей строки со стандартного ввода на стандартный вывод. |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа, состоящая из двух процессов, копирует */ /* строки со стандартного ввода на стандартный вывод,*/ /* "прокачивая" их через разделяемый сегмент памяти. */ /* Для синхронизации доступа к разделяемому сегменту */ /* используются сигналы реального времени */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <signal.h> #include <sys/mman.h> #include <fcntl.h> #include <limits.h> #include <sys/wait.h> #include <assert.h> #include "g_shm.h" /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Создание разделяемого сегмента памяти, */ /* чтение со стандартного ввода и запись строк в сегмент*/ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта в разделяемой памяти*/ FILE *fp; /* Поток для записи в объект */ char line [LINE_MAX]; /* Буфер для копируемых строк */ struct sigaction sact; /* Структура для обработки сигналов */ union sigval sg_val;/* Значение сигнала */ int sg_no; /* Номер принятого сигнала */ pid_t cpid;/* Идентификатор порожденного процесса */ /* Создадим разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDWR | O_CREAT, 0777)) < 0) { perror ("SHM_CREAT"); return (1); } /* Сформируем поток данных по файловому дескриптору */ /* объекта в разделяемой памяти */ assert ((fp = fdopen (fd_shm, "w")) != NULL); /* Отменим буферизацию вывода */ setbuf (fp, NULL); /* Сформируем маску сигналов (блокируем SIG_SHM) */ (void) sigemptyset (&sact.sa_mask); (void) sigaddset (&sact.sa_mask, SIG_SHM); (void) sigprocmask (SIG_BLOCK, &sact.sa_mask, (sigset_t *) NULL); /* Установим для сигнала SIG_SHM флаг SA_SIGINFO */ sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = (void (*) (int, siginfo_t *, void *)) SIG_DFL; (void) sigaction (SIG_SHM, &sact, (struct sigaction *) NULL); /* Подготовительная работа закончена */ switch (cpid = fork ()) { case -1: perror ("FORK"); return (2); case 0: /* Чтение из объекта и выдачу на стандартный */ /* вывод реализуем в порожденном процессе */ if (execl ("./g_r_shm", "g_r_shm", (char *) NULL) < 0) { perror ("EXECL"); return (3); } } /* Чтение со стандартного ввода и запись в объект */ /* возложим на родительский процесс.*/ /* В начальный момент объект в разделяемой памяти*/ /* доступен для записи */ assert (fseek (fp, 0, SEEK_SET) == 0); fputs ("Вводите строки\n", fp); /* Сообщим порожденному процессу,*/ /* что объект в разделяемой памяти заполнен*/ sg_val.sival_int = SIGVAL_LINE; assert (sigqueue (cpid, SIG_SHM, sg_val) == 0); while (fgets (line, sizeof (line), stdin) != NULL) { assert (fseek (fp, 0, SEEK_SET) == 0); /* Дождемся, когда в объект можно будет писать */ if ((sigwait (&sact.sa_mask, &sg_no) != 0) || (sg_no != SIG_SHM)) { return (4); } assert (fputs ("Вы ввели: ", fp) != EOF); assert (fputs (line, fp) != EOF); assert (sigqueue (cpid, SIG_SHM, sg_val) == 0); } /* Сообщим о конце файла */ sg_val.sival_int = SIGVAL_EOF; assert (sigqueue (cpid, SIG_SHM, sg_val) == 0); fclose (fp); (void) wait (NULL); if (shm_unlink (O_SHM_NAME) != 0) { perror ("SHM_UNLINK"); return (7); } return (0); } |
Листинг 4.22. Исходный текст начального процесса двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод. |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Процесс читает строки из объекта в разделяемой памяти */ /* и копирует их на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <signal.h> #include <sys/mman.h> #include <fcntl.h> #include <limits.h> #include <assert.h> #include "g_shm.h" /* * * * * * * * * * * * * * * * * * * * */ /* Открытие разделяемого сегмента памяти,*/ /* чтение из сегмента и выдача строк */ /* на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта */ /* в разделяемой памяти */ FILE *fp; /* Поток для чтения из объекта */ char line [LINE_MAX];/* Буфер для копируемых строк */ sigset_t smask; /* Маска ожидаемых сигналов */ siginfo_t sinfo; /* Структура для получения */ /* данных о сигнале */ pid_t ppid; /* Идентификатор родительского */ /* процесса */ /* Откроем разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDONLY, 0777)) < 0) { perror ("SHM_OPEN"); return (1); } /* Сформируем поток по файловому дескриптору объекта */ /* в разделяемой памяти */ assert ((fp = fdopen (fd_shm, "r")) != NULL); /* Отменим буферизацию ввода */ setbuf (fp, NULL); /* Запомним идентификатор родительского процесса */ ppid = getppid (); /* Сформируем маску ожидаемых сигналов (SIG_SHM) */ (void) sigemptyset (&smask); (void) sigaddset (&smask, SIG_SHM); /* Подготовительная работа закончена */ while ((fseek (fp, 0, SEEK_SET) == 0) && /* Дождемся, когда из объекта можно будет читать */ (sigwaitinfo (&smask, &sinfo) == SIG_SHM) && /* И прочитаем строку, а не конец файла */ (sinfo.si_value.sival_int == SIGVAL_LINE)) { (void) fgets (line, sizeof (line), fp); /* Сообщим родительскому процессу, */ /* что данные из объекта извлечены */ assert (kill (ppid, SIG_SHM) == 0); /* Выдадим, наконец, строку на стандартный вывод */ assert (fputs (line, stdout) != EOF); } fclose (fp); return 0; } |
Листинг 4.23. Исходный текст порождаемого процесса (файл g_r_shm.c) двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод. |
Закрыть окно |
Разделяемые сегменты памяти
Разделяемые сегменты памяти, называемые далее объектами в разделяемой памяти, - самый эффективный способ передачи данных между процессами. Одной операцией записи можно передать данные сразу многим процессам, разделяющим тот же объект.
Объекты в разделяемой памяти полезны как для систем, поддерживающих виртуальную память и раздельные адресные пространства для процессов, так и для встроенных систем с минимальной аппаратной поддержкой.
Минимальный мобильный программный интерфейс к объектам в разделяемой памяти включает функции открытия (возможно, с созданием) подобного объекта и получения его дескриптора, а также удаления ранее созданного объекта.
Стандартный программный интерфейс состоит из двух функций: shm_open() и shm_unlink() (см. листинг 4.19).
#include <sys/mman.h>
int shm_open (const char *name, int oflag, mode_t mode);
int shm_unlink (const char *name);
Листинг 4.20. Описание функций shm_open() и shm_unlink(). (html, txt)
При открытии с помощью функции shm_open() возвращается файловый дескриптор. Имя (аргумент name) трактуется стандартным для рассматриваемых средств межпроцессного взаимодействия образом. Посредством аргумента oflag могут указываться флаги O_RDONLY, O_RDWR, O_CREAT, O_EXCL и/или O_TRUNC. Если объект создается, то режим доступа к нему формируется в соответствии со значением аргумента mode и маской создания файлов процесса.
После создания объект в разделяемой памяти существует, пока не будет удален функцией shm_unlink(). Он сохраняет свое состояние после закрытия всех ссылающихся на него дескрипторов, однако эффект от перезагрузки системы стандарт POSIX-2001 не специфицирует.
Представляется естественным, что способ доступа к объектам определяется типом дескриптора, возвращаемого при их открытии. Если это адрес, то доступ сводится к операциям чтения/записи из/в память. Если это файловый дескриптор, то для доступа должны использоваться функции файлового ввода/вывода - read(), write() и т.п. Подобное естественное применение объектов в разделяемой памяти иллюстрируется двухпроцессной программой, копирующей строки со стандартного ввода на стандартный вывод (см.
листинги 4.21, 4.22, 4.23). Предполагается, что заголовочный файл называется "g_shm.h", а файл с образом процесса, запускаемого посредством execl(), - "g_r_shm".
#ifndef g_SHM #define g_SHM
/* Имя объекта в разделяемой памяти */ #define O_SHM_NAME "/g_o.shm"
/* Используемый номер сигнала реального времени */ #define SIG_SHM SIGRTMIN
/* Используемые значения сигнала реального времени */ #define SIGVAL_LINE 0 #define SIGVAL_EOF EOF
#endif
Листинг 4.21. Заголовочный файл "g_shm.h" программы, копирующей строки со стандартного ввода на стандартный вывод. (html, txt)
Листинг 4.22. Исходный текст начального процесса двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод. (html, txt)
Листинг 4.23. Исходный текст порождаемого процесса (файл g_r_shm.c) двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод. (html, txt)
Объект в разделяемой памяти используется в приведенной программе как буфер на один элемент (одну строку). Чтение и запись производятся с начала объекта, для чего применяется файловое позиционирование. Разумеется, в такой ситуации порожденный процесс не может обычным образом обнаружить конец передаваемого ему файла, поэтому приходится применять дополнительные средства. В данном случае это сигналы реального времени, которые несут двойную нагрузку - обеспечивают синхронизацию доступа к объекту в разделяемой памяти и передают от родительского процесса порожденному информацию о конце файла.
К сожалению, стандарт POSIX-2001 не следует приведенным выше естественным предположениям, касающимся связи между типом дескриптора и способом доступа к объекту, так что приведенная программа, строго говоря, не соответствует стандарту. Дело в том, что файловый дескриптор, возвращаемый функцией shm_open(), является дескриптором "второго сорта" (хотя и первой свежести): результат применения к нему функций fdopen(), read(), write() и т.п. не специфицирован.
Далее мы увидим, для чего этот дескриптор можно употребить и как организовать межпроцессное взаимодействие через разделяемые сегменты памяти реального времени в полном соответствии с положениями стандарта POSIX-2001.Здесь же отметим, что в большинстве реализаций для представления разделяемых сегментов применяются файлы, отображенные в память, так что их дескрипторы вполне пригодны для выполнения файловых операций, а мобильность приведенной программы с практической точки зрения можно считать удовлетворительной.
Семафоры реального времени
Семафор реального времени - это эффективный механизм синхронизации процессов, представляющий собой общесистемный разделяемый ресурс, имеющий неотрицательное целочисленное значение.
Основными операциями над семафором являются захват и освобождение. Если делается попытка захвата семафора, когда его значение равно нулю, выполнение вызывающего потока управления приостанавливается и он добавляется к множеству потоков, ждущих на семафоре; в противном случае значение уменьшается.
Если при освобождении семафора множество ждущих потоков было непусто, один из них удаляется из этого множества и его выполнение возобновляется; в противном случае значение семафора просто увеличивается.
Семафоры бывают именованными и безымянными (неименованными). Первые именуются цепочками символов и создаются функцией sem_open() с флагом O_CREAT, вторые создаются функцией sem_init(). При прочих операциях семафор идентифицируется открытым дескриптором (который может быть унаследован у родительского процесса, вызвавшего fork()). Дескриптор реализуется как указатель на объект типа sem_t.
Перед выполнением операций семафор необходимо инициализировать, задав неотрицательное значение. Отрицательные значения (точнее, их абсолютная величина) могут использоваться реализацией для указания числа ждущих потоков управления.
Семафор сохраняет свое состояние после закрытия последней ссылки на него, то есть если позднее он будет вновь открыт, его значение окажется тем же, что и перед закрытием.
Детальное описание функций, обслуживающих семафоры, мы начнем, разумеется, с sem_open() и sem_init() (см. листинг 4.11), которые обеспечивают открытие, создание и инициализацию.
#include <semaphore.h>
sem_t *sem_open (const char *name, int oflag, ...);
int sem_init (sem_t *sem, int pshared, unsigned value);
Листинг 4.11. Описание функций sem_open() и sem_init(). (html, txt)
Имена семафоров (аргумент name функции sem_open()) устроены и трактуются так же, как и описанные выше имена очередей сообщений. Флагов (в аргументе oflag) может быть установлено два: O_CREAT и/или O_EXCL.
Если установлен флаг O_CREAT, то при вызове функции sem_open() необходимо задать два дополнительных аргумента: режим доступа (тип mode_t) и значение семафора (тип unsigned int).
В случае ошибки функция sem_open() возвращает значение SEM_FAILED, отличное от любого допустимого указателя на объект типа sem_t, а sem_init() "по старинке" возвращает -1.
Отметим, что нормальный результат для функции sem_init() в POSIX-2001 не стандартизован; вероятно, в будущих версиях им станет нуль. Инициализированный объект типа sem_t помещается по указателю sem. Аргумент value задает начальное значение создаваемого неименованного семафора. Если аргумент pshared отличен от нуля, семафор разделяется между процессами; в противном случае разделение возможно только между потоками управления вызывающего процесса.
Именованный семафор можно закрыть, обратившись к функции sem_close(), и удалить с помощью функции sem_unlink(); для ликвидации неименованных семафоров служит функция sem_destroy() (см. листинг 4.12). Нормальный результат этих функций равен нулю, в случае ошибки возвращается -1.
#include <semaphore.h>
int sem_close (sem_t *sem);
int sem_unlink (const char *name);
int sem_destroy (sem_t *sem);
Листинг 4.12. Описание функций закрытия и ликвидации семафоров. (html, txt)
Отметим, что эффект от вызова sem_close() для неименованного семафора, равно как и последствия вызова sem_destroy() для семафора именованного, не определены. Также не определен результат применения функции sem_destroy() к семафору, на котором имеются ждущие потоки управления.
Для захвата семафоров служат функции sem_wait(), sem_trywait() и sem_timedwait() (см. листинги 4.13 и 4.14).
#include <semaphore.h>
int sem_wait (sem_t *sem);
int sem_trywait (sem_t *sem);
Листинг 4.13. Описание функций захвата семафоров. (html, txt) #include <semaphore.h> #include <time.h> int sem_timedwait (sem_t *restrict sem, const struct timespec *restrict abstime);
Листинг 4.14. Описание функции захвата семафора с контролем времени ожидания. (html, txt)
Если значение семафора было положительным, все три перечисленные функции без каких-либо задержек завершаются успехом, уменьшая это значение до нуля и возвращая нулевой результат. В противном случае вызов sem_trywait() завершается неудачей, вызов sem_wait() блокируется до освобождения семафора, вызов sem_timedwait() также блокируется, но с контролем времени ожидания.
Освобождение семафора осуществляется функцией sem_post() (см. листинг 4.15). Для возобновления выполнения (если таковое имеет место) выбирается наиболее приоритетный поток управления, а среди потоков с равными приоритетами - тот, что ждал дольше других.
#include <semaphore.h> int sem_post (sem_t *sem);
Листинг 4.15. Описание функции sem_post(). (html, txt)
Функция sem_getvalue() (см. листинг 4.16) позволяет опросить значение семафора, не меняя его состояния.
#include <semaphore.h> int sem_getvalue (sem_t *restrict sem, int *restrict sval);
Листинг 4.16. Описание функции sem_getvalue(). (html, txt)
Значение семафора записывается по указателю sval. Если семафор был захвачен, это значение окажется нулевым или отрицательным.
Если сопоставить семафоры реального времени с теми, что были описаны в курсе [1], то можно сделать те же выводы, что и для очередей сообщений. Семафоры реального времени, согласно стандарту POSIX-2001, по сути являются бинарными. Они устроены проще и, следовательно, могут быть реализованы эффективнее. Главное - отсутствуют тяжеловесные, со сложной семантикой, трудные для реализации групповые операции.
Использование семафоров реального времени проиллюстрируем программой, реализующей взаимодействие поставщик/потребитель (см. листинг 4.17).
Листинг 4.17. Пример программы, реализующей взаимодействие поставщик/потребитель. (html, txt)
Применение неименованных семафоров в данном случае представляется вполне естественным. Поставщик захватывает семафор записи, заполняет буфер и освобождает семафор, разрешающий чтение. Потребитель действует симметричным образом.
Отметим, что семафоры ликвидируются после терминирования использующих их потоков управления, что, согласно стандарту POSIX-2001, является безопасным.
Поясним и обсудим сделанное выше замечание о том, что, согласно стандарту POSIX-2001, семафоры реального времени по сути являются бинарными. Такой вывод можно сделать по двум фразам из описания функции sem_trywait(). Во-первых, функция sem_trywait() захватывает семафор только в том случае, если он еще не захвачен, то есть если значение семафора положительно. Во-вторых, в случае успешного завершения семафор захватывается и должен оставаться в этом состоянии, пока не будет успешно выполнена функция sem_post().
Здесь ничего не говорится об уменьшении положительного значения без захвата семафора, зато намеки на это имеются в части, где приводятся детальное разъяснение положений стандарта и обоснование принятых решений. Смущает и то, что в описании функции sem_post() не запрещается освобождать свободный семафор, что, естественно, выливается в увеличение его значения. Далее, в разных местах семафоры называются то бинарными, то целочисленными. Наконец, автору не известно ни одной реализации, где семафоры реального времени не были бы целочисленными. В общем, сложное это дело - толкование внутренне противоречивых стандартов, вступающих в конфликт с реальной жизнью...
Если считать, что рассматриваемые семафоры являются целочисленными, приведенную выше программу можно усовершенствовать, сделав буфер кольцевым, с размером, большим единицы (см. листинг 4.18).
Листинг 4.18. Пример программы, реализующей взаимодействие поставщик/потребитель с помощью целочисленных семафоров. (html, txt)
Значение семафора w_sem равно числу элементов буфера, доступных для записи, r_sem - для чтения. Многопотоковым инвариантом программы является сумма этих величин, равная размеру буфера. В начальный момент буфер целиком доступен для записи. После этого вызовы sem_wait() и sem_post() уменьшают значение "своего" и увеличивают значение "чужого" семафора. Поток управления приостанавливается в sem_wait(), когда "свое" значение уменьшается до нуля, у нужно ждать, пока другой поток вызовом sem_post() не увеличит его, сделав положительным.
Разумеется, обсуждение темы семафоров было бы неполным без обеда философов. Мы приведем программу, написанную С.В. Самборским (см. листинг 4.19). В ней семафоры используются как бинарные, поэтому ее стандартность и мобильность не вызывают сомнений.
Листинг 4.19. Многопотоковый вариант решения задачи об обедающих философах с использованием семафоров реального времени. (html, txt)
/* Дадим потокам повыполняться */ sleep (10);
/* Терминируем потоки */ (void) pthread_cancel (w_ptid); (void) pthread_cancel (r_ptid);
/* Дождемся завершения потоков */ (void) pthread_join (w_ptid, NULL); (void) pthread_join (r_ptid, NULL); /* Ликвидируем семафоры */ if (sem_destroy (&w_sem) != 0) { perror ("SEM_DESTROY-W"); return (-1); } if (sem_destroy (&r_sem) != 0) { perror ("SEM_DESTROY-R"); return (-1); }
printf ("Сумма сгенерированных чисел: %d\n", sum);
return 0; }
Листинг 4.17. Пример программы, реализующей взаимодействие поставщик/потребитель.
Применение неименованных семафоров в данном случае представляется вполне естественным. Поставщик захватывает семафор записи, заполняет буфер и освобождает семафор, разрешающий чтение. Потребитель действует симметричным образом.
Отметим, что семафоры ликвидируются после терминирования использующих их потоков управления, что, согласно стандарту POSIX-2001, является безопасным.
Поясним и обсудим сделанное выше замечание о том, что, согласно стандарту POSIX-2001, семафоры реального времени по сути являются бинарными. Такой вывод можно сделать по двум фразам из описания функции sem_trywait(). Во-первых, функция sem_trywait() захватывает семафор только в том случае, если он еще не захвачен, то есть если значение семафора положительно. Во-вторых, в случае успешного завершения семафор захватывается и должен оставаться в этом состоянии, пока не будет успешно выполнена функция sem_post().
Здесь ничего не говорится об уменьшении положительного значения без захвата семафора, зато намеки на это имеются в части, где приводятся детальное разъяснение положений стандарта и обоснование принятых решений. Смущает и то, что в описании функции sem_post() не запрещается освобождать свободный семафор, что, естественно, выливается в увеличение его значения. Далее, в разных местах семафоры называются то бинарными, то целочисленными. Наконец, автору не известно ни одной реализации, где семафоры реального времени не были бы целочисленными.
В общем, сложное это дело - толкование внутренне противоречивых стандартов, вступающих в конфликт с реальной жизнью...
Если считать, что рассматриваемые семафоры являются целочисленными, приведенную выше программу можно усовершенствовать, сделав буфер кольцевым, с размером, большим единицы (см. листинг 4.18).
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует взаимодействие */ /* поставщик/потребитель (писатель/читатель). */ /* Поставщик генерирует случайные целые числа */ /* и помещает их в кольцевой буфер, */ /* потребитель извлекает их оттуда и суммирует.*/ /* Для синхронизации используются */ /* неименованные семафоры */ /* * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <semaphore.h> #include <pthread.h> #include <errno.h>
/* Буфер для хранения генерируемых данных */ static int my_buf [BUFSIZ];
/* Индекс, по которому можно записать очередной элемент */ static int w_ind = 0;
/* Индекс, по которому можно прочитать очередной элемент */ static int r_ind = 0;
/* Семафор, разрешающий записывать в буфер новые данные */ static sem_t w_sem;
/* Семафор, разрешающий читать данные из буфера */ static sem_t r_sem;
/* Общий результат суммирования */ static int sum = 0;
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Стартовая функция потока, генерирующего числа*/ /* * * * * * * * * * * * * * * * * * * * * * * */ void *start_writer (void *dummy) { while (sem_wait (&w_sem) == 0) { my_buf [w_ind] = rand (); w_ind = (w_ind + 1) % BUFSIZ; if (sem_post (&r_sem) != 0) { perror ("SEM_POST-R"); return (NULL); } }
return (NULL); }
/* * * * * * * * * * * * * * * */ /* Стартовая функция потока, */ /* читающего и суммирующего числа*/ /* * * * * * * * * * * * * * * */ void *start_reader (void *dummy) { while (sem_wait (&r_sem) == 0) { sum += my_buf [r_ind]; r_ind = (r_ind + 1) % BUFSIZ; if (sem_post (&w_sem) != 0) { perror ("SEM_POST-W"); return (NULL); } }
return (NULL); }
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Инициализация семафоров, */ /* создание и терминирование потоков управления*/ /* * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { pthread_t w_ptid;/* Идентификатор потока-писателя */ pthread_t r_ptid;/* Идентификатор потока-читателя */
/* Инициализируем семафоры. */ /* Семафор записи будет свободен,*/ /* разрешая заполнить весь буфер,*/ /* семафор чтения - захвачен */ if (sem_init (&w_sem, 0, BUFSIZ) == -1) { perror ("SEM_INIT-W"); return (-1); } if (sem_init (&r_sem, 0, 0) == -1) { perror ("SEM_INIT-R"); return (-1); }
/* Создадим потоки управления - писателя и читателя */ if ((errno = pthread_create (&w_ptid, NULL, start_writer, NULL)) != 0) { perror ("PTHREAD_CREATE-W"); return (errno); } if ((errno = pthread_create (&r_ptid, NULL, start_reader, NULL)) != 0) { perror ("PTHREAD_CREATE-R"); return (errno); }
/* Дадим потокам повыполняться */ sleep (10);
/* Терминируем потоки */ (void) pthread_cancel (w_ptid); (void) pthread_cancel (r_ptid);
/* Дождемся завершения потоков */ (void) pthread_join (w_ptid, NULL); (void) pthread_join (r_ptid, NULL);
/* Ликвидируем семафоры */ if (sem_destroy (&w_sem) != 0) { perror ("SEM_DESTROY-W"); return (-1); }
if (sem_destroy (&r_sem) != 0) { perror ("SEM_DESTROY-R"); return (-1); }
printf ("Сумма сгенерированных чисел: %d\n", sum);
return 0; }
Листинг 4.18. Пример программы, реализующей взаимодействие поставщик/потребитель с помощью целочисленных семафоров.
Значение семафора w_sem равно числу элементов буфера, доступных для записи, r_sem - для чтения. Многопотоковым инвариантом программы является сумма этих величин, равная размеру буфера. В начальный момент буфер целиком доступен для записи. После этого вызовы sem_wait() и sem_post() уменьшают значение "своего" и увеличивают значение "чужого" семафора. Поток управления приостанавливается в sem_wait(), когда "свое" значение уменьшается до нуля, у нужно ждать, пока другой поток вызовом sem_post() не увеличит его, сделав положительным.
Разумеется, обсуждение темы семафоров было бы неполным без обеда философов. Мы приведем программу, написанную С.В. Самборским (см. листинг 4.19). В ней семафоры используются как бинарные, поэтому ее стандартность и мобильность не вызывают сомнений.
/* Обедающие философы. Многопотоковая реализация с помощью семафоров. Запуск: mudrecSem [-a | -p | -I] [-t число_секунд] имя_философа ...
Опции: -t число_секунд - сколько секунд моделируется
Стратегии захвата вилок: -a - сначала захватывается вилка с меньшим номером; -p - сначала захватывается нечетная вилка; -I - некорректная (но эффективная) интеллигентная стратегия: во время ожидания уже захваченная вилка кладется.
Пример запуска: mudrecSem -p -t 300 A B C D E F G H I J K L M N\ O P Q R S T U V W X Y Z */
static char rcsid[] __attribute__((unused)) = \ "$Id: mudrecSem.c,v 1.2 2004/03/18 10:28:38 sambor Exp $";
#include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <signal.h> #include <string.h> #include <fcntl.h> #include <limits.h> #include <errno.h>
#define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)>(b)?(b):(a))
struct mudrec { char *name; int left_fork, right_fork; int eat_time, wait_time, think_time, max_wait_time; int count; pthread_t thread; int private_pFdIn; } *kafedra;
/* Глобальные счетчики и логические переменные */ int Stop = 0; /* Признак конца обеда */
/* Различные дескрипторы */ int protokol [2] = {-1, -1}; #define pFdIn (protokol [1]) #define pFdOut (protokol [0])
/* Массив семафоров для синхронизации доступа к вилкам */ sem_t *semFork;
/* Разные алгоритмы захвата вилок */ static void get_forks_simple (struct mudrec *this); static void get_forks_odd (struct mudrec *this); static void get_forks_maybe_infinit_time (struct mudrec *this);
/* Используемый метод захвата вилок */ void (*get_forks) (struct mudrec *this) = get_forks_simple;
/* Возвращение вилок */ static void put_forks (struct mudrec *this);
/* * Потоки-философы */ void *filosof (void *arg) { struct mudrec *this = arg; char buffer [LINE_MAX]; int bytes; int private_pFdIn = this->private_pFdIn;
while (!Stop) { /* Пора подкрепиться */ { int wait_time, tm = time (NULL);
sprintf (buffer, "%s: хочет есть\n", this->name); bytes = write (private_pFdIn, buffer, strlen (buffer));
(*get_forks) (this);
wait_time = time (NULL) - tm; this->wait_time += wait_time; this->max_wait_time = max (wait_time, this->max_wait_time);
sprintf (buffer,"%s: ждал вилок %d сек\n", this->name, wait_time); bytes = write (private_pFdIn, buffer, strlen (buffer)); }
/* Может, обед уже закончился? */ if (Stop) { put_forks (this); break; }
/* Ест */ { int eat_time = rand () % 20 + 1;
sleep (eat_time);
this->eat_time += eat_time; this->count++; sprintf (buffer,"%s: ел %d сек\n", this->name, eat_time); bytes = write (private_pFdIn, buffer, strlen (buffer)); }
/* Отдает вилки */ put_forks (this);
if (Stop) break;
/* Размышляет */ { int think_time = rand () % 10 + 1;
sleep (think_time); this->think_time += think_time; } } /* while (!Stop) */
sprintf (buffer,"%s: уходит\n", this->name); bytes = write (private_pFdIn, buffer, strlen (buffer));
close (private_pFdIn);
return (NULL); } /* Поток-философ */
/* Кладет вилки одну за другой */ static void put_forks (struct mudrec *this) { sem_post (&semFork [this->left_fork - 1]); sem_post (&semFork [this->right_fork - 1]); }
/* Берет вилки по очереди в порядке номеров */ static void get_forks_simple (struct mudrec *this) { int first = min (this->left_fork, this->right_fork); int last = max (this->left_fork, this->right_fork);
sem_wait (&semFork [first - 1]); sem_wait (&semFork [last - 1]); }
/* Берем сначала нечетную вилку */ /* (если обе нечетные - то с большим номером) */ static void get_forks_odd (struct mudrec *this) { int left = this->left_fork, right = this->right_fork;
int first; int last;
if ((left & 1) > (right & 1)) { first = left; last = right; } else if ((left & 1) < (right & 1)) { first = right; last = left; } else { first = max (left, right); last = min (left, right); }
sem_wait (&semFork [first - 1]); sem_wait (&semFork [last - 1]); }
/* Берем вилки по очереди, в произвольном порядке. * Но если вторая вилка не берется сразу, то кладем первую. * То есть философ не расходует вилочное время впустую. */ static void get_forks_maybe_infinit_time (struct mudrec *this) { int left = this->left_fork, right = this->right_fork;
for (;;) { sem_wait (&semFork [left - 1]); if (0 == sem_trywait (&semFork [right - 1])) return; sem_post (&semFork [left - 1]); sem_wait (&semFork [right - 1]); if (0 == sem_trywait (&semFork [left - 1])) return; sem_post (&semFork [right - 1]); } }
/* Мелкие служебные функции */ static void stop (int dummy) { Stop = 1; }
static void usage (char name []) { fprintf (stderr, "Использование: %s [-a | -p | -I] [-t число_секунд] " "имя_философа ...\n", name); exit (1); }
/* Точка входа демонстрационной программы */ int main (int argc, char *argv []) { char buffer [LINE_MAX], *p; int i, n, c; int open_room_time = 300; int nMudr; struct sigaction sact;
while ((c = getopt (argc, argv, "apIt:")) != -1) { switch (c) { case 'a': get_forks = get_forks_simple; break; case 'p': get_forks = get_forks_odd; break; case 'I': get_forks = get_forks_maybe_infinit_time; break; case 't': open_room_time = strtol (optarg, &p, 0); if (optarg [0] == 0 || *p != 0) usage (argv [0]); break; default : usage (argv [0]); } }
nMudr = argc - optind; if (nMudr < 2) usage (argv [0]); /* Меньше двух */ /* философов неинтересно ... */
/* Создание канала для протокола обработки событий */ pipe (protokol);
kafedra = calloc (sizeof (struct mudrec), nMudr);
/* Зачисление на кафедру */ for (i = 0; i < nMudr; i++, optind++) { kafedra [i].name = argv [optind]; /* Выдадим телефон */ kafedra [i].private_pFdIn = fcntl (pFdIn, F_DUPFD, 0); /* Укажем новичку, какими вилками пользоваться */ kafedra [i].left_fork = i + 1; kafedra [i].right_fork = i + 2; } kafedra [nMudr - 1].right_fork = 1; /* Последний*/ /* пользуется вилкой первого */
/* Зададим реакцию на сигналы и установим будильник */ /* на конец обеда */ sact.sa_handler = stop; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGINT, &sact, ( struct sigaction *) NULL); (void) sigaction (SIGALRM, &sact, (struct sigaction *) NULL); alarm (open_room_time);
/* Создадим семафоры для охраны вилок */ semFork = calloc (sizeof (sem_t), nMudr); for (i = 0; i < nMudr; i++) { sem_init (&semFork [i], 0, 1 /* На каждое место */ /* по одной вилке */); }
/* Философы входят в столовую */ for (i = 0; i < nMudr; i++) pthread_create (&kafedra [i].thread, NULL, &filosof, (void *) &kafedra [i]);
/* Выдача сообщений на стандартный вывод и выход */ /* после окончания всех задач */ close (pFdIn); while (1) { n = read (pFdOut, buffer, LINE_MAX); if (n == 0 || (n < 0 && errno != EINTR)) break; for (i = 0; i < n; i++) putchar (buffer [i]); } close (pFdOut);
/* Уничтожение семафоров */ for (i = 0; i < nMudr; i++) { sem_destroy (&semFork [i]); }
/* Выдача сводной информации */ { int full_eating_time = 0; int full_waiting_time = 0; int full_thinking_time = 0; for (i = 1; i <= nMudr; i++) { struct mudrec *this = &kafedra [i - 1];
full_eating_time += this->eat_time; full_waiting_time += this->wait_time; full_thinking_time += this->think_time;
if (this->count > 0) { float count = this->count; float think_time = this->think_time / count; float eat_time = this->eat_time / count; float wait_time = this->wait_time / count;
printf ("%s: ел %d раз в среднем: думал=%.1f " "ел=%.1f ждал=%.1f (максимум %d)\n", this->name, this->count, think_time, eat_time, wait_time, this->max_wait_time); } else printf ("%s: не поел\n", this->name); } /* for */
{ float total_time = (full_eating_time + full_waiting_time + full_thinking_time) / (float) nMudr;
printf("Среднее число одновременно едящих = %.3f\n", full_eating_time / total_time); printf("Среднее число одновременно ждущих = %.3f\n", full_waiting_time / total_time); } } /* Выдача сводной информации */
free (semFork); free (kafedra);
/* Сообщим об окончании работы. */ printf ("Конец обеда\n");
return 0; }
Листинг 4.19. Многопотоковый вариант решения задачи об обедающих философах с использованием семафоров реального времени.
Объекты в типизированной памяти
Объекты в типизированной памяти - это конфигурируемые реализацией именованные пулы памяти, доступные одному или нескольким процессорам системы через один или несколько портов. Пример подобной конфигурации показан на рис. 5.1.
Рис. 5.1. Пример многопроцессорной системы с несколькими пулами памяти
Здесь пул памяти M4 доступен только процессору P2. Пул M2 включает в себя пулы M2.1 и M2.2, причем доступ к M2.1 возможен только через порт B1. Остальные пулы доступны обоим процессорам через указанные на рисунке порты.
Разные пулы памяти могут обладать различными операционными характеристиками. Это может быть статическая или динамическая память, ПЗУ, энергонезависимая память и т.п.
Каждая допустимая комбинация пула памяти и порта идентифицируется именем, определяемым при конфигурировании системы способом, зависящим от реализации. Используя это имя, объект в типизированной памяти можно открыть и отобразить в адресное пространство процесса. Реализация должна поддерживать как динамическое выделение (резервирование) памяти из пула (аналог malloc(), когда приложение задает только запрашиваемый объем), так и отображение части пула с заданным в приложении смещением от начала. Зарезервированный фрагмент в последующем подлежит освобождению. Наконец, при доступе через определенный порт нужно иметь возможность выяснить смещение и размер непрерывного участка типизированной памяти.
Согласно стандарту POSIX-2001, для открытия объектов в типизированной памяти служит функция posix_typed_mem_open() (см. листинг 5.11).
#include <sys/mman.h> int posix_typed_mem_open (const char *name, int oflag, int tflag);
Листинг 5.11. Описание функции posix_typed_mem_open(). (html, txt)
Нормальным результатом функции posix_typed_mem_open() является файловый дескриптор объекта в типизированной памяти с именем name.
Аргумент oflag задает один из трех разрешенных видов доступа к объекту - O_RDONLY, O_WRONLY или O_RDWR.
Значение аргумента tflag определяет поведение объекта в типизированной памяти при последующих отображениях посредством функции mmap().
Может быть установлен один из трех флагов:
POSIX_TYPED_MEM_ALLOCATE
При вызове mmap() резервировать и отображать память, возможно, состоящую из нескольких несмежных сегментов (но в любом случае отображается она в непрерывный фрагмент адресного пространства процесса).
POSIX_TYPED_MEM_ALLOCATE_CONTIG
При вызове mmap() резервировать и отображать один непрерывный сегмент памяти.
POSIX_TYPED_MEM_MAP_ALLOCATABLE
При вызове mmap() отображать объект в адресное пространство процесса, не воздействуя на возможности резервирования памяти.
Если не установлен ни один из описанных флагов, указанный фрагмент объекта отображается в адресное пространство процесса и становится недоступным для резервирования; после отмены отображения возможность резервирования восстанавливается.
Отметим, что, в отличие от объектов в разделяемой памяти, объекты в типизированной памяти нельзя создать - их можно только открыть. Не применима к ним (точнее, к их открытым дескрипторам) и функция установки размера ftruncate().
После того, как объект в типизированной памяти открыт, посредством функции posix_typed_mem_get_info() (см. листинг 5.12) можно выяснить максимальный объем памяти, доступной для резервирования. Это важно, поскольку типизированная память зачастую является дефицитным ресурсом.
#include <sys/mman.h> int posix_typed_mem_get_info (int fildes, struct posix_typed_mem_info *info);
Листинг 5.12. Описание функции posix_typed_mem_get_info(). (html, txt)
Соответствующая информация возвращается в структуре типа posix_typed_mem_info, на которую указывает аргумент info. Согласно стандарту POSIX-2001, эта структура должна содержать поле
size_t posix_tmi_length; /* Максимальный объем памяти, */ /* доступной для резервирования*/
Функция posix_typed_mem_get_info() принимает во внимание флаг POSIX_TYPED_MEM_ALLOCATE или POSIX_TYPED_MEM_ALLOCATE_CONTIG, если он был установлен при открытии объекта в типизированной памяти. При отсутствии этих флагов результат вызова posix_typed_mem_get_info() не специфицирован.
Разумеется, возвращаемый максимальный объем памяти, доступной для резервирования, есть величина, динамически изменяющаяся. Если объект в типизированной памяти является разделяемым, то нет гарантии, что в интервале между опросом и попыткой резервирования этот объем останется прежним.
Еще одним проявлением общего принципа "если объект может измениться, должна быть возможность опросить его текущее состояние" является наличие в стандарте POSIX-2001 функции posix_mem_offset() (см. листинг 5.13).
#include <sys/mman.h> int posix_mem_offset ( const void *restrict addr, size_t len, off_t *restrict off, size_t *restrict contig_len, int *restrict fildes);
Листинг 5.13. Описание функции posix_mem_offset(). (html, txt)
Функция posix_mem_offset() позволяет выяснить адрес (смещение от начала), длину и дескриптор объекта (блока) в типизированной памяти, отображенного в адресное пространство процесса, начиная с адреса addr и имеющего длину len. Искомое смещение записывается по указателю off. По указателю contig_len помещается минимум из значения len и длины максимального непрерывного блока типизированной памяти, отображенного, начиная с addr. По указателю fildes выдается дескриптор, использованный в вызове mmap() при задании опрашиваемого отображения (если с тех пор дескриптор закрыли, выдается -1).
Если дескриптор объекта в типизированной памяти был открыт без флагов POSIX_TYPED_MEM_ALLOCATE и POSIX_TYPED_MEM_ALLOCATE_CONTIG, то возвращенные функцией posix_mem_offset() значения, будучи подставлены в вызов mmap(), зададут отображение в точности того же блока типизированной памяти, что и при первоначальном обращении к mmap().
Поведение функции posix_mem_offset() при попытке опросить характеристики отображения объекта, не являющегося объектом в типизированной памяти, зависит от реализации.
Рассмотрим возможную последовательность применения описанных функций. Пусть процессу A1, выполняющемуся на процессоре P1 (см. выше рис. 5.1), требуется зарезервировать блок памяти из пула M2, причем этот блок предполагается использовать совместно с процессом A2, выполняющемся на процессоре P2.
Поскольку P2 имеет доступ только к M2.2, процессы должны использовать именно эту часть M2.
В качестве первого шага процесс A1 вызывает функцию posix_typed_mem_open() с именем объекта "/m2.2-b1" (или каким-то еще в том же духе, включающим имена пула и порта) и установленным в аргументе tflag флагом POSIX_TYPED_MEM_ALLOCATE, получая открытый дескриптор, пригодный для резервирования памяти. Затем A1 обращается с этим дескриптором к функции mmap(), указывая в качестве длины довольно большое значение, например, 1048576. Пусть в ответ на запрос система выделяет два несмежных блока типизированной памяти длиной 524288 байт каждый, отображает их в один непрерывный фрагмент адресного пространства процесса A1 и возвращает указатель на него. Теперь процесс A1 может стандартным образом работать с полученным мегабайтным массивом.
Если процесс A1 пожелает выяснить, какие части пула M2.2 были зарезервированы, он должен обратиться к функции posix_mem_offset() с адресом первого элемента массива и полной длиной (1048576). В ответ он получит смещение и длину первого из выделенных блоков (524288). Поскольку возвращенная длина меньше запрошенной, требуется повторный вызов posix_mem_offset() со смещением в полмегабайта от начала массива и соответственно уменьшенной длиной. (В общем случае, разумеется, следует организовать цикл, вызывая posix_mem_offset() до тех пор, пока не будет исчерпан весь отображенный массив. Чтобы избежать подобных относительно сложных действий, можно при открытии объекта в типизированной памяти установить в tflag флаг POSIX_TYPED_MEM_ALLOCATE_CONTIG, но тогда, возможно, попытка резервирования непрерывного мегабайтного блока закончится неудачей.)
Чтобы обеспечить совместное использование типизированной памяти с процессом A2, A1 должен каким-то образом (очевидно, с помощью средств межпроцессного взаимодействия) передать тому смещения и длины зарезервированных блоков. Получив эти данные, A2 обращается к posix_typed_mem_open() с именем "/m2.2-b2" (доступ через порт B2) и нулевым значением tflag, а затем дважды вызывает mmap() для отображения обоих непрерывных фрагментов типизированной памяти в свое адресное пространство.Вероятно, при втором вызове целесообразно указать соответствующий адрес начала и флаг MAP_FIXED, чтобы "склеить" эти отображения и, как и в случае с A1, получить непрерывный мегабайтный массив.
Отображение объектов в адресное пространство процессов
Идея, лежащая в основе рассматриваемого класса средств, в сущности, весьма проста. Если отобразить объект в адресное пространство процесса, то доступ к объекту можно осуществлять обычными операциями чтения/записи. Если один объект отображен в адресное пространство нескольких процессов, он превращается в средство межпроцессного взаимодействия, так как данные, записанные в объект одним процессом, появляются в адресных пространствах всех участников отображения.
Отображаться могут обычные файлы, а также объекты в разделяемой и типизированной памяти.
Реализация должна обеспечивать, чтобы отображению подвергалось целое число страниц памяти из адресного пространства процесса. Размер страницы является значением конфигурационной переменной PAGESIZE. Отображаемая часть объекта должна начинаться с границы страницы. Длина отображаемой части не обязана быть кратной странице.
Попытки доступа к отображенной памяти, лежащей за текущей границей объекта, приводят к генерации сигнала SIGBUS.
К отображенным страницам могут предоставляться различные виды доступа: на чтение, запись и/или выполнение. При попытке нарушить наложенные ограничения процессу доставляется сигнал SIGSEGV.
Отображение объектов в адресное пространство процессов осуществляется функцией mmap() (см. листинг 5.1).
#include <sys/mman.h> void *mmap (void *addr, size_t len, int prot, int flags, int fildes, off_t off);
Листинг 5.1. Описание функции mmap(). (html, txt)
Отображаемая часть объекта задается файловым дескриптором fildes, смещением off и длиной len. (Вот для чего, заметим в скобках, при открытии объекта в разделяемой памяти функцией shm_open() возвращается файловый дескриптор - чтобы передать его mmap() и отобразить объект в адресное пространство процесса, а вовсе не для того, чтобы применять к нему операции файлового ввода/вывода. В исторически сложившихся реализациях функция mmap() появилась как средство отображения в память обычных файлов, так что с расширением функциональности другие виды отображаемых объектов пришлось стричь под ту же гребенку.)
Идея, лежащая в основе рассматриваемого класса средств, в сущности, весьма проста. Если отобразить объект в адресное пространство процесса, то доступ к объекту можно осуществлять обычными операциями чтения/записи. Если один объект отображен в адресное пространство нескольких процессов, он превращается в средство межпроцессного взаимодействия, так как данные, записанные в объект одним процессом, появляются в адресных пространствах всех участников отображения.
Отображаться могут обычные файлы, а также объекты в разделяемой и типизированной памяти.
Реализация должна обеспечивать, чтобы отображению подвергалось целое число страниц памяти из адресного пространства процесса. Размер страницы является значением конфигурационной переменной PAGESIZE. Отображаемая часть объекта должна начинаться с границы страницы. Длина отображаемой части не обязана быть кратной странице.
Попытки доступа к отображенной памяти, лежащей за текущей границей объекта, приводят к генерации сигнала SIGBUS.
К отображенным страницам могут предоставляться различные виды доступа: на чтение, запись и/или выполнение. При попытке нарушить наложенные ограничения процессу доставляется сигнал SIGSEGV.
Отображение объектов в адресное пространство процессов осуществляется функцией mmap() (см. листинг 5.1).
#include <sys/mman.h> void *mmap (void *addr, size_t len, int prot, int flags, int fildes, off_t off);
Листинг 5.1. Описание функции mmap().
Отображаемая часть объекта задается файловым дескриптором fildes, смещением off и длиной len. (Вот для чего, заметим в скобках, при открытии объекта в разделяемой памяти функцией shm_open() возвращается файловый дескриптор - чтобы передать его mmap() и отобразить объект в адресное пространство процесса, а вовсе не для того, чтобы применять к нему операции файлового ввода/вывода. В исторически сложившихся реализациях функция mmap() появилась как средство отображения в память обычных файлов, так что с расширением функциональности другие виды отображаемых объектов пришлось стричь под ту же гребенку.)
Значения аргументов addr, prot и flags задают характеристики отображения - рекомендуемый адрес в адресном пространстве процесса, запрашиваемые виды доступа к отображенным страницам и управляющие флаги.
Результатом функции mmap() служит выбранный реализацией адрес в адресном пространстве процесса, начиная с которого отображен заданный фрагмент объекта, или, в случае неудачи, значение MAP_FAILED. Если в аргументе flags установлен флаг MAP_FIXED, результирующий адрес должен совпадать со значением addr; предполагается, что приложение располагает знаниями о целевой архитектуре, достаточными для точной спецификации отображения, что, впрочем, отрицательным образом сказывается на мобильности. Если флаг MAP_FIXED не установлен, а значение addr равно NULL, реализация имеет полную свободу в выборе результирующего адреса; подобную комбинацию следует рекомендовать как максимально мобильную.
В значении аргумента flags, помимо MAP_FIXED, могут быть установлены флаги MAP_SHARED (запись в отображенную память изменяет отображаемый объект со всеми вытекающими отсюда последствиями) или MAP_PRIVATE (изменения в отображенной памяти видны только записывающему процессу и не распространяются на отображаемый объект). Стандарт POSIX-2001 не специфицирует, имеет ли место обратная зависимость, то есть видны ли процессу, установившему флаг MAP_PRIVATE, изменения в отображаемом объекте.
Аргумент prot, определяющий разрешенные виды доступа к отображенным страницам, строится как комбинация флагов PROT_READ, PROT_WRITE и PROT_EXEC (разрешен доступ, соответственно, на чтение, запись и/или выполнение). Альтернативный вариант - установить флаг PROT_NONE, запрещающий доступ к странице (из общих соображений выключатель всегда должен быть под рукой). Значение аргумента prot должно быть согласовано с флагами, указанными при открытии объекта и получении дескриптора fildes.
Стандарт POSIX-2001 предусматривает возможность динамической смены разрешенных видов доступа к отображенным страницам посредством вызова функции mprotect() (см.
листинг 5.2).
#include <sys/mman.h> int mprotect (void *addr, size_t len, int prot);
Листинг 5.2. Описание функции mprotect(). (html, txt)
Виды доступа меняются в соответствии со значением аргумента prot для всех страниц, пересекающихся с частью адресного пространства процесса, начинающейся со значения addr (которое должно указывать на границу страницы) и имеющей длину len байт.
Нормальным результатом функции mprotect() (и описываемых далее функций) служит нуль; в случае ошибки возвращается -1.
Для отмены отображений в адресное пространство процессов служит функция munmap() (см. листинг 5.3).
#include <sys/mman.h> int munmap (void *addr, size_t len);
Листинг 5.3. Описание функции munmap(). (html, txt)
Отображения отменяются для страниц, пересекающихся с частью адресного пространства процесса, начинающейся со значения addr (которое должно указывать на границу страницы) и имеющей длину len байт.
Отображение объектов в адресное пространство процессов - универсальное, исключительно мощное средств. В качестве примера применим его для реализации упрощенного аналога служебной программы cat (см. листинг 5.4).
Листинг 5.4. Пример программы, использующей файлы, отображенные в память. (html, txt)
Отметим, что обрабатываемые файлы только открываются, но не читаются, а отображаются в память, откуда и выдаются на стандартный вывод. Роль буфера играют отображенные страницы.
Значения аргументов addr, prot и flags задают характеристики отображения - рекомендуемый адрес в адресном пространстве процесса, запрашиваемые виды доступа к отображенным страницам и управляющие флаги.
Результатом функции mmap() служит выбранный реализацией адрес в адресном пространстве процесса, начиная с которого отображен заданный фрагмент объекта, или, в случае неудачи, значение MAP_FAILED. Если в аргументе flags установлен флаг MAP_FIXED, результирующий адрес должен совпадать со значением addr; предполагается, что приложение располагает знаниями о целевой архитектуре, достаточными для точной спецификации отображения, что, впрочем, отрицательным образом сказывается на мобильности. Если флаг MAP_FIXED не установлен, а значение addr равно NULL, реализация имеет полную свободу в выборе результирующего адреса; подобную комбинацию следует рекомендовать как максимально мобильную.
В значении аргумента flags, помимо MAP_FIXED, могут быть установлены флаги MAP_SHARED (запись в отображенную память изменяет отображаемый объект со всеми вытекающими отсюда последствиями) или MAP_PRIVATE (изменения в отображенной памяти видны только записывающему процессу и не распространяются на отображаемый объект). Стандарт POSIX-2001 не специфицирует, имеет ли место обратная зависимость, то есть видны ли процессу, установившему флаг MAP_PRIVATE, изменения в отображаемом объекте.
Аргумент prot, определяющий разрешенные виды доступа к отображенным страницам, строится как комбинация флагов PROT_READ, PROT_WRITE и PROT_EXEC (разрешен доступ, соответственно, на чтение, запись и/или выполнение). Альтернативный вариант - установить флаг PROT_NONE, запрещающий доступ к странице (из общих соображений выключатель всегда должен быть под рукой). Значение аргумента prot должно быть согласовано с флагами, указанными при открытии объекта и получении дескриптора fildes.
Стандарт POSIX-2001 предусматривает возможность динамической смены разрешенных видов доступа к отображенным страницам посредством вызова функции mprotect() (см.
листинг 5.2).
#include <sys/mman.h> int mprotect (void *addr, size_t len, int prot);
Листинг 5.2. Описание функции mprotect().
Виды доступа меняются в соответствии со значением аргумента prot для всех страниц, пересекающихся с частью адресного пространства процесса, начинающейся со значения addr (которое должно указывать на границу страницы) и имеющей длину len байт.
Нормальным результатом функции mprotect() (и описываемых далее функций) служит нуль; в случае ошибки возвращается -1.
Для отмены отображений в адресное пространство процессов служит функция munmap() (см. листинг 5.3).
#include <sys/mman.h> int munmap (void *addr, size_t len);
Листинг 5.3. Описание функции munmap().
Отображения отменяются для страниц, пересекающихся с частью адресного пространства процесса, начинающейся со значения addr (которое должно указывать на границу страницы) и имеющей длину len байт.
Отображение объектов в адресное пространство процессов - универсальное, исключительно мощное средств. В качестве примера применим его для реализации упрощенного аналога служебной программы cat (см. листинг 5.4).
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа выдает на стандартный вывод файлы,*/ /* имена которых заданы в командной строке */ /* * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/mman.h>
int main (int argc, char *argv []) { struct stat sbuf; /* Структура для получения информации */ /* о файле */ int fd; /* Дескриптор обрабатываемого файла */ void *maddr; /* Адрес отображенного в память файла */ int i;
for (i = 1; i < argc; i++) { if ((fd = open (argv [i], O_RDONLY)) < 0) { perror ("OPEN"); continue; } if (fstat (fd, &sbuf) != 0) { perror ("FSTAT"); close (fd); continue; } if ((maddr = mmap (NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED){ perror ("MMAP"); close (fd); continue; } if (write (STDOUT_FILENO, maddr, sbuf.st_size) != sbuf.st_size) { perror ("WRITE"); } if (munmap (maddr, sbuf.st_size) != 0) { perror ("MUNMAP"); } close (fd); } /* for */
return 0; }
Листинг 5.4. Пример программы, использующей файлы, отображенные в память.
Отметим, что обрабатываемые файлы только открываются, но не читаются, а отображаются в память, откуда и выдаются на стандартный вывод. Роль буфера играют отображенные страницы.
При работе с объектами в памяти полезны функции truncate() и, особенно, ftruncate() (см. листинг 5.5), позволяющие установить размер объекта равным заданной величине length. Напомним, что попытка записи за пределы отображенного объекта не только не расширит его, но, весьма вероятно, приведет к доставке процессу сигнала SIGBUS.
#include <unistd.h>
int truncate (const char *path, off_t length);
int ftruncate (int fildes, off_t length);
Листинг 5.5. Описание функций truncate() и ftruncate.
Функции truncate() и ftruncate() применимы к обычным файлам, которые в первом случае задаются маршрутным именем, а во втором - открытым дескриптором. Кроме того, функция ftruncate() применима к объектам в разделяемой памяти (и это единственная узаконенная стандартом POSIX-2001 файловая операция над дескриптором подобного объекта). Других способов изменить размер объекта в разделяемой памяти стандарт не предусматривает.
Применение объектов в разделяемой памяти в сочетании с функциями mmap() и ftruncate() иллюстрируется модифицированным вариантом двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод (см. листинги 5.6, 5.7, 5.8). Как и для исходного варианта, предполагается, что заголовочный файл называется "g_shm.h", а файл с образом процесса, запускаемого посредством execl(), - "g_r_shm".
#ifndef g_SHM #define g_SHM
/* Имя объекта в разделяемой памяти */ #define O_SHM_NAME "/g_o.shm"
/* Номер сигнала для синхронизации /* доступа к разделяемому сегменту */ #define SIG_SHM SIGALRM
#endif
Листинг 5.6. Заголовочный файл "g_shm.h" программы, копирующей строки со стандартного ввода на стандартный вывод. /* * * * * * * * * * * * * * * * * * * * * */ /* Программа, состоящая из двух процессов, */ /* копирует строки со стандартного ввода на*/ /* стандартный вывод, используя в качестве */ /* буфера разделяемый сегмент, отображенный*/ /* в адресные пространства процессов. */ /* Для синхронизации доступа к разделяемому*/ /* сегменту используется сигнал SIGALRM */ /* * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdio.h> #include <signal.h> #include <sys/mman.h> #include <fcntl.h> #include <limits.h> #include <sys/wait.h> #include <assert.h>
#include "g_shm.h"
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Создание разделяемого сегмента памяти, */ /* отображение его в адресное пространство процесса, */ /* чтение строк со стандартного ввода в сегмент */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта в разделяемой памяти */ void *addr_shm; /* Адрес отображенного в память объекта */ sigset_t smask; /* Маска блокируемых сигналов */ siginfo_t sinfo;/* Структура для получения данных о сигнале */ /* Длительность ожидания прихода сигнала */ struct timespec stmspc = {10, 0}; pid_t cpid; /* Идентификатор порожденного процесса */
/* Создадим разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDWR | O_CREAT, 0777)) < 0) { perror ("SHM_CREAT"); return (1); }
/* Сделаем размер созданного объекта равным LINE_MAX */ if (ftruncate (fd_shm, LINE_MAX) != 0) { perror ("FTRUNCATE"); return (2); }
/* Отобразим сегмент в память */ if ((addr_shm = mmap (NULL, LINE_MAX, PROT_READ | PROT_WRITE, MAP_SHARED, fd_shm, 0)) == MAP_FAILED) { perror ("MMAP-P"); return (3); }
/* Сформируем маску сигналов (блокируем SIG_SHM) */ (void) sigemptyset (&smask); (void) sigaddset (&smask, SIG_SHM); (void) sigprocmask (SIG_BLOCK, &smask, (sigset_t *) NULL);
/* Подготовительная работа закончена */
switch (cpid = fork ()) { case -1: perror ("FORK"); return (4); case 0: /* Чтение из объекта и выдачу на стандартный*/ /* вывод реализуем в порожденном процессе*/ if (execl ("./g_r_shm", "g_r_shm", ( char *) NULL) < 0) { perror ("EXECL"); return (5); } }
/* Чтение строк со стандартного ввода в объект */ /* возложим на родительский процесс. */ /* В начальный момент объект в разделяемой памяти */ /* доступен для записи*/ while (fgets (addr_shm, LINE_MAX, stdin) != NULL) { /* Сообщим порожденному процессу, */ /* что в объект в разделяемой памяти */ /* помещена очередная строка */ assert (kill (cpid, SIG_SHM) == 0); /* Дождемся, когда в объект можно будет прочитать*/ /* следующую строку */ if (sigtimedwait (&smask, &sinfo, &stmspc) != SIG_SHM) { break; } }
/* Порожденный процесс должен завершиться*/ /* по контролю времени ожидания*/ (void) wait (NULL);
if (shm_unlink (O_SHM_NAME) != 0) { perror ("SHM_UNLINK"); return (6); }
return (0); }
Листинг 5.7. Исходный текст начального процесса двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод. /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Процесс берет строки из объекта в разделяемой памяти*/ /* и выдает их на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <unistd.h> #include <stdio.h> #include <signal.h> #include <sys/mman.h> #include <fcntl.h> #include <limits.h> #include <assert.h>
#include "g_shm.h"
/* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Открытие разделяемого сегмента памяти, */ /* отображение его в адресное пространство процесса, */ /* чтение из сегмента и выдача строк на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта в разделяемой */ /* памяти */ void *addr_shm; /* Адрес отображенного в память */ /* объекта */ sigset_t smask; /* Маска ожидаемых сигналов */ siginfo_t sinfo; /* Структура для получения данных*/ /* о сигнале */ /* Длительность ожидания строки */ struct timespec stmspc = {10, 0}; pid_t ppid; /* Идентификатор родительского процесса */
/* Откроем разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDONLY, 0777)) < 0) { perror ("SHM_OPEN"); return (1); }
/* Отобразим сегмент в память */ if ((addr_shm = mmap (NULL, LINE_MAX, PROT_READ, MAP_SHARED, fd_shm, 0)) == MAP_FAILED) { perror ("MMAP-C"); return (2); }
/* Запомним идентификатор родительского процесса */ ppid = getppid ();
/* Сформируем маску ожидаемых сигналов (SIG_SHM) */ (void) sigemptyset (&smask); (void) sigaddset (&smask, SIG_SHM);
/* Подготовительная работа закончена */ fputs ("Вводите строки\n", stdout);
while (sigtimedwait (&smask, &sinfo, &stmspc) == SIG_SHM) { /* Дождались, когда в объекте появилась строка.*/ /* Выдадим ее на стандартный вывод*/ assert (fputs ("Вы ввели: ", stdout) != EOF); assert (fputs (addr_shm, stdout) != EOF); /* Сообщим родительскому процессу, */ /* что данные из объекта извлечены */ assert (kill (ppid, SIG_SHM) == 0); }
return 0; }
Листинг 5.8. Исходный текст порождаемого процесса (файл g_r_shm.c) двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод.
В модифицированном варианте сигналы (не имеет значения - обычные или реального времени) применяются только как средство синхронизации. Для выявления конца файла задействован механизм контроля длительности ожидания. Читателю предлагается поварьировать эти длительности, немного задержаться с вводом строк с терминала и проанализировать поведение программы.
Отметим, что при использовании объектов в разделяемой памяти, отображенных в адресные пространства процессов, передачи данных как таковой не требуется. В приведенной программе начальный процесс читает строки в разделяемый объект, а порожденный берет их оттуда же и выводит без каких-либо дополнительных копирований. Флаг MAP_SHARED в вызове mmap() обеспечивает доступность данных, записанных в объект одним процессом, всем другим процессам-читателям.
Еще одна возможность, полезная в связи с отображением объектов в адресное пространство процессов, - синхронизация (согласование состояния) оперативной и долговременной памяти. Это возможность реализует функция msync() (см. листинг 5.9).
#include <sys/mman.h> int msync (void *addr, size_t len, int flags);
Листинг 5.9. Описание функции msync().
Функция msync() записывает в долговременную память все измененные страницы объекта в памяти, пересекающиеся с фрагментом адресного пространства процесса, начинающимся с адреса addr (это должна быть граница страницы) и имеющим длину len байт.
Если объектом является файл, отображенный в память (естественно, без флага MAP_PRIVATE), то вызов msync() гарантирует завершение всех операций записи с обеспечением целостности данных синхронизированного ввода/вывода (см.
курс [1]). Эффект от применения msync() к объектам в разделяемой и типизированной памяти не специфицирован.
Аргумент flags определяет характер действий при записи измененных страниц. Флаг MS_ASYNC означает асинхронную, а MS_SYNC - синхронную запись. Кроме того, флаг MS_INVALIDATE предписывает выполнить инициализацию кэша данных (приведение его в соответствие с содержимым долговременной памяти).
Отметим, что функция msync() обязана присутствовать только на системах, поддерживающих две необязательные возможности стандарта POSIX-2001 - файлы, отображенные в память, и синхронизированный ввод/вывод. В этом смысле ее можно считать дважды необязательной.
Вообще говоря, вызов msync() - не единственная причина, способная инициировать запись в долговременную память; подобная запись может стать следствием нормальной системной активности.
Проиллюстрируем работу с файлами, отображенными в память, еще одним примером, реализующим некоторые функции систем управления базами данных в памяти (см. листинг 5.10).
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует некоторые функции систем управления базами*/ /* данных в памяти, следуя принципу неуничтожения информации */ /* (изменения записываются не в сам объет, а в его новую версию) */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <sys/stat.h> #include s<ys/mman.h> #include <assert.h>
/* Размер страницы */ static int pgsz;
/* Структура для получения */ /* информации об исходном файле*/ static struct stat sbuf_src;
/* Адрес отображенного в память исходного файла*/ static char *saddr;
/* Признак того, что выполнялась функция */ /* обработки сигнала SIGSEGV */ static char sigsegv_catching;
/* * * * * * * * * * * * * * * * * * * * */ /* Функция обработки сигнала SIGSEGV. */ /* Предполагается, что этот сигнал */ /* генерируется при попытке записи в */ /* страницы, доступные только на чтение. */ /* Функция добавляет разрешение на запись*/ /* * * * * * * * * * * * * * * * * * * * */ static void sigsegv_sigaction (int sig, siginfo_t *sig_info, void *addr) { void *page_addr; /* Адрес страницы, в которую*/ /*пытались писать*/
page_addr = (char *) sig_info->si_addr - (off_t) ((char *) sig_info->si_addr - saddr) % pgsz; assert (mprotect (page_addr, pgsz, PROT_READ | PROT_WRITE) == 0); sigsegv_catching = 1; }
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Установка способа обработки сигнала SIGSEGV */ /* * * * * * * * * * * * * * * * * * * * * * * */ static int sigsegv_set_proc (void (*sigsegv_actn) (int, siginfo_t *, void *), struct sigaction *old_sigsegv_sact) { struct sigaction sact;
(void) sigemptyset (&sact.sa_mask); sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = sigsegv_actn; if (sigaction (SIGSEGV, &sact, old_sigsegv_sact) != 0) { perror ("SIGACTION"); return (-1); }
return 0; }
/* * * * * * * * * * * * * * * * * * * * * */ /* Функция начала работы с исходным файлом.*/ /* Нормальный результат равен нулю */ /* * * * * * * * * * * * * * * * * * * * * */ static int init_src (char *name_src) { int fd_src; /* Дескриптор исходного файла */
/* Откроем исходный файл и отобразим его в память */ if ((fd_src = open (name_src, O_RDONLY)) < 0) { perror ("OPEN-SRC"); return (-1); } if (fstat (fd_src, &sbuf_src) != 0) { perror ("FSTAT"); return (-1); } if ((saddr = (char *) mmap (NULL, sbuf_src.st_size, PROT_READ, MAP_PRIVATE, fd_src, 0)) == MAP_FAILED) { perror ("MMAP"); return (-1); }
return 0; } /* * * * * * * * * * * * * * * * * * */ /* Опрос конфигурационных параметров */ /* * * * * * * * * * * * * * * * * * */ static int init_conf_params (void) { if ((pgsz = sysconf (_SC_PAGESIZE)) == -1) { perror ("SYSCONF"); return (-1); }
return 0; }
/* * * * * * * * * * * */ /* Общая инициализация */ /* * * * * * * * * * * */ static int init_all (char *name_src) { if ((init_src (name_src) != 0) || (sigsegv_set_proc (sigsegv_sigaction, (struct sigaction *) NULL) != 0) || (init_conf_params () != 0)) { return (-1); }
return 0; }
/* * * * * * * * * * * * * * * * * */ /* Запись в файл измененных страниц*/ /* * * * * * * * * * * * * * * * * */ static int write_dlt (char *name_dlt) { int fd_dlt; /* Дескриптор файла изменений*/ volatile char tmp; /* Значение пробного байта*/ off_t pos;/* Позиция в отображенной памяти*/
if ((fd_dlt = open (name_dlt, O_CREAT | O_WRONLY | O_TRUNC, 0777)) < 0) { perror ("OPEN-DLT"); return (-1); }
/* Измененные страницы выявим путем пробных записей. */ for (pos = 0; pos < sbuf_src.st_size; pos += pgsz) { tmp = saddr [pos]; sigsegv_catching = 0; saddr [pos] = tmp; if (sigsegv_catching == 0) { /* В страницу удалось записать */ /* без генерации сигнала SIGSEGV.*/ /* Значит, она была доступна на запись.*/ /* Следовательно, ее модифицировали.*/ /* Запишем ее в файл изменений */ (void) lseek (fd_dlt, pos, SEEK_SET); assert (write (fd_dlt, saddr + pos, pgsz) == pgsz); } /* Уберем добавленное разрешение на запись */ assert (mprotect (saddr + pos, pgsz, PROT_READ) == 0); } /* for */
return (close (fd_dlt)); }
/* * * * * * * * * * * * * */ /* Функция main() вызывает */ /* описанные выше функции */ /* и обращается на запись */ /* к отображенной памяти */ /* * * * * * * * * * * * * */ int main (int argc, char *argv []) { if (argc != 3) { fprintf (stderr, "Использование: %s исходный_файл " "файл_изменений\n", argv [0]); return (-1); }
if (init_all (argv [1]) != 0) { fprintf (stderr, "Ошибка инициализации\n"); return (-1); }
printf ("Размер страницы: %d\n" "Адрес отображенного в память исходного файла: %p\n" "Длина отображенного в память исходного файла: %ld\n", pgsz, saddr, sbuf_src.st_size);
saddr [0] = '*';
return (write_dlt (argv [2])); }
Листинг 5.10. Пример программы, использующей файлы, отображенные в память.
Приведенная программа следует фундаментальному принципу неуничтожения информации. При модификациях порождаются новые версии объектов; первоначальные объекты остаются неизменными. Подобный подход особенно полезен, когда СУБД является технологическим элементом инструментальной среды разработки программ, поскольку историю создания и модификации программных систем обязательно нужно сохранять.
Обратим внимание на изменение разрешенных видов доступа к отображенным страницам, реализуемое обращениями к функции mprotect().
Первоначально отображенная память доступна только на чтение. При попытке записи в отображенную память генерируется сигнал SIGSEGV, и функция его обработки добавляет доступ на запись к соответствующей странице. В результате появляется возможность отслеживания изменений и сохранения в последующем только измененных страниц, так что дисковое пространство на версии объектов будет расходоваться экономно.
Выбранный способ выявления измененных страниц путем пробных записей может показаться несколько вычурным (в функции обработки сигнала SIGSEGV можно было бы формировать список адресов страниц, в которые производится запись, и тогда в функции фиксации изменений достаточно просто пройти по этому списку), но он вытекает из еще одного важного принципа - в максимальной степени упрощать и ускорять обычную работу, даже за счет возможного усложнения и замедления редко исполняемых фрагментов.
К сожалению, приведенная программа не вполне соответствует стандарту POSIX-2001 в части обработки сигналов. По стандарту поведение процесса после нормального выхода из функций обработки сигналов SIGBUS, SIGFPE, SIGILL и SIGSEGV не определено, если только последние не были сгенерированы вызовами kill(), sigqueue() или raise(). (Заметим попутно, что и игнорирование перечисленных сигналов приводит к неопределенному поведению.) Строго говоря, и вызов mprotect() из функций обработки сигналов не считается безопасным. В общем, функция sigsegv_sigaction() - сама нестандартность и немобильность, и это, конечно, плохо, но то, что вся нестандартность и немобильность программы сосредоточена в одной небольшой функции, безусловно, хорошо.
size_t len, int prot, int
#include <sys/mman.h> void *mmap (void *addr, size_t len, int prot, int flags, int fildes, off_t off); |
Листинг 5.1. Описание функции mmap(). |
Закрыть окно |
#include <sys/mman.h> int mprotect (void *addr, size_t len, int prot); |
Листинг 5.2. Описание функции mprotect(). |
Закрыть окно |
#include <sys/mman.h> int munmap (void *addr, size_t len); |
Листинг 5.3. Описание функции munmap(). |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * * * */ /* Программа выдает на стандартный вывод файлы,*/ /* имена которых заданы в командной строке */ /* * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/mman.h> int main (int argc, char *argv []) { struct stat sbuf; /* Структура для получения информации */ /* о файле */ int fd; /* Дескриптор обрабатываемого файла */ void *maddr; /* Адрес отображенного в память файла */ int i; for (i = 1; i < argc; i++) { if ((fd = open (argv [i], O_RDONLY)) < 0) { perror ("OPEN"); continue; } if (fstat (fd, &sbuf) != 0) { perror ("FSTAT"); close (fd); continue; } if ((maddr = mmap (NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED){ perror ("MMAP"); close (fd); continue; } if (write (STDOUT_FILENO, maddr, sbuf.st_size) != sbuf.st_size) { perror ("WRITE"); } if (munmap (maddr, sbuf.st_size) != 0) { perror ("MUNMAP"); } close (fd); } /* for */ return 0; } |
Листинг 5.4. Пример программы, использующей файлы, отображенные в память. |
Закрыть окно |
#include <unistd.h> int truncate (const char *path, off_t length); int ftruncate ( int fildes, off_t length); |
Листинг 5.5. Описание функций truncate() и ftruncate. |
Закрыть окно |
#ifndef g_SHM #define g_SHM /* Имя объекта в разделяемой памяти */ #define O_SHM_NAME "/g_o.shm" /* Номер сигнала для синхронизации /* доступа к разделяемому сегменту */ #define SIG_SHM SIGALRM #endif |
Листинг 5.6. Заголовочный файл "g_shm.h" программы, копирующей строки со стандартного ввода на стандартный вывод. |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * */ /* Программа, состоящая из двух процессов, */ /* копирует строки со стандартного ввода на*/ /* стандартный вывод, используя в качестве */ /* буфера разделяемый сегмент, отображенный*/ /* в адресные пространства процессов. */ /* Для синхронизации доступа к разделяемому*/ /* сегменту используется сигнал SIGALRM */ /* * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <signal.h> #include <sys/mman.h> #include <fcntl.h> #include <limits.h> #include <sys/wait.h> #include <assert.h> #include "g_shm.h" /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Создание разделяемого сегмента памяти, */ /* отображение его в адресное пространство процесса, */ /* чтение строк со стандартного ввода в сегмент */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта в разделяемой памяти */ void *addr_shm; /* Адрес отображенного в память объекта */ sigset_t smask; /* Маска блокируемых сигналов */ siginfo_t sinfo;/* Структура для получения данных о сигнале */ /* Длительность ожидания прихода сигнала */ struct timespec stmspc = {10, 0}; pid_t cpid; /* Идентификатор порожденного процесса */ /* Создадим разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDWR | O_CREAT, 0777)) < 0) { perror ("SHM_CREAT"); return (1); } /* Сделаем размер созданного объекта равным LINE_MAX */ if (ftruncate (fd_shm, LINE_MAX) != 0) { perror ("FTRUNCATE"); return (2); } /* Отобразим сегмент в память */ if ((addr_shm = mmap (NULL, LINE_MAX, PROT_READ | PROT_WRITE, MAP_SHARED, fd_shm, 0)) == MAP_FAILED) { perror ("MMAP-P"); return (3); } /* Сформируем маску сигналов (блокируем SIG_SHM) */ (void) sigemptyset (&smask); (void) sigaddset (&smask, SIG_SHM); (void) sigprocmask (SIG_BLOCK, &smask, (sigset_t *) NULL); /* Подготовительная работа закончена */ switch (cpid = fork ()) { case -1: perror ("FORK"); return (4); case 0: /* Чтение из объекта и выдачу на стандартный*/ /* вывод реализуем в порожденном процессе*/ if (execl ("./g_r_shm", "g_r_shm", ( char *) NULL) < 0) { perror ("EXECL"); return (5); } } /* Чтение строк со стандартного ввода в объект */ /* возложим на родительский процесс. */ /* В начальный момент объект в разделяемой памяти */ /* доступен для записи*/ while (fgets (addr_shm, LINE_MAX, stdin) != NULL) { /* Сообщим порожденному процессу, */ /* что в объект в разделяемой памяти */ /* помещена очередная строка */ assert (kill (cpid, SIG_SHM) == 0); /* Дождемся, когда в объект можно будет прочитать*/ /* следующую строку */ if (sigtimedwait (&smask, &sinfo, &stmspc) != SIG_SHM) { break; } } /* Порожденный процесс должен завершиться*/ /* по контролю времени ожидания*/ (void) wait (NULL); if (shm_unlink (O_SHM_NAME) != 0) { perror ("SHM_UNLINK"); return (6); } return (0); } |
Листинг 5.7. Исходный текст начального процесса двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод. |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Процесс берет строки из объекта в разделяемой памяти*/ /* и выдает их на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <signal.h> #include <sys/mman.h> #include <fcntl.h> #include <limits.h> #include <assert.h> #include "g_shm.h" /* * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Открытие разделяемого сегмента памяти, */ /* отображение его в адресное пространство процесса, */ /* чтение из сегмента и выдача строк на стандартный вывод */ /* * * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { int fd_shm; /* Дескриптор объекта в разделяемой */ /* памяти */ void *addr_shm; /* Адрес отображенного в память */ /* объекта */ sigset_t smask; /* Маска ожидаемых сигналов */ siginfo_t sinfo; /* Структура для получения данных*/ /* о сигнале */ /* Длительность ожидания строки */ struct timespec stmspc = {10, 0}; pid_t ppid; /* Идентификатор родительского процесса */ /* Откроем разделяемый сегмент памяти */ if ((fd_shm = shm_open (O_SHM_NAME, O_RDONLY, 0777)) < 0) { perror ("SHM_OPEN"); return (1); } /* Отобразим сегмент в память */ if ((addr_shm = mmap (NULL, LINE_MAX, PROT_READ, MAP_SHARED, fd_shm, 0)) == MAP_FAILED) { perror ("MMAP-C"); return (2); } /* Запомним идентификатор родительского процесса */ ppid = getppid (); /* Сформируем маску ожидаемых сигналов (SIG_SHM) */ (void) sigemptyset (&smask); (void) sigaddset (&smask, SIG_SHM); /* Подготовительная работа закончена */ fputs ("Вводите строки\n", stdout); while (sigtimedwait (&smask, &sinfo, &stmspc) == SIG_SHM) { /* Дождались, когда в объекте появилась строка.*/ /* Выдадим ее на стандартный вывод*/ assert (fputs ("Вы ввели: ", stdout) != EOF); assert (fputs (addr_shm, stdout) != EOF); /* Сообщим родительскому процессу, */ /* что данные из объекта извлечены */ assert (kill (ppid, SIG_SHM) == 0); } return 0; } |
Листинг 5.8. Исходный текст порождаемого процесса (файл g_r_shm.c) двухпроцессной программы, копирующей строки со стандартного ввода на стандартный вывод. |
Закрыть окно |
#include <sys/mman.h> int msync (void *addr, size_t len, int flags); |
Листинг 5.9. Описание функции msync(). |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа реализует некоторые функции систем управления базами*/ /* данных в памяти, следуя принципу неуничтожения информации */ /* (изменения записываются не в сам объет, а в его новую версию) */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <sys/stat.h> #include s<ys/mman.h> #include <assert.h> /* Размер страницы */ static int pgsz; /* Структура для получения */ /* информации об исходном файле*/ static struct stat sbuf_src; /* Адрес отображенного в память исходного файла*/ static char *saddr; /* Признак того, что выполнялась функция */ /* обработки сигнала SIGSEGV */ static char sigsegv_catching; /* * * * * * * * * * * * * * * * * * * * */ /* Функция обработки сигнала SIGSEGV. */ /* Предполагается, что этот сигнал */ /* генерируется при попытке записи в */ /* страницы, доступные только на чтение. */ /* Функция добавляет разрешение на запись*/ /* * * * * * * * * * * * * * * * * * * * */ static void sigsegv_sigaction (int sig, siginfo_t *sig_info, void *addr) { void *page_addr; /* Адрес страницы, в которую*/ /*пытались писать*/ page_addr = (char *) sig_info->si_addr - (off_t) ((char *) sig_info->si_addr - saddr) % pgsz; assert (mprotect (page_addr, pgsz, PROT_READ | PROT_WRITE) == 0); sigsegv_catching = 1; } /* * * * * * * * * * * * * * * * * * * * * * * */ /* Установка способа обработки сигнала SIGSEGV */ /* * * * * * * * * * * * * * * * * * * * * * * */ static int sigsegv_set_proc (void (*sigsegv_actn) (int, siginfo_t *, void *), struct sigaction *old_sigsegv_sact) { struct sigaction sact; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = SA_SIGINFO; sact.sa_sigaction = sigsegv_actn; if (sigaction (SIGSEGV, &sact, old_sigsegv_sact) != 0) { perror ("SIGACTION"); return (-1); } return 0; } /* * * * * * * * * * * * * * * * * * * * * */ /* Функция начала работы с исходным файлом.*/ /* Нормальный результат равен нулю */ /* * * * * * * * * * * * * * * * * * * * * */ static int init_src (char *name_src) { int fd_src; /* Дескриптор исходного файла */ /* Откроем исходный файл и отобразим его в память */ if ((fd_src = open (name_src, O_RDONLY)) < 0) { perror ("OPEN-SRC"); return (-1); } if (fstat (fd_src, &sbuf_src) != 0) { perror ("FSTAT"); return (-1); } if ((saddr = (char *) mmap (NULL, sbuf_src.st_size, PROT_READ, MAP_PRIVATE, fd_src, 0)) == MAP_FAILED) { perror ("MMAP"); return (-1); } return 0; } /* * * * * * * * * * * * * * * * * * */ /* Опрос конфигурационных параметров */ /* * * * * * * * * * * * * * * * * * */ static int init_conf_params (void) { if ((pgsz = sysconf (_SC_PAGESIZE)) == -1) { perror ("SYSCONF"); return (-1); } return 0; } /* * * * * * * * * * * */ /* Общая инициализация */ /* * * * * * * * * * * */ static int init_all (char *name_src) { if ((init_src (name_src) != 0) || (sigsegv_set_proc (sigsegv_sigaction, (struct sigaction *) NULL) != 0) || (init_conf_params () != 0)) { return (-1); } return 0; } /* * * * * * * * * * * * * * * * * */ /* Запись в файл измененных страниц*/ /* * * * * * * * * * * * * * * * * */ static int write_dlt (char *name_dlt) { int fd_dlt; /* Дескриптор файла изменений*/ volatile char tmp; /* Значение пробного байта*/ off_t pos;/* Позиция в отображенной памяти*/ if ((fd_dlt = open (name_dlt, O_CREAT | O_WRONLY | O_TRUNC, 0777)) < 0) { perror ("OPEN-DLT"); return (-1); } /* Измененные страницы выявим путем пробных записей. */ for (pos = 0; pos < sbuf_src.st_size; pos += pgsz) { tmp = saddr [pos]; sigsegv_catching = 0; saddr [pos] = tmp; if (sigsegv_catching == 0) { /* В страницу удалось записать */ /* без генерации сигнала SIGSEGV.*/ /* Значит, она была доступна на запись.*/ /* Следовательно, ее модифицировали.*/ /* Запишем ее в файл изменений */ (void) lseek (fd_dlt, pos, SEEK_SET); assert (write (fd_dlt, saddr + pos, pgsz) == pgsz); } /* Уберем добавленное разрешение на запись */ assert (mprotect (saddr + pos, pgsz, PROT_READ) == 0); } /* for */ return (close (fd_dlt)); } /* * * * * * * * * * * * * */ /* Функция main() вызывает */ /* описанные выше функции */ /* и обращается на запись */ /* к отображенной памяти */ /* * * * * * * * * * * * * */ int main (int argc, char *argv []) { if (argc != 3) { fprintf (stderr, "Использование: %s исходный_файл " "файл_изменений\n", argv [0]); return (-1); } if (init_all (argv [1]) != 0) { fprintf (stderr, "Ошибка инициализации\n"); return (-1); } printf ("Размер страницы: %d\n" "Адрес отображенного в память исходного файла: %p\n" "Длина отображенного в память исходного файла: %ld\n", pgsz, saddr, sbuf_src.st_size); saddr [0] = '*'; return (write_dlt (argv [2])); } |
Листинг 5.10. Пример программы, использующей файлы, отображенные в память. |
Закрыть окно |
#include <sys/mman.h> int posix_typed_mem_open (const char *name, int oflag, int tflag); |
Листинг 5.11. Описание функции posix_typed_mem_open(). |
Закрыть окно |
#include <sys/mman.h> int posix_typed_mem_get_info ( int fildes, struct posix_typed_mem_info *info); |
Листинг 5.12. Описание функции posix_typed_mem_get_info(). |
Закрыть окно |
#include <sys/mman.h> int posix_mem_offset ( const void * restrict addr, size_t len, off_t *restrict off, size_t *restrict contig_len, int *restrict fildes); |
Листинг 5.13. Описание функции posix_mem_offset(). |
Закрыть окно |
#include <sys/mman.h> int mlock (const void *addr, size_t len); |
Листинг 5.14. Описание функции mlock(). |
Закрыть окно |
#include <sys/mman.h> int munlock (const void *addr, size_t len); |
Листинг 5.15. Описание функции munlock(). |
Закрыть окно |
#include <sys/mman.h> int mlockall (int flags); int munlockall (void); |
Листинг 5.16. Описание функций mlockall() и munlockall(). |
Закрыть окно |
/* * * * * * * * * * * * * * * * * * * * * * */ /* Программа демонстрирует удержание в памяти*/ /* всего адресного пространства процесса, */ /* в том числе стека с учетом возможного роста*/ /* * * * * * * * * * * * * * * * * * * * * * */ #include <stdio.h> #include <sys/mman.h> #define LOCKED_STACK_SIZE 048576 /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Следующая функция нужна, чтобы вызвать рост стека до*/ /* требуемого размера. На всякий случай примем меры для*/ /* защиты от слишком умного оптимизирующего компилятора*/ /* * * * * * * * * * * * * * * * * * * * * * * * * * * */ static int dummy_func (void) { char dummy_arr [LOCKED_STACK_SIZE]; int i; int res = 0; dummy_arr [0] = 0; for (i = 1; i < LOCKED_STACK_SIZE; i++) { dummy_arr [i] = dummy_arr [i - 1] + i; } for (i = 0; i < LOCKED_STACK_SIZE; i++) { res += dummy_arr [i]; } return (res); } /* * * * * * * * * * * * * * * * * * * * * * * * * */ /* Функция main() вызывает вспомогательную функцию */ /* * * * * * * * * * * * * * * * * * * * * * * * * */ int main (void) { if (mlockall (MCL_CURRENT | MCL_FUTURE) != 0) { perror ("MLOCKALL"); return (1); } fprintf (stderr, "Результат вспомогательной функции:" "%d\n", dummy_func ()); return 0; } |
Листинг 5.17. Пример программы, удерживающей в памяти растущий стек. |
Закрыть окно |
Средства удержания процессов в памяти
Современные системы имеют сложную аппаратную организацию. Отметим следующие особенности таких систем, существенные для приложений реального времени.
поддержка страничной виртуальной памяти с подкачкой по запросу;применение нескольких уровней кэширования;наличие нескольких видов физической памяти с разными характеристиками (быстрой статической, более медленной, но и более дешевой динамической и т.п.).
Подкачка страниц, непопадание в кэш, обращение к более медленной памяти вносят элементы недетерминированности в работу приложений. Чтобы по возможности избавиться от них, в стандарт POSIX введены средства удержания в физической памяти страниц из адресного пространства процессов. Они не ликвидируют недетерминированность полностью (например, ничего не говорится о длительности трансляции логического адреса в физический), но позволяют обеспечить разумную верхнюю границу времени доступа к командам и данным процессов.
Если реализация не поддерживает виртуальной памяти, то описываемые далее функции могут просто ничего не делать (кроме, быть может, проверки корректности аргументов).
Для удержания в физической памяти группы страниц из адресного пространства процесса служит функция mlock() (см. листинг 5.14), воспользоваться которой может только процесс, обладающий соответствующими привилегиями (см. курс [1], где раскрывается понятие "соответствующие привилегии").
#include <sys/mman.h> int mlock (const void *addr, size_t len);
Листинг 5.14. Описание функции mlock(). (html, txt)
После успешного завершения вызова mlock() резидентными в памяти станут все страницы, пересекающиеся с частью адресного пространства процесса, начинающейся со значения addr и имеющей длину len байт. Реализация может требовать, чтобы значение addr указывало на границу страницы.
Если некоторая страница несколько раз отображена в адресные пространства процессов, то для ее удержания в памяти достаточно позаботиться о каком-либо одном отображении.
Удержание отменяется после вызовов fork() и exec(), а также ликвидации по какой-либо причине соответствующей части адресного пространства процесса (отмена отображения, завершение процесса и т.п.).
Явная отмена удержания группы страниц реализуется функцией munlock() (см. листинг 5.15).
#include <sys/mman.h> int munlock (const void *addr, size_t len);
Листинг 5.15. Описание функции munlock(). (html, txt)
Более точно, munlock() разрешает больше не удерживать в памяти заданные страницы из адресного пространства вызывающего процесса. Если страница была отображена несколько раз и удерживалась в памяти несколькими вызовами mlock(), то одного обращения к munlock() для отмены удержания недостаточно.
Если нужно удерживать в памяти все адресное пространство процесса (что имеет место для большинства приложений реального времени), целесообразно воспользоваться функциями mlockall() и munlockall() (см. листинг 5.16).
#include <sys/mman.h>
int mlockall (int flags);
int munlockall (void);
Листинг 5.16. Описание функций mlockall() и munlockall(). (html, txt)
Поскольку адресное пространство процесса при выполнении, вообще говоря, меняется, необходимо уточнить, что именно должно удерживаться в памяти. Для этого служит аргумент flags функции mlockall(), в значении которого могут фигурировать следующие флаги.
MCL_CURRENT
Удерживать в памяти страницы, присутствующие в адресном пространстве процесса на момент вызова.
MCL_FUTURE
Удерживать в памяти страницы, которые будут отображены в адресное пространство процесса после вызова.
Очевидно, установка обоих флагов позволяет удерживать в памяти и текущие, и будущие страницы.
Если установлен флаг MCL_FUTURE, то со временем общий объем удерживаемых страниц может превысить размеры физической памяти. В таком случае поведение операционной системы зависит от реализации.
Функция munlockall() отменяет удержание для всех страниц, присутствующих в адресном пространстве процесса на момент вызова или отображенных позднее, если только при вызове mlockall() не был установлен флаг MCL_FUTURE.
Определенную проблему составляет удержание в памяти страниц стека, рост которого не всегда можно точно оценить. (А стек, конечно, нуждается в удержании, иначе, например, функция обработки сигнала может выполниться с неожиданной задержкой.) Одно из возможных решений этой проблемы состоит в установке флага MCL_FUTURE при обращении к mlockall() и последующем вызове вспомогательной функции, в которой продекларирован автоматический массив достаточного размера (см.листинг 5.17).
Листинг 5.17. Пример программы, удерживающей в памяти растущий стек. (html, txt)
При написании такого рода программ следует позаботиться о защите от слишком интеллектуального оптимизирующего компилятора, который не только способен избавиться от неиспользуемых локальных переменных, но и может не включать в выходной код ненужные функции.