Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программное обеспечение » Linux программирование в примерах - Арнольд Роббинс

Linux программирование в примерах - Арнольд Роббинс

Читать онлайн Linux программирование в примерах - Арнольд Роббинс

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 76 77 78 79 80 81 82 83 84 ... 165
Перейти на страницу:

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

Следующая программа, ch09-pipeline.c, создает эквивалент следующего конвейера оболочки:

$ echo hi there | sed s/hi/hello/g

hello there

Вот программа:

1  /* ch09-pipeline.c --- ответвляет два процесса в их собственный конвейер.

2     Для краткости проверка ошибок сведена к минимуму. */

3

4  #include <stdio.h>

5  #include <errno.h>

6  #include <sys/types.h>

7  #include <sys/wait.h>

8  #include <unistd.h>

9

10 int pipefd[2];

11

12 extern void left_child(void), right_child(void);

13

14 /* main --- порождение процессов и ожидание их завершения */

15

16 int main(int argc, char **argv)

17 {

18  pid_t left_pid, right_pid;

19  pid_t ret;

20  int status;

21

22  if (pipe(pipefd) < 0) { /* создать канал в самом начале */

23   perror("pipe");

24   exit(1);

25  }

26

27  if ((left_pid = fork()) < 0) { /* порождение левого потомка */

28   perror("fork");

29   exit(1);

30  } else if (left_pid == 0)

31  left_child();

32

33  if ((right_pid = fork()) < 0) { /* порождение правого потомка */

34   perror("fork");

35   exit(1);

36  } else if (right_pid == 0)

37  right_child();

38

39  close(pipefd[0])); /* закрыть родительские копии канала */

40  close(pipefd[1]);

41

42  while ((ret = wait(&status)) > 0) { /* wait for children */

43   if (ret == left_pid)

44    printf("left child terminated, status: %xn", status);

45   else if (ret == right_pid)

46    printf("right child terminated, status: %xn", status);

47   else

48    printf("yow! unknown child %d terminated, status %xn",

49     ret, status);

50  }

51

52  return 0;

53 }

Строки 22–25 создают канал. Это должно быть сделано в самом начале.

Строки 27–31 создают левого потомка, а строки 33–37 создают правого потомка. В обоих случаях родитель продолжает линейное исполнение ветви main() до тех пор, пока порожденный процесс не вызовет соответствующую функцию для манипулирования дескрипторами файла и осуществления exec.

Строки 39–40 закрывают родительскую копию канала.

Строки 42–50 в цикле ожидают потомков, пока wait() не вернет ошибку.

55 /* left_child --- осуществляет работу левого потомка */

56

57 void left_child(void)

58 {

59  static char *left_argv[] = { "echo", "hi", "there", NULL };

60

61  close(pipefd[0]);

62  close(1);

63  dup(pipefd[1]);

64  close(pipefd[1]);

65

66  execvp("echo", left_argv);

67  _exit(errno == ENOENT ? 127 : 126);

68 }

69

70 /* right_child --- осуществляет работу правого потомка */

71

72 void right_child(void)

73 {

74  static char *right_argv[] = { "sed", "s/hi/hello/g", NULL };

75

76  close(pipefd[1]);

77  close(0);

78  dup(pipefd[0]);

79  close(pipefd[0]));

80

81  execvp("sed", right_argv);

82  _exit(errno == ENOENT ? 127 : 126);

83 }

Строки 57–68 являются кодом для левого потомка. Процедура следует приведенным выше шагам, закрывая ненужный конец канала, закрывая первоначальный стандартный вывод, помещая с помощью dup() записываемый конец канала на номер 1 и закрывая затем первоначальный записываемый конец. В этот момент строка 66 вызывает execvp(), и если она завершается неудачей, строка 67 вызывает _exit(). (Помните, что строка 67 никогда не выполняется, если execvp() завершается удачно.)

Строки 72–83 делают подобные же шаги для правого потомка. Вот что происходит при запуске:

$ ch09-pipeline /* Запуск программы */

left child terminated, status: 0 /* Левый потомок завершается до вывода (!) */

hello there /* Вывод от правого потомка */

right child terminated, status: 0

