В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13 |
Общий терминальный интерфейс
При работе с (псевдо)терминалами (а также с каналами и сокетами) полезна функция poll() (см. листинг 9.9), входящая в расширение XSI и позволяющая мультиплексировать ввод/вывод в пределах набора файловых дескрипторов.
#include <poll.h> int poll (struct pollfd fds [], nfds_t nfds, int timeout);Листинг 9.9. Описание функции poll().
Для каждого элемента массива fds (с числом элементов nfds ) функция проверяет, наступили ли заданные события; в частности, возможны ли чтение или запись без блокирования процесса.
Согласно стандарту, структура pollfd содержит по крайней мере следующие поля.
int fd; /* Опрашиваемый файловый дескриптор */ short events; /* Флаги опрашиваемых событий */ short revents; /* Флаги произошедших событий */
Среди событий, обрабатываемых функцией poll(), выделим POLLIN (информирует о том, что можно без блокирования прочитать данные, не являющиеся высокоприоритетными), POLLRDNORM (можно прочитать обычные данные), POLLOUT (без блокирования можно записать обычные данные), POLLERR (выходной флаг - зафиксирована ошибка устройства или потока).
Разделение данных на обычные, приоритетные и высокоприоритетные зависит от реализации.
Если запрашиваемые события не наступают, poll() ждет по крайней мере timeout миллисекунд. Значение -1 задает бесконечную задержку.
В качестве примера описанных функций приведем упрощенную версию программы, запускающей командный интерпретатор на псевдотерминале (см. листинг 9.10). Программа использует библиотеку curses для высокоуровневой работы с терминалами, которую мы не описываем, однако понять смысл фигурирующих в ней функций довольно просто. Отметим лишь, что функция endwin() восстанавливает характеристики терминала.
/* * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа запускает shell на псевдотерминале */ /* * * * * * * * * * * * * * * * * * * * * * * * */ #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> #include <termios.h> #include <signal.h> #include <poll.h> #include <sys/resource.h> #include <curses.h> /* Действия при завершении процесса */ static void termination (int errcode) { endwin (); exit (errcode); } /* Функция обработки сигнала SIGCHLD */ static void chldied (int dummy) { /* Просто завершимся*/ termination (34); } int main (void) { WINDOW *win1, *win2; /* win1 - окно только для рамки */ /* win2 - окно для shell */ int pty, tty; /* Дескрипторы обеих сторон псевдотерминала */ int fr; /* Результат fork'а */ unsigned char ch; /* Прочитанный символ */ struct termios pt; /* Структура для смены характеристик псевдотерминала */ struct pollfd fds [2]; /* Массив параметров для вызова poll */ char ptybuf [L_ctermid]; /* Массив для хранения имени псевдотерминала */ char *s, *t; /* Указатели для перебора компонентов имени псевдотерминала */ int w2lines, w2cols; /* Размер создаваемого окна */ int x, y; /* Координаты в окне */ struct sigaction sact; int i; initscr (); cbreak (); noecho (); win1 = newwin (LINES, COLS, 0, 0); box (win1, 0, 0); wrefresh (win1); w2lines = LINES - 2; w2cols = COLS - 4; win2 = newwin (w2lines, w2cols, 1, 2); scrollok (win2, TRUE); /* Откроем первый свободный псевдотерминал */ for (s = "pqrs"; *s; s++) { for (t = "0123456789abcdef"; *t; t++) { sprintf (ptybuf, "/dev/pty%c%c", *s, *t); if ((pty = open (ptybuf, O_RDWR)) >= 0) { goto findpty; } } } fprintf (stderr, "Не удалось найти свободный псевдотерминал\n"); termination (-1); findpty: ptybuf [5] = 't'; if ((tty = open (ptybuf, O_RDWR)) < 0) { perror ("TTY OPEN ERROR"); termination (-1); } /* Установим подходящие характеристики псевдотерминала */ if (tcgetattr (pty, &pt) < 0) { perror ("PTY TERMIOS GET ERROR"); return (1); } pt.c_iflag = 0; pt.c_oflag = ONLCR; pt.c_cflag = CS8 | HUPCL; pt.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK; pt.c_cc [VINTR] = 3; /* CTRL+C */ pt.c_cc [VEOF] = 4; /* CTRL+D */ if (tcsetattr (pty, TCSADRAIN, &pt) < 0) { perror ("PTY TERMIOS SET ERROR"); return (2); } /* То же - для стандартного ввода */ (void) tcgetattr (0, &pt); pt.c_lflag &= ~ISIG; (void) tcsetattr (0, TCSADRAIN, &pt); /* Установим обработку сигнала о завершении потомка */ sact.sa_handler = chldied; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGCHLD, &sact, (struct sigaction *) NULL); /* Раздвоимся на процесс чтения с клавиатуры и вывода на экран */ /* и на процесс, в рамках которого запустим shell */ if ((fr = fork ()) < 0) { perror ("FORK1 ERROR"); termination (-1); } else if (fr) { /* Это процесс, читающий с клавиатуры */ /* и выводящий на экран */ close (tty); /* Будем ждать ввода с клавиатуры или псевдотерминала */ fds [0].fd = 0; fds [0].events = POLLIN; fds [1].fd = pty; fds [1].events = POLLIN; while (1) { if (poll (fds, 2, -1) < 0) { perror ("POLL ERROR"); termination (0); } if (fds [0].revents & POLLIN) { /* Пришел символ со стандартного ввода */ read (0, &ch, 1); write (pty, &ch, 1); } if (fds [1].revents & POLLIN) { /* Пришел символ с псевдотерминала */ read (pty, &ch, 1); switch (ch) { case '\n': { /* Проинтерпретируем перевод строки */ getyx (win2, y, x); if (y == (w2lines - 1)) { wmove (win2, y, w2cols - 1); waddch (win2, (chtype) ch); } else { wmove (win2, y + 1, 0); } break; } default: { /* Символ не интерпретируется */ waddch (win2, (chtype) ch); break; } } wrefresh (win2); } } /* Просто завершимся */ termination (0); } else { /* Порожденный процесс - запустим в нем shell */ /* Закроем все файлы, кроме псевдотерминала */ for (i = 0; i < RLIMIT_NOFILE; i++) { if (i != tty) { (void) close (i); } } /* Сделаем процесс лидером сеанса */ (void) setsid (); /* Свяжем стандартные ввод, вывод и протокол с псевдотерминалом */ (void) fcntl (tty, F_DUPFD, 0); (void) fcntl (tty, F_DUPFD, 0); (void) fcntl (tty, F_DUPFD, 0); close (tty); /* Сделаем псевдотерминал управляющим */ if ((tty = open (ptybuf, O_RDWR)) < 0) { perror ("TTY OPEN ERROR"); exit (-1); } close (tty); /* Поместим в окружение параметры псевдотерминала */ { char lnbuf [20]; char clbuf [20]; sprintf (lnbuf, "LINES=%2d", w2lines); sprintf (clbuf, "COLUMNS=%2d", w2cols); putenv (lnbuf); putenv (clbuf); } if (execl ("/bin/sh", "sh", (char *) NULL) < 0) { perror ("EXECL ERROR"); exit (-1); } } return 0; }Листинг 9.10. Пример программы, использующей псевдотерминалы.
В соответствии с определением, выводимые на псевдотерминал символы являются входными для порожденного процесса. Они читаются по дескриптору pty, а затем интерпретируются и выводятся на физический терминал. Обратим также внимание на манипуляции с флагом ISIG: сигналы разрешены только для псевдотерминала (но не для стандартного ввода).
В целях полноты изложения укажем, что в (частично) описанное выше семейство tc*() входят еще три функции, предназначенные для работы с управляющими терминалами (см. листинг 9.11).
#include <unistd.h> pid_t tcgetpgrp (int fildes); #include <unistd.h> int tcsetpgrp (int fildes, pid_t pgid_id); #include <termios.h> pid_t tcgetsid (int fildes);Листинг 9.11. Описание функций семейства tc*() для работы с управляющими терминалами.
Функция tcgetpgrp() позволяет узнать идентификатор группы процессов переднего плана, ассоциированной с терминалом, заданным открытым файловым дескриптором fildes. Для установки идентификатора группы служит функция tcsetpgrp(). Требуется, чтобы аргумент fildes соответствовал управляющему терминалу вызывающего процесса. Функция tcgetsid(), входящая в расширение XSI, по сути аналогична tcgetpgrp(). Она позволяет узнать идентификатор группы процессов лидера сеанса, для которого терминал, ассоциированный с файловым дескриптором fildes, является управляющим.
Для выполнения в известном смысле обратной операции - получения маршрутного имени управляющего терминала для вызывающего процесса - служит функция ctermid() (см. листинг 9.12).
#include <stdio.h> char *ctermid (char *s);Листинг 9.12. Описание функции ctermid().
Если значение аргумента s отлично от NULL, маршрутное имя управляющего терминала записывается по этому адресу; предполагается, что массив s имеет длину не менее L_ctermid байт. В противном случае имя помещается в область статической памяти, которая перезаписывается каждым вызовом.