Мария Сысойкина
[email protected]

В последнее время Интернет превратился в весьма удобное и распространенное средство ведения бизнеса; при правильном его использовании можно существенно улучшить динамику взаимоотношений бизнес-партнеров. Но по мере перевода рабочих процессов реального предприятия в Интернет обостряется проблема управления смысловым содержанием Web-сайта.

Любой Web-сайт состоит из набора страниц, организованных в соответствии с определенными задачами и требованиями. Существуют два принципиально разных подхода к организации сайта - статический и динамический.

Статические сайты

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

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

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

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

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

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

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

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

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

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

Динамические сайты

На динамическом сайте в основу любой Web-страницы положен шаблон, определяющий отображение страницы в окне Web-браузера. Информация помещается на страницы при помощи стандартных средств, не требующих знания языка HTML, и достаточно сложных для неспециалиста процедур публикации Web-страницы.

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

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

Для разработки динамических сайтов чаще всего используются такие системы и языки программирования, как ASP, PHP, Perl, С++. Контент динамических сайтов хранится обычно в базе данных, а на указанных языках пишутся программы, "на лету" генерирующие из содержимого таких баз HTML-странички, которые и видит пользователь.

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

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

Системы управления Web-контентом: типовая структура

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

Архитектура клиент-сервер

В двухзвенной архитектуре клиент-сервер большинство приложений выполняется на клиентском компьютере. При этом для доступа к необходимым данным клиентское приложение должно "знать", как организованы данные и где они хранятся. В этом случае компьютер клиента должен быть достаточно мощным для обработки данных из хранилища (такая архитектура обычно называется "толстый клиент"). На предприятии, взаимодействующем с клиентами (через Интернет), партнерами (через экстранет) и собственными работниками (через интрасети) требования приложений диктуют необходимость в трехзвенной архитектуре (ее еще называют "тонкий клиент"). Она не требует особой мощности от компьютеров клиентов и позволяет избежать загруженности сети, связанной с перемещением данных. Получая запрос, сервер приложений обрабатывает его, связываясь с хранилищем данных, которое может находиться как на этой же машине, так и на удаленном компьютере. Клиент получает лишь результат запроса в виде HTML-файла. Таким образом, сервер приложений - это стандартизованная платформа для динамической доставки контента и построения основных приложений. Надо заметить, что самих серверов приложений может быть много, а связь с ними происходит через Web-сервер.

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

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

Основные функции

Производители систем управления Web-контентом единодушны только в самом общем определении того, что должны предоставлять такие системы. Их основные параметры можно сгруппировать в три категории: разработка контента, управление сайтом, доставка контента.

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

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

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

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

Обратимся теперь к конкретным системам управления Web-контентом, успешно применяемым в электронной коммерции.

Allaire Spectra

Allaire Spectra поддерживает платформы Intel и Solaris, операционные системы Windows NT 4.0 Server и Windows 2000 и СУБД Microsoft SQL Server 7.0 или 2000, Oracle 8.0 или 8i, Sybase System 11.9.2 или 12.0 и IBM DB2 6.1.

В качестве базовой технологии Allaire Spectra использует сервер приложений Allaire ColdFusion. Продукт включает объектную базу данных, которая выступает как надстройка к любым используемым базам данных, как внутренним, так и внешним. Allaire Spectra предоставляет шесть основных видов услуг, покрывающих все стадии, которые проходит Web-контент: от самого начала разработки до публикации на сайте в доступном всем пользователям виде. Схематически архитектура Allaire Spectra представлена на рис. 2.

Рис. 2. Архитектура Allaire Spectra.

COAPI (Content Object API) - это модель программирования, позволяющая компаниям создавать Web-системы на основе объектно-ориентированного программирования и управления информацией. WEBTOP - высокоуровневая среда для поддержки различных групп участников, задействованных в моделировании, создании и управлении Web-бизнесом.

Управление контентом. На самом этапе создания контента множество авторов имеют возможность разрабатывать его без помощи ИТ-специалистов, в том числе и удаленно. Для этого автору достаточно входить в систему под своим именем, а система будет предоставлять соответствующую удобную среду для выполнения функций, определенных данному пользователю. Кроме того, система предоставляет возможность работы со встроенным HTML-редактором, который можно использовать прямо в браузере. Тем самым отпадает необходимость работы с внешними редакторами, такими как FrontPage или Microsoft Word.

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

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

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

Автоматизация процессов и документооборота. Данная услуга дает возможность определять и внедрять конкретные процедуры для создания и обновления контента. Удобство заключается в том, что Allaire Spectra не навязывает никакую схему ведения документооборота. Менеджеры могут самостоятельно определять этапы, которые должен проходить контент в процессе движения от создания к публикации, и соответствующие формы документов.

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

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

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

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

Documentum 4i

Documentum 4i можно определить как корпоративную систему поиска и управления информацией. Она позволяет осуществлять поиск необходимых данных в сети Интернет, управлять этими данными в корпоративной информационной базе, регулировать доступ к информации как внутри фирмы, так и со стороны внешних пользователей, создавать компоненты пользовательского интерфейса и шаблоны процессов (при помощи встроенного редактора Developer Studio) и т.д. Архитектура системы представлена на рис. 3.

Рис. 3. Архитектура системы Documentum 4i.

eContent Server. Основная технология Documentum, управляющая хранилищем данных и обеспечивающая широкий набор сервисов по управлению контентом внутри и между корпоративными системами.

WebPublisher (Content Contributor). Инструмент для создания и редактирования Web-контента. Возможности интеграции позволяют пользователям работать в удобной для них среде, а неспециалистам - создавать контент без знания HTML или XML.

FtpIntegrator. Средство интеграции контента из хранилища и любого Web инструмента, поддерживающего FTP. Пользователи могут поставлять любой контент в хранилище по протоколу FTP.

Web Development Kit и Developer Studio. Набор компонентов и среда разработки для создания собственных Web-приложений для работы с eContent Server и хранилищем.

WebCache. Утилита кэширования контента, ускоряющая доставку информации через Интернет.

ContentCaster. Мощное средство для безопасной передачи и доставки контента от центрального сервера к многочисленным рабочим станциям и удаленным компьютерам.

EConnector. Интегрированный пакет программ, позволяющий обмениваться контентом с различными внешними приложениями, такими, например, как Lotus Notes Mail.

Documentum 4i поддерживает такие сетевые стандарты и ОС, как Microsoft Windows NT и UNIX, и интегрирована с коммерческими системами управления базами данных: Microsoft SQL Server, Oracle, Sybase и Informix.

Основной инструмент Documentum 4i - это Docbase, хранилище данных, специально созданное для коммерческих предприятий. Documentum 4i предоставляет возможности размещения информации и автоматического управления ею. Пользователь может получить доступ к информации различными способами, например, при помощи браузера, проводника Windows, Microsoft Office или других систем.

Интересная возможность - формирование Docbase Federations (Федерации информационных баз). Это группа информационных баз, соблюдающих определенные форматы хранения и передачи данных для выполнения некоторых совместных операций. Изменения установок системы безопасности и зарегистрированных пользователей в одной информационной базе автоматически синхронизируются с конфигурациями безопасности и списком пользователей во всех информационных базах Федерации. Благодаря Docbase Federations пользователь получает ряд преимуществ в общении с базами данных: например, он может работать с данными целого ряда информационных баз, независимо от того, где фактически находится интересующая его информация.

Система поиска информации в Documentum 4i отчасти напоминает поисковый интерфейс Microsoft Windows. В ней также реализован поиск по различным критериям, возможен поиск по фрагментам текста. Пользователь может увидеть и запрос, и результат поиска в одном окне, а по желанию сохранить критерии в Smart list для дальнейшего использования.

В Documentum 4i есть система Electronic Signature Support (поддержка электронной подписи). EDM Server предоставляет возможность хранения четырех реквизитов электронной подписи: это имя пользователя, объект подписания, цель подписания, дата и время подписания. Для обеспечения дополнительной безопасности на электронную подпись можно поставить пароль.

Достаточно большое внимание уделено в Documentum 4i вопросу безопасности. Пользователь может беспрепятственно (в зависимости от уровня доступа) изымать документы из Docbase и редактировать их на своем компьютере. По окончании работы с данным документом он может поместить его обратно в Docbase, после чего система самостоятельно создает более позднюю, отредактированную версию документа. Администратор системы может выставить приоритеты пользователям, основываясь на нескольких различных уровнях доступа, начиная с "No Access" и заканчивая "Delete". Именно для этих целей в Documentum 4i существует Access ControlLists, в который записываются уровни доступа для индивидуумов и/или групп пользователей.

Vignette V/5

sV/5 eBusiness Platform представляет собой открытую, масштабируемую архитектурную основу, обеспечивающую работу приложений в области электронного бизнеса. В настоящее время платформа Vignette существует в двух вариантах - для серверов Sun с ОС Solaris и для серверов Intel с ОС Windows NT/2000. Платформа интегрирована для работы с такими серверами БД, как Oracle 8, Sybase Adaptive server, MS SQL Server.

V/5 Content Management Server. Осуществляет управление контентом, автоматизацию документооборота и доставку контента пользователям. Обеспечивает комплексную поддержку управления контентом, содержащимся в базах данных, XML-хранилищах и статических файлах. Сервер позволяет нетехническим специалистам представлять данные, используя привычные средства, такие как Microsoft Office, Microsoft Outlook, Quark XPress, Web-браузеры.

V/5 Lifecycle Personalization Server. Предлагает широкий спектр возможностей персонализации, с помощью которых можно представлять услуги клиентам, сотрудникам и бизнес-партнерам. Хорошо интегрирован с системой управления контентом.

V/5 Relationship Marketing Server. Обеспечивает решение электронного маркетинга с обратной связью. Включает в себя инструментарий для анализа данных, их сегментации, контролирует предоставление соответствующего интересам и требованиям пользователей контента.

V/5 Communication Server. Позволяет предприятию подключаться к мобильной аудитории и управлять взаимоотношениями с клиентами, партнерами и сотрудниками с помощью различных средств связи, таких, как электронная почта, пейджеры, электронные органайзеры и мобильные телефоны, поддерживающие WAP. Он позволяет распространять персонифицированный контент и обеспечивает полное взаимодействие с мобильной аудиторией электронного бизнеса.

V/5 Syndication Server. Позволяет расширить электронный бизнес до масштабируемой сети. Построенный с использованием открытых протоколов (XML и ICE), он позволяет готовить и распространять цифровые активы, такие, как контент, приложения, информация поставщика и каталоги продуктов, среди большого числа филиалов.

V/5 Advanced Deployment Server. Позволяет на уровне предприятия управлять разработкой и тестированием приложений электронного бизнеса. С его помощью географически удаленные группы разработчиков, журналисты, редакторы и администраторы могут подключать свои индивидуальные рабочие среды Vignette для создания защищенной, многоуровневой системы постадийной работы.

V/5 eBusiness Platform Architecture. В нее встроена поддержка двух основных парадигм (ASP/Windows DNA и JSP*/J2EE) для разработки приложений и их корпоративного внедрения. Кроме того, данная архитектура поддерживает различные механизмы интеграции (COM, EJB, C/C++, HTTP/Servlets, XML, SQL) для дочерних систем, серверов приложений и приложений третьих сторон.

Vignette V/5 Applications. Клиенты Vignette получают доступ к модулям и приложениям на основе Vignette Application Foundation (VAF), предоставленным другими клиентами и партнерами Vignette. Это могут быть либо контентные модули (например, основные финансовые новости дня), либо функциональные (например, кредитные калькуляторы, комментарии к статьям).

Vignette Application Power Pack. Полный набор повторно используемых, межплатформных модулей приложений, предназначенных для ускорения компоновки, использования и управления приложениями электронного бизнеса. Power Pack обеспечивает готовые пользовательские интерфейсы на основе браузеров для управления и поиска информации, персонифицированное взаимодействие и создание онлайновых сообществ.

Vignette V 2B Services. Упрощает процесс закупки, внедрения и использования приложений для электронного бизнеса. Эти услуги предоставляются через Vignette V2B MarketPlace, глобальный центр торговли приложениями, компонентами и услугами для электронного бизнеса, и Vignette V2B Communities, предоставляющий поддержку как корпоративным пользователям, так и партнерам-разработчикам.

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

Применение системы управления контентом при разработке web-проекта позволило не работать над созданием кода разметки каждой страницы, программированием и интегрированием их графического оформления. Достаточно выбрать готовый модуль, из ранее созданных и протестированных. Интеграция в систему делается по единому стандарту.

Начало развитие web-среды происходит в 90_ых гг. Этот период характеризовался относительно невысоким уровнем развития web технологий и web-проекты способные на интерактивное взаимодействие могли создаваться только программистами.

Вся динамика реализовывалась через CGI и другие сложные технологии и сайт обычно представлял собой набор статичных html_страниц, подготовленных в html редакторах. После набора, страницы, для осуществления возможности межстраничных переходов, объединялись ссылками и размещались на сервере. Вся работа по обновлению информации и проверке работоспособности проекта перекладывалась на его автора. Если требовалось не просто изменить существующую страницу, а добавить новую, то приходилось решать вопросы, связанные с логическим и физическим внедрением последней в весь проект. И если объем страниц возрастал, то «справляться» с ними становилось еще сложнее. Изменять и расширять динамические проекты было еще более проблематично.

Систем автоматизации всех этих процессов на тот момент практически не было. Однако такое положение длилось относительно недолго, и в дополнение к статичным html_страницами, CGI программированию появились более «дружественные» технологии - Asp (конец 1996 г.), ColdFusion (июнь 1995 г.), а позже и PHP. Новые технологии позволили совместить разметку html_страниц и несложный программный код, сделав тем самым пассивные html_страницы активными. Активность страниц позволила организовать интерактивное взаимодействие с пользователями и при обращении к одной и той же активной странице пользователь мог получать новые данные. В то же время стали создаваться активные страницы для автоматизации определенных действий, например, процесса загрузки файлов на сервер или же процесса создания новых страниц. Такие полезные страницы постепенно собирались во вспомогательные пакеты. Подобные пакеты применялись в типовых задачах. Вскоре разработчики пришли к решению о необходимости создания универсальных систем. Таким образом и появились первые универсальные коммерческие системы управления. Утвердилась и аббревиатура CMS. Позже появились и открытые системы управления. Дальнейшая эволюция этих систем привела к расширению функциональности и теперь они способны работать с темами, модулями, а также управлять другими элементами. Современные системы уже можно смело именовать «системами управления web-проектом».

Сейчас системы управления это не только удобная оболочка-менеджер для пользователя, но и мощный инструмент для web-разработчика (последнее справедливо далеко не для всех систем). Благодаря таким системам, все реже возникает необходимость в разработке web-проектов «с нуля» - подготовленному пользователю достаточно выбрать, установить и настроить существующую систему, чтобы получить приемлемый результат профессионального уровня.

К сожалению, некоторая часть существующих web-сайтов создана без систем управления. Это так называемые «пустышки».

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

По объективным оценкам на данный момент существует пока немалая часть web-проектов без систем управления.

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

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

На данном этапе будут рассмотрены два наиболее очевидных вида классификации:

  • - классификация по «степени открытости»;
  • - классификация по «разработчику системы»;

Классификация по «степени открытости»:

Многие пользователи уже успели попробовать такие «открытые» продукты как OpenOffice, Firefox, Linux, а также другие известные программы и системы, которые распространяются не просто бесплатно, но и с открытыми исходными кодами. Последнее означает, что каждый желающий может вносить изменения в исходный код таких программ и даже распространять их в модифицированном виде.

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

Открытый продукт можно «пересобрать» с учетом своих собственных требований и тем самым получить максимальный результат.

Классификация по «разработчику системы»:

Разработчик системы, как правило, является автором, и сопроводителем системы. Современная CMS система должна позволять, не прибегая к дополнительному программированию, выполнять: редактировать содержимое страниц, включая добавление / удаление графики; добавление новых страниц; изменение структуры сайта и различных мета данных; настройку регистрационных форм; управление опросами, голосованиями и форумами; вывод статистики посещений; распределение прав по управлению сайтом среди пользователей т.д. .

Многие сегодняшние начинающие программисты выросли на Windows. И кто-то наверно уже не знает, что до того, как появилась Windows, пользователи работали в операционной системе DOS.

DOS очень сильно отличается от Windows. Намного сильнее, чем Windows 7 от Windows 8. И поэтому, когда возникает необходимость работы в ДОС, у большинства пользователей это вызывает шок и глубокую депрессию.

Чтобы уберечь вас от этих последствий, я решил написать небольшую статью о работе в операционной системе ДОС.

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

Самой первой ОС для первого же компьютера IBM PC, созданного в 1981г., является DOS - Disk Operating System (Дисковая операционная система). Она была разработана фирмой MicroSoft и поэтому имела приставку MS (полное ее имя MS-DOS). Другие фирмы также занимались разработкой подобных систем, которые мало чем отличались друг от друга, но в своих названиях имели другие приставки (PC-DOS, NW-DOS, DR-DOS и др.). Обычно эти приставки не упоминают, и пишут просто DOS.

Ядро, то сеть основную часть MS-DOS составляют три файла:

  1. IO.SYS
  2. MSDOS.SYS
  3. COMMAND.COM
Файл IO.SYS как бы продолжает собой BIOS, предоставляя более широкий набор функций. Файл MSDOS.SYS отвечает за работу основных устройств, входящих в состав компьютера. Третий файл, COMMAND.COM, представляет собой командный процессор, с помощью которого пользователь может вводить команды, используя клавиатуру.

В настоящее время в DOS почти никто не работает, так как появились более современные операционные системы, такие как WINDOWS 95/98/ME/2000/XP/7/8 и другие, не только Windows, но и, например, Android. Справедливости ради следует отметить, что все они (ну или почти все) базируются на старом добром DOSе.

Поэтому изучение DOS никому не повредит - в жизни пригодится. Хотя, начиная с WINDOWS XP, операционная система DOS уже не используется как основа. Отчасти по этой причине некоторые старые программы не работают с новыми версиями Windows.

