41 Тогда, схему взаимодействия процессов через ядро ОС можно представить в виде:
in |
Закрыт |
-----> |
in |
Parent Process |
Kernel |
|
Child Process |
out |
-----> |
Закрыт |
out |
На листинге 1.8 представлен исходый текст программы, реализующий этот проект.
Листинг 1.8 — Родитель передает строку дочернему процессу через pipe-канал
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h>
int main(void) {
int fd[2], pid; ssize_t n;
char buf[BUFSIZ]; char buf2[4];
char msg[] = "Ввели строку: ";
char pmsg[] = "Родительский процесс пишет: "; char gmsg[] = "Дочерний процесс читает: ";
puts("Программа lab1.7: Введи произвольную строку и нажми клавишу Enter> "); n = read(0, buf, sizeof(buf));
if(n < 0) {
puts("\nОшибка: ошибка чтения строки\n"); return 1;
}
buf[n] = '\0';
write(1, msg, sizeof(msg)); write(1, buf, n);
if (pipe(fd) == -1) { |
// Создали канал |
perror("pipe"); |
|
exit(EXIT_FAILURE); |
|
} |
|
pid = fork(); |
// Создаем дочерний процесс |
if (pid == -1) { |
|
perror("fork"); exit(EXIT_FAILURE);
} |
|
if (pid > 0) { |
// Родительский процесс |
write(1, pmsg, sizeof(pmsg)); |
|
write(1, buf, |
n); |
close(fd[0]); |
// Закрываем канал чтения |
write(fd[1], buf, n); // Пишем в канал |
|
close(fd[1]); |
// Закрываем канал записи |
wait(NULL); |
|
puts("Родительский процесс завершил работу..."); |
|
}else{ |
// Дочерний процесс |
close(fd[1]); |
// Закрываем канал записи |
write(1, gmsg, sizeof(gmsg)); |
|
while(read(fd[0], buf2, 1) > 0) |
|
write(1, |
buf2, 1); |
close(fd[0]); |
// Закрываем канал чтения |
puts("Дочерний процесс завершил работу...");
}
return EXIT_SUCCESS;
42
}
Замечание
Следует помнить:
•с каналами не работает функция lseek(...);
•системные вызовы не всегда корректно работают с консолями систем разработки, поэтому программы лучше запускать в терминале из командной строки.
На рисунке 1.14 представлен пример запуска программы, представленной на листинге 1.8.
Рисунок 1.14 — Запуск программы листинга 1.8
Неименованные каналы UNIX позволяют общаться только родственным друг другу процессам, которые полученны с помощью fork().
Для целей взаимодействия не родственных («чужих») процессов предназначены именованные каналы FIFO, которые также создаются в ядре ОС, но имеют имена, отображаемые в файловой системе.
Типичное применение каналов FIFO - разработка приложений «клиент — сервер», когда:
•несколько процессов могут записывать или читать FIFO одновременно;
•режим работы с FIFO - полудуплексный, что позволяет процессам общаться только в одном из направлений.
Создать канал FIFO можно с помощью изученной уже в подразделе 1.3 функции mknod(...), если во втором параметре mode установить тип файла S_IFIFO, а третьему параметру dev присвоить значение 0.
43
Стандарт POSIX предоставляет для создания именованных каналов специальный системный вызов mkfifo(...), имеющий следующий синтаксис его описания:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
где pathname — имя создаваемого канала;
mode — режимы доступа к каналу с учетом маски, которую можно установить с помощью системного вызова umask(...).
При возникновении ошибки функция возвращает -1, в противном случае 0.
Замечание
Системный вызов umask(...), синтаксис которого представлен ниже, используется для установки системной маски ОС при всех формах создания и открытия файлов:
#include <sys/types.h> #include <sys/stat.h> mode_t umask(mode_t mask);
umask(...) устанавливает и возвращает значение равное mask & 0777. Например, если выполнить последовательность команд:
umask(022); mkfifo("/home/upk/cfifo", 0666);
то канал /home/upk/cfifo откроется с правами: 0666 & ~022 = 0644 = rw-r--r--
Перед использованием каналы FIFO требуют своего открытия с помощью системного вызова open(…). Если при этом не указать режим O_NONBLOCK, то:
•открытие FIFO блокируется и для записи, и для чтения;
•при записи канал блокируется до тех пор, пока другой процесс не откроет FIFO для чтения;
•при чтении канал снова блокируется до тех пор, пока другой процесс не запишет данные;
•если FIFO закрыть для записи через close(...) или fclose(...), то это значит, что для чтения в FIFO помещается EOF.
Флаг открытия канала O_NONBLOCK может использоваться только при досту-пе для чтения. При попытке открыть FIFO с O_NONBLOCK для записи возникает ошибка открытия.
Если несколько процессов пишут в один и тот же FIFO, необходимо обратить внимание на то, чтобы сразу не записывалось больше, чем PIPE_BUF байтов. Это необходимо, чтобы данные не смешивались друг с другом.
44
Чтобы не запускать множество программ, рассмотрим работу каналов FIFO на примере двух родственных процессов:
•программа запускается и создает канал FIFO с именем /dev/home/cfifo, а затем выполняет fork(), создавая дочерний процесс;
•родительский процесс открывает канал на чтение, читает данные и выводит
их на терминал; если прочитано менее 2-х байт, то — ожидает завершение дочернего процесса, а затем завершается сама;
•дочерний процесс, в цикле, читает строку данных с клавиатуры, открывает
канал для записи, пишет в него данные и закрывает канал; если с клавиатуры прочитано менее 2-х байт, то, после передачи их в канал, завершает свою работу.
На листинге 1.9 представлен исходый текст программы, реализующий этот проект.
Листинг 1.9 — Взаимодействие процессов через именованный канал FIFO
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <limits.h> #include <sys/wait.h>
int main(void) { int fd, pid; ssize_t n;
char buf[PIPE_BUF];
puts("Программа lab1.8: Взаимодействие через канал /home/upk/cfifo"); // Создаем именованный канал
unlink("/home/upk/cfifo"); umask(0);
if ((mkfifo("/home/upk/cfifo", 0666)) < 0) { perror("fifo");
exit(EXIT_FAILURE);
}
pid = fork(); |
// Создаем дочерний процесс |
|
if (pid == -1) { |
|
|
perror("fork"); |
|
|
exit(EXIT_FAILURE); |
|
|
} |
|
|
if (pid > 0) { |
// Родительский процесс |
|
while ((fd = open("/home/upk/cfifo", O_RDONLY)) > -1) { |
||
|
|
_BUF); |
n = read(fd, buf, PIPE |
||
close(fd); |
|
|
if (n < 2) break; |
|
|
buf[n] = '\0'; |
|
|
printf("Родительский |
процесс прочитал %lu байт: %s", n, buf); |
|
} |
|
|
wait(NULL);
puts("Родительский процесс завершил работу..."); }else{ // Дочерний процесс
while (1) {
puts("Дочерний процесс: Введи строку и нажми Enter "); n = read(0, buf, PIPE_BUF);
45
if ((fd = open("/home/upk/cfifo", O_WRONLY)) < 0) { perror("fifo - дочерний процесс");
exit(EXIT_FAILURE);
}
if (n < 2) write(fd, "\n", 1); else write(fd, buf, n); close(fd);
if (n < 2) break;
}
puts("Дочерний процесс завершил работу...");
}
return EXIT_SUCCESS;
}
На рисунке 1.15 приведен пример вывода этой программы.
Рисунок 1.15 — Пример вывода программы листинга 1.9
Замечание
Изученные в данном подразделе системные средства работы с каналами являются достаточно эффективными, но требуют значительных усилий по синхронизации работы процессов. В теме 9 будут изучены проблемы синхронизации и блокировки процессов, также расстотрены подходы к их решению.
Кроме того, имеется утилита mkfifo, которая позволяет создавать именованные каналы из командной строки терминала.