Опубликован: 15.06.2004 | Доступ: свободный | Студентов: 2557 / 712 | Оценка: 4.35 / 3.96 | Длительность: 27:47:00
ISBN: 978-5-9556-0011-6
Лекция 9:

Общий терминальный интерфейс

< Лекция 8 || Лекция 9: 12345 || Лекция 10 >

При работе с (псевдо)терминалами (а также с каналами и сокетами) полезна функция 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 байт. В противном случае имя помещается в область статической памяти, которая перезаписывается каждым вызовом.

< Лекция 8 || Лекция 9: 12345 || Лекция 10 >
Антон Коновалов
Антон Коновалов

В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13
Планируется ли актуализация материалов данного очень полезного курса?