GLADE User Guide
GLADE, GNAT Library for Ada Distributed Environment
Laurent Pautet, Samuel Tardieu
GLADE Version 3.15pff
Date: 28 May 2002
Перевод Copyright (C) 2003 А.Гавва.
Коммерческое распространение перевода, без разрешения автора перевода, запрещено.
GLADE - это GNAT-реализация дополнения стандарта Ada95 для распределенных систем (Ada95 Distributed Systems Annex).
Содержание
- Об этом руководстве
- Введение в распределенные системы
- Дополнение стандарта Ada95 для распределенных систем
- Основы GLADE
- Знакомство с GLADE
- Как конфигурировать распределенное приложение
- Опции командной строки gnatdist
- Действия выполняемые gnatdist
- Язык конфигурирования
- Зарезервированные слова
- Директивы и спецификаторы представления
- Описание конфигурации
- Описание раздела
- Описание расположения
- Атрибут раздела 'Main
- Директива Starter
- Директива Boot_Location
- Атрибут раздела 'Self_Location
- Атрибут раздела 'Passive
- Атрибут раздела 'Data_Location
- Атрибут раздела 'Host
- Директива Import
- Атрибут раздела 'Directory
- Атрибут раздела 'Command_Line
- Атрибут 'Termination для разделов
- Атрибут раздела 'Reconnection
- Описание канала
- Атрибут 'Filter для раздела и канала
- Директива Registration_Filter
- Директива Version
- Атрибут раздела 'Task_Pool
- Полный пример
- Опции командной строки для раздела распределенной программы
- Опция раздела boot_location
- Опция раздела self_location
- Опция раздела data_location
- Опция раздела nolaunch
- Опция раздела detach
- Опция раздела slave
- Опция раздела boot_mirror
- Опция раздела mirror_expected
- Опция раздела connection_hits
- Опция раздела reconnection
- Опция раздела termination
- Опция раздела trace
- Опция раздела replay
- Средства отладки
- Иерархия файлов GLADE
- Внутренняя организация GLADE
- Примечания для удаленного командного интерпретатора shell
- DSA и CORBA
Об этом руководстве
Что содержит это руководство
Это руководство содержит следующие главы:
- "Введение в распределенные системы" рассматривает различные способы разработки систем, которые должны работать на сети компьютеров.
- "Дополнение стандарта Ada95 для распределенных систем" рассматривает средства предоставляемые дополнением стандарта Ada95 для распределенных систем (Annex E: "The Ada95 Distributed Systems Annex"). Эта глава предусматривает учебный материал для начинающих, а также включает несколько полезных примеров для более опытных программистов.
- "Основы GLADE" описывает использование средства конфигурирования gnatdist, и детали системы взаимодействия разделов GLADE (GLADE Partition Communication Subsystem), GARLIC.
- "DSA и CORBA" содержит подробное сравнение возможностей CORBA и дополнения стандарта Ada95 для распределенных систем (DSA - Distributed Systems Annex).
Введение в распределенные системы
Архитектура распределенной системы состоит из сети компьютеров и компонентов программного обеспечения, которые выполняются на этих компьютерах. Подобные архитектуры используются для повышения производительности, стабильности и повторного использования сложных приложений. Обычно удаленно расположенные компоненты не имеют общего адресного пространства памяти (как правило, такие компоненты выполняются на различных узлах сети), и, таким образом, для взаимодействия таких компонентов должна использоваться какая-либо форма передачи сообщений.
Использование сетевых сервисов операционной системы
Существуют различные программные технологии разработки распределенных систем. Традиционно подобные приложения разрабатываются с помощью использования сетевых программных интерфейсов, таких как сокеты. Для этого программист должен использовать явные обращения к сервисам операционной системы, что утомительно и не защищено от ошибок. Подобный подход включает инициализацию соединения и определение равноправного расположения, упаковку/распаковку и передачу/прием структур данных, посылку и прием сообщений, одновременную отладку и проверку различных программ, а также перенос приложения на различные платформы без раскрытия тонкостей различных сетевых интерфейсов.
Конечно, подобный код взаимодействия, с целью упрощения, может быть инкапсулирован в "обертки" (wrappers), однако очевидно, что большая его часть может генерироваться автоматически. Следует заметить, что непосредственное программирование передачи сообщений отвлекает внимание разработчика от предметной области приложения. Сценарий запрос/ответ - является классической схемой, которая применяется в распределенных приложениях. Использование передачи сообщений, в этой схеме, можно сравнить с использованием механизма goto в нераспределенных приложениях. С точки зрения современной инженерии программирования подобная методология неприменима. Более качественный дизайн требует использование структурированного подхода, который основывается на вызовах подпрограмм.
В некоторых отношениях, сетевое программирование можно сравнить с многопоточным/многонитиевым программированием. Пользователь может разделить свой код на несколько частей и самостоятельно мультиплексировать выполнение нитей, используя таблично управляемую модель. В финале, в код пользователя встраивается код планировки выполнения. Следует заметить, что такое решение не защищено от ошибок и в последствии его будет трудно модифицировать. Использование реализации нитей, которая предусматривается в POSIX, является более хорошим решением, а самым удачным вариантом решения будет использование примитивов языка, который обладает встроенной поддержкой многозадачности (как Ада).
Использование промежуточного окружения
Промежуточное окружение должно предусматривать высокоуровневые абстракции, облегчающие разработку приложений пользователя. Окружения подобные CORBA или окружению распределенных вычислений DCE (Distributed Computing Environment) предусматривают среду разработки клиент-серверных приложений, которая основывается на модели вызова удаленных подпрограмм RPC (Remote Procedure Call). Модель RPC является производной от схемы запрос/ответ. Передача аргументов и дополнительных данных, определяющих удаленную процедуру, которую необходимо выполнить, в поток, является грубой аналогией вызова обычной процедуры. Поток передается к серверу по сети. Сервер декодирует поток, выполняет локальный вызов обычной подпрограммы, а затем помещает выходные параметры в другой поток, наряду с исключением, если оно было возбуждено в процессе выполнения подпрограммы. После этого сервер посылает поток обратно к клиенту вызвавшему подпрограмму. Клиент декодирует поток и, при необходимости, локально возбуждает исключение.
CORBA предусматривает такие же расширения для модели удаленных процедур, какие объектно-ориентированные языки предусматривают для классических процедурных языков. Такие расширения включают инкапсуляцию, наследование, проверку типа и исключения. Эти средства обеспечиваются с помощью языка описания интерфейсов IDL (Interface Definition Language).
Промежуточная среда взаимодействия предусматривает все механизмы для прозрачного осуществления вызовов удаленных процедур и/или методов удаленных объектов. Например, каждый интерфейс CORBA взаимодействует с каким-либо Object Request Broker (ORB). Задача подсистемы взаимодействия, подобной ORB, заключается в том, чтобы избавить приложения использующие удаленные объекты от непосредственного использования низкоуровневых механизмов, реализующих передачу сообщений. Дополнительно, при разработке распределенного приложения, пользователю могут понадобиться некоторые более сложные сервисы. Некоторые из таких сервисов могут оказаться крайне необходимыми, например, сервис расположения, который позволяет клиентам обращаться к удаленным сервисам посредством использования высокоуровневых имен вместо традиционной схемы адресации удаленных сервисов, которая использует IP адреса и номера портов. Другие сервисы могут предусматривать интерфейсы не зависящие от доменов, которые часто используются в распределенных системах.
Если мы вернемся к сравнению с многопоточным/многонитиевым программированием, то обнаружим, что промежуточная среда предусматривает средства, которые подобны средствам, предусматриваемым библиотекой POSIX или языком подобным Esterel1, для разработки многопоточных/многонитиевых приложений. Промежуточная среда, такая как DCE, подобна библиотеке POSIX с точки зрения уровней абстракции. Ее функциональность очень низкоуровнева и сложна. CORBA, с точки зрения процесса разработки, ближе к Esterel. Управляющая часть приложения может быть определена с помощью языка описания. После чего, для построения вычислительной части приложения, разработчик заполняет автоматически сгенерированный код шаблонов ("заглушки" stub и "скелета" skeleton). Распределение является пре-компиляционным процессом и границы распределения всегда являются явными. При использовании CORBA, распределенная часть пишется на IDL, а ядро приложения - на хост-языке, таком как C++.
Использование распределенного языка
Более удачной альтернативой, по сравнению с определением нового языка, такого как CORBA IDL, является расширение какого-либо существующего языка программирования дополнительными средствами поддержки программирования распределенных систем. Парадигма распределенных объектов предусматривает объектно-ориентированный подход программирования распределенных систем. Понятие распределенного объекта является расширением абстрактного типа данных, которое позволяет осуществлять вызовы сервисов, предусматриваемых в интерфейсе типа, вне зависимости от того где фактический сервис будет выполняться. При комбинировании с объестно-ориентированными свойствами, такими как наследование и полиморфизм, распределенные объекты предоставляют более динамичное и структурированное вычислительное окружение для распределенных приложений.
Дополнение стандарта Ada95 для распределенных систем (DSA) определяет некоторые расширения с помощью которых можно строить распределенные системы полностью написанные на языке программирования Ада. Все типы распределенных объектов, сервисы, которые они предусматривают, и тела удаленных методов согласованно описываются в пакетах Ады. Модель Ada95 аналогична модели Java/RMI. В обоих языках, язык IDL заменяется хорошо определенными языковыми конструкциями. Следовательно, язык прозрачно поддерживает вызовы удаленных процедур и вызовы методов удаленных объектов, причем, семантика распределения согласована с остальными элементами языка.
Предполагается, что программа, написанная на таком языке, будет взаимодействовать с программой, которая написана на том же языке, однако, такое ограничение обладает некоторыми полезными следствиями. Язык может предоставить собственные, более мощные средства поскольку он не ограничен необходимостью использования только общеупотребимых средств, которые доступны во всех хост языках. В Ada95, пользователь будет описывать спецификацию удаленных сервисов и реализовывать эти сервисы таким же ообразом как это осуществляется в случае обычных, не распределенных сервисов. Окружение Ada95 будет компилировать такой код генерируя файл "заглушки" (на стороне клиента) и файл "скелета", который автоматически включает тела реализации сервисов (на стороне сервера). Создание объектов, получение или регистрация ссылок на объекты, или адаптация "скелетов" объектов к пользовательской реализации объектов выполняется прозрачно поскольку окружение языка позволяет сохранять полный контроль над процессом разработки.
По сравнению с многопоточным/многонитиевым программированием, решение, которое основано на языковом расширении, эквивалентно решению, которое адаптировано к средствам поддержки многозадачности Ады. Написание распределенного приложения является таким же простым как и написание многозадачного приложения: при этом не рассматривается связывание с внешними библиотеками и отсутствует код "оберток" (wrapper). Большая часть низкоуровневой работы возлагается на язык программирования и его систему времени выполнения, что позволяет не отвлекать внимание программиста от прикладных задач приложения.
Дополнение стандарта Ada95 для распределенных систем
Принципиальная особенность дополнения к стандарту Ada95 для распределенных систем (DSA) заключается в том, что это дополнение предоставляет пользователю возможность разрабатывать свое приложение таким же самым образом, как в случае когда его приложение выполняется как набор из нескольких программ распределенной системы, так и в случае когда его приложение выполняется как одиночная программа не распределенной системы. Таким образом, дополнение стандарта Ada95 для распределенных систем было разработано для минимизации необходимых изменений исходных текстов, при преобразовании обычной не распределенной программы в распределенную программу.
Простейший путь начать разработку программы с учетом дополнения к стандарту Ada95 для распределенных систем - это начать разрабатывать приложение для не распределенной системы. Естественно, проект приложения должен учитывать то, что некоторые модули приложения будут доступны удаленно. В случае написания распределенной Ада-программы, пользователю необходимо учитывать, что некоторые компилируемые модули приложения уровня библиотеки будут отмечены директивами категорирования. Как правило, категорированию подлежат удаленно вызываемые модули и модули, которые предусматривают типы данных для удаленного доступа.
Чтобы гарантировать возможность распределенного выполнения на категорированные модули накладываются ограничения, которые допускают использовать в этих модулях ограниченный набор конструкций языка Ада. Например, если распределенная система не имеет общей памяти, то использование общих переменных должно быть запрещено. Для определения природы таких ограничений дополнение к стандарту Ada95 для распределенных систем предусматривает различные директивы категорирования. Каждая такая директива исключает использование некоторых конструкций языка в категорированных пакетах.
Конечно, пользователь может разрабатывать не распределенное приложение с помощью своего обычного окружения разработки. Следует особо подчеркнуть, что пользователь не нуждается в каких-либо специализированных средствах для разработки распределенного приложения. Например, пользователь может выполнять отладку своего приложения используя обычный отладчик. Следует заметить, что не следует отождествлять не распределенную программу с распределенной программой, которая состоит только из одной программы. Последняя построена с помощью средства конфигурирования и содержит комуникационную библиотеку.
Как только завершена разработка не распределенной версии программы, программа должна быть сконфигурирована в отдельные разделы (partitions). По сравнению с разработкой приложения этот этап необычайно прост. Этап конфигурирования состоит из отображения наборов компилируемых модулей в индивидуальные разделы распределенной программы, и спецификации отображения разделов на узлы компьютерной сети. Такое отображение осуществляется с помощью GLADE.
Распределенная версия приложения пользователя будет выполнять работу также как и не распределенная версия приложения. Однако, даже в случае когда из одного и того же исходного текста программы можно построить не распределенную и распределенную версию приложения, выполнение распределенной и не распределенной версий программы будет отличаться. Эти различия рассматриваются в последующих секциях (см. Директива Asynchronous и Директива All_Calls_Remote).
Разработка не распределенного приложения с целью сделать его в последствии распределенным является обычным подходом для новичков. Естественно, распределенное приложение не всегда можно написать так же как не распределенное. Например, клиент-серверное приложение не относится к этой категории поскольку несколько экземпляров клиентов могут быть одновременно активны. Подобные приложения очень легко разрабатывать используя GLADE, и мы рассмотрим это в последующих секциях.
Архитектура распределенного приложения Ada95
Распределенная система является объединением состоящим из одного или более узлов обработки и нуль или более узлов хранения данных. Распределенная программа включает в себе один или более раздел. Раздел распределенной программы - это совокупность библиотечных модулей. Взаимодействие разделов осуществляется с помощью общих данных или вызовов удаленных процедур (RPC). Пасивный раздел не содержит нити (потока) управления. На узле хранения данных можно сконфигурировать только пассивный раздел. Активный раздел содержит нуль или более нитей управления, и может быть сконфигурирован на узле обработки.
Библиотечный модуль является ключевым компонентом распределенного приложения Ada95. Пользователь может явно назначать библиотечные модули для разделов распределенного приложения. Разделение на разделы является пост-компиляционным процессом. Пользователь идентифицирует интерфейсные пакеты в процессе компиляции. Пакеты категорируются с помощью директив категорирования. Каждая из этих директив поддерживает использование одной из следующих классических парадигм:
- Удаленные подпрограммы:
Вызов удаленной подпрограммы, для программиста, подобен вызову обычной подпрограммы. Для удаленных подпрограмм может быть использовано связывание времени выполнения, которое применяется к ссылочным типам для подпрограмм. В основном, такие удаленные подпрограммы определяются в библиотечных модулях, которые категорированы как интерфейсы удаленного вызова (RCI - Remote Call Interface).- Распределенные объекты:
Для обозначения удаленных объектов могут быть определены специальные ссылочные типы. При вызове примитивной диспетчеризуемой операции, который осуществляется для объекта обозначаемого удаленной ссылкой, осуществляется прозрачный удаленный вызов раздела распределенной задачи, где располагается этот объект. Типы таких распределенных объектов определяются в библиотечных модулях, которые категорированы как удаленные типы (RT - Remote Types).- Общие объекты:
Глобальные данные могут использоваться несколькими активными разделами совместно, предоставляя хранилище, подобно общей памяти, общей файловой системе или базе данных. Защищенные объекты без входов позволяют организовать надежный параллельный доступ и обновление общих объектов. Это является ортогональным свойством представления распределенных объектов, которые могут быть доступны только через экспортируемые сервисы. Подобные общие объекты определяются в библиотечных модулях, которые категорированы как общие пассивные (SP - Shared Passive).Для удаленно вызываемых подпрограмм, определенных в библиотечном модуле, который категорирован как интерфейс удаленного вызова (RCI - Remote Call Interface) или как удаленные типы (RT - Remote Types) может осуществляться как статическое, так и динамическое связывание подпрограмм. Раздел распределенной программы на котором выполняется статически связанная удаленная подпрограмма может быть определен до осуществления вызова. Такой вызов называют статическим удаленным вызовом подпрограммы. В отличие от этого, удаленный метод или разыменование ссылки на удаленную подпрограмму являются динамически связанными удаленными вызовами поскольку раздел распределенной программы, на котором будет выполняться удаленная программа, определяется во время выполнения, в процессе осуществления фактического вызова.
В показанном на рисунке примере модули Data_1 и Data_2 являются общими пассивными (SP) библиотечными модулями. Модуль Data_1 сконфигурирован на пассивном разделе распределенной программы, который отображается на узел хранения данных. Разделы Partition_1 и Partition_2 являются активными разделами распределенной программы. Примечательно, что в некоторых случаях какой-либо раздел, например раздел Partition_2, может дублироваться. Для дублирования, модули Unit_2 и Unit_3, которые сконфигурированы на разделе Partition_2 должны предусматривать только динамически связываемые удаленные подпрограммы. В противном случае, какой-либо раздел, вызывающий удаленную подпрограмму модуля Unit_2, не сможет статически определить как осуществить удаленный вызов к двум экземплярам модуля Unit_2.
![]()
Директивы категорирования
Библиотечные модули могут быть категорированы в соответствии с ролью, которую они играют в распределенной программе. Директивы категорирования являются директивами библиотечных модулей, ограничивающими использование некоторых видов описаний, которые могут присутствовать в библиотечном модуле и возможно в его дочерних модулях, а также допустимую семантику зависимости, которую может иметь категорированный модуль. Существует несколько директив категорирования:
- Remote_Call_Interface
- Remote_Types
- Shared_Passive
- Pure
Последующие параграфы не представляют все детали семантики этих директив (все формальные подробности находятся в справочном руководстве по языку программирования Ada95 - Ada95 Reference Manual). Цель этих параграфов - дать интуитивное представление о назначении этих директив. Не категорированный библиотечный модуль называют обычным библиотечным модулем, и в распределенном приложении такой модуль не играет никакой специальной роли. Подобные модули дублируются в любом разделе распределенной программы, в котором они указаны.
Небольшое замечание: во избежание необходимости использования специфических библиотек времени выполнения для дополнения к стандарту Ada95 для распределенных систем (DSA), понятие удаленных рандеву не вводится в Ada95, задача одного раздела распределенной программы не может быть непосредственно вызвана из другого раздела. Следовательно, описания типов задач и общих защищенных типов с входами в категорированных библиотечных модулях Ады не допускается.
Директива объявления Pure
Эта директива не является специфичной для дополнения к стандарту Ada95 для распределенных систем (DSA) и может использоваться как в контексте категорированных пакетов, так и в контексте не категорированных пакетов. Пакеты Pure ("чистые") являются пре-элаборируемыми пакетами, которые не содержат описания каких-либо переменных или именованных ссылочных типов. Такие пакеты удобно использовать для описания типов, констант и подпрограмм, общих для некоторых категорированных пакетов. В противоположность этому, обычные пакеты не могут встречаться в контексте описаний категорированных пакетов. Поскольку "чистые" пакеты не имеют состояния, они могут дублироваться в нескольких разделах распределенной программы.
Директива Remote_Call_Interface
Обзор директивы Remote_Call_Interface
Библиотечные модули, категорированные с помощью этой директивы, определяют подпрограммы, которые могут быть вызваны и выполнены удаленно. Модуль RCI играет роль сервера для удаленных вызовов. Клиенты и сервер не имеют общего пространства памяти. Вызов подпрограммы, который обращен к одной из подобных подпрограмм является классической операцией вызова удаленной процедуры (RPC). Для этой операции используется статическое связывание, поскольку компилятор может точно идентифицировать вызываемую подпрограмму.
Динамическое связывание вызовов подпрограмм предусматривается с помощью двух механизмов:
- Разыменование значения ссылки на подпрограмму access_to_subprogram, например, значения имеющего ссылочный тип remote_access_to_subprogram (RAS), который ссылается (указывает) на удаленную подпрограмму.
- Диспетчеризуемый вызов, чей управляющий аргумент является операндом надклассового ссылочного типа (RACW - Remote Access on Class Wide types). Такие удаленные ссылочные типы также могут быть описаны в пакетах RCI.
Удаленный ссылочный тип (RAS или RCI) можно рассматривать как "полный" указатель (fat pointer), который является структурой состоящей из удаленного адреса и локального адреса (подобно URL: <protocol>://<remote-machine>/<local-directory>). Удаленный адрес должет обозначать хост (узез в сети) раздела распределенной задачи на котором создана соответствующая сущность, а локальный адрес описывает локальный адрес памяти на этом хосте.
Дублирование модулей RCI в распределенной системе маловероятно. Однако, возможна реализация допускающая существование нескольких копий модуля RCI, в случае, если эти копии будут гарантированно обеспечивать согласованное состояние для всех клиентов. В общем случае, обеспечение такой согласованности состояния достаточно затратно. Исходя из таких рассуждений, реализация может требовать уникальность модуля RCI в распределенной системе.
Обычные удаленные подпрограммы (RCI)
В показанном ниже примере, RCI_Bank предоставляет несколько удаленных сервисов: Balance, Transfert, Deposit и Withdraw. С вызывающей стороны, клиенты банка используют файлы "заглушек" (stub files) модуля RCI_Bank. На приемной стороне, банк-приемник использует файлы "скелетов" (skeleton files) модуля RCI_Bank, включающие тело этого пакета.
package Types is pragma Pure; type Customer_Type is new String; type Password_Type is new String; end Types;
with Types; use Types; package RCI_Bank is pragma Remote_Call_Interface; function Balance (Customer : in Customer_Type; Password : in Password_Type) return Integer; procedure Transfer (Payer : in Customer_Type; Password : in Password_Type; Amount : in Positive; Payee : in Customer_Type); procedure Deposit (Customer : in Customer_Type; Amount : in Positive); procedure Withdraw (Customer : in Customer_Type; Password : in Password_Type; Amount : in out Positive); end RCI_Bank;
with Types; use Types; with RCI_Bank; use RCI_Bank; procedure RCI_Client is B : Integer; C : Customer_Type := "rich"; P : Password_Type := "xxxx"; begin B := Balance (C, P); end RCI_Client;Ссылки на удаленные подпрограммы (RAS)
В следующем примере несколько "зеркальных" банков предлагают свои сервисы посредством одной и той же базы данных. Каждый банк регистрирует ссылку на каждый свой сервис в центральном банке. Клиент центрального банка запрашивает сервис у одного из "зеркальных" банков. Для удовлетворения запросов, модуль RCI RAS_Bank описывает тип Balance_Type, для доступа к удаленным подпрограммам (следует напомнить, что ссылочный тип, описываемый в удаленном модуле, может быть или ссылочным типом для подпрограмм, или надклассовым ссылочным типом).
Необходимо отметить, что подпрограмма, которая предоставляет удаленную ссылку, должна быть также удаленной. Следовательно, модуль Mirror_Bank является библиотечным модулем RCI.
with Types; use Types; package RAS_Bank is pragma Remote_Call_Interface; type Balance_Type is access function (Customer : in Customer_Type; Password : in Password_Type) return Integer; procedure Register (Balance : in Balance_Type); function Get_Balance return Balance_Type; -- [...] Другие сервисы end RAS_Bank;Показанный ниже код демонстрирует как "зеркальный" банк регестрирует свои сервисы в центральном банке.
with Types; use Types; package Mirror_Bank is pragma Remote_Call_Interface; function Balance (Customer : in Customer_Type; Password : in Password_Type) return Integer; -- [...] Другие сервисы end Mirror_Bank;
with RAS_Bank, Types; use RAS_Bank, Types; package body Mirror_Bank is function Balance (Customer : in Customer_Type; Password : in Password_Type) return Integer is begin return Something; end Balance; begin -- Регистрация динамически связываемой удаленной подпрограммы (Balance) -- с помощью статически связанной удаленной подпрограммы (Register) Register (Balance'Access); -- [...] Регистрация других сервисов end Mirror_Bank;В показанном ниже коде, клиент центрального банка запрашивает "зеркальный" банк и вызывает сервис Balance этого "зеркального" банка, путем разыменования удаленного ссылочного типа.
with Types; use Types; with RAS_Bank; use RAS_Bank; procedure Bank_Client is B : Integer; C : Customer_Type := "rich"; P : Password_Type := "xxxx"; begin -- Получиение динамически связанной удаленной подпрограммы -- с помощью статически связанной удаленной подпрограммы (Get_Balance). -- Разыменование для осуществления динамического вызова. B := Get_Balance.all (C, P); end Bank_Client;Ссылки на удаленные надклассовые типы (RACW)
Теперь предположим, что клиент банка подключен к банку через терминал. Банк хочет уведомить подключенного клиента о том, что другой клиент пересылает какую-либо сумму денег на его счет с помощью посылки сообщения на терминал. В следующем примере, терминал спроектирован как распределенный объект. Каждый клиент банка будет регистрировать свой терминалльный объект на сервере банка для последующего использования. В показанном ниже коде, тип Term_Type является корневым типом иерархии распределенных терминалов.
with Types; use Types; package Terminal is pragma Pure; type Term_Type is abstract tagged limited private; procedure Notify (My_Term : access Term_Type; Payer : in Customer_Type; Amount : in Integer) is abstract; private type Term_Type is abstract tagged limited null record; end Terminal;В показанном ниже коде, RCI-модуль RACW_Bank описывает удаленный надклассовый ссылочный тип Term_Access. Тип Term_Access будет ссылаться на распределенный объект. В следующей секции мы рассмотрим как можно описать производный тип, чтобы расширить тип Term_Type, как создать распределенный объект и как использовать ссылку на него.
with Terminal, Types; use Terminal, Types; package RACW_Bank is pragma Remote_Call_Interface; type Term_Access is access all Term_Type'Class; procedure Register (My_Term : in Term_Access; Customer : in Customer_Type; Password : in Password_Type); -- [...] Другие сервисы end RACW_Bank;Резюме по директиве Remote_Call_Interface
Модули вызова удаленных интерфейсов (модули RCI):
- Позволяют подпрограммам быть вызванными и выполненными удаленно
- Позволяют статическое связывание удаленных вызовов (для удаленных подпрограмм)
- Позволяют динамическое связывание удаленных вызовов (для удаленных ссылочных типов)
- Запрещают описание переменных и не удаленных ссылочных типов
- Предотвращают зависимость спецификации от обычных модулей
Директива Remote_Types
Обзор директивы Remote_Types
В отличие от модулей RCI, библиотечные модули, которые категорированы с помощью этой директивы, могут описывать распределенные объекты и их удаленные методы. Модули RCI и RT могут описывать удаленные ссылочные типы, как это было показано выше (для RACW). Какая-либо самостоятельная подпрограмма, описанная в модуле RT, не является удаленной подпрограммой. В отличие от модулей RCI, модули RT могут дублироваться на нескольких разделах распределенной программы. В таком случае, все сущности, которые описаны внутри этих модулей, будут отличаться друг от друга. Экземпляры этих модулей на каждом разделе распределенной программы, для которого они определены, - отличаются.
Распределенный объект
Если мы хотим реализовать средство уведомления, которое было предложено в предыдущей секции, нам необходимо описать тип, производный от типа Term_Type. Это можно выполнить с помощью модуля удаленных типов (RT), подобного модулю New_Terminal (см. ниже). Любой объект типа New_Term_Type будет распределенным объектом и любая ссылка на подобный объект будет "полным" указателем (fat pointer) или ссылкой на распределенный объект (см. описание Term_Access в Ссылки на удаленные надклассовые типы (RACW)).
with Types, Terminal; use Types, Terminal; package New_Terminal is pragma Remote_Types; type New_Term_Type is new Term_Type with null record; procedure Notify (My_Term : access New_Term_Type; Payer : in Customer_Type; Amount : in Integer); function Current return Term_Access; end New_Terminal;В показанном ниже коде, клиент регистрирует свое имя и свой терминал с помощью RACW_Bank. Следовательно, когда любой плательщик пересылает ему некоторую сумму денег RACW_Bank имеет возможность уведомить клиента о пересылке денежных средств.
with New_Terminal, RACW_Bank, Types; use New_Terminal, RACW_Bank, Types; procedure Term1_Client is My_Term : Term_Access := Current; Customer : Customer_Type := "poor"; Password : Password_Type := "yyyy"; begin Register (My_Term, Customer, Password); -- [...] Выполнение других действий end Term1_Client;В показанном ниже коде, какой-либо второй клиент, плательщик, регистрирует свой терминал в банке и выполняет пересылку денег для первого клиента.
with New_Terminal, RACW_Bank, Types; use New_Terminal, RACW_Bank, Types; procedure Term2_Client is My_Term : Term_Access := Current; Payer : Customer_Type := "rich"; Password : Password_Type := "xxxx"; Payee : Customer_Type := "poor"; begin Register (My_Term, Payer, Password); Transfer (Payer, Password, 100, Payee); end Term2_Client;В показанном ниже коде, мы описываем общий дизайн процедуры Transfer. Сначала выполняются классические операции Withdraw и Deposit Затем RACW_Bank получает терминал получателя денег (если он есть) и вызывает диспетчеризуемый вызов, разыменовывая распределенный объект Term. Ссылка проверяется во время выполнения, и выполнение этой операции осуществляется на том разделе распределенной программы, на котором располагается распределенный объект.
with Types; use Types; package body RACW_Bank is procedure Register (My_Term : in Term_Access; Customer : in Customer_Type; Password : in Password_Type) is begin Insert_In_Local_Table (My_Term, Customer); end Register; procedure Transfer (Payer : in Customer_Type; Password : in Password_Type; Amount : in Positive; Payee : in Customer_Type) is -- Поиск терминала заказчика. Term : Term_Access := Find_In_Local_Table (Payee); begin Withdraw (Payer, Amount); Deposit (Payee, Amount); if Term /= null then -- Уведомление терминала получателя. Notify (Term, Payer, Amount); end if; end Transfer; -- [...] Другие сервисы end RACW_Bank;Передача динамических структур данных
with Ada.Streams; use Ada.Streams; package String_Array_Stream is pragma Remote_Types; type List is private; procedure Append (L : access List; O : in String); function Delete (L : access List) return String; private type String_Access is access String; type Node; type List is access Node; type Node is record Content : String_Access; Next : List; end record; procedure Read (S : access Root_Stream_Type'Class; L : out List); procedure Write (S : access Root_Stream_Type'Class; L : in List); for List'Read use Read; for List'Write use Write; end String_Array_Stream;Не-удаленные ссылочные типы не могут быть описаны в публично доступной части модуля удаленных типов (RT). Однако, существует возможность описания приватных не-удаленных ссылочных типов наряду с тем, что пользователь обеспечит соответствующие подпрограммы передачи (marshalling procedures), которые будут служить механизмом посылки значений типа в поток взаимодействия. Показанный ниже код демонстрирует как можно передать связанную динамическую структуру данных.
Описание пакета предусматривает описание типа связанного списка неограниченных строк. Реализация подпрограмм передачи может иметь следующий вид:
package body String_Array_Stream is procedure Read (S : access Root_Stream_Type'Class; L : out List) is begin if Boolean'Input (S) then L := new Node; L.Content := new String'(String'Input (S)); List'Read (S, L.Next); else L := null; end if; end Read; procedure Write (S : access Root_Stream_Type'Class; L : in List) is begin if L = null then Boolean'Output (S, False); else Boolean'Output (S, True); String'Output (S, L.Content.all); List'Write (S, L.Next); end if; end Write; -- [...] Другие сервисы end String_Array_Stream;Резюме для модулей удаленных типов (RT)
Модули удаленных типов (RT):
- Поддерживают описания распределенных объектов
- Позволяют осуществлять динамическое связывание удаленных вызовов (через удаленные ссылочные типы)
- Позволяют использовать не-удаленные ссылочные типы (с обеспечением подпрограмм передачи)
- Не могут иметь спецификацию, которая зависит от обычных модулей
Директива Shared_Passive
Обзор директивы Shared_Passive
Сущности описанные в библиотечном модуле, который категорирован с помощью этой директивы, будут отображены в общее виртуальное адресное пространство (файл, память, база данных). Когда два раздела распределенной программы используют такой библиотечный модуль, они могут взаимодействовать между собой посредством чтения или записи значений одних и тех же переменных в общем модуле. Это обеспечивает поддержку парадигмы согласованных общих переменных. Для обеспечения атомарности доступа к общим данным, в таких модулях можно описывать защищенные объекты без входов, реализуя простой механизм транзакций. Когда адресное пространство является файлом или базой данных, пользователь может воспользоваться преимуществами свойств устойчивости, которые обеспечиваются подобными узлами хранения данных.
Общие и защищенные объекты
В показанним ниже коде, мы описываем два вида общих объектов. Объект с внешней синхронизацией External_Synchronization, который требует, чтобы различные разделы, обновляющие эти данные, самостоятельно выполняли синхронизацию доступа для предотвращения конфликтных операций, выполняемых с общим объектом. Объект с внутренней синхронизацией Internal_Synchronization, предусматривающий встроенные средства обеспечения атомарности операций, выполняемых с общим объектом. Следует заметить, что в общих пассивных модулях допустимо использовать только без-входные подпрограммы.
package Shared_Objects is pragma Shared_Passive; Max : Positive := 10; type Index_Type is range 1 .. Max; type Rate_Type is new Float; type Rates_Type is array (Index_Type) of Rate_Type; External_Synchronization : Rates_Type; protected Internal_Synchronization is procedure Set (Index : in Index_Type; Rate : in Rate_Type); procedure Get (Index : in Index_Type; Rate : out Rate_Type); private Rates : Rates_Type; end Internal_Synchronization; end Shared_Objects;Резюме по директиве Shared_Passive
- Позволяют осуществлять прямой доступ к данным из различных разделов распределенной программы
- Предусматривают поддержку для общей (распределенной) памяти
- Поддерживают защиту памяти, с помощью использования защищенных объектов без входов
- Предотвращают зависимость спецификации от обычных модулей
Дополнительные сведения о директивах категорирования
Переменные и не-удаленные ссылочные типы
В описаниях пакетов RT или RCI, запрещается описывать переменные, а не-удаленные ссылочные типы могут быть описаны наряду с явним определением подпрограмм передачи (marshaling subprograms). (см. Передача динамических структур данных)..
Ошибки RPC
Вызовы выполняются хотя бы один раз: они выполняются однократно или терпят неудачу, возбуждая исключение. При возникновении ошибки взаимодействия (communication error), возбуждается исключение System.RPC.Communication_Error.
Исключения
Любое исключение, возбужденное вызовом удаленного метода или подпрограммы, распространяется обратно, к вызвавшему клиенту. Семантика исключений сохраняется традиционной для Ады.
package Internal is Exc : exception; end Internal;
package Rem_Pkg2 is pragma Remote_Call_Interface; procedure Subprogram; end Rem_Pkg2;
package Rem_Pkg1 is pragma Remote_Call_Interface; procedure Subprogram; end Rem_Pkg1;Предположим, что пакеты Rem_Pkg2, Internal и Rem_Exc_Main расположены на разделе Partition_1, а пакет Rem_Pkg1 - на разделе Partition_2.
with Rem_Pkg1, Ada.Exceptions; use Ada.Exceptions; package body Rem_Pkg2 is procedure Subprogram is begin Rem_Pkg1.Subprogram; exception when E : others => Raise_Exception (Exception_Identity (E), Exception_Message (E)); end Subprogram; end Rem_Pkg2;
with Internal, Ada.Exceptions; use Ada.Exceptions; package body Rem_Pkg1 is procedure Subprogram is begin Raise_Exception (Internal.Exc'Identity, "Message"); end Subprogram; end Rem_Pkg1;
with Ada.Text_IO, Ada.Exceptions; use Ada.Text_IO, Ada.Exceptions; with Rem_Pkg2, Internal; procedure Rem_Exc_Main is begin Rem_Pkg2.Subprogram; exception when E : Internal.Exc => Put_Line (Exception_Message (E)); -- Вывод "Message" end Rem_Exc_Main;Кргда подпрограмма Rem_Pkg1.Subprogram на разделе Partition_1 возбуждает исключение Internal.Exc, это исключение распространяется обратно на раздел Partition_2 Поскольку исключение Internal.Exc не определено на разделе Partition_2, то возможность "отловить" и обработать это исключение без обработчика when others - отсутствует. Когда это исключение повторно возбуждается в подпрограмме Rem_Pkg1.Subprogram, оно распространяется на раздел Partition_1. В этой ситуации, исключение Internal.Exc является видимым, и его можно обработать также как это делается в обычной Ада-программе (программе, которая состоит из одного раздела). Естественно, что сообщение исключения также сохраняется.
Директива Asynchronous
По умолчанию, удаленный вызов является блокирующим: вызывающий клиент ожидает завершения обработки вызова и приема выходного потока. В противоположность этому, удаленная подпрограмма, отмеченная директивой Asynchronous, позволяет осуществлять асинхронное выполнение статически и динамически связанных удаленных вызовов. Вызов асинхронной процедуры не ожидает завершение удаленного вызова, и позволяет вызывающему клиенту продолжать свое выполнение. Такая удаленная процедура должна иметь только in-параметры, и любое исключение, возбуждаемое в процессе выполнения удаленной процедуры, будет утеряно.
Когда директива Asynchronous применяется для обычной подпрограммы с in-параметрами, любой вызов этой подпрограммы будет осуществляться асинхронно. Следующий пример демонстрирует описание Asynchronous_RCI.Asynchronous.
package Asynchronous_RCI is pragma Remote_Call_Interface; procedure Asynchronous (X : Integer); pragma Asynchronous (Asynchronous); procedure Synchronous (X : Integer); type Asynchronous_RAS is access procedure (X : Integer); pragma Asynchronous (Asynchronous_RAS); end Asynchronous_RCI;
package Asynchronous_RT is pragma Remote_Types; type Object is tagged limited private; type Asynchronous_RACW is access all Object'Class; pragma Asynchronous (Asynchronous_RACW); procedure Asynchronous (X : Object); procedure Synchronous (X : in out Object); function Create return Asynchronous_RACW; private type Object is tagged limited null record; end Asynchronous_RT;Директива Asynchronous применяется к удаленной ссылке на подпрограмму (RAS). Асинхронаая ссылка на подпрограмму может быть как асинхронной, так и синхронной, в зависимости от фактически обозначаемой подпрограммы с помощью этого ссылочного значения. Например, в показанном ниже коде, удаленный вызов (1) является асинхронным, а удаленный вызов (2) - синхронным.
Директива Asynchronous может также применяться для ссылок на удаленные надклассовые типы (RACW). В этом случае, вызов любого метода с in-параметрами всегда будет осуществляться асинхронно. Таким образом, в показанном ниже коде, вызов удаленного метода (3) является асинхронным, а вызов удаленного метода (4) - синхронным.
with Asynchronous_RCI, Asynchronous_RT; use Asynchronous_RCI, Asynchronous_RT; procedure AsynchronousMain is RAS : Asynchronous_RAS; RACW : Asynchronous_RACW := Create; begin -- Асинхронный динамически связываемый удаленный вызов (1) RAS := Asynchronous_RCI.Asynchronous'Access; RAS (0); -- Abbrev for RAS.all (0) -- Синхронный динамически связываемый удаленный вызов (2) RAS := Asynchronous_RCI.Synchronous'Access; RAS (0); -- Асинхронный динамически связываемый удаленный вызов (3) Asynchronous (RACW.all); -- Синхронный динамически связываемый удаленный вызов (4) Synchronous (RACW.all); end AsynchronousMain;Это свойство поддерживает парадигму согласованной передачи сообщений. Пользователь должен учитывать, что эта парадигма, и, в частности, асинхронные удаленные вызовы обладают некоторыми отрицательными свойствами:
- Нарушается семантика оригинальных (удаленных) процедур
- Возможен механизм, эквивалентный удаленному GOTO
- Усложняется разработка и отладка в не-распределенном контексте
- Потенциально возможно состояние "состязания за ресурс"
Для иллюстрации последнего, рассмотрим следующий пример:
package Node_2 is pragma Remote_Call_Interface; procedure Send (X : Integer); pragma Asynchronous (Send); end Node_2;
package body Node_2 is V : Integer := 0; procedure Send (X : Integer) is begin V := X; end Send; end Node_2;
package Node_1 is pragma Remote_Call_Interface; procedure Send (X : Integer); pragma Asynchronous (Send); end Node_1;
with Node_2; package body Node_1 is procedure Send (X : Integer) is begin Node_2.Send (X); end Send; end Node_1;
with Node_1, Node_2; procedure Non_Deterministic is begin Node_1.Send (1); Node_2.Send (2); end Non_Deterministic;Предположим, что выбрана следующая конфигурация: Main располагается на разделе Partition_0, Node_1 - на разделе Partition_1, и Node_2 - на разделе Partition_2 Если процедуры Node_1.Send и Node_2.Send были синхронными или если не было определено времени ожидания для сетевого взаимодействия, то мы получим следующий порядок RPC: Main удаленно вызывает Node_1.Send, которая удаленно вызывает Node_2.Send, что установит V в 1. Затем, Main удаленно вызывает Node_2.Send и установливает V в 2.
Теперь, предположим, что обе процедуры Send являются асинхронными, и что соединение взаимодействия между разделами Partition_1 и Partition_2 очень медленное. В такой ситуации, достаточно часто возникает следующий сценарий. Main удаленно вызывает Node_1.Send и не блокируется. Немедленно, после этого вызова, Main удаленно вызывает Node_2.Send и установливает V в 2. Как только это выполнено, удаленный вызов Node_1.Send завершается на разделе Partition_1 и удаленно вызывается Node_2.Send, который устанавливает V в 1.
Директива All_Calls_Remote
Директива All_Calls_Remote в модуле RCI принудительно направляет вызовы удаленных процедур через подсистему коммуникации (взаимодействия), даже для локальных вызовов. Это облегчает отладку приложения в не-распределенном окружении, которое подобно распределенному, поскольку подсистема взаимодействия (включая процедуры передачи (marshalling и unmarshalling)) может быть оттестирована на одиночном узле.
В некоторых случаях, поведение не-распределенного приложения может отличаться от распределенного приложения, размещенного на одном разделе распределенной программы. Это может произойти при одновременном использовании директив All_Calls_Remote и Asynchronous (для примера см. Директива Asynchronous). Другим случаем является ситуация, когда операции передачи (marshalling) возбуждают исключения. В показанном ниже примере, когда модуль ACR_RCI является пакетом All_Calls_Remote, программа возбуждает исключение Program_Error. Когда модуль ACR_RCI не является пакетом All_Calls_Remote, программа выполняется без сообщений об ошибках.
with Ada.Streams; use Ada.Streams; package ACR_RT is pragma Remote_Types; type T is private; private type T is new Integer; procedure Read (S : access Root_Stream_Type'Class; X : out T); procedure Write (S : access Root_Stream_Type'Class; X : in T); for T'Read use Read; for T'Write use Write; end ACR_RT;
package body ACR_RT is procedure Read (S : access Root_Stream_Type'Class; X : out T) is begin raise Program_Error; end Read; procedure Write (S : access Root_Stream_Type'Class; X : in T) is begin raise Program_Error; end Write; end ACR_RT;
with ACR_RT; use ACR_RT; package ACR_RCI is pragma Remote_Call_Interface; pragma All_Calls_Remote; procedure P (X : T); end ACR_RCI;
package body ACR_RCI is procedure P (X : T) is begin null; end P; end ACR_RCI;
with ACR_RCI, ACR_RT; procedure ACR_Main is X : ACR_RT.T; begin ACR_RCI.P (X); end ACR_Main;Настраиваемые категорированные модули
generic package Generic_RCI is pragma Remote_Call_Interface; procedure P; end Generic_RCI;
with Generic_RCI; package RCI_Instantiation is new Generic_RCI; pragma Remote_Call_Interface (RCI_Instantiation);
with Generic_RCI; package Normal_Instantiation is new Generic_RCI;Любой из категорированных модулей может быть настраиваемым. Следует учесть, что экземпляры настроенных модулей не наследуют категорирование настраиваемых модулей автоматически, и они должны быть категорированы явно. Если экземпляр настроенного модуля не категорирован, то он является обычным компилируемым модулем. Подобно другим категорированным модулям, категорированные экземпляры настроенных модулей должны располагаться на уровне библиотеки. При конкретизации настраиваемых модулей к ним применяются обычные для категорированных модулей ограничения (в частности, для формальных параметров настройки).
Зависимости категорированных модулей
Каждая директива категорирования обладает специфическими правилами видимости. Общим правилом является следующее: RCI > RT > SP > Pure, где сравнение показывает допустимую семантическую зависимость. Это подразумевает, что пакет Remote_Types может сделать видимыми в своей спецификации только модули: Remote_Types, Shared_Passive и Pure.
Подсистема взаимодействия разделов
Операции пересылки (marshalling и unmarshalling)
Подсистема взаимодействия разделов (PCS - Partition Communication Subsystem) осуществляет передачу и прием (marshall и unmarshall) данных вызывающего клиента и сервера через поток типа System.RPC.Params_Stream_Type:
type Params_Stream_Type (Initial_Size : Ada.Streams.Stream_Element_Count) is new Ada.Streams.Root_Stream_Type with private;Этот тип является контейнером передачи данных между разделами распределенной программы. Корневым типом является тип Root_Stream_Type, который определяет базовый тип потока и две абстрактные операции: Write, для помещения в поток, и Read, для извлечения из потока, объектов типа Stream_Element_Array, которые являются массивом байтов представляющих индивидуальные данные.
Чтение и запись потоков осуществляется с помощью использования четырех атрибутов:
- 'Write:
записывает элемент в поток, допустимо только лоя ограниченных (constrained) типов- 'Read:
читает ограниченный (constrained) элемент из потока- 'Output:
то же самое, что и 'Write, но, при необходимости, дополнительно записывает в поток границы и дискриминанты- 'Input:
то же самое, что и 'Read, но, при необходимости, дополнительно читает из потока границы и дискриминанты (атрибут 'Input обозначает функцию)Любой Ада-компилятор предусматривает, принимаемые по умолчанию, операции 'Read и 'Write. Однако, ответственность за обеспечение принимаемых по умолчанию операций 'Read и 'Write, которые будут надежно работать в гетерогенной архитектуре, возлагается на реализацию подсистемы взаимодействия разделов (см. Гетерогенная система).
Пользователь может осуществлять совмещение (переопределение) этих операций (исключая предопределенные типы). Совмещение с текстуальной версией, обеспечивает пользователю возможность отладки своего приложения (даже вне использования даполнения для распределенных систем).
with Ada.Streams; use Ada.Streams; package New_Integers is pragma Pure; type New_Integer is new Integer; procedure Read (S : access Root_Stream_Type'Class; V : out New_Integer); procedure Write (S : access Root_Stream_Type'Class; V : in New_Integer); for New_Integer'Read use Read; for New_Integer'Write use Write; end New_Integers;
package body New_Integers is procedure Read (S : access Root_Stream_Type'Class; V : out New_Integer) is B : String := String'Input (S); begin V := New_Integer'Value (B); end Read; procedure Write (S : access Root_Stream_Type'Class; V : in New_Integer) is begin String'Output (S, New_Integer'Image (V)); end Write; end New_Integers;Язык принуждает пользователя обеспечивать операции чтения и записи для не-удаленных ссылочных типов. Передача какого-либо ссылочного значения, путем простой записи его содержимого в поток, не имеет смысла когда значение передается на другой раздел распределенной задачи (разделы используют различные адресные пространства памяти). Для передачи не-удаленных ссылочных типов см. Передача динамических структур данных.
Неверная удаленная диспетчеризация
Когда удаленная подпрограмма принимает параметр надклассового типа существует риск использования объекта производного типа, который нельзя переслать. В качестве примера, рассмотрим следующую ситуацию. Предположим, что существует тип Root_Type. Предположим также, что существует удаленная процедура, которая принимает параметр типа Root_Type'Class. В этом случае, пользователь может вызвать эту процедуру и передать ей как аргумент экземпляр типа Derived_Type, который является типом, производным от типа Root_Type, причем, Derived_Type расширяет тип Root_Type дополнительным полем, которое содержит тип задачи. В результате, это приводит к попытке передачи, между разделами распределенной программы, непересылаемого типа данных.
Для предотвращения подобных ситуаций, параграф E.4(18) руководства по языку программирования Ada95 объясняет, что любой фактический тип используемый как параметр удаленного вызова, чей формальный тип является надклассовым типом, должен быть описан в видимой части пакета, который определен как Pure или Remote_Types пакет. Это требование сохраняется также для удаленных функций, которые возвращают значения надклассового типа. Таким образом, фактически используемый тип должен быть приемлемым для непосредственного указания в том месте, в котором указан корневой тип. Если удаленной подпрограмме передан "плохой" объект, то в точке вызова подпрограммы возбуждается исключение Program_Error.
Идентификаторы разделов
Атрибут U'Partition_ID идентифицирует раздел распределенной программы, где осуществлена элаборация модуля U. Для этой цели PCS предусматривает целочисленный тип Partition_ID, который уникально обозначает раздел распределенной программы. Примечательно, что Partition_ID представляется как универсальное целое, и не имеет смысла вне PCS. Стандарт требует чтобы в одно и тоже время два разных раздела распределенной программы имели различные Partition_ID. Partition_ID может назначаться или не назначаться статически (во время компиляции или компоновки программы). Partition_ID может зависеть или не зависеть от физического расположения раздела.
Partition_ID можно использовать для проверки того, что пакет RCI сконфигурирован локально.
with RCI; with Ada.Text_IO; procedure Check_PID is begin if RCI'Partition_ID = Check_PID'Partition_ID then Ada.Text_IO.Put_Line ("package RCI is configured locally"); else Ada.Text_IO.Put_Line ("package RCI is configured remotely"); end if; end Check_PID;Одновременные удаленные вызовы
Спецификация PCS не определяет сколько нитей (потоков) управления должно быть доступно для входящих сообщений и ожидания завершения. Однако, от реализации PCS требуется обеспечение реентерабельности, что позволяет обслуживать одновременные вызовы удаленных подпрограмм внутри раздела сервера. Это подразумевает, что на уровне реализации PCS управляет пулом вспомогательных задач. Это (отдельно от производительности) не видимо для пользователя.
Согласованность и элаборация
Библиотечный модуль является согласованным, если одна и та же версия его описаний используется всеми модулями, которые от него зависят. Такое же требование предъявляется к модулю, на который ссылаются несколько разделов распределенной программы. Если модуль U - это общий пассивный библиотечный модуль или библиотечный модуль RCI, и он включен в какой-либо раздел P распределенной программы, то при элаборации другого раздела P1 этой же распределенной программы зависящего от модуля U, версия которого отличается, произойдет ошибка связывания. В результате этой ошибки, в процессе элаборации, в одном или обоих разделах распределенной программы может быть возбуждено исключение Program_Error.
Атрибут U'Version выдает строку, которая идентифицирует версию описания модуля U и любого описания модуля от которого он зависит. Атрибут U'Version_Body выдает строку, которая идентифицирует версию тела модуля U. Эти атрибуты используются PCS для проверки согласованности приложения.
После элаборации библиотечных модулей, но перед вызовом головной подпрограммы распределенной программы, PCS проверяет версии RCI-модулей, и только после этого принимает любые входящие RPC. Чтобы гарантировать безопасность вызова принятых "заглушек", любой поступивший RPC остается в ожидании до тех пор, пока раздел распределенной программы не завершит процесс элаборации.
Принудительное завершение (abortion) и прекращение (termination)
Если конструкция, содержащая удаленный вызов, завершена принудительно (с помощью abort), то вызов удаленной подпрограммы отменяется. Будет-ли выполнение удаленной процедуры завершено немедленно, как результат принудительного завершения, определяется реализацией.
Какой-либо активный раздел распределенной задачи прекращает свое выполнение при прекращении выполнения его задачи окружения. Другими словами, раздел распределенной задачи не может прекратить свое выполнение до того как прекратит свое выполнение Ада-программа. Принят стандартный механизм завершения, однако он может быть расширен дополнительными правилами (для примеров см. Атрибут 'Termination для разделов).
Основные свойства в одном примере
Пример, который показан на рисунке ниже, демонстрирует основные свойства дополнения к стандарту Ada95 для распределенных систем (DSA). Система основана на наборе "фабрик" (factory), "работников" (worker) и одного "хранилища" (storage). Каждая сущность является самостоятельным разделом. "Фабрика" нанимает "работника" из пула работников (hire - 1) и назначает для работника работу (query - 2). "Работник" выполняет работу и сохраняет результат (reply - 3) в общем для всех фабрик хранилище. После этого, "работник" уведомляет "фабрику" о завершении своей работы (notify - 4).
![]()
Когда "работник" завершает свою работу, результат работы должен быть сохранен в общем хранилище. Для выполнения этого, мы определяем защищенную область в SP-пакете Storage (см. код ниже). Защищенный объект без входов гарантирует атомарность доступа к этой области.
package Storage is pragma Shared_Passive; protected Queue is procedure Insert (Q, R : Integer); procedure Remove (Q : in Integer; R : out Integer); private -- Other declarations end Queue; end Storage;Общим пакетом является Remote_Types-пакет, который определяет большинство удаленных сервисов показанной системы (см. код примера ниже). Во-первых, мы определяем способ с помощью которого "работники" сигнализируют о завершении своей работы. Этот механизм обратного вызова (callback) реализуется с помощью RAS Notify.
with Storage; use Storage; package Common is pragma Remote_Types; type Notify is access procedure (Q : Integer); pragma Asynchronous (Notify); type Worker is abstract tagged limited private; procedure Assign (W : access Worker; Q : in Integer; N : in Notify) is abstract; type Any_Worker is access all Worker'Class; pragma Asynchronous (Any_Worker); private type Worker is abstract tagged limited null record; end Common;Мы описываем абстрактный тэговый тип Worker, который необходим как корневой тип иерархии всех распределенных объектов. Процедура Assign позволяет какой-либо "фабрике" указывать для "работника" работу и способ сигнализирования об окончании работы. Тип Any_Worker - это удаленный надклассовый ссылочный тип (RACW). Другими словами, это ссылка на распределенный объект любого типа, который принадлежит к классу Worker (т.е. производного от типа Worker). Примечательно, что удаленные ссылочные типы (Any_Worker и Notify) описаны как асинхронные. Следовательно, любая переопределенная процедура Assign будет выполняться асинхронно. Чтобы быть асинхронным, какой-либо объект типа Notify должен быть ссылкой на асинхронную процедуру.
Тип New_Worker является производным типом от типа Worker и его процедура Assign - переопределена.
with Common, Storage; use Common, Storage; package New_Workers is pragma Remote_Types; type New_Worker is new Worker with private; procedure Assign (W : access New_Worker; Q : Integer; N : Notify); private type New_Worker is new Worker with record NewField : Field_Type; -- [...] Другие поля end record; end New_Workers;Следующий код демонстрирует как описать следующее поколение "работников" New_New_Worker от первого поколения New_Worker. Как говорилось ранее, этот RT-пакет может дублироваться на различных разделах распределенного приложения для получения нескольких типов "работников", а также нескольких удаленных "работников".
with Common, Storage, New_Workers; use Common, Storage, New_Workers; package New_New_Workers is pragma Remote_Types; type New_New_Worker is new New_Worker with private; procedure Assign (W : access New_New_Worker; Q : Integer; N : Notify); private type New_New_Worker is new New_Worker with record NewField : Field_Type; -- [...] Другие поля end record; end New_New_Workers;В следующем коде, мы описываем уникальное место, в котором "работники" ожидают работу. Пакет Worker_City является пакетом Remote_Call_Interface, который обслуживает "наем" и "увольнение" "работников". В отличие от пакетов Remote_Types, пакеты Remote_Call_Interface не могут дублироваться и назначаются на один определенный раздел распределенной программы.
with Common; use Common; package Worker_City is pragma Remote_Call_Interface; procedure Insert (W : in Any_Worker); procedure Remove (W : out Any_Worker); end Worker_City;Для того чтобы использовать еще больше возможностей, предоставляемых дополнением к стандарту Ada95 для распределенных систем (DSA), пакет Factory можно описать как настраиваемый RCI-пакет. В этом случае, каждая конкретизация настраиваемого пакета будет определять новую "фабрику" (см. пример ниже). Чтобы быть RCI-пакетом, экземпляр настроенного модуля, который получен в результате конкретизации, должен быть категорирован.
with Storage; use Storage; generic package Factory is pragma Remote_Call_Interface; procedure Notify (Q : Integer); pragma Asynchronous (Notify); end Factory;
with Factory; package NewFactory is new Factory; pragma Remote_Call_Interface (NewFactory);Основы GLADE
Эта глава описывает обычный способ использования GLADE для компиляции распределенных Ада-программ.
Знакомство с GLADE
Распределенное приложение Ada95 состоит из нескольких разделов, которые могут одновременно выполняться на одном и том же компьютере, или даже могут быть распределены между компьютерами сети. Способ взаимодействия разделов описывается в приложении E руководства по языку программирования Ada95 (Ada 95 Reference Manual, Annex E).
Раздел является набором компилируемых модулей, которые компонуются вместе, в единый исполняемый файл. Распределенная программа состоит из двух или более взаимодействующих между собой разделов.
Дополнение стандарта Ada95 для распределенных систем (DSA - Distributed Systems Annex) не описывает способ конфигурирования распределенного приложения. Таким образом, определение содержимого разделов распределенной программы и указание компьютеров, на которых эти разделы будут исполняться, возлагается на пользователя.
Инструментальная утилита gnatdist и ее язык конфигурирования позволяют пользователю разделить свою программу на разделы и указать компьютеры на которых будут исполняться индивидуальные разделы распределенного приложения.
Утилита gnatdist читает файл конфигурации (синтаксис этого файла описывается в секции Язык конфигурирования) и осуществляет построение нескольких самостоятельных исполняемых файлов. При этом, каждому разделу распределенного приложения соответствует определенный исполняемый файл. Кроме того, утилита gnatdist заботится о том, чтобы при запуске на выполнение исполняемых файлов разделов каждому исполняемому файлу раздела были переданы необходимые параметры (по умолчанию).
Как конфигурировать распределенное приложение
- Чтобы ознакомиться с окружением GLADE, напишите не-распределенное Ада-приложение. Используйте директивы категорирования для указания пакетов, которые могут быть вызваны удаленно.
- Когда не-распределенное приложение будет работать, напишите конфигурационный файл, который отобразит категорированные пакеты пользователя на определенные разделы распределенного приложения. В частности, это относится к пакетам удаленно вызываемых интерфейсов и к пакетам удаленных типов. Определите главную процедуру распределенного приложения (см. Атрибут раздела 'Main).
- Напечатайте команду "gnatdist <имя_файла_конфигурации>".
- Запустите распределенное приложение с помощью скрипта запуска интерпретатора shell или с помощью Ада-программы по умолчанию (в зависимости от опций стартера (Starter), см. Директива Starter).
Опции командной строки gnatdist
gnatdist [опции] configuration-file [list-of-partitions]В настоящее время gnatdist использует те же опции, что и утилита gnatmake. По умолчанию, в процессе своей работы, gnatdist отображает отчет конфигурации и выполняемые действия. Опция -n позволяет gnatdist отбрасывать этап перекомпиляции не-распределенного приложения.
Имена всех конфигурационных файлов должны иметь суффикс .cfg. Поскольку пользователю могут потребоваться несколько распределенных конфигураций (в зависимости от загрузки или каких-либо других характеристик вычислительного окружения), то для одного и того же приложения могут существовать несколько файлов конфигурации.
При указании списка разделов в командной строке запуска gnatdist осуществляется построение только тех разделов, которые указаны в командной строке. Следующий пример демонстрирует, что пользователь может напечатать:
gnatdist <configuration> <partition_2> <partition_3>Действия выполняемые gnatdist
Ниже перечислены действия выполняемые gnatdist в процессе построения распределенного приложения:
- Осуществляется компиляция каждого компилируемого модуля программы в соответствующий объектный модуль (точно также как и в случае не-распределенной программы). Это достигается путем вызова утилиты gnatmake для исходных текстов различных разделов распределенного приложения.
- "Заглушки" и "скелеты" компилируются в объектные модули (это фрагменты кода, которые позволяют разделу, который выполняется на машине A, взаимодействовать с разделом, который выполняется на машине B). Выполняются различные проверки меток времени создания/модификации, чтобы избежать бесполезную перекомпиляцию кода и генерацию "заглушек".
- gnatdist выполняет несколько проверок целостности/согласованности. Например, проверяется, что все пакеты, которые отмечены как пакеты интерфейса удаленного вызова (RCI) и общие пассивные пакеты (SP), отображены на разделы. Кроме того, проверяется, что пакеты RCI и/или SP отображаются только на один раздел распределенного приложения.
- В заключение, создаются исполняемые файлы для каждого раздела распределенной программы. Код запуска разделов встроен в главный раздел программы, за исключением случаев указания других опций (см. Директива Starter). В этом случае, осуществляется генерация скрипта командного интерпретатора shell (также возможна ситуация, когда ничего не генерируется) для запуска разделов на соответствующих машинах. Это может быть полезно когда кому-либо необходима возможность написания клиент/серверных приложений для которых заранее не извесно число экземпляров разделов.
Язык конфигурирования
Язык конфигурирования является Ада-образным языком, который развивается по мере развития возможностей GLADE. Большинство атрибутов и директив могут быть переопределены во время выполнения с помощью использования аргументов командной строки или переменных окружения.
Зарезервированные слова
Все зарезервированные (ключевые) слова Ады являются также зарезервированными словами языка конфигурирования GLADE. Утилита gnatdist генерирует полноценный Ада-код, который необходим для построения различных исполняемых файлов. С целью предотвращения конфликтов именования, между Адой и языком конфигурирования GLADE, зарезервированы все ключевые слова Ады, даже если эти слова непосредственно не используются языком конфигурирования GLADE.
Кроме того, существуют три новых зарезервированных слова:
- configuration для инкапсуляции конфигурации
- Partition является предопределенным типом, который предназначен для описания разделов
- Channel является предопределенным типом, который предназначен для описания каналов между разделами.
Директивы и спецификаторы представления
Существует возможность изменения принимаемого по умолчанию поведения конфигурации путем указания какой-либо директивы.
PRAGMA ::= pragma PRAGMA_NAME [(PRAGMA_ARGUMENTS)];Также, существует возможность изменения принимаемого по умолчанию поведения всех разделов (или каналов) путем указания атрибутов, которые относятся к предопределенному типу Partition (или Channel).
REPRESENTATION_CLAUSE ::= for Partition'ATTRIBUTE_NAME use ATTRIBUTE_ARGUMENTS; | for Channel'ATTRIBUTE_NAME use ATTRIBUTE_ARGUMENTS;Кроме того, существует возможность изменения принимаемого по умолчанию поведения конкретного раздела (или канала) путем указания атрибутов, которые непосредственно относятся к указанному разделу (или каналу).
REPRESENTATION_CLAUSE ::= for PARTITION_IDENTIFIER'ATTRIBUTE_NAME use ATTRIBUTE_ARGUMENTS;Когда указание атрибута используется для выбранного объекта предопределенного типа, то осуществляется переопределение любого указания атрибута предопределенного типа. Следует заметить, что в последующих секциях атрибуты используются с объектами, а не с предопределенными типами.
Описание конфигурации
Распределение одной или нескольких Ада-программ описывается в одном модуле конфигурации. Этот модуль конфигурации содержит часть спецификации и необязательную часть тела. Модуль конфигурации описывается подобно процедуре Ады. Для этого используется зарезервированное слово configuration.
CONFIGURATION_UNIT ::= configuration IDENTIFIER is DECLARATIVE_PART [begin SEQUENCE_OF_STATEMENTS] end [IDENTIFIER];Описание раздела
В части описаний пользователь описывает разделы своего приложения, и может изменить их поведение по умолчанию. Утилита gnatdist предусматривает предопределенный тип Partition. Пользователь может описать список разделов, и может также инициализировать эти разделы начальным списком Ада-модулей.
DECLARATIVE_PART ::= {DECLARATIVE_ITEM} DECLARATIVE_ITEM ::= PARTITION_DECLARATION | CHANNEL_DECLARATION | REPRESENTATION_CLAUSE | SUBPROGRAM_DECLARATION | PRAGMA SUBPROGRAM_DECLARATION ::= MAIN_PROCEDURE_DECLARATION | PROCEDURE_DECLARATION | FUNCTION_DECLARATION PARTITION_DECLARATION ::= DEFINING_IDENTIFIER_LIST : Partition [:= ENUMERATION_OF_ADA_UNITS]; DEFINING_IDENTIFIER_LIST ::= DEFINING_IDENTIFIER {, DEFINING_IDENTIFIER} STATEMENT ::= IDENTIFIER := ENUMERATION_OF_ADA_UNITS; SEQUENCE_OF_STATEMENTS ::= STATEMENT {STATEMENT}После описания раздел содержит пустой список Ада-модулей. Знак операции ":=" добавляет список Ада-модулей, указанных справа, к текущему списку Ада-модулей, который уже отображен на раздел распределенного приложения. Эта операция не является деструктивной. Проверка того, что какой-либо модуль является релевантным (актуальным) Ада-модулем или нет осуществляется позже, back-end-ом gnatdist. Подобные присваивания могут осуществляться как в части описаний, так и в части тела.
ENUMERATION_OF_ADA_UNITS ::= ({ADA_UNIT {, ADA_UNIT}});Описание расположения
В языке конфигурации GLADE существует несколько видов размещений (location) Мы рассмотрим их далее, а здесь представим краткий обзор этих размещений:
- Boot_Location определяет сетевое размещение, которое используется для взаимодействия с сервером загрузки (boot server) в процессе фазы загрузки.
- Self_Location определяет сетевое размещение, которое используется другими разделами для взаимодействия с текущим разделом.
- Data_Location определяет размещение накопителя данных (data storage), которое используется текущим разделом для отображения пассивных общих модулей.
Размещение (location) состоит из имени поддержки (support name) и определенных для этой поддержки данных. Например, сетевое размещение (network location) состоит из имени протокола, подобного tcp, и данных протокола, подобных <machine>:<port>. Размещение накопителя данных (storage location) состоит из имени поддержки накопителя, подобного dfs (для Distributed File System - распределенная файловая система), и данных поддержки накопителя, подобных имени каталога /dfs/glade.
LOCATION ::= ([Support_Name =>] STRING_LITTERAL, [Support_Data =>] STRING_LITTERAL) LOCATION_LIST ::= (LOCATION [,LOCATION)])Примечательно, что размещение может иметь неопределенные или не полные данные поддержки. В этом случае, поддержка может вычислить данные поддержки. Например, ("tcp", "") указывает используемый протокол, но данные протокола <machine>:<port> должны быть определены протоколом самостоятельно.
Размещение или список размещений может объединяться в одиночную строку, для использования как опция командной строки или как переменная окружения (см. Опции командной строки для раздела распределенной программы).
Если одному разделу необходимо взаимодействие с другим разделом, как только список размещений последнего известен, вызывающий раздел будет использовать первое расположение вызываемого раздела, чей протокол локально доступен. Например, если вызываемый раздел экспортирует три размещения: ("N1", "D1"), ("N2", "D2") и ("N3", "D3"), - то вызывающий раздел с локально доступными протоколами N2 и N3 будет пытаться взаимодействовать с вызываемым разделом используя протокол с именем N2 и определенными данными D2.
Атрибут раздела 'Main
По существу, дополнение для распределенных систем (DSA) помогает пользователю в построении распределенного приложения из не-распределенного приложения (естественно, это не единственно возможная модель предлагаемая DSA). Пользователь может спроектировать, реализовать и оттестировать свое приложение в не-распределенном окружении, а затем, может перейти от не-распределенного варианта к распределенному. Как указывалось ранее, такой подход двух-этапного проектирования обладает рядом преимуществ.
В не-распределенном варианте, пользователь исполняет только один главный исполняемый модуль, имя которого, возможно, соответствует имени главного модуля приложения пользователя. При использовании gnatdist, в распределенном варианте, главный исполняемый модуль, с именем, которое соответствует имени главного модуля приложения пользователя, отвечает за запуск всего распределенного приложения. Следовательно, пользователь может запустить свое приложение таким же способом который используется для запуска не-распределенного варианта приложения.
Исходя из этого, язык конфигурации предусматривает способ описания главной процедуры не-распределенного приложения.
MAIN_PROCEDURE_DECLARATION ::= procedure MAIN_PROCEDURE_IDENTIFER is in PARTITION_IDENTIFIER;В этом случае, раздел на который отображается главная процедура приложения будет называться главным разделом приложения. Он включает в свой код вызов этой главной процедуры. На главный раздел возлагается еще одна, дополнительная роль поскольку на нем размещается сервер загрузки (boot server) (см. Внутренняя организация GLADE).
Главные процедуры для других разделов имеют пустые (null) тела. Однако, пользователь может также модифицировать их поведение, предусматривая альтернативную главную процедуру. Для выполнения этого, может быть описана альтернативная главная подпрограмма, которую, затем, можно назначить атрибуту раздела 'Main
PROCEDURE_DECLARATION ::= procedure PROCEDURE_IDENTIFIER; REPRESENTATION_CLAUSE := for PARTITION_IDENTIFIER'Main use PROCEDURE_IDENTIFIER;Директива Starter
По умолчанию, главный исполняемый модуль является полной стартерной процедурой Ады. Это подразумевает, что такая процедура стартует (запускает) все остальные разделы из Ада-программы. Директива Starter позволяет пользователю требовать использование того или иного стартера. Когда хост раздела не определен статически (см. Атрибут раздела 'Host), подпрограмма-стартер, во время выполнения, будет интерактивно запрашивать указание хоста раздела.
CONVENTION_LITERAL ::= Ada | Shell | None PRAGMA ::= pragma Starter ([Convention =>] CONVENTION_LITERAL);
- Принятый по умолчанию метод заключается в том, что старт (запуск) разделов из Ада-подпрограммы главного раздела осуществляется с помощью использования удаленного командного интерпретатора remote shell.
- Пользователь может указать использование скрипта командного интерпретатора Shell, который будет запускать различные разделы (одновременно - один раздел) на соответствующих удаленных машинах, используя для этого remote shell. Так же как и Ада-стартер, скрипт командного интерпретатора будет интерактивно запрашивать хост раздела, когда хост раздела заранее не определен. Наличие текстуального скрипта командного интерпретатора предоставляет пользователю удобную возможность его редактирования для внесения необходимых изменений.
- Пользователь может указать отсутствие None стартера. В этом случае, ответственность за старт различных разделов возлагается на пользователя. Пользователь может предусмотреть указание расположения сервера загрузки в командной строке. (см. Архитектура GLADE PCS).
Директива Boot_Location
Когда какой-либо раздел начинает выполняться, один из первых шагов заключается в подключении к разделу загрузки, на котором размещается сервер загрузки (см. Архитектура GLADE PCS). Эта директива предусматривает указание одного или нескольких расположений для получения подключения с разделом загрузки.
PRAGMA ::= PRAGMA_WITH_NAME_AND_DATA | PRAGMA_WITH_LOCATION | PRAGMA_WITH_LOCATION_LIST PRAGMA_WITH_NAME_AND_DATA ::= pragma Boot_Location ([Protocol_Name =>] STRING_LITERAL, [Protocol_Data =>] STRING_LITERAL); PRAGMA_WITH_LOCATION ::= pragma Boot_Location ([Location =>] LOCATION); PRAGMA_WITH_LOCATION_LIST ::= pragma Boot_Location ([Locations =>] LOCATION_LIST);Размещение сервера загрузки может быть объединено в одиночную строку для последующего использования в качестве опции командной строки или переменной окружения (см. Опции командной строки для раздела распределенной программы).
Примечание:
В настоящее время директива Boot_Server считается устаревшей. Вместо нее рекомендуется использоват директиву Boot_Location. Такая редакция признана более согласованной с остальной частью языка конфигурации (см, Self_Location Опция раздела self_location и Data_Location Опция раздела data_location).Атрибут раздела 'Self_Location
За исключением загрузочного раздела, на котором размещается сервер загрузки, какой-либо раздел может быть доступен с помощью динамического определения размещения (например, раздел осуществляет поиск свободного порта, когда выбран протокол tcp). Пользователю может понадобиться, чтобы подобный раздел был доступен из определенного расположения, например, в случае, когда пользователю необходимо сделать раздел, который будет служить "зеркалом" загрузочного раздела. Для выполнения этого, пользователь может использовать средства Self_Location, для принудительного размещения раздела.
REPRESENTATION_CLAUSE ::= for PARTITION_IDENTIFIER'Self_Location use LOCATION; | for PARTITION_IDENTIFIER'Self_Location use LOCATION_LIST;Если указание атрибута используется для предопределенного типа Partition, то указание расположения будет не полным. В противном случае, все разделы будут доступны через одно и то же расположение, что не рекомендуется.
Когда указание атрибута 'Self_Location используется для конкретного раздела, модули поддержки протокола, которые необходимы для этого раздела, будут компоноваться в результирующий исполняемый модуль. По умолчанию, когда не осуществляется переопределение атрибута 'Self_Location, протоколом, который используется разделом и загружается в результирующий исполняемый модуль будет протокол tcp
Атрибут раздела 'Passive
По умолчанию, какой-либо раздел считается активным разделом. Атрибут 'Passive позволяет указывать пассивный раздел. В таком случае, gnatdist будет проверять, что на этот раздел отображаются только пассивные общие модули. Поскольку такой раздел не может себя зарегистрировать, его расположение жестко кодируется во всех разделах, которые зависят от его общих пассивных модулей.
REPRESENTATION_CLAUSE ::= for PARTITION_IDENTIFIER'Passive use BOOLEAN_LITERAL;Атрибут раздела 'Data_Location
Пассивные общие модули могут быть отображены на пассивные или активные разделы. В обоих случаях, существует возможность выбрать поддержку накопителя данных и сконфигурировать ее со специфическими данными размещения.
REPRESENTATION_CLAUSE ::= for PARTITION_IDENTIFIER'Data_Location use LOCATION; | for PARTITION_IDENTIFIER'Data_Location use LOCATION_LIST;Когда атрибут 'Data_Location указывается для определенного раздела, модули поддержки накопителя данных, которые необходимы для этого раздела, будут компоноваться в результирующий исполняемый модуль раздела. По умолчанию, когда атрибут 'Data_Location не переопределяется, используется поддержка dfs, которая компонуется в результирующий исполняемый модуль раздела. Распределенная файловая система dfs (Distributed File System) является доступной поддержкой накопителя поскольку разделы могут использовать общие файлы.
Возможность отображения различных общих пассивных модулей данного раздела на другое размещение накопителя данных отсутствует. GLADE требует, чтобы все общие пассивные модули данного раздела отображались на одну и ту же поддержку накопителя. Когда атрибут 'Data_Location, указанный для какого-либо раздела, является списком размещений, все модули поддержки накопителя данных, которые необходимы для этого раздела, будут компоноваться в результирующий исполняемый модуль раздела. По умолчанию, осуществляется активация только первого указанного размещения. Пользователь может изменить выбор активированной поддержки на какое-либо другое размещение, которое указано в списке размещений. Это может быть выполнено с помощью использования опции раздела Data_Location (см. Опция раздела data_location).
Поскольку пассивный раздел не может быть активирован, то возможность указания списка размещений как атрибута 'Data_Location отсутствует. Также, отсутствует возможность динамического изменения расположения пассивного раздела.
Атрибут раздела 'Host
Логические узлы (или разделы) могут быть отображены на физические узлы. Имя хоста (иначе, компьютера в сети) может быть как статическим, так и динамическим значением. В случае использования статического значения, выражение, задающее имя хоста, является строковым литералом. В случае использования динамического значения, аргументом спецификатора предстовления является функция, которая принимает в качестве своего параметра строку и возвращает строковое значение в качестве результата. При вызове такой функции, ей, как параметр, передается имя раздела, а возвращаемое значение должно являться именем хоста.
FUNCTION_DECLARATION ::= function FUNCTION_IDENTIFIER (PARAMETER_IDENTIFIER : [in] String) return String; REPRESENTATION_CLAUSE ::= for PARTITION_IDENTIFIER'Host use STRING_LITERAL; | for PARTITION_IDENTIFIER'Host use FUNCTION_IDENTIFIER;Функция, которая возвращает имя хоста, может быть любой функцией Ады (по умолчанию) или скриптом командного интерпретатора shell. Для импортирования функции Ады или скрипта командного интерпретатора shell используется директива Import. (см. Директива Import).
Эта функция вызывается главным разделом распределенной программы, посредством GLADE PCS, для запуска указанного раздела на указанном логическом узле. В случае необходимости балансирования нагрузки, эта функция может возвращать наиболее подходящее имя хоста, которое выбирается по определенному критерию из множества имен хостов.
Директива Import
Язык конфигурации GLADE позволяет использовать две разновидности подпрограмм. Главная процедура используется как атрибут раздела 'Main, а функция используется как атрибут раздела 'Host
SUBPROGRAM_DECLARATION ::= procedure MAIN_PROCEDURE_IDENTIFIER is in PARTITION_NAME; | procedure PROCEDURE_IDENTIFIER; | function FUNCTION_IDENTIFIER (PARAMETER_IDENTIFIER : [in] String) return String;Функция может быть обычной функцией Ады (по умолчанию) или скриптом командного интерпретатора shell. Для импорта скрипта командного интерпретатора shell должна использоваться директива Import:
PRAGMA ::= pragma Import ([Entity =>] FUNCTION_IDENTIFIER, [Convention =>] CONVENTION_LITERAL, [External_Name =>] STRING_LITERAL); pragma Import (Best_Node, Shell, "best-node");В этом случае, GLADE PCS вызывает скрипт командного интерпретатора shell, указывая имя раздела в качестве аргумента командной строки. При этом подразумевается, что скрипт командного интерпретатора shell возвратит имя хоста раздела (см. Атрибут раздела 'Host).
Атрибут раздела 'Directory
Атрибут 'Directory позволяет пользователю указать в каком каталоге располагается исполняемый модуль раздела. Этот атрибут может быть полезен при построении гетерогенных систем, когда пользователю необходимо хранить исполняемые модули, предназначенные для определенной целевой платформы, в едином каталоге. Кроме того, указание каталога может оказаться полезным когда исполняемый модуль раздела не является непосредственно доступным в рабочем окружении пользователя. Например, когда вызывается удаленная команда, подобная rsh, исполняемый модуль раздела должен быть доступен для пользователя посредством переменной окружения PATH. В результате указания атрибута 'Directory, будет использоваться полное имя исполняемого модуля раздела.
REPRESENTATION_CLAUSE ::= for PARTITION_IDENTIFIER'Directory use STRING_LITERAL;Атрибут раздела 'Command_Line
Пользователю может понадобиться возможность передачи аргументов в командной строке запуска исполняемого модуля раздела. Однако, когда раздел запускается автоматически, посредством запуска из главного раздела, командная строка запуска содержит только аргументы, которые необходимы GLADE. Для указания дополнительных аргументов командной строки пользователь может использовать атрибут 'Command_Line.
REPRESENTATION_CLAUSE ::= for PARTIITON_IDENTIFIER'Command_Line use STRING_LITERAL;Атрибут 'Termination для разделов
Руководство по языку программирования Ada95 (The Ada95 Reference Manual) не предусматривает какое-либо правило обработки глобального завершения распределенного приложения (см. Принудительное завершение (abortion) и прекращение (termination)).
В GLADE, по умолчанию, множество разделов завершает выполнение когда каждый раздел может буть завершен и когда не остается ни одного передаваемого сообщения. Алгоритм распределения, который прверяет глобальные условия, периодически активируется главным сервером загрузки.
TERMINATION_LITTERAL ::= Global_Termination | Local_Termination | Defered_Termination REPRESENTATION_CLAUSE ::= for PARTIITON_IDENTIFIER'Termination use TERMINATION_LITERAL;
- Когда раздел сконфигурирован в соответствии с глобальной политикой завершения, он завершает свою работу как только главный сервер загрузки посылает ему соответствующий сигнал. Главный сервер загрузки осуществляет периодическую проверку возможности завершения приложения. Когда все разделы готовы к завершению выполнения, главный сервер загрузки посылает каждому разделу запрос на завершение. Глобальная политика завершения является политикой завершения принимаемой по умолчанию.
- Отложенная политика завершения очень похожа на глобальную политику завершения. Отличие заключается в том, что когда раздел с отложенной политикой завершения получает от главного сервера загрузки запрос на завершение, он его игнорирует. Такая политика позволяет разделу выполняться постоянно без предотвращения завершения какого-либо набора разделов. В настоящий момент эта политика не реализована.
- Когда раздел сконфигурирован в соответствии с политикой локального завершения, он завершает свою работу как только обнаружено классическое условие завершения Ада-программы для исполняемого модуля раздела. Это подразумевает, что такой раздел не ожидает посылки запроса на завершение от главного сервера загрузки.
В любом случае, когда "погибает" главный раздел (и когда не может быть выбран альтернативный загрузочный раздел, см. Архитектура GLADE PCS), все разделы распределенного приложения также "погибают", какой бы ни была политика их завершения. При этом следует заметить, во-первых, какой-либо раздел не может выполняться без раздела загрузки. Во-вторых, когда пользователь желает "убить" процесс не-распределенного приложения, он "убивает" главную программу. Навязывание механизма, который описан выше, гарантирует, что "убийство" главного раздела автоматически "убьет" все разделы, то есть, все распределенное приложение.
Атрибут раздела 'Reconnection
Когда на разделе не сконфигурирован пакет интерфейса удаленного вызова (RCI), многократный запуск такого раздела на выполнение не вызывает проблем. Когда на разделе сконфигурирован один или более пакетов RCI, раздел не может быть запущен на выполнение более одного раза. Если такой раздел запускается на выполнение повторно, то невозможно определить какой экземпляр раздела должен исполнить удаленный вызов подпрограммы.
Когда раздел "разрушается" или останавливается, кому-либо может понадобиться перестартовать этот раздел и, возможно, восстановить его состояние (например для пакетов Shared_Passive). В подобном случае, этот раздел уже известен другим разделам и, возможно, отмечен как "мертвый" раздел. Может быть выбрана различная политика переподключения:
RECONNECTION_LITTERAL ::= Reject_On_Restart | Fail_Until_Restart | Wait_Until_Restart REPRESENTATION_CLAUSE ::= for PARTITION_IDENTIFIER'Reconnection use RECONNECTION_LITTERAL;
- Когда раздел сконфигурирован для использования политики Reject_On_Restart, "погибший" раздел остается "погибшим" и любая попытка перестартовать его будет неудачной. Любой удаленный вызов подпрограммы этого раздела приведет к возбуждению исключения Communication_Error. Политика Reject_On_Restart является политикой устанавливаемой по умолчанию.
- Когда раздел сконфигурирован для использования политики Fail_Until_Restart, "погибший" раздел может быть перезапущен. Любой удаленный вызов подпрограммы этого раздела будет приводить к возбуждению исключения Communication_Error до тех пор, пока раздел не будет перестартован. Как только раздел будет перестартован, удаленные вызовы подпрограмм этого раздела будут выполняться нормально.
- Когда раздел сконфигурирован для использования политики Wait_Until_Restart, "погибший" раздел может быть перезапущен. Любой удаленный вызов подпрограммы этого раздела будет отложен до рестарта "умершего" раздела. Как только раздел будет перестартован, удаленные вызовы подпрограмм этого раздела будут выполняться нормально, и будет продолжена обработка отложенных вызовов удаленных подпрограмм этого раздела.
Описание канала
Язык конфигурации позволяет описывать не только разделы распределенного приложения, но и соединения между ними. Такие соединения называют каналами (Channel), которые предоставляют двунаправленное соединение между двумя разделами.
CHANNEL_DECLARATION ::= CHANNEL_IDENTIFIER : Channel [:= PARTITION_PEER]; PARTITION_PEER ::= (PARTITION_IDENTIFIER, PARTITION_IDENTIFIER);Соединение разделов является парой различных имен разделов. Порядок перечисления не имеет значения. Естественно, что описание разделов должно предшествовать описанию каналов.
A_Channel : Channel := (Partition_1, Partition_2);Показанный выше пример описывает имя A_Channel как соединение между разделами Partition_1 и Partition_2. Следует заметить, что для двух одних и тех же разделов нельзя описать более одного канала взаимодействия.
Атрибут 'Filter для раздела и канала
GLADE содержит прозрачный расширяемый механизм фильтрации, который позволяет пользователю определять различные преобразования данных, осуществляемые над значениями аргументов удаленных вызовов и возвращаемыми значениями. Одним из возможных применений этого механизма является компрессия всех данных перед посылкой и декомпрессия данных после приема.
При использовании GLADE, приложению нет необходимости заботится о подобных преобразованиях. Вместо этого, пользователи могут написать свои собственные преобразования данных и интегрировать их в GLADE таким образом, чтобы они автоматически и прозрачно использовались, в зависимости от конфигурации распределенного приложения.
По умолчанию GLADE не выполняет никакой фильтрации, даже когда фильтры компрессии/декомпрессии всегда доступны. Пользователь может самостоятельно указать в конфигурации своего распределенного приложения на необходимость использования этих фильтров.
Чтобы указать необходимость фильтрации нужно сначала описать соединительные каналы между разделами распределенного приложения. После описания каналов можно описать преобразования данных, которые необходимо осуществлять при пересылках данных:
A_Channel : Channel := (Partition_1, Partition_2); for A_Channel'Filter use "ZIP";Показанный пример указывает, что все пересылаемые через соединительный канал данные должны быть преобразованы с помощью фильтра This specifies that all data sent over this channel should be transformed by the filter named ZIP (Фильтр с именем ZIP должен существовать, а его реализация должна присутствовать в пакете System.Garlic.Filters.Zip).
Может оказаться полезной возможность указания того, что раздел должен использовать какой-либо определенный фильтр для выполнения всех удаленных вызовов, вне зависимости от канала (например, вне зависимости от раздела, который принимает удаленный вызов). Это можно осуществить используя атрибут 'Filter для раздела:
for Partition_1'Filter use "ZIP";или
for Partition'Filter use "ZIP";Позднее, можно установить фильтр по умолчанию для в