Драйвер терминала поддерживает два режима ввода. В каноническом режиме ввод группируется в строки (с одним из символов-разделителей в конце), позволяя редактировать текущую строку. Неканонический режим дает возможность приложению считывать ввод посимвольно, не дожидаясь ввода символа-разделителя; редактирование текущей строки при этом отключено. Завершение ввода в неканоническом режиме определяется полями MIN и TIME структуры termios, обозначающими минимальное количество символов, которое нужно прочитать, и, соответственно, время ожидания, относящееся к операции чтения. Мы описали четыре разных сценария чтения в неканоническом режиме.
Так сложилось, что драйверы терминала в седьмой редакции UNIX и в системе BSD предоставляли три режима ввода: с обработкой, без обработки и cbreak. Каждый из них обеспечивал определенную степень интерпретации ввода и вывода. Cbreak и режим без обработки можно эмулировать путем изменения различных полей в структуре termios.
Для выполнения других операций с терминалом предусмотрен ряд функций. Это касается изменения скорости терминала и управления строками (генерирование разрывов строки, ожидание передачи вывода, сброс входящей и исходящей очередей, приостановка и возобновление передачи данных между компьютером и терминалом). Другие функции позволяют получить имя терминала и проверить, ссылается ли на него заданный файловый дескриптор. С помощью системного вызова ioctl() можно выполнить ряд операций, связанных с терминалом, включая извлечение и изменение информации о размере его окна.
В книге [Stevens, 1992] тоже описывается программирование терминалов и раскрывается гораздо больше подробностей о работе с последовательными портами. Этой теме посвящено также несколько сетевых ресурсов. В частности, на веб-сайте проекта LDP (www.tldp.org) находятся методические пособия Дэвида Лойера по
58.1. Реализуйте функцию isatty() (для этого вам может пригодиться описание функции tcgetattr() в разделе 58.2).
58.2. Реализуйте функцию ttyname().
58.3. Реализуйте функцию getpass(), описанную в разделе 8.5 (для получения файлового дескриптора управляющего терминала можно открыть файл /dev/tty).
58.4. Напишите программу, которая выводит информацию о том, в каком режиме работает терминал, связанный со стандартным вводом, — каноническом или неканоническом, и затем отображает значения TIME и MIN.
59. Альтернативные модели ввода/вывода
В данной главе будут рассмотрены три альтернативы традиционной модели файлового ввода/вывода, которую мы применяли в большинстве программ в этой книге:
• мультиплексированный ввод/вывод (системные вызовы select() и poll());
• ввод/вывод, основанный на сигналах;
• программный интерфейс epoll, доступный только в Linux.
Большинство программ, представленных на данный момент в этой книге, использует модель ввода/вывода, согласно которой процесс работает одновременно только с одним файловым дескриптором, и каждый системный вызов блокируется в ожидании передачи данных. Например, при чтении из канала вызов read() обычно останавливается, если в этом канале не обнаружено никаких данных; то же самое происходит и с вызовом write() при нехватке в канале места для записи. Аналогичное поведение проявляется при работе с файлами других типов, включая очереди FIFO и сокеты.
Дисковые файлы представляют собой особый случай. Как уже говорилось в главе 13, ядро использует буферный кэш, чтобы ускорить запросы ввода/вывода к диску. Следовательно, вызов write() возвращается сразу после передачи данных в буферный кэш ядра, не дожидаясь фактической их записи на диск (если только при открытии файла не был указан флаг O_SYNC). Соответственно, вызов read() передает данные из буферного кэша в пользовательский буфер, и если данных в кэше нет, то ядро приостанавливает процесс, считывая тем временем данные с диска.
Традиционной блокирующей модели ввода/вывода достаточно для большинства приложений, но не для всех. В частности, иногда может возникнуть необходимость в выполнении одной из следующих операций (или сразу обеих):
• проверка доступности ввода/вывода для файлового дескриптора; при этом, если ответ отрицательный, то операция не должна заблокироваться;
• мониторинг нескольких файловых дескрипторов для определения доступности ввода/вывода для любого из них.
Мы уже познакомились с двумя методиками, которые позволяют частично удовлетворить эти потребности: неблокирующий ввод/вывод и применение нескольких процессов или потоков.