Книга: Linux программирование в примерах

9.5. Пример: двусторонние каналы в gawk

9.5. Пример: двусторонние каналы в gawk

Двусторонний канал соединяет два процесса двунаправленным образом. Обычно, по крайней мере для одного из процессов, на канал с другим процессом настраиваются как стандартный ввод, так и стандартный вывод. Оболочка Корна (ksh) ввела двусторонние каналы на уровне языка, обозначив термином сопроцесса (coprocess):

команды и аргументы движка базы данных |& /* Запустить сопроцесс в фоновом режиме */
print -p "команда базы данных" /* Записать в сопроцесс */
read -p db_response /* Прочесть из сопроцесса */

Здесь движок базы данных представляет любую серверную программу, которая может управляться интерфейсной частью, в данном случае, сценарием ksh. У движка базы данных стандартный ввод и стандартный вывод подсоединены к оболочке посредством двух отдельных односторонних каналов.[102] Это показано на рис. 9.7.


Рис. 9.7. Сопроцессы оболочки Корна

В обычном awk каналы к или от подпроцесса являются односторонними: нет способа послать данные в программу и прочесть посланные от нее в ответ данные — нужно использовать временный файл. GNU awk(gawk) заимствует обозначение '|&' от ksh для расширения языка awk:

print "команда" |& "движок базы данных" /* Запустить сопроцесс, записать в него */
"движок базы данных" |& getline db_response /* Прочесть из сопроцесса */
gawk
использует запись '|&' также для сокетов TCP/IP и порталов BSD, которые не рассматриваются в данной книге. Следующий код из io.c в дистрибутиве gawk 3.1.3 является частью функции two_way_open(), которая устанавливает простой сопроцесс: она создает два канала, порождает новый процесс и осуществляет все манипуляции с дескриптором файла. Мы опустили ряд не относящихся к делу частей кода (эта функция занимает больше места, чем следовало бы):