Но если вы собираетесь серьезно заняться программированием, то изучить DOS просто необходимо. Потом сами поймете почему.

Здесь мы поговорим только о самых основных командах DOS. В отличие, например, от Windows, где вы можете выполнить одно и тоже действие разными способами (с помощью мыши или клавиатуры и т.п.), в DOS все команды пишутся в Командной строке, а выполняются эти команды после нажатия на клавишу ENTER. После загрузки DOS вы увидите такую строку (приглашение):

где С - это имя диска. Чтобы перейти на другой диск (например, на диск А), наберите

и нажмите ENTER. Буква может быть как большой, так и маленькой.

ПРИМЕЧАНИЕ
Чтобы потренироваться работать в командной строке, необязательно перезагружать компьютер в DOS. Windows тоже имеет командную строку, которая "работает" по тем же принципам. Щелкните по кнопке ПУСК и выберите пункт меню ВЫПОЛНИТЬ (для Windows ME). Перед вами откроется окно с командной строкой. В эту строку и нужно вписывать команду, после чего, также как и в DOS, следует нажать клавишу ENTER или щелкнуть по кнопке ОК.

Пакетные (командные) файлы
С помощью команд DOS можно создавать так называемые пакетные файлы (их ещё называют "командные файлы", BAT-файлы, или просто "пакетники"). Поскольку Windows поддерживает команды DOS, то эти файлы будут работатьи в Windows. Системные администраторы и программисты до сих пор широко используют пакетные файлы. Чтобы разобраться с командными файлами, скачайте контрольную работу по этой теме.

Вид приглашения можно изменить командой PROMPT. Для этого в командной строке нужно набрать следующее:

PROMPT Текст

где Текст - любое текстовое сообщение. Например, после ввода команды:

PROMPT Привет

вместо стандартного С:\> будет выводиться слово Привет.

Практически все команды сводятся к открытию какого-либо файла или программы (вообще-то программа - это тоже файл). Общий вид команды, которая открывает файл:

С:\>Папка1\ . . . \ПапкаN\ИмяФайла

где С - это имя диска; Папка1 . . . ПапкаN - это имена каталогов (полный путь к файлу): ИмяФайла - имя файла с раширением. Если же такого файла нет, то будет выведено сообщение об ошибке.

ПРИМЕЧАНИЕ
Если вы работаете в командной строке Windows, то таким образом можно открыть практически любой файл. Если расширение файла не указывать, то откроется файл с расширением EXE, BAT или СОМ. Ну а если вы работаете в DOS, то открыть вы сможете только исполняемые файлы (программы). Поэтому расширение можно и не указывать.

Для примера рассмотрим такой вариант: нам нужно открыть файл с именем FILE.COM, который находится в папке TEXT, а эта папка, в свою очередь, находится в папке DOC. А папка DOC находится на диске С. Для этого пишем в командной строке следующее:

C:\>DOC\TEXT\FILE

и нажимаем ENTER. Если файл существует, то он откроется (точнее, запустится программа, так как файлы с расширением СОМ - это программы).

ПРИМЕЧАНИЕ
Если вы хотите поработать в условиях, "приближенным к боевым", то вы можете выбрать в главном меню (кнопка "Пуск") пункт "Сеанс MS-DOS". После запуска программы вы увидите черное окно, где и будет командная строка DOS. Здесь вы можете опробовать все приводимые в этой статье команды. Если же в главном меню вы почему-то не обнаружили вышеупомянутого пункта, то воспользуйтесь функцией поиска файлов, чтобы найти файл COMMAND.СОМ или СMD.EXE (для Windows NT*). Можно также просто открыть командную строку и ввести там COMMAND.СОМ или СMD.EXE (для Windows NT*).

О любой команде DOS вы можете получить короткую справку, набрав имя команды в командной строке с параметром "?". Например:

Если вы работаете не в "чистой" DOS, а под управлением Windows (см. ПРИМЕЧАНИЕ выше), то после нажатия клавиши ENTER вы получите примерно такую информацию:

Windows Millennium [Версия 4.90.3000]

Ну а если вы работаете в DOS, то получите информацию о версии DOS. Как вы поняли, команда VER выводит информацию о версии операционной системы.

Команда CD

Эта команда, как и большинство других, имеет несколько параметров:

CD\ - переход в корневой каталог данного диска. Например, если вы находитесь в

C:\DIR1\DIR2\DIR3

то после выполнения этой команды вы перейдете на диск С:

СD \GAME - переход к дочернему каталогу GAME из корневого каталога

СD GAME - переход к дочернему каталогу GAME из текущего каталога

CD.. - выход из каталога (переход к родительскому каталогу). Например, если вы находитесь в

C:\DIR1\DIR2\DIR3

то после выполнения этой команды вы перейдете в:

Команда DIR

DIR - просмотр списка директорий и файлов в данной директории

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

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

Команда MD

Эта команда создает новый каталог

MD PAPKA - создается каталог РАРКА в текущей папке или на текущем диске.

MD\PAPKA - создается каталог РАРКА в корневом каталоге.

MD С:\DIR\PAPKA - создается каталог РАРКА в каталоге DIR .

Команда REN

Эта команда переименовывает файл

REN FILE_1.txt F_1.txt - Файл FILE_1 переименовывается в F_1

Команда COPY

Эта команда копирует файлы

COPY C:\MYFILE\TEXT.ТХТ A:\ - копирование файла TEXT.ТХТ из каталога MYFILE на дискету А.

COPY C:\TEXT\*.ТХТ A:\ - копирование всех файлов с расширением ТХТ каталога ТЕХТ на дискету А.

COPY FILE_1.TXT A:\TEXT\FILE_2.TXT - копирование файла под новым именем из текущего каталога активного диска в каталог ТЕХТ диска А.

COPY F_1.TXT+F_2.TXT+F3.TXT F_END.TXT - объединение (слияние) трех файлов в один файл (F_END.TXT). Все четыре файла в текущем каталоге.

COPY FILE_1.TXT PRN - печать (копирование на принтер) файла FILE_1.TXT из текущего каталога.

COPY FILE_1.TXT CON - просмотр файла. Здесь в качестве параметра команды используется слово CON (сокращение от CONSOL). Это слово зарезервировано операционной системой для стандартных устройств ввода-вывода. При вводе данных этим устройством является клавиатура, при выводе - монитор. Аналогичного результата можно достичь командой TYPE FILE_1.TXT .

COPY CON FILE_1.TXT - создание (копирование с клавиатуры) нового текстового файла FILE_1.TXT в текущем каталоге. После выполнения этой команды можно вводить с клавиатуры любой текст. Для завершения процесса создания файла следует ввести специальный управляющий символ "конец файла". Для этого нужно нажать комбинацию клавиш Ctrl+Z, а затем нажать клавишу ENTER.

Команда XCOPY

Эта команда копирует каталоги.

XCOPY C:\TEXT\*.* A:\PAPKA\*.* /S /E - создание на дискете А каталога РАРКА (если его там не было) и копирование в нее всех каталогов и подкаталогов (включая пустые).

Команда DEL

Эта команда удаляет файлы.

DEL *.dat - удаление всех файлов с расширением DAT из текущего каталога

DEL *.* - удаление всех файлов из текущего каталога

DEL C:\TEXT\FILE.txt - удаление файла FILE.ТХТ из каталога ТЕХТ.

DEL C:\TEXT\*.doc /P - удаление всех файлов с расширением DOC из каталога ТЕХТ. Параметр Р означает, что перед удалением каждого файла у вас запросят подтверждение.

Команда RD

Эта команда удаляет пустые каталоги.

RD TEXT - удаление каталога ТЕХТ.

Команда PATH

Эта команда задает пути поиска файлов с исполнимыми программами.

PATH C:\;C:\Windows;C:\MyProg

Если вы введете такую команду, то вам уже не придется прописывать полный путь к файлам, которые находятся на диске С и в каталогах WINDOWS и MYPROG. Например, если в каталоге MYPROG у вас есть программа с именем PROGA, то после ввода вышеуказанной команды для запуска этой программы в командной строке будет достаточно написать PROGA и нажать ENTER. Обратите внимание, что при перечислении каталогов в команде PATH между ними НЕ ДОЛЖНО быть пробелов.

Команда EXIT

Эта команда осуществляет выход из программы COMMAND (Сеанс MS-DOS). Если вы запустили его из Windows, то обратно в Windows и вернетесь.

Текстовый редактор EDIT

Это простой и весьма удобный текстовый редактор. Он вам может пригодиться, чтобы посмотреть текстовые файлы, которые имеют досовскую кодировку символов. (В Windows вы можете открыть такие файлы с помощью блокнота, но только вместо нормального текста увидите набор "иероглифов"). Чтобы запустить этот редактор, наберите в командной строке слово EDIT и нажмите клавишу ENTER. Этот редактор можно запустить и из командной строки Windows.

ПРИМЕЧАНИЕ
Здесь имена всех команд напечатаны большими буквами, однако, это не имеет значения - можно писать и маленькими буквами.

И в заключение упомянем две команды для работы с диском:

FORMAT - форматирование диска

FORMAT A: - форматирование диска А. Прежде чем форматировать диск - хорошо подумайте: стоит ли?

FDISK - деление винчестера на логические диски. При этом ВСЯ информация на диске теряется. Если не знаете, что делаете - лучше не используйте эту команду. А вообще, эта команда требует более детального рассмотрения. Как-нибудь я к ней еще вернусь.

Заключение

Цель этой статьи - познакомить людей, воспитанных на Windows, с общими принципами работы в DOS. Как видите, все не так уж страшно. И если вы не хотите быть просто "юзером", то знание DOS вам необходимо. Да и вообще, как говорит один мой знакомый программист: "Винчестер в компьютере - вещь не главная". Однако Windows вы без винчестера не запустите - слишком много она места занимает, а вышеупомянутые три основных досовских файла легко умещаются на дискете (все вместе они занимают около 150КБ - размер зависит от версии). Поэтому советую всегда иметь загрузочную дискету - вдруг придется проверить компьютер, на котором нет винчестера или винчестер неисправен. Если у вас нет возможности создать загрузочную дискету "чистой" DOS, то это и не страшно. Создайте загрузочную дискету Windows - на ней будет все та же DOS, только с кучей разных полезных программ (драйверы CD-ROM, русификатор клавиатуры и т.п.).

Если у вас Windows 95\98\ME и им подобные, то сделать это можно так:

Пуск > Настройка > Панель Управления > Установка и удаление программ

Затем выбираете вкладку "Загрузочный диск" и нажимаете кнопку "Создать диск".

Чтобы загрузиться с дискеты, вставьте ее в дисковод, и перезагрузите компьютер. Если в вашей BIOS установлены настройки по умолчанию, то компьютер загрузится с дискеты. А загрузит он не что иное, как DOS. Ну а если компьютер загрузил Windows, то требуется изменить настройки BIOS. Но это уже другая тема...

И еще один полезный совет

В начале статьи я упоминал командную строку Windows. Этой строкой иногда удобнее пользоваться, чем щелкать по ярлыкам. Если на вашем компьютере слишком много разных программ, то весь рабочий стол "загажен" ярлыками. Это не только мешает любоваться красивыми "обоями" - найти с первого раза нужный ярлык будет непросто. А если еще мышь сломается? Поэтому программы, которыми вы часто пользуетесь, лучше запускать из командной строки. Однако в командной строке нужно писать полный путь к файлу, а это неудобно. Но данную проблему можно решить, например, сделав соответствующие изменения в файле AUTOEXEC.ВАТ. А если вы смутно представляете, что это за файл и зачем он нужен, то есть более простой способ. Например, у вас есть программа "MyProg", которая находится в папке "Progy" на диске "С". В этом случае запускать из командной строки ее нужно так:

Чтобы не писать полный путь, нужно сделать следующее:

  • Открываем папку "Progy"
  • Правой кнопкой мыши щелкаем на файле "MyProg"
  • Из меню выбираем пункт "Создать ярлык"
  • Созданный ярлык переименовываем (правая кнопка -> переименовать). Дадим ему имя, которое будет легко запомнить, например, "mp".
  • Этот ярлык перетаскиваем в папку WINDOWS (если Windows установлена у вас в другой папке, то в другую этот ярлык и перетаскиваем).
Все. Теперь можно легко запустить программу "MyProg" из командной строки. Пишем там

и нажимаем ENTER. См. также команду PATH .

Если программа почему-то не запустилась, попробуйте перезагрузить компьютер. Если не поможет, значит, вы что-то сделали неправильно.

Основное преимущество командной строки в том, что с ее помощью можно довольно быстро запускать программы, не пользуясь мышью. Если вы не знаете, как открыть командную строку без мыши, приведу один способ для Windows, который подойдет и для большинства версий Windows:

  • Нажимаем клавишу Win (это вторая клавиша от пробела). Появляется меню "Пуск". Меню "Пуск" можно открыть и другим способом, нажав комбинацию клавиш CTRL + ESC.
  • Нажимая клавишу со стрелкой вверх (или вниз), выбираем пункт меню "Выполнить" и нажимаем ENTER.
  • Пишем в командной строке соответствующую команду и нажимаем ENTER.

Второй способ:

  • Просто нажимаем комбинацию клавиш WIN + R.

Если у вас старая клавиатура, которая не имеет клавиши WIN, то открыть меню ПУСК можно комбинацией клавиш CTRL + ESC.

3.1. Организация приложений MS-DOS
Как уже отмечалось выше, язык ассемблера является отражением архитектуры процессора, и изучение языка в сущности означает изучение системы команд и способов адресации, реализуемых процессором. Одна ко любой язык программирования полезен лишь постольку, поскольку на нем можно написать какие-то работоспособные программы. В то же время трудно представить себе реальную программу, которая выполняет чисто логические или вычислительные действия, ничего не вводя и не выводя и не взаимодействуя с другими программами. Однако такие вопросы, как организация выполнимой программы, ее запуск, взаимодействие с разнообразными аппаратными и программными объектами вычислительной системы (клавиатурой, дисками, таймером, памятью, системными драй верами и проч.) и, наконец, завершение являются прерогативой операционной системы. Поэтому в программах на языке ассемблера всегда широко используются системные средства, например, для вывода па экран или ввода с клавиатуры, чтения или записи файлов, управления памятью и проч. Более того, сама внутренняя организация программы, ее структура и, в определенной степени, алгоритмы поведения в сильной степени определяются правилами организации вычислительного процесса, заложенными в DOS. Изучение языка ассемблера в отрыве от конкретной операционной системы вырождается в схоластическое занятие, результатом которого будет знание формальных правил написания программных предложений без возможности применить эти правила для создания работоспособных программ.
В то же время возможности даже такой относительно простой операционной системы, как MS-DOS, весьма велики и многообразны, и их изучение составляет самостоятельный раздел программирования. В настоящей книге средства DOS рассматриваются лишь в том минимальном объеме, который необходим для создания простых, но работоспособных программ на языке ассемблера, а также для демонстрации основных алгоритмов и приемов программирования.
Желающие получить более глубокое представление о возможностях MS-DOS и использовании функций DOS в прикладном программировании, могут обратиться к книге: К.Г.Финогенов "Самоучитель по системным функциям MS-DOS", M., Радио и связь, Энтроп, 1995.
К числу важнейших вопросов, требующих хотя бы минимального рассмотрения, следует отнести требования, предъявляемые MS-DOS к структуре прикладных программ, а также к особенностям их взаимодействия с самой DOS и с другими программами.
Программы, предназначенные для выполнения под управлением MS-DOS, можно классифицировать по разным признакам. По внутренней организации все программы принадлежат к одному из двух типов, которым соответствуют расширения имен программных файлов.ЕХЕ и.СОМ. По взаимодействию с самой DOS программы подразделяются на транзитные и резидентные. Наконец, следует выделить важнейший класс программ, служащих для обработки аппаратных или программных прерываний, и называемых обычно обработчиками прерываний. Мы не касаемся здесь таких специфических программ, как устанавливаемые драйверы устройств, командные процессоры (к их числу принадлежит COMMAND.COM) или оболочки DOS (например, широко распространенная программа Norton Commander), которые можно выделить в самостоятельные классы.
Первый пример законченной программы, рассмотренный нами в гл. 2, относился к наиболее распространенному типу.ЕХЕ-приложений. Для такой программы характерно наличие отдельных сегментов команд, данных и стека; для адресации к полям каждого сегмента используется свой сегментный регистр. Удобство.ЕХЕ-программы заключается в том, что ее можно почти неограниченно расширять за счет увеличения числа сегментов. В случае большого объема вычислений в программу можно включить несколько сегментов команд, обеспечив, разумеется, переходы из сегмента в сегмент с помощью команд дальних переходов или дальних вызовов подпрограмм. Если же программа должна обрабатывать большие объемы данных, в ней можно предусмотреть несколько сегментов данных. Каждый сегмент не может иметь размер более 64 Кбайт, однако в сумме их объем ограничивается только наличной оперативной памятью. Правда, в реальном режиме затруднительно обратиться к памяти за пределами 1 Мбайт адресного пространства, так что максимальный размер программы, если не предусматривать в ней какие-то специальные средства поочередной загрузки сегментов, ограничен величиной 550 ... 600 Кбайт. Наличие в МП 86 лишь двух сегментных регистров данных (DS и ES) несколько усложняет алгоритмы обработки больших объемов данных, так как приходится постоянно переключать эти регистры с одного сегмента на другой. Однако реально в современных процессорах имеются не два, а четыре сегментных регистра данных (DS, ES, FS и GS), которые вполне можно использовать в приложениях DOS, упростив тем самым процедуры обращения к данным и ускорив выполнение программ. Позже все эти возможности будут рассмотрены более подробно.
Во многих случаях объем программы оказывается невелик - меньше, а часто и много меньше, чем 64 Кбайт. Такую программу нет никакой необходимости составлять из нескольких сегментов: и команды, и данные, и стек можно разместить в единственном сегменте, настроив на его начало все 4 сегментных регистра. Для односегментных программ в MS-DOS существует специальный формат и специальные правила их составления. Программные файлы с программами, составленными по этим правилам, имеют расширение.СОМ. В формате.СОМ обычно пишутся резидентные программы и драйверы, хотя любую прикладную программу небольшого объема можно оформить в виде.СОМ-приложения. Если посмотреть список системных программ, входящих в DOS, и реализующих, в частности, внешние команды DOS, то можно заметить, что приблизительно треть этих программ написана в формате.COM (COMMAND.COM, FORMAT.COM, SYS.COM и др.), а две трети - в формате.EXE (FC.EXE: PRINT.EXE, XCOPY.EXE и т.д.). Ниже мы рассмотрим правила составления и особенности исполнения как.ЕХЕ-, так и.СОМ-программ.
Другой критерий классификации программ определяет способ взаимодействия прикладной программы с другими программами и самой DOS. По этому критерию программы делятся на два вида: транзитные и резидентные.
Ход выполнения транзитной программы (а к транзитным относится подавляющее большинство приложений DOS) выглядит следующим образом. Пользователь запускает программу, вводя с клавиатуры ее имя, завершаемое нажатием клавиши Enter. Соответствующие программы-компоненты DOS отыскивают на диске файл с указанным именем, загружают его в память и передают управление на входную точку этой программы. Далее программа выполняется, фактически монополизируя ресурсы компьютера. Пока она не завершилась, пользователь не имеет доступа к DOS и, соответственно, лишен возможности запустить другую программу или выполнить какую-либо команду DOS. Ввод с клавиатуры возможен только в ответ на запрос текущей программы, если в ней предусмотрено обращение к клавиатуре за получением каких-либо данных.
Совсем по-другому функционирует резидентная программа. Пользователь запускает ее точно так же, как и транзитную, вводя с клавиатуры ее имя. Программы DOS загружают программный файл в память и передают управление на точку входа. Однако дальше вычислительный процесс развивается поиному. Программа выполняет только свой начальный, инициализирующий фрагмент, после чего вызывает специальную функцию DOS (с номером 31h). Эта функция завершает программу и возвращает управление в DOS, но не освобождает память от завершившейся программы, а оставляет эту программу в памяти, делая ее резидентной. Программа остается в памяти и, можно сказать, ничего не делает. Поскольку управление передано DOS, пользователь может вводить с клавиатуры любые команды и, в частности, запускать другие транзитные (или резидентные) программы. Когда будет активизирована находящаяся в памяти резидентная программа? Как правило, резидентные программы включают в себя обработчики аппаратных или программных прерываний. Если, например, в резидентной программе имеется обработчик прерываний от системного таймера, который, как известно, выдает сигналы прерываний приблизительно 18 раз в секунду, то каждое такое прерывание будет предавать управление резидентной программе, которая может, например, периодически выводить на экран текущее время или какую-то иную информацию. Работа резидентной программы будет протекать независимо от других программ и параллельно с ними. Другим классическим примером резидентной программы является русификатор клавиатуры, который получает управление при нажатии любой клавиши, независимо от того, какая программа сейчас выполняется. Задача русификатора - определить по имеющемуся в нем флагу, на каком языке работает пользователь, и в необходимых случаях сформировать соответствующий нажатой клавише код ASCII русской буквы.
Следует заметить, что необходимость в резидентных программах возникла лишь потому, что MS-DOS является существенно однозадачной системой. В многозадачной операционной системе Windows понятие резидентной программы в принципе отсутствует.
Разумеется, своими особенностями составления и функционирования обладают и обработчики прерываний - чрезвычайно важный класс программ, обслуживающих многочисленные внешние устройства компьютера - клавиатуру, мышь, магнитные диски и проч., а также нестандартную аппаратуру, если компьютер используется для управления научной установкой или технологическим процессом.
Рассмотрим основные правила составления и функционирования перечисленных типов программ, чтобы в дальнейшем можно было исполь-зовать их в примерах, иллюстрирующих те или иные средства языка ассемблера.

Программа типа.ЕХЕ
Характерные особенности программ типа.ЕХЕ подробно рассматри-вались в предыдущих главах. Приведем еще несколько обобщающих сооб-ражений. Структура типичной программы на языке ассемблера выглядит следующим образом.

Программа начинается с директивы ассемблера.586, разрешающей использовать в тексте программы весь набор команд процессора Pentium (кроме привилегированных). Если программа будет использовать только базовый набор команд МП 86, указание этой директивы не обязательно.
С другой стороны, ее указание не обязывает нас обязательно использовать команды Pentium. Если в программе предполагается использовать лишь дополнительные команды процессоров 486 или 386, то вместо.586 можно написать.486 или.386.
Указание любого номера 32-разрядного процессора приведет к тому, что по умолчанию программа будет транслироваться, как 32-разрядное приложение, в то время как нам нужно создать обычное 16-разрядное приложение. Для того, чтобы все адреса в программе рассматривались, как 16-битовые, необходимо придать сегментам команд и данных описатели use16. Для сегмента стека этот описатель не нужен, так как в стеке нет поименованных ячеек.
Программа состоит из трех сегментов - команд, данных и стека. Имена сегментов выбраны произвольно. Собственно программа обычно состоит из процедур. Деление на процедуры не обязательно, но повышает ее наглядность и облегчает передачу управления на подпрограммы. В рассматриваемом примере сегмент команд содержит единственную процедуру main, открываемую оператором ргос (от procedure, процедура) и закрываемую оператором endp (end procedure, конец процедуры). Перед обоими операторами указывается имя процедуры, которое в дальнейшем может использоваться в качестве относительного адреса процедуры (в сущности, относительного адреса первого выполнимого предложения этой процедуры). У нас это имя выступает в качестве параметра завершающей программу директивы end. Имена процедур, так же, как и имена сегментов, выбираются произвольно.
Если программа имеет сегмент данных с какими-либо данными, то для того, чтобы к этим данным можно было обратиться, необходимо занести сегментный адрес сегмента данных в один из сегментных регистров. Обычно в качестве такого регистра выбирают DS. Таким образом, предложения, с которых начался текст главной процедуры

mov AX,data ;Инициализация
mov DS,АХ;сегментного регистра DS

где data - имя, данное сегменту данных, практически являются обяза-тельными для любой программы.
Точно также обязательными яачяются и завершающие предложения

mov AX,4C00h ;Вызов функции DOS
int 21h ;завершения программы

в которых вызывается функция DOS с номером 4Ch. Эта функция, как уже отмечалось, завершает программу, освобождает занимаемую ею память и передает управление командному процессору COMMAND.COM. Еще два замечания следует сделать относительно процедуры трансляции и компоновки программы. Если сегмент данных расположить после сегмента команд, как это сделано в нашем примере, то у транслятора возникнут сложности при обработке встречающихся в программных пред-
ложениях имен полей данных, так как эти имена еще неизвестны транслятору. Для того, чтобы такие, как говорят, "ссылки вперед" могли правильно обрабатываться, следует в команде вызова транслятора TASM заказать два прохода. Это делается указанием ключа /m2.
С другой неприятностью мы столкнемся, если попытаемся включить в программу операции с 32-разрядными операндами (даже и с командами МП 86). Компоновщик TASM по умолчанию запрещает такого рода операции. Чтобы преодолеть этот запрет, следует в команде вызова компоновщика указать ключ /3.
Таким образом, приведенный в гл. 1 командный файл должен выглядеть (для подготовки программы P.ASM) следующим образом:

tasm /z /zi /n /m2 p,p,p
tlink /x /v /3 p,p

Включение указанных описателей и ключей не обязывает нас использовать новые команды или 32-разрядные операнды, так что приведенные выше тексты командного файла и самой программы можно использовать как образец для подготовки всех приведенных в этой книге программных примеров, даже если они используют только средства МП 86. В дальнейших примерах программ, в основном посвященных системе команд МП 86, эти описатели будут опускаться.
Приведем в качестве еще одного примера простую законченную программу типа.ЕХЕ, которая выясняет букву - обозначение текущего диска и выводит ее на экран с поясняющей надписью.


Рассмотрим текст приведенного примера. После настройки сегментного регистра DS на сегмент данных, вызывается функция DOS с номером 19h, которая позволяет получить код текущего диска. У этой функции нет никаких параметров, а результат своей работы она возвращает в регистре AL в виде условного кода. 0 обозначает диск А:, 1 диск В:, 2 диск С: и т.д. Если, например, пользователь работает на диске F, то функция 19h вернет в AL код 5.
Для преобразования кода диска в его буквенное обозначение, мы воспользовались широко распространенным приемом. В полях данных определена символьная строка, которая будет выводиться на экран. Для удобства работы она разделена на две части, каждая из которых имеет свое имя. Началу строки присвоено имя msg, а той ее части, которая начинается с обозначения диска А:, имя disk (разумеется, имена выбраны произвольно). Если посмотреть на таблицу кодов ASCII, то можно заметить, что код каждой следующей буквы алфавита на 1больше предыдущей. Таким образом, если к коду латинской буквы A (41h) прибавить 1, получится код буквы В, а если прибавить, например, 5, получится код буквы F. Именно эта операция и выполняется в предложении

add disk,AL ;Преобразуем номер в код ASCII

где к байту с адресом disk прибавляется код, возвращенный функцией DOS.
Выполнив модификацию строки, мы выводим ее на экран уже знакомой нам функцией DOS 09h. Она выводит все символы строки, пока не встретится с символом $, которым наша строка и завершается. Перед знаком S в строке имеются два числа: 13 и 10. При выводе текстовой строки на экран любой функцией DOS код 13 трактуется DOS, как команда вернуть курсор в начато строки ("возврат каретки"), а код 10 - как команда на перевод строки. Два эти кода переводят курсор в начало следующей строки экрана. В данном случае, когда на экран ничего больше не выводится, можно было обойтись и без этих кодов, которые включены лишь в познавательных целях.
Между прочим, правильная работа программы основана на том предположении (безусловно правильном), что ассемблер расположит наши данные в памяти в точности в том же порядке, как они описаны в программе. Именно это обстоятельство и позволяет дробить единую строку на части, не опасаясь, что в память они попадут в разные места, что привело бы, разумеется, к непредсказуемому результату.После вывода на экран сообщения о текущем диске в программе вызывается функция DOS с номером 01h. Эта функция вводит с клавиатуры один символ. Если символов нет (мы после запуска программы не нажимали на клавиши), функция 01h ждет нажатия фактически любой клавиши (более точно - любой алфавитно-цифровой или функциональной кла-виши). Такой весьма распространенный прием позволяет остановить выполнение программы до нажатия клавиши, что дает возможность программисту посмотреть, что вывела программа на экран, и оценить правильность ее работы.
Наконец, последнее действие носит, как уже отмечалось, сакраментальный характер. Вызовом функции DOS 4Ch программа завершается с передачей управления DOS.
Взглянем еще раз на текст программы 3-1. Если не считать первых предложений инициализации регистра DS, то в программе имеется лишь одна строка, носящая, можно сказать, вычислительный характер - это прибавление полученного кода диска к содержимому байта памяти. Все остальные строки служат для вызова тех или иных функций DOS - получения информации о текущем диске, вывода строки на экран, остановки программы и, наконец, ее завершения. Это подтверждает высказанное выше утверждение о важности изучения системных средств и широком использовании их в программах на языке ассемблера. Разумеется, в программе могут быть и сколь угодно сложные и протяженные участки обработки данных и других вычислений, но такие операции, как ввод с клавиатуры, вывод на экран, работа с файлами, получение, как в нашем примере, системной информации и многое другое выполняется исключительно с помощью вызова тех или иных функций DOS (или BIOS). Программу на языке ассемблера просто невозможно написать без использования системных средств.


Структура и образ памяти программы.СОМ
Как уже отмечалось, программа типа.СОМ отличается от программы типа.ЕХЕ тем, что содержит лишь один сегмент, включающий все компоненты программы: PSP, программный код (т.е. оттранслированные в машинные коды программные строки), данные и стек. Струкгура типичной программы типа.СОМ на языке ассемблера выглядит следующим образом:

Программа содержит единственный сегмент code. В операторе ASSUME указано, что сегментные регистры CS и DS будут указывать на этот единственный сегмент. Оператор ORG 100h резервирует 256 байт для PSP. Заполнять PSP будет попрежнему система, но место под него в начале сегмента должен отвести программист. В программе нет необходимости инициализировать сегментный регистр DS, поскольку его, как и остальные сегментные регистры, инициализирует система. Данные можно разместить после программной процедуры (как это показано в приведенном примере), или внутри нес, или даже перед ней. Следует только иметь в виду, что при загрузке программы типа.СОМ регистр IP всегда инициализируется числом 100h, поэтому сразу вслед за оператором ORG 100h должна стоять первая выполнимая команда программы. Если данные желательно расположить в начале программы, перед ними следует помес-тить оператор перехода на фактическую точку входа, например jmp entry.
Образ памяти программы типа.СОМ показан на рис. 3.2. После загрузки программы все сегментные регистры указывают на начато единственного сегмента, т.е. фактически на начато PSP. Указатель стека автоматически инициализируется числом FFFEh. Таким образом, независимо от фактического размера программы, ей выделяется 64 Кбайт адресного пространства, всю нижнюю часть которого занимает стек. Поскольку верхняя граница стека не определена и зависит от интенсивности и способа использования стека программой, следует опасаться затирания стеком нижней части программы. Впрочем, такая опасность существует и в программах типа.ЕХЕ, так как в реальном режиме нет никаких механизмов защиты, и при сохранении в стеке большего объема данных, чем может так поместиться, данные начнут затирать поля того сегмента, который рас-положен за стеком (если таковой сегмент существует).

Рис. 3.2. Образ памяти программы.СОМ

Программы типа.СОМ отличаются от.ЕХЕ-программ не только отсутствием сегментов данных и стека. В гл. 2 было показано, что при выравнивании сегментов на байт, что делается с помощью описателя byte

data segment byte

системные программы располагают сегменты загружаемой программы с некоторым перекрытием, что позволяет избежать пустых промежутков между сегментами в памяти, возникающих из-за того, что размеры сегментов могут быть не кратны величине параграфа - 16 байт. Такое распо-ложение сегментов требует изменения значений ссылок на адреса ячеек памяти. В состав программного файла с расширением.ЕХЕ входит таблица с перечнем байтов программы, содержимое которых может подвергнуться изменению в процессе загрузки программы в память. Поэтому, кстати, размер файла с расширением.ЕХЕ может превышать истинный размер программы в памяти.
Программа типа.СОМ состоит из единственного сегмента, и проблема настройки ссылок не возникает. Файл с расширением.СОМ почти в точности отражает содержимое памяти после загрузки программы. Отличие заключается только в том, что в программном файле отсутствует префикс программы PSP, который система вставляет в программу в процессе ее загрузки. Таким образом, файл с расширением.СОМ обычно оказывается на 256 байт короче своего образа в памяти.
Если оттранслировать и скомпоновать программу, написанную в формате.СОМ, обычным образом, образуется программный файл с расширением.ЕХЕ. Этот файл можно запустить на выполнение, однако работать он будет неверно. Дело в том, что система, загружая файл типа.ЕХЕ в память, пристраивает перед загруженной программой префикс и настраивает на него регистры DS и ES. В результате значение DS окажется на 10h меньше, чем сегментный адрес сегмента с командами и данными, что приведет к неправильной адресации при обращении к полям данных. Программу, написанную в формате.СОМ, можно запускать только в виде файла с расширением.СОМ, для которого в DOS предусмотрен CBI алгоритм загрузки и запуска. Для того, чтобы компоновщик создал файл с расширением.СОМ, в строке запуска компоновщика необходимо предусмотреть ключ /t (при использовании компоновщика TLINK.EXE):

tlink /x /v /3 /t p,p

Для того, чтобы избежать ошибок при подготовке программ, целее образно подготовить два командных файла для трансляции и компоновки программных примеров - один для программ типа.ЕХЕ, и другой для программ типа.СОМ. Разумеется, файлам надо назначить различающие имена.
Рассмотрим пример законченной программы типа.СОМ, которая выводит на экран строку текста.

В начале программы отведено 256 байт под PSP; в программе отсутствует инициализация регистра DS; поле данных размещено в программном сегменте непосредственно после последней команды. Для разнообразия в строку, выводимую на экран, включены коды 16 и 17, которые отображаются на экране в виде залитых треугольников (рис. 3.3). Как видно из этого рисунка, программа имела имя Р. СОМ и запускалась из каталога F:\CURRENT.
Рассмотрим важный в принципиальном плане вопрос о месте размещения данных в.СОМ-программе. В нашем примере данные описаны в конце программного сегмента вслед за процедурой main, которая, как и в предыдущих примерах, введена скорее для порядка, чем по необходимости.

Рис. 3.3. Вывод программы 3.2.

С таким же успехом можно было предложение с именем msg поместить после вызова int21h, внутри процедуры main. Третий возможный вариант, с которым мы еще столкнемся в примерах резидентных программ, приведен ниже.


Таким образом, данные могут быть размещены как после программы, так и среди выполнимых предложений программы. Важно только соблюсти обязательное условие: ни при каких обстоятельствах на данные не должно быть передано управление. В первом случае (пример 3-2) данные помещены за вызовом функции DOS, завершающей программу. Ясно, что после выполнения этой функции управление уже не вернется в нашу программу, а будет передано командному процессору, поэтому размещение здесь данных вполне возможно. В последнем фрагменте данные описаны, можно сказать, в середине программы. Однако перед ними стоит команда безусловного перехода jmp, которая приводит при выполнении программы к обходу данных.
А вот чего нельзя было сделать, так это разместить данные после зак-рытия сегмента, как это сделано в приведенном ниже (неправильном!) фрагменте:

Это второе обязательное условие: из чего бы ни состояла программа, все ее компоненты должны входить в те или иные сегменты. Вне сегментов допускаются только нетранслируемые директивы ассемблера типа.586 или assume.
Наконец, третье условие, о котором уже говорилось, относится только к программам типа.COM. DOS, загрузив программу в память, инициализирует указатель команд числом 100h, т.е. адресом первой команды вслед за оператором org 100h. Поэтому главная процедура.СОМ-программы (если в ней имеется несколько процедур) обязательно должна быть первой, причем первое предложение этой процедуры должно быть выполнимой командой (например, командой jmp, как это показано выше).

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

Рис. 3.4. Функционирование программного комплекса с обработчиком прерываний.

Обработчик прерываний может входить в состав программы в виде процедуры, или просто являться частью программы, начинающейся с некоторой метки (входной точки обработчика) и завершающейся командой выхода из прерывания iret. Пока мы не будем рассматривать более сложный случай, когда обработчик представляет собой самостоятельную резидентную программу.
Программа, начиная свою работу, прежде всего должна выполнить инициализирующие действия по установке обработчика прерываний. В простейшем случае эти действия заключаются в занесении в соответствующий вектор полного адреса (сегмента и смещения) обработчика. Поскольку обработчик входит в состав программы, его относительный адрес известен; это имя его процедуры или метка входной точки. Что же касается сегментного адреса, то обработчик может входить в сегмент основной части программы, если она невелика по объему и занимает один сегмент, но может образовывать и самостоятельный сегмент. В любом случае в качестве сегментного адреса можно использовать имя соответствующего сегмента.
Часто инициализация обработчика, помимо установки вектора, предполагает и другие действия: сохранение исходного содержимого вектора прерывания, размаскирование соответствующего уровня прерываний в контроллере прерываний, посылка в устройство команды разрешения прерываний и проч.
Установив обработчик, программа может приступить к дальнейшей работе. В случае прихода прерывания, процессор сохраняет в стеке флаги и текущий адрес программы, извлекает из вектора адрес обработчика и передает управление на его входную точку. Все время, пока выполняется программа обработчика, основная программа, естественно, стоит. Завершающая обработчик команда irct извлекает из стека сохраненные там данные и возвращает управление в прерванную программу, которая может продолжить свою работу. Последующие прерывания обслуживаются точно так же.
Функции обработчика прерываний зависят от решаемой задачи и назначения того устройства, от которого поступают сигналы прерываний. В случае прерываний от клавиатуры задача обработчика прерываний - принять и сохранить код нажатой клавиши. Прерывания от мыши свидетельствуют о ее перемещении, что требует обновления положения курсора на экране. Если обслуживаемым устройством является физическая установка, то сигнал прерывания может говорить о том, что в установке накоплен определенный объем данных, которые надо перенести из памяти установки в память компьютера. В любом случае обработчик прерываний должен быть программой несложной, для выполнения которой не требуется много машинного времени.
Рассмотрим структуру программы с обработкой аппаратных прерываний. Наиболее удобным аппаратным прерыванием, которое можно использовать в исследовательских целях, является прерывание от системного таймера, которое генерируется 18.2 раза в секунду и служит источником сигналов для хода системных часов, отсчитывающих время, истекшее после включения машины. Замена системного обработчика на прикладной не приводит к каким-либо неприятностям, кроме, возможно, остановки на некоторое время системных часов.
Будем считать, что наш программный комплекс представляет собой программу типа.ЕХЕ и что обработчик прерываний входит в общий с основной программой программный сегмент. Для определенности будем использовать вектор 08h, хотя, разумеется, для любого другого аппаратного вектора структура программы останется той же. Поначалу приведем текст программы с некоторыми купюрами.

В приведенном примере обработчик прерываний расположен в конце программы, после главной процедуры main. Взаимное расположение процедур не имеет ни малейшего значения; с таким же успехом обработчик можно было поместить в начале программы. Не имеет также значения, выделен ли обработчик в отдельную процедуру или просто начинается с метки.
Для того, чтобы прикладной обработчик получал управление в результате прерываний, его адрес следует поместить в соответствующий вектор прерывания. При этом исходное содержимое вектора будет затерто, и если прерывания будут поступать и после завершения программы, возникнет весьма неприятная ситуация, когда управление будет передаваться по адресу, по которому в памяти может располагаться что угодно. Поэтому стандартной методикой является сохранение в памяти исходного содержимого вектора и восстановление этого содержимого перед завершением программы.
Хотя и чтение, и заполнение вектора прерываний можно выполнить с помощью простых команд mov, однако предпочтительнее использовать специально предусмотренные для этого функции DOS. Для чтения вектора используется функция с номером 35h. В регистр AL помещается номер вектора. Функция возвращает исходное содержимое вектора в парс регистров ES:BX (легко догадаться, что в ES сегментный адрес, а в ВХ смещение). Для хранения исходного содержимого вектора в сегменте данных предусмотрена двухсловная ячейка old_08. В младшем слове этой ячейки (с фактическим адресом old_08) будет хранится смещение, в старшем (с фактическим адресом old_08+2) - сегментный адрес. Для того, чтобы обратиться к словам, составляющим эту ячейку, приходится использовать описатель word ptr, который как бы заставляет транслятор на время забыть о начальном объявлении ячейки и позволяет рассматривать ее, как два отдельных слова. Если бы мы отвели для исходного вектора две 16-битовые ячейки, например

то к ним можно было бы обращаться без всяких описателей.
Сохранив исходный вектор, можно установить в нем адрес нашего обработчика. Для установки вектора в DOS предусмотрена функция 25h. Она требует указания номера устанавливаемого вектора в регистре AL, a его полного адреса - в парс регистров DS:DX. Здесь нас подстерегает неприятность. Занести в регистр DX смещение нашего обработчика new_08 не составляет труда, это делается командой

mov DX,offset new_08 ;Смещение нашего обработчика

Однако регистр DS у нас занят - в нем хранится сегментный адрес сегмента данных. Придется на какое-то время сохранить этот адрес, для чего удобнее всего воспользоваться стеком. Откуда взять сегментный адрес обработчика? Между прочим, в языке ассемблера существует специальная конструкция, позволяющая определить сегментный адрес любого поля. В нашем случае она выглядела бы таким образом:

В примере 3-3 использован другой прием - содержимое CS отправляется в стек и тут же извлекается оттуда в регистр DS:

push CS pop DS

После возврата из DOS надо не забыть восстановить исходное содержимое DS, сохраненное в стеке. Инициализация обработчика прерываний закончена. Начиная с этого момента, каждый сигнал таймера будет приводить к прерыванию продолжающейся основной программы и передаче управления на процедуру new_08.
Перед завершением программы необходимо поместить в вектор 8 адрес исходного, системного обработчика, который был сохранен в двухсловном поле old_08. Перед вызовом функции 25h установки вектора в регистры DS:DX надо занести содержимое этого двухсловного поля. Эту операцию можно выполнить одной командой Ids, если указать в качестве ее первого операнда регистр DX, а в качестве второго - адрес двухсловной ячейки, в нашем случае old_08. Именно имея в виду использование этой команды, мы и объявили поле для хранения вектора двухсловным, отчего возникли некоторые трудности при его заполнении командами mov. Если бы мы использовали второй предложенный выше вариант и отвели для хранения вектора две однословные ячейки (old_08_offs и old_08_seg), то команду Ids пришлось бы снабдить описателем изменения размера ячейки:

Ids DX,dword ptr old_08_offs

Между прочим, здесь так же разрушается содержимое DS, но поскольку сразу же вслед за функцией 25h вызывается функция 4Ch завершения программы, это не имеет значения.
Последнее, что нам осталось рассмотреть - это стандартные действия по завершению самого обработчика прерываний. Выше уже говорилось, что последней командой обработчика должна быть команда iret, возвращающая управление в прерванную программу. Однако перед ней необходимо выполнить еще одно обязательное действие - послать в контроллер
прерываний команду конца прерываний. Дело в том, что контроллер прерываний, передав в процессор сигнал прерывания INT, блокирует внутри себя линии прерываний, начиная с той, которая вызвала данное прерывание, и до последней в порядке возрастания номеров IRQ. Таким образом, прерывание, пришедшее, например, по линии IRQ 6 (гибкий диск) заблокирует дальнейшую обработку прерываний по линиям 6 и 7, а прерывание от таймера (IRQ0) блокирует вообще все прерывания (IRQ0...IRQ7, а также и IRQ8...IRQ15, поскольку все они являются разветвлением уровня IRQ2, см, гл. 1, рис. 1.11). Любой обработчик аппаратного прерывания обязан перед своим завершением снять блокировку в контроллере прерываний, иначе вся система прерываний выйдет из строя. Снятие блокировки осуществляется посылкой команды с кодом 20h в один из двух портов, закрепленных за контроллером прерываний. Для ведущего контроллера эта команда посылается в порт 20h, для ведомого - в порт A0h. Таким образом, если бы мы обрабатывали прерывания от часов реального времени (линия прерываний IRQ8, вектор 70h, ведомый контроллер), то команда конца прерывания выглядела бы так:

Указанную последовательность команд иногда называют приказом, или командой EOI (от end of interrupt, конец прерывания).
Разобравшись в этих общих вопросах, рассмотрим пример реальной программы, включающей обработчик прерываний от таймера. Для того, чтобы приведенную выше фрагментарную программу преобразовать в действующую, надо написать содержательную часть самого обработчика, а также придумать, что будет делать основная программа после инициализации прерываний. Все это сделать очень просто.
Пусть наш обработчик в ответ на каждое прерывание от таймера выводит на экран какой-нибудь символ. Для этого можно воспользоваться функцией 0Eh прерывания BIOS 10h. Это прерывание обслуживает большое количество различных функций, обеспечивающих управление экраном. Сюда входят функции вывода символов и строк, настройки режимов видеосистемы, загрузки нестандартных таблиц символов и многие другие. Функция 0Eh предназначена для вывода на экран отдельных символов. Она требует указания в регистре AL кода выводимого символа. Процедура new_08 будет выглядеть в этом случае следующим образом:


Что же касается основной программы, то самое простое включить в нее (после завершения действий по инициализации обработчика прерываний) функцию DOS 0 Hi ожидания ввода с клавиатуры:

mov AH,01h
int 21h

В результате программа, дойдя до этих строк, остановится (фактически будет выполняться цикл опроса клавиатуры в ожидании нажатия клавиши, включенный в состав программы реализации функции 01h DOS), а на экран непрерывной чередой будут выводиться символы коммерческого at (рис. 3.5). После нажатия на любую клавишу программа завершится.

Рис. 3.5. Вывод программы 3-3.

Приведенный пример позволяет обсудить чрезвычайно важный вопрос о взаимодействии обработчиков аппаратных прерываний с прерываемой программой и операционной системой. Особенностью аппаратных прерываний является то, что они могут возникнуть в любой момент времени и, соответственно, прервать выполняемую программу в любой точке. Текущая программа, разумеется, использует регистры, как общего назначения, так и сегментные. Если в обработчике прерывания мы разрушим содержимое хотя бы одного из регистров процессора, прерванная программа по меньшей мере продолжит свое выполнение неправильным образом, а скорее всего произойдет зависание системы. Поэтому в любом обработчике аппаратных прерываний необходимо в самом его начале сохранить все регистры, которые будут использоваться в программе обработчика, а перед завершением обработчика (перед командой iret) восстановить их. Регистры, которые обработчиком не используются, сохранять не обязательно.
В нашем простом обработчике используется только один регистр АХ. Его мы и сохраняем в стеке первой же командой push AX, восстанавливая в самом конце обработчика командой pop AX.
Вторая неприятность может возникнуть из-за того, что в обработчике аппаратного прерывания мы воспользовались для вывода на экран функцией BIOS. Вообще говоря, считается, что в обработчиках аппаратных прерываний нельзя использовать никакие системные средства. Причина такого запрета заключается в том, что аппаратное прерывание может придти в любой момент, в частности тогда, когда прерываемая программы сама выполняет какую-либо функцию DOS или BIOS. Однако, если мы прервем выполнение системной функции на полпути, и начнем выполнять ту же самую или даже другую функцию с начала, произойдет разрушение системы, которая не предусматривает такое "вложенное" выполнение своих программ. В настоящее время разработаны программные приемы, позволяющие эффективно обойти указанный запрет, однако использование их в программе драматически увеличивает ее сложность и объем, и рассматривать эти приемы мы здесь не будем.
В нашем случае дело усугубляется тем, что прерывания от таймера не только могут придти в тот момент, когда выполняется функция DOS, но и неминуемо приходят только в такие моменты, так как мы остановили программу с помощью вызова функции 01h прерывания 2Hi и, следовательно, все время, пока наша программа ждет нажатия клавиши, в действительности выполняются внутренние программы DOS. Именно поэтому мы отказались от использования в обработчике прерывания функции DOS и выводим на экран символы с помощью прерывания BIOS. Выполнение функции BIOS "на фоне" DOS не так опасно. Надо только следить за тем, чтобы наше прерывание BIOS в обработчике не оказалось вложенным в такое же прерывание BIOS в прерываемой программе.
Рассмотренный пример имеет существенный недостаток. Записав в вектор прерываний 8 адрес нашего обработчика, мы затерли исходное содержимое вектора и тем самым ликвидировали (в логическом плане) исходный, системный обработчик. Практически это приведет к тому, что на время работы нашей программы остановятся системные часы, и если в системе есть какие-то другие программы, использующие прерывания от таймера, они перестанут работать должным образом. Ликвидировать указанный недостаток очень просто: надо "сцепить" наш обработчик с системным так, чтобы в ответ на каждый сигнал прерывания активизировались последовательно оба обработчика. Рассмотрим методику сцепления обработчиков.
При инициализации прикладного обработчика, сцепляемого с системным, следует точно так же, как и раньше, сохранить в программе адрес системного обработчика и поместить в вектор прерывания адрес прикладного обработчика. Изменениям подвергнется только программа самого обработчика, начало которой должно выглядеть следующим образом:

Как будет работать такая программа? После того, как процессор выполнит процедуру прерывания, в стеке прерванного процесса оказываются три слова: слово флагов, а также двухсловный адрес возврата в прерванную программу (рис.3.6, нижние три слова стека).

Рис. 3.6. Стек прерванной программы в процессе выполнения прикладного обработчика прерываний.
CS1 - сегментный адрес прерванного процесса;
IP1 - смещение точки возврата в прерванную программу;
CS2 - сегментный адрес прикладного обработчика;
IP2 - смещение точки возврата в прикладной обработчик.

Именно такая структура данных должна быть на верху стека, чтобы команда iret, которой завершается любой обработчик прерываний, могла вернуть управление в прерванную программу.
Первая команда нашего обработчика pushf засылает в стек еще раз слово флагов, а команда дальнего вызова процедуры call cs:old_08 (где ячейка old_08 объявлена с помощью оператора dd двойным словом) в процессе передачи упражнения системному обработчику помещает в стек двухсловный адрес возврата на следующую команду прикладного обработчика. В результате в стеке формируется трехсловная структура, которая нужна команде iret.
Системный обработчик, закончив обработку данного прерывания, завершается командой iret. Эта команда забирает из стека три верхние слова и осуществляет переход по адресу CS2:IP2, т.е. на продолжение прикладного обработчика.
Завершающая команда нашего обработчика iret снимает со стека три верхних слова и передает упражнение по адресу CS1:IP1.
Описанная методика сцепления прикладного обработчика с систем-ным используется чрезвычайно широко и носит специальное название перехвата прерывания.

Обработчики программных прерываний
Программные прерывания вызываются командой int, операндом которой служит номер вектора с адресом обработчика данного прерывания. Команда int используется прежде всего, как стандартный механизм вызова системных средств. Так, команда int 2 Hi позволяет обратиться к многочисленным функциям DOS, а команды int 10h, int 13h или int 16h - к группам функций BIOS, отвечающим за управление теми или иными аппаратными средствами компьютера. В этих случаях обработчики прерываний представляют собой готовые системные программы, и в задачу программиста входит только вызов требуемого программного средства с помощью команды int с подходящим номером.
В некоторых специальных случаях, однако, программисту приходится писать собственный обработчик прерывания, которое уже обслуживается системой. Таким образом, например, осуществляется управление резидентными программами, которые для связи с внешним миром обычно используют прерывание 2Fh. В каждой резидентной программе имеется собственный обработчик этого прерывания, который, выполнив свою долю действий, передает управление "предыдущему", адрес которого находил-ся ранее в векторе 2Fh, и был сохранен обработчиком в своих полях данных. Другой пример - перехват прерываний BIOS в обработчиках аппаратных прерываний с целью обнаружения моментов времени, когда ни одна из наличных программ не использует данное прерывание и, следовательно, сам обработчик может им воспользоваться.
Наконец, прикладной программист может воспользоваться одним из свободных векторов, написать собственный обработчик соответствующего прерывания и оставить его резидентным в памяти. После этого любые программы могут с помощью команды int вызывать этот обработчик, который, таким образом, становится резидентной программой общего пользования.

Резидентные программы
Большой класс программ, обеспечивающих функционирование вычислительной системы (драйверы устройств, оболочки DOS, русификаторы, интерактивные справочники и др.), должны постоянно находиться в памяти и мгновенно реагировать на запросы пользователя, или на какие-то события, происходящие в вычислительной системе. Такие программы носят названия программ, резидентных в памяти (Terminate and Stay Resident, TSR), или просто резидентных программ. Сделать резидентной можно как программу типа.СОМ, так и программу типа.ЕХЕ, однако поскольку резидентная программа должна быть максимально компактной, чаще всего в качестве резидентных используют программы типа.СОМ.
Программы, предназначенные для загрузки и оставления в памяти, обычно состоят из двух частей (секций) - инициализирующей и рабочей (резидентной). В тексте программы резидентная секция размещается в начале, инициализирующая - за ней.
При первом вызове программа загружается в память целиком и управление передается секции инициализации, которая заполняет или модифицирует векторы прерываний, настраивает программу на конкретные условия работы (возможно, исходя из параметров, переданных программе при ее вызове) и с помощью прерывания DOS Int 21h с функцией 31h завершает программу, оставляя в памяти ее резидентную часть. Размер резидентной части программы (в параграфах) передается DOS в регистре DX. Указывать при этом сегментный адрес программы нет необходимости, так как он известен DOS. Для определения размера резидентной секции ее можно завершить предложением вида

ressize=$-main

где main - смещение начала программы, а при вызове функции ЗШ в регистр DX заслать результат вычисления выражения (rcssLze+10Fh)/16.
Разность S - main представляет собой размер главной процедуры. Однако перед главной процедурой размещается префикс программы, имеющий размер 100h байт, который тоже надо оставить в памяти. Далее, при целочисленном делении отбрасывается остаток, т.е. происходит округление результата в сторону уменьшения. Для компенсации этого дефекта можно прибавить к делимому число 15 = Fh. Деление всего этого выражения на 16 даст требуемый размер резидентной части программы в параграфах (возможно, с небольшим кусочком секции инициализации величиной до 15 байт).
Функция 31h, закрепив за резидентной программой необходимую для ее функционирования память, передает управление командному процессору COMMAND.СОМ, и вычислительная система переходит, таким образом, в исходное состояние. Наличие программы, резидентной в памяти, никак не отражается на ходе вычислительного процесса за исключением того, что уменьшается объем свободной памяти. Одновременно может быть загружено несколько резидентных программ.
Для того, чтобы активизировать резидентную программу, ей надо как-то передать управление и, возможно, параметры. Как правило, активизация резидентной программы осуществляется с помощью механизма прерываний.
Кроме того, специально для взаимодействия с резидентными программами в DOS предусмотрено мультиплексное прерывание 2Fh.
Рассмотрим типичную структуру резидентной программы и системные средства оставления ее в памяти. Как уже отмечалось, резидентные программы чаще всего пишутся в формате.СОМ:


При первом запуске программы с клавиатуры управление передается на начато процедуры main (первый байт после префикса программы). Командой jmp осуществляется переход на секцию инициализации, в которой, в частности, подготавливаются условия для дальнейшей активизации программы уже в резидентном состоянии. Последними строками секции инициализации вызывается функция ЗШ, которая выполняет завершение программы с оставлением в памяти указанной ее части. С целью экономии памяти секция инициализации располагается в конце программы и отбрасывается при ее завершении.
Содержательная часть резидентной программы, начинающаяся с метки entry, активизируется, как уже отмечаюсь выше, с помощью аппаратного или программного прерывания и заканчивается командой iret. На рис. 3.7 приведена типичная структура резидентной программы.

Рис. З.7. Структура резидентной программы.

Как видно из рис. 3.7, резидентная программа имеет по крайней мере две точки входа. После загрузки программы в память командой оператора, вводимой на командной строке, управление передается в точку, указанную в поле завершающего текст программы оператора end (на рисунке - начало процедуры main). Для программ типа.СОМ эта точка входа должна соответствовать самой первой строке программы, идущей вслед за префиксом программы. Поскольку при загрузке программы должна выполниться ее установка в памяти, первой командой программы всегда является команда перехода на секцию инициализации и установки (jmp init на рисунке).
После установки в памяти резидентная программа остается пассивной и никак не проявляет своего существования, пока не будет активизирована предусмотренным в ней для этого способом. Эта, вторая точка вызова обозначена на рисунке меткой entry.
К сожалению, резидентные программы, выполняющие полезную работу, оказываются довольно сложными. Мы же в качестве примера можем рассмотреть только совсем простую резидентную программу, в принципе правильную и работоспособную, но не претендующую на практическую ценность. Программа активизируется прерыванием от клавиши Print Screen и выводит на экран содержимое сегментного регистра CS, что позволяет определить ее положение в памяти.
Как известно, клавиша Print Screen в DOS выполняет печать содержимого экрана на принтере. Каков механизм этой операции? При нажатии на любую клавишу клавиатуры возникает сигнал прерывания, инициирующий активизацию обработчика прерываний от клавиатуры, находящегося в ПЗУ BIOS. При нажатии на алфавитно-цифровые и некоторые другие клавиши (например, функциональные клавиши ...F<12>) обработчик сохраняет в определенном месте памяти код нажатой клавиши и завершается. Текущая программа может с помощью соответствующих функций DOS или BIOS извлечь этот код и использовать его в своих целях. Если же пользователь нажимает на клавишу Print Screen, то обработчик прерываний, в числе прочих действий, выполняет команду hit 5, передавая управление через вектор 5 на обработчик этого программного прерывания, который тоже располагается в ПЗУ BIOS. Задача обработчика прерывания 5 заключается в чтении содержимого видеобуфера и выводе его на устройство печати.
Таким образом, если мы напишем собственный обработчик прерывания и поместим его адрес в вектор с номером 5, он будет активизироваться нажатием клавиши Print Screen. Обратите внимание на то обстоятельство, что прерывание 5 является прерыванием программным; оно возбуждается командой int 5 и не имеет отношения к контроллеру прерываний. Однако активизируется это прерывание не командой int в прикладной программе, а нажатием клавиши, т.е., фактически, аппаратным прерыванием.
Перехват прерывания 5 осуществляется значительно проще, через перехват "истинного" аппаратного прерывания от клавиш клавиатуры, из-за чего мы и воспользовались им в нашем примере.

Структура программы соответствует описанной ранее. В секции инициализации выполняется установка обработчика прерывания 05h, при этом исходное содержимое вектора 5 не сохраняется. Это, разумеется, очень плохо, так как лишает нас возможности этот вектор восстановить. С другой стороны, восстанавливать перехваченные векторы надлежит при за-вершении программы, а применительно к резидентной программе - при ее выгрузке из памяти. Однако в нашей простой программе не предусмотрено средств выгрузки (процедура выгрузки довольно сложна), и программе придется находиться в памяти до перезагрузки машины.
Установив вектор, программа завершается с оставлением в памяти ее резидентной части с помощью функции 31h.
Резидентная часть программы является классическим обработчиком программного прерывания. В первых же предложениях сохраняются регистры АХ и ВХ, используемые далее в программе, а затем содержимое сегментного регистра CS переносится в регистр ВХ. С таким же успехом можно было скопировать содержимое любого из регистров DS, ES или SS, так как в программе типа.СОМ все регистры настроены на один и тот же сегментный адрес (см. рис. 3.2). Копирование из сегментного регистра в регистр общего назначения понадобился потому, что в дальнейшем нам придется работать с отдельными половинками сегментного адреса, а у сегментных регистров половинок нет.
Далее старшая половина сегментного адреса заносится в регистр AL, и вызовом уже знакомой нам функции BIOS 0 Eh этот код выводится на экран. Затем таким же образом выводится младшая половина сегментного адреса. Наконец, после восстановления регистров ВХ и АХ (в обратном порядке по отношению к их сохранению) командой iret управление возвращается в прерванную программу, которой в данном случае является COMMAND.COM.
Вывод программы (ей для наглядности было дано имя TSR.COM) для конкретного прогона показан на рис. 3.8.

Рис. 3.8. Вывод программы 3-4.

Полученный результат далек от наглядности. Действительно, разделив сегментный адрес на две половины длиной в байт каждая, мы просто записали в видеобуфер эти числа. Каждое число размером в байт можно трактовать, как код ASCII какого-то символа. При выводе числа на экран эти символы и отображаются. Изображение пикового туза соответствует коду 06, а знак равенства имеет код 3Dh (см. таблицу кодов ACSII на рис. 3.1). Таким образом, сегментный адрес находящейся в памяти резидентной программы оказался равен 063Dh, что соответствует приблизительно 25 Кбайт. Так и должно быть, так как конфигурация компьютера, использованного для подготовки примеров, предусматривала хранение большей части DOS в расширенной памяти, в области НМА. В основной памяти в этом случае располагается кусочек DOS вместе с драйверами обслуживания расширенной памяти и частью программы COMMAND.COM общим объемом около 25 Кбайт.
Для того, чтобы получить на экране сегментный адрес в привычной нам форме, его двоичное машинное представление необходимо преобразовать в коды ASCII, отображающие шестнадцатеричное (или, если угодно, десятичное) представление этого числа. В нашем примере, чтобы получить на экране изображение числа 063Dh, надо было сформировать та-кую цепочку кодов ASCII (в шестнадцатеричном представлении):

30 36 33 44 68

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

3.2. Циклы и условные переходы
Циклы, позволяющие выполнить некоторый участок программы многократно, в любом языке являются одной из наиболее употребительных конструкций. В системе команд МП 86 циклы реализуются, главным образом, с помощью команды loop (петля), хотя имеются и другие способы организации циклов. Во всех случаях число шагов в цикле определяется содержимым регистра СХ, поэтому максимальное число шагов составляет 64 К.
Рассмотрим простой пример организации цикла. Пусть в программе зарезервировано место для массива размером 10000 слов, и этот массив надо заполнить натуральным рядом чисел от 0 до 9999. Эти числа, заполняющие последовательные элементы массива, иногда называют числами-заполнителями. Соответствующий фрагмент программы будет выглядеть следующим образом:

На этапе подготовки мы заносим в регистр ВХ относительный адрес начала массива, отождествляемый с его именем array, устанавливаем начальное значение индекса элемента массива в регистре SI (с таким же успехом можно бьшо взять DI) и начальное значение числа-заполнителя. Сам цикл состоит из трех команд - единственной содержательной команды засылки числа-заполнителя в очередной элемент массива (по адресу, который вычисляется, как сумма содержимого регистров ВХ и SI), а также модификации числа-заполнителя и индекса очередного элемента массива. Завершающей командой loop управление передается на метку fill, и цикл повторяется столько раз, каково содержимое СХ, в данном случае 10000 шагов.
Следует обратить внимание на команду модификации индекса - в каждом шаге к содержимому SI добавляется 2, так как массив состоит из двухбайтовых слов. Если бы нужно бьшо заполнить байтовый массив, то в каждом шаге содержимое регистра цикла SI следовало увеличивать на 1.
Стоит отметить некоторые детали, связанные с механизмом выполнения команды loop. При реализации этой команды процессор сначала уменьшает содержимое регистра СХ на 1, а затем сравнивает полученное число с нулем. Если СХ > 0, переход на указанную метку выполняется. Если СХ = 0, цикл разрывается и процессор переходит на команду, следующую за командой loop. Поэтому после нормального выхода из цикла содержимое СХ всегда равно 0.
Другое обстоятельство связано с кодированием команды loop. В ее коде под смещение к точке перехода отводится всего 1 байт. Поскольку смещение должно являться величиной со знаком, максимальное расстояние, на которое можно передать управление командой loop, составляет от -128 до +127 байт (хотя довольно трудно представить себе цикл, в котором переход осуществляется вперед). Другими словами, тело цикла ограничивается всего 128 байтами. Если циклически повторяемый фрагмент программы имеет большую длину, цикл придется организовать другим, более сложным способом:

В этом, весьма типичном фрагменте мы "вручную" уменьшаем содержимое счетчика цикла и сравниваем полученное значение с 0. Если СХ = О, это значит, что в цикле выполнено заданное число шагов, и командой условного перехода je осуществляется переход на продолжение программы (метка finish). Если СХ еще не равно нулю, командой безусловного перехода jmp осущестатяется возврат в начало цикла. Как было показано в гл. 2, команда jmp позволяет перейти в любую точку сегмента, и огра-ничение на размер тела цикла снимается.
При необходимости организовать вложенные циклы, для сохранения счетчика внешнего цикла на время выполнения внутреннего удобно воспользоваться стеком. В следующем фрагменте организуется временная задержка длительностью несколько секунд (конкретная величина задержки зависит от скорости работы процессора).

Программные задержки удобно использовать при отладке программ, чтобы замедлить их работу и успеть рассмотреть их частичные результаты; иногда программные задержки позволяют синхронизовать работу аппаратуры, подключенной к компьютеру, если скорость отработки аппаратурой посылаемых в нее из компьютера команд меньше скорости процессора.
В приведенном выше фрагменте внешний цикл выполняется 2000 раз; внутренний - 65536 раз. При счете числа шагов внутреннего цикла используется явление оборачивания, которое уже упоминалось ранее. Начальное значение в регистре СХ равно нулю; после выполнения тела цикла 1 раз команда loop уменьшает содержимое СХ на 1, что дает число FFFFh (которое можно рассматривать, как -1). В результате цикл повторяется еще 65535 раз, а в сумме - точно 64 К шагов.
Команда loop внутреннего цикла передает управление на саму себя, т.е. тело внутреннего цикла состоит из единственной команды loop. В этом нет ничего незаконного. Любая команда, в том числе и loop, требует какого-то времени для своего выполнения, и повторение 64 К раз команды loop дает некоторую временную задержку (на современных процессорах порядка тысячной доли секунды).
Перейдем теперь к рассмотрению команд условных переходов.
В приведенном выше фрагменте для реализации длинного цикла использовалась команда условного перехода по равенству je. В системе команд МП 86 имеется свыше трех десятков команд условных переходов, позволяющих осуществлять переходы при наличии разнообразных усло-вий: равенства, неравенства, положительности или отрицательности ре-зультата и проч. При выполнении всех этих команд процессор анализирует содержимое регистра флагов и осуществляет (или не осуществляет) переход на указанную метку в зависимости от состояния отдельных флагов или их комбинаций. Поскольку на состояние регистра флагов влияют многие команды процессора, командами условных переходов можно пользоваться не только после команд сравнения или анализа, но и после многих других команд, если внимательно изучить влияние этих команд на флаги процессора. Приведем несколько абстрактных примеров.

В гл. 2 отмечалось, что двоичные числа, записываемые в регистры процессора или ячейки памяти, можно рассматривать, либо как числа существенно положительные, т.е. числа без знака, либо как числа со знаком. Например, адреса ячеек, разумеется, не могут быть отрицательными. Поэтому число FFFFh, если по смыслу программы оно является адресом, обозначает 65535. Если, однако, то же число FFFFh получилось в арифметической операции вычитания 2 из 1, то его надо рассматривать, как - 1. Точно так же понятие знака бессмысленно по отношению к кодам символов, которые с равным успехом могут принимать любое значение из диапазона 0...255. С другой стороны, мы можем условно считать, что коды символов первой половины таблицы ASCII положительны, а коды второй половины таблицы (у них установлен старший бит) отрицательны, и использовать для обработки символов команды, чувствительные к знаку.
В составе команд условных переходов имеются две группы команд для сравнения чисел без знака (это команды ja, jae, jb, jbc, jna, jnae, jnb и jnbe) и чисел со знаком (jg, jge, jl, jle, jng, jnge, jnl и jnle). В аббревиатурах этих команд для сравнения чисел без знака используются слова above (выше) и below (ниже), а для чисел со знаком - слова greater (больше) и less (меньше).
Разница между теми и другими командами условных переходов заключается в том, что команды для чисел со знаком рассматривают понятия "больше-меньше" применительно к числовой оси -32К...0...+32К, а команды для чисел без знака - применительно к числовой оси 0...64К. Поэтому для первых команд число 7FFFh (+32767) больше числа S000h (-32768), а для вторых число 7FFFh (32767) меньше числа S000h (32768). Аналогично, команды для чисел со знаком считают, что 0 больше, чем FFFFh (-1), а команды для чисел без знака - меньше.
Рассмотрим пример использования команд условных переходов для обработки символов. Пусть мы вводим с клавиатуры некоторую строку символов (например, имя файла), и хотим, чтобы в программе эта строка была записана прописными буквами, независимо от того, какие буквы использовались при ее вводе. Между прочим, при вводе с клавиатуры команд DOS система всегда выполняет эту операцию, поэтому и команды, и ключи, и имена файлов можно вводить как прописными, так и строчными буквами - DOS во всех случаях преобразует все буквы в прописные.

В начале программы на экран выводится служебное сообщение "Вводите!", которое служит запросом программы, адресованным пользователю. Далее с помощью функции DOS 3Fh выполняется ввод строки текста с клавиатуры. Функция 3Fh может вводить данные из разных устройств - файлов, последовательного порта, клавиатуры. Различные устройства идентифицируются их дескрипторами. При работе с файлами дескриптор каждого файла создается системой в процессе операции открытия или создания этого файла, а для стандартных устройств - клавиатуры, экрана, принтера и последовательного порта действуют дескрипторы, закрепляемые за этими устройствами при загрузке системы. Для ввода с клавиатуры используется дескриптор 0, для вывода на экран дескриптор 1.
При вызове функции 3Fh в регистр ВХ следует занести требуемый дескриптор, в регистр DX - адрес области в программе, выделенной для приема вводимых с клавиатуры символов, а в регистр СХ - максимальное число вводимых символов. Мы считаем, что пользователь не будет вводить более 80 символов. Можно ввести и меньше; в любом случае ввод строки следует завершить нажатием клавиши . Функция 3Fh, отработав, вернет в регистре АХ реальное число введенных символов (включая коды 13 и 10, образуемые при нажатии клавиши ). В примере 3.5 число введенных символов сохраняется в ячейке actlen с целью использования далее по ходу программы.
Далее в цикле из actlen шагов выполняется анализ каждого введенного символа путем сравнения с границами диапазонов строчных русских букв. Русские строчные буквы размещаются в двух диапазонах кодов ASCII (а...п и р...с), причем для преобразования в прописные букв первого диапазона их код следует уменьшать на 20h, а для преобразования букв второго диапазона - на 50h. Поэтому анализ проводится с помощью четырех команд сравнения сmр и соответствующих команд условных переходов. Модифицированный символ записывается на то же место в буфере buf.
После завершения анализа и преобразования введенных символов, выполняется контрольный вывод содержимого buf на экран. Поскольку мы заранее не знаем, сколько символов будет введено, вывод на экран осуществляется функцией 40h, среди параметров которой указывается число выводимых символов. Так же, как и в случае функции ввода 3Fh, для функции вывода 40h в регистре ВХ необходимо указать дескриптор устройства ввода, в данном случае экрана, а в регистре DX - адрес выво-димой строки.
Коды символов являются числами без знака, и использование в данном случае команд условных переходов для чисел без знака представляется логичным и даже единственно возможным. Если, однако, внимательно рассмотреть понятия больше-меньше для чисел со знаком и без знака, то легко увидеть, что пока мы сравниваем друг с другом только "положительные" или только "отрицательные" числа, команда ja эквивалентна команде jg, а команда jb эквивалентна команде jl. Однако при сравнении, например, кодов цифр с кодами русских букв, правильный результат можно получить лишь при использовании команд переходов для чисел без знака. Впрочем, всегда нагляднее и надежнее использовать те команды, которые соответствуют существу рассматриваемых данных, даже если такой же правильный результат получится и при использовании "неправильных" команд.
Более отчетливо разница между числами со знаком и без знака проявляется при использовании арифметических операций, например, операций умножения или деления. Здесь для чисел со знаком и чисел без знака предусмотрены отдельные команды:

mul - команда умножения чисел без знака;
imul - команда умножения чисел со знаком;
div - команда деления чисел без знака;
idiv - команда деления чисел со знаком.

Поясним различия этих команд на формальных примерах.

Обе команды, mul и imul, дают в данном случае одинаковый результат, так как положительные числа со знаком совпадают с числами без знака. Не так обстоит дело при умножении отрицательных чисел.

Здесь действие команд mul и imul над одними и теми же операндами дает разные результаты. В первом примере число без знака FCh, которое интерпретируется, как 252, умножается на 4, давая в результате число без знака 3F0, т.е. 1008. Во втором примере то же число FCh рассматривается, как число со знаком. В этом случае оно составляет -4. Умножение на 4 дает FFF0h, т.е. -16.

3.3. Обработка строк
Для работы со строками, или цепочками символов или чисел (т.е. попросту говоря, с массивами произвольных данных) в МП предусмотрен ряд специальных команд:

movs - пересылка строки;
cmps - сравнение двух строк;
seas - поиск в строке заданного элемента;
lods - загрузка аккумулятора (регистров AL или АХ) из строки;
stos - запись элемента строки из аккумулятора (регистров АХ или AL).

Эти команды очень удобны, однако их использование сопряжено с некоторыми трудностями, так как процессор, выполняя эти команды, неявным образом использует ряд своих регистров. Только если все эти регистры настроены должным образом, команды будут выполняться правильно. В результате включение в программу предложения с командой, например, movs, требует иной раз 6-7 дополнительных предложений, в которых осуществляется подготовка условий для правильного выполнения этой команды.
Хотя команды обработки строк, как правило, включаются в программу без явного указания операндов, однако каждая команда, в действительности, использует два операнда. Для команд seas и stos операндом-источником служит аккумулятор, а операнд-приемник находится в памяти. Для команды lods, наоборот, операнд-источник находится в памяти, а приемником служит аккумулятор. Наконец, для команд movs и cmps оба операнда, и источник, и приемник, находятся в памяти.
Все рассматриваемые команды, выполняя различные действия, подчиняются одинаковым правилам, перечисленным ниже. Операнды, находящиеся в памяти, всегда адресуются единообразно: операнд-источник через регистры DS:SI, а операнд-приемник через регистры ES:DI. При однократном выполнении команды обрабатывают только один элемент, а для обработки строки команды должны предваряться одним из префиксов повторения. В процессе обработки строки регистры SI и DI автоматически смещаются по строке вперед (если флаг DF = 0) или назад (если флаг DF = 1), обеспечивая адресацию последующих элементов. Каждая команда имеет модификации для работы с байтами или словами (напри-мер, movsb и movsw).
Таким образом, для правильного выполнения команд обработки строк необходимо (в общем случае) предварительно настроить регистры DS:SI и ES:DI, установить или сбросить флаг DF, занести в СХ длину обрабаты-ваемой строки, а для команд seas и stos еще поместить операнд-источник в регистр АХ (или AL при работе с байтами).
Однако сама операция, после всей этой настройки, осуществляется одной командой, которая обычно даже не содержит операндов, хотя может иметь префикс повторения.
Стоит подчеркнуть, что строки, обрабатываемые рассматриваемыми командами, могут находиться в любом месте памяти: в полях данных программы, в системных областях данных, в ПЗУ, в видеобуфере. Например, с помощью команды movs можно скопировать массив данных из одной массивной переменной в другую, а можно переслать страницу текста на экран терминала. Рассмотрим несколько примеров использования команд обработки строк, ограничившись лишь теми фрагментами программ, которые имеют отношение к рассматриваемому вопросу.



Известно, что в ПЗУ BIOS, сегментный адрес которого составляет F000h (см. рис. 1.5), наряду с программами управления аппаратурой компьютера, хранятся еще и некоторые идентификаторы. Так, в восьми байтах ПЗУ, начиная с адреса F000h:FFFSh, записана в кодах ASCII дата разработки ПЗУ. В примере 3.6 выполняется чтение этой даты, сохранение ее в памяти и вывод на экран для контроля. Поскольку интересующая нас дата хранится в ПЗУ BIOS в кодах ASCII, никаких преобразований содержимого этого участка ПЗУ перед выводом на экран не требуется.
В программе осуществляется настройка всех необходимых для выполнения команды movs регистров (DS:SI, ES:DI, CX и флага DF) и одной командой movsb с префиксом rep содержимое требуемого участка ПЗУ переносится в поле bios. Перенос строки байтами подчеркивает ее формат (в строке записаны байтовые коды ASCII), однако в нашем примере, при четном числе переносимых байтов, более эффективно осуществить пере-нос по словам. В этом варианте команда movs будет фактически повторяться не 8 раз, а только 4. Для этого достаточно занести в СХ число 4 (вместо 8) и использовать вариант команды niovsw.
Для выполнения команды movs нам пришлось настроить сегментный регистр DS на сегмент BIOS. Если в дальнейшем предполагается обращение к полям данных программы, как это имеет место в примере 3-6, в регистр DS следует занести сегментный адрес сегмента данных. После этого, настроив остальные регистры для вызова функции 40h, прочитанную из BIOS строку можно вывести на экран.
В рассмотренном примере неявно предполагалось, что программа будет в дальнейшем как-то использовать полученную из BIOS информацию. Если задача программы заключается просто в выводе на экран даты выпуска BIOS, то нет необходимости сначала копировать эту дату из BIOS в поля данных программы, а потом выводить ее на экран. Можно было поступить гораздо проще: настроив регистр DS на сегмент BIOS, а регистр DX на адрес строки с датой, вызвать функцию 40h и вывести на экран текст непосредственно из сегмента BIOS. Тогда содержательная часть программы сократится в два раза и примет такой вид:

Приведенный фрагмент не имеет отношения к данному разделу, так как в нем уже нет команд обработки строк. В то же время он подчеркивает важность сегментных регистров и гибкость сегментной адресации. Функция 40h ожидает найти адрес выводимой на экран строки в регистрах DS:DX, и никакие другие регистры в этом случае использовать нельзя. С другой стороны, эти регистры можно настроить на любой участок памяти и вывести на экран (а также и на принтер, в файл или в последовательный порт) данные откуда угодно.
Рассмотрим теперь пример работы с командами lods и stos, которые можно использовать как по отдельности, так и в паре друг с другом. Эти команды очень удобны, в частности, для прямого обращения к видеопамяти.
К экрану, как и к любому другому устройству, входящему в состав компьютера, можно обращаться тремя способами: с помощью функций DOS (прерывание 21h), с использованием прерывания BIOS (для управления экраном используется прерывание 10h) и, наконец, путем прямого программирования аппаратуры, в данном случае видеобуфера (видеопамяти). Функции DOS позволяют выводить только черно-белый текст и имеют ряд других ограничений (нельзя очистить экран, нет средств позиционирования курсора); при использовании прерывания BIOS все эти ограничения снимаются, однако программирование с помощью средств BIOS весьма трудоемко; наконец, прямая запись в видеопамять, предоставляя возможность вывода цветного текста в любую точку экрана, является процедурой очень простой и, к тому же, повышает скорость вывода (по сравнением с использованием системных средств) в десятки и сотни раз. Прямое обращение к видеобуферу удобно использовать, например, в обработчиках прерываний, где запрещен вызов функций DOS и имеются ограничения на обращение к средствам BIOS.
Пусть по ходу программы необходимо вывести в нижнюю строку экрана предупреждающее сообщение. Для этого в программу надо включить следующие предложения:

Регистры DS:SI настраиваются на адрес начата выводимой строки; регистры ES:DI - на адрес требуемой позиции в видеобуфере. В регистр СХ надо поместить длину строки в байтах, а флаг DF сбросить, чтобы двигаться по строке вперед. На экран будет выводиться содержимое регистра АХ, в младшем байте которого должен находиться код ASCII выводимого символа, а в старшем байте - атрибут символа, т.е. код цвета символа (в младшем полубайте) и код цвета фона (в старшем полубайте). В примере число 31h образует синие символы по бирюзовому фону. При желании можно выбрать другую комбинацию цветов, выбрав ее с помощью табл. 3.1.


Таблица 3.1. Коды цветов стандартной цветовой палитры

Код Цвет Код Цвет
0h Черный 8h Серый
1h Синий 9h Голубой
2h Зеленый 10h Салатовый
3h Бирюзовый 11h Светло-бирюзовый
4h Красный 12h Розовый
5h Фиолетовый 13h Светло-фиолетовый
6h Коричневый 14h Желтый
7h Белый 15h Ярко-белый

Выбирая цвета, следует иметь в виду, что при стандартной настройке видеосистемы для цвета фона можно использовать лишь значения из левого столбца таблицы; выбор любого яркого цвета из правого столбца приведет в выводу мерцающего символа. Например, атрибут символа Bill образует синий мерцающий символ на бирюзовом фоне (а не синий символ на светло-бирюзовом фоне).
Содержательную часть цикла вывода образуют две команды lodsb и stosw. Первая команда загружает в регистр AL код очередного символа, вторая выводит его вместе с атрибутом, хранящемся в АН, на экран. При этом после каждого выполнения команды lodsb содержимое SI увеличивается процессором на 1, смещая адресацию к следующему символу строки; в то же время каждое выполнение команды stosw увеличивает DI на 2 (потому что команда stosw работает со словами), смещая адресацию на экране на 2 байт, т.е. как раз к позиции следующего символа.
Примеры использования команд cmps и seas можно найти в Приложении.

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

В тексте программы сначала описана процедура-подпрограмма, затем основная программа. Как уже отмечалось, порядок их описания роли не играет; важно только, чтобы в завершающей директиве окончания трансляции end был указан в качестве точки входа адрес основной программы (main в нашем примере).
Подпрограмма реализует задержку с помощью вложенных циклов с командой loop, использующей в качестве счетчика шагов регистр СХ. В основной программе этот регистр используется для организации цикла вывода трех строк. Поэтому первое, что должна сделать подпрограмма - это сохранить содержимое регистра СХ, для чего естественно использовать стек. Перед завершающей командой ret регистр СХ должен быть восстановлен. Фрагмент, реализующий задержку, был описан ранее, в разделе 3.2.
Основная программа выводит на экран с помощью функции 09h три строки текста. Для упрощения программы, а также чтобы продемонстрировать некоторые приемы программирования, вывод строк реализован в цикле. Строки сделаны одной длины, и модификация смещения к очередной строке выполняется прибавлением к содержимому регистра DX длины строки. Полезно обратить внимание на организацию цикла в основной программе. В цикл, помимо команды вызова подпрограммы задержки и предложения, модифицирующего регистр DX, включена лишь команда int 21h. Регистр АН с номером функции заново не настраивается. Это и не нужно, так как DOS, выполняя затребованную операцию, первым делом сохраняет все регистры программы, а перед возвратом в программу их восстанавливает. Поэтому, вызывая функции DOS (или BIOS) можно не заботиться о сохранении регистров - их содержимое система на разрушает. Надо только иметь в виду, что многие функции DOS и BIOS после своего завершения возвращают в программу некоторую информацию (число реально введенных символов, доступный объем памяти, номер видео-режима и т.п.) Обычно эта информация возвращается в регистре АХ, однако могут использоваться и другие регистры или их сочетания. Поэтому, обращаясь в программе к системным функциям, необходимо ознакомиться с их описанием и, в частности, посмотреть, какие регистры они могут использовать для возвращаемых значений.
Запустив программу, можно убедиться в том, что строки текста появляются на экране через заметные промежутки времени.
В примере 3-8 подпрограмма не требовала параметров. Чаще, однако, подпрограмма должна принимать один или несколько параметров и возвращать результат. В этом случае необходимо организовать взаимодействие основной программы и подпрограммы. Никаких специальных средств языка для этого не существует; передачу параметров в подпрограмму и из нее программист организует по своему усмотрению. Для передачи параметров как в одну, так и в другую сторону можно использовать регистры общего назначения, ячейки памяти или стек. Например, нетрудно преобразовать подпрограмму delay из примера 3-8 так, чтобы ей можно было передавать величину требуемой задержки. Пусть эта величина (в числе шагов внешнего цикла) передается в регистре SI.

Можно пойти еще дальше и составить подпрограмму таким образом, чтобы передаваемый в нее параметр характеризовал время задержки в секундах. Если не связываться с использованием системного таймера в качестве инструмента для определения интервала времени, а попрежнему реализовывать задержку с помощью процессорного цикла, ее величина будет зависеть от скорости работы конкретного компьютера и должна быть подобрана экспериментально. Приведенный ниже вариант подпрограммы правильно работал на процессоре Pentium с тактовой частотой 200 МГц.

Эксперименты показали, что для получения правильной задержки значение параметра, обозначающее число секунд, следует умножать на 600. Поскольку при умножении в системе команд МП 86 первый сомножитель должен находиться в регистре АХ, а второй не может быть непосредственным значением и тоже, следовательно, должен быть помещен в один из регистров, и, к тому же, произведение занимает два регистра DX:AX, приходится сохранять при входе в подпрограмму не один регистр, как в предыдущем примере, а 4. Передаваемый в SI параметр переносится в АХ, в ВХ загружается второй сомножитель, а из полученного с помощью команды mul произведения используется младшая часть, находящаяся в АХ. Таким образом, для данного варианта подпрограммы значение задержки не должно превышать 109 с (109 х 600 = 65500, что почти совпадает с максимально возможным значением 65535).
Следует обратить внимание на опасность, подстерегающую нас при выполнении операции умножения. Пусть значение передаваемого параметра составляет всего 5. При умножении на 600 получится число 3000, которое безусловно помещается в регистре АХ. Однако операция умноже-ния 16-разрядных операндов

mul BX

всегда, независимо от конкретной величины произведения, помещает его в пару регистров DX:AX, и, следовательно, при небольшой величине произведения регистр DX будет обнуляться. Поэтому, хотя мы и не используем старшую часть произведения и фактически ее может и не быть, сохранение и последующее восстановление регистра DX является обязательным.
Передача параметров в подпрограмму через регистры общего назначения или даже через сегментные регистры вполне возможна, однако на практике для передачи параметров чаще всего используют стек, хотя бы потому, что регистров немного, а в стек можно поместить любое число параметров. При этом применяется своеобразная методика работы со стеком не с помощью команд push и pop, а с помощью команд mov с кос-венной адресацией через регистр ВР, который архитектурно предназначен именно для адресации к стеку. Преобразуем пример 3-8а так, чтобы единственный в этом примере параметр (условная величина задержки) передавался в подпрограмму не через регистр SI, а через стек. Вызов подпрограммы delay в этом случае должен выполняться следующим образом:

push 2000 ;Проталкиваем в стек значение параметра
call delay ;Вызываем подпрограмму delay

Текст подпрограммы подвергнется значительным изменениям:

Команда call, передавая управление подпрограмме, сохраняет в стеке адрес возврата в основную программу. Подпрограмма сохраняет в стеке еще два 16-разрядных регистра. В результате стек оказывается в состоянии, изображенном на рис. 3.9.
После сохранения в стеке исходного содержимого регистра ВР (в основной программе нашего примера этот регистр не используется, однако в общем случае это может быть и не так), в регистр ВР копируется содержимое указателя стека, после чего в ВР оказывается смещение вершины стека. Далее командой mov в регистр СХ заносится содержимое ячейки стека, на 6 байтов ниже текущей вершины. В этом месте стека как раз находится передаваемый в подпрограмму параметр, как это показано в левом столбце рис. 3.9. Конкретную величину смещения относительно вершины стека надо для каждой подпрограммы определять индивидуально,

Рис. 3.9. Состояние стека в подпрограмме после сохранения регистров.

исходя из того, сколько слов сохранено ею в стеке к этому моменту. Напомним, что при использовании косвенной адресации с регистром ВР в качестве базового, по умолчанию адресуется стек, что в данном случае и требуется.
Параметр, полученный таким образом, используется далее в подпрограмме точно так же, как и в примере 3-8а.
Выполнив возложенную на нее задачу, подпрограмма восстанавливает сохраненные ранее регистры и осуществляет возврат в основную программу с помощью команды ret, в качестве аргумента которой указывается число байтов, занимаемых в стеке отправленными туда перед вызовом подпрограммы параметрами. В нашем случае единственный параметр занимает 2 байт. Если здесь использовать обычную команду ret без аргумента, то после возврата в основную программу параметр останется в стеке, и его надо будет оттуда извлекать (между прочим, не очень понятно, куда именно, ведь все регистры у нас могут быть заняты). Команда же с аргументом, осуществив возврат в вызывающую программу, увеличивает содержимое указателя стека на значение ее аргумента, тем самым осуществляя логическое снятие параметра. Физически этот параметр, как, впрочем, и все остальные данные, помещенные в стек, остается в стеке и будет затерт при дальнейших обращениях к стеку.
Разумеется, в стек можно было поместить не один, а сколько угодно параметров. Тогда для их чтения надо было использовать несколько команд mov со значениями смещения ВР+6, ВР+8, BP+0Ah и т.д.
Рассмотренная методика может быть использована и при дальних вызовах подпрограмм, но в этом случае необходимо учитывать, что дальняя команда call сохраняет в стеке не одно, а два слова, что повлияет на величину рассчитываемого смещения относительно вершины стека.

3.5. Двоично-десятичные числа
В гл. 2 уже говорилось о двоично-десятичных числах - специальном формате хранения данных, используемом в ряде технических приложений. Часто эти числа называют BCD-числами (от binary-coded decimal, двоично-кодированные десятичные числа). Для обработки BCD-чисел (сложения, вычитания, умножения и деления) в МП 86 предусмотрены специальные команды. Рассмотрим этот вопрос на комплексном примере обработки показаний КМОП-часов реального времени.
Как известно, в современных компьютеров имеются два независимых таймера. Один из них ("часы реального времени") включен в состав микросхемы с очень низким потреблением тока, питается от батарейки или аккумулятора, находящегося на системной плате, и работает даже на выключенной из сети машине. В этом таймере хранится и автоматически наращивается текущее календарное время (год, месяц, день, час, минута и секунда).
После включения компьютера вступает в работу другой таймер, который обычно называют системным. Датчиком сигналов времени для него служит кварцевый генератор, работающий на частоте 1,19318 МГц, сигналы от которого, после пересчета в отношении 65536:1, поступают в контроллер прерываний и инициируют прерывания через вектор 8 с частотой 18,2065 Гц. Эти прерывания активизируют программу BIOS, периодически выполняющую инкремент содержимого четырехбайтовой ячейки памяти с текущим временем, находящейся по адресу 46Ch. После включения машины программы BIOS считывают из часов реального времени текущее время суток, преобразуют его в число тактов системного таймера (т.е. в число интервалов по 1/18,2065 с) и записывают в ячейку текущего времени. Датее содержимое этой ячейки наращивается уже системным таймером, работающим в режиме прерываний.
Для определения текущего времени прикладная программа может вызвать соответствующие функции прерывания 21h DOS (конкретно, с номером 2Ah для получения даты и 2Ch для получения времени суток), а может прочитать время непосредственно из часов реального времени с помощью прерывания lAh BIOS. При этом прерывание 1А1г позволяет, помимо чтения текущего времени (функция 02h) и текущей даты (функция 04h), выполнять и целый ряд других функций, среди которых мы отметим только возможность установить "будильник", т.е. записать в микросхему часов значение календарного времени, когда часы должны выдать сигнал аппаратного прерывания. Этот сигнал через вектор 70h инициирует обработчик прерываний, входящий в состав BIOS, который проверяет, возникло ли данное прерывание в результате достижения времени установки будильника (часы реального времени могут инициировать прерывания и по других причинам), тестирует заодно батарейное питание микросхемы, а затем посылает в оба контроллера прерываний команды конца прерываний и завершается командой iret. Однако по ходу своего выполнения обработчик прерывания 70h выполняет команду hit 4Ah, которая передает управление на обработчик этого прерывания, тоже входящий в состав BIOS. Системный обработчик прерывания 4Ah ничего особенно полезного не делает, в сущности представляя собой просто программу-заглушку. Однако программист имеет возможность записать в вектор 4Ah адрес прикладного обработчика прерываний, который будет активизироваться прерыванием будильника. Функции прикладного обработчика определяет программист.
В примере 3-9 устанавливается прикладной обработчик прерывания 4All, который сам по себе вызваться никогда не будет, так как по умолча-нию будильник часов реального не работает. Если, однако, прочитать системное время с помощью функции 02h прерывания lAh, прибавить к нему некоторую величину, например, 1 секунду, и установить будильник на это время (с помощью функции 06h прерывания lAh), то через одну секунду будет активизирован наш обработчик. В примере 3-9 этот процесс сделан бесконечным: в обработчике прерываний будильника снова выполняется чтение времени, прибавление к нему 1 секунды и установка будильника на новое время. В результате наш обработчик будет вызываться каждую секунду до завершения всей программы.
Помимо служебной функции установки будильника на следующую секунду, обработчик прерываний выполняет и полезную работу: он выводит текущее время в определенное место экрана. Поскольку обработчик активизируется каждую секунду, выводимое значение времени будет обновляться каждую секунду.
Как уже говорилось, в часах реального времени значение времени хранится в виде упакованных двоично-десятичных чисел. При выполнении арифметических операций с числами BCD (а нашем случае операции заключаются в прибавлении 1) необходимо использовать предназначенные для этого команды процессора. В примере проиллюстрировано использование одной из этих команд, конкретно, команды daa.
Для того, чтобы вывести на экран значение времени, его надо преобразовать в последовательность кодов ASCII. Процедура преобразования упакованных двоично-десятичных чисел в строку символов также включена в рассматриваемый пример.

В примере 3-9 используются несколько команд, отсутствующих в МП 86: команды сохранения в стеке и восстановления всех регистров общего назначения pusha и рора, а также команда сдвига shl с числовым операндом. Для того, чтобы эти команды распознавались ассемблером, в программу включена директива.586 (можно было бы обойтись и директивой.386). В этом случае необходимо оба сегмента объявить с описателем use16.
Программа состоит из главной процедуры main, процедуры new_4a обработчика прерываний от будильника, а также трех вспомогательных процедур-подпрограмм add_time, add_unit и conv. Главная процедура сохраняет исходный вектор прерывания 4Ah, устанавливает новый обработчик этого прерывания, читает текущее время и устанавливает будильник на время, отстоящее от текущего на 1 секунду, а затем останавливается в ожидании нажатия любой клавиши. Пока программа стоит, обрабатываются прерывания от будильника и в правый верхний угол экрана каждую секунду выводится текущее время. После нажатия любой клавиши программа завершается, предварительно сбросив будильник и восстановив исходное содержимое вектора 4Ah.
Легко видеть, что в предложенном варианте программа имеет мало практического смысла, так как она не выполняет, кроме вывода времени, никакой полезной работы. В то же время, пока эта программа не завершилась, запустить другую программу нельзя, так как DOS является однозадачной системой. Если, однако, написать нашу программу в формате.СОМ и сделать ее резидентной, мы получим возможность запускать любые программы и одновременно наблюдать на экране текущее время. Такого средства в DOS нет, и в какой-то ситуации оно может оказаться полезным. Методика разработки резидентных программ описана выше; читатель может выполнить необходимые преобразования самостоятельно.
Рассмотрим теперь программу обработчика прерываний будильника. Прежде всего в нем командой pusha (push all, сохранить все) сохраняются все регистры общего назначения и, кроме того, два сегментных регистра DS и ES, которые будут использоваться в обработчике. Далее регистр DS настраивается на сегментный адрес того сегмента, в который входит ячейка hour, т.е. фактически на наш сегмент команд. На первый взгляд это действие может показаться бессмысленным. Ведь в начале процедуры main в регистр DS уже был помещен адрес нашего сегмента данных data. Зачем же эту операцию повторять? Дело в том, что процедура new_4a, будучи формально обработчиком программного прерывания 4Ah, фактически представляет собой обработчик аппаратного прерывания от часов реального времени, которое, как и любое аппаратное прерывание, может придти в любой момент времени. В принципе прерываемая программа в этот момент может выполнять любые действия, и содержимое регистра DS может быть любым. Если же говорить о нашей программе, то она находится в цикле ожидания нажатия клавиши. Этот цикл организует функция 01h DOS, которая, между прочим, время от времени обращается к своему драйверу клавиатуры, а тот - к программам BIOS ввода символа с клавиатуры. Вполне вероятно (а на самом деле так оно и есть), что при выполнении упомянутых операций используется регистр DS, который в этом случае указывает уже не на наш сегмент данных, а на различные системные области. Другими словами, при входе в обработчик прерывания содержимое регистра DS неизвестно, и его следует инициализировать заново, обязательно сохранив исходное значение. Если перед выходом из обработчика это исходное значение не восстановить, будет неминуемо разрушена DOS.
Сохранив регистры и настроив DS, мы вызываем функцию 02h прерывания lAh чтения текущего времени. Время возвращается, как уже говорилось, в упакованном двоично-десятичном формате (по две цифры в байте) в регистрах СН (часы), CL (минуты) и DH (секунды). Нам это время понадобится еще раз в конце обработчика для установки будильника заново, и чтобы второй раз не вызывать функцию 02h, полученное время (т.е. содержимое регистров СХ и DX) сохраняется в стеке.
Далее выполняется последовательное преобразование BCD-цифр, составляющих время, в коды ASCII соответствующих символов. Число часов (две упакованные BCD-цифры) переносится в регистр AL, и вызывается подпрограмма conv, которая преобразует старшую цифру часов в код ASCII и возвращает его в регистре АН. Этот код помещается в объявленную в сегменте данных строку-шаблон hour, в которой заготовлены пустые пока места для символов цифр, составляющих время, а также имеются разделительные двоеточия. Для удобства обращения к элементам этой строки, она разделена на части и каждая часть снабжена собственным именем - min для поля минут и sec для поля секунд.
Подпрограмма conv преобразования BCD-цифры в код ASCII состоит всего из трех предложений, не считая заключительной команды ret. Двух разрядное BCD-число передается в подпрограмму в регистре AL. После обнуления регистра АН, который будет служить приемником для образования конечного результата, содержимое AL сдвигается командой shl влево на 4 бит, в результате чего старший полубайт регистра AL, т.е. старшая цифра числа, перемещается в регистр АН (рис. 3.10). Двоично-десятичная цифра представляет собой просто двоичное представление цифры; прибавление к ее коду кода символа "0" (числа 30h) дает код ASCII этой цифры.
Мы преобразовали пока только старший полубайт регистра СН. Для выделения младшего полубайта на регистр СН накладывается маска 0Fh,

Рис. 3.10. Алгоритм работы подпрограммы conv.

которая обнуляет старший полубайт, не затрагивая младшего. Прибавление кода ASCII нуля к коду десятичной цифры образует код ASCII этой цифры, который и переносится затем в строку-шаблон. Описанная процедура повторяется затем для регистров CL (минуты) и DH (секунды).
Для вывода строки с временем на экран используется прямое обращение в видеопамяти. В регистр ES заносится сегментный адрес видеобуфера BS00h, а в регистр DI - требуемое смещение видеопамяти к тому месту, начиная с которого мы хотим вывести строку. В регистр SI заносится адрес строки-источника, в регистр СХ - число шагов, а в регистр АН - выбранный нами атрибут символов (красные символы по синему полю). Поскольку перемещение и по строке-шаблону, и по экрану должно осуществляться вперед, командой сld сбрасывается флаг DF. Наконец, циклическое выполнение пары команд

lodsb stosw

приводит к выводу в заданное место экрана всей строки hour.
Выполнив вывод на экран текущего времени, надо снова установить будильник. Для этого сначала запрещается работа ранее установленного будильника, восстанавливается текущее время в регистрах DX и СХ, и вызовом процедуры add_time к текущему времени прибавляется 1 секунда. Далее вызовом функции 06h заново устанавливается будильник, восстанавливаются сохраненные в начале программы обработчика регистры, и, наконец, командой iret обработчик завершает свою работу.
Рассмотрим теперь процедуру прибавления 1 к текущему времени. Она состоит из двух компонентов - подпрограммы add_time, которая организует правильное сложение чисел, обозначающих время, чтобы прибавление 1 секунды к 59 секундам дало 0 секунд и увеличило на 1 число минут (и то же самое для минут) и подпрограммы add_uuit, выполняющей прибавление 1 к упакованному коду BCD.
Подпрограмма add_time переносит число секунд из DH в AL, с помощью подпрограммы add_unit увеличивает его на 1 и возвращает в DH. Подпрограмма add_unit сигнализирует установкой флага CF о необходимости переноса 1 в следующий разряд времени (число секунд составляло 59). Поэтому после возврата из add_iuit проверяется флаг CF и, если он сброшен, т.е. следующий разряд времени модифицировать не надо, подпрограмма add_time завершается. Если же флаг CF установлен, выполняется аналогичная процедура прибавления 1 к числу минут, которое находится в регистре CL. Далее опять анализируется флаг CF, и если он установлен (текущее время было 59 мин 59 с), прибавляется 1 к числу часов. Наконец, подпрограмма завершается командой ret.
Подпрограмма add_unit получает упакованное двоично-десятичное число, к которому надо прибавить 1, в регистре AL. Командой add к нему прибавляется 1, после чего в некоторых случаях образуется правильная сумма, а в некоторых - неправильная. Так, 14h + 1 = 15h, что правильно, однако 19h + 1 = lAh, что неверно. Такого двоично-десятичного числа не существует, а после прибавления 1 к 19 должно получиться 20 (и записано в виде 20h). Коррекцию после сложения BCD-чисел осуществляет команда daa, которая в приведенном примере преобразует lAh в 20h, и которая должна всегда следовать за командой сложения.
Наши двоично-десятичные числа специфичны в том отношении, что они не могут превышать 59. Поэтому после коррекции результат сравнивается с 60h. Если сумма меньше 60h, флаг CF сбрасывается и выполняется команда ret. Если сумма равна 60h, регистр AL обнуляется, флаг CF устанавливается, сигнализируя о переносе 1 в следующий разряд времени (минут или часов) и выполняется та же команда ret. Таким образом, флаг CF процессора в точке возврата из подпрограммы add_unit говорит не о наличии или отсутствии арифметического переноса, а выполняет роль флага "исключительной ситуации" - перехода времени на следующую минуту или на следующий час. Такое нестандартное использование флага CF является общеупотребительным приемом.

3.6. Программирование аппаратных средств
Программирование аппаратуры - как штатных периферийных устройств компьютера, таких, как видеосистема, клавиатура, последовательный или параллельный интерфейс и др., так и нестандартных измерительных или управляющих устройств, подключаемых к компьютеру, если он используется для автоматизации научных исследований или управления технологическим процессом - является одним из важнейших и наиболее оправданных применения языка ассемблера. Во-первых, от программ управления аппаратурой часто требуется максимальное быстродействие. Во-вторых, эти программы, призванные управлять аппаратурой на низком уровне, путем обращения к регистрам и их отдельными битам, часто ничего не выигрывают от использования языков высокого уровня, в которых те же операции реализуются с помощью процедур языка, менее наглядных и эффективных, чем "чистые" команды процессора. В-третьих, при программировании аппаратуры, особенно, экспериментальной, важно жестко соблюдать временную и событийную последовательность команд и сигналов, воспринимаемых программируемым устройством, что естественным образом достигается при использовании языка ассемблера, в котором каждое предложение языка реализуется вполне определенной командой процессора.
В зависимости от назначения и способа функционирования аппаратуры, она может требовать различных режимов программного управления. В основном существуют три режима, или способа взаимодействия программы и аппаратуры: режим свободного доступа, режим ожидания готовности и режим прерываний.
Режим свободного доступа используется в тех случаях, когда момент обращения к устройству целиком определяется программой. Например, регистры, управляющие работой аппаратуры, обычно доступны в любой момент времени. Программа может в любой момент прочитать содержимое этих регистров и определить по нему текущий режим работы устройства, или, наоборот, послать в управляющие регистры требуемую последовательность команд с целью изменения рабочего режима.
Режим ожидания готовности необходимо использовать в тех случаях, когда после приема некоторой команды устройству требуется определенное время для ее выполнения. Например, в последовательный порт, через который компьютер связывается с другими компьютерами или телефон ной сетью, нельзя посылать следующую порцию информации (байт), пока устройствами последовательного интерфейса не будет отправлена в канат связи предыдущая порция. Режимом ожидания готовности часто пользуются для приема информации из измерительной аппаратуры, если требуется обеспечить максимальную скорость ее получения.
Режим прерываний является важнейшим способом связи с относительно медленным периферийным оборудованием. В этом случае устройство подключается не только к линиям адресов, данных и управления системной магистрали компьютера, но и к одной из специально выделенных линий прерываний. В режиме прерывания устройство само решает, когда ему требуется обслуживание, и посылкой в компьютер сигнала прерывания оповещает об этом процессор. Типичным примером является клавиатура, посылающая сигнал прерывания каждый раз, когда пользователь нажимает на ту или иную клавишу. Большая часть штатных устройств компьютера - мышь, диски, таймер и др. - используют режим прерываний. Типичен этот режим также и для связи с измерительной аппаратурой в тех случаях, когда аппаратура регистрирует относительно редкие события, или измерительные данные накапливаются в аппаратуре в течение заметного времени и затем пересылаются в компьютер сразу целой пачкой.
Как уже отмечалось в гл. 1, связь с аппаратными средствами самого компьютера, а также с подключаемыми к нему устройствами осуществляется главным образом через адресное пространство ввода-вывода. Это значит, что за каждым устройством закрепляется один или, чаще, несколько портов, и программирование устройства осуществляется исключительно с помощью команд in и out (а также ins и cuts, если программируемое устройство может посылать данные потоком).
В простейшем случае программирование устройства сводится к выполнению единственной команды in в случае чтения из устройства, или out в случае записи в него. Рассмотрим, например, процедуры маскирования и размаскирования аппаратных прерываний. В каждом из двух контроллеров прерываний, включаемых в состав компьютера, имеется регистр маски (рис. 3.11). Значение 0 в бите маски разрешает прохождение сигнала прерывания, значение 1 запрещает. Пройдя через маску и через последующие узлы контроллера прерываний (не показанные на рис. 3.11), сигнал прерываний поступает на вход INT микропроцессора. Программирование регистров маски осуществляется через порт 21h для ведущего контроллера и A1h для ведомого.
Исходное значение маски устанавливается программами начальной загрузки компьютера в зависимости от конфигурации вычислительной системы. Типичным является значение A8h, показанное на рис. 3.11. При этом значении маски размаскированными оказываются системный таймер, клавиатура, мышь, подключенная к первому последовательному порту

Рис. 3.11. Регистр маски ведущего контроллера прерываний.

СОМ1, гибкий диск, а также выход от ведомого контроллера, подключаемый ко входу IRQ2 ведущего. Замаскированы оба параллельного порта (принтер, подключаемый к порту LPT1, обычно не использует прерываний, а второй параллельный порт часто просто отсутствует) и второй последовательный порт, к которому ничего не подключено. Другими словами, размаскировано все нужное, и замаскировано все ненужное.
В раде случаев возникает необходимость замаскировать прерывания от системного таймера, который является единственным постоянно активным источником прерываний. Такая ситуация типична, в частности, для автоматизированных измерительных систем, в которых недопустимо прерывать поток данных, поступающих от измерительной установки в компьютер. Любое прерывание процесса приема данных может привесит к потере части принимаемой информации и нарушению работы установки. Для запрета прерываний от таймера надо выполнить такую последовательность команд:


or AL,1 ;Установка 1 в бите 0
out 21h,AL ;Запись нового значения маски

Восстановление исходного состояния вычислительной системы с разрешенными прерываниями от таймера осуществляется следующим образом:

in AL,21h ;Чтение регистра маски
and AL, 0FEh ;Установка 0 в бите 0
out 21h,AL ;Запись нового значения маски

Другим примером использования режима свободного доступа к устройству является программирование энергонезависимой КМОП-микросхемы, включающей в себя часы реального времени, о которых уже говорилось в разделе 5 этой главы, а также информацию о конфигурации компьютера и в некоторых случаях пароль. Общий объем КМОП-памяти составляет 64 байт (от 00h до 3Fh); доступ к байтам КМОП-памяти осуществляется через порты 70h и 71h.
В КМОП-микросхеме реализован способ обращения к ее отдельным ячейкам, широко используемый в микропроцессорной технике. Если программировать КМОП-память прямым образом, для обращения к ее 64 ячейкам в адресном пространстве ввода-вывода пришлось бы выделить 64 адреса. Для сокращения числа используемых адресов в состав микросхемы введены два служебных регистра - адресный и данных. В адресный регистр (порт 70h) заносится номер той ячейки КМОП-памяти, к которой требуется обращение. После этого чтение регистра данных (порт 7 Hi) позволяет прочитать содержимое выбранной ячейки, а запись в регистр данных выполняет передачу данного в эту ячейку. Приведем полный текст программы, которая читает содержимое ячейки с номером 0Dh. В ней хранится состояние батареи, питающей КМОП-микросхему. Если бит 7 этой ячейки установлен, батарея исправна; если этот бит сброшен, напряжение батареи упало ниже допустимого предела, и ее надо менять.

;Пример 3-10. Чтение ячейки КМОП-микросхемы
code segment
assume cs:code
main proc
mov AL,ODh ;Будем читать ячейку ODh
out 70h,AL ;Задание номера ячейки
in AL,71h ;чтение из ячейки
test AL,80h ;Проверка бита 7
jnz ok ;Бит 7 = 1, перейти на OK
mov AH,02h ;Бит 7=0, питания нет
mov DL,"-" ; Выведем в знак этого
int 21h ; Символ минус
jmp exit ;Переход на завершение
ok: mov АН,02h ;Батарея в порядке,
mov DL,"+" ;выведем в знак этого
int 21h ;символ плюс
;Завершим программу
exit: mov AX,4COOh
int 21h
main endp
code ends
end main

Рассмотрим теперь программирование периферийного оборудования в режиме ожидания готовности на примере параллельного интерфейса. В стандартной конфигурации компьютера к параллельному интерфейсу обычно подключается принтер, однако его можно использовать и для связи с нестандартным (измерительным или управляющим) оборудованием.
В компьютерах используется разновидность параллельного интерфейса под названием Centronics, отличающаяся относительно высокой скоростью передачи данных (до 150 Кбайт/с) и простотой программирования. Правда, Centronics позволяет передавать данные только в одном направлении - из компьютера в устройство, однако эту проблему можно частично решить, если воспользоваться для приема данных линиями состояния интерфейса.
Разумеется, в установке, подключаемой к компьютеру через параллельный интерфейс, должно быть предусмотрено устройство сопряжения, воспринимающее и вырабатывающее сигналы обмена с интерфейсом.
Интерфейс Centronics подключается к периферийному устройству (принтеру) с помощью кабеля, содержащего 17 сигнальных линий и несколько линий нуля. Управление интерфейсом осуществляется через три закрепленных за ним порта: порта данных с адресом 378h, порта состояния принтера с адресом 379h и порта управления принтером с адресом 37Аh. Порты фактически представляют собой 8-разрядные регистры, биты которых соответствуют сигналам интерфейса. Некоторые из этих сигналов, конкретно, сигналы портов данных и управления, являются для интерфейса выходными; их должна устанавливать программа, управляющая передачей информации. Другие сигналы, наоборот, поступают из периферийного устройства и отображаются в состоянии закрепленных за ними битов порта состояния; программа должна читать и анализировать эти биты. На рис. 3.12 показаны порты интерфейса Centronics с указанием сигналов, соответствующим конкретным битам.

378h порт данных

379h порт состояния

37Ah портуправления

Рис. 3.12. Порты интерфейса Centronics

Программирование параллельного интерфейса требует некоторых сведений о его протоколе, т.е. последовательности и взаимодействии сигналов, которыми интерфейс обменивается с подключенным к нему устройством. Некоторые из этих сигналов имеют узко специализированное назначение и возникают лишь в особых случаях (например, сигнал РЕ - конец бумаги), другие же принимают обязательное участие в процедуре передачи данных. К последним относятся 8 бит данных и три управляющих сигнала STROBE", BUSY и АСК" (рис. 3.13).



Рис. 3.13. Протокол передачи данных для интерфейса Centronics.

Сигнал BUSY считается активным, когда он имеет высокое значение. В противоположность этому активное состояние сигналов STROBE" и АСК" низкое, отчего они и обозначаются с тем или иным дополнительным значком (с чертой наверху, со знаком минус или с апострофом, как у нас). Прослеживая соответствие сигналов интерфейса состоянию битов его портов, необходимо иметь в виду, что для некоторых сигналов (SLCT, РЕ, STROBE) в порты записываются их прямые значения, а для других (ERROR, АСК, BUSY) - инверсные.
Вывод на принтер каждого байта данных состоит из трех этапов. Прежде всего программа должна дождаться неактивного состояния сигналов BUSY и АСК (это и есть ожидание готовности устройства). Убедившись, что биты 6 и 7 порта состояния 379h установлены в 1 (см. рис. 3.12), програм-ма посылает в порт данных 378h байт данных, что приводит к установке кода данных на линиях интерфейса D7...D0. Наконец, программа должна установить на короткое время сигнал STROBE, что реализуется путем установки и затем сброса бита 0 порта управления 37All. Следующие бай-ты посылаются точно таким же образом.
Выполняя все эти операции, необходимо учитывать временные ха-рактеристики интерфейса. Сигнал STROBE можно посылать в порт уп-равления не ранее, чем через 0,5 мкс после установки данных, что может потребовать введению в программу небольшой программной задержки (одной или нескольких команд jmp, см. приведенный ниже текст про-граммы). То же относится и к длительности сигнала STROBE, которая не должна быть меньше той же величины 0,5 мкс. Практически программные задержки часто оказываются не нужны.
Обратимся еще раз к рис. 3.13. Принтер, сняв с линий данных байт данных, и начав его обработку (вывод на печать или сохранение во внутренней памяти), устанавливает ответный сигнал BUSY, действующий все время, пока принтер занят обработкой байта данных. Закончив обработку байта, принтер на некоторое время устанавливает сигнал АСК и сбрасывает сигнал BUSY. Окончание сигнала АСК (при сброшенном состоянии сигнала BUSY) говорит интерфейсу об окончании данной операции обмена и о возможности посылки следующего байта данных. Ввиду краткости сигнала АСК часто оказывается, что ожидать его снятия нет необходимости; достаточно дождаться неактивного состояния сигнала BUSY (т.е. 1 в бите 7 порта состояния). Вообще следует заметить, что различные принтеры могут несколько поразному выполнять свою часть протокола обмена. Рассмотренный ниже пример отлаживался на принтере Epson LQ100.
Приведем текст программы, в которой принтер программируется, как говорят, на физическом уровне, т.е. путем обращения к его портам. Разумеется, в большинстве случаев для вывода на принтер текста из выполняемой программы проще воспользоваться функциями DOS. Однако в некоторых специальных случаях приходится прибегать и к программированию через порты, например, если принтер используется в нестандартном режиме, или параллельный интерфейс служит для связи с нестандартным устройством.
В приведенном примере предполагается, что принтер выбран и установлен в исходное рабочее состояние, что обычно выполняется автоматически при его включении. Свидетельством этого будут установленные биты 2 и 3 (SLCT IN и INIT) в порте управления, а также бит 4 (SLCT) в порте состояния. В программе не выполняется анализ байта состояния на наличие ошибки или конца бумаги, что при работе с принтером, вообще говоря, следует предусматривать.
Третий метод программирования периферийного устройства - режим прерываний - рассмотрим на примере обработки прерывания от мыши. Как известно, мышь обычно подключается к первому последовательному порту СОМ1 и работает в режиме прерываний. Нажатие или отпускание любой клавиши, так же, как даже минимальное перемещение по столу, вырабатывает сигналы прерываний, сопровождаемые определенными кодами, которые поступают в порт данных интерфейса. Написав собственный обработчик прерываний для последовательного порта, мы получим возможность выполнять заданные действия, например, при нажатии левой и правой клавиш мыши. Следует подчеркнуть, что эти действия начнут выполняться практически в тот же момент, когда мы нажали на клавишу. В приведенной ниже программе при нажатии левой клавиши в центр экрана выводится цветная надпись "Левая!", а при нажатии правой клавиши - надпись "Правая" другого цвета.
Для того, чтобы приведенная программа работала, следует загрузить драйвер мыши, который инициализирует последовательный интерфейс и саму мышь. В состав этого драйвера входит свой обработчик прерываний. Мы замещаем его адрес в векторе 0Ch адресом нашего обработчика, и поскольку в программе не предусмотрено сцепление обработчиков, на время действия программы стандартная обработка прерываний от мыши отключается. Перед завершением программы содержимое вектора 0Сh восстанавливается, и мышь опять начинает работать, как обычно.
Каждое нажатие (или отпускание) клавиши мыши, так же, как и ее перемещение, в действительности вырабатывают не по одному, а по три последовательных прерывания с различными кодами в порте данных интерфейса. Так, нажатие левой клавиши мыши дает последовательность кодов 60h, 0, 0, нажатие правой клавиши - последовательность 50h, 0, 0, отпускание любой клавиши - 40h, 0, 0, перемещение вверх - 4Ch, 0, 3Fh, перемещение вниз - 40h, 0, 1 и т.д. Таким образом, по-настоящему надо было сохранять в обработчике прерываний все три кода и затем анализировать всю последовательность. Мы для простоты ограничились анализом только первого кода. Как видно из приведенного выше перечня, анализ одного кода не дает возможность отличить, например, отпускание клавиши от перемещения вниз.
Коды, генерируемые мышью, могут зависеть от ее типа, что надо учитывать при подготовке этого примера. Для получения значений генерируемых кодов можно предусмотреть в обработчике прерываний вывод их на экран с помощью функции прерывания 10h BIOS, как это было сделано, например, в примере 3-5, или прямым выводом в видеобуфер. Следует только иметь в виду, что перехват любого прерывания от последовательного интерфейса должен обязательно сопровождаться чтением из его порта данных, так как интерфейс может принять очередной байт данных только после чтения предыдущего и освобождения своего регистра данных.

;Пример 3-12. Программирование мыши в режиме прерываний
.586 ;Будут команды новых процессоров
code segment use16 ;16-разрядное приложение
assume CS: code,DS:code ;Данные в сегменте команд
main proc
push CS ;Настроим DS
pop DS ;на сегмент команд
;Сохраним обработчик прерываний последовательного порта
mov AX,350Ch ;Функция 35h, вектор 0Сh
int 21h
mov word ptr old_Oc,BX ;Сохраним смещение
mov word ptr old_Oc+2,ES ;Сохраним сегмент
;Установим наш обработчик прерываний последовательного порта
mov AX,25ОСЬ;Функция 25h, вектор 0Сh
mov DX,offset new_0c ;Адрес нашего обработчика
int 21h
;Остановим программу функцией ввода с клавиатуры
mov AH,01h
int 21h
;Восстановим исходный обработчик драйвера мыши
mov AX,250Ch ;Функция 25h, вектор 0Сh
Ids DX,old_0c ;Сохраненный адрес
int 21h
mov AX,4C00h ;Завершим программу
int 21h
main endp
new_0c proc
pusha ;Сохраним все регистры
push DS ;Сегментные регистры не
push ES ;сохраняются командой pusha
mov DX,3F8h ;Порт данных
in AL,DX ;Прочитаем
cmp AL, 60h ;Левая клавиша - код 60h
je Ibtn ;Переход на отработку
cmp AL, 5Oh ;Правая клавиша - код 5Oh
je rbtn ;Переход на отработку
;Завершение обработчика прерываний
outret:pop ES ;Восстановим сегментные
pop DS ;регистры
mov AL,20h ;Команда EOI
out 20h,AL ;в контроллер прерываний
рора;Восстановим все регистры
iret ;Выход из прерывания
;Если нажата левая клавиша мыши
Ibtn: mov АН, 1Eh ;Атрибут символов желтый по
; синему
mov SI,offset msgdn ;Адрес выводимой строки
jmp commn ;Ha общую часть вывода
;Если нажата правая клавиша мыши
rbtn: mov AH,2Eh ;Атрибут символов желтый по
;зеленому
mov SI,offset msgdn ;Адрес выводимой строки
;Общая часть вывода на экран диагностической строки
commn: mov BX,OB800h ;Настроим ES
mov ES,BX ;на видеобуфер
push CS ;Настроим DS
pop DS ;на наш сегмент
mov CX,6 ;Число выводимых символов
mov DI,2000 ;Смещение на экране
cld ;Движение вперед
scr: lodsb ;АL=очередной символ
stosw ;Из АХ на экран
loop scr ;Цикл
jmp outret ;После вывода завершить
;обработку прерывания
new_0c endp
old_0c dd 0 ;Ячейка для исходного
;вектора
msgdn db "Левая!" ;Выводимые сообщения
msgup db "Правая"
code ends
stk segment stack
dw 128 dup(O)
stk ends
end main

Приведенный пример с точки зрения его структуры построен обычным образом. Исходное содержимое вектора 0Ch сохраняется в ячейке old_0c и используется перед завершением программы для восстановления вектора. Для упрощения установки обработчика прерываний программа написана без сегмента данных; ее немногие данные размещены в сегменте команд. Поскольку в начале программы регистр DS настраивается на сегмент команд, адресация к данным (в основной программе) возможна через DS. Для того, чтобы можно было наблюдать обработку прерываний от мыши, основная программа после выполнения инициализирующих действий останавливается с помощью функции 01h DOS ожидания ввода символа с клавиатуры. После нажатия любой клавиши программа завершается, восстановив предварительно исходное состояние вектора последовательного порта.
Действия, которые должны инициироваться нажатием левой или правой клавиш мыши (например, включение или выключение некоторого оборудования), в программе заменены выводом на экран коротких диагностических сообщений. Вывод осуществляется прямой записью в видеобуфер, поскольку, как уже говорилось ранее, в обработчике аппаратных прерываний нельзя использовать функции DOS и рискованно - функции BIOS. Вывод на экран с помощью команд обработки строк lodsb и stosw требует настройки большого количества регистров - в DS:SI должен находиться адрес строки-источника, в ES:DI адрес позиции в видеобуфере, в СХ число выводимых символов. Кроме этого, в обработчике прерываний используются регистры АХ, ВХ и DX. Для сохранения всех регистров общего назначения используется команда pusha, а для их восстановления команда рора. Однако эти команды не принимают в расчет сегментные регистры, и их приходится сохранять и восстанавливать от дельными командами.
С восстановлением регистров может возникнуть некоторая сложность. Обработчик прерывания должен завершаться посылкой в контроллер прерываний команды EOI, а для этого необходим регистр AL. Поэтому восстановление регистров, во всяком случае, регистра АХ, необходимо выполнять после команды EOI. С другой стороны, команда EOI разблокирует нижележащие уровни прерываний в контроллере прерываний (см. гл. 3), что может привести к прохождению через контроллер очередного (вложенного в наше) прерывания, которое прервет наш обработчик в точке, где еще не восстановлены регистры. Это неминуемо приведет к краху системы. Однако в процессоре предусмотрены меры устранения этого неприятного явления. Остановимся на них более подробно.
Процессор, приняв любой сигнал прерывания, сбрасывает флаг IF в своем регистре флагов, запрещая тем самым все аппаратные прерывания. Поэтому вход в обработчик прерываний всегда осуществляется при запрещенных прерываниях. Блокировка нижележащих уровней в контроллере прерываний просто накладывается на этот общий запрет и новых ограничений не вносит.
Если в тексте обработчике прерываний нет команды разрешения прерываний sti, то прерывания будут запрещены до самого его конца, до завершающей команды iret. Эта команда извлекает из стека и восстанавливает исходное содержимое регистров CS:IP, а также регистра флагов. В момент прерывания в регистре флагов был безусловно установлен флаг IF, иначе прерывание не могло бы возникнуть. Восстановление регистра флагов приводит к установке этого флага и разрешению всех аппаратных прерываний, но уже после завершения обработчика прерываний. Таким образом, снятие аппаратной блокировки прерываний командой EOI в действительности не приводит к разрешению прерываний, и любые строки, стоящие после этой команды, выполняются при запрещенных прерываниях. В результате никаких проблем с восстановлением регистров после команды EOI не возникает.
Обычно, однако, используется другой вариант построения обработчика прерываний. В этом варианте в начале программы обработчика выполняется команда sti, устанавливающая флаг IF и разрешающая все аппаратные прерывания, кроме тех, которые заблокированы в контроллере прерываний. В результате программа обработчика может быть прервана любым прерыванием более высокого уровня IRQ (т.е. уровня с меньшим номером), но не прерывается сигналами прерываний этого же и более низких уровней. Такое построение обработчиков прерываний удобно тем, что "более важные" прерывания, например, от таймера или клавиатуры, могут быть обработаны без задержки. Для того, чтобы исключить возможные неприятности с восстановлением регистра АХ после команды EOI, перед ней прерывания запрещаются командой cli и структура обработчика прерываний приобретает приблизительно такой вид:

sti
pusha ;Сохранение регистров
... ;Тело обработчика
cli ;Запрещение всех прерываний
mov AL,20h ;Команда EOI
out 20h,AL ;контроллеру прерываний
рора;Восстановление регистров
iret ;Возврат из обработчика
Команды рора и iret выполняются в этому случае при запрещенных прерываниях, но после отработки команды iret в регистре флагов восстанавливается его исходное содержимое (в котором IF = 1), и прерывания, таким образом, снова разрешаются.