Программирование >>  Структура ядра и системные вызовы 

1 ... 34 35 36 [ 37 ] 38 39 40 ... 98


Порожденные процессы-братья: родительский вызывает pipe для создания канала, затем порождает два или больше процессов-братьев. Порожденные процессы могут сообшаться по каналу посредством своих дескрипторов fifof0] и flfo[ 1].

Так как буфер, связанный с каналом, имеет конечный размер (PIPE BUF), то при попытке процесса записать данные в уже заполненный канал ядро заблокирует его до тех пор, пока другой процесс не произведет считывание из канала достаточного количества данных, чтобы заблокированный процесс смог возобновить операцию записи. И наоборот, если канал пуст, а процесс пытается прочитать из него данные, он будет блокироваться до тех пор, пока другой процесс не запишет данные в канал. Эти блокирующие механизмы можно использовать для синхронизации выполнения двух (и более) процессов.

Количество процессов, которые могут параллельно присоединяться к любому концу канала, не ограничено. Однако если два или более процесса записывают в канал данные одновременно, каждый процесс за один раз может записать максимум PIPE BUF байтов данных. Предположим, процесс (назовем его А) пытается записать X байтов данных в канал, в котором имеется место для Y байтов данных. Если X больше, чем Y, только первые Y байтов данных записываются в канал, и процесс блокируется. Запускается другой процесс (например. В); в это время в канале появляется свободное пространство (благодаря третьему процессу, считывающему данные из канала). Процесс В записывает данные в канал. Затем, когда выполнение процесса А возобновляется, он записывает оставшиеся X-Y байтов данных в канал. В результате данные в канал записываются поочередно двумя процессами. Аналогичным образом, если два (или более) процесса одновременно попытаются прочитать данные из канала, может случиться так, что каждый из них прочитает только часть необходимых данных.

Для предотвращения перечисленных выше недостатков коммуникационный канал обычно делают однонаправленным и устанавливают его только между двумя процессами, чтобы один процесс являлся отправителем, а другой - получателем. Если два процесса, например А и В, нуждаются в двунаправленном коммуникационном канале, они создадут два канала: один для записи данных процесса А в процесс В, другой для осуществления обратной операции.

Если дескриптора файла, связанного с записывающей стороной канала, не существует, то последняя считается закрытой и любой процесс, пытающийся считать данные из канала, полушет оставшиеся в нем данные. Однако если все данные из канала уже выбраны, процесс, пытающийся продолжать чтение данных из канала, получает признак конца файла (системный вызов read возвращает нулевое значение). С другой стороны, если не существует дескриптора файла, связанного с читающей стороной канала, а процесс пытается записать данные в канал, он получает от ядра сигнал SIGPIPE (разорванный канал). Это происходит потому, что данные, записанные в канал, не могут быть выбраны процессом и, следовательно, операция записи считается недопустимой. Процесс, производящий запись, будет наказан

этим сигналом (действие этого сигнала по умолчанию - преждевременное завершение процесса).

Системный вызов pipe используется shell UNIX для реализации командного канала ( ) с целью соединения стандартного вывода одного процесса со стандартным вводом другого. Он также применяется для реализации библиотечных С-функций рореп и pclose. Описание этих функций приводится в следующем разделе.

В случае успешного завершения pipe возвращает О, а в случае неудачи -1. Возможные значения, которые этот API присваивает переменной егто, приведены в следующей таблице:

Значение егто

Смысл

EFAULT

Аргумент/(/ о задан неправильно

ENFILE

Системная таблица файлов заполнена

Как используется API pipe, показано на примере следующей программы:

♦include <iostream.h> tinclude <stdio.h> tinclude <stdlib.h> finclude <string.h> tinclude <sys/types.h> tinclude <sys/wait.h> finclude <unistd.h>

int mainO {

pid t child pid;

int : fifo[2], status;

char.j;. buf [80];

if (pipe(fifo)==-l) perrorCpipe ), exit(l);

switch (child pid=forlt() )

case -1: perror ( forlt ) ; exit (2) ;

case 0: close(fifо[0]); /♦ порожденный процесс */

write(fifo[l], Child %d executed\n ,getpid()); close(fifo[l]); exit (0) ;

} , ,

close(fifo[l]); /*. родительский процесс */

while (read(fifo[0],buf,80)) cout buf endl; olose(fifo[0]);

if (waitpid(child pid,sstatus,0)==child pid && WIFEXITED(status))

return WEXITSTATUS(status); return 3;



в этой программе показан простой вариант использования pipe. Родительский процесс вызывает pipe для создания канала. Затем он вызывает fork для создания порожденного процесса. Как родительский, так и порожденный процессы имеют доступ к каналу через сюи собственные копии переменной fifo.

В данном примере порожденный процесс определяется как отправитель сообщения родительскому, записывающий в канал сообщение Child <child pid> executed посредством дескриптора fifoflj. Системный вызов getpid возвращает значение идентификатора порожденного процесса. После записи порождершый процесс завершается с помощью exit с нулевым кодом возврата.

Родительский процесс определяется как получатель, который читает сообщение порожденного процесса с помощью дескриптора fifof0]. Следует отметить, что родительский процесс сначала закрывает дескриптор У(/Ь у, а затем входит в цикл для чтения данных из канала. Это необходимо для того, чтобы записывающая сторона канала закрывалась, когда порожденный процесс после завершения записи закроет свой дескриптор fifof 1]. После чтения всех сообщений порожденного процесса родительский процесс получает признак конца файла. Если родительский процесс не закроет fifof 1] до окончания своего цикла чтения, то он будет заблокирован в системном вызове read сразу же после считывания всех данных из канала (записывающая сторона канала, обозначенная дескриптором файла flfofl], все еще открыта, поэтому признака конца файла в канале не будет).

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

После выхода из цикла чтения родительский процесс вызывает waitpid для получения статуса завершения порожденного процесса и завершается либо с кодом завершения порожденного процесса (если порожденный процесс завершился с помощью exit), либо с кодом неудачного завершения 3.

Вьшолнение этой программы может дать следующий результат:

% СС test pipe.C -о test pipe; test pipe Child 1234 executed

8.2.6. Переназначение ввода-вывода

в ос UNIX процесс может с помощью библиотечной С-фуакшт freopen изменять свои порты стандартного ввода и (или) вывода, переназначая их с консоли на текстовые файлы. Например, приведенные ниже операторы заменяют стандартный вывод процесса на файл foo, чтобы оператор printf мог записать в этот файл сообщение Greeting message to foo:

FILE *fptr = freopenCfoo , w , stdout); printf( Greeting message to foo\n );

Аналогичным образом следующие операторы заменяют стандартный ввод процесса на файл foo, направляя содержимое всего файла на стандартный вывод:

char buf[256];

FILE *fptr = freopen ( foo , r , stdin); while (gets(buf)) puts(buf);

По сути, функция freopen при переназначении стандартного ввода и вывода использует системные вызовы open и dup2. Так, для переназначения стандартного ввода процесса из файла srcstream можно сделать следующее:

tinclude <unistd.h>

int fd = open( src stream , 0 RDONLY) ;

if (fd != -1) dup2(fd, STDIN FILENO); close(fd);

Сначала указанные операторы открывают файл scrstream только для чтения, а дескриптор/rfобозначает открытый файл. Если вызов open проходит успешно (то есть значение fd не равно -1), вызывается функция dup2 - для того, чтобы макрос STDINFILENO (который определен в заголовке <unistd.h> и является значением дескриптора файла стандартного ввода) обозначал теперь файл scrstream. Затем дескриптор fd отсоединяется от файла посредством системного вызова close. В результате этого дескриптор STDIN FILENO процесса будет теперь обозначать файл scr stream.

Подобные системные вызовы можно использовать и для перенаправления стандартного вывода процесса в файл:

♦include <unistd.h>

int fd = open( dest stream , b WRONLY 0 CREAT I 0 TRUNC, 0644); if (fd != -1) dup2(fd, STDOUT FILENO); close(fd);

в результате действия перечисленных операторов любые данные, посылаемые на стандартный вывод процесса с дескриптором STDOUT FILENO, будут записываться в файл dest Jile.

Функция freopen может быть реализована следующим способом:

FILE* freopen (const char* file name, const char* mode, FILE* old fstream)

if (strcmp(mode, r ) && strcmp(mode, w ))

return NULL; /* недопустимое значение mode */

int fd = open(file name, *mode==r ? 0 RDONLY :

0 WRONLY I 0 CREAT 0 TRUNC, 0644); if (fd == -1) return NULL; if (!old fstream) return fdopen(fd, mode); fflush(old fstream);

int fd2 = dup2(fd, fileno(old fstream)); close (fd);

return (fd2 == -1) ? NULL : old fstream;