1561 static int
1562 two_way_open(const char *str, struct redirect *rp)
1563 {
      ...
1827 /* случай 3: двусторонний канал с порожденным процессом */
1828 {
1829  int ptoc[2], сtop[2];
1830  int pid;
1831  int save_errno;
1835
1836  if (pipe(ptoc) < 0)
1837   return FALSE; /* установлен errno, диагностика от вызывающего */
1838
1839  if (pipe(ctop) < 0) {
1840   save_errno = errno;
1841   close(ptoc[0]);
1842   close(ptoc[1]);
1843   errno = save_errno;
1844   return FALSE;
1845  }

Первым шагом является создание двух каналов, ptoc является каналом «от родителя к потомку», а ctop — «от потомка к родителю». Во время чтения держите в уме, что индекс 0 является читаемым концом, а 1 — записываемым.

Строки 1836–1837 создают первый канал, ptoc. Строки 1839–1845 создают второй канал, закрывая при неудачном создании и первый. Это важно. Небрежность в закрытии открытых, но не используемых каналов ведет к утечкам дескрипторов файлов. Как и память, дескрипторы файлов являются конечным ресурсом, и когда они иссякают, то теряются.[103] То же верно и для открытых файлов: убедитесь, что ваш обрабатывающий ошибки код всегда закрывает все открытые файлы и каналы, которые не нужны, когда происходит ошибка.

save_errno сохраняет значения errno, установленные pipe(), на тот редкий случай, когда close() может завершиться неудачей (строка 1840). Затем errno восстанавливается в строке 1843.

1906 if ((pid = fork()) < 0) {
1907  save_errno = errno;
1908  close(ptoc[0]); close(ptoc[1]);
1909  close(ctop[0]); close(ctop[1]);
1910  errno = save_errno;
1911  return FALSE;
1912 }

Строки 1906–1912 порождают процесс, на этот раз закрывая оба канала, если fork() потерпит неудачу. Здесь также первоначальное значение errno сохраняется и восстанавливается для последующего использования при диагностике.

1914 if (pid == 0) { /* порожденный процесс */
1915  if (close(1) == -1)
1916   fatal(_("close of stdout in child failed (%s)"),
1917    strerror(errno));
1918  if (dup(ctop[1]) != 1)
1919   fatal(_{"moving pipe to stdout in child failed (dup: %s)"), strerror(errno));
1920  if (close(0) == -1)
1921   fatal(_("close of stdin in child failed (%s)"),
1922    strerror(errno));
1923  if (dup(ptoc[0]) != 0)
1924   fatal(_("moving pipe to stdin in child failed (dup: %s)"), strerror(errno));
1925  if (close(ptoc[0]) == -1 || close(ptoc[1]) == -1
1926   || close(ctop[0]) == -1 || close(ctop[1]) == -1)
1927   fatal(_("close of pipe failed (%s)"), strerror(errno));
1928  /* stderr HE дублируется в stdout потомка */
1929  execl("/bin/sh", "sh", "-c", str, NULL);
1930  _exit(errno == ENOENT ? 127 : 126);
1931 }

Строки 1914–1931 обрабатывают код потомка, с соответствующей проверкой ошибок и сообщениями на каждом шагу. Строка 1915 закрывает стандартный вывод. Строка 1918 копирует записываемый конец канала от потомка к родителю на 1. Строка 1920 закрывает стандартный ввод, а строка 1923 копирует читаемый конец канала от родителя к потомку на 0. Если это все работает, стандартные ввод и вывод теперь на месте и подключены к родителю.

Строки 1925–1926 закрывают все четыре первоначальные дескрипторы файлов каналов, поскольку они больше не нужны. Строка 1928 напоминает нам, что стандартная ошибка остается на месте. Это лучшее решение, поскольку пользователь увидит ошибки от сопроцесса. Программа awk, которая должна перехватить стандартную ошибку, может использовать в команде обозначение '2>&1' для перенаправления стандартной ошибки сопроцесса или записи в отдельный файл.

Наконец, строки 1929–1930 пытаются запустить для оболочки execl() и соответственно выходят, если это не удается.

1934 /* родитель */
1935 rp->pid = pid;
1936 rp->iop = iop_alloc(ctop[0], str, NULL);
1937 if (rp->iop == NULL) {
1938  (void)close(ctop[0]);
1939  (void)close(ctop[1]);
1940  (void)close(ptoc[0]);
1941  (void)close(ptoc[1]);
1942  (void)kill(pid, SIGKILL); /* overkill? (pardon pun) */
1943
1944  return FALSE;
1945 }

Первым шагом родителя является настройка входного конца от сопроцесса. Указатель rp указывает на struct redirect, которая содержит поле для сохранения PID порожденного процесса, FILE* для вывода и указатель IOBUF* с именем iop. IOBUF является внутренней структурой данных gawk для осуществления ввода. Она, в свою очередь, хранит копию нижележащего дескриптора файла.

Строка 1935 сохраняет значение ID процесса. Строка 1936 выделяет память для новой IOBUF для данных дескриптора файла и командной строки. Третий аргумент здесь равен NULL: он позволяет при необходимости использовать предварительно выделенный IOBUF.

Если выделение памяти потерпело неудачу, строки 1937–1942 производят очистку, закрывая каналы и посылая сигнал «kill» порожденным процессам, чтобы заставить их завершить работу. (Функция kill() описана в разделе 10.6.7 «Отправка сигналов kill() и killpg()».)

1946 rp->fp = fdopen(ptoc[1], "w");
1947 if (rp->fp == NULL) {
1948  iop_close(rp->iop);
1949  rp->iop = NULL;
1950  (void)close(ctop[0]);
1951  (void)close(ctop[1]);
1952  (void)close(ptoc[0]);
1953  (void)close(ptoc[1]);
1954  (void)kill(pid, SIGKILL); /* избыточно? (пардон, каламбур)
[104] */
1955
1956  return FALSE;
1957 }

Строки 1946–1957 аналогичны. Они устанавливают вывод родителя на потомка, сохраняя дескриптор файла для записывающего конца канала от родителя к потомку в FILE*, используя функцию fdopen(). Если это завершается неудачей, строки 1947–1957 предпринимают те же действия, что и ранее: закрывают все дескрипторы каналов и посылают сигнал порожденным процессам.

С этого момента записываемый конец канала от родителя к потомку и читаемый конец канала от потомка к родителю хранятся в более крупных структурах: FILE* и IOBUF соответственно. Они автоматически закрываются обычными процедурами, которые закрывают эти структуры. Однако, остаются две задачи:

1960   os_close_on_exec(ctop[0], str, "pipe", "from");
1961   os_close_on_exec(ptoc[1], str, "pipe", "from");
1962
1963   (void)close(ptoc[0]);
1964   (void)close(ctop[1]);
1966
1967   return TRUE;
1968  }
      ...
1977 }

Строки 1960–1961 устанавливают флаг close-on-exec для двух дескрипторов, которые остались открытыми. os_close_on_exec() является простой функцией-оболочкой, которая выполняет эту работу на Unix- и POSIX-совместимых системах, но ничего не делает на системах, в которых нет флага close-on-exec. Это скрывает проблему переносимости в одном месте и позволяет избежать в коде множества запутывающих #ifdef здесь и в других местах io.c.

Наконец, строки 1963–1964 закрывают концы каналов, которые не нужны родителю, а строка 1967 возвращает TRUE для обозначения успеха.

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


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