Системы, управляемые событиями
В начале 70-х годов появилась новая архитектура многозадачных систем, довольно резко отличающаяся от вышеописанной модели последовательных процессов. Речь идет о так называемых системах, управляемых событиями (event-driven systems). На первый взгляд, концепция систем, управляемых событиями, близко родственна гармонически взаимодействующим процессам. Различие между этими архитектурами состоит, скорее, во взгляде на то, что представляет собой программа. В модели гармонически взаимодействующих потоков процесс исполнения программного комплекса представляет собой совокупность взаимодействующих нитей управления. В системе, управляемой событиями, программа представляет собой совокупность объектов, обменивающихся сообщениями о событиях, а также реагирующих на сообщения, приходящие из внешних источников. В идеале, объекты взаимодействуют между собой только через сообщения. Приходящие сообщения побуждают объект изменить свое состояние и, возможно, породить некоторое количество сообщений, предназначенных для других объектов. При такой модели взаимодействия нам неважно, исполняются ли методы объектов как параллельные (или псевдопараллельные) нити, или же последовательно вызываются единой нитью, менеджером или диспетчером сообщений.
Впервые эта архитектура была реализована в экспериментальных настольных компьютерах Alto, разработанных в 1973 году в исследовательском центре PARC фирмы Xerox. Целью эксперимента было создание операционной среды, удобной для создания интерактивных программ с динамичным пользовательским интерфейсом. В этих системах впервые была реализована многооконная графика, когда пользователь одновременно видит на экране графический вывод нескольких программ и может активизировать любую из них, указав на соответствующее окно при помощи манипулятора-“мыши”.
При каждом движении мыши, нажатии на ее кнопки или клавиши на клавиатуре генерируется событие. События могут также генерироваться системным таймером или пользовательскими программами. Нельзя не упомянуть “визуальные” события, которые порождаются в ситуации, когда пользователь сдвинул или закрыл одно из окон и открыл при этом часть окна, находившегося внизу. Этому окну посылается событие, говорящее о том, что ему нужно перерисовать часть себя.
Каждое сообщение о событии представляет собой структуру данных, которая содержит код, обозначающий тип события: движение мыши, нажатие кнопки и т. д., а также поля, различные для различных типов событий. Для "мышиных" событий – это текущие координаты мыши и битовая маска, обозначающая состояние кнопок (нажата/отпущена). Для клавиатурных событий – это код нажатой клавиши, обычно, ASCII-код символа для алфавитно-цифровых и специальные коды для стрелок и других “расширенных” и “функциональных” клавиш – и битовая маска, обозначающая состояние различных модификаторов, таких как SHIFT, CNTRL, ALT и т. д. Для визуальных событий – это координаты прямоугольника, который нужно перерисовать, и т. д.
Все сообщения о событиях помещаются в очередь в порядке их возникновения.
В системе существует понятие обработчика событий. Обработчик событий представляет собой объект, т. е. структуру данных, с которой связано несколько подпрограмм-методов. Один из методов вызывается при поступлении сообщения. Обычно он также называется обработчиком событий. Некоторые системы предлагают объектам-обработчикам предоставлять различные методы для обработки различных событий, например, метод onclick будет вызываться, когда придет событие, сигнализирующее о том, что кнопка мыши была нажата и отпущена, когда курсор находился над областью, занимаемой объектом на экране.
Рассмотрим объект графического интерфейса, например меню. При нажатии на кнопку мыши в области этого меню вызывается обработчик события. Он разбирается, какой из пунктов меню был выбран, и посылает соответствующее командное сообщение объекту, с которым ассоциировано меню. Этот объект, в свою очередь, может послать командные сообщения каким-то другим объектам. Например, если была выбрана команда File/Open, меню передаст обработчику основного окна приложения сообщение FILEOPEN, а тот, в свою очередь, может передать команду Open объекту, отвечающему за прорисовку и обработку файлового диалога.
Таким образом, вместо последовательно исполняющейся программы, время от времени вызывающей систему для исполнения той или иной функции, мы получаем набор обработчиков, вызываемых системой в соответствии с желаниями пользователя. Каждый отдельный обработчик представляет собой конечный автомат, иногда даже вырожденный, не имеющий переменной состояния. Код обработчика по реализации обычно похож на оператор Switch.
Специальная программа, менеджер событий, просматривает очередь и передает поступающие события обработчикам. События, связанные с экранными координатами, передаются обработчику, ассоциированному с соответствующим окном. Клавиатурные события передаются фокусу клавиатуры – текущему активному обработчику клавиатуры.
Общая структура взаимодействия элементов системы представлена на рисунке:
Управление событиями позволяет относительно легко разрабатывать динамичные пользовательские интерфейсы, привычные для пользователей современных графических оболочек.
Высокая динамичность интерфейса проще всего обеспечивается, если каждый обработчик быстро завершается. Если же в силу природы запрашиваемой операции она не может завершиться быстро – например, если мы вставили символ, параграф удлинился на строку, и в результате текстовому процессору типа WYSIWYG (как мы видим, так и получаем) приходится переформатировать и переразбивать на страницы весь последующий текст – мы можем столкнуться с проблемой. В такой ситуации (а при написании реальных приложений она возникает сплошь и рядом) мы и вынуждены задуматься о том, что же в действительности представляют собою обработчики – процедуры, синхронно вызываемые единственной нитью менеджера событий, или параллельно исполняющиеся нити. Первая стратегия называется синхронной обработкой сообщений, а вторая, соответственно, асинхронной. Графические интерфейсы первого поколения – Mac OS, Win 16 – реализовывали синхронную обработку сообщений, а когда обработчик задумывался надолго, рисовали неотъемлемый атрибут этих систем – курсор мыши в форме песочных часов. Несколько более совершенную архитектуру предлагает оконная подсистема OS/2, Presentation Manager. PM также реализует синхронную стратегию обработки сообщений (менеджер событий всегда ждет завершения очередного обработчика), но в системе, помимо менеджера событий, могут существовать и другие нити. Если обработка события требует длительных вычислений или других действий (например, обращения к внешним устройствам или к сети), рекомендуется создать для этого отдельную нить и продолжить обработку асинхронно. Если же приложение этого не сделает (например, обработчик события просто зациклится или заснет на семафоре – это целочисленная переменная, через которую управляется вычислением нити: вычислять или поставить в очередь), системная очередь сообщений будет заблокирована и ни одно из графических приложений не сможет работать. Современные версии РМ предоставляют в этом случае возможность отцепить “ненормальное” приложение от очереди или даже принудительно завершить его. Асинхронные очереди сообщений предоставляют Win32 и оконная система X Window. Впрочем, и при асинхронной очереди занимающийся длительными размышления однопоточный обработчик событий – тоже малоприятное зрелище, ведь он не может перерисовать собственное окно, поэтому передвижение других окон по экрану порождает любопытные спецэффекты. Разработчикам приложений для названных систем также рекомендуется выносить длительные вычисления в отдельные нити.
Большинство реальных приложений для современных ОС, имеющих пользовательский интерфейс, таким образом, имеют двух- или более слойную архитектуру. При этом архитектура ближайшего к пользователю слоя (frontend), как правило, тяготеет к событийно-управляемой, а следующие слои (backend) обычно состоят из более традиционных взаимодействующих (не всегда, впрочем, строго гармонически) параллельно исполняющихся нитей, зачастую даже разнесенных по разным вычислительным системам.
Windows 9X как пример системы, управляемой событиями
Когда Windows обнаруживает какое-либо событие (например, нажата клавиша на клавиатуре, сдвинута мышь, включен или выключен принтер), она пытается решить, как избежать конфликтов между разными приложениями, распределив работу среди отдельных уровней, образующих ее архитектуру. Главными уровнями Windows являются:
1. Центральный уровень;
2. Аппаратно-независимый уровень;
3. Уровень приложений.
Отношения между уровнями Windows, а также между ними и другими частями системы приведены на рисунке.
Здесь GDI - интерфейс графического устройства.
Центральный уровень представляет собой набор файлов, которые управляют базовыми операциями Windows. Центральный уровень состоит из трех компонентов. Ядро представляет собой часть операционной среды, которая управляет всеми периферийными устройствами, подсоединенными к центральному процессору. В ДОС таким ядром являются файл COMMAND.COM, ПЗУ с BIOS и несколько системных файлов. До тех пор, пока ядро не загружено, нельзя пользоваться монитором, клавиатурой и другими устройствами. Windows 3.X может использовать для своей работы два режима: стандартный и расширенный. Стандартный режим использует способность 286 процессора адресовать расширенную память. В этом случае адресация сегмента кода не более 64Кб, и обмен кода на диск при использовании виртуальной памяти ограничивается одним 64Кб сегментом, что замедляет работу, так как файлы в Windows достигают объема 640Кб и более. Недостаток использования Windows в стандартном режиме - невозможность выполнения ДОС- и Windows-приложения одновременно, так как ДОС-приложения не согласуются с графическим интерфейсом Windows. Windows 9X используют только расширенный режим. В этом случае Windows создает в памяти виртуальные машины для каждого ДОС- или Windows-приложения. При этом каждая виртуальная машина рассматривается как отдельный компьютер, а все виртуальные машины управляются Windows. Этот режим возможен только на машинах с процессорами 80386 и выше. В расширенном режиме все регистры общего назначения процессора становятся из 16-тиразрядных 32-разрядными, что позволяет адресовать память в пределах свыше 1Мб. В случае, когда для выполнения приложения не хватает физической памяти, Windows скачивает часть неиспользуемого кода на жесткий диск и загружает его в память машины по мере надобности. Менеджер событий (USER) управляет всеми событиями в системе. Если, например, нажимается какая-нибудь клавиша, эта программа определяет, какому приложению предназначено это нажатие. Эта программа управляет также размерами и перемещением окон на экране, отслеживает и контролирует использование всех пиктограмм. Если введена команда, меняющая вид окна, менеджер событий передает это событие файлу графического интерфейса. Программа графического интерфейса отвечает за прорисовку частей окон, пиктограмм и других рисунков на экране. Запросы на изменение экрана поступают от менеджера событий, который посылает данные о изменении экрана ядру Windows для запоминания.
Аппаратно-независимый уровень. Если необходимо выполнять два или больше приложений, то окружение должно быть способно разрешать любые конфликты, могущие возникнуть при попытке разных приложений использовать одно и то же устройство в одно и то же время. Windows обеспечивает отдельный слой файлов, которые обеспечивают аппаратно-независимый подход. При таком подходе устройства, подключенные к системе, управляются непосредственно Windows, а не приложениями. Windows достигает этого прежде всего за счет установки драйверов устройств, а также шрифтов (фонтов). Шрифты могут быть спроектированы как под конкретное устройство (чаще всего монитор) или определенный размер, так и под любое устройство с возможностью масштабирования (шрифты TrueType).
Уровень приложений не встроен в Windows, а состоит из приложений (программ), которые выполняются под Windows. Windows управляет этими приложениями. Все Windows-приложения проектируются таким образом, чтобы соответствовать интерфейсу прикладных программ (API) Windows. Этот интерфейс представляет собой набор правил, которым должна следовать программа, чтобы иметь возможность запрашивать и получать помощь от операционной среды. ДОС и зачастую сеть имеют собственные интерфейсы прикладных программ. Интерфейс прикладных программ Windows характеризует динамическая библиотека связей (DLL) - это набор функций, которые находятся на жестком диске в файле, и могут быть использованы приложением.