Inno Setup
Далее в нашем хит-параде — Inno Setup. Это небольшой (1,1 Мб - дистрибутив, 2 Мб - в установке), но очень шустрый (а главное, бесплатный) продукт. Разработчик - Jordan Russel (http://www.jrsoftware.org/isdl.php).
Рис 7. Режим редактирования скрипта в Inno Setup
Inno Setup может стать хорошим решением для распространения совсем простых программ. Имеет 2 режима - мастер установки и редактирование скрипта (рис. 7). Позволяет показать файл лицензии, добавить ярлык нашей программы в меню Пуск и на рабочий стол, запустить программу после установки, но не может работать с реестром.
За 2 минуты (в нем действительно просто разобраться!) Inno Setup создал файл setup.exe - дистрибутив нашей программки размером 700 Кб. Но, к сожалению, он справился не со всеми пунктами поставленной задачи.
InstallShield for Windows Installer
Среди разработчиков особую популярность приобрел InstallShield for Windows Installer (см. рис. 1). У этого продукта понятный интерфейс, подсказки на каждом шагу, да и занимает он на жестком диске всего 66 Mб.
Рис 1. Секция Release в InstallShield for Windows Installer
Мастера в InstallShield for Windows Installer удобны и продвинуты; кроме того, предусмотрена возможность изменения настроек дистрибутива в следующих секциях раздела Workspace: Project (общие настройки проекта, пути, переменные проекта, строковые ресурсы инсталлятора); Setup Design (файлы, включенные в проект (см. рис. 2), пути реестра, ярлыки, регистрация COM-объектов и типов файлов, управление службами Windows NT); Sequences (последовательность инсталляции); Actions/Scripts (комментарии к действиям, добавление скриптов); User Interface (настройка интерфейса: диалогов и сообщений); Release — результат (дистрибутивы и log-файлы их создания (рис. 1); подстройка под физический носитель (сеть, компакт-диск), посредством которого будет распространяться приложение; языки интерфейса).
Рис 2. Секция Setup Design в InstallShield for Windows Installer
Мастер (wizard) создания дистрибутива (кстати, достаточно длинный: 11 шагов, в каждом из которых несколько настроек) справился с поставленной задачей. Дистрибутив InS занял 872 Mб (с компрессией, без модулей MSI).
InstallShield Professional
Наиболее весомое (267 Mб в полной установке) и наиболее сложное средство создания дистрибутивов. InstallShield Professional 6.2 (рис. 3) имеет собственный скриптовый язык, большое количество настроек и предназначен для создания дистрибутивов крупных корпоративных приложений.
Рис 3. Вкладка скриптов и файл Setup.rul в InstallShield Professional
При создании нового проекта основную работу (как и в случае с предыдущим продуктом) можно поручить мастеру - для обычного проекта или для проекта Visual Basic. Мастер задаст много вопросов, потом немного попыхтит и, в конце концов, покажет проект инсталляции, скомпилировав который, мы и получим дистрибутив.
На левой панели InstallShield Professional видны семь вкладок, каждая из которых отвечает за свою группу настроек инсталляции: Scripts - здесь находится основной скрипт процесса инсталляции - файл setup.rul, который можно создать с помощью мастера, а после редактировать вручную, отлаживать и компилировать. Скриптовый язык InstallShield немного похож на VB, но вполне поддается пониманию; File Groups - на этой вкладке в проект добавляются файлы: исполняемые, библиотеки, файлы помощи и примеров; Components - здесь перечислены компоненты проекта. Они обязательно должны включать группы файлов из предыдущего раздела; Setup Types - тут описываются типы установки (компактная, обычная, пользовательская) и то, какие компоненты из предыдущего раздела включаются в каждый тип установки; Setup Files - здесь перечислены файлы, включенные в инсталляцию, настраиваются зависимости от операционной системы и языка. Тут же можно отредактировать или заменить заставку; Resources - ресурсные файлы инсталляции: таблица переменных проекта (для каждого языка своя), записи в реестр, включение в меню Пуск => Программы => Автозагрузка, добавление объектов различных сред исполнения; Media - на этой вкладке находится результат нашей работы: дистрибутив, файлы журнала и отчета (см. рис. 4). Широко варьируется способ распространения дистрибутива: на компакт-диске, дискетах 3,5", через интернет и пр.
Рис 4. Состав дистрибутива, файлы журнала и отчета в InstallShield Professional
Размер дистрибутива InS занял 2 Mб.
Программа компании MJK Software Writers,
Программа компании MJK Software Writers, Inc (http://www.mjksw.com) сразу очаровывает приятным и нестандартным интерфейсом (рис. 8).
Рис 8. Кнопка Main Screen в Quick Install Maker 2000 Большие и с красивыми рисунками кнопки расположены удобно; ничего лишнего (кроме назойливых приглашений зарегистрироваться) нет. Четыре правые кнопки отвечают за следующие аспекты создания инсталляции: Main Screen - настройка внешнего вида инсталляции нашего приложения: фон или изображение на экране, надписи, а также начальные параметры установки (рис. 8); Install Files - включение в инсталляцию файлов, добавление ярлыков на рабочий стол и в меню Пуск * Программы; INI\REG - добавление ключей реестра или INI-файла, строк в файлы autoexec.bat и config.sys; Disk Builder - создание дистрибутива, его архивирование и копирование на дискеты (рис. 9).
Рис 9. Кнопка Disk Builder в Quick Install Maker 2000 Демо-версия Quick Install Maker 2000, которую можно загрузить с сайта производителя, весит 2,2 Мб, а установка программы занимает 2,8 Мб. Дистрибутив InS занял 754 Kб, с поставленной задачей справился полностью. Правда, при установке несколько раз сообщил о том, что он не зарегистрирован и вообще является демо-версией. Конечно, инсталляторов существует намного больше, чем рассмотрено в этой статье. Бесплатные и условно-бесплатные продукты различных компаний и отдельных разработчиков можно загрузить с сервера или . Если уж на то пошло, инсталлятор можно написать самостоятельно. document.write('');
|
This Web server launched on February 24, 1997 Copyright © 1997-2000 CIT, © 2001-2009 |
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. |
Предлагаем на продажу новейшие модели грузовиков по очень интересным ценам, приезжайте! |
Семейство InstallShield
Одним из гигантов производства инсталляторов (причем гигантов в мировом масштабе) признана компания InstallShield. На ее сайте (http://www.installshield.com) представлена целая линейка этих продуктов - различных по сложности и стоимости (кстати, после регистрации можно получить пробную 30-дневную версию:
InstallShield Developer (поддержка распараллеленной установки, интеграция с Visual Studio .NET, создание патчей, визуальный редактор диалогов, контроль исходного кода); InstallShield Professional (скриптовый язык, средства отладки, многочисленные настройки пользовательского интерфейса, возможность создания дистрибутивов под Web); InstallShield Express - облегченная версия, простая в использовании, поставляется с Delphi 5 и Visual Studio 6; InstallShield MultiPlatform (поддержка Linux, Solaris, HP-UX, AIX, OS/2, Windows и OS/400, функционирование на основе Java VM); InstallShield AdminStudio (системное администрирование, управление рабочим процессом, разрешение конфликта приложений, поддержка модулей MSI (Microsoft Installer)); InstallShield DemoShield - средство создания итерактивных презентаций, каталогов на компакт-диске; InstallShield Update Service - инструмент для создания и управления обновлениями программного обеспечения на компьютерах клиентов; InstallShield Package for the Web - средство для распространения приложений через интернет, для доставки и цифровой подписи интернет-модулей.
Такие разные инсталляторы
Татьяна Михно,
Конечный пользователь — нежен и привередлив, ему приятно, когда программа сама устанавливается на его компьютер и радостно сообщает о своей готовности к работе
Поговорим о верных помощниках разработчика - инсталляторах, программах, которые умеют создавать дистрибутив приложения. Дистрибутивы обычно занимаются установкой приложения на компьютер пользователя, а в случае необходимости - переустановкой или удалением.
Для сравнения инсталляторов воспользуемся приложением Ins - это простенький текстовый редактор, за 10 минут созданный в MS Visual C++, MFC. Он состоит из двух файлов: C:\InS.src\ins.exe и C:\InS.src\mfc42.dll. Чтобы корректно установить его на другой компьютер, нужно: скопировать исполняемый файл ins.exe на жесткий диск; скопировать в системный каталог Windows файл библиотеки MFC mfc42.dll; прописать в системном реестре каталог установки и текущий каталог редактора; создать каталог С:\Мои документы - текущий каталог редактора по умолчанию; создать ярлык для запуска Ins на рабочем столе и в меню Пуск => Программы панели задач.
Несмотря на то что опытный программист может проделать это без посторонней помощи, давайте все же проследим, как с этой задачей справятся различные инсталляторы.
Wise InstallMaster
Wise InstallMaster 8.1 - произведение компании Wise Solutions (http://www.wise.com) - обладает не меньшей функциональностью, чем предыдущий продукт. Однако его интерфейс (рис. 5) более понятен простому человеку.
Рис 5. Начало создания дистрибутива в Wise InstallMaster
Процесс создания дистрибутива разбит на 6 этапов:
1. files and components - задается список файлов и компонент, составляющих наше приложение. В этом разделе нужно указать, откуда и какие файлы помещать в дистрибутив, куда их класть при инсталляции. Задаются также настройки для патчей, деинсталляции, шрифтов, сред исполнения (runtime) Visual Basic, Visual Foxpro, BDE, Crystall Reports, Windows и баз данных;
2. system additions - в этом разделе задаются настройки для иконок, ключей реестра, INI-файлов и регистрации типов файлов Windows. Здесь же добавляются службы Windows NT и устройства Windows 3.1х и 9х, необходимые для работы приложения. Кроме того, задаются изменения, которые необходимо добавить в файлы autoexec.bat и config.sys, а также информация о том, в каком каталоге создавать log-файл инсталляции нашего приложения;
3. user system checks - этот раздел отвечает за системные требования нашего приложения к компьютеру пользователя и ранее установленные версии нашего приложения;
4. wizard appearance - в этом разделе описывается, как будет выглядеть процесс инсталляции. Редактированию поддаются фон и диалоговые окна, можно добавить свою рекламу, которая будет показываться в процессе инсталляции;
5. advanced functionality - в раздел включены возможности защиты дистрибутива паролем, online-регистрации и поддержки Windows CE;
6. finish - здесь указывается, в каком виде будет создан дистрибутив (в одном файле или в нескольких), и создавать ли CAB-файл. В этом же разделе находятся настройки для распространения приложения через интернет, контроля версий и специальные настройки для установки и удаления в Windows 2000.
Скрипт создания дистрибутива в Wise можно редактировать, как показано на рис. 6.
Рис 6. Редактирование скрипта в Wise InstallMaster
Дистрибутив приложения InS, созданный в Wise, занял 600 Kб.
Следует заметить, что с сайта компании-изготовителя можно загрузить не только 30-дневную демонстрационную версию инсталлятора под Windows, но и надстройки для нескольких сред исполнения (runtime) и руководство пользователя. Дистрибутив Wise InstallMaster 8.1 занимает 9 Мб, а после установки на жесткий диск - 15,5 Мб.
Абстрактная модель акселератора
В нашей модели акселератор является сопроцессором, команды которому выдаются основным процессором. Акселератор работает синхронно с процессором. Акселератор может иметь собственную локальную память и имеет доступ к общей памяти между процессором и всеми акселераторами.
Ассемблерный синтаксис команд акселератора
Отдельная секция файла описания модели акселератора отвечает за опреде-ление синтаксиса ассемблера для системы команд этого акселератора. Более точно, эта секция определяет синтаксис для подмножества системы команд основного процессора, соответствующего командам запуска инструкций этого акселератора (см. 2.1.3.1). Это полезно для адекватного отра-жения на уровне ассемблера соответствующей семантики команд, так как одни и те же (в смысле машинных кодов) команды основного процессора (для запуска акселераторов) могут иметь разную семантику, в зависимости от конкретной конфигурации акселераторов в системе, специфичной для каждого заказчика. В дополнение к определению семантики команд акселераторов (см. 2.2), воз-можность настраивать синтаксис для этих команд является важной функцио-нальностью рассматриваемой системы, позволяющей легко получать готовую для производственного использования кросс-систему, адаптированную для специфичной конфигурации "процессор + акселераторы пользователя".
Описание ассемблерного синтаксиса системы команд состоит из трех секций:
Секция типов операндов и псевдонимов Секция команд Секция ограничений (constraints)Детальное рассмотрение средств описания синтаксиса команд выходит за рамки данной статьи. Ниже приводится только краткий обзор основных возможностей.
Дескриптор структуры памяти
Существуют отдельные синтаксические конструкции для описания следующих типов областей памяти:
Памяти данных Регистровые файлы Одиночные регистрыС помощью этих конструкций можно задать дескриптор структуры памяти
(см. 2.1.1.1).Пример. Структура памяти простого акселератора:
две памяти данных LDM и TM размером 2048 слов каждая с разрядностью 16 и 64 бита со скоростью доступа 3 такта Регистровый файл GRF из двух 16-ти разрядных регистров GR0 и GR1 Одиночный регистр-аккумулятор ACR разрядностью 36 бит DECLARE_MEMORY(INT(16, 3), 2048) LDM; DECLARE_MEMORY(INT(64, 3), 2048) TM; DECLARE_REGISTERS_FILE(INT(16), 2) GRF; DECLARE_REGISTER(UINT(36)) ACR; // debugging names and registers file structure MEMORY(LDM, "Acc LDM"); MEMORY(TM, "Acc TM"); REGFILE_BEGIN(GRF, "General Registers") REGISTER(0, "GR0"); REGISTER(1, "GR1"); REGFILE_END()Динамическая настройка кросс-системы
В данной главе приводится краткий обзор нашей технологии динамической настройки кросс-системы для работы с моделями акселераторов, заданных в соответствии с 2.2 и 2.3 в виде файлов описания акселераторов.
Динамическая поддержка расширений процессора в кросс-системе
В.В. Рубанов, А.И. Гриневич, Д.А. Марковцев, М.А. Миткевич
Труды
Конфигурация системы
Под процессом конфигурации системы подразумевается определение конкретного набора акселераторов (типа и номера каждого акселератора). Тип акселератора (конкретная модель) определяется файлом спецификации на языке ISE. Интегрированная среда позволяет задать упорядоченный список акселераторов (файлов описания акселераторов), который и определяет конфигурацию системы, используемую для настройки соответствующих компонентов кросс-инструментария.
В рамках одной сессии интегрированной среды пользователь может много-кратно менять как конфигурацию системы, так и сами файлы описания акселе-раторов (и, конечно же, прикладную программу). Однако изменение конфигу-рации невозможно в режиме отладки, когда работает симулятор. Для смены конфигурации сессия отладки должна быть остановлена. Для редактирования файлов описания акселераторов может быть использован визуальный front-end, который поддерживает средства анализа и верификации спецификаций (напри-мер, обнаружение конфликтующих машинных кодов для разных команд).
Модель акселератора
Операцией [элементарной] называется, заданная на множестве состояний памяти акселератора {s}A (см. 2.1.1.1) функция ?, формирующая следующее состояние памяти акселератора на основе предыдущего. Операции соответствуют действиям, которые могут быть выполнены за один такт (например, сложение двух регистров):
? : {s}A -> {s}A
Множество операций акселератора обозначим ?A={?}. Для любого акселератора в этом множестве присутствует так называемая пустая операция, не изменяющая состояние и обозначаемая ?0.
Каждая элементарная операция характеризуется функциональными ресурсами, которые необходимы для выполнения этой операции. Обозначим множество всех ресурсов акселератора как RA, а множество всех его подмножеств (включая пустое) как pA. Функция rA, отображающая множество операций ?A на множество pA, называется функцией ресурсов. Она задает набор ресурсов для каждой операции в ?A:
rA : ?A -> PA
Обозначим множество всех подмножеств в ?A, которые состоят из операций, использующих не пересекающиеся ресурсы, как ?A:
Таким образом, элемент множества ?A задает группу операций {?i}, которые могут выполняться параллельно (в рамках одного такта). Применением параллельной композиции (см. выше) этот элемент задает функцию на множестве состояний памяти акселератора {s}A. Далее элементы множества ?A будем отождествлять с задаваемыми ими функциями и называть комплекс-операциями. Заметим, что в этом смысле
.Определим два управляющих действия: продвижения next и окончания end. Каждое представляет собой параметрическую функцию на множестве управляющих состояний акселератора. Параметром функции является номер слота
. Действие next увеличивает на 1 значение поля состояния команды t для слота, заданного параметром nl.Действие end переводит соответствующий слот в холостое состояние.
Множество из этих управляющих действий обозначим UA={next,end}.
Дескриптором команды акселератора называется функция f, вычисляющая пару из комплекс-операции
и управляющего действия на основании состояния памяти акселератора и состояния команды (см. 2.1.1.2):Множество дескрипторов команд акселератора обозначается IA={fi}. Это множество конечно и каждому элементу f приписывается номер (например, в порядке возрастания машинного кода в соответствии с отображением, задаваемым функцией декодирования, см. ниже). При этом для дескриптора команды всегда верно следующее (единственность конца команды):
Дескриптор команды f однозначно задает функцию потактового поведения команды bf, определенную на множестве состояний акселератора и параметризуемую номером слота nl. Значение функции bf не определено, если значение поля номера команды в соответствующем слоте nl не совпадает с номером соответствующего дескриптора f. В ином случае функция bf(nl):{aA} -> {aA} задается следующим образом:
На основании состояния памяти s и значения поля состояния команды t в слоте c номером nl c помощью дескриптора команды определяется пара из комплекс-операции ? и управляющего действия u: {?,u} = f{s,t}
C помощью ? определяется следующее состояние памяти s`=?(s), а управляющее действие задает следующее управляющее состояние p`=u(nl,p). Пара (s`,p`) задает значение bf(nl,s,p). Таким образом, множество дескрипторов команд однозначно задает множество функций потактового поведения BA, имеющее столько же элементов.
Множество кодов инструкций акселератора CA={ci} представляет собой множество двоичных чисел одинаковой разрядности. Каждый элемент этого множества соответствует одному из возможных значений поля код инструкции акселератора в машинном коде команды запуска акселератора (см. 2.1.3.1).
Функция декодирования dA задает отображение множества кодов инструкций акселератора CA на множество дескрипторов команд IA:
dA:CA -> IA
Функция декодирования однозначно задает декодер акселератора - функцию DA:
DA:CAx{p}A -> {p}A:
Функция DA определяется следующим образом: если , то , иначе значение DA не определено. Иными словами, в случае наличия свободного управляющего слота декодер инициирует состояние этого слота в (f,1), где f определяется по коду инструкции с помощью функции декодирования dA.
Тактовая функция TA на множестве состояний акселератора {a}A, определяет изменение состояния акселератора на каждом такте:
TA:{a}A -> {a}A
Эта функция однозначно задается параллельной композицией (см. 2.1.1.3) функций потактового поведения bf, соответствующих дескрипторам команд f, заданным в поле nf каждого активного управляющего слота. Иными словами для каждого такта управляющее состояние акселератора определяет набор активных команд, характеризуемых функциями потактового поведения. Параллельная композиция этих функций задает отображение TA (поведение акселератора) для текущего такта. Если активных слотов нет, то состояние акселератора не меняется.
Абстрактная модель акселератора MA определяется следующими описанными выше элементами:
MA={SA,PA,CA,DA,TA}
Моделирование расширений процессора
В данной главе рассматриваются вопросы моделирования расширений процес-сора в виде акселераторов. Для этого вводится абстрактная (математическая) модель акселератора, охватывающая достаточно широкий класс возможной аппаратуры (см. 2.1). В рамках этой абстрактной модели определяются сред-ства описания конкретных моделей акселераторов в виде файлов на специа-льном языке ISE (см. 2.2). Описанные таким образом модели акселераторов используются для автоматической конфигурации компонентов кросс-системы - симулятора, ассемблера, дисассемблера, отладчика и профилировщика (см. главу 3).
Настройка ассемблера/дисассемблера
Для обеспечения динамической настройки ассемблера на новые команды акселераторов был разработан "универсальный" ассемблер, интерпрети-рующий описание синтаксиса системы команд и отображения в машинные коды, заданное в соответствии с 2.3, в процессе ассемблирования входной программы. Заметим, что часть системы команд основного процессора, не относящаяся к командам запуска акселераторов, также задается в виде 2.3. Таким образом, кроме собственно текста прикладной программы, входной информацией для ассемблера является набор файлов описания для системы команд ядра процессора и всех акселераторов системы. Объединение этих описаний задает общий ассемблерный синтаксис всех команд системы "процессор + акселераторы".
В конкретной реализации, информация о синтаксисе извлекается ассемблером либо из указанных в параметрах командной строки библиотек DLL или непосредственно из файлов описания акселераторов. При этом описание системы команд ядра основного процессора зашито в самом ассемблере, так как она не изменяется пользователем.
Заметим, что в текущей версии динамически настраиваемым является только синтаксис отдельных команд. Общий синтаксис ассемблерного файла фиксирован:
Секции Объявление переменных Выражения Конструкции для макропроцессора Отладочная информация С-компилятораДисассемблер извлекает информацию о синтаксисе команд из набора файлов описания также динамически во время своей работы (in run-time).
Настройка отладчика
Под настройкой отладчика подразумевается настройка соответствующих окон для отображения состояния памяти акселераторов во время симуляции, включая разбиение на именованные области. С помощью отладчика пользователь также может вручную изменять значения определенных ячеек.
Во время старта отладочной сессии отладчик считывает информацию о структуре памяти акселераторов (см. 2.2.1) из соответствующих библиотек DLL или непосредственно из файлов описания акселераторов. Также для настройки подсветки синтаксиса ассемблерных команд в редакторе используется инфор-мация из синтаксической части (см. 2.3). Оттуда же берет информацию и один из профилировщиков (а именно, профилировщик покрытия системы команд).
Настройка симулятора
Для настройки симулятора используется информация из файла описания акселератора, соответствующая элементам, описанным в 2.2.
В существующей реализации для настройки симулятора используется два альтернативных подхода:
Компиляция файла описания акселератора внешним компилятором C++ Интерпретация файла описания во время работы (run-time)В первом случае файл описания акселератора транслируется внешним компи-лятором в динамическую библиотеку DLL. Заметим, что в этом случае синтак-сис, описанный в 2.2, трактуется как макросы, определения переменных (2.2.1) и функции (2.2.2) C++. Функция-декодер задается таблицей соответствия шаблонов машинных команд и указателей на функции команд (2.2.2.3). Инфор-мация о синтаксисе команд (2.3) не используется симулятором и для компиля-тора в этом случае представляется в виде строки инициализации специальной глобальной переменной для использования ассемблером и дисассемблером.
Программный интерфейс (API) этой библиотеки DLL содержит набор функций для извлечения информации об акселераторе. Память акселератора симулируется в виде переменных и массивов в адресном пространстве DLL. Функции поведения команд акселератора (включая операции) компилируются в исполняемый код хост-машины. Симулятор основного процессора обращается к соответствующим функциям DLL для выдачи команд акселе-ратора и инициировании очередного такта (тактовый генератор находится в отладчике). Для синхронизации потактового выполнения команд основного процессора и всех акселераторов используется модель нитей с ручным переключением контекста. Такие возможности предоставляются в операционной системе Windows в виде примитивов Fibers. Для платформы UNIX используется библиотека QuickThreads (David Keppel, 1993). Директива FinishCycle() в этом случае вызывает явное переключение нитей (fibers).
В случае отсутствия внешнего компилятора C++, используется другой подход, когда функции поведения команд интерпретируются внутренней виртуальной машиной во время симуляции. Однако в этом случае существует ряд ограни-чений на использование конструкций и типов языка C++ при описании модели акселератора, так как не все возможности поддерживаются интерпретатором.
Ограничения
2.3.2.1. На отдельные операнды
Ограничения на отдельные операнды определяются соответствующим типом, к которому принадлежит операнд. Однако для наглядной генерации ошибок можно задать более широкий тип для операнда и воспользоваться механизмом общих ограничений (см. 2.3.2.2) для выделения допустимых значений.
2.3.2.2. На взаимосвязь операндов командыОграничения на взаимосвязь операндов команды задаются в виде набора логических высказываний относительно сравнений арифметических выражений, которые могут содержать значения любых использованных в команде операндов. Считается, что ограничения выполнены, если все логические высказывания для данной инструкции принимают значение "истина".
Выражения ограничений поддерживают следующие операции:
Логические операции | &&, , ! |
Операции сравнения | , =, ==, <> |
Арифметические операции | +, -, *, /, % |
Битовые операции | |, &, ^, ~ |
В выражениях могут быть использованы числовые константы и, собственно, ссылки на операнды. Если несколько операндов имеют одинаковый тип, то следует использовать псевдонимы типов для различения операндов в выражениях ограничений. Пример. Операнды 1 и 2 не должны быть равны:
ADD {GRs#8;4}, {GRt#4;4} % 0x796000 0xf9f000 (GRs <> GRt) % "Operands must be different for ADD"Заметим, что с помощью описанного механизма также можно задавать ограничения на отдельные операнды. Таким образом, возможны две стратегии работы с ограничениями на значения операндов: с одной стороны можно создать систему общих типов и затем сужать множества значений с помощью логических высказываний, а с другой работать с большим количеством специфических типов. В первом случае можно достичь более конкретных сообщений об ошибках в операндах инструкции, во втором - упрощается описание системы команд. Пользователь может выбрать любую стратегию.
2.3.2.3. На комбинации командКаждой команде можно назначить некоторый набор свойств и задать для них значения и области активации. В качестве значения свойства может выступать либо константа, либо значение одного из операндов команды.
Область активации задает диапазон соседних команд, на котором данное свойство активно. Область активации по умолчанию [1;1] затрагивает только текущую команду. Механизм описания свойств (дополненный предикатами совместимости - см. ниже) фактически является модифицированным описанием таблиц использования ресурсов (reservation tables).
Пример:
MAC {acr#14;1},{grs#4;4},{grt#0;4} % 0x6c2000 0xffb800 [read_grn:grs, read_grn:grt, write_acr:acr:2;2]
Данная команда обладает следующими свойствами:
read_grn - двойное свойство со значениями равными значениям операндов grs и grt. Область активации по умолчанию затрагивает только текущую команду (здесь означает, что значения регистров, заданных операндами grs и grt, читаются на первом такте).
write_acr - значение свойства равно значению операнда acr. Область активации [2;2] затрагивает следующую команду (здесь означает, что значение acr будет записано на втором такте).
На механизме описания свойств базируется способ задания ограничений на использование ресурсов. Задается список предикатов совместимости свойств. Предикат совместимости свойств задает в квадратных скобках набор из пар свойств (пары разделяются запятыми, свойства в паре знаком =). Предикат истинен для пары команд, когда выполняются следующие условия: первая команда обладает всеми свойствами из левых частей пар, вторая обладает всеми свойствами из правых частей пар; при этом для каждой пары значения свойств совпадают на пересечении их областей активации. Предикаты совместимости оцениваются ассемблером для всех пар команд при ассемблировании программы, таким образом обнаруживаются конфликтующие команды. Заметим, что оценки работают гарантированно корректно только на линейных участках.
Пример:
[write_acr=read_acr] % warning: "WAR conflict for ACRs"
Данный предикат будет верен, если у пары команд значение свойства write_acr первой команды совпадет со значением свойства read_acr второй команды на пересечении областей активации этих свойств. В данном примере это отражает конфликт по данным (по аккумуляторным регистрам) типа WRITE AFTER READ.
Зарезервировано специальное свойство "any", которым по умолчанию обладает любая инструкция. [any=X] дает истинный предикат, если вторая инструкция обладает свойством X (независимо от его значения).
Описание поведения
К семантике поведения акселератора относятся следующие элементы модели mA: множество ресурсов RA, функция ресурсов rA, множество операций ?A, множество дескрипторов команд IA и функция декодирования dA (вместе с CA).
2.2.2.1. ОперацииДля задания операций из ?A используется язык C++. Ячейки памяти акселератора доступны в качестве глобальных переменных (регистровые файлы и памяти в виде массивов). Для удобства описания могут объявляться собственные локальные переменные. Также могут быть использованы возможности специальной библиотеки (например, N-битные типы данных INT< N>, UINT< N>, типы данных с фиксированной точкой FIXED< I,F>, операции битовых манипуляций и т.п.). Используемые в операции ресурсы обозначаются в виде вызова функции UseResources(resources) (тем самым задается функция rA). В существующей реализации список используемых в данной операции ресурсов передаются в виде битовой строки, где каждый ресурс соответствует определенному биту. Множество ресурсов RA задается в виде перечисления (enum) со значениями элементов по степеням двойки:
enum Resources {MAC_ADDER=1, MAC_MULTIPLIER=2, ALU_ADDER=4};Пример 1. Операция по сложению двух 36-ти разрядных чисел:
void ADD_36_36(INT& res, INT a, INT b) { UseResources(MAC_ADDER); res = a + b; }Пример 2. Операция по перемножению двух 16-ти разрядных знаковых чисел:
void SMUL_16_16(INT& res, INT a, INT b) { UseResources(); res = a * b; }Заданная на C++ операция может быть оформлена в виде отдельной функции (см. примеры выше) или встраиваться непосредственно в тело функции поведения команды (см. примеры в 2.2.2.2).
2.2.2.2. Дескрипторы командДескрипторы команд акселератора из IA задаются соответствующими функциями поведения команд. Функция поведения может принимать аргументы в виде параметров инструкции pi. Тем самым одна функция поведения может описывать набор дескрипторов (один дескриптор соответствует одному конкретному набору значений параметров). Тело функции поведения может описываться на языке C++.
Отображение в операции для соответствующих значений состояния команды t неявно задается путем использования специальной функции FinishCycle(). Вызовы данной функции отделяют операции внутри функции поведения, относящиеся к последовательным тактам исполнения (значениям параметра t дескриптора команды). Для описания динамического характера выбора операций в зависимости от состояния акселератора (аргумент дескриптора s) в описании функции поведения команды допускается использование управляющих конструкций языка C, в частности циклов и ветвлений (см. пример 3 ниже). Вызов функции FinishCycle() означает окончание всех операций для текущего такта команды и соответствует управляющему действию next. Возврат из функции поведения команды соответствует управляющему действию end. Использо-вание такого решения позволяет эффективно описывать дескрипторы команд, тем самым определяя потактовое поведение команд акселератора.
Пример 1. Однотактовая команда перемещения между регистрами, содержащая единственную операцию, задаваемую конструкцией
GRF[greg] = LRF[lreg]: ACC_FUNCTION Move_LREG_GREG(INT lreg, INT greg) { GRF[greg] = LRF[lreg]; FinishCycle(); }
Пример 2. Двухтактовая команда перемножения и аккумуляции результата. На первом такте происходит перемножение операндов (операция SMUL_16_16 - см. пример 2 в 2.2.2.1), на втором аккумуляция результата (операция ADD_36_36 - см. пример 1 в 2.2.2.1):
ACC_FUNCTION MAC_LREG_GREG(INT grs, INT grt) { SMUL_16_16 (mulres, GRF[grs], GRF[grt]); FinishCycle(); ADD_36_36 (ACC, ACC, mulres); FinishCycle(); }
Заметим, что две выдачи подряд этой команды процессором приведут к ситуации, когда одновременно будут исполняться две различные стадии этой функции (стадия умножения второй команды и стадия сложения первой команды). Такой эффект может быть использован для моделирования конвейера акселератора.
Пример 3. Команда свертки векторов, расположенных в памятях DM0 и TM0. Длительность команды зависит от данных (длина векторов задается регистром LOOPREG).
Заметим, что в теле цикла за один такт выполняются несколько операций, использующих непересекающиеся ресурсы. Для синхронизации с процессором используется механизм прерывания:
ACC_FUNCTION CONV_ACC_DM0_TM0(INT dreg, INT treg) { SMUL_16_16 (mulres, DM0[AR[dreg]++], TM0[AR[treg]++]); FinishCycle(); while (LOOPREG>0) { ADD_36_36 (ACC, ACC, mulres); SMUL_16_16 (mulres, DM0[AR[dreg]++], TM0[AR[dreg]++]); LOOPREG--; FinishCycle(); } ADD_36_36 (ACC, ACC, mulres); InterruptProcessor(); FinishCycle(); }
2.2.2.3. Функция декодирования
Функция декодирования dA задается описанием множества пар из формата машинного слова команды и ссылки на функцию поведения команды:
INSTRUCTION(< format_string>, < invoker_name>);
Формат машинного слова команды задается строкой в следующем алфавите:
Битовые символы: '0' и '1' Параметрические символы: 'A-Z' и 'a-z' Групповой символ: '*' Разделительный символ: '-'
Символы из пунктов 1-3 называются значимыми символами. Заметим, что число значимых символов в строке формата команды должно быть равно разрядности машинного слова в системе.
Непрерывная цепочка параметрических символов задает операнд. Декодер акселератора выделит указанные биты и передаст полученное значение в функцию поведения команды в виде параметра pi. Различные операнды разделяются групповым или разделительным символом. Операнды нумеруются в порядке справа налево.
Битовые символы задают фиксированные значения в соответствующих позициях машинного слова. На месте параметрических и групповых символов в машинном коде команды может быть любое битовое значение. Разделительные символы используются для косметических целей, а также для отделения подряд идущих операндов.
Пример:
INSTRUCTION("11-**-0000-0000-0001-LREG-GREG", Move_LREG_GREG);
Функция поведения MoveLREG_GREG (см. пример 1 в 2.2.2.1) имеет два параметра по 4 бита каждый (LREG[4;7] и GREG[0;3]). Биты [20;21] могут принимать любые значения для данной команды (в данном примере эти биты относятся к коду акселератора и используются командой запуска акселератора основного процессора).Остальные биты фиксированы и составляют КОП инструкции аскелератора.
Заметим, что совокупность всех строк форматов машинного слова задает множество допустимых кодов инструкций данного акселератора (CA в 2.1.2).
Число управляющих слотов NS задается директивой SLOTS(< NS>).
Отображение ассемблерных команд в машинное слово
Общий шаблон допустимого ассемблерного синтаксиса для команд акселератора задается в виде:
command ::= mnemonic [parameter {, parameter}*] { mnemonic [parameter {, parameter}*]}* mnemonic ::= const_string parameter ::= operand {[const_string] [operand]}* operand ::= const_string const_string ::= любой текст без запятых и пробеловПримеры возможных команд:
DMOVE ACR1.h, DM0(DA0--), ACR1.L, TM0(TA0++) MOVE GRA, DM1(TA0+25) ADD GR3, ACR2.HНа языке ассемблера команда состоит из мнемоники (нескольких мнемоник для параллельных команд) и набора параметров, разделенных запятой. Каждый параметр может содержать несколько частей - операндов, принадлежащих к каким-либо из описанных типов. В рамках одного параметра операнды должны отделяться непустыми строками константных символов. Комбинация мнемоник ассемблерного синтаксиса отображается в поле КОП соответс-твующей команды. Операнды (operand) отображаются на поля-операнды машинного слова. Возможно задание отображения на не непрерывные поля (когда биты поля перемежаются битами других полей).
Пример:
.types grn [gr0:0] [gr1:1] [gr2:2] [gr3:3] const6b $ -32 31 .mnemonics MOVE {grn#0;2},{const6b#2;4#8;2} % 0xA8C0 0xFCC0Описание задает команду MOVE с двумя операндами. Первый операнд типа grn является регистром общего назначения, код регистра размещается в 2х битах начиная с 0-го. Второй операнд является константой в диапазоне [-32; 31] и располагается в машинном слове в двух частях: в 4х битах, начиная со 2-го, и 2х битах, начиная с 8-го. КОП равен 1010-10XX-11XX-XXXX.
Симуляция акселератора
Для симуляции акселератора, заданного моделью MA (см. 2.1.2), необходим генератор тактов, а также определенное начальное состояние памяти
. В начальном управляющем состоянии p0 все слоты свободны. В рассматриваемой системе акселераторы и основной процессор работают тактово-синхронно (тактовый генератор единый для всей системы), то есть такт работы акселератора равен такту работы процессора. Кроме тактового генератора, единственным внешним событием для акселератора является выдача очередной команды основным процессором (см. 2.1.3.1 ниже). 2.1.3.1. Запуск команд акселератораВ рассматриваемой модели аппаратуры множество команд основного процессора должно иметь непустое подмножество, представляющее собой команды запуска акселераторов. Такие команды инициируют запуск определенной инструкции соответствующего акселератора. С точки зрения основного процессора команда запуска акселераторов определяется тремя полями машинного кода (порядок полей несущественен, также поля не обязательно должны быть непрерывными):
{КОП, номер акселератора, код инструкции акселератора}
Действия основного процессора при выполнении команды запуска акселератора заключаются в активации акселератора с номером в соответствующем поле и выдаче этому акселератору кода инструкции акселе-ратора для дальнейшего декодирования и выполнения команды в самом акселераторе параллельно с работой процессора. Для основного процессора выполнение команды запуска акселератора всегда занимает один такт. В терминах абстрактной модели выдача команды акселератора основным процессором заключается в передаче кода инструкции акселератора в функцию декодера акселератора DA ( CA это подмножество множества значений поля код инструкции акселератора). За один такт процессор может выдать не более одной команды акселератора. Заметим, что код инструкции акселератора в свою очередь может содержать КОП команды акселератора и операнды.
Акселератор может параллельно выполнять несколько многотактовых команд, в том числе с одинаковым дескриптором.
То есть основной процессор может выдавать новую команду акселератора, до того как отработали предыдущие команды. В рамках рассматриваемой модели это возможно, если все операции, выполняемые параллельно на каждом такте, используют непересекающиеся ресурсы (см. 2.1.2). На практике это возможно, если позволяет конвейер и функциональные устройства акселератора, при этом ответственность за корректную (своевременную) выдачу команд акселератора лежит на прикладном программисте.
2.1.3.2. Тактовое поведение акселератора
В ответ на событие от тактового генератора, в рассматриваемой модели действия акселератора сводятся к изменению состояния в соответствии со своей тактовой функцией TA. Эта функция определяет поведение акселератора на каждом такте.
2.1.3.3. Обмен данными и синхронизация с процессором
Обмен данными между процессором и акселераторами осуществляется через разделяемую (общую) память (см. 2.1.1.1). Заметим, что дополнительная информация от процессора к акселератору может также поступать в виде параметров инструкции (см. 2.1.3.1). Заметим, что разные акселераторы не имеют доступа к локальной памяти друг друга.
Разделение доступа к общей памяти в нашей модели соответствует типу CREW (Common Read Exclusive Write). Это означает, что процессор и акселераторы могут одновременно (в рамках текущего такта) читать из ячейки памяти, однако одновременная запись запрещена. В рассматриваемой модели области памяти могут иметь задержку записи, характеризуемую скоростью доступа к памяти (см. 2.1.1.1). По умолчанию, все области памяти имеют задержку 1, то есть изменения могут быть прочитаны только на следующем такте (flip-flop модель). Заметим, что если задержка больше ноля, то возможна одновременная запись и чтение одной и той же ячейки, при чтении считывается предыдущее значение.
Команды акселератора могут занимать фиксированное или переменное (в зависимости от данных) число тактов. С точки зрения прикладного программиста (компилятора) существует три способа синхронизации вычислений основного процессора и результатов работы определенной команды акселератора:
Когда команда акселератора всегда имеет фиксированное число тактов выполнения, программист может статически просчитать, когда будут готовы результаты вычислений (процессор и акселераторы работают синхронно, см. 2.1.3). Акселератор в процессе выполнения может выставлять определенные флаги (менять ячейки) в общей памяти. Программа основного процессора может считывать значения этих флагов и определять готовность результатов вычислений акселератора. Частным случаем пункта 2 является вызов акселератором прерывания основного процессора. Обработчик прерывания может прочитать результаты вычислений акселератора.
Смежные работы
Для спецификации аппаратуры на низком уровне используются языки HDL (Hardware Description Languages), наиболее известными из которых являются Verilog [6] и VHDL [7]. Задачей этих языков является создание спецификации, пригодной для синтеза реальной аппаратуры. Поэтому, задание аппаратуры на данном уровне абстракции является трудоемким и непригодным для быстрого проведения исследования альтернатив дизайна, далее DSE (Design Space Exploration). Также автоматическое построение кросс-системы затруднительно на основании описания HDL, так как информация о системе команд не определяется явно (см. [1]).
Интересный подход для моделирования аппаратуры разрабатывается в рамках программы Open SystemC Initiative [8], первоначально представленной в 2000 году. В настоящее время все активности по SystemC спонсируются и управляются комитетом из индустриальных компаний: ARM, Cadence, CoWare, Fujitsu, Mentor, Motorola, NEC, Sony, ST, Synopsys. SystemC это библиотека C++ классов, которая упрощает создание поведенческих моделей аппаратных систем за счет предоставления набора макросов и классов, реализующих элементы аналогичные конструкциям HDL. С помощью этих конструкций (см. [9]) возможно структурное описание системы, используя понятия модулей, процессов, портов, сигналов, интерфейсов, событий и т.п. Библиотека также предоставляет набор типов, удобных для моделирования аппаратных элементов, таких как битовые строки и числа с фиксированной точкой. Однако модель системы на SystemC предназначена только для симуляции и последующего синтеза аппаратуры, система команд явно не выделяется и построение полной кросс-системы на основании модели на SystemC затруднительно (см. [5]). В этом смысле SystemC относится скорее к классу HDL языков, при этом существуют автоматические конвертеры из Verilog и VHDL в SystemC ([10] и [11]). Скорость симуляции моделей SystemC невелика ввиду слишком низкого уровня описания деталей аппаратуры, несущественных для кросс-системы. Однако заметим, что для описания поведения отдельных операций аппаратуры примитивы SystemC очень удобны, аналогичные конструкции предоставляются и в рассматриваемой работе для описания операций и команд (см. 0).
В частности предоставляются типы данных, аналогичные SystemC; также синхронизация между командами аналогична синхронизации между процессами SystemC (FinishCycle() аналогична функции wait() в SystemC).
Для решения задачи автоматической генерации компонентов кросс системы предназначены языки класса ADL (Architecture Description Languages). Дополнительная информация и полный обзор языков ADL может быть найден в [1] - [5]. Здесь приведем только наиболее известные решения.
Одним из первых языков ADL был nML [12], изначально разработанный в Техническом Университете Берлина, Германия (1991). nML использовался в качестве способа описания аппаратуры для симулятора SIGH/SIM и компилятора CBC (с языка ALDiSP). В nML система команд процессора описывается с помощью атрибутных грамматик. Атрибуты включают в себя поведение (action), ассемблерный синтаксис (syntax) и отображение в машинные коды (image). Оригинальный nML не содержит механизмов описания многотактовых команд. Однако nML получил дальнейшее развитие в бельгийском научно-исследовательском центре микроэлектроники IMEC, где в рамках дочерней компании Target Compiler Technologies была создана коммерческая среда разработки [13]-[14], ориентированная на DSP архитектуры (1995). В эту среду входят компилятор CHESS (с языка C), симулятор CHECKERS, ассемблер, дисассемблер и линкер. Также поддерживается синтез VHDL описания. В рамках этой коммерческой среды компания Target Compiler Technologies модифицировала nML для поддержки более сложной аппаратуры (в частности введены механизмы явного описания конвейера), хотя из маркетинговых заявлений компании (технические спецификации недоступны) до конца не ясно, какие именно средства описания ILP поддерживаются. Также nML поддерживает только команды фиксированной длительности и производительность симулятора, опубликованная в [14], невысока. Последователем nML стал язык Sim-nML [15], работы над которым ведутся с 1998 года в Индийском Технологическом Институте (Indian Institute of Technology Kanpur) при поддержке компании Cadence.
Главным принци- пиальным нововведением стал дополнительный атрибут использования ресурсов (uses) в грамматике описания команд. Это позволяет описывать использование ресурсов и, тем самым, обнаруживать конфликты между командами. В рамках проекта Sim-nML были разработаны кодогенератор для компилятора, симулятор, ассемблер и дисассемблер. К сожалению, отсутствует интегрированная среда разработки и отладки.
Язык ISDL был разработан в университете MIT, США [16] и представлен на конференции по автоматизированному дизайну DAC [17] в 1997 году. Основной специализацией ISDL является описание VLIW архитектур. Изначально задумывалась реализация компилятора, ассемблера и симулятора, а также генератора Verilog описания. Аналогично nML, ISDL главным образом описывает систему команд процессора, включающую в себя семантику поведения, синтаксис ассемблера, машинные коды, а также описание ресурсных конфликтов, используя атрибутную грамматику. К важным достоинствам языка можно отнести возможность точно специфицировать задержки и конфликтные ситуации для ILP в виде логических правил, хотя явное описание конвейера отсутствует. Несмотря на довольно продуманный язык, к сожалению, отсутствуют в доступном виде реальные утилиты, поддерживающие его. Инициаторы проекта ограничились только реализацией ассемблера и некоторых модулей симулятора и кодогенератора для компилятора в качестве диссертационных работ MIT.
Язык EXPRESSION [18]-[19] разработан в Университете Калифорнии (University of California, Irvine, США), впервые был представлен на конференции DATE в 1999 году. Этот язык поддерживает широкий класс встроенных систем с ILP и иерархиями памяти от RISC, DSP, ASIP до VLIW. EXPRESSION содержит интегрированное описание структуры и поведения подсистемы процессор-память. Спецификация на EXPRESSION состоит из шести секций (первые три отвечают за поведение, последние три за структуру):
Спецификация операций (набор атомарных команд с кодами, описанием параметров и семантики (поведения)).
Описание формата команды (команда состоит из ячеек, ответственных за определенный функциональный модуль, которые могут заполняться атомарными операциями для параллельного выполнения).
Отображение общих операций компилятора на машинные операции, описанные в первой секции.
Данное описание используется для кодогенератора компилятора.
Описание компонент (функциональные устройства, шины, порты и т.п.).
Описание конвейера и связей компонентов.
Описание иерархии памяти (регистровая память, кэш, SRAM, DRAM).
Из описания EXPRESSION автоматически генерируются компилятор EXPRESS и симулятор SYMPRESS. К недостаткам решения на основе EXPRESSION следует отнести невысокую скорость симуляции и относительную трудоемкость описания (из-за наличия детальной структурной составляющей). В этом смысле EXPRESSION стоит между чистыми поведенческими ADL решениями (типа nML) и структурными описаниями HDL уровня.
Язык LISA [21]-[22] разрабатывался в университете RWTH Aachen (Германия) изначально в качестве средства описания аппаратуры для генерации симуляторов. Первые результаты работ по проекту LISA были опубликованы в 1996 году. Первоначальной целевой архитектурой были DSP процессоры. К ключевым характеристикам LISA можно отнести подробное описание конвейера на уровне операций с возможностью задания зависимостей и блокировок. Конвейерные конфликты задаются явно. Каждая команда задается в виде набора операций, которые определяются как регистровые пересылки за время одного кванта синхронизации. Описание LISA состоит из двух основных частей: спецификации ресурсов и описания операций. Описание операций в свою очередь содержит следующие секции:
DECLARE (определение объектов и групп через другие объекты и операции - фактически правила грамматики).
CODING (описание бинарного кодирования операции).
SYNTAX (описание ассемблерного синтаксиса и параметров).
BEHAVIOR и EXPRESSION (описание поведения операции в виде кода на языке C/C++).
ACTIVATION (описание задержек (timings) и поведения конвейера).
К сожалению, отсутствует полная публичная спецификация языка LISA, вся информация взята из различных статей. Согласно [20], из всех представленных подходов только система на основе языка EXPRESSION предоставляет средства для описания акселераторов. Однако, она ориентирована только на проведение фазы DSE, в ней отсутствуют такие производственные компоненты как ассемблер, дисассемблер, отладчик.Кроме того, ни одна из систем не поддерживает динамическую настройку компонентов, так как все компоненты создаются специальными генераторами кода в виде исходного кода на C/C++ и необходимо использование внешних компиляторов для получения готовой кросс системы. Настройка обработки ошибок в ассемблере также не поддерживается указанными языками.
Сообщения об ошибках
2.3.3.1. Ошибки симуляции
Число NS (см. 2.1.1.2) определяет максимальное количество параллельных команд в акселераторе. Симулятор генерирует ошибку, если процессор пытается выдать команду, когда нет свободных слотов.
Другой механизм обнаружения ошибок выполнения основывается на использовании ресурсов (см. 2.1.2). Если в пределах одного такта один и тот же ресурс используется разными параллельно выполняющимися командами, симулятор генерирует ошибку выполнения.
Для обеспечения CREW модели доступа к данным симулятор обнаруживает запись разными процессами в одну и ту же ячейку памяти и генерирует ошибку выполнения.
2.3.3.2. Ошибки ассемблированияДля ограничений 2.3.2.2 и 2.3.2.3 пользователь может задавать специализированный текст сообщений об ошибке и критичность ошибки (warning или error). Существует возможность задавать как индивидуальные тексты для каждого ограничения, так и ссылаться на общую таблицу сообщений. Данный подход предоставляет пользователю возможность точной настройки диагностики ошибок ассемблирования с детализированными описаниями, что является необходимым условием промышленной эксплуатации кросс-системы.
Состояние акселератора
2.1.1.1. Память акселератора
Ячейка памяти представляет собой набор двоичных переменных (далее битов), с возможными значениями 0 или 1. Число битов определяет разрядность ячейки. Набор из одной или более ячеек одинаковой разрядности образует область памяти. Набор из одной или более областей образует память. Память будем обозначать большой буквой S. Состояние ячейки памяти определяется набором конкретных значений всех ее битов. Состояние памяти определяется состоянием всех ячеек ее областей. Состояние памяти будем обозначать маленькой буквой s. Обозначим через N сумму разрядностей всех ячеек памяти S, тогда память может находиться в одном из 2N состояний. Множество состояний памяти будем обозначать {s}. Заметим, что это множество однозначно задается структурой памяти. Дескриптор структуры памяти
представляет собой следующий набор чисел: число областей O и набор из O пар (Wi, Si), задающих разрядность Wi ячеек области и их количество Si.В нашей модели память системы состоит из памяти основного процессора SP, разделяемой памяти SS и локальных памятей акселераторов Sa:
, - число акселераторов
Рассмотрение памяти процессора не принципиально для данной статьи, так как акселератор имеет доступ только к разделяемой и своей локальной памяти. Пара из этих памятей формирует полную память акселератора:
SA={SS,Sa}
Множество возможных состояний памяти акселератора обозначим как {S}A={S}Sx{S}a. Каждая область в локальной и разделяемой памяти характе-ризуется скоростью доступа - числом, означающим, сколько тактов проходит после записи в ячейку этой области, прежде чем измененное значение может быть прочитано; до этого момента при чтении считывается старое значение.
Обычно в памяти акселератора можно выделить следующие области:
Набор из одной или более памятей данных Набор из одного или более регистровых файлов Одиночные регистры 2.1.1.2. Управляющее состояниеАкселератор имеет фиксированное количество управляющих слотов, совокупность которых обозначается pA. Каждый слот имеет номер, который далее будет отождествляться с соответствующим слотом.
Каждый слот Li представляет собой пару целочисленных переменных (полей): поле номера команды nf и поле состояния команды t. Множество значений поля команды {nf} конечно. Ноль всегда принадлежит {nf}. Существует взаимно однозначное соответствие между {nf}\0 и множеством дескрипторов команд, определение которого будет дано ниже. Поэтому мы будем отождествлять номер
2.1.1.3. Состояние акселератора Состояние акселератора a задается парой из состояния памяти и управляющего состояния: a={s, p}. Множество состояний акселератора обозначим как {a}A={S}Ax{p}A.
Элементом состояния акселератора называется любая ячейка памяти акселератора или любой управляющий слот. Состоянием элемента называется соответственно состояние ячейки или слота. Параллельной композицией функций ?1,?2,...,?n (заданных на множестве состояний акселератора) назовем функцию ? = ?1?2...?n(также заданную на множестве состояний акселератора), получаемую следующим образом: пусть множество всех элементов состояния акселератора, Ai - множество элементов, состояние a? которых было изменено функцией .
Если пересечение всех Ai,i=1..n не пустое множество, то значение функции ? не определено.В ином случае значение ? задается следующим образом:
, здесь a`? и a`? - новое и старое состояние элемента соответственно |
Средства описания конкретных моделей акселераторов
Для задания конкретной модели акселератора необходимо определить следующие параметры, множества и функции:
Соответствующие определения были даны в 2.1.1 и 2.1.2, где также было показано, что они однозначно задают все элементы абстрактной модели:
Для описания конкретных моделей акселераторов в ИСП РАН был разработан язык спецификации ISE (Instruction Set Extension). Кроме собственно спецификации соответствующих элементов конкретной модели акселератора (см. выше), в язык также входят средства описания дополнительной информации об ассемблерном синтаксисе команд акселератора, отображении ассемблерных команд в машинные коды и описание форматов для визуализации областей памяти в отладчике. Модель акселератора далее будет отождествляться со спецификацией этой модели на языке ISE.
В статье рассматривается задача моделирования
В статье рассматривается задача моделирования расширений процессора в виде акселераторов (сопроцессоров) для автоматической настройки инструмен-тария кросс-разработки для поддержки этих расширений. Под инструмен-тарием кросс-разработки (кросс-системой) понимается набор программных компонентов (ассемблер, компоновщик, симулятор, отладчик и профилиро-вщик) для разработки прикладных программ с использованием хост-машины, отличной от целевой аппаратуры. Под поддержкой расширений кросс-системой подразумевается ассемблирование, потактовая симуляция и отладка прик-ладных программ, содержащих команды, не известные на этапе построения основного инструментария (реализуемых специфическими для пользователя акселераторами). Рассматриваемый подход основан на предоставлении пользо-вателю возможности описать модели акселераторов на разработанном языке спецификации с последующим использованием этих моделей для настройки компонентов кросс-системы.
Данная задача возникает в связи с тем, что многие современные аппаратные решения строятся на основе использования стандартного процессорного ядра со специализированными расширениями в виде акселераторов. Часть системы команд ядра зарезервирована для команд обращения к интерфейсу запуска инструкций акселераторов. Однако семантика реальных действий и вычис-лений, которые инициируют такие команды, определяется конкретными акселераторами и не зависит от основного процессора. Производитель ядра и производители акселераторов могут быть разными компаниями, при этом инструментарий кросс-разработки от производителя основного процессора должен уметь поддерживать неизвестные для него расширения аппаратуры, которые создаются заказчиками. В данной работе под расширениями процессора понимается добавление акселераторов, которые могут вводить в систему новые элементы памяти (регистры, памяти данных) и определять семантику команд запуска инструкций акселераторов. Аппаратура на базе основного процессора с акселераторами представляется для прикладного программиста как вычислительная система с единой системой команд и одной программой.
Память системы состоит из памяти основного процессора, разделяемой памяти и локальных памятей акселераторов.
Для выделения класса поддерживаемых акселераторов и интерфейса с процессором была разработана абстрактная математическая модель, позво-ляющая моделировать состояние и поведение широкого класса акселераторов с потактовой точностью. Для описания конкретных моделей, в рамках данной абстрактной, предложен язык спецификации (ISE), разработаны средства визуального редактирования спецификаций на этом языке и средства анализа и выявления ошибок в спецификациях. Реализован подход интерактивной пере-настройки кросс-системы, заключающийся в настройке компонентов (ассемблер, дисассемблер, симулятор, отладчик) на основании интерпретации описаний моделей акселераторов. Система используется в коммерческой эксплуатации, в ней успешно реализованы модели реальных акселераторов.
Статья состоит из введения, трех глав и заключения. В первой главе описы-вается подход к моделированию акселераторов, вводится абстрактная модель акселератора и средства описания конкретных моделей на языке ISE. Во второй главе содержится описание технологии интерактивной перенастройки кросс-системы для поддержки заданных пользователем моделей акселератов. Третья глава содержит краткий обзор смежных работ. В заключении приводятся практические результаты, полученные при эксплуатации разработанной системы. Приводится план будущих направлений развития темы.
В данной статье представлена технология
В данной статье представлена технология динамической настройки кросс-системы для поддержки ассемблирования, симуляции и отладки программ, содержащих команды, неизвестные на этапе построения основного инструмен-тария. Семантика и синтаксис этих команд определяются конкретными акселе-раторами, создаваемыми пользователями при построении специфической конфигурации системы "процессор + акселераторы" и неизвестными производителю основного процессора (и соответственно кросс-системы).
Для решения этой задачи была разработана абстрактная модель поддерживаемых акселераторов и интерфейса с процессором, охватывающая широкий спектр возможной аппаратуры. Предложенный язык спецификации ISE позволяет пользователям описывать спецификации конкретных акселераторов в рамках этой абстрактной модели. Созданные файлы спецификаций регистрируются в настройках интегрированной среды при описании конфигурации системы (см. 3.1). При этом компоненты кросс- инструментария настраиваются в соответствии с этими спецификациями динамически (in-run-time). В результате прикладные программисты получают возможность писать и отлаживать программы с использованием новых команд. Спецификации и конфигурация акселераторов в системе могут многократно меняться в рамках одного сеанса интегрированной среды, в том числе с помощью визуальных средств редактирования, анализа и верификации.
На основе описанной технологии в рамках коммерческого проекта в ИСП РАН была реализована настраиваемая кросс-система для DSP процессора заказчика (поддерживающего акселераторы). Авторам известно, что с помощью этой сис-темы пользователями были созданы рабочие модели реальных акселераторов:
a. Быстрого преобразования Фурье
b. Алгоритмов эхо подавления
c. Операций с комплексными числами
d. Операций кодирования видео
e. Операций цифровой фильтрации звука Пиковая производительность симулятора центрального процессора (на хост-машине PIII-1000MHz) в рассматриваемой системе составляет порядка 10 миллионов тактов в секунду. При использовании конфигурации с одним акселератором (эхо подавления) производительность составила порядка 1 миллиона тактов в секунду (при этом симулируются процессор и акселератор, работающие параллельно), что обусловлено большими потерями на синхро-низацию процессов выполнения процессора и акселератора.
Дальнейшие наши работы в этой области направлены на расширение языка ISE и соответствующих утилит для поддержки моделирования полной системы, включая описание центрального процессора. Отдельное внимание уделяется увеличению производительности симулятора за счет использования JIT технологий и использования знаний о конкретной программе. Также предполагается расширить возможности системы для настройки компилятора с языка высокого уровня для генерации кода с учетом наличия акселераторов (в настоящее время команды акселератора на уровне языка C используются вручную в виде ассемблерных вставок).
Определения
Начнем с определения термина типа. За основу возмем определение термина абстрактный тип данных (АТД), данное Бертраном Мейером (Bertrand Meyer) [].
Спецификация АТД состоит из четырех разделов: типы; сигнатуры функций; аксиомы функций и типов; предусловия применения частично определенных функций.
Далее будем придерживаться следующих соглашений: саму спецификацию будем называть типом; доменом типа будем называть множество всех величин данного типа, то есть величин, удовлетворяющих спецификации; в разделе типы (по Мейеру) будут содержаться обозначение домена типа, аксиомы для его описания, а также, возможно, спецификации простых вспомогательных типов; в разделе функций будут вводиться сигнатуры функций, предусловия и постусловия применения функций; из всего набора функций выделяются функции-генераторы (generator, на выходе имеется обозначение домена типа) и функции-наблюдатели (observer, на выходе функции отсутствует обозначение домена типа); особенно важную роль играют функции, не выводимые из других функции; в разделе аксиом будут определяться аксиомы, описывающие попарное применение функций-генераторов и функций-наблюдателей.
В простых и привычных случаях , когда это не приводит к путанице, типом можно называть домен типа. Например, можно называть типом прямое произведение двух множеств A ? B, подразумевая, что имеется спецификация функций проекции. Обозначение A ? B, мыслимое без функций проекции – это домен типа, A ? B, мыслимое вместе с аксиомами функций проекции, – это тип.
Далее, уточним следующее определение Мейера []: Класс – это абстрактный тип данных, поставляемый с возможно частичной реализацией.
Спецификация (то есть тип), в которой произведена замена аксиом, описывающих попарное поведение функций, на частично определенные функции, будем называть аппликативной реализацией. Определение класса на языке программирования L, удовлетворяющее требованиям спецификации, будет называть (обычно, императивной) L-реализацией.
нарушения принципа подстановки
Специфицируем на RSL аппликативную реализацию С++-функции LSVP, приводимой во введении для иллюстрации нарушения принципа подстановки. Спецификация этой функции могла бы выглядеть следующим образом: ---- File:./rsl/v.rsl AS scheme V = extend AS with class value LSPV : Figure >< (Figure >< UReal -> Figure) -> Unit LSPV (r, setWidth) is let f = SetHeight(setWidth(r,5.0),4.0), w = GetWidth (f), h = GetHeight(f) in if ( h * w = 20.0) then skip else chaos end end -- pre -- (all h: UReal:- GetWidth(SetHeight(setWidth(r,5.0), h)) = GetWidth(setWidth(r,5.0))) end ---- End Of File:./rsl/v.rsl
Аппликативная реализация функции LSPV делает точно то же, что и C++-реализация. Функция последовательно применяет функции setWidth и SetHeight к полученной на вход переменной r: Figure и, если произведение высоты r на длину r полученного прямоугольника не равняется 20, ведет себя хаотическим, неопределенным образом (chaos). Чтобы подчеркнуть наличие ошибки, в комментарии написано предусловие этой частично определенной функции LSPV, хотя в сигнатуре она описана как полностью определенная.
Честно говоря, доводы Мартина насчет того, что его пример демонстрирует реальную проблему, выглядят не слишком убедительно: So here is the real problem: Was the programmer who wrote that function justified in assuming that changing the width of a Rectangle leaves its height unchanged? Clearly, the programmer of LSPV made this very reasonable assumption.
Здесь имеется реальная проблема: Оправданным ли образом программист, написавший эту функцию, полагал, что при изменении длины прямогульника его высота остается неизменной? Яcно, что программист функции LSPV сделал вполне осмысленное предположение.
То есть Мартин считает, что этого программиста осуждать не за что. И это действительно так, но здесь нет никакой проблемы. Программист, использующий класс Rectangle, должен делать только те предположения об используемом классе, которые соответствуют спецификации класса. Формально говоря, функция LSPV при получении на вход переменной a: Square сделала то, что от нее и требовалось. Поэтому нельзя говорить об изменении поведения LSPV. Вот если бы была написана хотя бы частичная спецификация этой функции, если хотя бы было указано, что LSPV – это всюду определенная функция (LSPV : Rectangle > Unit), а при получении a: Square она бы повисла, то есть оказалась бы не всюду определенной, то это была бы действительно проблема несоответствия реализации спецификации.
Наличие аппликативной реализации и C++ -реализации, приводящих к одинаковым трудностям, говорит о том, что дело здесь не в языке реализации.
Спецификации типов примера
Вопросы единственности типов, полноты типов (в смысле полноты системы аксиом), противоречивости типов (в смысле противоречивости системы аксиом), единственности реализации, оптимальности спецификации и реализаций, синтаксического сахара RSL и др. здесь не рассматриваются.
Попытаемся специфицировать типы описанных выше С++-реализаций Rectangle и Square на языке формальных спецификаций RSL в соответствии с методом, принятым в RDG. Дополнительный тип UReal (домен которого включает все положительные вещественные значения) вводится, чтобы функции были всюду определены, и не нужно было заниматься предусловиями.
Ссылки
Бертран Мейер. Основы объектно-ориентированного программирования. Статические структуры: классы. Бертран Мейер. Основы объектно-ориентированного программирования. Абстрактные типы данных (АТД). А.Г. Пискунов. The RAISE Method Group: Алгебраическое проектирование класса , 2007. Bertrand Meyer. Основы объектно-ориентированного программирования. Техника наследования. Robert C.Martin. The Liskov Substitution Principle. C++ Report, March 1996. Barbara H. Liskov, Jeannette M. Wing. A behavioral notion of subtyping. ACM Transactions on Programming Languages and Systems, Volume 16 Issue 6, Nov. 1994.
Тип Rectangle
Аксиомы gw_sw и gh_sh очевидны: что положил, то и получил назад. Аксиомы gw_sh и gh_sw означают независимость высоты от ширины в значениях множества Figure, соответствующих спецификации Rectangle. Например, для любого f: Figure значения, возвращаемые функцией GetWidth не зависят от применения функции SetHight. ---- File:./rsl/rectangle.rsl scheme Rectangle = class type Figure, UReal = {| r: Real :- r > 0.0 |} value SetWidth : Figure >< UReal -> Figure, SetHeight : Figure >< UReal -> Figure, GetWidth : Figure -> UReal, GetHeight : Figure -> UReal axiom [gw_sw] all w: UReal, f: Figure:- GetWidth(SetWidth(f, w)) = w, [gw_sh] all h: UReal, f: Figure:- GetWidth(SetHeight(f, h)) = GetWidth(f), [gh_sh] all h: UReal, f: Figure:- GetHeight(SetHeight(f, h)) = h, [gh_sw] all w: UReal, f: Figure:- GetHeight(SetWidth(f, w)) = GetHeight(f) end ---- End Of File:./rsl/rectangle.rsl
Аппликативная реализация типа Rectangle может иметь следующий вид: ---- File:./rsl/ar.rsl scheme AR = class type Figure = UReal >< UReal, UReal = {| r: Real :- r > 0.0 |} value SetWidth : Figure >< UReal -> Figure SetWidth ((h, w), v) is (h, v), SetHeight : Figure >< UReal -> Figure SetHeight ((h, w), v) is (v, w), GetWidth : Figure -> UReal GetWidth (h, w) is w, GetHeight : Figure -> UReal GetHeight (h, w) is h end ---- End Of File:./rsl/ar.rsl
Домен типа Rectangle (множество Figure) объявляется как прямое произведение UReal ? UReal, функции GetWidth и GetHeight – проекции.
Тип Square
В типе Square, предположительно являющемся подтипом Rectangle, аксиомы gw_sh и gh_sw имеют смысл, строго противоположный смыслу аналогичных аксиом в Rectangle: ---- File:./rsl/square.rsl scheme Square = class type Figure, UReal = {| r: Real :- r > 0.0 |} value SetWidth : Figure >< UReal -> Figure, SetHeight : Figure >< UReal -> Figure, GetWidth : Figure -> UReal, GetHeight : Figure -> UReal axiom [gw_sw] all w: UReal, f: Figure:- GetWidth(SetWidth(f, w))= w, [gw_sh] all h: UReal, f: Figure:- GetWidth(SetHeight(f, h)) = h, [gh_sh] all h: UReal, f: Figure:- GetHeight(SetHeight(f, h)) = h, [gh_sw] all w: UReal, f: Figure:- GetHeight(SetWidth(f, w)) = w end ---- End Of File:./rsl/square.rsl Вот аппликативная реализаци типа Square, записанная в форме наследования: ---- File:./rsl/as.rsl AR scheme AS = extend hide SetWidth, SetHeight in AR with class value SetWidth : Figure >< UReal -> Figure SetWidth ((h, w), v) is (v, v), SetHeight : Figure >< UReal -> Figure SetHeight ((h, w), v) is (v, v) end ---- End Of File:./rsl/as.rsl
В новом классе AS "наследуется" (extend) все из класса AR, но при этом "прячутся" (hide) родительские функции SetWidth, SetHeight и объявляются свои.
Уточнение терминов
Под термином выделение типа (subtyping) будем понимать добавление к супертипу непротиворечивых уточнений. Слово уточнение здесь означает, что все предыдущие свойства типа должны сохраняться. Система аксиом для домена и функций должна оставаться непротиворечивой. Домен типа не может расшириться. Функции могут быть инкапсулироваться (удаляться из описания), добавляться или переопределяться (т.е. в спецификации подтипа может быть удалена некоторая функция супертипа и добавлена своя с той же сигнатурой), но аксиомы супертипа должны выполняться.
То есть требуется уточнение спецификации типа в соответствии с принципами проектирования по контракту [].
В нашем случае предположим, что тип Square является подтипом типа Rectangle. Тогда в типе-потомке должны одновременно выполняться обе версии аксиом gw_sh, одна из Rectangle: [gw_sh] all h: UReal, f: Figure:- GetWidth(SetHeight(f, h)) = GetWidth(f), вторая из Square: [gw_sh] all h: UReal, f: Figure:- GetWidth(SetHeight(f, h)) = h
То есть в совокупности мы получаем следующую аксиому: all h: UReal, f: Figure:- h = GetWidth(SetHeight(f, h)) /\ GetWidth(SetHeight(f, h)) = GetWidth(f) Отсюда следует аксиома: all h: UReal, f: Figure:- h = GetWidth(f) Однако если мы возьмем в UReal некоторое значение w, такое что w ? h, то по аксиоме gw_sw получим: h = GetWidth(f) = GetWidth(SetWidth(f1, w)) = w
Полученное противоречие показывает, что две разные версии аксиом gw_sh не могут выполняться вместе. Отсюда следует, что нет никакой возможности в принципе считать Square подтипом Rectangle. Их можно считать "братьями", являющимися подтипами некоторого неполного, неоднозначного типа.
К этому же заключению можно придти на основе того наблюдения, что можно написать С++-реализацию Square, от которой унаследовать С++-реализацию Rectangle. Было бы очень странно иметь в языке возможность наследования супертипа от подтипа.
В заключение специфицируем неполностью определенный тип с удаленными аксиомами gw_sh и gh_sw, являющийся настоящим родителем обоих классов: ---- File:./rsl/realdad.rsl scheme RealDad = class type Figure, UReal = {| r: Real :- r > 0.0 |} value SetWidth : Figure >< UReal -> Figure, SetHeight : Figure >< UReal -> Figure, GetWidth : Figure -> UReal, GetHeight : Figure -> UReal axiom [gw_sw] all w: UReal, f: Figure:- GetWidth(SetWidth(f, w)) = w, [gh_sh] all h: UReal, f: Figure:- GetHeight(SetHeight(f, h)) = h end ---- End Of File:./rsl/realdad.rsl С++-реализация этого типа может выглядеть следующим образом: ---- File:./rsl/realdad.cpp class RealDad{ public: virtual void SetWidth(double w)=0; virtual void SetHeight(double h)=0; double GetHeight() const {return itsHeight;} double GetWidth() const {return itsWidth;} private: double itsWidth; double itsHeight; }; ---- End Of File:./rsl/realdad.cpp
Оба класса Square и Rectangle должны наследоваться от класса RealDad, который и следует использовать для написания полиморфных функций вроде LSPV в качестве типа формального параметра.
Кроме того, нужно сказать, что принцип подстановки Лисков в формулировке 1994 года [] выглядит лучше: Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true for objects y of type S where S is a subtype of T.
Пусть φ(x) – это свойство, доказываемое для объектов x типа T. Тогда свойством φ(x) должны обладать объекты y типа S, где S является подтипом T.
Принцип подстановки Лисков помогает понять
Принцип подстановки Лисков помогает понять суть термина suptyping – выделение подтипа, а статья Роберта Мартина [] показывает некоторое несоответствие между наследованием в языке C++ и выделением подтипа. В этой статье принцип формулируется следующим образом: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T. Перевод может звучать как-то так: Если для каждого объекта o1 типа S существует объект o2 типа T, такой что любая программа P, определенная в терминах T, не изменяет своего поведения при подстановке объекта o1 вместо объекта o2, то тип S является подтипом T. Эта формулировка допускает достаточно неоднозначную трактовку. Чтобы понять, что имеется в виду, рассмотрим пример из статьи с прямоугольниками и квадратами. Определим родительский класс Rectangle: ---- File:./rsl/rectangle.cpp class Rectangle{ public: virtual void SetWidth(double w){itsWidth=w;} virtual void SetHeight(double h) {itsHeight=H;} double GetHeight() const {return itsHeight;} double GetWidth() const {return itsWidth;} private: double itsWidth; double itsHeight; }; ---- End Of File:./rsl/rectangle.cpp и наследуем от него класс Square: ---- File:./rsl/square.cpp class Square : Rectangle { public: virtual void SetWidth(double w); virtual void SetHeight(double h); }; void Square::SetWidth(double w){ Rectangle::SetWidth(w); Rectangle::SetHeight(w); } void Square::SetHeight(double h){ Rectangle::SetHeight(h); Rectangle::SetWidth(h); } ---- End Of File:./rsl/square.cpp Автор статьи не без основания утверждает, что в приведенном примере в фукнции LSPV таки нарушается принцип подстановки: ---- File:./rsl/violation.cpp void LSPV(Rectangle& r){ r.SetWidth(5); r.SetHeight(4); assert(r.GetWidth() * r.GetHeight()) == 20); } ---- End Of File:./rsl/violation.cpp Можно было бы возразить автору, что принцип подстановки плохо сформулирован, что не определен термин замена (хотя в данном примере все понятно: в программу передается ссылка на объект подкласса), что непонятен термин поведение программы и так далее. Но попробуем проанализировать пример с точки зрения методов RDG [] и придать точный теоретико-множественный смысл этим и некоторым другим терминам.
Создание и тестирование многопоточной программы
Вячеслав Любченко
Постановка задачи. Пусть мы имеем двух спортсменов-бегунов и тренера, фиксирующего в динамике число шагов, которые они пробежали на тот или иной момент времени. В целях простоты бегуны стартуют одновременно, бегут с одинаковой скоростью и длина шага у них одна. Необходимо создать программу, моделирующую поведение бегунов и их тренера.
Модель решения. Можно условиться, что спортсмен имеет счетчик шагов, который на каждом шаге увеличивается на единицу. Достигнув заданного числа шагов, "бегуны" останавливаются. "Тренер" в процессе бега отслеживает текущее значения счетчиков каждого спортсмена, выводя их значения на экран монитора (варианты - фиксирует в блокноте, записывает в текстовый файл на диск:). На структурном уровне программа, реализующая поставленную задачу, содержит три параллельных не взаимодействующих между собой объекта (двух бегунов и одного тренера).
Программная реализация. Выделим для каждого объекта свой поток. Код класса модели спортсмена-бегуна может быть таким (Листинг 1): #include "living_object.h" class CDat; class Trotter : public living_object { public: Trotter(CDat *d, int num, long lSteps = 100000000); virtual ~Trotter(); void run(); private: CDat *pCDat; int nNumTrotter; }; Trotter::Trotter(CDat *d, int num, long lSteps): living_object() { pCDat = d; pCDat->nStepsAll = lSteps; nNumTrotter = num; } Trotter::~Trotter() { } void Trotter::run() { for (int i=1; inStepsAll; i++) { if (nNumTrotter ==0) { pCDat->nCurStep0++; } else { pCDat->nCurStep1++; } if (nNumTrotter ==0) { if (pCDat->nStepsAll == pCDat->nCurStep0) break; } else { if (pCDat->nStepsAll == pCDat->nCurStep1) break; } // Sleep(0); } } Листинг 1. Код класса "бегуна".
Здесь класс living_object - базовый класс для любого, так называемого, активного/живого объекта на базе одного потока. Его код представляет Листинг 2. Здесь метод run() реализует жизненный цикл активного объекта. В рамках его каждый объект-спортсмен в зависимости от своего номера - nNumTrotter наращивает в цикле значение соответствующего счетчика шагов.
Первый объект изменяет счетчик nCurStep0, второй - nCurStep1. Когда значение счетчика шагов достигнет заданного числа - nStepsAll, объект завершает свою работу.
class living_object { static DWORD WINAPI HelperThreadProc(LPVOID p); public: living_object(); virtual void run() {}; virtual ~living_object() {}; };
extern HANDLE hStart; extern CRITICAL_SECTION cs; extern int total_threads; extern DWORD endTick;
living_object::living_object() { DWORD threadID; if(!CreateThread(NULL, 0, HelperThreadProc, (LPVOID)this, 0, &threadID)) printf("GetLastError: %d\n", GetLastError()); }
DWORD WINAPI living_object::HelperThreadProc(LPVOID p) { EnterCriticalSection(&cs); total_threads++; LeaveCriticalSection(&cs);
WaitForSingleObject(hStart, INFINITE);
((living_object*)p)->run();
EnterCriticalSection(&cs); total_threads--; endTick=GetTickCount(); LeaveCriticalSection(&cs); return 0; } Листинг 2. Код класса базового активного объекта living_object .
Тренер, бегущий рядом с бегунами, имеет следующий код (Листинг 3):
#include "living_object.h"
class CDat; class PrnCurStep : public living_object { public: PrnCurStep(CDat *d); virtual ~PrnCurStep(); void run(); private: CDat *pCDat; long lTmp0; long lTmp1; };
PrnCurStep::PrnCurStep(CDat *d): living_object() { pCDat = d; lTmp0= lTmp1= 0; }
PrnCurStep::~PrnCurStep() { }
void PrnCurStep::run() { while (pCDat->nStepsAll != pCDat->nCurStep0) { // printf("%d %d\n", pCDat->nCurStep0, pCDat->nCurStep1); ///* if (lTmp0 != pCDat->nCurStep0 lTmp1 != pCDat->nCurStep1) { printf("%d %d\n", pCDat->nCurStep0, pCDat->nCurStep1); lTmp0 = pCDat->nCurStep0; lTmp1 = pCDat->nCurStep1; } // Sleep(0); //*/ } }
Листинг 3. Код класса "тренер".
В рамках метода run "тренер" на каждой итерации цикла выводит на консоль текущие значения счетчика циклов обоих бегунов. Тестирование. Запустив программу, моделирующую бегунов, мы получим результат, который показан на рис.1.
Рис. 1. Печать только изменений значения Можно предположить, что в протоколе работы программы отражено только изменение значение счетчиков (см. также код "тренера"). Попробуем в этом убедиться: Эксперимент 1. Внесем небольшое изменение в код "тренера", чтобы он выводил значения счетчиков "бегунов" на каждом шаге своей итерации. В результате получим следующий протокол работы программы, который представлен на рис. 2.
Рис. 2. Вывод на каждой итерации цикла печати. Легко видеть, что, получив в свое распоряжение процессор, "тренер" выводит многократно текущие значения счетчиков шагов. Последние не изменяются, т.к. оба "бегуна" простаивают из-за того, что цикл процессора захвачен монопольно "тренером". По числу повторений можно определить число итераций, которые успевает сделать "тренер" в пределах выделенного ему времени процессора. Эксперимент 2. Вставим в цикл "бегуна", прерывание, которое инициирует выход в ядро операционной системы (вызов функции Sleep с нулевым значение параметра "засыпания"). Так мы "насильно" изменим квантование процессора. Дело в том, что пропуски и/или повторы в отображении текущего значения счетчиков происходят потому, что "бегуны" успевают сделать достаточное число шагов, прежде чем тренер начнет выводить их текущее значение. Примечание 1. То, что значения счетчиков разные на одном и том же шаге - такте дискретного времени, тоже непорядок. Ведь спортсмены начали бежать одновременно и бегут с одинаковой скоростью. В результате получим протокол работы программы, который показан на рис.3. Из него видно, что теперь отображается каждый шаг бегуна/бегунов.
Рис. 3. Вставка Sleep в тело цикла "бегунов" Примечание 2. Т.к. вывод данных резко увеличил время работы программы, то, чтобы оно имело приемлемое для нас значение, мы взяли меньшее число шагов (ср. время работы программы на рис. 1 и рис.3 с общим числом выполненных шагов). Кстати, так можно оценить время, затрачиваемое на вывод данных. Однако, мы, оказывается, имеем лишь кажущееся благополучие, т.к.
если вставить вызов Sleep в каждую итерацию цикла печати и немного увеличить заданное число шагов, то получим еще один протокол (рис. 4). Видно, что по каким-то причинам выводится значения до 66-го шага, а не все 100 шагов, как ожидалось.
Рис. 4. Вставка Sleep в тело цикла "тренера" Эксперимент 3. Проведем эксперимент, запустив программу вне среды визуального проектирования. Два варианта исполняемых программных модули, созданных в проектах типа Release и Debug, при одном и том же значении максимального числа шагов показывают, как мы видим из протоколов, показанных на рис. 5, 6, разные "картинки". Более того, эти картинки отличаются и от результата работы программы под управлением среды проектирования, который ранее был представлен протоколом на рис.1.
Рис.5. Вне среды, режим Release
Рис.6. Вне среды, режим Debug Эксперимент 4.. Совсем простой эксперимент: если мы повторно запустим программу, например, в режиме Debug, то увидим уже другие результаты работы. Они представлены на рис. 7
Рис.7. Повторный запуск (режим Debug). Таким образом, последний эксперимент с многопоточной программой убеждает, что не надо даже и вносить изменения в программный код, чтобы получить результат работы программы, который отличается от предыдущего ее запуска. Мир - удивителен! Но еще большее удивление можно испытать от непредсказуемой работы многопоточных программ! Зная об этом, с этим можно бороться тем или иным образом, но результат такой "борьбы" и есть то сложное и трудоемкое многопоточное программирование. Но самое опасное, что и это не дает полной гарантии от непредсказуемого (случайного) поведения подобных программ, поскольку такое поведение - одна из особенностей многопоточной модели параллельных вычислений.