<<
>>

Реализация драйвера клавиатуры

Теперь мы обратимся к аппаратно-зависимому коду, обеспечивающему работу консоли. В MINIX 3 консоль состоит из клавиатуры IBM PC и отображаемого на память экрана. Физические устройства консоли полностью различны, у стандартных настольных систем экран поддерживается при помощи контроллера (одного из дюжины типов), а работу клавиатуры обеспечивают схемы на материнской плате, взаимодействующие с однокристальным 8-разрядным компьютером клавиатуры.
Для поддержания двух различных устройств требуются два варианта программного обеспечения, которые в MINIX находятся в файлах keyboard.с и console.с.

С точки зрения операционной системы клавиатура и экран являются частями одного устройства, /dev/console. Если у видеоконтроллера достаточно памяти, то в систему может быть включена поддержка виртуальной консоли, тогда, помимо устройства /dev/console, могут существовать дополнительные логические устройства, /dev/ttycl, /dev/ tty с 2 и т. д. В любой момент времени на экране видна только одна из этих консолей, эта же консоль получает ввод с единственной клавиатуры.

Логически клавиатура подчинена консоли, хотя подтверждается это только двумя достаточно незначительными фактами. Во-первых, в структуре консоли tty, хранящейся в tty_table, есть отдельные заполняемые при запуске поля для ввода и вывода, например tty_devread и tty_devwrite, являющиеся указателями на функции в файлах keyboard, с и console. с. Однако есть только одно поле tty_priv, ссылающееся исключительно на структуры данных консоли. Во-вторых, перед входом в главный цикл tty_task проводит инициализацию всех логических устройств. Для /dev/console процедура инициализации расположена в файле console.с,и код для инициализации клавиатуры вызывается из нее. В то же время иерархия может быть перевернута. Имея дело с устройствами ввода-вывода, мы всегда сначала рассматривали ввод, а затем вывод, и сейчас мы продолжим эту традицию, рассмотрев код в файле keyboard.
с в текущем разделе и отложив файл console. с до следующего.

Файл keyboard, с, как и многие другие, начинается с нескольких директив #include. Но одна из них необычна. Файл keymaps/us-std. src, включенный в строке 15218, не является заголовочным С-файлом, это — файл исходных кодов, который при компиляции определяет раскладку клавиатуры по умолчанию, попадающую в keyboard, о в виде инициализированного массива. Сам этот файл довольно велик, и в табл. 3.11 мы привели лишь несколько типичных записей из него. После директив # inc lude следуют макросы, задающие разнообразные константы. Первая группа привлекается для низкоуровневого взаимодействия с контроллером клавиатуры. Большая часть из них задают адреса портов ввода-вывода или битовые комбинации, используемые при таком взаимодействии. Следующая группа макросов описывает символические имена для специальных клавиш. Константа KB_IN_BYTES (строка 15249), имеющая значение 32, определяет размер циклического клавиатурного буфера. Следующие переменные хранят различные параметры, необходимые для правильной интерпретации нажатия клавиши. Поскольку буфер только один, необходимо гарантировать полную обработку его содержимого перед сменой виртуальной консоли.

Следующая группа переменных обеспечивает хранение различных состояний, что необходимо для корректной интерпретации нажатия клавиши. Они имеют разное назначение. Например, флаг caps_down (строка 15266) переключается с TRUE на FALSE, и наоборот, при нажатии клавиши Caps Lock. Флаг shift (строка 15264) устанавливается в TRUE, когда нажимается клавиша Shift, и сбрасывается, когда она отпускается. Переменная esc устанавливается, когда получен код опроса escape. Она всегда сбрасывается после получения одного символа.

Макрос map_keyO (строка15297) возвращает ASCII-код, соответствующий данному коду опроса без учета модификаторов, то есть попадающий в первую колонку карты клавиш. «Старший брат» этого макроса, макрос map_key (строка 15303), выполняет преобразование кода опроса в ASCII-код с учетом всех модификаторов, действующих при вводе символа.

Когда нажимается или отпускается клавиша, вызывается обработчик прерываний клавиатуры, подпрограмма kbd_interrupt (строка 15335). Чтобы узнать от контроллера клавиатуры код опроса, она вызывает sc ode. Когда прерывание инициировано отпусканием клавиши, у кода опроса устанавливается старший значащий бит, и в этом случае нажатие игнорируется, если это — не одна из клавиш-модификаторов. Чтобы обслужить прерывание как можно быстрее, все необработанные коды опроса помещаются в кольцевой буфер, и для текущей консоли устанавливается флаг tp-> tty_events (строка 15350). Как и ранее, мы будем считать, что вызовов select не производилось, и выход из обработчика kbd_interrupt выполняется немедленно после этого. На рис. 3.25 представлен пример содержимого кольцевого буфера для короткой строки, содержащей два символа в верхнем регистре, каждый из которых предваряется кодом опроса, соответствующим нажатию клавиши Shift, а следом за кодом символа идет код отпускания клавиши Shift.

Рис. 3.25. Содержимое входного буфера для строки текста, введенной с клавиатуры. Вторая строка таблицы описывает нажатия клавиш. Символы L+, L-, R+ и R- означают соответственно нажатие и отпускание правой и левой клавиши Shift. Код отпускания клавиши на 128 больше кода нажатия

Функция kb_read извлекает из циклического буфера коды опроса и помещает в свой локальный буфер ASCII-коды. Этот локальный буфер должен быть достаточно емким, чтобы вместить ESC-последовательности, генерируемые в ответ на нажатия некоторых клавиш на цифровой клавиатуре. Затем, чтобы поместить символы во входную очередь, вызывается функция in_process. В строке 15379 значение icount инкрементируется. Вызов make_break возвращает ASCII-код в виде целого числа. В этой точке специальные клавиши, например клавиши цифровой клавиатуры и функциональные клавиши, имеют коды большие, чем OxFF.

Коды в диапазоне от НОМЕ до INSRT (от 0x101 до ОхЮС, эти константы задаются в файле include/minix/keymap.h) при помощи массива numpad_map преобразуются в трехсимвольные ESC-последовательности, показанные в табл. 3.13.

Эти последовательности затем передаются в функцию in_process (строки 15392-15397). Большие по величине коды в функцию in_process не передаются, среди них ищутся коды, соответствующие комбинациям клавиш Alt+ и от Alt+F1 до Alt+F12. Если обнаруживается одна из таких комбинаций, вызывается функция select _с on sole, переключающая виртуальные консоли. Сочетания от Ctrl+F1 до Ctrl+F12 также обрабатываются особо. Сочетание Ctrl+F1 показывает текущие назначения функциональных клавиш (более подробно об этом мы скажем позже), а сочетание Ctrl+F3 обеспечивает переключение между программной и аппаратной прокруткой на экране консоли. Сочетания Ctrl+F7,

Ctrl+F8 и Ctrl+F9 генерируют те же сигналы, что Ctrl+\, Ctrl+C и Ctrl+U соответственно, с тем исключением, что они не могут быть изменены командой stty.

Таблица 3.13. ЕЗС-последовательности, генерируемые для клавиш цифровой клавиатуры. Когда коды опроса преобразуются в АЗСН-коды, специальным клавишам присваиваются «псевдоАЗСИ-коды», большие ОхРР

Функция шаке_Ьгеак преобразует коды опроса в АБСИ-коды и обновляет значение переменных, отслеживающих значения модификаторов. Но перед этим она ищет «волшебную» комбинацию клавиш СМ+АИ+Бе!, которую все пользователи РС знают как способ принудительной перезагрузки МБ-БОБ. Обратите внимание на комментарий о том, что это действие было бы лучше выполнить на более низком уровне. Тем не менее простота обработки прерываний в М1№Х 3 в пространстве ядра делает обнаружение нажатия этого сочетания невозможным, когда уведомление о прерывании послано, но код опроса клавиатуры еще не считан.

Желательным является штатное завершение работы системы, поэтому вместо обращения к подпрограммам РС ВЮБ выполняется вызов ядра зуз_кИ1, инициирующий передачу сигнала ЗЮК1ЬЬ процессу — родителю всех ос

тальных процессов (строка 15448).

Ожидается, что in.it обработает этот сигнал и в нормальном порядке завершит работу системы, прежде чем вернуться в монитор начальной загрузки, из которого можно управлять либо полным перезапуском системы, либо перезагрузкой МШ1Х 3.

Конечно, было бы неправильно ожидать, что этот механизм будет работать всегда. Большинство пользователей понимают опасность внезапной перезагрузки и не прибегают к клавишам СМ-ЬАН+Бе! до тех пор, пока не произойдет что-то действительно серьезное, после чего управлять системой будет невозможно. К этому моменту может сложиться ситуация, когда правильно отправить сигнал другому процессу уже невозможно. Именно потому в шаке_Ьгеак есть статическая переменная САБ_соипк. При большинстве сбоев система обработки прерываний продолжает работать, значит, клавиатурный ввод продолжает поступать, и таймерное задание остается работоспособным. МШ1Х 3 учитывает поведение пользователей, которые, когда система не реагирует на нажатия клавиш, начинают в сердцах нажимать все подряд. Если попытка отправить сигнал SIGABRT процессу in it не удается, и пользователь обращаетсяся к магической комбинации Ctrl+Alt+Del во второй раз, делается прямой вызов функции wreboot, которая принудительно возвращает управление монитору начальной загрузки.

Основная часть make_break устроена не сложно. В переменную make записывается признак, было ли прерывание вызвано нажатием или отпусканием клавиши, после чего в переменную ch помещается ASCII-код, возвращаемый функцией map_key. Далее следует инструкция switch, проверяющая значение ch (строки 15460-15499). Рассмотрим два случая: случай обычной клавиши и случай специальной клавиши. Если нажата обычная клавиша, ни одно из условий в switch не будет выполнено, в варианте по умолчанию также ничего не произойдет, поскольку обычные символы принимаются только при нажатии клавиши. Если каким-то образом обычная клавиша была воспринята при отпускании, ее код заменяется значением -1, которое игнорируется вызывающей функцией kb_read. Специальная клавиша, например Ctrl, обнаруживается в соответствующем месте инструкции switch, в данном случае — в строке 15461.

Состояние make фиксируется в надлежащей переменной (ctrl), а в качестве кода символа возвращается (и игнорируется) -1. Обработка колов ALT, CALOCK, NLOCK и SLOCK сложнее, но во всех этих вариантах действия похожи: в переменную записывается либо новое состояние модификатора (если модификатор действует только тогда, когда удерживается клавиша), либо инвертированное старое состояние (для клавиш наподобие Caps Lock).