$ ch09-pipeline /* Повторный запуск программы */

hello there /* Вывод от правого потомка и ... */

right child terminated, status: 0 /* Правый потомок завершается до левого */

left child terminated, status: 0

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

Весь процесс показан на рис. 9.5.

Рис. 9.5. Создание конвейера родителем

На рис. 9.5 (а) изображена ситуация после создания родителем канала (строки 22–25) и двух порожденных процессов (строки 27–37).

На рис. 9.5 (b) показана ситуация после закрытия родителем канала (строки 39–40) и начала ожидания порожденных процессов (строки 42–50). Каждый порожденный процесс поместил канал на место стандартного вывода (левый потомок, строки 61–63) и стандартного ввода (строки 76–78).

Наконец, рис. 9.5 (с) изображает ситуацию после закрытия потомками первоначального канала (строки 64 и 79) и вызова execvp() (строки 66 и 81).

9.4.2. Создание нелинейных конвейеров: /dev/fd/XX

Многие современные системы Unix, включая GNU/Linux, поддерживают в каталоге /dev/fd[98] специальные файлы. Эти файлы представляют дескрипторы открытых файлов с именами /dev/fd/0, /dev/fd/1 и т.д. Передача такого имени функции open() возвращает новый дескриптор файла, что в сущности является тем же самым, что и вызов dup() для данного номера дескриптора.

Эти специальные файлы находят свое применение на уровне оболочки: Bash, ksh88 (некоторые версии) и ksh93 предоставляют возможность замещения процесса (process substitution), что позволяет создавать нелинейные конвейеры. На уровне оболочки для входного конвейера используется запись '<(...)', а для выходного конвейера запись '>(...)'. Например, предположим, вам нужно применить команду diff к выводу двух команд. Обычно вам пришлось бы использовать временные файлы:

command1 > /tmp/out.$$.1

command2 > /tmp/out.$$.2

diff /tmp/out.$$.1 /tmp/out.$$.2

rm /tmp/out.$$.1 /tmp/out.$$.2

С замещением процессов это выглядит следующим образом:

diff <(command1) <(command2)

Не надо никаких беспорядочных файлов для временного запоминания и удаления. Например, следующая команда показывает, что наш домашний каталог является ссылкой на другой каталог:

$ diff <(pwd) <(/bin/pwd)

1c1

< /home/arnold/work/prenhall/progex

---

> /d/home/arnold/work/prenhall/progex

Незамысловатая команда pwd является встроенной в оболочку: она выводит текущий логический путь, который управляется оболочкой с помощью команды cd. Программа /bin/pwd осуществляет обход физической файловой системы для вывода имени пути.

Как выглядит замещение процессов? Оболочка создает вспомогательные команды[99] ('pwd' и '/bin/pwd'). Выход каждой из них подсоединяется к каналу, причем читаемый конец открыт в дескрипторе нового файла для главного процесса ('diff'). Затем оболочка передает главному процессу имена файлов в /dev/fd в качестве аргументов командной строки. Мы можем увидеть это, включив в оболочке трассировку исполнения.

$ set -х /* Включить трассировку исполнения */

$ diff <(pwd) <(/bin/pwd) /* Запустить команду */

+ diff /dev/fd/63 /dev/fd/62 /* Трассировка оболочки: главная,

 программа, обратите внимание на аргументы */

++ pwd /* Трассировка оболочки: вспомогательные программы */

++ /bin/pwd

1c1 /* Вывод diff */

< /home/arnold/work/prenhall/progex

---

> /d/home/arnold/work/prenhall/progex

Это показано на рис. 9.6.

Рис. 9.6. Замещение процесса

Если на вашей системе есть /dev/fd, вы также можете использовать преимущества этой возможности. Однако, будьте осторожны и задокументируйте то, что вы делаете. Манипуляции с дескриптором файла на уровне С значительно менее прозрачны, чем соответствующие записи оболочки!

9.4.3. Управление атрибутами файла: fcntl()

1 ... 76 77 78 79 80 81 82 83 84 ... 165
Перейти на страницу:
Тут вы можете бесплатно читать книгу Linux программирование в примерах - Арнольд Роббинс.
Комментарии