Если значение аргумента тмойе не равно V или V, функция возврашает NULL-указатель потока, так как другие режимы доступа функция не поддерживает. Если файл, заданный аргументом. /е awe, невозможно открыть в указанном режиме, функция также возвращает NULL-указатель потока. Если же вызов open проходит успешно и аргумент oldJstream равен О, то исходного потока для переназначения нет. В этом случае функция просто преобразует дескриптор файла fd в указатель потока (с помощью функции fdopen) и возвратит его вызывающему процессу.

Если, однако, значение oldJstream не равно NULL, функция вначале сбросит все данные, хранящиеся в буфере ввода-вывода этого потока, посредством вызова функции fflush, а затем с помощью dup2 сделает так, что дескриптор файла, связанный с old Jstream, будет обозначать открытый файл. Следует заметить, что использовать здесь fclose для очистки буфера и закрытия old Jstream не разрешается. Чтобы открыть новый файл, в функции/георея нужно повторно использовать структуру FILE, обозначенную указателем old Jstream, но fclose освобождает эту структуру. Макрокоманда /ело определяется в заголовке <stdio.h>. Она возвращает дескриптор файла, связанный с данным указателем потока.

После вызова dup2 функция закрывает fd, так как он больше не нужен, и возвращает либо oldJstream, обозначающий теперь новый файл, либо NULL, если вызов dup2 прошел неудачно.

8.2.6.1. Переназначение ввода-вывода в ОС UNIX

Конструкции переназначения стандартных потоков ввода (<) и вывода (>) shell UNIX можно реализовать с помощью метода, описанного выше. Правда, операция переназначения будет выполнена до того, как порожденный процесс вызовет функцию exec для выполнения команды пользователя. В частности, команда shell может быть реализована с помощью такой программы:

% sort < foo > results

Приведенная ниже программа демонстрирует одну из возможностей переназначения стандартного ввода и вывода:

tinclude <unistd.h>

int mainO

int fd, fd2; switch ( forkO ) {

case -1: perror( fork ); break; case 0: if (fd=open( foo , 0 RDONLy)) == -1 II : # (fd=open( results , 0 WRONLY I

. 0 CREAT I 0 TRUNC, 0644)) == -1 )

I perror( open ); * exit(l); )

/* переназначить стандартный ввод из foo */ j . : if ( dup2(fd, STDIN FILENO) == -1 ) exit(5);

/* переназначить стандартный вывод в result */ if ( dup2(fd2, STDOUT FILENO) == -1 ) exit(6);

close(fd); close(fd2);

execlp( sort , sort , 0); perror( execlp ); exit (8);

return 0;

Данная программа создает для выполнения команды sort порожденный процесс. Этот процесс переназначает свой стандартный ввод на файл foo, а стандартный вывод - на файл results. Если оба вызова open успешны, порожденный процесс вызывает exec для выполнения команды sort. Так как у команды sort нет аргумента, она будет получать данные со стандартного ввода (файл/оо). Отсортированные данные направляются на стандартный вывод процесса, которым теперь является файл result.

Можно также переназначить порты стандартного ввода и (или) вывода родительского процесса еще до вызова fork и exec. Разница с порожденным процессом заключается в том, что после вызова fork родительский процесс либо не использует переназначенный порт, либо устанавливает его в исходное положение (например, в /dev/tty). Затем его можно использовать таким же образом, как и до вызова функции fork. Поэтому в порожденном процессе стандартный ввод и (или) вывод легче переназначать непосредственно перед системным вызовом exec.

8.2.6.2. Командные каналы ОС UNIX

Командные каналы shell операционной системы UNIX позволяют одновременно выполнять множество команд таким образом, что стандартный вывод одной команды (указанной слева от символа ) связывается напрямую со стандартным вводом следующей команды (указанной справа от символа I ). Так, при получении команды

% Is -11 sort -г

shell создает два порожденных процесса; один выполняет команду Is -/, а другой - команду sort.

Данные со стандартного вывода порожденного процесса, выполняющего команду Is -/, будут перенаправлены на стандартный ввод порожденного процесса, выполняющего команду sort. В результате действия обеих команд будет получен отсортированный перечень содержимого текущего кататога.

Программа command р )е. С демонстрирует возможность одновременного использования двух команд с помощью командного канала:

♦include <stdio.h> finclude <stdlib.h>



1 ... 34 35 36 [ 37 ] 38 39 40 ... 98

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика