Содержание занятия
Скачать 0.66 Mb.
|
Процессы и потоки. Средства межпроцессного взаимодействия: каналы, сигналы, очереди сообщений, семафоры, разделяемые сегменты памяти Содержание занятия 1.Теоретическая часть. Средства межпроцессного взаимодействия 1.1 Каналы 1.2 Сигналы 1.3 Очереди сообщений 1.4 Семафоры 1.5 Разделяемые сегменты памяти канал сигнал очередь сообщений память Цель работы: знакомство с основными средствами организации межпроцессного взаимодействия. 1. Теоретическая часть. Средства межпроцессного взаимодействия.1.1 КаналыСредства локального межпроцессного взаимодействия реализуют высокопроизводительную, детерминированную передачу данных между процессами в пределах одной системы. К числу наиболее простых и в то же время самых употребительных средств межпроцессного взаимодействия принадлежат каналы, представляемые файлами соответствующего типа. Стандарт POSIX-2001 различает именованные и безымянные каналы. Напомним, что первые создаются функцией mkfifo() и одноименной служебной программой, а вторые - функцией pipe(). Именованным каналам соответствуют элементы файловой системы, ко вторым можно обращаться только посредством файловых дескрипторов. В остальном эти разновидности каналов эквивалентны. Взаимодействие между процессами через канал может быть установлено следующим образом: один из процессов создает канал и передает другому соответствующий открытый файловый дескриптор. После этого процессы обмениваются данными через канал при помощи функций read() и write(). Примером подобного взаимодействия служит программа, показанная в пример 8.1. #include #include #include #include /* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через канал. */ /* Используются функции ввода/вывода нижнего уровня */ #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 #include #include #include #include #include /* Программа копирует строки со стандартного ввода на стандартный вывод, */ /* "прокачивая" их через канал. */ /* Используются функции буферизованного ввода/вывода */ 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 int pclose (FILE *stream); Листинг 8.3. Описание функции pclose(). Функция pclose() не только закрывает поток, сформированный popen(), но и дожидается завершения порожденного процесса, возвращая его статус. Типичное применение popen() - организация канала для выдачи динамически порождаемых данных на устройство печати командой lp (пример 8.4). #include /* Программа печатает несколько первых строк треугольника Паскаля */ #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 #include #include #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. Пример создания и использования канала для ввода данных. |