Оглавление    

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


Введение

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

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

/dev/ser1

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

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




Рис. 11.1. Модуль io-char реализован в виде библиотеки


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

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

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

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


Рис. 11.2. Ввод/вывод данных на устройства в ОС QNX Neutrino

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

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

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

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

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

Драйверы устройств просто добавляют полученные данные в очередь "сырых" входных данных или принимают данные из очереди выходных данных и передают их на устройство. Модуль io-char решает, когда приостановить передачу выходных данных (если это необходимо), когда и как осуществлять эхо-возврат данных и т. д.
Управление устройствами
Низкоуровневое управление устройствами осуществляется с помощью вызова devctl(). В стандарте POSIX реализованы следующие функции управления терминалами на основе вызова devctl():
QNX-расширения
ОС QNX Neutrino имеет следующие расширения программного интерфейса для управления терминалами: Модуль io-char работает непосредственно с типовым набором команд devctl(), которые поддерживаются большинством устройств. Команды набора devctl(), специфичные для конкретного устройства, приложения отправляют драйверам с помощью модуля io-char.
Режимы ввода
Каждое устройство может работать либо в режиме "сырых" (raw) входных данных, либо в режиме редактируемых (edited) входных данных.
Режим "сырых" входных данных
В режиме "сырых" (raw) входных данных модуль io-char не применяет редактирования к принимаемым данным. Это уменьшает обработку каждого символа до минимума и дает наивысшую производительность при чтении данных.

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

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

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


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


Рис. 11.3.
Условия выполнения запросов на ввод данных

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

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

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

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

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

Условие FORWARD является расширением, принятым в QNX, и не входит в спецификацию POSIX.

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

Механизм определения символа перенаправления данных (data-forwarding) также может быть реализован внутри интеллектуальных многопортовых последовательных адаптеров, что позволяет значительно сократить частоту, с которой адаптер должен прерывать главный процессор для обработки прерываний.
Режим редактируемых входных данных
В редактируемом режиме модуль io-char выполняет редактирование строки при получении каждого символа. Строка символов передается приложению только после того, как она "полностью принята" (как правило, после получения символа возврата каретки). Этот режим называют каноническим (canonical), или иногда "режим с обработкой" (cooked mode).

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

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

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

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

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

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

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

stty term=ansi </dev/ser1
Производительность устройств
Поток событий внутри устройства организуется таким образом, чтобы минимизировать накладные расходы и довести до максимума производительность устройства при работе в режиме "сырых" входных данных. Для этого применяются специальные правила
В сочетании с чрезвычайно небольшой задержкой обработки прерывания и задержкой планирования, которые свойственны ОС QNX Neutrino, эти правила дают очень компактную модель ввода, которая соответствует спецификации 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* напрямую поддерживает большинство неинтеллектуальных многопортовых последовательных плат.

В ОС QNX Neutrino имеется несколько драйверов последовательного порта (например, devc-ser8250, devc-serppc800 и т. д.). Более подробные сведения о драйверах devc-ser* содержатся в руководстве "Справочник по утилитам".

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

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

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

Рис. 11.4. Псевдотерминальные устройства (pseudo-tty)

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

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


      Оглавление