Материал: 6262

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

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

1.5.2 Именованные каналы FIFO

Неименованные каналы 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, которая позволяет создавать именованные каналы из командной строки терминала.

Источник: https://studfile.net/preview/16438530/