Нужно рассмотреть еще один вариант, код EXTKEY и переменную esc. Не путайте этот случай с клавишей Esc на клавиатуре, которой соответствует код опроса 0x1 В. Код EXTKEY нельзя сгенерировать, нажав какую-либо клавишу или их комбинацию. Это префикс расширенных клавиш для клавиатур PC, первый байт двухбайтового кода опроса, означающего, что передаваемый далее код опроса не является частью обычного набора клавиш PC. Во многих случаях программное обеспечение интерпретирует обычный и расширенный коды одинаково. Например, это почти всегда так для обычной клавиши / и клавиши / на цифровой клавиатуре. В других случаях может потребоваться различать их нажатия. Так, во многих раскладках клавиатур для языков, отличных от английского, левая и правая клавиши Alt интерпретируются по-разному, позволяя вводить три разных символа с помощью одной клавиши. Код опроса у обеих клавиш одинаков и равен 56, но при нажатии правой клавиши Alt код опроса предваряется кодом EXTKEY. Когда получен код EXTKEY, устанавливается флаг esc, и в этом случае функция make_break выполняет возврат прямо из инструкции switch, тем самым обходя операторы в конце функции, записывающие в переменную esc нулевое значение. В результате esc действует только на один символ, который должен быть получен следующим. Если вы знакомы с особенностями обычного программирования ввода с клавиатур PC, то это также будет вам понятно, хотя и немного странно, так как BIOS PC не позволяет считывать код опроса для клавиши Alt и возвращает другое значение для расширенного кода, нежели MINIX 3.

Функция set_leds (строка 15508) управляет светодиодами на клавиатуре, индицирующими состояние клавиш Num Lock, Caps Lock и Scroll Lock. Чтобы указать клавиатуре, что следующий записанный в порт байт управляет индикаторами, в порт записывается управляющий байт, LED_CODE. Состояние всех трех светодиодов кодируется тремя битами этого байта. Разумеется, данные операции выполняются вызовами ядра, обращающимися к системному заданию для записи в выходные порты. Поддержка осуществляется с помощью двух функций. Функция kb_wait (строка 15530) вызывается тогда, когда необходимо определить момент готовности клавиатуры к получению управляющей последовательности, a kb_ack (строка 15552) проверяет, что команда подтверждена. Обе эти команды используют механизм активного ожидания, непрерывно считывая состояние до тех пор, пока не получено нужное значение. Такая методика не рекомендуется для операций ввода-вывода, но переключение индикаторов не должно происходить часто, поэтому временные издержки не так велики. Заметьте, что и kb_wait, и kb_ack могут завершиться неудачей, что видно из кода возврата функций. Число повторных попыток ограничено счетчиком цикла. Правда, переключение индикаторов на клавиатуре — не самая важная задача, поэтому возвращаемое значение не проверяется и set_leds выполняется «вслепую».

Так как клавиатура является составной частью консоли, ее подпрограмма инициализации, kb_init (строка 15572), вызывается из scr_init в файле console.с, а не напрямую из tty_init в tty. с. Если включены виртуальные консоли (то есть константа NR_CONS в include/minix/conf ig.h больше 1), kb_init вызывается для каждой логической консоли. Следующая функция, kb_init_ once (строка 15583), вызывается всего один раз, что и отражено в ее названии. Она задает состояние индикаторов на клавиатуре и сканирует клавиатуру, чтобы удостовериться, что не считано никаких остаточных нажатий клавиш. Затем инициализируются два массива, fkey_obs и sfkey_obs, предназначенные для связывания функциональных клавиш с процессами, которые должны на них реагировать. Когда все готово, выполняются два вызова ядра, sys_irqsetpolicy и sys_irqenable, задающие запросы на прерывание от клавиатуры и автоматическое разрешение прерываний. Таким образом, tty_task будет получать уведомление при каждом нажатии и отпускании клавиши.

Хотя рассмотрением работы функциональных клавиш мы займемся чуть позже, сейчас вполне целесообразно изучить массивы fkey_obs и sfkey_obs. Каждый из них содержит 12 элементов, соответствующих 12 функциональным клавишам, которыми оснащены современные персональные компьютеры. Первый массив предназначен для ^модифицированных функциональных клавиш, а второй — для функциональных клавиш, нажатых одновременно с Shift. Массивы включают элементы типа obs_t, представляющего собой структуру, хранящую номер процесса и целое число. Структура и сами массивы объявлены в файле keyboard. с в строках 15279-15281. При инициализации структуры ее полю ргос_пг присваивается значение, определенное как NONE и указывающее на то, что она не используется. Значение NONE лежит за пределами, допустимыми для номеров процессов. Обратите внимание на то, что номер процесса — это не его идентификатор, а индекс в таблице процессов. Возможно, терминология несколько запутанна, однако для передачи уведомления используется именно номер, а не идентификатор процесса, поскольку номера процессов индексируют таблицу priv, разрешающую процессам принимать уведомления. Целочисленная переменная events также имеет нулевое начальное значение и применяется для подсчета количества событий.

Следующие три функции довольно просты. Подпрограмма kbd_loadmap (строка 15610) почти тривиальна. Она вызывается из do_ioctl файла tty. с с целью скопировать карту клавиш из пользовательского адресного пространства. При этом новая раскладка записывается поверх раскладки по умолчанию, сгенерированной благодаря тому, что исходный файл раскладки включен в начало файла keyboard. с.

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

Таблица 3.14. Функциональные клавиши, обнаруживаемые функцией func_key

Клавиша Действие

F1 Вывод таблицы процессов ядра

F2 Вывод информации о занятой процессом памяти

F3 Вывод загрузочного образа

F4 Вывод привилегий процессов

F5 Вывод параметров монитора загрузки

F6 Вывод информации о прерываниях

F7 Вывод сообщений ядра

F10 Вывод параметров ядра

F11 Вывод таймерной информации

F12 Вывод информации об очередях

SF1 Вывод таблицы процессов менеджера процессов

SF2 Вывод сигналов

SF3 Вывод таблицы процессов файловой системы

SF4 Отображение устройства/драйвера

SF5 Отображение клавиш печати

SF9 Вывод Ethernet-статистики (только для RTL8139)

CF1 Отображение клавиш вывода

CF3 Переключение между программной и аппаратной прокруткой

CF7 Отправка сигнала SIGQUIT, тот же эффект дает нажатие клавиш Ctrl+\

CF8 Отправка сигнала SIGINT, тот же эффект дает нажатие клавиши Del

CF9 Отправка сигнала SIGKILL, тот же эффект дает нажатие клавиш Ctrl+U

Нажатие функциональных клавиш поодиночке или совместно с клавишей Shift приводит к появлению событий, которые невозможно обработать с помощью драйвера терминала. Эти события приводят к передаче уведомлений драйверам и серверам. Поскольку загрузка, включение и выключение драйверов в MINIX 3 возможны в процессе работы операционной системы, статическая привязка к ним клавиш во время компиляции вряд ли является удачным решением. Для поддержки изменений в реальном времени подпрограмма tty_task принимает сообщения типа FKEY_CONTROL, а обработкой запросов занимается функция do_f key_ ctl (строка 15624). Существует три типа запросов: FKEY_MAP, FKEY_UNMAP и FKEY_EVENTS. Первые два соответственно назначают и отменяют функциональную клавишу процессу, указанному в битовой карте сообщения. Третий запрос возвращает битовую карту нажатых клавиш, связанную с вызывающим процессом, и очищает поле events для этих событий. Информационный сервер (Information Server, IS) инициализирует параметры процессов в загрузочном образе, а также участвует в генерации откликов. Тем не менее драйверы также могут быть связаны с функциональной клавишей и реагировать на нее. Как правило, это имеет место для Ethernet-драйверов, сбрасывающих дампы со статистикой пакетов, которые полезны при разрешении сетевых проблем.

Функция func_key (строка 15715) вызывается из kb_read для того, чтобы определить, имеются ли нажатые клавиши для локальной обработки. Это действие выполняется каждый раз и в первую очередь при получении кода опроса. Если клавиша не является функциональной, то как минимум три сравнения выполняются прежде, чем управление возвращается kb_read. Если же клавиша является функциональной и связана с процессом, последнему посылается уведомление. В случае когда процесс связан с единственной клавишей, само уведомление указывает ему, что делать. Если же процесс связал себя с несколькими функциональными клавишами, требуется диалог: процесс посылает запрос FKEY_EVENTS драйверу терминала, который обрабатывает его с помощью подпрограммы do_ fkey_ctl и информирует источник о том, какие клавиши являются активными. Затем процесс может вызвать процедуру обработки для каждой нажатой клавиши.

Функция scan_keyboard (строка 15800) взаимодействует с аппаратным интерфейсом клавиатуры, считывая и записывая байты из портов ввода-вывода. Контроллер клавиатуры информируется о том, что символ был считан последовательностью команд в строках 15809-15810. Эти команды считывают байт, затем записывают его обратно с установленным старшим битом, а затем перезаписывают его еще раз со сброшенным битом. В результате те же самые данные при следующем чтении не встречаются. В этой функции не делается никаких проверок, что не важно, так как функция scan_keyboard вызывается только из обработчика прерываний.

Завершает файл keyboard.c функция do_panic_dumps (строка 15819). Вызываемая в результате краха системы, она дает пользователю возможность просмотреть отладочную информацию при помощи функциональных клавиш. Цикл в строках 15830-15854 — еще один пример активного ожидания. Клавиатура непрерывно опрашивается до тех пор, пока не нажата клавиша Esc. Безусловно, никто не может заявить, что после сбоя, когда ожидается команда на перезагрузку, имеет смысл применять более эффективные методики. Внутри цикла редко используемая неблокирующая операция приема nb_receive служит для приема сообщений и проверки ввода с клавиатуры одного из вариантов, предложенных в сообщении:

Hit ESC to reboot, DEL to shutdown, F-keys for debug dumps

В следующем разделе мы рассмотрим код функций do_newkmess и do_ diagnostics.

3.8.6. Реализация драйвера экрана

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

Специфичный для отображаемого на память экрана PC код расположен в файле console, с. Здесь объявляется структура console (строки 15981-15998), которая по своему смыслу является расширением структуры tty из файла tty.с. При инициализации в поле tp->tty_priv структуры tty для консоли записывается указатель на принадлежащую этой консоли структуру console. Первое поле структуры является указателем, содержащим обратную ссылку на структуру tty. Остальные компоненты структуры содержат вполне ожидаемую для видеоустройства информацию: переменные, хранящие текущие координаты курсора, адрес начала области памяти, отведенной для дисплея, и ее размер, адрес, на который ссылается регистр базы чипа контроллера, текущий адрес курсора. Другие переменные требуются для работы с ESC-последовательностями. Так как символы принимаются в виде 8-разрядных байтов и перед выводом в видеопамять должны быть скомбинированы с байтом атрибутов, блок данных подготавливается к передаче в массиве c_ramqueue, объема которого достаточно для хранения 80 16-разрядных пар «символ-атрибут». Каждой виртуальной консоли требуется собственная структура console, для хранения которой выделяется область памяти в cons_table (строка 16001). Как и в случае tty и других структур, мы обычно будем ссылаться на поля console через указатель, например: cons->c_tty.

Для каждой консоли в поле tp->tty_devwrite хранится адрес функции cons_ write (строка 16036). Она вызывается только из одного места — обработчика handle_events в файле tty. с. Большая часть остальных функций в console. с призвана обеспечивать работу функции cons_write. Когда она вызывается впервые после того, как процесс-клиент сделал вызов write, выводимые данные располагаются в буфере в адресном пространстве этого процесса. Определить положение буфера можно при помощи полей tp->tty_outproc и tp->out_ vir структуры tty. Поле tp->tty_outleft говорит о том, сколько символов должно быть передано, а поле tp->tty_outcum изначально равно нулю, сообщая о том, что ни одного символа еще не выведено. Это — обычная ситуация для функции cons_write, так как она выводит все данные, указанные в вызове. Но если пользователь хочет замедлить процесс вывода, он может ввести с клавиатуры символ STOP (Ctrl+S), который приводит к тому, что устанавливается флаг tp->tty_inhibited. Когда этот флаг установлен, cons_write немедленно возвращает управление, даже если вызов write еще не выполнен полностью. Причем handle_events продолжит вызывать cons_write и когда флаг tp->tty_inhibited будет сброшен, а для этого необходимо ввести символ START (Ctrl+Q), прерванная передача данных возобновится.

Первым аргументом cons_write является указатель на структуру tty для данной консоли, поэтому первым действием инициализируется указатель cons, содержащий адрес структуры console (строка 16049). Затем необходимо проверить, действительно ли есть какая-либо работа, так как handle_events всегда вызывает cons_write. Если нет, функция быстро завершается (строка 16056). После этого она входит в свой главный цикл (строки 16061-16089). Этот цикл очень похож на цикл функции in_transf er в файле tty. с. При помощи вызовов sys_vircopy ядра локальный буфер объемом 64 символа заполняется данными из пользовательского буфера, обновляются указатель на начало буфера и счетчик символов, после чего все символы из локального буфера переносятся в массив cons->c_ramqueue, дополненные байтом атрибутов. Позже эти данные выводятся на экран с помощью функции flush.

Как вы могли видеть на рис. 3.23, существуют несколько способов осуществить эту передачу. Можно вызывать out_char для каждого символа, но лишь в расчете на то, что при выводе символов не потребуется ни одна из специальных функций out_char, не выводится ESC-последовательность, ширина экрана не превышена и массив cons->c_ramqueue не заполнен. Если все функции out_char не требуются, символ можно поместить в массив cons ->c_ramqueue напрямую, вместе с байтом атрибутов (поле cons->c_attr), а все переменные cons->c_rwords (индекс в очереди), cons->c_column (отслеживает текущий столбец на экране) и tbuf (указатель на буфер) инкрементируются. Прямой перенос символов в cons->c_ramqueue соответствует штриховой линии с левой стороны рис. 3.23. При необходимости вызывается функция out_char (строка 16082). В этом вызове выполняются все подсчеты и по мере надобности вызывается функция flush, которая обеспечивает окончательную передачу данных в видеопамять.

Перекачка данных из пользовательского буфера в локальный производится до тех пор, пока значение в поле tp->tty_outlef t говорит о наличии символов и не установлен флаг tp->tty_inhibited. Если передача останавливается, в результате завершения вызова write или потому, что установлен флаг tp->tty_ inhibited, то чтобы передать последние символы из очереди в память экрана, опять вызывается функция flush. Когда операция завершена (то есть проверка показывает, что поле tp->tty_outleft содержит нулевое значение), при помощи tty_reply отправляется ответное сообщение (строки 16096-16097).

В дополнение к вызовам cons_write из handle_events, символы на консоль могут выводить функции echo и rawecho в аппаратно-независимой части драйвера терминала. Если текущим устройством вывода является консоль, косвенные вызовы через указатель tp->tty_echo перенаправляются функции cons_ echo (строка 16105). Она делает свою работу, вызывая out_char и затем flush. Пользователь вводит данные символ за символом, и ему предпочтительно, чтобы эхо отображалось сразу же, без видимой задержки, поэтому помещать символы в очередь было бы неправильно.

Итак, теперь мы добрались до функции out_char (строка 16119). Сначала она проверяет, вводится ли ESC-последовательность, вызывая parse_escape, и если это так, немедленно завершается (строки 16124-16126). В противном случае управление передается в конструкцию switch, которая проверяет различные особые случаи: нулевой символ, символ забоя, символ звонка и т. п. Несложно проследить, как обрабатывается большая часть этих ситуаций. Самые сложные варианты — символы перевода строки и табуляции, поскольку они сложным образом меняют координаты курсора на экране и могут вызвать прокрутку. Последняя проверка выполняется на код ESC. Если он обнаруживается, устанавливается флаг cons->c_esc_state (строка 16181) и последующие вызовы out_ char перенаправляются в parse_escape до тех пор, пока последовательность не завершится. Вариант по умолчанию выполняется для печатных символов. Если превышена ширина экрана, при необходимости делается прокрутка экрана и вызывается flush. Прежде чем поместить символ в выходную очередь, проверяется, заполнена ли она, и если заполнена, вызывается flush. Как мы видели ранее при описании функции cons_write, в случае занесения символа в очередь необходимо учесть это, обновив значения нескольких переменных.

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

Мы рассмотрим случай прокрутки вверх. Сначала переменной chars присваивается значение, равное размеру экрана минус 1. Когда прокрутка делается программно, для ее выполнения достаточно одного вызова функции vid_vid_copy, которая копирует chars символов на одну строку ниже в памяти. Эта функция умеет переходить в начало области при достижении ее конца, и наоборот. Так, если ей указано скопировать блок памяти, начало которого выходит за верхнюю границу памяти, то не укладывающиеся в область видеопамяти данные берутся из ее нижней части, то есть видеопамять рассматривается как циклический массив. Простота этого вызова не компенсирует низкую скорость операции. Несмотря на то что подпрограмма vid_vid_copy написана на языке ассемблера (ее код хранится в файле drivers/tty/vidcopy. s), для ее выполнения необходимо скопировать 3840 байт, что довольно много даже для ассемблерного кода.

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

В первой части составного условия (строка 16226) проверяется значение переменной wrap. Эта переменная содержит TRUE для старых экранов, поддерживающих аппаратную прокрутку, и если проверка не выполняется, в ветви else производится простая аппаратная прокрутка (строка 16230). Соответственно, значение указателя на начало экранной области, используемого видеоконтроллером, cons->c_orig, изменяется так, чтобы указывать на первый символ той строки, которая окажется наверху экрана. Если значение wrap равно FALSE, проверка составного условия продолжается. В этом случае проверяется, поместится ли перемещаемый блок памяти в области памяти, отведенной для консоли. Если нет, то вызовом vid_vid_copy содержимое копируется физически в начало области видеопамяти. Если же адреса не перекрываются, делается простая аппаратная прокрутка, всегда практикуемая в более старых видеоконтроллерах. Для этого изменяется значение cons->c_org, и новое значение указателя на начало области заносится в нужный управляющий регистр контроллера. Соответствующий вызов делается позднее, как и вызов, очищающий нижнюю строку экрана для достижения эффекта «прокрутки».

Код прокрутки вниз очень похож на тот, что прокручивает экран вверх. В конце нижняя (или верхняя) строка экрана, на которую указывает переменная new_ line, очищается вызовом mem_vid_copy, обновляются значения некоторых переменных и делается проверка того, что координаты курсора имеют приемлемые значения. При необходимости, если, например, ESC-последовательность переместила курсор на столбец с отрицательным номером, координаты корректируются. В завершение вычисляется, где должен быть курсор, и это значение сравнивается с cons->c_cur. Если значения не совпадают, а обрабатываемая память принадлежит текущей виртуальной консоли, то чтобы записать корректные значения в регистр курсора контроллера, делается вызов подпрограммы set_6845. На рис. 3.26 показано, как можно представить анализ ESC-последовательностей при помощи конечного автомата. Этот автомат реализуется подпрограммой parsе_еscape (строка 16293), вызываемой в начале кода out_char, если поле cons->c_esc_state не равно нулю. Сам символ ESC обнаруживается в out_ char, и переменная cons->c_esc_state переводится в состояние 1. Когда получен следующий символ, функция parse_escape подготавливается к обработке дальнейшей информации: значение ' \0 ' заносится в поле cons->c_esc_ intro, указатель на начало массива параметров, cons->c_esc_paramv [ 0 ], заносится в cons->c_esc_paramp, а сам массив параметров заполняется нулями. Затем проверяется первый символ, следующий за ESC. Допустимыми значениями являются [ и м. В первом случае символ [ копируется в переменную cons->c_esc_intro, и автомат переходит в состояние 2. Во втором случае вызывается функция do_escape, которая выполняет действие, и автомат возвращается в состояние 0. Если же за ESC следует недопустимый символ, он игнорируется, а дальше все обрабатывается, как обычно.

Рис. 3.26. Конечный автомат для обработки ESC-последовательностей

Когда автомат получает последовательность ESC [, следующий полученный символ обрабатывается в состоянии 2. В этой точке возможны три варианта. Если на входе числовой символ, его значение добавляется к увеличенному в десять раз значению параметра, на который в текущий момент указывает cons-> c_esc_paramp (сначала этот указатель ссылается на cons->c_esc_paramv [ 0 ], и все параметры равны нулю). Здесь состояние автомата не меняется. Это позволяет передавать параметры в виде последовательности чисел, накапливая их итог, хотя максимальное значение, в текущий момент распознаваемое MINIX, равно 80. Оно может быть использовано в последовательности, перемещающей курсор в указанную позицию на экране (строки 16335-16337). Если получено не число, а точка с запятой, тогда указатель на текущий параметр перемещается к следующему параметру, чтобы начать считывание в нечисловых значений (строки 16339-16341). Благодаря такому подходу, если изменить константу МАХ_ ESC_PARMS в сторону массива большего объема, код менять не придется. Наконец, в третьем случае, когда получен символ, не являющийся ни числом, ни точкой с запятой, вызывается do_escape.

Функция do_escape (строка 16352) весьма объемна, несмотря на относительно скромную поддержку ESC-последовательностей в MINIX 3. Но при любом объеме код должен быть достаточно простым. После того как делается вызов flush, нужно убедиться, что содержимое экрана полностью обновлено. Инструкция i f выполняет простую проверку, является ли следующий за ESC символ преамбулой ESC-последовательности или нет. Если нет, допустимо только одно действие — перемещение курсора на строку вверх, этому соответствует ESC-последо- вательность ESC М. Обратите внимание, что проверка значения осуществляется в инструкции switch в расчете на появление новых вариантов, то есть новых последовательностей, не соответствующих формату ESC [. Поэтому обрабатывается вариант без преамбулы типичным для многих последовательностей образом: по значению переменной cons->c_row определяется, необходима ли прокрутка. Если курсор находится в нулевой строке, делается вызов scrol l_screen с параметром SCROLL_DOWN. Если нет, курсор просто сдвигается на одну строку вверх, для чего переменная cons->c_row уменьшается на 1 и вызывается flush. Если обнаружена преамбула ESC-последовательности, срабатывает другая ветвь инструкции if (строка 16377). Сначала проверяется, не символ [ ли это, то есть единственная возможная преамбула, обрабатываемая в текущий момент в MINIX 3. Если символ корректен, в переменную value записывается значение первого полученного параметра, или ноль, если параметров нет (строка 16380). Если последовательность некорректна, ничего не происходит, за исключением того, что switch с большим телом (строки 16381-16586) пропускается, а состояние автомата сбрасывается в 0 перед вызовом do_escape. В более интересном случае, когда последовательность правильна, выполняется инструкция switch. Все возможные варианты мы рассматривать не будем. Вместо этого обсудим только наиболее характерные типы действий, управляемых ESC-последовательностями.

Первые пять последовательностей без числовых аргументов на клавиатурах IBM PC генерируются клавишами со стрелками и клавишей Ноте. Две последовательности, ESC [А и ESC [В, сходны с ESC М, с той разницей, что числовой параметр позволяет перемещаться более чем на одну строку, а при достижении границ экрана содержимое не прокручивается. Функция flush в этом случае обнаруживает попытки передвинуть курсор за границы экрана и ограничивает его перемещение. Две другие последовательности, ESC [С и ESC [D, перемещающие курсор вправо и влево, аналогичным образом ограничены функцией flush. Когда они генерируются клавишами управления курсором, числовой аргумент не передается, поэтому происходит перемещение на одну строку или один столбец.

Далее, последовательность ESC [Н может иметь два числовых параметра, например ESC [2 0 ; 6ОН. Эти параметры задают положение курсора в абсолютных координатах, а не относительно предыдущего места расположения, и для правильной интерпретации преобразуются из координат, отсчитываемых с 1, в координаты с началом в 0. Клавиша Ноте на клавиатуре генерирует последовательность без параметров (с параметрами по умолчанию), которая перемещает курсор в положение (1;1).

Две следующие последовательности, ESC [sJ и ESC [sK, очищают либо часть строки, либо часть всего экрана, в зависимости от переданного параметра. В обоих случаях подсчитывается количество символов. Например, для последовательности ESC [ 1J в count заносится количество символов от начала экрана до текущего положения курсора, и это количество и параметр положения, dst, который может быть равен началу экрана, cons->c_org, используются как аргументы для вызова mem_vid_copy. Аргументы процедуры таковы, что она заполняет указанную область экрана текущим цветом фона.

Четыре следующие последовательности вставляют новые строки и удаляют строки и пробелы в текущем местоположении курсора, и их работа не нуждается в детальном рассмотрении. Последний вариант, последовательность ESC [тп (обратите внимание, что п — это числовой аргумент, a m — литера, часть последовательности), оказывает влияние на параметр сопз->с_аЬЬг. Это — байт атрибутов, который при записи символов в видеопамять чередуется с кодами символов.

Функция зеЬ_6 84 5 (строка 16594) вызывается тогда, когда необходимо обновить информацию видеоконтроллера. У контроллера 6845 есть внутренние 16-разрядные регистры, которые программируются по 8 бит за раз. Поэтому для записи одного регистра требуются четыре операции с портами ввода-вывода. В каждой из них создается массив (вектор) пар (порт, значение) и осуществляется вызов ядра зуз_уоиЬЬ, обращающийся к системному заданию для выполнения ввода-вывода. Некоторые регистры микросхемы видеоконтроллера 6845 перечислены в табл. 3.15.

Таблица 3.15. Некоторые из регистров микросхемы 6845
Регистры Назначение
10-11 Размер курсора
12-13 Начальный адрес видимой части экрана
14-15 Положение курсора

Следующая функция, get_6845 (строка 16613), возвращает значения доступных для считывания регистров видеоконтроллера. Для этого она пользуется вызовами ядра. В текущем коде MINIX 3 эта функция не вызывается, однако может быть полезна для расширений в будущем (например, для добавления графической поддержки).

Функция beep (строка 16629) вызывается при нажатии клавиш Ctrl+G. Она подает на внутренний динамик прямоугольный сигнал, опираясь на встроенные аппаратные возможности PC. Звук появляется путем некоторых магических манипуляций с портами ввода-вывода, которые интересны только программистам на ассемблере. Более интересно то, что сигнал выключается при помощи уведомления. Будучи процессом с системными привилегиями (такими как запись в таблице priv), драйвер терминала может установить таймер с помощью библиотечной функции tmrs_settimers. Это делается в строке 16655, а следующая функция, stop_beep, вызывается после истечения таймера. Этот таймер ставится в собственную очередь драйвера терминала. Вызов ядра sys_setalarm, следующий далее, обращается к системному заданию для установки таймера в ядре. По его истечении главный цикл драйвера терминала, tty_task, обнаруживает сообщение SYN_ALARM и вызывает функцию expire_timers. Последняя обрабатывает все таймеры, принадлежащие драйверу терминала, один из которых установлен функцией beep.

Адрес следующей подпрограммы, stop_beep (строка 16666), помещен функцией beep в поле tmr_func таймера. Она выключает сигнал после истечения заданного интервала времени и сбрасывает флаг beeping. Это предотвращает обработку лишних обращений к функции генерации сигнала.

Подпрограмма scr_init вызывается из tty_init столько раз, сколько указано в NR_CONS. При каждом вызове аргументом подпрограммы является указатель на структуру, один из элементов массива tty_table. В строках 16693 и 16694 вычисляется значение line, будущий индекс в массиве cons_table, это значение проверяется на корректность, и если все правильно, оно используется для инициализации указателя cons, ссылающегося на текущую запись в массиве cons_table. К этому моменту поле cons->c_tty может быть инициализировано указателем на главную структуру tty для устройства, а в tp->tty_priv, в свою очередь, может быть записан указатель на структуру console_t устройства. Затем для инициализации клавиатуры вызывается подпрограмма kb_init и устанавливаются указатели на специфичные для данного устройства подпрограммы. После этого tp->tty_devwrite ссылается на cons_write, a tp-> tty_echo содержит указатель на cons_echo. Далее из BIOS извлекается адрес ввода-вывода регистра базы видеоконтроллера и в соответствии с типом видеоконтроллера устанавливается флаг wrap, определяющий способ прокрутки (строки 16708-16731). Затем в глобальной таблице дескрипторов запоминается дескриптор сегмента области видеопамяти (строка 16735).

В дальнейшем происходит инициализация виртуальных консолей. При инициализации каждой консоли с разным значением tp вызывается scr_init, и таким образом, для каждой консоли в scr_init используются собственные значения line и cons (строки 16750-16753), и каждая консоль «арендует» собственную область видеопамяти. Затем каждый экран очищается, и, наконец, консоль с нулевым номером становится активной.

Ряд подпрограмм отображает вывод от имени драйвера терминала, ядра или другого системного компонента. Первая, kputc (строка 16775), просто вызывает функцию putk, которая побайтно выводит текст. Она применяется здесь потому, что библиотечная подпрограмма, предоставляющая возможность использования printf системным компонентам, связывается с одноименной подпрограммой символьной печати, однако другие функции драйвера терминала рассчитаны на подпрограмму с именем putk.

Функция do_new_kmess (строка 16784) применяется для печати сообщений из ядра. Здесь термин «сообщения» не самый удачный; он не имеет отношения к сообщениям, обеспечивающим взаимодействие между процессами. Данная функция отображает на консоли текст, содержащий информацию, предупреждения и сведения об ошибках.

Ядру необходим особый механизм отображения данных. Этот механизм должен быть достаточно самостоятельным, чтобы работать во время загрузки, когда компоненты операционной системы еще не функционируют, и во время сбоев, когда основные компоненты могут быть недоступными. Ядро помещает текст в циклический символьный буфер, входящий в структуру, содержащую указатели на следующий записываемый байт и размер еще не обработанного текста. При появлении нового текста ядро посылает драйверу терминала сообщение SYS_SIG, и если главный цикл в процедуре tty_task функционирует, вызывается функция do_new_kmess. При внештатной ситуации (например, в случае краха системы) сообщение SYS_SIG обнаруживается циклом процедуры do_panic_ dumps с помощью операции неблокирующего чтения, которую мы видели в файле keyboard. с, и функция do_new_kmess вызывается оттуда. В обоих случаях вызов ядра sys_getkmessages получает копию структуры ядра, байты отображаются по одному с помощью функции putk, а последний вызов putk с нулевым символом приводит к сбросу вывода. Хранение позиции в буфере для различных сообщений осуществляется с помощью локальной статической переменной.

Функция do_diagnostics (строка 16823) имеет назначение, схожее с do_ new_kmess, однако обеспечивает вывод сообщений не для ядра, а для системных процессов. Сообщение типа DIAGNOSTICS может быть получено главным циклом функции tty_task либо циклом в функции do_panic_dumps. В обоих случаях производится вызов do_diagnostics. Сообщение содержит указатель на буфер вызывающего процесса и его размер. Локальная буферизация не используется; вместо этого текст побайтно доставляется путем многократных вызовов sys_vircopy ядра. Это защищает драйвер терминала; в случае если произойдет сбой и процесс начнет генерировать большие объемы данных, проблема переполнения буфера не сможет возникнуть. Вывод символов осуществляется по одному функцией putk и завершается нулевым байтом.

Функция putk (строка 16850) печатает символы от имени кода драйвера терминала и используется описанными функциями для вывода текста от лица ядра или других системных компонентов. Она просто вызывает функцию out_char для каждого полученного ненулевого символа и функцию flush для нулевого символа, которым заканчивается строка.

Остальные процедуры в файле console.с просты и невелики, поэтому мы не потратим на них много времени. Функция toggle_scroll (строка 16869) делает именно то, что означает ее название, она изменяет значение флага типа прокрутки: аппаратная или программная. Помимо этого, она выводит текст в текущей позиции курсора, сообщая, какой режим выбран. Функция cons_stop (строка 16869) инициализирует консоль, переводя ее в состояние, которое ожидает монитор начальной загрузки. Это делается перед выходом из системы или перезагрузкой. Функция cons_orgO (строка 16893) используется только тогда, когда нажатием клавиши F3 изменяется режим прокрутки или идет подготовка к выходу из системы. Функция select_console (строка 16917) выбирает (активизирует) виртуальную консоль. При вызове ей передается индекс новой консоли, и она дважды вызывает функцию set_6845, чтобы показать на экране данные из соответствующей части видеопамяти.

Две последние подпрограммы в значительной степени зависят от особенностей программного обеспечения. Функция con_loadf ont (строка 16931) загружает в видеоконтроллер шрифт, обеспечивая выполнение операции TIOCSFON вызова ioctl. Эта функция серией вызовов ga_program (строка 16971) делает так, что становится видимой память шрифтов контроллера, которая в обычной ситуации не адресуема. Затем, чтобы скопировать шрифт в ставшую доступной область памяти, вызывается функция phys_copy, а после этого другая последовательность команд возвращает устройство в нормальный режим работы.

Последняя функция, cons_ioctl (строка 16987), совершает лишь одно действие: задает размер экрана. Она вызывается функцией scr_init, используя зна-

Резюме

чения, полученные из ВЮБ. Если бы возникла необходимость вызова 1осЬ1 для изменения размеров экрана, в МШ1Х 3 пришлось бы добавить код для работы с новыми параметрами.

<< | >>
Источник: Э. ТАНЕНБАУМ, А. ВУДХАЛЛ. ОПЕРАЦИОННЫЕ СИСТЕМЫ Разработка и реализация 3-е издание. 2007

Еще по теме Реализация драйвера клавиатуры:

  1. Драйвер Счастья в Работе:
  2. Глава 6. Реализация права
  3. Э. ТАНЕНБАУМ, А. ВУДХАЛЛ. ОПЕРАЦИОННЫЕ СИСТЕМЫ Разработка и реализация 3-е издание, 2007
  4. 4.2. Реализация
  5. 4. Реализация прав по ипотеке
  6. 3.3. Дальнейшая реализация проекта
  7. РЕАЛИЗАЦИЯ МЫСЛЕННОГО ПРЕДСТАВЛЕНИЯ
  8. 10. Реализация заложенного имущества
  9. Механизм реализации личности
  10. Статья 591. Реализация предмета залога
  11. Практическая реализация.