Средства
К числу наиболее простых и в то же время самых употребительных средств и одноименной служебной программой, а вторые - функцией pipe(). Именованным
Взаимодействие между процессами через read() и write(). Примером подобного взаимодействия служит программа, показанная в листинге 8.1.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через канал. */
/* Используются функции ввода/вывода нижнего уровня */
#define MY_PROMPT "Вводите строки\n"
#define MY_MSG "Вы ввели: "
int main (void) {
int fd [2];
char buf [1];
int new_line = 1; /* Признак того, что надо выдать сообщение MY_MSG */
/* перед отображением очередной строки */
/* Создадим безымянный канал */
if (pipe (fd) < 0) {
perror ("PIPE");
exit (1);
}
switch (fork ()) {
case -1:
perror ("FORK");
exit (2);
case 0:
/* Чтение из канала и выдачу на стандартный вывод */
/* реализуем в порожденном процессе. */
/* Необходимо закрыть дескриптор, предназначенный */
/* для записи в канал, иначе чтение не завершится */
/* по концу файла */
close (fd [1]);
while (read (fd [0], buf, 1) == 1) {
if (write (1, buf, 1) != 1) {
perror ("WRITE TO STDOUT");
break;
}
}
exit (0);
}
/* Чтение со стандартного ввода и запись в канал */
/* возложим на родительский процесс. */
/* Из соображений симметрии закроем дескриптор, */
/* предназначенный для чтения из канала */
close (fd [0]);
if (write (fd [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) !=
sizeof (MY_PROMPT) - 1) {
perror ("WRITE TO PIPE-1");
}
while (read (0, buf, 1) == 1) {
/* Перед отображением очередной строки */
/* нужно выдать сообщение MY_MSG */
if (new_line) {
if (write (fd [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) {
perror ("WRITE TO PIPE-2");
break;
}
}
if (write (fd [1], buf, 1) != 1) {
perror ("WRITE TO PIPE-3");
break;
}
new_line = (buf [0] == '\n');
}
close (fd [1]);
(void) wait (NULL);
return (0);
}
Решение той же задачи, но с использованием функций
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <sys/wait.h>
#include <assert.h>
/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через канал. */
/* Используются функции буферизованного ввода/вывода */
int main (void) {
int fd [2];
FILE *fp [2];
char line [LINE_MAX];
/* Создадим безымянный канал */
if (pipe (fd) < 0) {
perror ("PIPE");
exit (1);
}
/* Сформируем потоки по файловым дескрипторам канала */
assert ((fp [0] = fdopen (fd [0], "r")) != NULL);
assert ((fp [1] = fdopen (fd [1], "w")) != NULL);
/* Отменим буферизацию вывода */
setbuf (stdout, NULL);
setbuf (fp [1], NULL);
switch (fork ()) {
case -1:
perror ("FORK");
exit (2);
case 0:
/* Чтение из канала и выдачу на стандартный вывод */
/* реализуем в порожденном процессе. */
/* Необходимо закрыть поток, предназначенный для */
/* записи в канал, иначе чтение не завершится */
/* по концу файла */
fclose (fp [1]);
while (fgets (line, sizeof (line), fp [0]) != NULL) {
if (fputs (line, stdout) == EOF) {
break;
}
}
exit (0);
}
/* Чтение со стандартного ввода и запись в канал */
/* возложим на родительский процесс. */
/* Из соображений симметрии закроем поток, */
/* предназначенный для чтения из канала */
fclose (fp [0]);
fputs ("Вводите строки\n", fp [1]);
while (fgets (line, sizeof (line), stdin) != NULL) {
if ((fputs ("Вы ввели: ", fp [1]) == EOF) ||
(fputs (line, fp [1]) == EOF)) {
break;
}
}
fclose (fp [1]);
(void) wait (NULL);
return (0);
}
Если не указано противное, обмен данными через O_NONBLOCK (это может быть сделано при помощи функции fcntl() ). В таком случае чтение или запись, которые невозможно выполнить немедленно, завершаются неудачей.
Подчеркнем, что при попытке чтения из пустого fd [1], возвращенного функцией pipe() как дескриптор для записи в
Функция popen(), описанная выше, при рассмотрении командного интерпретатора, является более высокоуровневой по сравнению с pipe(). Она делает сразу несколько вещей: порождает процесс, обеспечивает выполнение в его рамках заданной команды, организует popen() задан режим " w ", то стандартный ввод команды, выполняющейся в рамках порожденного процесса, перенаправляется на конец r ", то в
После вызова popen() процесс может писать в pclose() (см. листинг 8.3).
#include <stdio.h> int pclose (FILE *stream);
Функция pclose() не только закрывает popen(), но и дожидается завершения порожденного процесса, возвращая его статус.
Типичное применение popen() - организация lp (см. листинг 8.4).
#include <stdio.h>
/* Программа печатает несколько первых строк треугольника Паскаля */
#define T_SIZE 16
int main (void) {
FILE *outptr;
long tp [T_SIZE]; /* Массив для хранения текущей строки треугольника */
int i, j;
/* Инициализируем массив, чтобы далее все элементы */
/* можно было считать и выводить единообразно */
tp [0] = 1;
for (i = 1; i < T_SIZE; i++) {
tp [i] = 0;
}
/* Создадим канал с командой */
if ((outptr = popen ("lp", "w")) == NULL) {
perror ("POPEN");
return (-1);
}
(void) fprintf (outptr, "\nТреугольник Паскаля:\n");
for (i = 0; i < T_SIZE; i++) {
/* Элементы очередной строки нужно считать от конца к началу */
/* Элемент tp [0] пересчитывать не нужно */
for (j = i; j > 0; j--) {
tp [j] += tp [j - 1];
}
/* Вывод строки треугольника в канал */
for (j = 0; j <= i; j++) {
(void) fprintf (outptr, " %ld", tp [j]);
}
(void) fprintf (outptr, "\n");
}
return (pclose (outptr));
}
Сходным образом можно организовать
#include <stdio.h>
#include <limits.h>
#include <assert.h>
#define MY_CMD "ls -l *.c"
int main (void) {
FILE *inptr;
char line [LINE_MAX];
assert ((inptr = popen (MY_CMD, "r")) != NULL);
while (fgets (line, sizeof (line), inptr) != NULL) {
fputs (line, stdout);
}
return (pclose (inptr));
}
Как и
Согласно стандарту POSIX-2001, под
Говорят, что kill() и т.п.). Иногда по одному
В момент
В каждом процессе определены действия, предпринимаемые в ответ на все предусмотренные системой сигналы. Говорят, что sigwait().
В интервале от генерации до доставки или принятия
У каждого
С
SIG_DFL
Подразумеваемые действия, зависящие от сигнала. Они описаны в заголовочном файле <signal.h>.
SIG_IGN
указатель на функцию
Обработать сигнал, выполнив при его доставке заданную функцию. После завершения функции обработки процесс возобновляет выполнение с точки прерывания. Обычно функция обработки вызывается в соответствии со следующим C-заголовком: void func (int signo) ; где signo - номер доставленного сигнала.
Первоначально, до входа в функцию main(), реакция на все SIG_DFL или SIG_IGN.
Функция называется асинхронно-сигнально-безопасной (АСБ), если ее можно вызывать без каких-либо ограничений при обработке сигналов. В стандарте POSIX-2001 имеется список функций, которые должны быть либо
Если
Перейдем к изложению возможностей по kill как средство терминирования процессов извне. На самом деле она посылает заданный сигнал; то же делает и одноименная функция (см. листинг 8.6).
#include <signal.h> int kill (pid_t pid, int sig);
Сигнал задается аргументом , значение которого может быть нулевым; в этом случае действия функции kill() сводятся к проверке допустимости значения pid (нулевой результат - признак успешного завершения kill() ).
Если pid > 0, это значение трактуется как идентификатор процесса. При нулевом значении pid pid равно -1, адресатами являются все процессы, которым вызывающий имеет право pid pid.
Процесс имеет право послать pid, если он (процесс) имеет соответствующие привилегии или его реальный или
У служебной программы kill имеется полезная опция -l, позволяющая увидеть соответствие между номерами kill -l может выглядеть так, как показано в листинге 8.7.
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1 34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5 38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9 42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13 46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14 50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10 54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6 58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2 62) SIGRTMAX-1 63) SIGRTMAX
Мы не будем пояснять назначение всех представленных в листинге сигналов, ограничившись кратким описанием тех, что фигурируют в стандарте POSIX-2001 как обязательные для реализации. Попутно отметим, что, согласно стандарту языка C, должны быть определены имена всего шести сигналов: SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV и SIGTERM.
SIGABRT
Сигнал аварийного завершения процесса. Подразумеваемая реакция предусматривает, помимо аварийного завершения, создание файла с образом памяти процесса.
SIGALRM
Срабатывание будильника. Подразумеваемая реакция - аварийное завершение процесса.
SIGBUS
Ошибка системной шины как следствие обращения к неопределенной области памяти. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGCHLD
Завершение, остановка или продолжение порожденного процесса. Подразумеваемая реакция - игнорирование.
SIGCONT
Продолжение процесса, если он был остановлен. Подразумеваемая реакция - продолжение выполнения или игнорирование (если процесс не был остановлен).
SIGFPE
Некорректная арифметическая операция. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGHUP
Сигнал разъединения. Подразумеваемая реакция - аварийное завершение процесса.
SIGILL
Некорректная команда. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGINT
SIGKILL
Уничтожение процесса (этот
SIGPIPE
Попытка записи в
SIGQUIT
Сигнал выхода, поступивший с терминала. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGSEGV
Некорректное обращение к памяти. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGSTOP
Остановка выполнения (этот
SIGTERM
Сигнал терминирования. Подразумеваемая реакция - аварийное завершение процесса.
SIGTSTP
Сигнал остановки, поступивший с терминала. Подразумеваемая реакция - остановка процесса.
SIGTTIN
Попытка чтения из фонового процесса. Подразумеваемая реакция - остановка процесса.
SIGTTOU
Попытка записи из фонового процесса. Подразумеваемая реакция - остановка процесса.
SIGUSR1, SIGUSR2
Определяемые пользователем сигналы. Подразумеваемая реакция - аварийное завершение процесса.
SIGPOLL
Опрашиваемое
SIGPROF
Срабатывание
SIGSYS
Некорректный системный вызов. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGTRAP
Попадание в точку трассировки/прерывания. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGURG
Высокоскоростное поступление данных в сокет. Подразумеваемая реакция - игнорирование.
SIGVTALRM
Срабатывание виртуального таймера. Подразумеваемая реакция - аварийное завершение процесса.
SIGXCPU
Исчерпан лимит процессорного времени. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGXFSZ
Превышено ограничение на размер файлов. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
Процесс ( raise() (см. листинг 8.8). Для процесса вызов raise() эквивалентен kill (
#include <signal.h> int raise (int sig);
abort() (см. листинг 8.9), вызывающей аварийное завершение процесса. (Заметим, что этого не произойдет, если SIGABRT не возвращает управления. С другой стороны, abort() отменяет блокирование или игнорирование SIGABRT.)
#include <stdlib.h> void abort (void);
Опросить и изменить sigaction() (см. листинг 8.10).
#include <signal.h>
int sigaction (int sig, const struct sigaction
*restrict act, struct sigaction
*restrict oact);
Для описания sigaction, которая должна содержать по крайней мере следующие поля:
void (*sa_handler) (int); /* Указатель на функцию обработки сигнала */ /* или один из макросов SIG_DFL или SIG_IGN */ sigset_t sa_mask; /* Дополнительный набор сигналов, блокируемых */ /* на время выполнения функции обработки */ int sa_flags; /* Флаги, влияющие на поведение сигнала */ void (*sa_sigaction) (int, siginfo_t *, void *); /* Указатель на функцию обработки сигнала */
Приложение, соответствующее стандарту, не должно одновременно использовать поля обработчиков sa_handler и sa_sigaction.
Тип sigset_t может быть целочисленным или структурным и представлять
Тип siginfo_t должен быть структурным по крайней мере со следующими полями:
int si_signo; /* Номер сигнала */ int si_errno; /* Значение переменной errno, ассоциированное с данным сигналом */ int si_code; /* Код, идентифицирующий причину сигнала */ pid_t si_pid; /* Идентификатор процесса, пославшего сигнал */ uid_t si_uid; /* Реальный идентификатор пользователя процесса, пославшего сигнал */ void *si_addr; /* Адрес, вызвавший генерацию сигнала */ int si_status; /* Статус завершения порожденного процесса */ long si_band; /* Событие, связанное с сигналом SIGPOLL */
В заголовочном файле <signal.h> определены именованные константы, предназначенные для работы с полем si_code, значения которого могут быть как специфичными для конкретного сигнала, так и универсальными. К числу универсальных кодов относятся:
SI_USER
Сигнал послан функцией kill().
SI_QUEUE
Сигнал послан функцией sigqueue().
SI_TIMER
Сигнал сгенерирован в результате timer_settime().
SI_ASYNCIO
Сигнал вызван завершением асинхронной операции ввода/вывода.
SI_MESGQ
Сигнал вызван приходом сообщения в пустую
Из кодов, специфичных для конкретных сигналов, мы упомянем лишь несколько, чтобы дать представление о степени детализации диагностики, предусмотренной стандартом POSIX-2001. (Из имени константы ясно, к какому
ILL_ILLOPC
Некорректный код операции.
ILL_COPROC
Ошибка сопроцессора.
FPE_INTDIV
Целочисленное деление на нуль.
FPE_FLTOVF
Переполнение при выполнении операции
FPE_FLTSUB
Индекс вне диапазона.
SEGV_MAPERR
Адрес не отображен на объект.
BUS_ADRALN
Некорректное выравнивание адреса.
BUS_ADRERR
Несуществующий физический адрес.
TRAP_BRKPT
Процесс достиг точки прерывания.
TRAP_TRACE
Срабатывание трассировки процесса.
CLD_EXITED
Завершение порожденного процесса.
CLD_STOPPED
Остановка порожденного процесса.
POLL_PRI
Поступили высокоприоритетные данные.
Вернемся непосредственно к описанию функции sigaction(). Если аргумент act отличен от NULL, он указывает на структуру, специфицирующую действия, которые будут ассоциированы с . По адресу oact (если он не NULL ) возвращаются сведения о прежних действиях. Если значение act есть NULL, обработка
Следующие флаги в поле sa_flags влияют на поведение .
SA_NOCLDSTOP
Не генерировать SIGCHLD при остановке или продолжении порожденного процесса (значение аргумента должно равняться SIGCHLD ).
SA_RESETHAND
При входе в установить подразумеваемую реакцию SIG_DFL и очистить флаг SA_SIGINFO (см. далее).
SA_SIGINFO
Если этот флаг не установлен и определена , она вызывается с одним целочисленным аргументом - номером сигнала. Соответственно, в приложении следует использовать поле sa_handler структуры sigaction. При установленном флаге SA_SIGINFO функция обработки вызывается с двумя дополнительными аргументами, как void func (int второй аргумент указывает на данные, поясняющие причину ucontext_t - sa_sigaction и поля структуры типа siginfo_t. В частности, если значение si_code неположительно, si_pid и реальным идентификатором пользователя si_uid.
SA_NODEFER
По умолчанию обрабатываемый SA_NODEFER предписывает не делать этого, если только не фигурирует явным образом в sa_mask.
Опросить и изменить
trap [действие условие ...]
Аргумент "условие" может задаваться как EXIT (завершение командного интерпретатора) или как имя ). При задании аргумента "действие" минус обозначает подразумеваемую реакцию, пустая цепочка ("") - игнорирование. Если в качестве действия задана команда, то при наступлении условия она обрабатывается как eval действие.
Команда trap без аргументов выдает на стандартный вывод список команд, ассоциированных с каждым из условий. Выдача имеет формат, пригодный для восстановления
save_traps=$(trap) . . . eval "$save_traps"
Обеспечить выполнение утилиты logout из домашнего каталога пользователя во время завершения командного интерпретатора можно с помощью команды, показанной в листинге 8.12.
trap '$HOME/logout' EXIT
При SIGPIPE (см. листинг 8.13).
trap "" PIPE echo "$INITLOG_ARGS -n $0 -s \"$1\" -e 1" >21 trap - PIPE
К техническим аспектам можно отнести работу с sigemptyset() и sigfillset() инициализируют набор, делая его, соответственно, пустым или "полным". Функция sigaddset() добавляет signo к набору set, sigdelset() удаляет сигнал, а sigismember() проверяет вхождение в набор. Обычно признаком завершения является нулевой результат, в случае ошибки возвращается -1. Только sigismember() выдает 1, если signo входит в набор set.
#include <signal.h>
int sigemptyset (sigset_t *set);
int sigfillset (sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset (sigset_t *set, int signo);
int sigismember (const sigset_t *set,
int signo);
Функция sigprocmask() (см. листинг 8.15) предназначена для опроса и/или изменения
#include <signal.h> int sigprocmask (int how, const sigset_t *restrict set, sigset_t *restrict oset);
Если аргумент set отличен от NULL, он указывает на набор, используемый для изменения текущей how определяет способ изменения; он может принимать одно из трех значений: SIG_BLOCK (результирующая маска получается при объединении текущей и заданной аргументом set ), SIG_SETMASK (результирующая маска устанавливается равной set ) и SIG_UNBLOCK (маска set вычитается из текущей).
По адресу oset (если он не NULL ) возвращается прежняя маска. Если значение set есть NULL, набор блокируемых
Если к моменту завершения sigprocmask() будут существовать ждущие неблокированные сигналы, по крайней мере один из них должен быть доставлен до возврата из sigprocmask().
Нельзя
Функция sigpending() (см. листинг 8.16) позволяет выяснить набор блокированных сигналов, ожидающих доставки вызывающему процессу ( sigwait() (см. листинг 8.17).
#include <signal.h> int sigpending (sigset_t *set);
#include <signal.h>
int sigwait (const sigset_t *restrict set,
int *restrict sig);
Функция sigwait() выбирает . Если в момент вызова sigwait() нужного
Отметим, что стандарт POSIX-2001 не специфицирует воздействие функции sigwait() на обработку сигналов, включенных в набор set. Чтобы дождаться доставки обрабатываемого или терминирующего процесс сигнала, можно воспользоваться функцией pause() (см. листинг 8.18).
#include <unistd.h> int pause (void);
Функция pause() может ждать pause() осуществляется после возврата из -1 ). Если pause(), естественно, не происходит.
Несмотря на внешнюю простоту, использование функции pause() сопряжено с рядом тонкостей. При наивном подходе сначала проверяют некоторое условие, связанное с сигналом, и, если оно не выполнено (сигнал отсутствует), вызывают pause(). К сожалению, pause(), что нарушает логику работы процесса и способно привести к его зависанию. Решить подобную проблему позволяет функция sigsuspend() (см. листинг 8.19) в сочетании с рассмотренной выше функцией sigprocmask().
#include <signal.h> int sigsuspend (const sigset_t *sigmask);
Функция sigsuspend() заменяет текущую pause(). После возврата из sigsuspend() (если таковой произойдет) восстанавливается прежняя
Обычно sigprocmask() и sigsuspend() обрамляют sigprocmask() блокируют некоторые сигналы, а на выходе вызывают sigsuspend() с маской, которую возвратила sigprocmask(), восстанавливая тем самым набор блокированных
В качестве примера использования описанных выше функций работы с abort() (см. листинг 8.20).
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void abort (void) {
struct sigaction sact;
sigset_t sset;
/* Вытолкнем буфера */
(void) fflush (NULL);
/* Снимем блокировку сигнала SIGABRT */
if ((sigemptyset (sset) == 0) (sigaddset (sset, SIGABRT) == 0)) {
(void) sigprocmask (SIG_UNBLOCK, sset, (sigset_t *) NULL);
}
/* Пошлем себе сигнал SIGABRT. */
/* Возможно, его перехватит функция обработки, */
/* и тогда вызывающий процесс может не завершиться */
raise (SIGABRT);
/* Установим подразумеваемую реакцию на сигнал SIGABRT */
sact.sa_handler = SIG_DFL;
sigfillset (sact.sa_mask);
sact.sa_flags = 0;
(void) sigaction (SIGABRT, sact, NULL);
/* Снова пошлем себе сигнал SIGABRT */
raise (SIGABRT);
/* Если сигнал снова не помог, попробуем еще одно средство завершения */
_exit (127);
}
int main (void) {
printf ("Перед вызовом abort()\n");
abort ();
printf ("После вызова abort()\n");
return 0;
}
В качестве нюанса, характерного для работы с сигналами, отметим, что до первого обращения к raise() нельзя закрыть SIGABRT, возможно, осуществляет вывод.
Еще одним примером использования механизма sleep(), предназначенной для "засыпания" на заданное число секунд. (Можно надеяться, что не описанные пока средства работы с временем интуитивно понятны.)
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
/* Функция обработки сигнала SIGALRM. */
/* Она ничего не делает, но игнорировать сигнал нельзя */
static void signal_handler (int sig) {
/* В демонстрационных целях распечатаем номер обрабатываемого сигнала */
printf ("Принят сигнал %d\n", sig);
}
/* Функция для "засыпания" на заданное число секунд */
/* Результат равен разности между заказанной и фактической */
/* продолжительностью "сна" */
unsigned int sleep (unsigned int seconds) {
time_t before, after;
unsigned int slept;
sigset_t set, oset;
struct sigaction act, oact;
if (seconds == 0) {
return 0;
}
/* Установим будильник на заданное время, */
/* но перед этим блокируем сигнал SIGALRM */
/* и зададим свою функцию обработки для него */
if ((sigemptyset (set) < 0) || (sigaddset (set, SIGALRM) < 0) ||
sigprocmask (SIG_BLOCK, set, oset)) {
return seconds;
}
act.sa_handler = signal_handler;
act.sa_flags = 0;
act.sa_mask = oset;
if (sigaction (SIGALRM, act, oact) < 0) {
return seconds;
}
before = time ((time_t *) NULL);
(void) alarm (seconds);
/* Как атомарное действие восстановим старую маску сигналов */
/* (в надежде, что она не блокирует SIGALRM) */
/* и станем ждать доставки обрабатываемого сигнала */
(void) sigsuspend (oset);
/* сигнал доставлен и обработан */
after = time ((time_t *) NULL);
/* Восстановим прежний способ обработки сигнала SIGALRM */
(void) sigaction (SIGALRM, oact, (struct sigaction *) NULL);
/* Восстановим первоначальную маску сигналов */
(void) sigprocmask (SIG_SETMASK, oset, (sigset_t *) NULL);
return ((slept = after - before) > seconds ? 0 : (seconds - slept));
}
int main (void) {
struct sigaction act;
/* В демонстрационных целях установим обработку прерывания с клавиатуры */
act.sa_handler = signal_handler;
(void) sigemptyset (act.sa_mask);
act.sa_flags = 0;
(void) sigaction (SIGINT, act, (struct sigaction *) NULL);
printf ("Заснем на 10 секунд\n");
printf ("Проснулись, не доспав %d секунд\n", sleep (10));
return (0);
}
Обратим внимание на применение функции sigsuspend(), которая реализует (неделимую) транзакцию снятия sigsuspend() (после возврата из функции обработки), то автоматически восстанавливается sigsuspend(). В данном случае в этой маске блокирован SIGALRM, и потому можно спокойно менять способ его обработки.
Вызвать "недосыпание" приведенной программы можно, послав ей SIGALRM (например, посредством команды kill -s SIGALRM идентификатор_процесса) или SIGINT (путем нажатия на клавиатуре терминала комбинации клавиш CTRL+C).
Мы переходим к рассмотрению средств
Остановимся сначала на понятиях и структурах, общих для всех трех упомянутых средств.
Каждая msqid, semid и shmid и возвращается в качестве результатов функций msgget(), semget() и shmget().
При получении ftok() (см. листинг 8.22). Аргумент path должен задавать stat(). В качестве значения аргумента id, по соображениям мобильности, рекомендуется использовать однобайтный символ. Гарантируется, что функция ftok() сгенерирует один и тот же ключ для заданной пары (файл, символ) и разные ключи для разных пар.
#include <sys/ipc.h> key_t ftok (const char *path, int id);
С <sys/msg.h>, <sys/ и <sys/shm.h>.
В упомянутую структуру входит подструктура ipc_perm с данными о владельцах и режимом доступа, описанная в файле <sys/ipc.h> и содержащая по крайней мере следующие поля.
uid_t uid; /* Идентификатор владельца */ gid_t gid; /* Идентификатор владеющей группы */ uid_t cuid; /* Идентификатор пользователя, создавшего данное средство межпроцессного взаимодействия */ gid_t cgid; /* Идентификатор создавшей группы */ mode_t mode; /* Режим доступа на чтение/запись */
Управление доступом к описываемым средствам
Опросить статус присутствующих в данный момент в системе (т. е. активных) средств ipcs:
ipcs [-qms] [-a | -bcopt]
По умолчанию выдается краткая информация обо всех средствах - -q, -s и/или -m, соответственно.
Следующие опции управляют форматом выдачи. Задание опции -a равносильно указанию всех опций формата. Опция -b предписывает выдавать лимиты на размер (максимальное количество байт в сообщениях очереди и т.п.), -c - имена пользователя и группы создателя средства, -o - информацию об использовании (количество сообщений в очереди, их суммарный размер и т.п.), -p - информацию о процессах (идентификаторы последнего отправителя, получателя и т.п.), -t - информацию о времени (последняя управляющая операция, последняя
Для удаления из системы ipcrm (разумеется, подверженная контролю прав доступа). Удаляемые средства могут задаваться идентификаторами или ключами:
ipcrm [-q msgid | -Q msgkey | -s semid |
-S semkey | -m shmid | -M shmkey ] ...
На этом мы завершаем изложение общих вопросов, относящихся к средствам
Механизм
Прежде чем процессы смогут обмениваться сообщениями, один из них должен создать очередь. Одновременно определяются первоначальные права на выполнение операций для различных процессов, в том числе соответствующих управляющих действий над очередями.
Для работы с msgget() (получение msgctl() ( msgsnd() (
#include <sys/msg.h>
int msgget (key_t key, int msgflg);
int msgsnd (int msqid, const void *msgp,
size_t msgsz, int msgflg);
ssize_t msgrcv (int msqid, void *msgp,
size_t msgsz, long msgtyp,
int msgflg);
int msgctl (int msqid, int cmd,
struct msqid_ds *buf);
Структура msqid_ds, ассоциированная с
struct ipc_perm msg_perm; /* Данные о правах доступа к очереди сообщений */ msgqnum_t msg_qnum; /* Текущее количество сообщений в очереди */ msglen_t msg_qbytes; /* Максимально допустимый суммарный размер сообщений в очереди */ pid_t msg_lspid; /* Идентификатор процесса, отправившего последнее сообщение */ pid_t msg_lrpid; /* Идентификатор процесса, принявшего последнее сообщение */ time_t msg_stime; /* Время последней отправки */ time_t msg_rtime; /* Время последнего приема */ time_t msg_ctime; /* Время последнего изменения посредством msgctl() */
Перейдем к детальному рассмотрению функций для работы с
Функция msgget() возвращает key. Новая очередь, ее идентификатор и соответствующая структура msqid_ds создаются для заданного ключа, если значение аргумента key равно IPC_PRIVATE или очередь еще не ассоциирована с ключом, а в числе флагов msgflg задан IPC_CREAT.
Если необходима уверенность в том, что очередь с указанным ключом создается заново, в дополнение к флагу IPC_CREAT следует установить IPC_EXCL. Тогда попытка получить идентификатор уже существующий очереди завершится неудачей.
Структура msqid_ds для новой очереди инициализируется следующим образом.
msg_perm.cuid, msg_perm.uid, msg_perm.cgid и msg_perm.gid устанавливаются равными msg_perm.mode устанавливаются равными младшим девяти битам значения msgflg.msg_qnum, msg_lspid, msg_lrpid, msg_stime и msg_rtime обнуляются.msg_ctime помещается текущее время, а в поле msg_qbytes - определенный в системе лимит.Один из тонких вопросов, связанных с msqid необходимо знать ключ очереди. Задание ключа одинаковым константным значением во всех этих программах небезопасно, поскольку может оказаться так, что тот же ключ будет случайно задействован и другими программами. Как одно из возможных решений рекомендуется использование функции ftok(), вычисляющей действительно "уникальный" ключ.
В листинге 8.24 приведен простейший пример программы, где
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
/* Программа создает очередь сообщений. */
/* В командной строке задаются имя файла для ftok() */
/* и режим доступа к очереди сообщений */
#define FTOK_CHAR 'G'
int main (int argc, char *argv []) {
key_t key;
int msqid;
int mode = 0;
if (argc != 3) {
fprintf (stderr, "Использование: %s маршрутное_имя режим_доступа\n", argv [0]);
return (1);
}
if ((key = ftok (argv [1], FTOK_CHAR)) == (key_t) (-1)) {
perror ("FTOK");
return (2);
}
(void) sscanf (argv [2], "%o", (unsigned int *) mode);
if ((msqid = msgget (key, IPC_CREAT | mode)) < 0) {
perror ("MSGGET");
return (3);
}
return 0;
}
Если после выполнения этой программы воспользоваться командой ipcs -q, то результат может выглядеть так, как показано в листинге 8.25.
------ Message Queues -------- key msqid owner perms used-bytes messages 0x47034bac 163840 galat 644 0 0
Удалить созданную очередь из системы, соответствующей стандарту POSIX-2001, можно командой ipcrm -q 163840.
Операции msgsnd() и msgrcv() ; msgsnd() помещает сообщения в очередь, а msgrcv() читает и "достает" их оттуда.
В обоих случаях первый аргумент задает идентификатор очереди; второй является указателем на содержащую сообщение структуру. Сообщение состоит из двух частей: текста (последовательности байт) и так называемого типа (положительного целого числа). Тип, указанный во время отправки, используется впоследствии при выборе сообщения из очереди. Аргумент msgsz определяет длину сообщения; аргумент msgflg задает флаги.
В зависимости от значения, указанного в качестве аргумента msgtyp функции msgrcv(), из очереди выбирается то или иное сообщение. Если значение аргумента равно нулю, запрашивается первое сообщение в очереди, если больше нуля - первое сообщение типа msgtyp, а если меньше нуля - первое сообщение наименьшего из типов, не превышающих абсолютную величину аргумента msgtyp. Пусть, например, в очередь последовательно помещены сообщения с типами 5, 3 и 2. Тогда вызов msgrcv (msqid, msgp, size, 0, flags) выберет из msgrcv (msqid, msgp, size, -4, flags) - последнее сообщение, так как 2 - это наименьший из возможных типов в указанном диапазоне; наконец, вызов msgrcv (msqid, msgp, size, 3, flags) - сообщение с типом 3.
Во многих приложениях взаимодействующим посредством
Если не указано противное, функции msgsnd() и msgrcv() выполняют msgsnd (msqid, msgp, size, 0); msgrcv (msqid, msgp, size, type, 0). Чтобы выполнить IPC_NOWAIT: msgsnd (msqid, msgp, size, IPC_NOWAIT); msgrcv (msqid, msgp, size, type, IPC_NOWAIT).
Аргумент msgp указывает на значение структурного типа, в котором представлены тип и тело сообщения (см. листинг 8.26).
struct msgbuf {
long mtype; /* Тип сообщения */
char mtext [1]; /* Текст сообщения */
};
Для хранения реальных сообщений в прикладной программе следует определить аналогичную структуру, указав желаемый размер сообщения, например, так, как это сделано в листинге 8.27.
#define MAXSZTMSG 8192
struct mymsgbuf {
long mtype; /* Тип сообщения */
char mtext [MAXSZTMSG]; /* Текст сообщения */
};
struct mymsgbuf msgbuf;
В качестве аргумента msgsz обычно указывается размер текстового буфера, например: sizeof ( msgbuf.text ).
Если не указано противное, в случае, когда длина выбранного сообщения больше, чем msgsz, вызов msgrcv() завершается неудачей. Если же установить флаг MSG_NOERROR, длинное сообщение обрезается до msgsz байт. Отброшенная часть пропадает, а вызывающий процесс не получает никакого уведомления о том, что сообщение обрезано.
При успешном завершении msgsnd() возвращает 0, а msgrcv() - значение, равное числу реально полученных байт; при неудаче возвращается -1.
Процессы, обладающие достаточными правами доступа, посредством функции msgctl() могут получать информацию о состоянии очереди, изменять ряд характеристик, удалять очередь.
Управляющее действие определяется значением аргумента cmd. Допустимых значений три: IPC_STAT - получить информацию о состоянии очереди, IPC_SET - переустановить характеристики очереди, IPC_RMID - удалить очередь.
Команды IPC_STAT и IPC_SET для хранения информации об очереди используют имеющуюся в прикладной программе структуру типа msqid_ds, указатель на которую содержит аргумент buf: IPC_STAT копирует в нее ассоциированную с очередью структуру данных, а IPC_SET, наоборот, в соответствии с ней обновляет ассоциированную структуру. Команда IPC_SET позволяет переустановить значения идентификаторов владельца ( msg_perm.uid ) и владеющей группы ( msg_perm.gid ), режима доступа ( msg_perm.mode ), максимально допустимый суммарный размер сообщений в очереди ( msg_qbytes ). Увеличить значение msg_qbytes может только процесс, обладающий соответствующими привилегиями.
В листинге 8.28 приведена программа, изменяющая максимально допустимый суммарный размер сообщений в очереди. Предполагается, что
#include <stdio.h>
#include <sys/msg.h>
int main (int argc, char *argv []) {
int msqid;
struct msqid_ds msqid_ds;
if (argc != 3) {
fprintf (stderr, "Использование: %s идентификатор_очереди максимальный_размер\n", argv [0]);
return (1);
}
(void) sscanf (argv [1], "%d", msqid);
/* Получим исходное значение структуры данных */
if (msgctl (msqid, IPC_STAT, msqid_ds) == -1) {
perror ("IPC_STAT-1");
return (2);
}
printf ("Максимальный размер очереди до изменения: %ld\n", msqid_ds.msg_qbytes);
(void) sscanf (argv [2], "%d", (int *) msqid_ds.msg_qbytes);
/* Попробуем внести изменения */
if (msgctl (msqid, IPC_SET, msqid_ds) == -1) {
perror ("IPC_SET");
}
/* Получим новое значение структуры данных */
if (msgctl (msqid, IPC_STAT, msqid_ds) == -1) {
perror ("IPC_STAT-2");
return (3);
}
printf ("Максимальный размер очереди после изменения: %ld\n", msqid_ds.msg_qbytes);
return 0;
}
Две программы, показанные в листингах 8.29 и 8.30, демонстрируют полный цикл работы с msq_child текущего каталога.
#include <unistd.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/msg.h>
/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через очередь сообщений */
#define FTOK_FILE "/home/galat"
#define FTOK_CHAR "G"
#define MSGQ_MODE 0644
#define MY_PROMPT "Вводите строки\n"
#define MY_MSG "Вы ввели: "
int main (void) {
key_t key;
int msqid;
struct mymsgbuf {
long mtype;
char mtext [LINE_MAX];
} line_buf, msgbuf;
switch (fork ()) {
case -1:
perror ("FORK");
return (1);
case 0:
/* Чтение из очереди и выдачу на стандартный вывод */
/* реализуем в порожденном процессе. */
(void) execl ("./msq_child", "msq_child", FTOK_FILE, FTOK_CHAR, (char *) 0);
perror ("EXEC");
return (2); /* execl() завершился неудачей */
}
/* Чтение со стандартного ввода и запись в очередь */
/* возложим на родительский процесс */
/* Выработаем ключ для очереди сообщений */
if ((key = ftok (FTOK_FILE, FTOK_CHAR [0])) == (key_t) (-1)) {
perror ("FTOK");
return (3);
}
/* Получим идентификатор очереди сообщений */
if ((msqid = msgget (key, IPC_CREAT | MSGQ_MODE)) < 0) {
perror ("MSGGET");
return (4);
}
/* Приступим к отправке сообщений в очередь */
msgbuf.mtype = line_buf.mtype = 1;
strncpy (msgbuf.mtext, MY_PROMPT, sizeof (msgbuf.mtext));
if (msgsnd (msqid, (void *) msgbuf, strlen (msgbuf.mtext) + 1, 0) != 0) {
perror ("MSGSND-1");
return (5);
}
strncpy (msgbuf.mtext, MY_MSG, sizeof (msgbuf.mtext));
while (fgets (line_buf.mtext, sizeof (line_buf.mtext), stdin) != NULL) {
if (msgsnd (msqid, (void *) msgbuf, strlen (msgbuf.mtext) + 1, 0) != 0) {
perror ("MSGSND-2");
break;
}
if (msgsnd (msqid, (void *) line_buf, strlen (line_buf.mtext) + 1, 0) != 0) {
perror ("MSGSND-3");
break;
}
}
/* Удалим очередь */
if (msgctl (msqid, IPC_RMID, NULL) == -1) {
perror ("MSGCTL-IPC_RMID");
return (6);
}
return (0);
}
#include <stdio.h>
#include <limits.h>
#include <sys/msg.h>
/* Программа получает сообщения из очереди */
/* и копирует их тела на стандартный вывод */
#define MSGQ_MODE 0644
int main (int argc, char *argv []) {
key_t key;
int msqid;
struct mymsgbuf {
long mtype;
char mtext [LINE_MAX];
} msgbuf;
if (argc != 3) {
fprintf (stderr, "Использование: %s имя_файла цепочка_символов\n", argv [0]);
return (1);
}
/* Выработаем ключ для очереди сообщений */
if ((key = ftok (argv [1], *argv [2])) == (key_t) (-1)) {
perror ("CHILD FTOK");
return (2);
}
/* Получим идентификатор очереди сообщений */
if ((msqid = msgget (key, IPC_CREAT | MSGQ_MODE)) < 0) {
perror ("CHILD MSGGET");
return (3);
}
/* Цикл приема сообщений и выдачи строк */
while (msgrcv (msqid, (void *) msgbuf, sizeof (msgbuf.mtext), 0, 0) > 0) {
if (fputs (msgbuf.mtext, stdout) == EOF) {
break;
}
}
return 0;
}
Обратим внимание на способ выработки согласованного ключа, а также на то, что, вообще говоря, неизвестно, какой из процессов - родительский или порожденный - создаст очередь, а какой получит уже ассоциированный с ключом идентификатор (вызовы msgget() в обоих процессах одинаковы), но на корректность работы программы это не влияет.
Согласно определению стандарта POSIX-2001,
У
semget() ) и обрабатываются (функцией semop() ) наборами (массивами), причем операции над наборами с точки зрения приложений являются атомарными. В рамках групповых операций для любого
Процессы, обладающие соответствующими правами, также могут выполнять различные управляющие действия над semctl().
Описание перечисленных функций представлено в листинге 8.31.
#include <sys/sem.h>
int semget (key_t key, int nsems, int semflg);
int semop (int semid, struct sembuf *sops,
size_t nsops);
int semctl (int semid, int semnum,
int cmd, ...);
Структура semid_ds, ассоциированная с
struct ipc_perm sem_perm; /* Данные о правах доступа к набору семафоров */ unsigned short sem_nsems; /* Число семафоров в наборе */ time_t sem_otime; /* Время последней операции semop() */ time_t sem_ctime; /* Время последнего изменения посредством semctl() */
Отдельные
unsigned short semval; /* Значение семафора */ pid_t sempid; /* Идентификатор процесса, выполнившего последнюю операцию над семафором */ unsigned short semncnt; /* Число процессов, ожидающих увеличения текущего значения семафора */ unsigned short semzcnt; /* Число процессов, ожидающих обнуления значения семафора */
Функция semget() аналогична msgget() ; аргумент nsems задает число semid_ds инициализируется так же, как msqid_ds. Безымянные структуры, соответствующие отдельным semget() не инициализируются.
Операции, выполняемые посредством функции semop(), задаются массивом sops с числом элементов nsops, состоящим из структур типа sembuf, каждая из которых содержит по крайней мере следующие поля.
unsigned short sem_num; /* Номер семафора в наборе (нумерация с нуля) */ short sem_op; /* Запрашиваемая операция над семафором */ short sem_flg; /* Флаги операции */
Операция над sem_op: положительное значение предписывает
Выполнение массива операций с точки зрения пользовательского процесса является неделимым действием. Это значит, во-первых, что если операции выполняются, то только все вместе и, во-вторых, никакой другой процесс не может получить доступ к промежуточному состоянию semop() приостанавливается ( semop() значения всех
Приведенный в листинге 8.32 массив операций задает уменьшение (с блокировкой)
sembuf [0].sem_num = 1; sembuf [0].sem_flg = 0; sembuf [0].sem_op = -2; sembuf [1].sem_num = 0; sembuf [1].sem_flg = IPC_NOWAIT; sembuf [1].sem_op = 0;
Обращаясь к функции semctl(), процессы могут получать информацию о состоянии
Аргументы semid ( semnum (номер cmd. Если объектом является набор, значение semnum игнорируется.
Для некоторых действий задействован четвертый аргумент (см. листинг 8.33).
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
} arg;
Среди допустимых действий - GETVAL (получить SETVAL (установить arg.val ). Имеются и аналогичные групповые действия - GETALL (прочитать значения всех arg.array ) и SETALL (установить значения всех ), а также число процессов, ожидающих GETNCNT / GETZCNT )
val = semctl (semid, semnum, GETVAL); arg.val = ...; if (semctl (semid, semnum, SETVAL, arg) == -1) ...; arg.array = (unsigned short *) malloc (nsems * sizeof (unsigned short)); err = semctl (semid, 0, GETALL, arg); for (i = 0; i < nsems; i++) arg.array [i] = ...; err = semctl (semid, 0, SETALL, arg); lpid = semctl (semid, semnum, GETPID); ncnt = semctl (semid, semnum, GETNCNT); zcnt = semctl (semid, semnum, GETZCNT);
Наконец, для IPC_STAT (получить информацию о состоянии IPC_SET (переустановить характеристики), IPC_RMID (удалить
arg.buf = (struct semid_ds *) malloc (sizeof (struct semid_ds); err = semctl (semid, 0, IPC_STAT, arg); arg.buf->sem_perm.mode = 0644; err = semctl (semid, 0, IPC_SET, arg);
В качестве примера использования
Предлагаемое решение состоит из двух программ. Первая (см. листинг 8.36) реализует процесс-монитор, который порождает QPH ), ожидает, пока все процессы завершатся (все философы съедят свой обед), и удаляет ftok() ), что исходный текст программы находится в файле phdin.c (точнее, что такой файл существует).
#include <unistd.h>
#include <stdio.h>
#include <sys/sem.h>
#include <sys/wait.h>
/* Программа-монитор обеда философов */
#define QPH 5
#define ARG_SIZE 20
int main (void) {
int key; /* Ключ набора семафоров */
int semid; /* Идентификатор набора семафоров */
int no; /* Номер философа и/или вилки */
char ssemid [ARG_SIZE], sno [ARG_SIZE], sqph [ARG_SIZE];
/* Создание и инициализация набора семафоров */
/* (по семафору на вилку) */
key = ftok ("phdin.c", 'C');
if ((semid = semget (key, QPH, 0600 | IPC_CREAT)) < 0) {
perror ("SEMGET");
return (1);
}
for (no = 0; no < QPH; no++) {
if (semctl (semid, no, SETVAL, 1) < 0) {
perror ("SETVAL");
return (2);
}
}
sprintf (ssemid, "%d", semid);
sprintf (sqph, "%d", QPH);
/* Все - к столу */
for (no = 1; no <= QPH; no++) {
switch (fork ()) {
case -1:
perror ("FORK");
return (3);
case 0:
sprintf (sno, "%d", no);
execl ("./phil", "phil", ssemid, sqph, sno, (char *) 0);
perror ("EXEC");
return (4);
}
}
/* Ожидание завершения обеда */
for (no = 1; no <= QPH; no++) {
(void) wait (NULL);
}
/* Удаление набора семафоров */
if (semctl (semid, 0, IPC_RMID) < 0) {
perror ("SEMCTL");
return (5);
}
return 0;
}
Вторая программа (см. листинг 8.37) описывает обед каждого философа. Философ какое-то время беседует (случайное значение trnd ), затем пытается взять вилки слева и справа от себя, когда ему это удается, некоторое время ест (случайное значение ernd ), после чего освобождает вилки. Так продолжается до тех пор, пока не будет съеден весь обед. Предполагается, что выполнимый файл программы называется phil.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sem.h>
/* Процесс обеда одного философа */
#define ernd (rand () % 3 + 1)
#define trnd (rand () % 5 + 1)
#define FO 15
int main (int argc, char *argv []) {
int semid; /* Идентификатор набора семафоров */
int qph; /* Число философов */
int no; /* Номер философа */
int t; /* Время очередного отрезка еды или беседы */
int fo; /* Время до конца обеда */
struct sembuf sembuf [2];
if (argc != 4) {
fprintf (stderr, "Использование: %s идентификатор_набора_семафоров число_философов номер_философа \n", argv [0]);
return (1);
}
fo = FO;
sscanf (argv [1], "%d", semid);
sscanf (argv [2], "%d", qph);
sscanf (argv [3], "%d", no);
/* Выбор вилок */
sembuf [0].sem_num = no - 1; /* Левая */
sembuf [0].sem_flg = 0;
sembuf [1].sem_num = no % qph; /* Правая */
sembuf [1].sem_flg = 0;
while (fo > 0) { /* Обед */
/* Философ говорит */
printf ("Философ %d беседует\n", no);
t = trnd; sleep (t); fo -= t;
/* Пытается взять вилки */
sembuf [0].sem_op = -1;
sembuf [1].sem_op = -1;
if (semop (semid, sembuf, 2) < 0) {
perror ("SEMOP");
return (1);
}
/* Ест */
printf ("Философ %d ест\n", no);
t = ernd; sleep (t); fo -= t;
/* Отдает вилки */
sembuf [0].sem_op = 1;
sembuf [1].sem_op = 1;
if (semop (semid, sembuf, 2) < 0) {
perror ("SEMOP");
return (2);
}
}
printf ("Философ %d закончил обед\n", no);
return 0;
}
Отметим, что возможность выполнения групповых операций над
В листинге 8.38 приведен второй вариант решения задачи, предложенный С.В. Самборским. В нем реализованы четыре стратегии захвата вилок, которые сравниваются по результатам моделирования поведения философов в течение нескольких минут. Все стратегии гарантируют отсутствие тупиков, но только две из них, соответствующие опциям -a и -p, заведомо не позволят ни одному философу умереть от голода из-за невозможности получить обе вилки сразу. (Это свойство "стратегий -a и -p " является следствием упорядоченности ресурсов.)
/* Обедающие философы. Запуск:
mudrecProc [-a | -p | -I -V] [-t число_секунд] имя_философа ...
Опции:
-t число_секунд - сколько секунд моделируется
Стратегии захвата вилок:
-a - сначала захватывается вилка с меньшим номером;
-I - некорректная (но эффективная) интеллигентная стратегия: во время
ожидания уже захваченная вилка кладется;
-p - сначала захватывается нечетная вилка;
-V - использован групповой захват семафоров.
Пример запуска: mudrecProc -p -t 600 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: mudrecProc.c,v 1.7 2003/11/11 13:14:07 sambor Exp $";
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <limits.h>
#include <errno.h>
#include <sys/sem.h>
#include <sys/msg.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
} arg;
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)>(b)?(b):(a))
struct mudrec {
long num;
char *name;
int left_fork, right_fork;
int eat_time, wait_time, think_time, max_wait_time;
int count;
};
int Stop; /* Семафор для синхронизации выхода */
/* Различные дескрипторы */
int protokol [2] = {-1, -1};
#define pFdIn (protokol [1])
#define pFdOut (protokol [0])
int semFork; /* Вилки */
int from_fil; /* Очередь для возврата результатов */
/* Разные алгоритмы захвата вилок */
static void get_forks_simple (struct mudrec *this);
static void get_forks_parity (struct mudrec *this);
static void get_forks_maybe_infinit_time (struct mudrec *this);
static void get_forks_use_groups (struct mudrec *this);
/* Используемый метод захвата вилок */
void (*get_forks) (struct mudrec *this) = get_forks_simple;
/* Возвращение вилок */
static void put_forks (struct mudrec *this);
/*
* Философы
*/
void filosof (struct mudrec this) {
char buffer [LINE_MAX];
int bytes;
if (fork ()) return;
srandom (getpid ()); /* Очень важно для процессов, иначе получим одно и то же! */
random (); random (); random (); random (); random ();
random (); random (); random (); random (); random ();
/* Пока семафор Stop не поднят */
while (!semctl (Stop, 0, GETVAL)) {
/* Пора подкрепиться */
{
int wait_time, tm;
sprintf (buffer, "%s: хочет есть\n", this.name);
bytes = write (pFdIn, buffer, strlen (buffer));
tm = time (0);
(*get_forks) (this);
wait_time = time (0) - 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 (pFdIn, buffer, strlen (buffer));
}
/* Может, обед уже закончился? */
if (semctl (Stop, 0, GETVAL)) {
put_forks (this);
break;
}
/* Едим */
{
int eat_time = random () % 20 + 1;
sleep (eat_time);
this.eat_time += eat_time;
this.count++;
sprintf (buffer,"%s: ел %d сек\n", this.name, eat_time);
bytes = write (pFdIn, buffer, strlen (buffer));
}
/* Отдаем вилки */
put_forks (this);
if (semctl (Stop, 0, GETVAL)) break;
/* Размышляем */
{
int think_time = random () % 10 + 1;
sleep (think_time);
this.think_time += think_time;
}
}
sprintf (buffer,"%s: уходит\n", this.name);
bytes = write (pFdIn, buffer, strlen (buffer));
msgsnd (from_fil, this, sizeof (this), 0); /* Отослали статистику своего обеда */
_exit (0); /* ВАЖНО (_): Нам не нужны преждевременные вызовы cleanup_ipc */
}
/* Кладем вилки одну за другой */
static void put_forks (struct mudrec *this) {
struct sembuf tmp_buf;
tmp_buf.sem_flg = 0;
tmp_buf.sem_op = 1;
tmp_buf.sem_num = this->left_fork - 1;
semop (semFork, tmp_buf, 1);
tmp_buf.sem_flg = 0;
tmp_buf.sem_op = 1;
tmp_buf.sem_num = this->right_fork - 1;
semop (semFork, tmp_buf, 1);
}
/* Берем вилки по очереди в порядке номеров */
static void get_forks_simple (struct mudrec *this) {
struct sembuf tmp_buf;
int first = min (this->left_fork, this->right_fork);
int last = max (this->left_fork, this->right_fork);
tmp_buf.sem_flg = SEM_UNDO;
tmp_buf.sem_op = -1;
tmp_buf.sem_num = first - 1;
semop (semFork, tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO;
tmp_buf.sem_op = -1;
tmp_buf.sem_num = last - 1;
semop (semFork, tmp_buf, 1);
}
/* Берем сначала нечетную вилку (если обе нечетные - то с большим номером) */
static void get_forks_parity (struct mudrec *this) {
struct sembuf tmp_buf;
int left = this->left_fork, right = this->right_fork;
int first = max ((left 1) * 1000 + left, (right 1) * 1000 + right) % 1000;
int last = min ((left 1) * 1000 + left, (right 1) * 1000 + right) % 1000;
tmp_buf.sem_flg = SEM_UNDO;
tmp_buf.sem_op = -1;
tmp_buf.sem_num = first - 1;
semop (semFork, tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO;
tmp_buf.sem_op = -1;
tmp_buf.sem_num = last - 1;
semop (semFork, tmp_buf, 1);
}
/* Берем вилки по очереди, в произвольном порядке.
* Но если вторая вилка не берется сразу, то кладем первую.
* То есть философ не расходует вилочное время впустую.
*/
static void get_forks_maybe_infinit_time (struct mudrec *this) {
struct sembuf tmp_buf;
int left = this->left_fork, right = this->right_fork;
for (;;) {
tmp_buf.sem_flg = SEM_UNDO; /* Первую вилку берем с ожиданием */
tmp_buf.sem_op = -1;
tmp_buf.sem_num = left - 1;
semop (semFork, tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO | IPC_NOWAIT; /* Вторую - без ожидания */
tmp_buf.sem_op = -1;
tmp_buf.sem_num = right - 1;
if (0 == semop (semFork, tmp_buf, 1)) return; /* Успех */
tmp_buf.sem_flg = 0; /* Неуспех: возвращаем первую вилку */
tmp_buf.sem_op = 1;
tmp_buf.sem_num = left - 1;
semop(semFork,tmp_buf,1);
tmp_buf.sem_flg = SEM_UNDO; /* Отдав первую, ждем вторую */
tmp_buf.sem_op = -1;
tmp_buf.sem_num = right - 1;
semop (semFork, tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO | IPC_NOWAIT; /* Берем первую вилку без ожидания */
tmp_buf.sem_op = -1;
tmp_buf.sem_num = left - 1;
if (0 == semop (semFork, tmp_buf, 1)) return; /* Успех */
tmp_buf.sem_flg = 0; /* Неуспех: отдаем вторую вилку, */
tmp_buf.sem_op = 1; /* чтобы ждать первую */
tmp_buf.sem_num = right - 1;
semop (semFork, tmp_buf, 1);
}
}
/* Хватаем обе вилки сразу, используя групповые операции */
static void get_forks_use_groups (struct mudrec *this) {
struct sembuf tmp_buf [2];
tmp_buf[0].sem_flg = SEM_UNDO;
tmp_buf[0].sem_op = -1;
tmp_buf[0].sem_num = this->left_fork - 1;
tmp_buf[1].sem_flg = SEM_UNDO;
tmp_buf[1].sem_op = -1;
tmp_buf[1].sem_num = this->right_fork - 1;
semop (semFork, tmp_buf, 2);
}
/*
* Мелкие служебные функции.
*/
static void stop (int dummy) {
struct sembuf tmp_buf;
tmp_buf.sem_flg = 0;
tmp_buf.sem_op = 1;
tmp_buf.sem_num = 0;
semop (Stop, tmp_buf, 1);
}
void cleanup_ipc (void) {
/*
* Уничтожение семафоров.
*/
semctl (semFork, 1, IPC_RMID);
semctl (Stop, 1, IPC_RMID);
/* То же с очередью */
msgctl (from_fil, IPC_RMID, NULL);
}
static void usage (char name []) {
fprintf (stderr,"Использование: %s [-a | -p | -I| -V] [-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;
union semun tmp_arg;
int nMudr;
struct sigaction sact;
while ((c = getopt (argc, argv, "apIVt:")) != -1) {
switch (c) {
case 'a': get_forks = get_forks_simple; break;
case 'p': get_forks = get_forks_parity; break;
case 'I': get_forks = get_forks_maybe_infinit_time; break;
case 'V': get_forks = get_forks_use_groups; 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);
/*
* Создадим семафоры для охраны вилок
*/
semFork = semget (ftok (argv [0], 2), nMudr, IPC_CREAT | 0777);
tmp_arg.val = 1;
for (i=1; i <= nMudr; i++)
semctl (semFork, i - 1, SETVAL, tmp_arg); /* Начальное значение 1 */
/* Прежде чем впускать философов, обеспечим окончание обеда */
Stop = semget (ftok (argv [0], 3), 1, IPC_CREAT | 0777);
tmp_arg.val = 0;
semctl (Stop, 0, SETVAL, tmp_arg); /* Начальное значение 0 */
/* Очередь для возврата результатов */
from_fil = msgget (ftok (argv [0], 4), IPC_CREAT | 0777);
atexit (cleanup_ipc); /* Запланировали уничтожение семафоров */
/* и других средств межпроцессного взаимодействия */
/*
* Философы входят в столовую
*/
for (i = 0; i < nMudr; i++, optind++) {
struct mudrec next;
memset (next, 0, sizeof (next));
next.num = i + 1; /* Номер */
next.name = argv [optind]; /* Имя */
/* Указали, какими вилками пользоваться */
next.left_fork = i + 1;
next.right_fork = i + 2;
if (i == nMudr - 1)
next.right_fork = 1; /* Последний пользуется вилкой первого */
filosof (next);
}
/* Зададим реакцию на сигналы и установим будильник на конец обеда */
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);
/*
* Выдача сообщений на стандартный вывод и выход после окончания обеда.
*/
close (pFdIn); /* Сами должны закрыть, иначе из цикла не выйдем! */
for (;;) {
n = read (pFdOut, buffer, LINE_MAX);
if ((n == 0) || ((n == -1) (errno != EINTR))) break;
for (i = 0; i < n; i++) putchar (buffer [i]);
}
close (pFdOut);
/* Распечатали сводную информацию */
{
int full_eating_time = 0;
int full_waiting_time = 0;
int full_thinking_time = 0;
for (i = 1; i <= nMudr; i++) {
struct mudrec this;
/* Получили статистику обеда */
msgrcv (from_fil, this, sizeof (this), i, 0); /* За счет i получаем */
/* строго по порядку */
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);
}
{
float total_time = (full_eating_time + full_waiting_time
+ full_thinking_time) / (float)nMudr;
printf (" Среднее число одновременно едящих = %.3f\n Среднее число одновременно ждущих = %.3f\n",
full_eating_time / total_time, full_waiting_time / total_time);
}
}
/* Сообщим об окончании работы */
printf ("Конец обеда\n");
return 0;
}
Получит ли в конце концов философ вилки при групповых операциях (опция -V ), зависит от реализации. Может случиться так, что хотя бы одна из них в каждый момент времени будет в руках у одного из соседей. То же верно и для "интеллигентной" стратегии (опция -I ). Тем не менее, результаты моделирования показывают, что на практике две последние стратегии эффективнее в смысле минимизации времени ожидания вилок.
Отметим небольшие терминологические различия в двух приведенных вариантах решения
С методической точки зрения второй вариант интересен тем, что в нем использованы все рассмотренные нами средства SEM_UNDO обеспечивает корректировку
В листинге 8.39 приведена статистика поведения пяти философов для всех четырех стратегий при времени моделирования 100 секунд. Эти результаты говорят в пользу групповых операций над
-a: A: ел 2 раза в среднем: думал=3.5 ел=11.5 ждал=36.5 (максимум 73) B: ел 3 раза в среднем: думал=5.7 ел=7.7 ждал=20.0 (максимум 41) C: ел 3 раза в среднем: думал=5.7 ел=11.3 ждал=17.0 (максимум 33) D: ел 3 раза в среднем: думал=1.7 ел=16.7 ждал=15.7 (максимум 19) E: ел 1 раз в среднем: думал=10.0 ел=20.0 ждал=73.0 (максимум 41) Среднее число одновременно едящих = 1.471 Среднее число одновременно ждущих = 2.980 -p: A: ел 3 раза в среднем: думал=3.7 ел=15.3 ждал=16.0 (максимум 34) B: ел 4 раза в среднем: думал=5.0 ел=13.8 ждал=8.2 (максимум 15) C: ел 3 раза в среднем: думал=6.7 ел=3.7 ждал=25.7 (максимум 27) D: ел 4 раза в среднем: думал=5.8 ел=8.5 ждал=13.8 (максимум 28) E: ел 3 раза в среднем: думал=5.3 ел=15.3 ждал=16.7 (максимум 29) Среднее число одновременно едящих = 1.761 Среднее число одновременно ждущих = 2.413 -I: A: ел 5 раз в среднем: думал=4.2 ел=9.4 ждал=6.6 (максимум 15) B: ел 3 раза в среднем: думал=6.3 ел=10.3 ждал=17.0 (максимум 31) C: ел 4 раза в среднем: думал=6.8 ел=7.0 ждал=12.2 (максимум 45) D: ел 3 раза в среднем: думал=4.3 ел=16.0 ждал=13.0 (максимум 16) E: ел 4 раза в среднем: думал=5.8 ел=8.5 ждал=10.8 (максимум 22) Среднее число одновременно едящих = 1.858 Среднее число одновременно ждущих = 2.125 -V: A: ел 5 раз в среднем: думал=5.6 ел=5.6 ждал=8.8 (максимум 17) B: ел 3 раза в среднем: думал=6.3 ел=10.3 ждал=16.7 (максимум 20) C: ел 4 раза в среднем: думал=4.8 ел=11.0 ждал=9.8 (максимум 18) D: ел 4 раза в среднем: думал=5.2 ел=12.0 ждал=8.8 (максимум 15) E: ел 4 раза в среднем: думал=5.2 ел=10.5 ждал=10.2 (максимум 20) Среднее число одновременно едящих = 1.892 Среднее число одновременно ждущих = 2.049
В стандарте POSIX-2001 разделяемый объект памяти определяется как объект, представляющий собой память, которая может быть параллельно отображена в адресное пространство более чем одного процесса.
Таким образом, процессы могут иметь общие области
Работа с разделяемой памятью начинается с того, что один из взаимодействующих процессов посредством функции shmget()
Чтобы получить доступ к shmat() ), т. е. разместить shmdt().
Предусмотрена возможность выполнения shmctl() ).
Описание перечисленных функций представлено в листинге 8.40.
#include <sys/shm.h>
int shmget (key_t key, size_t size,
int shmflg);
void *shmat (int shmid, const void *shmaddr,
int shmflg);
int shmdt (const void *shmaddr);
int shmctl (int shmid, int cmd,
struct shmid_ds *buf);
Структура shmid_ds, ассоциированная с
struct ipc_perm shm_perm; /* Данные о правах доступа к разделяемому сегменту */ size_t shm_segsz; /* Размер сегмента в байтах */ pid_t shm_lpid; /* Идентификатор процесса, выполнившего последнюю операцию над разделяемым сегментом */ pid_t shm_cpid; /* Идентификатор процесса, создавшего разделяемый сегмент */ shmatt_t shm_nattch; /* Текущее число присоединений сегмента */ time_t shm_atime; /* Время последнего присоединения */ time_t shm_dtime; /* Время последнего отсоединения */ time_t shm_ctime; /* Время последнего изменения посредством shmctl() */
Функция shmget() аналогична msgget() и semget() ; аргумент size задает нижнюю границу размера
Структура shmid_ds инициализируется в соответствии с общими для средств shm_segsz устанавливается равным значению аргумента size.
Число уникальных shmget() (возвращается -1 ). Вызов shmget() завершится неудачей и тогда, когда значение аргумента size меньше минимально допустимого либо больше максимально допустимого размера
Чтобы присоединить shmat(). Аргумент shmid задает shmaddr - адрес, по которому SHM_RND, адрес присоединения округляется до величины, кратной константе SHMLBA.
Если shmaddr задан как пустой указатель, реализация выбирает адрес присоединения по своему усмотрению.
По умолчанию присоединяемый SHM_RDONLY предписывает присоединить
При успешном завершении функции shmat() результат равен адресу, который получил присоединенный -1. (Разумеется, для использования результата shmat() в качестве указателя его нужно преобразовать к требуемому типу.)
Отсоединение shmdt() ; аргумент shmaddr задает начальный адрес отсоединяемого
Управление shmctl(), аналогичной msgctl(). Как и для IPC_STAT (получить информацию о состоянии IPC_SET (переустановить характеристики), IPC_RMID (удалить
Аппарат
Для "создания" подобного механизма необходимо породить
#include <unistd.h>
#include <stdio.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
int main (void) {
struct region {
pid_t fpid;
} *shm_ptr;
struct sembuf P = {0, -1, 0};
struct sembuf V = {0, 1, 0};
int shmid;
int semid;
shmid = shmget (IPC_PRIVATE, sizeof (struct region), 0777);
semid = semget (IPC_PRIVATE, 1, 0777);
(void) semctl (semid, 0, SETVAL, 1);
switch (fork ()) {
case -1:
perror ("FORK");
return (1);
case 0:
if ((int) (shm_ptr = (struct region *) shmat (shmid, NULL, 0)) == (-1)) {
perror ("CHILD-SHMAT");
return (2);
}
if (semop (semid, p, 1) != 0) {
perror ("CHILD-SEMOP-P");
return (3);
}
printf ("Процесс-потомок вошел в критический интервал\n");
shm_ptr->fpid = getpid (); /* Монопольный доступ */
printf ("Процесс-потомок перед выходом из критического интервала\n");
if (semop (semid, V, 1) != 0) {
perror ("CHILD-SEMOP-V");
return (4);
}
(void) shmdt (shm_ptr);
return 0;
}
if ((int) (shm_ptr = (struct region *) shmat (shmid, NULL, 0)) == (-1)) {
perror ("PARENT-SHMAT");
return (2);
}
if (semop (semid, p, 1) != 0) {
perror ("PARENT-SEMOP-P");
return (3);
}
printf ("Родительский процесс вошел в критический интервал\n");
shm_ptr->fpid = getpid (); /* Монопольный доступ */
printf ("Родительский процесс перед выходом из критического интервала\n");
if (semop (semid, V, 1) != 0) {
perror ("PARENT-SEMOP-V");
return (4);
}
(void) wait (NULL);
printf ("Идентификатор родительского процесса: %d\n", getpid ());
printf ("Идентификатор процесса в разделяемой структуре: %d\n", shm_ptr->fpid);
(void) shmdt (shm_ptr);
(void) semctl (semid, 1, IPC_RMID);
(void) shmctl (shmid, IPC_RMID, NULL);
return 0;
}
Результат работы приведенной программы может выглядеть так, как показано в листинге 8.42.
Родительский процесс вошел в критический интервал Родительский процесс перед выходом из критического интервала Процесс-потомок вошел в критический интервал Процесс-потомок перед выходом из критического интервала Идентификатор родительского процесса: 2161 Идентификатор процесса в разделяемой структуре: 2162
В листинге 8.43 представлен пример использования SIGSEGV, который посылается процессу при некорректном обращении к памяти. Идея в том, чтобы
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Реализация "виртуальной" памяти из одного сегмента. */
/* Используются разделяемые сегменты памяти */
/* и обработка сигнала SIGSEGV */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include <signal.h>
/* Константа, зависящая от реализации */
#define SHM_BASE_ADDR 0x40014000
static int shm_id = -1;
static void *shm_addr;
/* Реакция на сигнал SIGSEGV. */
/* Создаем и присоединяем на чтение разделяемый сегмент, */
/* накрывающий переданный адрес. */
/* Если это не помогло, переприсоединяем сегмент на запись */
static void sigsegv_sigaction (int sig, siginfo_t *sig_info, void *addr) {
struct shmid_ds shmid_ds;
if (shm_id == -1) {
/* Сегмента еще нет. Создадим */
if ((shm_id = shmget (IPC_PRIVATE, SHMLBA, S_IRUSR)) == -1) {
perror ("SHMGET");
exit (1);
}
/* Присоединим сегмент на чтение */
if ((int) (shm_addr = shmat (shm_id, sig_info->si_addr, SHM_RDONLY | SHM_RND)) == (-1)) {
perror ("SHMAT-RDONLY");
exit (2);
}
return;
} else {
/* Сегмент уже есть, но обращение по адресу вызвало сигнал SIGSEGV. */
/* Значит, это была попытка записи, и сегмент нужно */
/* переприсоединить на запись, поменяв соответственно режим доступа */
if (shmctl (shm_id, IPC_STAT, shmid_ds) == -1) {
perror ("SHMCTL-IPC_STAT");
exit (3);
}
shmid_ds.shm_perm.mode |= S_IWUSR;
if (shmctl (shm_id, IPC_SET, shmid_ds) == -1) {
perror ("SHMCTL-IPC_SET");
exit (4);
}
(void) shmdt (shm_addr);
if (shmat (shm_id, shm_addr, 0) != shm_addr) {
perror ("SHMAT-RDWD");
exit (5);
}
}
}
int main (void) {
char *test_ptr;
struct sigaction sact;
/* Установим реакцию на сигнал SIGSEGV */
(void) sigemptyset (sact.sa_mask);
sact.sa_flags = SA_SIGINFO;
sact.sa_sigaction = sigsegv_sigaction;
(void) sigaction (SIGSEGV, sact, (struct sigaction *) NULL);
/* Убедимся, что разделяемые сегменты инициализируются нулями */
test_ptr = (char *) (SHM_BASE_ADDR + 3);
printf ("Результат попытки чтения до записи: %x\n", *test_ptr);
/* Попробуем записать */
*test_ptr = 'A';
printf ("Результат попытки чтения после записи: %x\n", *test_ptr);
return (shmctl (shm_id, IPC_RMID, NULL));
}
Обратим внимание на использование флагов округления адреса SHM_RND ) и присоединения только на чтение ( SHM_RDONLY ), а также обработчика сигналов, задаваемого полем sa_sigaction структуры типа sigaction (в сочетании с флагом SA_SIGINFO ) и имеющего доступ к расширенной информации о сигнале и его причинах.
Средства
К числу наиболее простых и в то же время самых употребительных средств и одноименной служебной программой, а вторые - функцией pipe(). Именованным
Взаимодействие между процессами через read() и write(). Примером подобного взаимодействия служит программа, показанная в листинге 8.1.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через канал. */
/* Используются функции ввода/вывода нижнего уровня */
#define MY_PROMPT "Вводите строки\n"
#define MY_MSG "Вы ввели: "
int main (void) {
int fd [2];
char buf [1];
int new_line = 1; /* Признак того, что надо выдать сообщение MY_MSG */
/* перед отображением очередной строки */
/* Создадим безымянный канал */
if (pipe (fd) < 0) {
perror ("PIPE");
exit (1);
}
switch (fork ()) {
case -1:
perror ("FORK");
exit (2);
case 0:
/* Чтение из канала и выдачу на стандартный вывод */
/* реализуем в порожденном процессе. */
/* Необходимо закрыть дескриптор, предназначенный */
/* для записи в канал, иначе чтение не завершится */
/* по концу файла */
close (fd [1]);
while (read (fd [0], buf, 1) == 1) {
if (write (1, buf, 1) != 1) {
perror ("WRITE TO STDOUT");
break;
}
}
exit (0);
}
/* Чтение со стандартного ввода и запись в канал */
/* возложим на родительский процесс. */
/* Из соображений симметрии закроем дескриптор, */
/* предназначенный для чтения из канала */
close (fd [0]);
if (write (fd [1], MY_PROMPT, sizeof (MY_PROMPT) - 1) !=
sizeof (MY_PROMPT) - 1) {
perror ("WRITE TO PIPE-1");
}
while (read (0, buf, 1) == 1) {
/* Перед отображением очередной строки */
/* нужно выдать сообщение MY_MSG */
if (new_line) {
if (write (fd [1], MY_MSG, sizeof (MY_MSG) - 1) != sizeof (MY_MSG) - 1) {
perror ("WRITE TO PIPE-2");
break;
}
}
if (write (fd [1], buf, 1) != 1) {
perror ("WRITE TO PIPE-3");
break;
}
new_line = (buf [0] == '\n');
}
close (fd [1]);
(void) wait (NULL);
return (0);
}
Решение той же задачи, но с использованием функций
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include <sys/wait.h>
#include <assert.h>
/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через канал. */
/* Используются функции буферизованного ввода/вывода */
int main (void) {
int fd [2];
FILE *fp [2];
char line [LINE_MAX];
/* Создадим безымянный канал */
if (pipe (fd) < 0) {
perror ("PIPE");
exit (1);
}
/* Сформируем потоки по файловым дескрипторам канала */
assert ((fp [0] = fdopen (fd [0], "r")) != NULL);
assert ((fp [1] = fdopen (fd [1], "w")) != NULL);
/* Отменим буферизацию вывода */
setbuf (stdout, NULL);
setbuf (fp [1], NULL);
switch (fork ()) {
case -1:
perror ("FORK");
exit (2);
case 0:
/* Чтение из канала и выдачу на стандартный вывод */
/* реализуем в порожденном процессе. */
/* Необходимо закрыть поток, предназначенный для */
/* записи в канал, иначе чтение не завершится */
/* по концу файла */
fclose (fp [1]);
while (fgets (line, sizeof (line), fp [0]) != NULL) {
if (fputs (line, stdout) == EOF) {
break;
}
}
exit (0);
}
/* Чтение со стандартного ввода и запись в канал */
/* возложим на родительский процесс. */
/* Из соображений симметрии закроем поток, */
/* предназначенный для чтения из канала */
fclose (fp [0]);
fputs ("Вводите строки\n", fp [1]);
while (fgets (line, sizeof (line), stdin) != NULL) {
if ((fputs ("Вы ввели: ", fp [1]) == EOF) ||
(fputs (line, fp [1]) == EOF)) {
break;
}
}
fclose (fp [1]);
(void) wait (NULL);
return (0);
}
Если не указано противное, обмен данными через O_NONBLOCK (это может быть сделано при помощи функции fcntl() ). В таком случае чтение или запись, которые невозможно выполнить немедленно, завершаются неудачей.
Подчеркнем, что при попытке чтения из пустого fd [1], возвращенного функцией pipe() как дескриптор для записи в
Функция popen(), описанная выше, при рассмотрении командного интерпретатора, является более высокоуровневой по сравнению с pipe(). Она делает сразу несколько вещей: порождает процесс, обеспечивает выполнение в его рамках заданной команды, организует popen() задан режим " w ", то стандартный ввод команды, выполняющейся в рамках порожденного процесса, перенаправляется на конец r ", то в
После вызова popen() процесс может писать в pclose() (см. листинг 8.3).
#include <stdio.h> int pclose (FILE *stream);
Функция pclose() не только закрывает popen(), но и дожидается завершения порожденного процесса, возвращая его статус.
Типичное применение popen() - организация lp (см. листинг 8.4).
#include <stdio.h>
/* Программа печатает несколько первых строк треугольника Паскаля */
#define T_SIZE 16
int main (void) {
FILE *outptr;
long tp [T_SIZE]; /* Массив для хранения текущей строки треугольника */
int i, j;
/* Инициализируем массив, чтобы далее все элементы */
/* можно было считать и выводить единообразно */
tp [0] = 1;
for (i = 1; i < T_SIZE; i++) {
tp [i] = 0;
}
/* Создадим канал с командой */
if ((outptr = popen ("lp", "w")) == NULL) {
perror ("POPEN");
return (-1);
}
(void) fprintf (outptr, "\nТреугольник Паскаля:\n");
for (i = 0; i < T_SIZE; i++) {
/* Элементы очередной строки нужно считать от конца к началу */
/* Элемент tp [0] пересчитывать не нужно */
for (j = i; j > 0; j--) {
tp [j] += tp [j - 1];
}
/* Вывод строки треугольника в канал */
for (j = 0; j <= i; j++) {
(void) fprintf (outptr, " %ld", tp [j]);
}
(void) fprintf (outptr, "\n");
}
return (pclose (outptr));
}
Сходным образом можно организовать
#include <stdio.h>
#include <limits.h>
#include <assert.h>
#define MY_CMD "ls -l *.c"
int main (void) {
FILE *inptr;
char line [LINE_MAX];
assert ((inptr = popen (MY_CMD, "r")) != NULL);
while (fgets (line, sizeof (line), inptr) != NULL) {
fputs (line, stdout);
}
return (pclose (inptr));
}
Как и
Согласно стандарту POSIX-2001, под
Говорят, что kill() и т.п.). Иногда по одному
В момент
В каждом процессе определены действия, предпринимаемые в ответ на все предусмотренные системой сигналы. Говорят, что sigwait().
В интервале от генерации до доставки или принятия
У каждого
С
SIG_DFL
Подразумеваемые действия, зависящие от сигнала. Они описаны в заголовочном файле <signal.h>.
SIG_IGN
указатель на функцию
Обработать сигнал, выполнив при его доставке заданную функцию. После завершения функции обработки процесс возобновляет выполнение с точки прерывания. Обычно функция обработки вызывается в соответствии со следующим C-заголовком: void func (int signo) ; где signo - номер доставленного сигнала.
Первоначально, до входа в функцию main(), реакция на все SIG_DFL или SIG_IGN.
Функция называется асинхронно-сигнально-безопасной (АСБ), если ее можно вызывать без каких-либо ограничений при обработке сигналов. В стандарте POSIX-2001 имеется список функций, которые должны быть либо
Если
Перейдем к изложению возможностей по kill как средство терминирования процессов извне. На самом деле она посылает заданный сигнал; то же делает и одноименная функция (см. листинг 8.6).
#include <signal.h> int kill (pid_t pid, int sig);
Сигнал задается аргументом , значение которого может быть нулевым; в этом случае действия функции kill() сводятся к проверке допустимости значения pid (нулевой результат - признак успешного завершения kill() ).
Если pid > 0, это значение трактуется как идентификатор процесса. При нулевом значении pid pid равно -1, адресатами являются все процессы, которым вызывающий имеет право pid pid.
Процесс имеет право послать pid, если он (процесс) имеет соответствующие привилегии или его реальный или
У служебной программы kill имеется полезная опция -l, позволяющая увидеть соответствие между номерами kill -l может выглядеть так, как показано в листинге 8.7.
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1 34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5 38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9 42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13 46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14 50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10 54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6 58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2 62) SIGRTMAX-1 63) SIGRTMAX
Мы не будем пояснять назначение всех представленных в листинге сигналов, ограничившись кратким описанием тех, что фигурируют в стандарте POSIX-2001 как обязательные для реализации. Попутно отметим, что, согласно стандарту языка C, должны быть определены имена всего шести сигналов: SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV и SIGTERM.
SIGABRT
Сигнал аварийного завершения процесса. Подразумеваемая реакция предусматривает, помимо аварийного завершения, создание файла с образом памяти процесса.
SIGALRM
Срабатывание будильника. Подразумеваемая реакция - аварийное завершение процесса.
SIGBUS
Ошибка системной шины как следствие обращения к неопределенной области памяти. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGCHLD
Завершение, остановка или продолжение порожденного процесса. Подразумеваемая реакция - игнорирование.
SIGCONT
Продолжение процесса, если он был остановлен. Подразумеваемая реакция - продолжение выполнения или игнорирование (если процесс не был остановлен).
SIGFPE
Некорректная арифметическая операция. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGHUP
Сигнал разъединения. Подразумеваемая реакция - аварийное завершение процесса.
SIGILL
Некорректная команда. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGINT
SIGKILL
Уничтожение процесса (этот
SIGPIPE
Попытка записи в
SIGQUIT
Сигнал выхода, поступивший с терминала. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGSEGV
Некорректное обращение к памяти. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGSTOP
Остановка выполнения (этот
SIGTERM
Сигнал терминирования. Подразумеваемая реакция - аварийное завершение процесса.
SIGTSTP
Сигнал остановки, поступивший с терминала. Подразумеваемая реакция - остановка процесса.
SIGTTIN
Попытка чтения из фонового процесса. Подразумеваемая реакция - остановка процесса.
SIGTTOU
Попытка записи из фонового процесса. Подразумеваемая реакция - остановка процесса.
SIGUSR1, SIGUSR2
Определяемые пользователем сигналы. Подразумеваемая реакция - аварийное завершение процесса.
SIGPOLL
Опрашиваемое
SIGPROF
Срабатывание
SIGSYS
Некорректный системный вызов. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGTRAP
Попадание в точку трассировки/прерывания. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGURG
Высокоскоростное поступление данных в сокет. Подразумеваемая реакция - игнорирование.
SIGVTALRM
Срабатывание виртуального таймера. Подразумеваемая реакция - аварийное завершение процесса.
SIGXCPU
Исчерпан лимит процессорного времени. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
SIGXFSZ
Превышено ограничение на размер файлов. Подразумеваемая реакция - аварийное завершение и создание файла с образом памяти процесса.
Процесс ( raise() (см. листинг 8.8). Для процесса вызов raise() эквивалентен kill (
#include <signal.h> int raise (int sig);
abort() (см. листинг 8.9), вызывающей аварийное завершение процесса. (Заметим, что этого не произойдет, если SIGABRT не возвращает управления. С другой стороны, abort() отменяет блокирование или игнорирование SIGABRT.)
#include <stdlib.h> void abort (void);
Опросить и изменить sigaction() (см. листинг 8.10).
#include <signal.h>
int sigaction (int sig, const struct sigaction
*restrict act, struct sigaction
*restrict oact);
Для описания sigaction, которая должна содержать по крайней мере следующие поля:
void (*sa_handler) (int); /* Указатель на функцию обработки сигнала */ /* или один из макросов SIG_DFL или SIG_IGN */ sigset_t sa_mask; /* Дополнительный набор сигналов, блокируемых */ /* на время выполнения функции обработки */ int sa_flags; /* Флаги, влияющие на поведение сигнала */ void (*sa_sigaction) (int, siginfo_t *, void *); /* Указатель на функцию обработки сигнала */
Приложение, соответствующее стандарту, не должно одновременно использовать поля обработчиков sa_handler и sa_sigaction.
Тип sigset_t может быть целочисленным или структурным и представлять
Тип siginfo_t должен быть структурным по крайней мере со следующими полями:
int si_signo; /* Номер сигнала */ int si_errno; /* Значение переменной errno, ассоциированное с данным сигналом */ int si_code; /* Код, идентифицирующий причину сигнала */ pid_t si_pid; /* Идентификатор процесса, пославшего сигнал */ uid_t si_uid; /* Реальный идентификатор пользователя процесса, пославшего сигнал */ void *si_addr; /* Адрес, вызвавший генерацию сигнала */ int si_status; /* Статус завершения порожденного процесса */ long si_band; /* Событие, связанное с сигналом SIGPOLL */
В заголовочном файле <signal.h> определены именованные константы, предназначенные для работы с полем si_code, значения которого могут быть как специфичными для конкретного сигнала, так и универсальными. К числу универсальных кодов относятся:
SI_USER
Сигнал послан функцией kill().
SI_QUEUE
Сигнал послан функцией sigqueue().
SI_TIMER
Сигнал сгенерирован в результате timer_settime().
SI_ASYNCIO
Сигнал вызван завершением асинхронной операции ввода/вывода.
SI_MESGQ
Сигнал вызван приходом сообщения в пустую
Из кодов, специфичных для конкретных сигналов, мы упомянем лишь несколько, чтобы дать представление о степени детализации диагностики, предусмотренной стандартом POSIX-2001. (Из имени константы ясно, к какому
ILL_ILLOPC
Некорректный код операции.
ILL_COPROC
Ошибка сопроцессора.
FPE_INTDIV
Целочисленное деление на нуль.
FPE_FLTOVF
Переполнение при выполнении операции
FPE_FLTSUB
Индекс вне диапазона.
SEGV_MAPERR
Адрес не отображен на объект.
BUS_ADRALN
Некорректное выравнивание адреса.
BUS_ADRERR
Несуществующий физический адрес.
TRAP_BRKPT
Процесс достиг точки прерывания.
TRAP_TRACE
Срабатывание трассировки процесса.
CLD_EXITED
Завершение порожденного процесса.
CLD_STOPPED
Остановка порожденного процесса.
POLL_PRI
Поступили высокоприоритетные данные.
Вернемся непосредственно к описанию функции sigaction(). Если аргумент act отличен от NULL, он указывает на структуру, специфицирующую действия, которые будут ассоциированы с . По адресу oact (если он не NULL ) возвращаются сведения о прежних действиях. Если значение act есть NULL, обработка
Следующие флаги в поле sa_flags влияют на поведение .
SA_NOCLDSTOP
Не генерировать SIGCHLD при остановке или продолжении порожденного процесса (значение аргумента должно равняться SIGCHLD ).
SA_RESETHAND
При входе в установить подразумеваемую реакцию SIG_DFL и очистить флаг SA_SIGINFO (см. далее).
SA_SIGINFO
Если этот флаг не установлен и определена , она вызывается с одним целочисленным аргументом - номером сигнала. Соответственно, в приложении следует использовать поле sa_handler структуры sigaction. При установленном флаге SA_SIGINFO функция обработки вызывается с двумя дополнительными аргументами, как void func (int второй аргумент указывает на данные, поясняющие причину ucontext_t - sa_sigaction и поля структуры типа siginfo_t. В частности, если значение si_code неположительно, si_pid и реальным идентификатором пользователя si_uid.
SA_NODEFER
По умолчанию обрабатываемый SA_NODEFER предписывает не делать этого, если только не фигурирует явным образом в sa_mask.
Опросить и изменить
trap [действие условие ...]
Аргумент "условие" может задаваться как EXIT (завершение командного интерпретатора) или как имя ). При задании аргумента "действие" минус обозначает подразумеваемую реакцию, пустая цепочка ("") - игнорирование. Если в качестве действия задана команда, то при наступлении условия она обрабатывается как eval действие.
Команда trap без аргументов выдает на стандартный вывод список команд, ассоциированных с каждым из условий. Выдача имеет формат, пригодный для восстановления
save_traps=$(trap) . . . eval "$save_traps"
Обеспечить выполнение утилиты logout из домашнего каталога пользователя во время завершения командного интерпретатора можно с помощью команды, показанной в листинге 8.12.
trap '$HOME/logout' EXIT
При SIGPIPE (см. листинг 8.13).
trap "" PIPE echo "$INITLOG_ARGS -n $0 -s \"$1\" -e 1" >21 trap - PIPE
К техническим аспектам можно отнести работу с sigemptyset() и sigfillset() инициализируют набор, делая его, соответственно, пустым или "полным". Функция sigaddset() добавляет signo к набору set, sigdelset() удаляет сигнал, а sigismember() проверяет вхождение в набор. Обычно признаком завершения является нулевой результат, в случае ошибки возвращается -1. Только sigismember() выдает 1, если signo входит в набор set.
#include <signal.h>
int sigemptyset (sigset_t *set);
int sigfillset (sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset (sigset_t *set, int signo);
int sigismember (const sigset_t *set,
int signo);
Функция sigprocmask() (см. листинг 8.15) предназначена для опроса и/или изменения
#include <signal.h> int sigprocmask (int how, const sigset_t *restrict set, sigset_t *restrict oset);
Если аргумент set отличен от NULL, он указывает на набор, используемый для изменения текущей how определяет способ изменения; он может принимать одно из трех значений: SIG_BLOCK (результирующая маска получается при объединении текущей и заданной аргументом set ), SIG_SETMASK (результирующая маска устанавливается равной set ) и SIG_UNBLOCK (маска set вычитается из текущей).
По адресу oset (если он не NULL ) возвращается прежняя маска. Если значение set есть NULL, набор блокируемых
Если к моменту завершения sigprocmask() будут существовать ждущие неблокированные сигналы, по крайней мере один из них должен быть доставлен до возврата из sigprocmask().
Нельзя
Функция sigpending() (см. листинг 8.16) позволяет выяснить набор блокированных сигналов, ожидающих доставки вызывающему процессу ( sigwait() (см. листинг 8.17).
#include <signal.h> int sigpending (sigset_t *set);
#include <signal.h>
int sigwait (const sigset_t *restrict set,
int *restrict sig);
Функция sigwait() выбирает . Если в момент вызова sigwait() нужного
Отметим, что стандарт POSIX-2001 не специфицирует воздействие функции sigwait() на обработку сигналов, включенных в набор set. Чтобы дождаться доставки обрабатываемого или терминирующего процесс сигнала, можно воспользоваться функцией pause() (см. листинг 8.18).
#include <unistd.h> int pause (void);
Функция pause() может ждать pause() осуществляется после возврата из -1 ). Если pause(), естественно, не происходит.
Несмотря на внешнюю простоту, использование функции pause() сопряжено с рядом тонкостей. При наивном подходе сначала проверяют некоторое условие, связанное с сигналом, и, если оно не выполнено (сигнал отсутствует), вызывают pause(). К сожалению, pause(), что нарушает логику работы процесса и способно привести к его зависанию. Решить подобную проблему позволяет функция sigsuspend() (см. листинг 8.19) в сочетании с рассмотренной выше функцией sigprocmask().
#include <signal.h> int sigsuspend (const sigset_t *sigmask);
Функция sigsuspend() заменяет текущую pause(). После возврата из sigsuspend() (если таковой произойдет) восстанавливается прежняя
Обычно sigprocmask() и sigsuspend() обрамляют sigprocmask() блокируют некоторые сигналы, а на выходе вызывают sigsuspend() с маской, которую возвратила sigprocmask(), восстанавливая тем самым набор блокированных
В качестве примера использования описанных выше функций работы с abort() (см. листинг 8.20).
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void abort (void) {
struct sigaction sact;
sigset_t sset;
/* Вытолкнем буфера */
(void) fflush (NULL);
/* Снимем блокировку сигнала SIGABRT */
if ((sigemptyset (sset) == 0) (sigaddset (sset, SIGABRT) == 0)) {
(void) sigprocmask (SIG_UNBLOCK, sset, (sigset_t *) NULL);
}
/* Пошлем себе сигнал SIGABRT. */
/* Возможно, его перехватит функция обработки, */
/* и тогда вызывающий процесс может не завершиться */
raise (SIGABRT);
/* Установим подразумеваемую реакцию на сигнал SIGABRT */
sact.sa_handler = SIG_DFL;
sigfillset (sact.sa_mask);
sact.sa_flags = 0;
(void) sigaction (SIGABRT, sact, NULL);
/* Снова пошлем себе сигнал SIGABRT */
raise (SIGABRT);
/* Если сигнал снова не помог, попробуем еще одно средство завершения */
_exit (127);
}
int main (void) {
printf ("Перед вызовом abort()\n");
abort ();
printf ("После вызова abort()\n");
return 0;
}
В качестве нюанса, характерного для работы с сигналами, отметим, что до первого обращения к raise() нельзя закрыть SIGABRT, возможно, осуществляет вывод.
Еще одним примером использования механизма sleep(), предназначенной для "засыпания" на заданное число секунд. (Можно надеяться, что не описанные пока средства работы с временем интуитивно понятны.)
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
/* Функция обработки сигнала SIGALRM. */
/* Она ничего не делает, но игнорировать сигнал нельзя */
static void signal_handler (int sig) {
/* В демонстрационных целях распечатаем номер обрабатываемого сигнала */
printf ("Принят сигнал %d\n", sig);
}
/* Функция для "засыпания" на заданное число секунд */
/* Результат равен разности между заказанной и фактической */
/* продолжительностью "сна" */
unsigned int sleep (unsigned int seconds) {
time_t before, after;
unsigned int slept;
sigset_t set, oset;
struct sigaction act, oact;
if (seconds == 0) {
return 0;
}
/* Установим будильник на заданное время, */
/* но перед этим блокируем сигнал SIGALRM */
/* и зададим свою функцию обработки для него */
if ((sigemptyset (set) < 0) || (sigaddset (set, SIGALRM) < 0) ||
sigprocmask (SIG_BLOCK, set, oset)) {
return seconds;
}
act.sa_handler = signal_handler;
act.sa_flags = 0;
act.sa_mask = oset;
if (sigaction (SIGALRM, act, oact) < 0) {
return seconds;
}
before = time ((time_t *) NULL);
(void) alarm (seconds);
/* Как атомарное действие восстановим старую маску сигналов */
/* (в надежде, что она не блокирует SIGALRM) */
/* и станем ждать доставки обрабатываемого сигнала */
(void) sigsuspend (oset);
/* сигнал доставлен и обработан */
after = time ((time_t *) NULL);
/* Восстановим прежний способ обработки сигнала SIGALRM */
(void) sigaction (SIGALRM, oact, (struct sigaction *) NULL);
/* Восстановим первоначальную маску сигналов */
(void) sigprocmask (SIG_SETMASK, oset, (sigset_t *) NULL);
return ((slept = after - before) > seconds ? 0 : (seconds - slept));
}
int main (void) {
struct sigaction act;
/* В демонстрационных целях установим обработку прерывания с клавиатуры */
act.sa_handler = signal_handler;
(void) sigemptyset (act.sa_mask);
act.sa_flags = 0;
(void) sigaction (SIGINT, act, (struct sigaction *) NULL);
printf ("Заснем на 10 секунд\n");
printf ("Проснулись, не доспав %d секунд\n", sleep (10));
return (0);
}
Обратим внимание на применение функции sigsuspend(), которая реализует (неделимую) транзакцию снятия sigsuspend() (после возврата из функции обработки), то автоматически восстанавливается sigsuspend(). В данном случае в этой маске блокирован SIGALRM, и потому можно спокойно менять способ его обработки.
Вызвать "недосыпание" приведенной программы можно, послав ей SIGALRM (например, посредством команды kill -s SIGALRM идентификатор_процесса) или SIGINT (путем нажатия на клавиатуре терминала комбинации клавиш CTRL+C).
Мы переходим к рассмотрению средств
Остановимся сначала на понятиях и структурах, общих для всех трех упомянутых средств.
Каждая msqid, semid и shmid и возвращается в качестве результатов функций msgget(), semget() и shmget().
При получении ftok() (см. листинг 8.22). Аргумент path должен задавать stat(). В качестве значения аргумента id, по соображениям мобильности, рекомендуется использовать однобайтный символ. Гарантируется, что функция ftok() сгенерирует один и тот же ключ для заданной пары (файл, символ) и разные ключи для разных пар.
#include <sys/ipc.h> key_t ftok (const char *path, int id);
С <sys/msg.h>, <sys/ и <sys/shm.h>.
В упомянутую структуру входит подструктура ipc_perm с данными о владельцах и режимом доступа, описанная в файле <sys/ipc.h> и содержащая по крайней мере следующие поля.
uid_t uid; /* Идентификатор владельца */ gid_t gid; /* Идентификатор владеющей группы */ uid_t cuid; /* Идентификатор пользователя, создавшего данное средство межпроцессного взаимодействия */ gid_t cgid; /* Идентификатор создавшей группы */ mode_t mode; /* Режим доступа на чтение/запись */
Управление доступом к описываемым средствам
Опросить статус присутствующих в данный момент в системе (т. е. активных) средств ipcs:
ipcs [-qms] [-a | -bcopt]
По умолчанию выдается краткая информация обо всех средствах - -q, -s и/или -m, соответственно.
Следующие опции управляют форматом выдачи. Задание опции -a равносильно указанию всех опций формата. Опция -b предписывает выдавать лимиты на размер (максимальное количество байт в сообщениях очереди и т.п.), -c - имена пользователя и группы создателя средства, -o - информацию об использовании (количество сообщений в очереди, их суммарный размер и т.п.), -p - информацию о процессах (идентификаторы последнего отправителя, получателя и т.п.), -t - информацию о времени (последняя управляющая операция, последняя
Для удаления из системы ipcrm (разумеется, подверженная контролю прав доступа). Удаляемые средства могут задаваться идентификаторами или ключами:
ipcrm [-q msgid | -Q msgkey | -s semid |
-S semkey | -m shmid | -M shmkey ] ...
На этом мы завершаем изложение общих вопросов, относящихся к средствам
Механизм
Прежде чем процессы смогут обмениваться сообщениями, один из них должен создать очередь. Одновременно определяются первоначальные права на выполнение операций для различных процессов, в том числе соответствующих управляющих действий над очередями.
Для работы с msgget() (получение msgctl() ( msgsnd() (
#include <sys/msg.h>
int msgget (key_t key, int msgflg);
int msgsnd (int msqid, const void *msgp,
size_t msgsz, int msgflg);
ssize_t msgrcv (int msqid, void *msgp,
size_t msgsz, long msgtyp,
int msgflg);
int msgctl (int msqid, int cmd,
struct msqid_ds *buf);
Структура msqid_ds, ассоциированная с
struct ipc_perm msg_perm; /* Данные о правах доступа к очереди сообщений */ msgqnum_t msg_qnum; /* Текущее количество сообщений в очереди */ msglen_t msg_qbytes; /* Максимально допустимый суммарный размер сообщений в очереди */ pid_t msg_lspid; /* Идентификатор процесса, отправившего последнее сообщение */ pid_t msg_lrpid; /* Идентификатор процесса, принявшего последнее сообщение */ time_t msg_stime; /* Время последней отправки */ time_t msg_rtime; /* Время последнего приема */ time_t msg_ctime; /* Время последнего изменения посредством msgctl() */
Перейдем к детальному рассмотрению функций для работы с
Функция msgget() возвращает key. Новая очередь, ее идентификатор и соответствующая структура msqid_ds создаются для заданного ключа, если значение аргумента key равно IPC_PRIVATE или очередь еще не ассоциирована с ключом, а в числе флагов msgflg задан IPC_CREAT.
Если необходима уверенность в том, что очередь с указанным ключом создается заново, в дополнение к флагу IPC_CREAT следует установить IPC_EXCL. Тогда попытка получить идентификатор уже существующий очереди завершится неудачей.
Структура msqid_ds для новой очереди инициализируется следующим образом.
msg_perm.cuid, msg_perm.uid, msg_perm.cgid и msg_perm.gid устанавливаются равными msg_perm.mode устанавливаются равными младшим девяти битам значения msgflg.msg_qnum, msg_lspid, msg_lrpid, msg_stime и msg_rtime обнуляются.msg_ctime помещается текущее время, а в поле msg_qbytes - определенный в системе лимит.Один из тонких вопросов, связанных с msqid необходимо знать ключ очереди. Задание ключа одинаковым константным значением во всех этих программах небезопасно, поскольку может оказаться так, что тот же ключ будет случайно задействован и другими программами. Как одно из возможных решений рекомендуется использование функции ftok(), вычисляющей действительно "уникальный" ключ.
В листинге 8.24 приведен простейший пример программы, где
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
/* Программа создает очередь сообщений. */
/* В командной строке задаются имя файла для ftok() */
/* и режим доступа к очереди сообщений */
#define FTOK_CHAR 'G'
int main (int argc, char *argv []) {
key_t key;
int msqid;
int mode = 0;
if (argc != 3) {
fprintf (stderr, "Использование: %s маршрутное_имя режим_доступа\n", argv [0]);
return (1);
}
if ((key = ftok (argv [1], FTOK_CHAR)) == (key_t) (-1)) {
perror ("FTOK");
return (2);
}
(void) sscanf (argv [2], "%o", (unsigned int *) mode);
if ((msqid = msgget (key, IPC_CREAT | mode)) < 0) {
perror ("MSGGET");
return (3);
}
return 0;
}
Если после выполнения этой программы воспользоваться командой ipcs -q, то результат может выглядеть так, как показано в листинге 8.25.
------ Message Queues -------- key msqid owner perms used-bytes messages 0x47034bac 163840 galat 644 0 0
Удалить созданную очередь из системы, соответствующей стандарту POSIX-2001, можно командой ipcrm -q 163840.
Операции msgsnd() и msgrcv() ; msgsnd() помещает сообщения в очередь, а msgrcv() читает и "достает" их оттуда.
В обоих случаях первый аргумент задает идентификатор очереди; второй является указателем на содержащую сообщение структуру. Сообщение состоит из двух частей: текста (последовательности байт) и так называемого типа (положительного целого числа). Тип, указанный во время отправки, используется впоследствии при выборе сообщения из очереди. Аргумент msgsz определяет длину сообщения; аргумент msgflg задает флаги.
В зависимости от значения, указанного в качестве аргумента msgtyp функции msgrcv(), из очереди выбирается то или иное сообщение. Если значение аргумента равно нулю, запрашивается первое сообщение в очереди, если больше нуля - первое сообщение типа msgtyp, а если меньше нуля - первое сообщение наименьшего из типов, не превышающих абсолютную величину аргумента msgtyp. Пусть, например, в очередь последовательно помещены сообщения с типами 5, 3 и 2. Тогда вызов msgrcv (msqid, msgp, size, 0, flags) выберет из msgrcv (msqid, msgp, size, -4, flags) - последнее сообщение, так как 2 - это наименьший из возможных типов в указанном диапазоне; наконец, вызов msgrcv (msqid, msgp, size, 3, flags) - сообщение с типом 3.
Во многих приложениях взаимодействующим посредством
Если не указано противное, функции msgsnd() и msgrcv() выполняют msgsnd (msqid, msgp, size, 0); msgrcv (msqid, msgp, size, type, 0). Чтобы выполнить IPC_NOWAIT: msgsnd (msqid, msgp, size, IPC_NOWAIT); msgrcv (msqid, msgp, size, type, IPC_NOWAIT).
Аргумент msgp указывает на значение структурного типа, в котором представлены тип и тело сообщения (см. листинг 8.26).
struct msgbuf {
long mtype; /* Тип сообщения */
char mtext [1]; /* Текст сообщения */
};
Для хранения реальных сообщений в прикладной программе следует определить аналогичную структуру, указав желаемый размер сообщения, например, так, как это сделано в листинге 8.27.
#define MAXSZTMSG 8192
struct mymsgbuf {
long mtype; /* Тип сообщения */
char mtext [MAXSZTMSG]; /* Текст сообщения */
};
struct mymsgbuf msgbuf;
В качестве аргумента msgsz обычно указывается размер текстового буфера, например: sizeof ( msgbuf.text ).
Если не указано противное, в случае, когда длина выбранного сообщения больше, чем msgsz, вызов msgrcv() завершается неудачей. Если же установить флаг MSG_NOERROR, длинное сообщение обрезается до msgsz байт. Отброшенная часть пропадает, а вызывающий процесс не получает никакого уведомления о том, что сообщение обрезано.
При успешном завершении msgsnd() возвращает 0, а msgrcv() - значение, равное числу реально полученных байт; при неудаче возвращается -1.
Процессы, обладающие достаточными правами доступа, посредством функции msgctl() могут получать информацию о состоянии очереди, изменять ряд характеристик, удалять очередь.
Управляющее действие определяется значением аргумента cmd. Допустимых значений три: IPC_STAT - получить информацию о состоянии очереди, IPC_SET - переустановить характеристики очереди, IPC_RMID - удалить очередь.
Команды IPC_STAT и IPC_SET для хранения информации об очереди используют имеющуюся в прикладной программе структуру типа msqid_ds, указатель на которую содержит аргумент buf: IPC_STAT копирует в нее ассоциированную с очередью структуру данных, а IPC_SET, наоборот, в соответствии с ней обновляет ассоциированную структуру. Команда IPC_SET позволяет переустановить значения идентификаторов владельца ( msg_perm.uid ) и владеющей группы ( msg_perm.gid ), режима доступа ( msg_perm.mode ), максимально допустимый суммарный размер сообщений в очереди ( msg_qbytes ). Увеличить значение msg_qbytes может только процесс, обладающий соответствующими привилегиями.
В листинге 8.28 приведена программа, изменяющая максимально допустимый суммарный размер сообщений в очереди. Предполагается, что
#include <stdio.h>
#include <sys/msg.h>
int main (int argc, char *argv []) {
int msqid;
struct msqid_ds msqid_ds;
if (argc != 3) {
fprintf (stderr, "Использование: %s идентификатор_очереди максимальный_размер\n", argv [0]);
return (1);
}
(void) sscanf (argv [1], "%d", msqid);
/* Получим исходное значение структуры данных */
if (msgctl (msqid, IPC_STAT, msqid_ds) == -1) {
perror ("IPC_STAT-1");
return (2);
}
printf ("Максимальный размер очереди до изменения: %ld\n", msqid_ds.msg_qbytes);
(void) sscanf (argv [2], "%d", (int *) msqid_ds.msg_qbytes);
/* Попробуем внести изменения */
if (msgctl (msqid, IPC_SET, msqid_ds) == -1) {
perror ("IPC_SET");
}
/* Получим новое значение структуры данных */
if (msgctl (msqid, IPC_STAT, msqid_ds) == -1) {
perror ("IPC_STAT-2");
return (3);
}
printf ("Максимальный размер очереди после изменения: %ld\n", msqid_ds.msg_qbytes);
return 0;
}
Две программы, показанные в листингах 8.29 и 8.30, демонстрируют полный цикл работы с msq_child текущего каталога.
#include <unistd.h>
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/msg.h>
/* Программа копирует строки со стандартного ввода на стандартный вывод, */
/* "прокачивая" их через очередь сообщений */
#define FTOK_FILE "/home/galat"
#define FTOK_CHAR "G"
#define MSGQ_MODE 0644
#define MY_PROMPT "Вводите строки\n"
#define MY_MSG "Вы ввели: "
int main (void) {
key_t key;
int msqid;
struct mymsgbuf {
long mtype;
char mtext [LINE_MAX];
} line_buf, msgbuf;
switch (fork ()) {
case -1:
perror ("FORK");
return (1);
case 0:
/* Чтение из очереди и выдачу на стандартный вывод */
/* реализуем в порожденном процессе. */
(void) execl ("./msq_child", "msq_child", FTOK_FILE, FTOK_CHAR, (char *) 0);
perror ("EXEC");
return (2); /* execl() завершился неудачей */
}
/* Чтение со стандартного ввода и запись в очередь */
/* возложим на родительский процесс */
/* Выработаем ключ для очереди сообщений */
if ((key = ftok (FTOK_FILE, FTOK_CHAR [0])) == (key_t) (-1)) {
perror ("FTOK");
return (3);
}
/* Получим идентификатор очереди сообщений */
if ((msqid = msgget (key, IPC_CREAT | MSGQ_MODE)) < 0) {
perror ("MSGGET");
return (4);
}
/* Приступим к отправке сообщений в очередь */
msgbuf.mtype = line_buf.mtype = 1;
strncpy (msgbuf.mtext, MY_PROMPT, sizeof (msgbuf.mtext));
if (msgsnd (msqid, (void *) msgbuf, strlen (msgbuf.mtext) + 1, 0) != 0) {
perror ("MSGSND-1");
return (5);
}
strncpy (msgbuf.mtext, MY_MSG, sizeof (msgbuf.mtext));
while (fgets (line_buf.mtext, sizeof (line_buf.mtext), stdin) != NULL) {
if (msgsnd (msqid, (void *) msgbuf, strlen (msgbuf.mtext) + 1, 0) != 0) {
perror ("MSGSND-2");
break;
}
if (msgsnd (msqid, (void *) line_buf, strlen (line_buf.mtext) + 1, 0) != 0) {
perror ("MSGSND-3");
break;
}
}
/* Удалим очередь */
if (msgctl (msqid, IPC_RMID, NULL) == -1) {
perror ("MSGCTL-IPC_RMID");
return (6);
}
return (0);
}
#include <stdio.h>
#include <limits.h>
#include <sys/msg.h>
/* Программа получает сообщения из очереди */
/* и копирует их тела на стандартный вывод */
#define MSGQ_MODE 0644
int main (int argc, char *argv []) {
key_t key;
int msqid;
struct mymsgbuf {
long mtype;
char mtext [LINE_MAX];
} msgbuf;
if (argc != 3) {
fprintf (stderr, "Использование: %s имя_файла цепочка_символов\n", argv [0]);
return (1);
}
/* Выработаем ключ для очереди сообщений */
if ((key = ftok (argv [1], *argv [2])) == (key_t) (-1)) {
perror ("CHILD FTOK");
return (2);
}
/* Получим идентификатор очереди сообщений */
if ((msqid = msgget (key, IPC_CREAT | MSGQ_MODE)) < 0) {
perror ("CHILD MSGGET");
return (3);
}
/* Цикл приема сообщений и выдачи строк */
while (msgrcv (msqid, (void *) msgbuf, sizeof (msgbuf.mtext), 0, 0) > 0) {
if (fputs (msgbuf.mtext, stdout) == EOF) {
break;
}
}
return 0;
}
Обратим внимание на способ выработки согласованного ключа, а также на то, что, вообще говоря, неизвестно, какой из процессов - родительский или порожденный - создаст очередь, а какой получит уже ассоциированный с ключом идентификатор (вызовы msgget() в обоих процессах одинаковы), но на корректность работы программы это не влияет.
Согласно определению стандарта POSIX-2001,
У
semget() ) и обрабатываются (функцией semop() ) наборами (массивами), причем операции над наборами с точки зрения приложений являются атомарными. В рамках групповых операций для любого
Процессы, обладающие соответствующими правами, также могут выполнять различные управляющие действия над semctl().
Описание перечисленных функций представлено в листинге 8.31.
#include <sys/sem.h>
int semget (key_t key, int nsems, int semflg);
int semop (int semid, struct sembuf *sops,
size_t nsops);
int semctl (int semid, int semnum,
int cmd, ...);
Структура semid_ds, ассоциированная с
struct ipc_perm sem_perm; /* Данные о правах доступа к набору семафоров */ unsigned short sem_nsems; /* Число семафоров в наборе */ time_t sem_otime; /* Время последней операции semop() */ time_t sem_ctime; /* Время последнего изменения посредством semctl() */
Отдельные
unsigned short semval; /* Значение семафора */ pid_t sempid; /* Идентификатор процесса, выполнившего последнюю операцию над семафором */ unsigned short semncnt; /* Число процессов, ожидающих увеличения текущего значения семафора */ unsigned short semzcnt; /* Число процессов, ожидающих обнуления значения семафора */
Функция semget() аналогична msgget() ; аргумент nsems задает число semid_ds инициализируется так же, как msqid_ds. Безымянные структуры, соответствующие отдельным semget() не инициализируются.
Операции, выполняемые посредством функции semop(), задаются массивом sops с числом элементов nsops, состоящим из структур типа sembuf, каждая из которых содержит по крайней мере следующие поля.
unsigned short sem_num; /* Номер семафора в наборе (нумерация с нуля) */ short sem_op; /* Запрашиваемая операция над семафором */ short sem_flg; /* Флаги операции */
Операция над sem_op: положительное значение предписывает
Выполнение массива операций с точки зрения пользовательского процесса является неделимым действием. Это значит, во-первых, что если операции выполняются, то только все вместе и, во-вторых, никакой другой процесс не может получить доступ к промежуточному состоянию semop() приостанавливается ( semop() значения всех
Приведенный в листинге 8.32 массив операций задает уменьшение (с блокировкой)
sembuf [0].sem_num = 1; sembuf [0].sem_flg = 0; sembuf [0].sem_op = -2; sembuf [1].sem_num = 0; sembuf [1].sem_flg = IPC_NOWAIT; sembuf [1].sem_op = 0;
Обращаясь к функции semctl(), процессы могут получать информацию о состоянии
Аргументы semid ( semnum (номер cmd. Если объектом является набор, значение semnum игнорируется.
Для некоторых действий задействован четвертый аргумент (см. листинг 8.33).
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
} arg;
Среди допустимых действий - GETVAL (получить SETVAL (установить arg.val ). Имеются и аналогичные групповые действия - GETALL (прочитать значения всех arg.array ) и SETALL (установить значения всех ), а также число процессов, ожидающих GETNCNT / GETZCNT )
val = semctl (semid, semnum, GETVAL); arg.val = ...; if (semctl (semid, semnum, SETVAL, arg) == -1) ...; arg.array = (unsigned short *) malloc (nsems * sizeof (unsigned short)); err = semctl (semid, 0, GETALL, arg); for (i = 0; i < nsems; i++) arg.array [i] = ...; err = semctl (semid, 0, SETALL, arg); lpid = semctl (semid, semnum, GETPID); ncnt = semctl (semid, semnum, GETNCNT); zcnt = semctl (semid, semnum, GETZCNT);
Наконец, для IPC_STAT (получить информацию о состоянии IPC_SET (переустановить характеристики), IPC_RMID (удалить
arg.buf = (struct semid_ds *) malloc (sizeof (struct semid_ds); err = semctl (semid, 0, IPC_STAT, arg); arg.buf->sem_perm.mode = 0644; err = semctl (semid, 0, IPC_SET, arg);
В качестве примера использования
Предлагаемое решение состоит из двух программ. Первая (см. листинг 8.36) реализует процесс-монитор, который порождает QPH ), ожидает, пока все процессы завершатся (все философы съедят свой обед), и удаляет ftok() ), что исходный текст программы находится в файле phdin.c (точнее, что такой файл существует).
#include <unistd.h>
#include <stdio.h>
#include <sys/sem.h>
#include <sys/wait.h>
/* Программа-монитор обеда философов */
#define QPH 5
#define ARG_SIZE 20
int main (void) {
int key; /* Ключ набора семафоров */
int semid; /* Идентификатор набора семафоров */
int no; /* Номер философа и/или вилки */
char ssemid [ARG_SIZE], sno [ARG_SIZE], sqph [ARG_SIZE];
/* Создание и инициализация набора семафоров */
/* (по семафору на вилку) */
key = ftok ("phdin.c", 'C');
if ((semid = semget (key, QPH, 0600 | IPC_CREAT)) < 0) {
perror ("SEMGET");
return (1);
}
for (no = 0; no < QPH; no++) {
if (semctl (semid, no, SETVAL, 1) < 0) {
perror ("SETVAL");
return (2);
}
}
sprintf (ssemid, "%d", semid);
sprintf (sqph, "%d", QPH);
/* Все - к столу */
for (no = 1; no <= QPH; no++) {
switch (fork ()) {
case -1:
perror ("FORK");
return (3);
case 0:
sprintf (sno, "%d", no);
execl ("./phil", "phil", ssemid, sqph, sno, (char *) 0);
perror ("EXEC");
return (4);
}
}
/* Ожидание завершения обеда */
for (no = 1; no <= QPH; no++) {
(void) wait (NULL);
}
/* Удаление набора семафоров */
if (semctl (semid, 0, IPC_RMID) < 0) {
perror ("SEMCTL");
return (5);
}
return 0;
}
Вторая программа (см. листинг 8.37) описывает обед каждого философа. Философ какое-то время беседует (случайное значение trnd ), затем пытается взять вилки слева и справа от себя, когда ему это удается, некоторое время ест (случайное значение ernd ), после чего освобождает вилки. Так продолжается до тех пор, пока не будет съеден весь обед. Предполагается, что выполнимый файл программы называется phil.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sem.h>
/* Процесс обеда одного философа */
#define ernd (rand () % 3 + 1)
#define trnd (rand () % 5 + 1)
#define FO 15
int main (int argc, char *argv []) {
int semid; /* Идентификатор набора семафоров */
int qph; /* Число философов */
int no; /* Номер философа */
int t; /* Время очередного отрезка еды или беседы */
int fo; /* Время до конца обеда */
struct sembuf sembuf [2];
if (argc != 4) {
fprintf (stderr, "Использование: %s идентификатор_набора_семафоров число_философов номер_философа \n", argv [0]);
return (1);
}
fo = FO;
sscanf (argv [1], "%d", semid);
sscanf (argv [2], "%d", qph);
sscanf (argv [3], "%d", no);
/* Выбор вилок */
sembuf [0].sem_num = no - 1; /* Левая */
sembuf [0].sem_flg = 0;
sembuf [1].sem_num = no % qph; /* Правая */
sembuf [1].sem_flg = 0;
while (fo > 0) { /* Обед */
/* Философ говорит */
printf ("Философ %d беседует\n", no);
t = trnd; sleep (t); fo -= t;
/* Пытается взять вилки */
sembuf [0].sem_op = -1;
sembuf [1].sem_op = -1;
if (semop (semid, sembuf, 2) < 0) {
perror ("SEMOP");
return (1);
}
/* Ест */
printf ("Философ %d ест\n", no);
t = ernd; sleep (t); fo -= t;
/* Отдает вилки */
sembuf [0].sem_op = 1;
sembuf [1].sem_op = 1;
if (semop (semid, sembuf, 2) < 0) {
perror ("SEMOP");
return (2);
}
}
printf ("Философ %d закончил обед\n", no);
return 0;
}
Отметим, что возможность выполнения групповых операций над
В листинге 8.38 приведен второй вариант решения задачи, предложенный С.В. Самборским. В нем реализованы четыре стратегии захвата вилок, которые сравниваются по результатам моделирования поведения философов в течение нескольких минут. Все стратегии гарантируют отсутствие тупиков, но только две из них, соответствующие опциям -a и -p, заведомо не позволят ни одному философу умереть от голода из-за невозможности получить обе вилки сразу. (Это свойство "стратегий -a и -p " является следствием упорядоченности ресурсов.)
/* Обедающие философы. Запуск:
mudrecProc [-a | -p | -I -V] [-t число_секунд] имя_философа ...
Опции:
-t число_секунд - сколько секунд моделируется
Стратегии захвата вилок:
-a - сначала захватывается вилка с меньшим номером;
-I - некорректная (но эффективная) интеллигентная стратегия: во время
ожидания уже захваченная вилка кладется;
-p - сначала захватывается нечетная вилка;
-V - использован групповой захват семафоров.
Пример запуска: mudrecProc -p -t 600 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: mudrecProc.c,v 1.7 2003/11/11 13:14:07 sambor Exp $";
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <limits.h>
#include <errno.h>
#include <sys/sem.h>
#include <sys/msg.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
} arg;
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)>(b)?(b):(a))
struct mudrec {
long num;
char *name;
int left_fork, right_fork;
int eat_time, wait_time, think_time, max_wait_time;
int count;
};
int Stop; /* Семафор для синхронизации выхода */
/* Различные дескрипторы */
int protokol [2] = {-1, -1};
#define pFdIn (protokol [1])
#define pFdOut (protokol [0])
int semFork; /* Вилки */
int from_fil; /* Очередь для возврата результатов */
/* Разные алгоритмы захвата вилок */
static void get_forks_simple (struct mudrec *this);
static void get_forks_parity (struct mudrec *this);
static void get_forks_maybe_infinit_time (struct mudrec *this);
static void get_forks_use_groups (struct mudrec *this);
/* Используемый метод захвата вилок */
void (*get_forks) (struct mudrec *this) = get_forks_simple;
/* Возвращение вилок */
static void put_forks (struct mudrec *this);
/*
* Философы
*/
void filosof (struct mudrec this) {
char buffer [LINE_MAX];
int bytes;
if (fork ()) return;
srandom (getpid ()); /* Очень важно для процессов, иначе получим одно и то же! */
random (); random (); random (); random (); random ();
random (); random (); random (); random (); random ();
/* Пока семафор Stop не поднят */
while (!semctl (Stop, 0, GETVAL)) {
/* Пора подкрепиться */
{
int wait_time, tm;
sprintf (buffer, "%s: хочет есть\n", this.name);
bytes = write (pFdIn, buffer, strlen (buffer));
tm = time (0);
(*get_forks) (this);
wait_time = time (0) - 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 (pFdIn, buffer, strlen (buffer));
}
/* Может, обед уже закончился? */
if (semctl (Stop, 0, GETVAL)) {
put_forks (this);
break;
}
/* Едим */
{
int eat_time = random () % 20 + 1;
sleep (eat_time);
this.eat_time += eat_time;
this.count++;
sprintf (buffer,"%s: ел %d сек\n", this.name, eat_time);
bytes = write (pFdIn, buffer, strlen (buffer));
}
/* Отдаем вилки */
put_forks (this);
if (semctl (Stop, 0, GETVAL)) break;
/* Размышляем */
{
int think_time = random () % 10 + 1;
sleep (think_time);
this.think_time += think_time;
}
}
sprintf (buffer,"%s: уходит\n", this.name);
bytes = write (pFdIn, buffer, strlen (buffer));
msgsnd (from_fil, this, sizeof (this), 0); /* Отослали статистику своего обеда */
_exit (0); /* ВАЖНО (_): Нам не нужны преждевременные вызовы cleanup_ipc */
}
/* Кладем вилки одну за другой */
static void put_forks (struct mudrec *this) {
struct sembuf tmp_buf;
tmp_buf.sem_flg = 0;
tmp_buf.sem_op = 1;
tmp_buf.sem_num = this->left_fork - 1;
semop (semFork, tmp_buf, 1);
tmp_buf.sem_flg = 0;
tmp_buf.sem_op = 1;
tmp_buf.sem_num = this->right_fork - 1;
semop (semFork, tmp_buf, 1);
}
/* Берем вилки по очереди в порядке номеров */
static void get_forks_simple (struct mudrec *this) {
struct sembuf tmp_buf;
int first = min (this->left_fork, this->right_fork);
int last = max (this->left_fork, this->right_fork);
tmp_buf.sem_flg = SEM_UNDO;
tmp_buf.sem_op = -1;
tmp_buf.sem_num = first - 1;
semop (semFork, tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO;
tmp_buf.sem_op = -1;
tmp_buf.sem_num = last - 1;
semop (semFork, tmp_buf, 1);
}
/* Берем сначала нечетную вилку (если обе нечетные - то с большим номером) */
static void get_forks_parity (struct mudrec *this) {
struct sembuf tmp_buf;
int left = this->left_fork, right = this->right_fork;
int first = max ((left 1) * 1000 + left, (right 1) * 1000 + right) % 1000;
int last = min ((left 1) * 1000 + left, (right 1) * 1000 + right) % 1000;
tmp_buf.sem_flg = SEM_UNDO;
tmp_buf.sem_op = -1;
tmp_buf.sem_num = first - 1;
semop (semFork, tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO;
tmp_buf.sem_op = -1;
tmp_buf.sem_num = last - 1;
semop (semFork, tmp_buf, 1);
}
/* Берем вилки по очереди, в произвольном порядке.
* Но если вторая вилка не берется сразу, то кладем первую.
* То есть философ не расходует вилочное время впустую.
*/
static void get_forks_maybe_infinit_time (struct mudrec *this) {
struct sembuf tmp_buf;
int left = this->left_fork, right = this->right_fork;
for (;;) {
tmp_buf.sem_flg = SEM_UNDO; /* Первую вилку берем с ожиданием */
tmp_buf.sem_op = -1;
tmp_buf.sem_num = left - 1;
semop (semFork, tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO | IPC_NOWAIT; /* Вторую - без ожидания */
tmp_buf.sem_op = -1;
tmp_buf.sem_num = right - 1;
if (0 == semop (semFork, tmp_buf, 1)) return; /* Успех */
tmp_buf.sem_flg = 0; /* Неуспех: возвращаем первую вилку */
tmp_buf.sem_op = 1;
tmp_buf.sem_num = left - 1;
semop(semFork,tmp_buf,1);
tmp_buf.sem_flg = SEM_UNDO; /* Отдав первую, ждем вторую */
tmp_buf.sem_op = -1;
tmp_buf.sem_num = right - 1;
semop (semFork, tmp_buf, 1);
tmp_buf.sem_flg = SEM_UNDO | IPC_NOWAIT; /* Берем первую вилку без ожидания */
tmp_buf.sem_op = -1;
tmp_buf.sem_num = left - 1;
if (0 == semop (semFork, tmp_buf, 1)) return; /* Успех */
tmp_buf.sem_flg = 0; /* Неуспех: отдаем вторую вилку, */
tmp_buf.sem_op = 1; /* чтобы ждать первую */
tmp_buf.sem_num = right - 1;
semop (semFork, tmp_buf, 1);
}
}
/* Хватаем обе вилки сразу, используя групповые операции */
static void get_forks_use_groups (struct mudrec *this) {
struct sembuf tmp_buf [2];
tmp_buf[0].sem_flg = SEM_UNDO;
tmp_buf[0].sem_op = -1;
tmp_buf[0].sem_num = this->left_fork - 1;
tmp_buf[1].sem_flg = SEM_UNDO;
tmp_buf[1].sem_op = -1;
tmp_buf[1].sem_num = this->right_fork - 1;
semop (semFork, tmp_buf, 2);
}
/*
* Мелкие служебные функции.
*/
static void stop (int dummy) {
struct sembuf tmp_buf;
tmp_buf.sem_flg = 0;
tmp_buf.sem_op = 1;
tmp_buf.sem_num = 0;
semop (Stop, tmp_buf, 1);
}
void cleanup_ipc (void) {
/*
* Уничтожение семафоров.
*/
semctl (semFork, 1, IPC_RMID);
semctl (Stop, 1, IPC_RMID);
/* То же с очередью */
msgctl (from_fil, IPC_RMID, NULL);
}
static void usage (char name []) {
fprintf (stderr,"Использование: %s [-a | -p | -I| -V] [-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;
union semun tmp_arg;
int nMudr;
struct sigaction sact;
while ((c = getopt (argc, argv, "apIVt:")) != -1) {
switch (c) {
case 'a': get_forks = get_forks_simple; break;
case 'p': get_forks = get_forks_parity; break;
case 'I': get_forks = get_forks_maybe_infinit_time; break;
case 'V': get_forks = get_forks_use_groups; 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);
/*
* Создадим семафоры для охраны вилок
*/
semFork = semget (ftok (argv [0], 2), nMudr, IPC_CREAT | 0777);
tmp_arg.val = 1;
for (i=1; i <= nMudr; i++)
semctl (semFork, i - 1, SETVAL, tmp_arg); /* Начальное значение 1 */
/* Прежде чем впускать философов, обеспечим окончание обеда */
Stop = semget (ftok (argv [0], 3), 1, IPC_CREAT | 0777);
tmp_arg.val = 0;
semctl (Stop, 0, SETVAL, tmp_arg); /* Начальное значение 0 */
/* Очередь для возврата результатов */
from_fil = msgget (ftok (argv [0], 4), IPC_CREAT | 0777);
atexit (cleanup_ipc); /* Запланировали уничтожение семафоров */
/* и других средств межпроцессного взаимодействия */
/*
* Философы входят в столовую
*/
for (i = 0; i < nMudr; i++, optind++) {
struct mudrec next;
memset (next, 0, sizeof (next));
next.num = i + 1; /* Номер */
next.name = argv [optind]; /* Имя */
/* Указали, какими вилками пользоваться */
next.left_fork = i + 1;
next.right_fork = i + 2;
if (i == nMudr - 1)
next.right_fork = 1; /* Последний пользуется вилкой первого */
filosof (next);
}
/* Зададим реакцию на сигналы и установим будильник на конец обеда */
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);
/*
* Выдача сообщений на стандартный вывод и выход после окончания обеда.
*/
close (pFdIn); /* Сами должны закрыть, иначе из цикла не выйдем! */
for (;;) {
n = read (pFdOut, buffer, LINE_MAX);
if ((n == 0) || ((n == -1) (errno != EINTR))) break;
for (i = 0; i < n; i++) putchar (buffer [i]);
}
close (pFdOut);
/* Распечатали сводную информацию */
{
int full_eating_time = 0;
int full_waiting_time = 0;
int full_thinking_time = 0;
for (i = 1; i <= nMudr; i++) {
struct mudrec this;
/* Получили статистику обеда */
msgrcv (from_fil, this, sizeof (this), i, 0); /* За счет i получаем */
/* строго по порядку */
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);
}
{
float total_time = (full_eating_time + full_waiting_time
+ full_thinking_time) / (float)nMudr;
printf (" Среднее число одновременно едящих = %.3f\n Среднее число одновременно ждущих = %.3f\n",
full_eating_time / total_time, full_waiting_time / total_time);
}
}
/* Сообщим об окончании работы */
printf ("Конец обеда\n");
return 0;
}
Получит ли в конце концов философ вилки при групповых операциях (опция -V ), зависит от реализации. Может случиться так, что хотя бы одна из них в каждый момент времени будет в руках у одного из соседей. То же верно и для "интеллигентной" стратегии (опция -I ). Тем не менее, результаты моделирования показывают, что на практике две последние стратегии эффективнее в смысле минимизации времени ожидания вилок.
Отметим небольшие терминологические различия в двух приведенных вариантах решения
С методической точки зрения второй вариант интересен тем, что в нем использованы все рассмотренные нами средства SEM_UNDO обеспечивает корректировку
В листинге 8.39 приведена статистика поведения пяти философов для всех четырех стратегий при времени моделирования 100 секунд. Эти результаты говорят в пользу групповых операций над
-a: A: ел 2 раза в среднем: думал=3.5 ел=11.5 ждал=36.5 (максимум 73) B: ел 3 раза в среднем: думал=5.7 ел=7.7 ждал=20.0 (максимум 41) C: ел 3 раза в среднем: думал=5.7 ел=11.3 ждал=17.0 (максимум 33) D: ел 3 раза в среднем: думал=1.7 ел=16.7 ждал=15.7 (максимум 19) E: ел 1 раз в среднем: думал=10.0 ел=20.0 ждал=73.0 (максимум 41) Среднее число одновременно едящих = 1.471 Среднее число одновременно ждущих = 2.980 -p: A: ел 3 раза в среднем: думал=3.7 ел=15.3 ждал=16.0 (максимум 34) B: ел 4 раза в среднем: думал=5.0 ел=13.8 ждал=8.2 (максимум 15) C: ел 3 раза в среднем: думал=6.7 ел=3.7 ждал=25.7 (максимум 27) D: ел 4 раза в среднем: думал=5.8 ел=8.5 ждал=13.8 (максимум 28) E: ел 3 раза в среднем: думал=5.3 ел=15.3 ждал=16.7 (максимум 29) Среднее число одновременно едящих = 1.761 Среднее число одновременно ждущих = 2.413 -I: A: ел 5 раз в среднем: думал=4.2 ел=9.4 ждал=6.6 (максимум 15) B: ел 3 раза в среднем: думал=6.3 ел=10.3 ждал=17.0 (максимум 31) C: ел 4 раза в среднем: думал=6.8 ел=7.0 ждал=12.2 (максимум 45) D: ел 3 раза в среднем: думал=4.3 ел=16.0 ждал=13.0 (максимум 16) E: ел 4 раза в среднем: думал=5.8 ел=8.5 ждал=10.8 (максимум 22) Среднее число одновременно едящих = 1.858 Среднее число одновременно ждущих = 2.125 -V: A: ел 5 раз в среднем: думал=5.6 ел=5.6 ждал=8.8 (максимум 17) B: ел 3 раза в среднем: думал=6.3 ел=10.3 ждал=16.7 (максимум 20) C: ел 4 раза в среднем: думал=4.8 ел=11.0 ждал=9.8 (максимум 18) D: ел 4 раза в среднем: думал=5.2 ел=12.0 ждал=8.8 (максимум 15) E: ел 4 раза в среднем: думал=5.2 ел=10.5 ждал=10.2 (максимум 20) Среднее число одновременно едящих = 1.892 Среднее число одновременно ждущих = 2.049
В стандарте POSIX-2001 разделяемый объект памяти определяется как объект, представляющий собой память, которая может быть параллельно отображена в адресное пространство более чем одного процесса.
Таким образом, процессы могут иметь общие области
Работа с разделяемой памятью начинается с того, что один из взаимодействующих процессов посредством функции shmget()
Чтобы получить доступ к shmat() ), т. е. разместить shmdt().
Предусмотрена возможность выполнения shmctl() ).
Описание перечисленных функций представлено в листинге 8.40.
#include <sys/shm.h>
int shmget (key_t key, size_t size,
int shmflg);
void *shmat (int shmid, const void *shmaddr,
int shmflg);
int shmdt (const void *shmaddr);
int shmctl (int shmid, int cmd,
struct shmid_ds *buf);
Структура shmid_ds, ассоциированная с
struct ipc_perm shm_perm; /* Данные о правах доступа к разделяемому сегменту */ size_t shm_segsz; /* Размер сегмента в байтах */ pid_t shm_lpid; /* Идентификатор процесса, выполнившего последнюю операцию над разделяемым сегментом */ pid_t shm_cpid; /* Идентификатор процесса, создавшего разделяемый сегмент */ shmatt_t shm_nattch; /* Текущее число присоединений сегмента */ time_t shm_atime; /* Время последнего присоединения */ time_t shm_dtime; /* Время последнего отсоединения */ time_t shm_ctime; /* Время последнего изменения посредством shmctl() */
Функция shmget() аналогична msgget() и semget() ; аргумент size задает нижнюю границу размера
Структура shmid_ds инициализируется в соответствии с общими для средств shm_segsz устанавливается равным значению аргумента size.
Число уникальных shmget() (возвращается -1 ). Вызов shmget() завершится неудачей и тогда, когда значение аргумента size меньше минимально допустимого либо больше максимально допустимого размера
Чтобы присоединить shmat(). Аргумент shmid задает shmaddr - адрес, по которому SHM_RND, адрес присоединения округляется до величины, кратной константе SHMLBA.
Если shmaddr задан как пустой указатель, реализация выбирает адрес присоединения по своему усмотрению.
По умолчанию присоединяемый SHM_RDONLY предписывает присоединить
При успешном завершении функции shmat() результат равен адресу, который получил присоединенный -1. (Разумеется, для использования результата shmat() в качестве указателя его нужно преобразовать к требуемому типу.)
Отсоединение shmdt() ; аргумент shmaddr задает начальный адрес отсоединяемого
Управление shmctl(), аналогичной msgctl(). Как и для IPC_STAT (получить информацию о состоянии IPC_SET (переустановить характеристики), IPC_RMID (удалить
Аппарат
Для "создания" подобного механизма необходимо породить
#include <unistd.h>
#include <stdio.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/wait.h>
int main (void) {
struct region {
pid_t fpid;
} *shm_ptr;
struct sembuf P = {0, -1, 0};
struct sembuf V = {0, 1, 0};
int shmid;
int semid;
shmid = shmget (IPC_PRIVATE, sizeof (struct region), 0777);
semid = semget (IPC_PRIVATE, 1, 0777);
(void) semctl (semid, 0, SETVAL, 1);
switch (fork ()) {
case -1:
perror ("FORK");
return (1);
case 0:
if ((int) (shm_ptr = (struct region *) shmat (shmid, NULL, 0)) == (-1)) {
perror ("CHILD-SHMAT");
return (2);
}
if (semop (semid, p, 1) != 0) {
perror ("CHILD-SEMOP-P");
return (3);
}
printf ("Процесс-потомок вошел в критический интервал\n");
shm_ptr->fpid = getpid (); /* Монопольный доступ */
printf ("Процесс-потомок перед выходом из критического интервала\n");
if (semop (semid, V, 1) != 0) {
perror ("CHILD-SEMOP-V");
return (4);
}
(void) shmdt (shm_ptr);
return 0;
}
if ((int) (shm_ptr = (struct region *) shmat (shmid, NULL, 0)) == (-1)) {
perror ("PARENT-SHMAT");
return (2);
}
if (semop (semid, p, 1) != 0) {
perror ("PARENT-SEMOP-P");
return (3);
}
printf ("Родительский процесс вошел в критический интервал\n");
shm_ptr->fpid = getpid (); /* Монопольный доступ */
printf ("Родительский процесс перед выходом из критического интервала\n");
if (semop (semid, V, 1) != 0) {
perror ("PARENT-SEMOP-V");
return (4);
}
(void) wait (NULL);
printf ("Идентификатор родительского процесса: %d\n", getpid ());
printf ("Идентификатор процесса в разделяемой структуре: %d\n", shm_ptr->fpid);
(void) shmdt (shm_ptr);
(void) semctl (semid, 1, IPC_RMID);
(void) shmctl (shmid, IPC_RMID, NULL);
return 0;
}
Результат работы приведенной программы может выглядеть так, как показано в листинге 8.42.
Родительский процесс вошел в критический интервал Родительский процесс перед выходом из критического интервала Процесс-потомок вошел в критический интервал Процесс-потомок перед выходом из критического интервала Идентификатор родительского процесса: 2161 Идентификатор процесса в разделяемой структуре: 2162
В листинге 8.43 представлен пример использования SIGSEGV, который посылается процессу при некорректном обращении к памяти. Идея в том, чтобы
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Реализация "виртуальной" памяти из одного сегмента. */
/* Используются разделяемые сегменты памяти */
/* и обработка сигнала SIGSEGV */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/shm.h>
#include <signal.h>
/* Константа, зависящая от реализации */
#define SHM_BASE_ADDR 0x40014000
static int shm_id = -1;
static void *shm_addr;
/* Реакция на сигнал SIGSEGV. */
/* Создаем и присоединяем на чтение разделяемый сегмент, */
/* накрывающий переданный адрес. */
/* Если это не помогло, переприсоединяем сегмент на запись */
static void sigsegv_sigaction (int sig, siginfo_t *sig_info, void *addr) {
struct shmid_ds shmid_ds;
if (shm_id == -1) {
/* Сегмента еще нет. Создадим */
if ((shm_id = shmget (IPC_PRIVATE, SHMLBA, S_IRUSR)) == -1) {
perror ("SHMGET");
exit (1);
}
/* Присоединим сегмент на чтение */
if ((int) (shm_addr = shmat (shm_id, sig_info->si_addr, SHM_RDONLY | SHM_RND)) == (-1)) {
perror ("SHMAT-RDONLY");
exit (2);
}
return;
} else {
/* Сегмент уже есть, но обращение по адресу вызвало сигнал SIGSEGV. */
/* Значит, это была попытка записи, и сегмент нужно */
/* переприсоединить на запись, поменяв соответственно режим доступа */
if (shmctl (shm_id, IPC_STAT, shmid_ds) == -1) {
perror ("SHMCTL-IPC_STAT");
exit (3);
}
shmid_ds.shm_perm.mode |= S_IWUSR;
if (shmctl (shm_id, IPC_SET, shmid_ds) == -1) {
perror ("SHMCTL-IPC_SET");
exit (4);
}
(void) shmdt (shm_addr);
if (shmat (shm_id, shm_addr, 0) != shm_addr) {
perror ("SHMAT-RDWD");
exit (5);
}
}
}
int main (void) {
char *test_ptr;
struct sigaction sact;
/* Установим реакцию на сигнал SIGSEGV */
(void) sigemptyset (sact.sa_mask);
sact.sa_flags = SA_SIGINFO;
sact.sa_sigaction = sigsegv_sigaction;
(void) sigaction (SIGSEGV, sact, (struct sigaction *) NULL);
/* Убедимся, что разделяемые сегменты инициализируются нулями */
test_ptr = (char *) (SHM_BASE_ADDR + 3);
printf ("Результат попытки чтения до записи: %x\n", *test_ptr);
/* Попробуем записать */
*test_ptr = 'A';
printf ("Результат попытки чтения после записи: %x\n", *test_ptr);
return (shmctl (shm_id, IPC_RMID, NULL));
}
Обратим внимание на использование флагов округления адреса SHM_RND ) и присоединения только на чтение ( SHM_RDONLY ), а также обработчика сигналов, задаваемого полем sa_sigaction структуры типа sigaction (в сочетании с флагом SA_SIGINFO ) и имеющего доступ к расширенной информации о сигнале и его причинах.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.