Книга: Основы программирования в Linux

Вызов pipe

Вызов pipe

Вы познакомились с высокоуровневой функцией popen, а теперь пойдем дальше и рассмотрим низкоуровневую функцию pipe. Она предоставляет средства передачи данных между двумя программами без накладных расходов на вызов командной оболочки для интерпретации запрашиваемой команды. Эта функция также позволит вам лучше управлять чтением и записью данных.

У функции pipe следующее объявление:

#include <unistd.h>
int pipe(int file_descriptor[2]);

Функции pipe передается указатель на массив из двух целочисленных файловых дескрипторов. Она заполняет массив двумя новыми файловыми дескрипторами и возвращает 0. В случае неудачи она вернет -1 и установит переменную errno для указания причины сбоя. В интерактивном справочном руководстве Linux на странице, посвященной функций pipe (в разделе 2 руководства), определены следующие ошибки:

EMFILE — процесс использует слишком много файловых дескрипторов;

ENFILE — системная таблица файлов полна;

EFAULT — некорректный файловый дескриптор.

Два возвращаемых файловых дескриптора подсоединяются специальным образом. Любые данные, записанные в file_descriptor[1], могут быть считаны обратно из file_descriptor[0]. Данные обрабатываются по алгоритму "первым пришел, первым обслужен", обычно обозначаемому как FIFO. Это означает, что если вы записываете байты 1, 2, 3 в file_descriptor[1], чтение из file_descriptor[0] выполняется в следующем порядке: 1, 2, 3. Этот способ отличается от стека, который функционирует по алгоритму "последним пришел, первым обслужен", который обычно называют сокращенно LIFO.

Примечание

Важно уяснить, что речь идет о файловых дескрипторах, а не о файловых потоках, поэтому для доступа к данным вы должны применять низкоуровневые системные вызовы read и write вместо библиотечных функций потоков fread и fwrite.

В упражнении 13.5 приведена программа pipe1.с, которая использует вызов pipe для создания канала.

Упражнение 13.5 Функция pipe

Следующий пример — программа pipe1.c. Обратите внимание на массив file_pipes, который передается функции pipe как параметр.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main() {
 int data_processed;
 int filepipes[2];
 const char some_data[] = "123";
 char buffer[BUFSIZ + 1];
 memset(buffer, '', sizeof(buffer));
 if (pipe(file_pipes) == 0) {
  data_processed = write(file_pipes[1], some_data, strlen(somedata));
  printf("Wrote %d bytesn", data_processed);
  data_processed = read(file_pipes[0], buffer, BUFSIZ);
  printf("Read %d bytes: %sn", data_processed, buffer);
  exit(EXIT_SUCCESS);
 }
 exit(EXIT_FAILURE);
}

Если вы выполните программу, то получите следующий вывод:

$ ./pipe1
Wrote 3 bytes
Read 3 bytes: 123

Как это работает

Программа создает канал с помощью двух файловых дескрипторов из массива file_pipes[]. Далее она записывает данные в канал, используя файловый дескриптор file_pipes[1], и считывает их обратно из file_pipes[0]. Учтите, что у канала есть внутренняя буферизация, позволяющая хранить данные между вызовами функций write и read.

Следует знать, что реакция на попытку писать с помощью дескриптора file_descriptor[0] или читать с помощью дескриптора file_descriptor[1] не определена, поэтому поведение программы может быть очень странным и меняться без каких-либо предупреждений. В системах авторов такие вызовы заканчивались аварийно и возвращали -1, что, по крайней мере, гарантирует легкость обнаружения такой ошибки.

На первый взгляд этот пример использования канала ничего не предлагает такого, чего мы не могли бы сделать с помощью простого файла. Действительные преимущества каналов проявятся, когда вам нужно будет передавать данные между двумя процессами. Как вы видели в главе 11, когда программа создает новый процесс с помощью вызова fork, уже открытые к этому моменту файловые дескрипторы так и остаются открытыми. Создав канал в исходном процессе и затем сформировав с помощью fork новый процесс, вы сможете передать данные из одного процесса в другой через канал (упражнение 13.6).

Упражнение 13.6. Каналы через вызов fork

1. Это пример pipe2.c. Он выполняется также как первый до того момента, пока вы не вызовете функцию fork.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main() {
 int data_processed;
 int file_pipes[2];
 const char some_data[] = "123";
 char buffer[BUFSIZ + 1];
 pid_t fork_result;
 memset(buffer, '0', sizeof(buffer));
 if (pipe(file_pipes) == 0) {
  fork_result = fork();
  if (fork_result == -1) {
   fprintf(stderr, "Fork failure");
   exit(EXIT_FAILURE);
  }

2. Вы убедились, что вызов fork отработал, поэтому, если его результат равен нулю, вы находитесь в дочернем процессе:

  if (fork_result == 0) {
   data_processed = read(file_pipes[0], buffer, BUFSIZ);
   printf("Read %d bytes: %sn", data_processed, buffer);
   exit(EXIT_SUCCESS);
  }

3. В противном случае вы должны быть в родительском процессе:

  else {
   data_processed = write(file_pipes[1], some_data,
    strlen(some_data));
   printf("Wrote %d bytesn", data_processed);
  }
 }
 exit(EXIT_SUCCESS);
}

После выполнения этой программы вы получите вывод, аналогичный предыдущему:

$ ./pipe2
Wrote 3 bytes
Read 3 bytes: 123

Вы можете столкнуться с повторным выводом строки приглашения для ввода команды перед завершающим фрагментом вывода, поскольку родительский процесс завершится раньше дочернего, поэтому мы подчистили вывод, чтобы его легче было читать.

Как это работает

Сначала программа создает канал с помощью вызова pipe. Далее она применяет вызов fork для создания нового процесса. Если fork завершился успешно, родительский процесс пишет данные в канал, в то время как дочерний считывает данные из канала. Оба процесса, и родительский, и дочерний, завершаются после одного вызова write и read. Если родительский процесс завершается раньше дочернего, вы можете увидеть между двумя выводами строку приглашения командной оболочки.

Несмотря на то, что программа внешне похожа на первый пример pipe, мы сделали большой шаг вперед, получив возможность использовать разные процессы для чтения и записи (рис. 13.2).


Рис. 13.2 

Оглавление книги


Генерация: 0.885. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз