Процессы и потоки. Средства межпроцессного взаимодействия: каналы, сигналы, очереди сообщений, семафоры, разделяемые сегменты памяти
Характеристика основных средств организации межпроцессного взаимодействия: каналы, сигналы, очереди сообщений, семафоры и разделяемые сегменты памяти. Определение производительности системы передач данных. Основные функции ввода и вывода нижнего уровня.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | лабораторная работа |
Язык | русский |
Дата добавления | 27.11.2013 |
Размер файла | 68,4 K |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Размещено на http://www.allbest.ru/
2
Процессы и потоки. Средства межпроцессного взаимодействия: каналы, сигналы, очереди сообщений, семафоры, разделяемые сегменты памяти
Содержание занятия
1.Теоретическая часть. Средства межпроцессного взаимодействия
1.1 Каналы
1.2 Сигналы
1.3 Очереди сообщений
1.4 Семафоры
1.5 Разделяемые сегменты памяти
канал сигнал очередь сообщений память
Цель работы: знакомство с основными средствами организации межпроцессного взаимодействия.
1. Теоретическая часть. Средства межпроцессного взаимодействия.
1.1 Каналы
Средства локального межпроцессного взаимодействия реализуют высокопроизводительную, детерминированную передачу данных между процессами в пределах одной системы.
К числу наиболее простых и в то же время самых употребительных средств межпроцессного взаимодействия принадлежат каналы, представляемые файлами соответствующего типа. Стандарт POSIX-2001 различает именованные и безымянные каналы. Напомним, что первые создаются функцией mkfifo() и одноименной служебной программой, а вторые - функцией 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);
}
Листинг 8.1. Пример взаимодействия между процессами через канал с помощью функций ввода/вывода нижнего уровня.
Решение той же задачи, но с использованием функций буферизованного ввода/вывода, показано в пример 8.2.
#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);
}
Листинг 8.2. Пример взаимодействия между процессами через канал с помощью функций буферизованного ввода/вывода.
Если не указано противное, обмен данными через канал происходит в синхронном режиме: процесс, пытающийся читать из пустого канала, открытого кем-либо на запись, приостанавливается до тех пор, пока данные не будут в него записаны; с другой стороны, запись в полный канал задерживается до освобождения необходимого для записи места. Чтобы отменить подобный режим взаимодействия, надо связать с дескрипторами канала флаг статуса O_NONBLOCK (это может быть сделано при помощи функции fcntl()). В таком случае чтение или запись, которые невозможно выполнить немедленно, завершаются неудачей.
Подчеркнем, что при попытке чтения из пустого канала результат равен 0 (как признак конца файла), только если канал не открыт кем-либо на запись. Под "кем-либо" понимается и сам читающий процесс; по этой причине в приведенной выше программе потребовалось закрыть все экземпляры файлового дескриптора fd [1], возвращенного функцией pipe() как дескриптор для записи в канал.
Функция popen(), описанная выше, при рассмотрении командного интерпретатора, является более высокоуровневой по сравнению с pipe(). Она делает сразу несколько вещей: порождает процесс, обеспечивает выполнение в его рамках заданной команды, организует канал между вызывающим и порожденным процессами и формирует необходимые потоки для этого канала. Если при обращении к popen() задан режим "w", то стандартный ввод команды, выполняющейся в рамках порожденного процесса, перенаправляется на конец канала, предназначенный для чтения; если задан режим "r", то в канал перенаправляется стандартный вывод.
После вызова popen() процесс может писать в канал или читать из него посредством функций буферизованного ввода/вывода, используя сформированный поток. Канал остается открытым до момента вызова функции pclose() (пример 8.3).
#include <stdio.h>
int pclose (FILE *stream);
Листинг 8.3. Описание функции pclose().
Функция 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));
}
Листинг 8.4. Пример создания и использования канала для вывода данных.
Сходным образом можно организовать канал для чтения результатов выполнения команды (пример 8.5).
#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));
}
Листинг 8.5. Пример создания и использования канала для ввода данных.
1.2 Сигналы
Как и каналы, сигналы являются внешне простым и весьма употребительным средством локального межпроцессного взаимодействия, но связанные с ними идеи существенно сложнее, а понятия - многочисленнее.
Согласно стандарту POSIX-2001, под сигналом понимается механизм, с помощью которого процесс или поток управления уведомляют о некотором событии, произошедшем в системе, или подвергают воздействию этого события. Примерами подобных событий могут служить аппаратные исключительные ситуации и специфические действия процессов. Термин "сигнал" используется также для обозначения самого события.
Говорят, что сигнал генерируется (или посылается) для процесса (потока управления), когда происходит вызвавшее его событие (например, выявлен аппаратный сбой, отработал таймер, пользователь ввел с терминала специфическую последовательность символов, другой процесс обратился к функции kill() и т.п.). Иногда по одному событию генерируются сигналы для нескольких процессов (например, для группы процессов, ассоциированных с некоторым управляющим терминалом). В момент генерации сигнала определяется, посылается ли он процессу или конкретному потоку управления в процессе. Сигналы, сгенерированные в результате действий, приписываемых отдельному потоку управления (таких, например, как возникновение аппаратной исключительной ситуации), посылаются этому потоку. Сигналы, генерация которых ассоциирована с идентификатором процесса или группы процессов, а также с асинхронным событием (к примеру, пользовательский ввод с терминала) посылаются процессу.
В каждом процессе определены действия, предпринимаемые в ответ на все предусмотренные системой сигналы. Говорят, что сигнал доставлен процессу, когда взято для выполнения действие, соответствующее данным процессу и сигналу. сигнал принят процессом, когда он выбран и возвращен одной из функций sigwait().
В интервале от генерации до доставки или принятия сигнал называется ждущим. Обычно он невидим для приложений, однако доставку сигнала потоку управления можно блокировать. Если действие, ассоциированное с заблокированным сигналом, отлично от игнорирования, он будет ждать разблокирования.
У каждого потока управления есть маска сигналов, определяющая набор блокируемых сигналов. Обычно она достается в наследство от родительского потока.
С сигналом могут быть ассоциированы действия одного из трех типов.
SIG_DFL
Подразумеваемые действия, зависящие от сигнала. Они описаны в заголовочном файле <signal.h>.
SIG_IGN
Игнорировать сигнал. Доставка сигнала не оказывает воздействия на процесс.
указатель на функцию
Обработать сигнал, выполнив при его доставке заданную функцию. После завершения функции обработки процесс возобновляет выполнение с точки прерывания. Обычно функция обработки вызывается в соответствии со следующим C-заголовком: void func (int signo); где signo - номер доставленного сигнала.
Первоначально, до входа в функцию main(), реакция на все сигналы установлена как SIG_DFL или SIG_IGN.
Функция называется асинхронно-сигнально-безопасной (АСБ), если ее можно вызывать без каких-либо ограничений при обработке сигналов. В стандарте POSIX-2001 имеется список функций, которые должны быть либо повторно входимыми, либо непрерываемыми сигналами, что превращает их в АСБ-функции. В этот список включены 117 функций, в том числе почти все из рассматриваемых нами.
Если сигнал доставляется потоку, а реакция заключается в завершении, остановке или продолжении, весь процесс должен завершиться, остановиться или продолжиться.
Перейдем к изложению возможностей по генерации сигналов. Выше была кратко рассмотрена служебная программа kill как средство терминирования процессов извне. На самом деле она посылает заданный сигнал; то же делает и одноименная функция (пример 8.6).
#include <signal.h>
int kill (pid_t pid, int sig);
Листинг 8.6. Описание функции kill().
Сигнал задается аргументом 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) SIGKILL10) SIGUSR111) SIGSEGV12) SIGUSR2
13) SIGPIPE14) SIGALRM15) SIGTERM17) SIGCHLD
18) SIGCONT19) SIGSTOP20) SIGTSTP21) SIGTTIN
22) SIGTTOU23) SIGURG24) SIGXCPU25) SIGXFSZ
26) SIGVTALRM27) SIGPROF28) SIGWINCH29) SIGIO
30) SIGPWR31) SIGSYS32) SIGRTMIN33) SIGRTMIN+1
34) SIGRTMIN+235) SIGRTMIN+336) SIGRTMIN+437) SIGRTMIN+5
38) SIGRTMIN+639) SIGRTMIN+740) SIGRTMIN+841) SIGRTMIN+9
42) SIGRTMIN+1043) SIGRTMIN+1144) SIGRTMIN+1245) SIGRTMIN+13
46) SIGRTMIN+1447) SIGRTMIN+1548) SIGRTMAX-1549) SIGRTMAX-14
50) SIGRTMAX-1351) SIGRTMAX-1252) SIGRTMAX-1153) SIGRTMAX-10
54) SIGRTMAX-955) SIGRTMAX-856) SIGRTMAX-757) SIGRTMAX-6
58) SIGRTMAX-559) SIGRTMAX-460) SIGRTMAX-361) SIGRTMAX-2
62) SIGRTMAX-163) SIGRTMAX
Листинг 8.7. Возможный результат выполнения команды kill -l.
Мы не будем пояснять назначение всех представленных в листинге сигналов, ограничившись кратким описанием тех, что фигурируют в стандарте 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 (getpid(), sig);
#include <signal.h>
int raise (int sig);
Листинг 8.8. Описание функции raise().
Посылка сигнала самому себе использована в функции abort() (пример 8.9), вызывающей аварийное завершение процесса. (Заметим, что этого не произойдет, если функция обработки сигнала SIGABRT не возвращает управления. С другой стороны, abort() отменяет блокирование или игнорирование SIGABRT.)
#include <stdlib.h>
void abort (void);
Листинг 8.9. Описание функции abort().
Опросить и изменить способ обработки сигналов позволяет функция sigaction() (пример 8.10).
#include <signal.h>
int sigaction (int sig, const struct sigaction
*restrict act, struct sigaction
*restrict oact);
Листинг 8.10. Описание функции sigaction().
Для описания способа обработки сигнала используется структура 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, он указывает на структуру, специфицирующую действия, которые будут ассоциированы с сигналом sig. По адресу oact (если он не NULL) возвращаются сведения о прежних действиях. Если значение act есть NULL, обработка сигнала остается неизменной; подобный вызов можно использовать для опроса способа обработки сигналов.
Следующие флаги в поле sa_flags влияют на поведение сигнала sig.
SA_NOCLDSTOP
Не генерировать сигнал SIGCHLD при остановке или продолжении порожденного процесса (значение аргумента sig должно равняться SIGCHLD).
SA_RESETHAND
При входе в функцию обработки сигнала sig установить подразумеваемую реакцию SIG_DFL и очистить флаг SA_SIGINFO (см. далее).
SA_SIGINFO
Если этот флаг не установлен и определена функция обработки сигнала sig, она вызывается с одним целочисленным аргументом - номером сигнала. Соответственно, в приложении следует использовать поле sa_handler структуры sigaction. При установленном флаге SA_SIGINFO функция обработки вызывается с двумя дополнительными аргументами, как void func (int sig, siginfo_t *info, void *context); второй аргумент указывает на данные, поясняющие причину генерации сигнала, а третий может быть преобразован к указателю на тип ucontext_t - контекст процесса, прерванного доставкой сигнала. В этом случае приложение должно использовать поле sa_sigaction и поля структуры типа siginfo_t. В частности, если значение si_code неположительно, сигнал был сгенерирован процессом с идентификатором si_pid и реальным идентификатором пользователя si_uid.
SA_NODEFER
По умолчанию обрабатываемый сигнал добавляется к маске сигналов процесса при входе в функцию обработки; флаг SA_NODEFER предписывает не делать этого, если только sig не фигурирует явным образом в sa_mask.
Опросить и изменить способ обработки сигналов можно и на уровне командного интерпретатора, посредством специальной встроенной команды trap:
trap [действие условие ...]
Аргумент "условие" может задаваться как EXIT (завершение командного интерпретатора) или как имя доставленного сигнала (без префикса SIG). При задании аргумента "действие" минус обозначает подразумеваемую реакцию, пустая цепочка ("") - игнорирование. Если в качестве действия задана команда, то при наступлении условия она обрабатывается как eval действие.
Команда trap без аргументов выдает на стандартный вывод список команд, ассоциированных с каждым из условий. Выдача имеет формат, пригодный для восстановления способа обработки сигналов (пример 8.11).
save_traps=$(trap)
. . .
eval "$save_traps"
Листинг 8.11. Пример сохранения и восстановления способа обработки сигналов посредством специальной встроенной команды trap.
Обеспечить выполнение утилиты logout из домашнего каталога пользователя во время завершения командного интерпретатора можно с помощью команды, показанной в пример 8.11.
trap '$HOME/logout' EXIT
Листинг 8.12. Пример использования специальной встроенной команды trap.
При перенаправлении вывода в файл приходится считаться с возможностью возникновения ошибок, специфичных для каналов. Чтобы защитить от них процедуры начальной загрузки, в ОС Lunix применяются связки из игнорирования и последующего восстановления подразумеваемой реакции на сигнал SIGPIPE (пример 8.13).
trap "" PIPE
echo "$INITLOG_ARGS -n $0 -s \"$1\" -e 1" >&21
trap - PIPE
Листинг 8.13. Пример использования специальной встроенной команды trap для защиты от ошибок, специфичных для каналов.
К техническим аспектам можно отнести работу с наборами сигналов, которая выполняется посредством функций, показанных в пример 8.14. Функции 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);
Листинг 8.14. Описание функций для работы с наборами сигналов.
Функция sigprocmask() (пример 8.15) предназначена для опроса и/или изменения маски сигналов процесса, определяющей набор блокируемых сигналов.
#include <signal.h>
int sigprocmask (int how, const sigset_t
*restrict set, sigset_t *restrict oset);
Листинг 8.15. Описание функции sigprocmask().
Если аргумент 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);
Листинг 8.16. Описание функции sigpending().
#include <signal.h>
int sigwait (const sigset_t *restrict set,
int *restrict sig);
Листинг 8.17. Описание функции sigwait().
Функция sigwait() выбирает ждущий сигнал из заданного набора (он должен включать только блокированные сигналы), удаляет его из системного набора ждущих сигналов и помещает его номер по адресу, заданному аргументом sig. Если в момент вызова sigwait() нужного сигнала нет, процесс (поток управления) приостанавливается до появления такового.
Отметим, что стандарт POSIX-2001 не специфицирует воздействие функции sigwait() на обработку сигналов, включенных в набор set. Чтобы дождаться доставки обрабатываемого или терминирующего процесс сигнала, можно воспользоваться функцией pause() (пример 8.18).
#include <unistd.h>
int pause (void);
Листинг 8.18. Описание функции pause().
Функция pause() может ждать доставки сигнала неопределенно долго. Возврат из pause() осуществляется после возврата из функции обработки сигнала (результат при этом равен -1). Если прием сигнала вызывает завершение процесса, возврата из функции pause(), естественно, не происходит.
Несмотря на внешнюю простоту, использование функции pause() сопряжено с рядом тонкостей. При наивном подходе сначала проверяют некоторое условие, связанное с сигналом, и, если оно не выполнено (сигнал отсутствует), вызывают pause(). К сожалению, сигнал может быть доставлен в промежутке между проверкой и вызовом pause(), что нарушает логику работы процесса и способно привести к его зависанию. Решить подобную проблему позволяет функция sigsuspend() (пример 8.19) в сочетании с рассмотренной выше функцией sigprocmask().
#include <signal.h>
int sigsuspend (const sigset_t *sigmask);
Листинг 8.19. Описание функции sigsuspend().
Функция sigsuspend() заменяет текущую маску сигналов вызывающего процесса на набор, заданный аргументом sigmask, а затем переходит в состояние ожидания, аналогичное функции 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;
}
Листинг 8.20. Упрощенная реализация функции abort() как пример использования функций работы с сигналами.
В качестве нюанса, характерного для работы с сигналами, отметим, что до первого обращения к raise() нельзя закрыть потоки (можно только вытолкнуть буфера), поскольку функция обработки сигнала SIGABRT, возможно, осуществляет вывод. Еще одним примером использования механизма сигналов может служить приведенная в пример 8.13 упрощенная реализация функции 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);
}
Листинг 8.21. Упрощенная реализация функции sleep() как пример использования механизма сигналов.
Обратим внимание на применение функции sigsuspend(), которая реализует (неделимую) транзакцию снятия блокировки сигналов и перехода в режим ожидания. Отметим также, что по умолчанию при входе в функцию обработки к маске добавляется принятый сигнал для защиты от бесконечной рекурсии. Наконец, если происходит возврат из функции sigsuspend() (после возврата из функции обработки), то автоматически восстанавливается маска сигналов, существовавшая до вызова sigsuspend(). В данном случае в этой маске блокирован сигнал SIGALRM, и потому можно спокойно менять способ его обработки.
Вызвать "недосыпание" приведенной программы можно, послав ей сигнал SIGALRM (например, посредством команды kill -s SIGALRM идентификатор_процесса) или SIGINT (путем нажатия на клавиатуре терминала комбинации клавиш CTRL+C).
1.3 Очереди сообщений
Мы переходим к рассмотрению средств локального межпроцессного взаимодействия, относящихся к необязательной части стандарта POSIX-2001, именуемой "X/Open-расширение системного интерфейса" (XSI). Будут описаны очереди сообщений, семафоры и разделяемые сегменты памяти.
Остановимся сначала на понятиях и структурах, общих для всех трех упомянутых средств. Каждая очередь сообщений, набор семафоров и разделяемый сегмент однозначно идентифицируются положительным целым числом, которое обычно обозначается, соответственно, как 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);
Листинг 8.22. Описание функции ftok().
С идентификатором средства межпроцессного взаимодействия ассоциирована структура данных, содержащая информацию о допустимых и выполненных операциях. Соответствующие декларации сосредоточены в заголовочных файлах <sys/msg.h>, <sys/sem.h> и <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 ] ...
На этом мы завершаем изложение общих вопросов, относящихся к средствам межпроцессного взаимодействия, и переходим к рассмотрению специфических возможностей каждого из них.
Механизм очередей сообщений позволяет процессам взаимодействовать, обмениваясь данными. Данные передаются между процессами дискретными порциями, называемыми сообщениями. Процессы выполняют над сообщениями две основные операции - прием и отправку. Процессы, отправляющие или принимающие сообщение, могут приостанавливаться, если требуемую операцию невозможно выполнить немедленно. В частности, могут быть отложены попытки отправить сообщение в заполненную до отказа очередь, получить сообщение из пустой очереди и т.п. ("операции с блокировкой"). Если же указано, что приостанавливать процесс нельзя, "операции без блокировки" либо выполняются немедленно, либо завершаются неудачей.
Прежде чем процессы смогут обмениваться сообщениями, один из них должен создать очередь. Одновременно определяются первоначальные права на выполнение операций для различных процессов, в том числе соответствующих управляющих действий над очередями.
Для работы с очередями сообщений стандарт POSIX-2001 предусматривает следующие функции (пример 8.23): msgget() (получение идентификатора очереди сообщений), msgctl() (управление очередью сообщений), msgsnd() (отправка сообщения) и msgrcv() (прием сообщения).
#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);
Листинг 8.23. Описание функций для работы с очередями сообщений.
Структура 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;
}
Листинг 8.24. Пример программы, создающей очередь сообщений.
Если после выполнения этой программы воспользоваться командой ipcs -q, то результат может выглядеть так, как показано в пример 8.25.
------ Message Queues --------
key msqid owner perms used-bytes messages
0x47034bac 163840 galat 644 0 0
Листинг 8.25. Возможный результат опроса статуса очередей сообщений.
Удалить созданную очередь из системы, соответствующей стандарту POSIX-2001, можно командой ipcrm -q 163840.
Операции отправки/приема сообщений выполняют функции msgsnd() и msgrcv(); msgsnd() помещает сообщения в очередь, а msgrcv() читает и "достает" их оттуда.
В обоих случаях первый аргумент задает идентификатор очереди; второй является указателем на содержащую сообщение структуру. Сообщение состоит из двух частей: текста (последовательности байт) и так называемого типа (положительного целого числа). Тип, указанный во время отправки, используется впоследствии при выборе сообщения из очереди. Аргумент msgsz определяет длину сообщения; аргумент msgflg задает флаги.
В зависимости от значения, указанного в качестве аргумента msgtyp функции msgrcv(), из очереди выбирается то или иное сообщение. Если значение аргумента равно нулю, запрашивается первое сообщение в очереди, если больше нуля - первое сообщение типа msgtyp, а если меньше нуля - первое сообщение наименьшего из типов, не превышающих абсолютную величину аргумента msgtyp. Пусть, например, в очередь последовательно помещены сообщения с типами 5, 3 и 2. Тогда вызов msgrcv (msqid, msgp, size, 0, flags) выберет из очереди сообщение с типом 5, поскольку оно отправлено первым; вызов 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.26. Описание структурного типа для представления сообщений.
Для хранения реальных сообщений в прикладной программе следует определить аналогичную структуру, указав желаемый размер сообщения, например, так, как это сделано в пример 8.27.
#define MAXSZTMSG 8192
struct mymsgbuf {
long mtype; /* Тип сообщения */
char mtext [MAXSZTMSG]; /* Текст сообщения */
};
struct mymsgbuf msgbuf;
Листинг 8.27. Описание структуры для хранения сообщений.
В качестве аргумента 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.28. Пример программы управления очередями сообщений.
Две программы, показанные в листингах пример 8.29 и пример 8.30, демонстрируют полный цикл работы с очередями сообщений - от создания до удаления. Программа из пример 8.29 представляет собой родительский процесс, читающий строки со стандартного ввода и отправляющий их в виде сообщений процессу-потомку (пример 8.30). Последний принимает сообщения и выдает их тела на стандартный вывод. Предполагается, что программа этого процесса находится в файле msq_child текущего каталога.
#include <unistd.h>
...Подобные документы
Запуск из одного приложения других приложений. Технология связывания и внедрения объектов. Понятие межпроцессного взаимодействия. Сценарий использования разделяемой памяти. Библиотеки динамической компоновки. Именованные и анонимные каналы, сокеты.
курсовая работа [46,5 K], добавлен 26.07.2014Модель программирования – SPMD, обеспечение взаимодействия. Программные средства, обеспечивающие передачу сообщений и соответствующие стандарту MPI. Процессы и потоки (треды). Операции передачи сообщений. Виртуальная топология, типы данных, ссылки.
презентация [116,4 K], добавлен 10.02.2014Общее понятие и специфика применения очереди в программировании. Способы реализации очереди, их сущностная характеристика. Основные проблемы в использовании списков. Представление очереди в виде массива и двух целочисленных переменных start и end.
презентация [895,9 K], добавлен 14.10.2013Рассмотрение способов организации передачи данных между различными процессами, основанных на использовании дейтаграммных каналов Mailslot. Однонаправленный интерфейс взаимодействия между процессами. Создание и открытие канала, запись и чтение сообщений.
контрольная работа [19,1 K], добавлен 10.10.2010Структура ядра операционной системы. Основные компоненты подсистемы управления процессами и памятью. Характеристика системных и прикладных процессов в Unix. Идентификация процесса Linux, его атрибуты и вызовы. Средства межпроцессного взаимодействия.
лекция [170,1 K], добавлен 29.07.2012Объем двухпортовой памяти, расположенной на кристалле, для хранения программ и данных в процессорах ADSP-2106x. Метод двойного доступа к памяти. Кэш-команды и конфликты при обращении к данным по шине памяти. Пространство памяти многопроцессорной системы.
реферат [28,1 K], добавлен 13.11.2009Блок-схема, отражающая основные функциональные компоненты компьютерной системы в их взаимосвязи. Устройства ввода-вывода информации. Определение объема оперативной памяти. Применение карт памяти и flash-дисков для долговременного хранения информации.
презентация [5,3 M], добавлен 28.01.2015Схема межпроцессного взаимодействия; создание программы моделирования обслуживания заявок в системе с двумя очередями и одним обслуживающим прибором. Структура сообщений, параметров и ограничения очередей; кодирование и функциональное тестирование.
курсовая работа [33,3 K], добавлен 12.05.2013Понимание принципа работы очереди. Возможности обращения к первому и последнему элементов очереди. Создание очереди с помощью массива. Рассмотрение примеров использования очереди с приоритетом в программе. Формирование односвязного и двусвязного списков.
контрольная работа [345,6 K], добавлен 26.11.2020Источники сообщений, сигналы и коды, примеры применения знания основ теории информации для практических целей. Расчет информационных характеристик и согласование дискретного источника с дискретным каналом без шума и с шумом, эффективное кодирование.
курсовая работа [179,6 K], добавлен 13.11.2009Моделирование работы компьютерного зала в течении 60 ч. Определение загрузки устройства подготовки данных (УПД), ЭВМ и вероятности отказа в обслуживании вследствие переполнения очереди. Определение соотношения желающих работать на ЭВМ и на УПД в очереди.
контрольная работа [275,7 K], добавлен 05.07.2014Принципы сегментации памяти. Классификация регистров по назначению и способу использования. "Перевернутое" представление данных в центральном процессоре. Адресация ввода/вывода информации. Программное, внутреннее и аппаратное прерывание выполнения команд.
презентация [107,4 K], добавлен 27.08.2013Характеристика модели клиент-сервер как технологии взаимодействия в информационной сети. Разработка и описание алгоритмов работы приложений на платформе Win32 в среде Microsoft Visual Studio, использующих для межпроцессного взаимодействия сокеты.
курсовая работа [544,6 K], добавлен 02.06.2014Базовая система ввода-вывода информации. Базовые функции интерфейса и настройки оборудования. Основные понятия и функционирование BIOS. Сведения о системной BIOS компьютера. Затенение ROM-памяти. Самотестирование процессора, модулей оперативной памяти.
реферат [21,7 K], добавлен 12.12.2011Понятие системных ресурсов, конфликты, связанные с ресурсами IRQ и DMA. Использование портов ввода-вывода. Разновидности памяти и особенности ее распределения в рамках операционной системы. Назначение адресов памяти средствами Windows 9x/NT/2000.
презентация [45,9 K], добавлен 27.08.2013Определение основных функций процессора. Микросхема процессора и выводы шин адреса, данных и управления. Функции памяти и устройств ввода/вывода (мыши, клавиатуры, джойстика). Описание функций внутренних регистров микропроцессора. Оперативная память.
презентация [603,1 K], добавлен 17.06.2014Изучение подсистемы ввода-вывода и файловой системы ОС семейства Windows NT. Анализ особенностей работы приложения TotalCommander и его взаимодействия с файловой системой и подсистемой ввода-вывода. Взаимодействие TotalCommander с сетевыми адаптерами.
лабораторная работа [1,1 M], добавлен 12.06.2012Главная задача компьютерной системы. Виртуальные адресные пространства нескольких программ. Классификация методов распределения памяти. Зависимость загрузки процессора от числа задач и интенсивности ввода-вывода. Схема функционирования кэш-памяти.
презентация [2,2 M], добавлен 14.11.2012Создание стека как линейного списка. Использование аналогичного ссылочного типа для организации очереди. Циклически связанный список. Построение сложных структур в динамической памяти. Бинарные (двоичные) деревья. Экран результата и контрольные расчеты.
лабораторная работа [398,9 K], добавлен 14.06.2009Особенности организации передачи данных в компьютерной сети. Эталонная модель взаимодействия открытых систем. Методы передачи данных на нижнем уровне, доступа к передающей среде. Анализ протоколов передачи данных нижнего уровня на примере стека TCP/IP.
курсовая работа [1,0 M], добавлен 07.08.2011