Наши партнеры








Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Linux-инструменты для Windows-программистов
Назад Библиотеки API POSIX Вперед

Терминал, режим ввода: канонический и некононический

Частным случаем блокирующего-неблокирующего вывода является ввод с терминала — часто задаваемый вопрос: как реализовать неблокирующий посимвольный ввод с терминала/консоли (такой режим часто используется, например, визуальными редакторами)? Какие вызовы API для этого использовать? Ответ состоит в том, что для неблокирующего ввода не существует какого-то специального набора вызовов POSIX, а используется соответствующий набор параметров терминала, и, в частности, канонический или неканонический режим ввода. Текущие установленные параметры терминала можно посмотреть (наиболее интересующие нас параметры в контексте данного рассмотрения: icanon, echo, min, time):

$ stty -a < /dev/tty 

speed 38400 baud; rows 33; columns 93; line = 0; 
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; 
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; 
min = 1; time = 0; 
-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts 
-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany 
imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke 

Примечание: Вся терминальная система (и команда stty) реализована в те давние времена, когда в большинстве случаев подключение терминала производилось через последовательные линии RS-232, поэтому очень много параметров ориентированы на параметры такой линии. Но, если возникает необходимость работать с RS-232 (для связи с устройствами), то оказывается полезной и ещё одна команда, вот как она возвращает, например, диагностику:

$ sudo setserial -bg /dev/ttyS* 

/dev/ttyS0 at 0x03f8 (irq = 4) is a 16550A 
/dev/ttyS1 at 0x02f8 (irq = 3) is a 16550A 

Режим обмена (то, что мы видели по команде stty) с терминалом описывается (<bits/termios.h>) программной структурой:

#define NCCS 32 
struct termios { 
   tcflag_t c_iflag;           /* input mode flags */ 
   tcflag_t c_oflag;           /* output mode flags */ 
   tcflag_t c_cflag;           /* control mode flags */ 
   tcflag_t c_lflag;           /* local mode flags */ 
   cc_t c_line;                /* line discipline */ 
   cc_t c_cc[NCCS];            /* control characters */ 
   speed_t c_ispeed;           /* input speed */ 
   speed_t c_ospeed;           /* output speed */ 
}; 

Основные функции (<termios.h>) работы с режимами терминала:

int tcgetattr( int fd, struct termios *termios );

int tcsetattr( int fd, int optional_actions, const struct termios *termios );

- где optional_actionsуказывает как поступать с вводом и выводом, уже поставленным в очередь. Это может быть (<bits/termios.h>) одно из следующих значений:

/* tcsetattr uses these */ 
#define TCSANOW         0 
#define TCSADRAIN       1 
#define TCSAFLUSH       2 

TCSANOW - делать изменения немедленно; TCSADRAIN - делать изменения после ожидания, пока весь поставленный в очередь вывод не выведен (обычно используется при изменении параметров, которые воздействуют на вывод); TCSAFLUSH - подобен TCSADRAIN, но отбрасывает любой поставленный в очередь ввод.

Этой информации достаточно, чтобы рассмотреть следующий пример (архив terminal.tgz): прямое управление курсором экрана терминала (нажатием клавиш 'd', 'u', 'l', 'r' — вверх, вниз, влево, вправо, соответственно, 'q' — выход из программы):

move.c :

#include <unistd.h> 
#include <stdlib.h> 
#include <fcntl.h> 
#include <termios.h> 
#include <stdio.h> 
int main ( int argc, char **argv ) { 
   struct termios savetty, tty; 
   char ch; 
   int x, y; 
   printf( "Enter start position (x y): " ); 
   scanf( "%d %d", &x, &y ); 
   if( !isatty( 0 ) ) { 
      fprintf( stderr, "stdin not terminal\n" ); 
      exit( EXIT_FAILURE ); 
   }; 
   tcgetattr( 0, &tty );             // получили состояние терминала 
   savetty = tty; 
   tty.c_lflag &= ~( ICANON | ECHO | ISIG ); 
   tty.c_cc[ VMIN ] = 1; 
   tcsetattr( 0, TCSAFLUSH, &tty ); // изменили состояние терминала 
   printf( "%c[2J", 27 );           // очистили экран 
   fflush( stdout ); 
   printf( "%c[%d;%dH", 27, y, x ); // установили курсор в позицию 
   fflush( stdout ); 
   for( ; ; ) { 
      read( 0, &ch, 1 ); 
      if( ch == 'q' ) break; 
      switch( ch ) { 
      case 'u': 
         printf( "%c[1A", 27 ); 
         break;
      case 'd': 
         printf( "%c[1B", 27 ); 
         break;
      case 'r': 
         printf( "%c[1C", 27 ); 
         break;
      case 'l': 
         printf( "%c[1D", 27 ); 
         break;
      }; 
      fflush( stdout ); 
   }; 
   tcsetattr( 0, TCSAFLUSH, &savetty ); // восстановили состояние терминала 
   printf( "\n" ); 
   exit( EXIT_SUCCESS ); 
} 

Примечание: Обязательная часть подобных программ (не показанная в примере, чтобы его не усложнять) это перехват сигналов завершения (SIGINT, SIGTERM) для восстановления перед завершением программы канонического режима ввода; в противном случае терминал будет «испорчен» для дальнейшего использования в качестве терминала. Для восстановления режима с успехом может быть использован вызов atexit(), как это сделано в примере (в том же архиве):

ncan.c :

...
struct termios saved_attributes; 
void reset_input_mode( void ) { 
   tcsetattr( STDIN_FILENO, TCSANOW, &saved_attributes); 
} 
void set_input_mode( void ) { 
   struct termios tattr; 
   ...
   tcgetattr( STDIN_FILENO, &saved_attributes ); 
   atexit( reset_input_mode ); 
   ...
   tcsetattr( STDIN_FILENO, TCSAFLUSH, &tattr ); 
} 
...

Предыдущий раздел: Оглавление Следующий раздел:
Асинхронный ввод-вывод   Источники информации