Символьный ввод/вывод

Работа с последовательными и параллельными символьными устройствами

Введение
Взаимодействие библиотекой с драйверами
Управление устройствами
Режимы ввода
Производительность подсистемы
Консольные устройства
Эмуляция терминала
Последовательные устройства
Параллельные устройства
Псевдотерминальные устройства (pty)

Введение

Ключевым требованием для любой операционной системы реального времени является наличие высокопроизводительной системы ввода/вывода символьных данных. В устройствах символьного ввода/вывода, в отличие от блок-ориентированных устройств (например, дисков), операции ввода/вывода применяются для серий последовательно передаваемых байт.

В соответствии со спецификациями POSIX и UNIX, устройства символьного ввода/вывода помещаются в пространстве путевых имен в каталоге /dev. Например, последовательный порт, к которому можно присоединить модем или терминал, может иметь следующее путевое имя:

/dev/ser1

Как правило, устройства символьного ввода/вывода встречаются следующих видов:

Программы могут обращаться к устройствам символьного ввода/вывода посредством стандартных API-функций open(), close(), read() и write(). Для управления другими параметрами устройств символьного ввода/вывода (например, скорость двоичной передачи, четность, сигналы управления потоками данных и т.д.) предусмотрены дополнительные функции.

Поскольку в системе часто имеется несколько устройств символьного ввода/вывода, в ЗОСРВ «Нейтрино» предусмотрено семейство драйверов в виде библиотеки io-char, которая позволяет повысить уровень повторного использования кода.

11_1.png
Рисунок 1. Библиотека io-char

Библиотека io-char содержит весь код, необходимый для поддержки POSIX-семантики на данном устройстве. Она также содержит значительный объем кода для реализации тех возможностей символьного ввода/вывода, которые не предусмотрены спецификацией POSIX, но требуются для той или иной системы реального времени. Так как данный программный код находится в общей библиотеке, все драйверы наследуют эти возможности.

Драйвер представляет собой процесс, который посылает вызовы библиотеке. При работе устройства сначала запускается драйвер, который затем вызывает библиотеку io-char. В ЗОСРВ «Нейтрино» драйверы сами по себе являются обычными процессами, которые могут выполняться с разными приоритетами в зависимости от характеристик устройства и запросов клиента.

После запуска первого устройства символьного ввода/вывода присоединение других устройств требует минимальных расходов памяти, так как для этого понадобится только код, реализующий новую структуру драйверов.

Взаимодействие библиотекой с драйверами

Библиотека io-char управляет потоком данных между приложением и драйвером устройства. Обмен данными между io-char и драйвером происходит в памяти с помощью очередей, связанных с каждым соответствующим устройством.

Для каждого устройства используется три очереди. Каждая очередь реализуется с помощью FIFO-механизма.

11_2.png
Рисунок 2. Ввод/вывод данных на устройства в ЗОСРВ «Нейтрино»

Полученные данные драйвер помещает в очередь "сырых" входных данных (raw input queue), и затем они используются библиотеку io-char, только когда приложение выполняет обработку запрошенных данных. Более подробно о "сырых", а также редактируемых (или канонических) входных данных, см. в параграфе Режимы ввода.

Обработчики прерываний в драйверах, как правило, вызывают соответствующую библиотечную подпрограмму из io-char для того, чтобы добавить данные в очередь ввода. Это обеспечивает согласованную процедуру ввода и минимизирует функциональность, возложенную на драйвер (а также усилия разработчика при создании новых драйверов).

Библиотека io-char помещает выходные данные в очередь вывода, которая обрабатывается драйвером по мере того, как символы физически передаются на устройство. Он вызывает процедуру внутри драйвера всякий раз, когда поступают новые данные, и инициирует работу драйвера (в случае если он был неактивен). Так как выходные данные организуются в очереди, библиотека io-char применяет кеширование при записи (write-behind) для всех устройств символьного ввода/вывода. Только в случае переполнения буфера выходных данных io-char может остановить процесс во время записи.

Каноническая очередь (canonical queue) используется при обработке входных данных в редактируемом режиме (edited mode) и полностью управляется библиотекой io-char. Размер этой очереди определяет максимальную длину строки редактируемых входных данных, которая может быть обработана при работе с данным устройством.

Размер этих очередей может быть установлен с помощью ключей командной строки. Значения, принятые по умолчанию, как правило, вполне достаточны для большинства аппаратных конфигураций. Тем не менее, можно применять собственные "настройки" для того, чтобы снижать общие системные требования на память, учитывать необычные аппаратные ситуации или применять особые требования протокола.

Драйверы устройств просто добавляют полученные данные в очередь "сырых" входных данных или принимают данные из очереди выходных данных и передают их на устройство. Библиотека io-char решает, когда приостановить передачу выходных данных (если это необходимо), когда и как осуществлять эхо-возврат данных и т.д.

Управление устройствами

Низкоуровневое управление устройствами осуществляется с помощью вызова devctl(). В стандарте POSIX реализованы следующие функции управления терминалами на основе вызова devctl():

tcgetattr()
Получить атрибуты терминала
tcsetattr()
Установить атрибуты терминала
tcgetpgrp()
Получить идентификатор лидера группы процессов для терминала
tcsetpgrp()
Установить идентификатор лидера группы процессов для терминала
tcsendbreak()
Отправить команду остановки передачи данных (break)
tcflow()
Приостановить или перезапустить передачу/прием данных

ЗОСРВ «Нейтрино» имеет следующие расширения программного интерфейса для управления терминалами:

tcdropline()
Инициировать разрыв соединения. Для устройств последовательного порта этот вызов сгенерирует сигнал DTR
tcinject()
Добавить символы в канонический буфер

Библиотека io-char работает непосредственно с типовым набором команд devctl(), которые поддерживаются большинством устройств. Команды набора devctl(), специфичные для конкретного устройства, приложения отправляют драйверам с помощью io-char.

Режимы ввода

Каждое устройство может работать либо в режиме "сырых" (raw) входных данных, либо в режиме редактируемых (edited) входных данных.

Режим "сырых" входных данных

В режиме "сырых" входных данных библиотека io-char не применяет редактирования к принимаемым данным. Это уменьшает обработку каждого символа до минимума и дает наивысшую производительность при чтении данных.

Полноэкранные программы и программы обмена данными по последовательному порту представляют собой примеры приложений, в которых используются устройства в режиме "сырых" входных данных.

В этом режиме каждый символ принимается в буфер "сырых" входных данных с помощью обработчика прерываний. Когда приложение запрашивает данные с устройства, оно может указать, при каких условиях данный запрос должен завершиться. До тех пор, пока не возникнут заданные условия, обработчик прерываний не запустит драйвер, а драйвер, соответственно, не возвратит данные приложению. Простая операция чтения, выполняемая приложением в типовом случае, будет блокирована, пока не будет получен хотя бы один символ.

На рисунке ниже показан весь набор условий чтения данных:

11_3.png
Рисунок 3. Условия удовлетворения запросов на ввод данных

Здесь:

MIN
Ответить, если принято не менее данного количества символов
TIME
Ответить в случае паузы в потоке символов
TIMEOUT
Ответить по истечении заданного периода времени
FORWARD
Ответить при получении кадрирующего символа

В случае если задано множество условий чтения данных, запрос на чтение будет выполнен при удовлетворении любого из них.

MIN

Условие MIN полезно в тех случаях, когда приложение знает, какое именно количество символов оно должно принять.

Условие MIN может применяться для ожидания всего кадра целиком в любом протоколе, в котором известно количество символов в кадре данных. Это значительно уменьшает межзадачный обмен сообщениями и упрощает планирование процессов. Условие MIN часто используется вместе с условием TIME или TIMEOUT. Оно входит в спецификацию POSIX.

TIME

Условие TIME полезно в тех случаях, когда приложение принимает поток данных и при этом необходимо следить за возникновением пауз или остановок в потоке. Период паузы задается в десятых долях секунды. Условие TIME входит в спецификацию POSIX.

TIMEOUT

Условие TIMEOUT полезно в тех случаях, когда приложение знает, в течение какого времени оно должно ожидать получения данных. Период ожидания задается в десятых долях секунды.

Условие TIMEOUT может применяться в любом протоколе, в котором известно количество символов в кадре данных. В сочетании со значением скорости передачи это условие позволяет надежно определять время ожидания данных. Условие служит в качестве таймера безопасности для выявления случаев пропуска символов. Оно также может использоваться в интерактивных программах с целью создания паузы для ожидания пользовательских данных.

Условие TIMEOUT является расширением, принятым в ЗОСРВ «Нейтрино», и не входит в спецификацию POSIX.

FORWARD

Условие FORWARD полезно в тех случаях, когда в протоколе используются специальные кадрирующие символы. Например, в протоколе PPP, применяемом для реализации TCP/IP на последовательных соединениях, пакеты данных начинаются и заканчиваются кадрирующим символом. В сочетании с условием TIMEOUT, условие FORWARD позволяет значительно повысить производительность протокола. Процесс, реализующий протокол, принимает данные не отдельными символами, а целыми кадрами. В случае пропуска кадрирующего символа условие TIMEOUT или TIME позволяет быстро продолжить работу.

Это значительно сокращает межзадачный обмен сообщениями и в результате позволяет сократить нагрузку на процессор для данной скорости передачи данных по протоколу TCP/IP. Интересно отметить, что в протоколе PPP не предусмотрен подсчет символов в кадре. Отсутствие символа перенаправления данных могло бы привести к необходимости чтения данных посимвольно.

Условие FORWARD является расширением, принятым в ЗОСРВ «Нейтрино», и не входит в спецификацию POSIX.

Возможность перенести функциональность, отвечающую за уведомление приложений, в сервисные компоненты ОС, сокращает частоту, с которой должна происходить обработка на пользовательском уровне. Это уменьшает работу механизма межзадачного взаимодействия и освобождает процессор для работы с приложением. Кроме того, если приложение, реализующее протокол, выполняется на одном сетевом узле, а коммуникационный порт находится на другом, это позволяет минимизировать количество сетевых транзакций.

Механизм определения символа перенаправления данных (data-forwarding) также может быть реализован внутри интеллектуальных многопортовых последовательных адаптеров, что позволяет значительно сократить частоту, с которой адаптер должен прерывать главный процессор для обработки прерываний.

Режим редактируемых входных данных

В редактируемом режиме библиотека io-char выполняет редактирование строки при получении каждого символа. Строка символов передается приложению только после того, как она "полностью принята" (как правило, после получения символа возврата каретки). Этот режим называют каноническим.

Большинство неполноэкранных приложений работают в редактируемом режиме, так как в этом случае они могут обрабатывать данные построчно, а не посимвольно (в последнем случае приложению приходится определять символы конца строки).

В редактируемом режиме каждый символ принимается в буфер "сырых" входных данных обработчиком прерываний. В отличие от режима "сырых" входных данных, в котором драйвер запускается только при заданных условиях, в редактируемом режиме обработчик прерываний запускает драйвер при каждом полученном символе.

Это обусловлено двумя причинами. Во-первых, режим редактируемых входных данных редко используется для высокопроизводительных коммуникационных протоколов. Во-вторых, редактирование требует большой работы и поэтому не подходит для обработчика прерываний.

Во время работы драйвера библиотека io-char проверяет символ и помещает его в канонический буфер, в котором происходит сборка строки. После завершения формирования строки и запроса приложения на чтение, строка передается из канонического буфера в приложение (передача строки производится напрямую из канонического буфера в буфер приложения без промежуточного копирования).

Редактируемый режим позволяет выполнять корректную обработку нескольких строк входных данных, находящихся в каноническом буфере, а также чтение неполных строк (например, если приложение запросило только один символ из 10-символьной строки). В этом случае, следующая операция чтения выполняется там, где закончилась предыдущая.

Библиотека io-char имеет широкий набор функций редактирования, в том числе полную поддержку перемещения по строке, а также изменение, вставку и удаление отдельных символов. Приведем некоторые из основных функций:

LEFT
Переместить курсор на один символ влево
RIGHT
Переместить курсор на один символ вправо
HOME
Переместить курсор в начало строки
END
Переместить курсор в конец строки
ERASE
Удалить символ слева от курсора
DEL
Удалить символ на текущей позиции курсора
KILL
Удалить всю строку ввода
UP
Удалить текущую строку и восстановить предыдущую строку
DOWN
Удалить текущую строку и восстановить следующую строку
INS
Переключение между режимом вставки и замещения (каждая новая строка начинается в режиме вставки)

Символы редактирования строки могут быть разными для разных терминалов. Консоль всегда запускается с полным набором кодов редактирования.

Если терминал присоединен к последовательному порту, то необходимо использовать символы редактирования, которые применяются для данного терминала. Для этого можно использовать утилиты stty. Например, если к последовательному порту присоединен ANSI-терминал (с именем /dev/ser1), то необходимо использовать следующую инструкцию, чтобы извлечь соответствующие коды редактирования из базы данных terminfo и применить их к /dev/ser1:

stty term=ansi </dev/ser1

Производительность подсистемы

Поток событий внутри устройства организуется таким образом, чтобы минимизировать накладные расходы и довести до максимума производительность устройства при работе в режиме "сырых" входных данных. Для этого применяются специальные правила:

В сочетании с чрезвычайно небольшой задержкой обработки прерывания и задержкой планирования, которые свойственны ЗОСРВ «Нейтрино», эти правила дают очень компактную модель ввода, которая соответствует спецификации POSIX и имеет расширения, удовлетворяющие требованиям реального времени в различных протоколах.

Консольные устройства

Системные консоли (с VGA-совместимым графическим процессором, работающим в текстовом режиме) управляются с помощью драйвера devc-con или devc-con-hid. Видеоплата и дисплей, а также клавиатура вместе называются физической консолью.

Драйвер devc-con позволяет одновременно запускать несколько виртуальных консолей (virtual console) на одной физической консоли. Как правило, драйвер devc-con управляет несколькими наборами очередей ввода/вывода, предназначенными для io-char. Для пользовательских процессов эти очереди отображаются в виде группы устройств символьного ввода/вывода с именами /dev/con1, /dev/con2 и т.д. C точки зрения приложения, они представляют собой несколько "реальных" консолей.

В действительности, конечно, имеется только одна физическая консоль (дисплей и клавиатура), поэтому только одна из этих виртуальных консолей отображается в каждый момент времени. Клавиатура просто "присоединяется" к той виртуальной консоли, которая видна в данный момент.

Эмуляция терминала

Консольные драйвера эмулируют ANSI-терминал.

Последовательные устройства

Последовательные коммуникационные каналы управляются с помощью семейства драйверов devc-ser*. Эти драйверы могут управлять несколькими физическими каналами и регистрируют имена для устройств символьного ввода/вывода (/dev/ser1, /dev/ser2 и т.д.).

При запуске драйвера devc-ser* аргументы командной строки позволяют задать, какие последовательные порты должны быть установлены и в каком количестве. В PC-совместимой системе, как правило, это могут быть два стандартных последовательных порта, которые часто обозначаются как com1 и com2. Драйвер devc-ser* напрямую поддерживает большинство неинтеллектуальных многопортовых последовательных плат.

В ЗОСРВ «Нейтрино» имеется несколько драйверов последовательного порта (например, devc-ser8250). Более подробные сведения о драйверах можно найти на странице devc-ser*.

Драйверы devc-ser* поддерживают функции аппаратного управления обменом данных (кроме редактируемого режима) при наличии этих возможностей в самом устройстве. Например, драйверу можно указать, что при потере несущей частоты, генерируемой модемом, процессу, реализующему приложение, следует передать сигнал SIGHUP (в соответствии со стандартом POSIX).

Параллельные устройства

Параллельные (принтерные) порты управляются с помощью драйвера devc-par. При запуске драйвера devc-par аргументы командной строки позволяют задать, какой параллельный порт должен быть установлен.

Данный драйвер управляет только выводом данных, поэтому в нем нет очередей "сырых" или канонических входных данных. Размер выходного буфера может быть задан с помощью аргумента командной строки. При задании большого размера выходного буфера возникает эффект программного буфера печати.

Псевдотерминальные устройства (pty)

Псевдотерминалы управляются с помощью драйвера devc-pty. Аргументы командной строки позволяют задать для драйвера devc-pty количество создаваемых псевдотерминалов.

Псевдотерминал (pty) представляет собой пару устройств символьного ввода/вывода: ведущее (master) и ведомое (slave) устройства. Ведомое устройство обеспечивает интерфейс, аналогичный интерфейсу tty-устройств в стандарте POSIX. Однако, в то время как обычные tty-устройства являются физическим оборудованием, pty-устройству сопоставлен еще один процесс, который управляет им посредством ведущей части псевдотерминала. Таким образом, любые данные, записываемые на ведущее устройство, передаются на ведомое как входные данные, а любые данные, записываемые на ведомое устройство, передаются как входные данные на ведущее устройство. В результате псевдотерминальные устройства (pseudo-tty) могут использоваться для подключения к процессам, которые рассчитаны на работу с устройством символьного ввода/вывода.

11_4.png
Рисунок 4. Псевдотерминальные устройства (pseudo-tty, pty)

Псевдотерминалы обычно используются с целью создания псевдотерминальных интерфейсов для таких программ, как pterm (терминальный эмулятор, который работает в графической оболочке Photon microGUI) и telnet (которая использует протокол TCP/IP для обеспечения терминальной сессии с удаленной системой).




Предыдущий раздел: перейти