6.2.2. Создание каналов на Си

Создание каналов на языке программирования Си может оказаться чуть более сложным, чем наш простенький shell-пример. Чтобы создать простой канал на Си, мы прибегаем к использованию системного вызова pipe(). Для него требуется единственный аргумент, который является массивом из двух целых (integer), и, в случае успеха, массив будет содержать два новых файловых дескриптора, которые будут использованы для канала. После создания канала процесс обычно порождает новый процесс (вспомним, что процесс-потомок наследует открытые файловые дескрипторы)

. SYSTEM CALL: pipe();

PROTOTYPE: int pipe( int fd[2] );

 

RETURNS: 0 в случае успеха

 

-1 в случае ошибки:

 

errno = EMFILE (нет свободных дескрипторов)

 

EMFILE (системная файловая таблица переполнена)

EFAULT (массив fd некорректен)

NOTES: fd[0] устанавливается для чтения, fd[1] - для записи.

Первое целое в массиве (элемент 0) установлено и открыто для чтения, в то время как второе целое (элемент 1) установлено и открыто для записи. Наглядно говоря, вывод fd1 становится вводом для fd0. Еще раз отметим, что все данные, проходящие через канал, перемещаются через ядро.

#include

#include

#include

main()

{

 

int fd[2];

pipe(fd);

.

.

}

Вспомните, что имя массива decays в указатель на его первый член. fd - это эквивалент &fd[0]. Раз мы установили канал, то ответвим нашего нового потомка

#include

#include

#include

main()

{

 

int fd[2];

pid_t childpid;

pipe(fd);

if((childpid = fork()) == -1)

{

 

perror("fork");

exit(1);

}

.

.

}

Если родитель хочет получить данные от потомка, то он должен закрыть fd1, а потомок должен закрыть fd0. Если родитель хочет послать данные потомку, то он должен закрыть fd0, а потомок - fd1. С тех пор как родитель и потомок делят между собой дескрипторы, мы должны всегда быть уверены, что не используемый нами в данный момент конец канала закрыт; EOF никогда не будет возвращен, если ненужные концы канала не закрыты.

#include

#include

#include

main()

{

 

int fd[2];

pid_t childpid;

if((childpid = fork()) == -1)

{

 

perror("fork");

exit(1);

}

if(childpid == 0)

{

 

/* Потомок закрывает вход */

close(fd[0]);

} else

{

 

/* Родитель закрывает выход */

close(fd[1]); }

.

.

}

Как было упомянуто ранее, раз канал был установлен, то файловые дескрипторы могут обрабатываться подобно дескрипторам нормальных файлов.


Excerpt from "Linux Programmer's Guide - Chapter 6" (C)opyright 1994-1995, Scott Burkett

MODULE: pipe.c

#include

#include

#include

int main(void)

{

 

int fd[2], nbytes;

pid_t childpid;

char string[] = "Hello, world!\n";

char readbuffer[80];

pipe(fd);

if((childpid = fork()) == -1)

{

 

perror("fork");

exit(1);

}

if(childpid == 0)

{

 

/* Потомок закрывает вход */

close(fd[0]);

/* Посылаем "string" через выход канала */

write(fd[1], string, strlen(string));

exit(0);

}

else

{

 

/* Родитель закрывает выход */

close(fd[1]);

/* Чтение строки из канала */

nbytes = read(fd[0], readbuffer, sizeof(readbuffer));

printf("Received string: %s", readbuffer);

} return(0);

}

Часто дескрипторы потомка раздваиваются на стандартный ввод или вывод. Потомок может затем exec() другую программу, которая наследует стандартные потоки. Давайте посмотрим на системный вызов dup():

SYSTEM CALL: dup();

PROTOTYPE: int dup( int oldfd );

 

RETURNS: new descriptor on success

 

-1 on error: errno = EBADF (oldfd некорректен)

 

EBADF ($newfd is out of range$)

EMFILE (слишком много дескрипторов для процесса)

NOTES: старый дескриптор не закрыт! Оба работают совместно!

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

Рассмотрим:

.

.

childpid = fork();

if(childpid == 0)

{

 

/* Закрываем стандартный ввод потомка */

close(0);

/* Дублируем вход канала на stdin */

dup(fd[0]);

execlp("sort", "sort", NULL);

.

}

Поскольку файловый дескриптор 0 (stdin) был закрыт, вызов dup() дублировал дескриптор ввода канала (fd0) на его стандартный ввод. Затем мы сделали вызов execlp(), чтобы покрыть код потомка кодом программы sort. Поскольку стандартные потоки exec()-нутой программы наследуются от родителей, это означает, что вход канала ста для потомка стандартным вводом! Теперь все, что первоначальный процесс-родитель посылает в канал, идет в sort.

Существует другой системный вызов, dup2(), который также может использоваться. Этот особенный вызов произошел с Version 7 of UNIX и был поддержан BSD, и теперь требуется по стандарту POSIX.

SYSTEM CALL: dup2();

PROTOTYPE: int dup2( int oldfd, int newfd );

 

RETURNS: новый дескриптор в случае успеха

 

-1 в случае ошибки: errno = EBADF (oldfd некорректен)

 

EBADF ($newfd is out of range$)

EMFILE (слишком много дескрипторов для процесса)

NOTES: старый дескриптор закрыл dup2()! Благодаря этому особенному вызову мы имеем закрытую операцию и действующую копию за один системный вызов. Вдобавок, он гарантированно неделим, что означает, что он никогда не будет прерван поступающим сигналом. С первым системным вызовом dup() программисты были вынуждены предварительно выполнять операцию close(). Это приводилок наличию двух системных вызовов с малой степенью защищенности в краткий промежуток времени между ними. Если бы сигнал поступил в течение этого интервала времени, копия дескриптора не состоялась бы. dup2() разрешает для нас эту проблему.

Рассмотрим:

.

.

childpid = fork();

if(childpid == 0)

{

/* Закрываем стандартный ввод, дублируем вход канала на стандартный ввод */

dup2(0, fd[0]);

execlp("sort", "sort", NULL);

.

.

}

Используются технологии uCoz