|
Программирование >> Структура ядра и системные вызовы
#include <unistd.h> ♦include <sys/types.h> ♦include <sys/wait.h> ♦define CLOSE ALL() close(fifo[0]), close(fifo[1]) int main( int argc, char* argv[l) { int fifo[2]; pid t pidl, pid2; if (pipe (fifo)) perror ( pipe*)u exit(2) ; switch (pidl=fork()) 7* создать командный канал */ /* выполйить команду, указанную слева.:От символа канала */ case -1: perror( fork ), exit(3); case О : if (dup2 (f ifo [1] , STDOUT FILENOii ==-1) perror( dup2 ), exit(4); CLOSE ALL(); if (execlp( /bin/sh , sh , -c , argv{l], 0) =-1) perror( execl ), exit(5); switch (pid2=fork()) /* выполнить команду справа от символа канала */ case -1: case О: perror( fork ), exit (6); if (dup2(flfofO),STDIN FILEN0)==-1) perror( dup2 ), exit(7); CLOSE ALL(); if (execlp( /bin/Sh , sh , -Q! argv[2],0)==-l) perror( execl ), exit(8); CLOSE ALL(); if (waitpid(pidl,&fifо[0],0)!=pidlI I waitpid(pid2,&fifо[1],0)!=pid2) perror ( waitpid ) ; ,1.. return fifo[0]+fifo[l]; Эта программа принимает 6 командной строке два аргумента. Каждый аргумент указывает команду, подлежащую выполнению. Если команда содержит аргументы, то она вместе с аргументами должна бьп-ь взята в кавычки, чтобы shell передал их как один аргумент программы. Например, в следующей команде аргументом argvflj программы будет Is -/, а аргументом argvf2] - sort -г. % a.out Is -1 sort -г Когда эта программа запускается, она создает между потоками стандартного ввода и вывода двух подлежащих выполнению программ неименованный канал. Если системный вызов pipe оказывается неудачным, программа вызывает perror для вывода на экран диагностического сообщения и затем завершает свою работу. После создания канала программа порождает новый процесс - для выполнения команды, указанной в argvflj. Однако перед вызовом execl порожденный процесс переназначает свой поток стандартного вывода на записывающую сторону канала, а затем закрывает свою копию дескриптора канала. Таким образом, при выполнении команды argvflj аапвые стандартного вывода будут направляться в канал. Программа создает также другой порожденный процесс, который с помощью вызова exec активизирует Bourne-shell. Последний, в свою очередь, выполняет команду, указанную в argvflj. Однако перед вызовом execl порожденный процесс переназначает свой поток стандартного ввода на читающую сторону канала и закрывает свою копию дескриптора канала. Поэтому при выполнении argvf2J данные стандартного ввода будут получены из канала. После создания двух порожденных процессов родительский процесс закрывает дескрипторы канала. Это необходимо для того, чтобы при завершении первого порожденного процесса (выполняющего argvflj) отсутствовал дескриптор, связанный с записывающей стороной канала, а второй процесс (выполняющий argvf2J) мог прочитать признак конца файла и знал, когда ему нужно самозавершиться. Родительский процесс ожидает завершения двух порожденных процессов и проверяет их статус завершения. Приведенная выше программа не совместима с POSIX. 1, так как Bourne-shell в POSIX. 1 не определен. Она, однако, совместима со стандартом P0SIX.2, в котором shell и командные каналы определены. Эта программа может дать следующий результат: % СС -о conirnand pipe command pipe.C % command pipe Is -1 wc 52 410 3034 % comman j3ipe pwd cat /home/book/chapterlO % command pipe cat sort -r Hello world Вye-В ye Bye-Bye Hello world Данную программу можно усовершенствовать таким образом, чтобы она использовала произвольное количество команд shell и каналов. В частности, если новая программа запускается командой % command pipe Is -1 sort -г wc cat ЭТО аналогично такой команде shell: * Is -1 I sort -г I wc I cat Как правило, если команда UNIX содержит п символов 1 , то shell должен создать п каналов (с помощью API pipe) n+I порожденных процессов. Так как для каждого вызова канала требуется два файловых дескриптора, а на процесс - максимум OPEN MAX дескрипторов за один раз, то shell должен использовать свои дескрипторы файлов многократно. Это позволяет системе с неименованными каналами обрабатывать неограниченное количество командных каналов. На примере программы command pipe.C покажем принцип обработки произвольного числа командных каналов и команд shell: tinclude <stdio.h> tinclude <stdlib.h> tinclude <sys/types.h>= tinclude <sys/wait.h> tinclude <unistd.h> tdefine CLOSE(fd) if (fd < -1) close(fd), fd=-l static int fifo[2][2] = { -1, -1, -1, -1 ), cur pipe=0; int main( int argc, char* afgv[]) for (int i=l; i argc; i++>-*K. if (pipe (fifo[cur pipe]Jj..!perr,or ( pipe ) , exit(2]; switch (forkO) .j, , /* выполнить, команду слева от символа канала */ Ч- , И: case -1: perror ( fork ) , exit(3]; case 0: if (i > 1) /*не первая команда */ [ . . . dup2(fifo[l-cur pipe][0],STDIN FILENO); CLOSE (f ifo [l-cur plpe] [0]),; if (i < argc-1) /* не последняя команда */ [ dup2(fifo[cur pipe][1],STDOUT FILENO); CLOSE(fifo[cur j)ipe][0]]; CLOSE(fifo[cur pipe][1]); if (execlp( /bin/sh , sh , -c ,argv[i],0)==-1) perror( execl ), exit(5]; CLOSE(fifo[l-cur pipe][0]]; CLOSE (fifo[cur pipe] [1].); cur pipe = 1 - cur pipe; CLOSE(fifo[l-cur pipe][0]); while (waitpid(-1,0,0]) ; return 0; Эта профамма создает уникальный канал между каждыми двумя последовательными командами. Все команды, кроме первой, получают данные стандартного ввода с читающей стороны своего канала (стандартный ввод первой команды осуществляется с консоли). Аналогичным образом все команды, кроме последней, посылают данные своего стандартного вывода на записывающую сторону командного канала (последняя посылает данные стандартного вывода на консоль). Единственным сложным моментом здесь является то, что родетельский процесс после создания каждого порожденного процесса закрывает все ненужные дескрипторы каналов. Это делается для того, чтобы родительский процесс не использовал все допустимые дескрипторы файлов и чтобы ненужные дескрипторы не использовались в новом порожденном процессе. Кроме того, родительский процесс должен убедиться в том, что в любой момент времени только один порожденный процесс имеет дескриптор, связанный с одной из сторон канала. Признак конца файла будет передаваться от первого командного процесса к последующему до тех пор, пока его не получат и не завершатся все порожденные процессы. Пробный запуск этой программы дает следующие результаты: % СС corraiiand pipe2 .с . % a.out Is sort wc ., 150 50 724 % a.out date Sun Apr 19 13:00 PST 1997 % a.out date wc 1 6 29 % a.out. pwd sort cat /home/book.chapterll 8.2.6.3. Функции popen и pclose В данном разделе описываются более сложные примеры использования функций fork, exec и pipe. В частности, здесь показано применение этих API для реализации библиотечных С-функций рореп и pclose. Функцию рореп можно задействовать для выполнения команды shell в программе пользователя. Прототип функции рореп выглядит так: FILE* рореп (const char* shell cmd, const char* mode); Первый аргумент, shell cmd,- это строка символов, содержащая любую команду shell, которую пользователь может задать в Bourne-shell. Рассматриваемая функция вызывает Bourne-shell для интерпретации и выполнения указанной команды. Второй аргумент, mode, равен г либо w . Он говорит о том, что указатель потока, возвращенный функцией, может быть использован как для чтения данных со стандартного вывода (если mode равен г ), так и для их записи на стандартный ввод (если mode равен w ) процесса Bourne-sheU, выполняющего shelljcmd. Если команда shell cmd не может быть выполнена, рореп возвращает значение NULL. Возможные причины неудачи: shell cmd - недопустимая команда либо у процесса нет права на ее выполнение. Функция pclose в качестве аргумента принимает указатель потока, возвращенный вызовом функции рореп. Она закрывает поток, связанный с этим указателем, а затем ожидает завершения соответствующего процесса Bourne-shell. Прототип этой функции выглядит так: int pclose (FILE* fstream); В случае успешного выполнения pclose возврашает статус завершения выполняемой команды, а в случае неудачи - -1. Возможная причина неудачи может заключаться в том, что использованное значение/j/reaw недопустимо или не определено вызовом рореп. Функция рореп реализуется путем вызова fork для создания порожденного процесса, который, в свою очередь, с помощью exec вызовет Bourne-shell {/bin/sh) для интерпретации и выполнения команды shell cmd. Кроме того, родительский процесс вызывает pipe, чтобы установить соединение либо между читаемой стороной канала и стандартным выводом порожденного процесса (если значение mode равно г ), либо между записывающей стороной канала и стандартным вводом порожденного процесса (если значение mode равно w ). Дескриптор файла другого конца канала преобразуется с помошью функции fdopen в указатель потока и возвращается в процесс, вызвавший функцию fdopen. Функция рореп может быть реализована следующим образом: ♦include <stdio.h> ♦include <stdlib.h> ♦include <sys/types.h> ♦include <sys/wait.h> ♦include <unistd.h> ♦include <string.h> ♦include <limits.h> struct sh rec { pid t sh pid; FILE* stream; } sh info[OPEN MAX]; static int num sh; FILE* popen(const char* shell cmd, const char* mode) ( int fifO{2]; if ((strcmp(mode, г ) &fr strcmp(mode, w ). ) pipe(fifo)==-l) -> return 0; , , switch (sh info[num sh] . sh pid=f or)c () ) { case -1: perror ( for)c ); return 0; case 0: (*mode==r) ? dup2(fifo[l], STDOUT FILENO): dup2(fifol-j, STDINFILENO); close(fifo{0]); close(fifo[l]); execl( /bin/sh , exit(5); sh , -C , shell cmd, 0); , if (*mode==r) { close(fifo{l]); return (sh info[nuin sh++].stream=fdopen(fifo{0], mode)); mi else { close (fifo {0]); ,з u , return (sh info[num sh++] .stream=fdopen(fifo{l); mode);) Следует отметить, что идентификатор каждого порожденного процесса и соответствующий указатель потока, полученные из вызова рореп, записываются в глобальный массив shjnfo. Массив shjnfo имеет 0PEN MAX-1 элементов, так как для вьщеления указателей потоков, обозначающих выполняемые команды shell, процесс может вызвать функцию рореп максимум 0PEN MAX-1 раз. Данные, хранящиеся в массиве shjnfo, используются функцией pclose следующим образом: после вызова pclose находит в массиве shjnfo элемент, значение stream которого совпадает со значением аргумента fstream. Если совпадения нет, то значение fstream является недопустимым и функция jjc/oje возврашает код неудачного завершения -1. Если совпадение есть, функция pclose закрывает поток (конец канала), указанный значением fstream, а затем ожидает окончания соответствующего порожденного процесса (идентификатор которого задан переменной sh pid в элементе shjnfo) и возврашает нулевой код успешного завершения. Функцию pclose можно реализовать следующим образом: int pclose (FILE* fstream) { int i, status, rc=-l; for (i=0; i < num sh; i++) if (sh info[i] .stream==fstream) brea)c; if (i==num sh) return -1; /*. недопустимое значение fstream */ fclose(fstream); if ;(waitpid(sh info{i].sh pid, sstatus, O)==sh info[i].sh pid WIFEXITED(status)) rc = WEXITSTATUS(status); for (i++; i < num sh; i++) sh info[i-l] = sh info[i]; num sh-; return rc;
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |