====== Сообщение о языке Active Oberon ======
Patrik Reali *
27 октября 2004 г.
Перевод Андреева М.В. опубликован с согласия автора. [[http://maxandreev.narod.ru/oberon/ActiveOberonReport_RUS.html|Оригинал]]
===== 1 Введение =====
**Active Oberon**((информация в [[https://ru.wikipedia.org/wiki/%D0%90%D0%BA%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9_%D0%9E%D0%B1%D0%B5%D1%80%D0%BE%D0%BD|Википедии]])) является расширением оригинального языка **Oberon** [29, 30]. Его цель — введение в язык свойств для выражения параллелизма посредством //активных объектов// (active objects((для более подробной информации смотрите [[https://ru.wikipedia.org/wiki/Active_Object]])) ). Данный отчет предполагает знакомство с языком **Oberon**; описываются только расширения языка.
Проектирование расширения языка направлялось на достижение унификации и гармонии. Изменения основаны на устоявшихся концепциях, таких как область действия и локальность. Обоснования, выходящие за рамки **Active Oberon**, описаны в [10].
==== 1.1 Благодарности ====
Большое спасибо **B. Kirk, A. Fischer, T. Frei, J. Gutknecht, D. Lightfoot**, и **P. Muller** за рецензию данного документа, за внесение поправок и улучшений, а так же **Владимиру Лосю** за усовершенствование примера <<барьер>>.
==== 1.2 Предыстория и родственные работы ====
Разработка языков программирования в **ETH Zurich** имеет богатые традиции. Язык **Oberon** — это последний наследник семейства **Algol, Pascal** и **Modula**. **Pascal** [16] задумывался как язык для представления маленьких программ; простота и компактность сделали его особенно подходящим для обучения программированию. **Modula** [28] эволюционировала из **Pascal** как язык для системного программирования, и извлекла пользу из практического опыта, полученного при проектировании рабочей станции **Lilith** [22] и операционной системы **Medos** [17]. Необходимость поддержки парадигмы «программирования в большом» была стимулом для создания **Oberon** [29]. Платформы **Ceres** [6] и **Chameleon** [12], //операционная система// **Oberon** [11] — эти проекты разрабатывались параллельно с проектированием языка, что позволило испытать и оценить его удобство.
Множество расширений языка **Oberon** было предложено как в **ETH**, так и вне его. **Object Oberon** [19], **Oberon-2** [18], и **Froderon** [7] исследуют добавление дополнительных объектно-ориентированных свойств в язык; **Oberon-V** [9] предлагает дополнения для поддержки параллельных операций на векторных компьютерах; **Oberon-XSC** [15] добавляет математические возможности для поддержки научных вычислений; также было предложено встраивание модулей [24]. //Параллелизм// был впервые добавлен в операционную систему через специальный системный **API** в **Concurrent Obero**n [25] и **XOberon** [2]; попытка моделирования параллелизма средствами самого языка была предпринята **Radenski** [23].
{{ ::activeoberonreport_rus0x.png?nolink&400 |}}
Рис. 1: Эволюция языков семейства Pascal
**Active Oberon** — это первый представитель нового поколения языков в данном семействе. Мы старались добавить в язык поддержку параллелизма и моделирования компонентов ясным и безболезненным способом.
==== 1.3 Дизайн языка ====
На дизайн **Active Oberon** повлиял опыт, полученный при проектировании **Oberon** и **Oberon-2**. Мы следуем нотации **Object Oberon** для объявления классов и методов, т. к. считаем, что данный способ выразительнее нотации **Oberon-2**: методы принадлежат классу и, следовательно, должны быть объявлены в классе; таким образом, прочие методы и поля, лежащие в области видимости записи, могут быть доступны без указания спецификатора. Защита от одновременного доступа при помощи модификатора ''EXLUSIVE'' более читабельна, если методы принадлежат одной области видимости. **Active Oberon** отходит от дизайна **Object Oberon** в том, что записи одновременно являются так же и классами, т. е. не позволяется сосуществование классов и записей в одной системе. Другое важное отличие продиктовано решением позволить компилятору обрабатывать //опережающие ссылки// (forward reference). Синтаксис **Object Oberon** и **Oberon-2** разработан в том числе и с целью упрощения создания компилятора, мы же постарались упростить работу программистов путем исключения ненужной избыточности опережающих объявлений, переложив тем самым работу на плечи компилятора.
**Java** [8] и **C#** [4] разделяют некоторые похожие идеи с **Active Oberon**. Они так же являются объектно-ориентированными языками из мира императивных языков, и механизм защиты экземпляров объектов от одновременного доступа так же связан с мониторами. С другой стороны, они делают акцент на объектно-ориентированноcти в такой экстремальной манере, что методы и поля класса трактуются как частный случай методов и полей экземпляра объекта, т. к. они лежат в пространстве имен класса. Более того, в **Jav**a нет //полноценной// поддержки для статического размещения структур: все структуры размещаются в динамической памяти, даже определенные пользователем массивы констант; поэтому, что бы получить приемлемую скорость исполнения, программы **Java** //нуждаются// в использовании сложной и затратной оптимизации при компиляции. Все языки из семейства //Oberon// рассматривают модули и классы как //ортогональные понятия//, каждое из них обладает своей областью действия; семантика модуля отличается от семантики класса так, как показано в [26] (для сравнения, **B. Meyer** защищает противоположное мнение [18]): модули группируют статические компоненты и соответствующие реализации, и предоставляют примитивы для развертывания и структурирования. На деле **Java** и **C#** вводят концепции, такие как //пакеты// (packages), //пространства имен// (namespaces) и //сборки// (assemblies), которые //фактически// являются //модулями//, но только под другим именем. Мы думаем, что все еще остаются веские причины для того, что бы статические структуры и модули были частью языка программирования.
Оператор ''AWAIT'' предложен и исследован **Brinch Hansen** [3], который показал его //концептуальную простоту// и элегантность, но в то же время думал, что его нельзя реализовать эффективно. Мы снова предлагаем этот оператор в **Active Oberon** с уверенностью, что это значительное усовершенствование по сравнению с сигналами и семафорами потому, что он вносит унификацию и ясность; это становится особенно очевидно при программировании в объектно-ориентированном стиле, для которого сигналы и семафоры //совершенно не подходят// в виду их неструктурного использования, т. к. они могут быть добавлены в программы //совершенно произвольным// образом. В диссертации **Питера Мюллера** (Pieter Muller) [21] доказывается, что, при определенных ограничениях, оператор ''AWAIT'' //может// быть реализован эффективно. Язык **Ada 95** [14] тоже использует конструкт, названный //барьеры// (barriers), который семантически весьма похож на ''AWAIT'' в **Active Oberon**, но только с детализацией на уровне процедур.
**Concurrent Oberon** был первой попыткой реализовать параллелизм в системе **Oberon**. Это было сделано через специальный **API**, определяющий тип ''Thread'' и функции для создания, остановки и возобновления исполнения. Защита была реализована при помощи одного глобального //системного замка// (system lock). Но не были предложены примитивы синхронизации. Эта модель так же слаба для **Active Oberon**, т. к. блокировки и синхронизация тесно связаны (когда выполняется синхронизация, блокировка снимается), и блокирующий механизм //слишком грубый//; один замок сделает мультипроцессорную систему (в которой множество процессов исполняется одновременно) //бесполезной//.
===== 2 Объектно-ориентированные расширения =====
==== 2.1 Указатель на безымянные типы записей ====
TYPE
(* пример указателя на безымянные типы записей *)
(* указатели на именованные типы записей *)
Tree = POINTER TO TreeDesc;
TreeDesc = RECORD key: INTEGER; l, r: Node END;
(* указатели на безымянные типы записей *)
Node = POINTER TO RECORD key: INTEGER; next: Node END;
DataNode = POINTER TO RECORD (Node) data: Data END;
DataTree = POINTER TO RECORD (Tree) data: Data END;
Типы ''Node'' и ''DataNode'' — //указатели на безымянные типы записей// (pointers to anonymous record types); ''Tree'' — //указатель на именованный тип записей// (pointer to named record type).
//Экземпляры// данных типов могут быть размещены //только// в динамической памяти (при помощи ''NEW''), статические экземпляры не доступны; это вызвано тем, что тип записей безымянный и невозможно определить переменную этого типа.
Типы ''RECORD'' и ''POINTER TO RECORD'' могут быть базовыми типами для указателя на безымянный тип записей; тип записей //не может// расширить указатель на анонимную запись, таким образом выполняется свойство, разрешающее //только// динамические экземпляры.
==== 2.2 Объектные типы ====
TYPE
(* объектные типы *)
DataObj = OBJECT VAR data: Data; l, r: DataObj END DataObj;
''DataObj'' — это //объектный тип// (object type).
Синтаксис типа ''OBJECT'' отличается от синтаксиса типа ''POINTER TO RECORD''; он должен следовать правилу [ DeclSeq ] Body вместо правила ''FieldList''. Это подразумевает, что процедуры могут быть объявлены //внутри// объектного типа. Мы называем их //методы// (methods) или //связанные с типом процедуры// (type-bound procedures).
Только //объектный// тип //может расширить другой объектный тип//. //Экземпляры// объектных типов должны размещаться динамически при помощи ''NEW'', подчиняясь правилу, продиктованному инициализаторами (Раздел 2.4).
==== 2.3 Связанные с типом процедуры ====
TYPE
Coordinate = RECORD x, y: LONGINT END;
VisualObject = OBJECT
VAR next: VisualObject;
PROCEDURE Draw*; (*нарисовать данный объект*)
BEGIN HALT(99); (*заставить расширения переопределять этот метод*)
END Draw;
END VisualObject;
Point = OBJECT (VisualObject)
VAR pos: Coordinate;
PROCEDURE Draw*; (*перекрываем метод Draw, объявленный в VisualObject*)
BEGIN MyGraph.Dot(pos.x, pos.y)
END Draw;
END Point;
Line = OBJECT (VisualObject)
VAR pos1, pos2: Coordinate;
PROCEDURE Draw*;
BEGIN MyGraph.Line(pos1.x, pos1.y, pos2.x, pos2.y)
END Draw;
END Line;
VAR
objectRoot: VisualObject;
PROCEDURE DrawObjects*;
VAR p: GraphObject;
BEGIN
(* рисуем все объекты из списка *)
p := objectRoot;
WHILE p # NIL DO p.Draw; p := p.next END;
END DrawObjects;
//Процедуры//, объявленные внутри объекта, называются //связанные с типом процедуры// (type-bound procedures) или методы (methods). //Методы// ассоциируются с экземпляром типа и оперируют им; внутри реализации метода, если другой метод виден по правилам видимости **Oberon**, то он доступен без указания квалификатора. Метод может //перекрывать// другой метод с таким же названием унаследованный //от базового типа// записей, но при этом он должен обладать //такой же сигнатурой//. Признак видимости является частью сигнатуры.
**Замечание**: Мы решили объявлять методы в области видимости объекта потому, что они принадлежат области видимости записи и могут быть доступны только через экземпляры записи. Это упрощает определение методов (нет получателя как в **Oberon-2** [20]) и доступ к полям и методам следует хорошо известным правилам видимости **Oberon**. Мы боялись того, что циклические ссылки станут проблемой (например, два объектных типа //взаимно ссылаются// друг на друга), но решили, что концептуальная элегантность языка //более важна//. Решение проблемы в //ослаблении// правил видимости — //допустить опережающие ссылки// на символы, объявленные позднее в исходном тексте (раздел 4.1). Альтернативный способ заключается в //расширении// опережающих описаний до описания всего типа (как в **Object Oberon** [19]). Мы отвергли данное решение в виду внесения //ненужной избыточности// в код и вместо этого переложили решение проблемы на компилятор.
Дан экземпляр объекта o типа ''T'' со связанными процедурами ''P'' и ''Q'', ''o.P'' — это //вызов// метода. Внутри метода типа ''T'' другой метод типа ''T'' может быть вызван как ''Q''. Метод ''P'' может вызвать метод, который он перекрывает, как ''P↑'' . Это //супервызов// и он может быть выполнен только //внутри// метода.
==== 2.4 Инициализаторы ====
Метод, помеченный символом ''&'' , является //инициализатором объекта// (object initializer). Этот метод вызывается автоматически при создании экземпляра объекта. Объектный тип может иметь //не более одного// инициализатора. Если он есть, он //всегда// общедоступен и может быть вызван //явно//, как метод; если отсутствует, то наследуется инициализатор базового типа. Инициализатор может иметь сигнатуру //отличающуюся// от сигнатуры унаследованного инициализатора базового типа, но в этом случае наименование инициализатора так же //должно// отличаться.
Если объектный тип ''T'' содержит или наследует инициализатор ''P'' с сигнатурой (p0: T0; .... pn: Tn), тогда конкретизация переменной ''o:T'' при помощи ''NEW'' требует указать параметры инициализатора: ''NEW(o, p0, ..., pn)''. Инициализатор исполняется автоматически вместе с ''NEW''.
**Замечание**: Необязательный инициализатор делает возможным параметризацию типа. В частности, это весьма удобно при работе с //активными объектами//, т .к. параметризация экземпляра должна быть выполнена //до старта// активности. Если на чистоту, то нам такая нотация //не нравится//, но это единственный способ, который мы смогли придумать, добавить свойство //не меняя сам язык//.
TYPE
Point = OBJECT (VisualObject)
VAR pos: Coordinate;
PROCEDURE & InitPoint(x, y: LONGINT);
BEGIN pos.x := x; pos.y := y
END InitPoint;
END Point;
PROCEDURE NewPoint(): Point;
VAR p: Point;
BEGIN NEW(p, x, y); (* вызывает NEW(p) и p.InitPoint(x, y) *)
RETURN p
END NewPoint;
==== 2.5 SELF ====
Ключевое слово ''SELF'' может быть использовано в //любом// методе или в любой локальной процедуре метода объекта. Тип данной переменной совпадает с типом объекта и ее значение //равно экземпляру// объекта, к которому привязан метод. Переменная используется для доступа к объекту в случае, если нужна ссылка на объект или когда поле или метод объекта перекрывается другим символом, например, поле записи перекрывается локальной переменной с тем же именем.
TYPE
ListNode = OBJECT
VAR data: Data; next: ListNode;
PROCEDURE & InitNode (data: Data);
BEGIN
SELF.data := data; (* инициализируем данные объекта *)
next := root; root := SELF (* добавляем node в начало списка *)
END InitNode;
END ListNode;
VAR
root: ListNode;
==== 2.6 Делегаты ====
TYPE
MediaPlayer = OBJECT
PROCEDURE Play; .... показать фильм .... END Play;
PROCEDURE Stop; .... остановить фильм .... END Stop;
END MediaPlayer;
ClickProc = PROCEDURE {DELEGATE};
Button = OBJECT
VAR
onClick: ClickProc;
caption: ARRAY 32 OF CHAR;
PROCEDURE OnClick;
BEGIN onClick END OnClick;
PROCEDURE & Init(caption: ARRAY OF CHAR; onClick: ClickProc);
BEGIN SELF.onClick := onClick; COPY(caption, SELF.caption)
END Init;
END Button;
PROCEDURE Init(p: MediaPlayer);
VAR b0, b1, b2: Button;
BEGIN
(* Перезагрузка -> вызвать системную функцию *)
NEW(b0, "Reboot", System.Reboot);
(* Интерфейс MediaPlayer: связываем кнопки с экземпляром плеера *)
NEW(b1, "Play", p.Play);
NEW(b2, "Stop", p.Stop);
END Init;
//Делегаты// подобны процедурным типам; они совместимы как с процедурами так и с методами, в то время как процедурные типы совместимы //только// с процедурами.
//Делегаты процедурных типов// помечаются модификатором ''DELEGATE''. Делегатам могут быть назначены процедуры и методы. Пусть даны переменная с процедурным типом ''t'', экземпляр объекта ''o'' и метод ''M'' связанный с ''o'', тогда можно записать ''o.M'' в ''t'', если конечно метод и ''t'' обладают совместимыми сигнатурами. Ссылка на сам объект исключается из сигнатуры процедурного типа. Всякий раз при вызове ''t'' назначенный объект o явно передается в self-ссылку (self-reference). Присваивания и вызовы процедур остаются совместимыми с описанием **Oberon**.
==== 2.7 Описания (definitions) ====
//Описание// (definition) — это синтаксический контракт 1, определяющий набор сигнатур методов. Описание ''D0'' может быть уточнено новым описанием ''D1'', которое наследует все методы, объявленные в ''D0''. Описания и их методы видимы //глобально//. Объект может реализовать одно или несколько описаний, в этом случае он обязуется реализовать все методы определенные в этих описаниях.
DEFINITION Runnable;
PROCEDURE Start;
PROCEDURE Stop;
END Runnable;
DEFINITION Preemptable REFINES Runnable;
PROCEDURE Resume;
PROCEDURE Suspend;
END Preemptable;
TYPE
MyThread = OBJECT IMPLEMENTS Runnable;
PROCEDURE Start;
BEGIN .... END Start;
PROCEDURE Stop;
BEGIN .... END Stop;
END MyThread;
Ключевое слово ''IMPLEMENTS'' используется для указания описаний, реализованных объектным типом. Объектный тип может реализовать //несколько// описаний.
Описания можно понимать как //дополнительные свойства//, которыми //должен// обладать объект, но которые ортогональны иерархии типов объектов. Метод объекта может быть вызван через описание, в этом случае во время исполнения проверяется, действительно ли экземпляр объекта реализует описание, и только после этого вызывается метод; если экземпляр объекта не реализует описание, то возникает //исключение// (run-time exception).
PROCEDURE Execute(o: OBJECT; timeout: LONGINT);
BEGIN
Runnable(o).Start;
Delay(timeout);
Runnable(o).Stop;
END Execute;
===== 3 Поддержка параллелизма =====
==== 3.1 Активные объекты ====
Определение объекта может включать ''StatBlock'', названное //телом объекта// (object body). Тело --- это активность объекта, которая исполняется после того, как экземпляр объекта размещен и инициализатор (если он есть) завершил свое исполнение; тело объекта помечается модификатором ''ACTIVE''. Во время размещения объекта так же размещается новый процесс, который исполняет тело //параллельно//; такой объект называется //активным объектом// (active object).
Если не указан модификатор ''ACTIVE'', тело исполняется //синхронно//; выход из ''NEW'' происходит только после того, как исполнение тела объекта завершается.
Система сохраняет явные ссылки на активные объекты до завершения исполнения активности для того, чтобы избежать утилизацию объекта в процессе сборки мусора. Объект //может// пережить свою активность.
TYPE
(* определяем объект и его поведение *)
Object = OBJECT
BEGIN {ACTIVE} (* тело объекта *)
... делаем что-либо ...
END Object;
PROCEDURE CreateAndStartObject;
VAR o: Object;
BEGIN
... NEW(o); ...
END CreateAndStartObject;
//Активность объекта// завершается //после// завершения исполнения //тела// объекта. Пока исполняется тело, объект продолжает существовать (например, он не может быть утилизирован при сборе мусора). После этого объект становится пассивным и может быть утилизирован в соответствии с обычными правилами.
==== 3.2 Защита ====
''Statement Block'' --- это последовательность операторов, заключенная между ''BEGIN'' и ''END''. Он может использоваться в любом месте как и простой оператор. Более полезно использовать его вместе с модификатором ''EXCLUSIVE'' для создания критической области для защиты операторов от одновременного исполнения.
PROCEDURE P;
VAR x, y, z: LONGINT;
BEGIN
x := 0;
BEGIN
y := 1
END;
z := 3
END P;
Объект может рассматриваться как ресурс и различные активности могут потенциально соревноваться за его использование или за эксклюзивный доступ к предоставляемым им средствам; в таком случае защита доступа //необходима//. Наша модель защиты --- //монитор//, размещенный в экземпляре объекта (instance-based monitor.).
(* Процедуры Set и Reset взаимно исключаемы *)
TYPE
MyContainer = OBJECT
VAR x, y: LONGINT; (* Инвариант: y = f(x) *)
PROCEDURE Set(x: LONGINT);
BEGIN {EXCLUSIVE} (* изменение x и y атомарно *)
SELF.x := x; y := f(x)
END Set;
PROCEDURE Reset;
BEGIN
...
BEGIN {EXCLUSIVE} (* изменение x и y атомарно *)
x := x0; y := y0;
END;
....
END Reset;
END MyContainer
Каждый экземпляр объекта защищен и единицей защиты является произвольный блок операторов от отдельного оператора до целого метода. Блок операторов может быть защищен от одновременного доступа при помощи модификатора ''EXLUSIVE''. Активность остается на входе в эксклюзивный блок до тех пор, пока другая активность находится в эксклюзивном блоке того же экземпляра объекта.
Активность //не может// заблокировать объект //более одного// раза, повторный вход //не допускается//.
Каждый модуль считается объектным типом с //единственным// экземпляром (singleton instance), таким образом его процедуры тоже могут быть защищены. Областью видимости защиты является //модуль целиком// как и в случае //мониторов// [13].
**Замечание**: Реализована только ''EXCLUSIVE'' блокировка: ''SHARED'' блокировки (как это описано в [10]) могут быть реализованы через ''EXCLUSIVE'' блокировки и соответственно не являются базовой концепцией. Они используются очень редко и их реализация //не оправдывает// усложнение языка. Реализация ''SHARED'' блокировок описана в Приложении B.1.
**Замечание**: Повторный вход в блокировку не поддерживается из-за //концептуальной нечистоты// (см. [27]) и его корректная обработка стоит //дорого//; в нем нет реальной необходимости, т. к. можно проектировать программы без его использования. Повторно-входимые блокировки могут быть реализованы при помощи простых блокировок (см. Приложение B.3).
==== 3.3 Синхронизация ====
TYPE
Synchronizer = OBJECT
awake: BOOLEAN
PROCEDURE Wait;
BEGIN {EXCLUSIVE} AWAIT(awake); awake := FALSE
END Wait;
PROCEDURE WakeUp;
BEGIN {EXCLUSIVE} awake := TRUE
END WakeUp;
END Synchronizer;
Встроенная процедура ''AWAIT'' используется для синхронизации активности с состоянием системы. Аргументом ''AWAIT'' может быть только //логическое условие//; активность сможет продолжить свое выполнение только после того, как условие станет истинным. Пока условие не выполняется, активность остается //приостановленной// (suspended); если это происходит внутри защищенного блока, то блокировка снимается на время приостановки активности (что позволяет другим активностям изменять состояние объекта и сделать условие истинным); активность возобновляет работу только если она сможет снова захватить блокировку.
Система отвечает за проверку условий и за возобновление работы приостановленных активностей. Условия внутри экземпляра объекта перепроверяются в случае, когда некоторая активность выходит из защищенного блока того же самого экземпляра объекта. Это означает, что изменение состояния объекта вне защищенного блока не приводит к перевычислению условия.
Когда несколько активностей соревнуются за одну и ту же блокировку, то активности с выполненными условиями рассматриваются раньше тех, которые только хотят войти в защищенную область.
Приложение B.6 демонстрирует синхронизацию внутри разделяемого буфера.
**Замечание**: Синхронизация //зависит// от состояния объекта, например, ожидание доступности некоторых данных или состояния для изменения. Объект используется как контейнер для данных и любой доступ осуществляется через защищенные методы или блоки. Мы подразумеваем, что при каждом доступе к защищенному блоку происходит изменение состояния объекта; таким образом условия перепроверяются только в этот момент. Это означает, что изменение состояния объекта вне защищенного блока не приводит к перевычислению условия. Для принудительной проверки условий можно вызвать пустой защищенный метод или войти в пустой защищенный блок.
===== 4 Прочие расширения языка =====
В данном разделе описываются несколько важных изменений, сделанных для лучшего интегрирования расширений в язык.
==== 4.1 Последовательность определений и опережающие ссылки ====
В **Active Oberon** область видимости описания символа распространяется на весь блок, содержащий его. Это означает, что символ может быть использован до своего определения, и что имена уникальны внутри области видимости.
==== 4.2 HUGEINT ====
В язык был добавлен 64 битный знаковый целый тип ''HUGEINT''. Он вписывается в иерархию числовых типов следующим образом:
''LONGREAL'' ⊇ ''REAL'' ⊇ ''HUGEINT'' ⊇ ''LONGINT'' ⊇ ''INTEGER'' ⊇ ''SHORTINT''
| Имя | Тип аргумента | Тип результата | Функция |
| ''SHORT(x)'' | ''HUGEINT'' | ''LONGINT'' | идентичность (возможно усечение) |
| ''LONG(x)'' | ''LONGINT'' | ''HUGEINT'' | идентичность |
| ''ENTIERH(x)'' | вещественный | ''HUGEINT'' | наибольшее целое, не превышающее x |
Таблица 1: Новые процедуры изменения типа
Таблица 1 показывает новые процедуры для изменения типа. Никаких новых правил описания констант не вводится; константы типизируются в соответствии с их значением.
| Имя | Функция |
| PUT8(adr: LONGINT; x: SHORTINT)
PUT16(adr: LONGINT; x: INTEGER)
PUT32(adr: LONGINT; x: LONGINT)
PUT64(adr: LONGINT; x: HUGEINT)
| Mem[adr] := x
|
| GET8(adr: LONGINT): SHORTINT
GET16(adr: LONGINT): INTEGER
GET32(adr: LONGINT): LONGINT
GET64(adr: LONGINT): HUGEINT
| RETURN Mem[adr]
|
| PORTIN(port: LONGINT; x: AnyType)
PORTOUT(port: LONGINT; x: AnyType)
| x := IOPort(port)
IOPort(port) := x
|
| CLI
STI
| отключить прерывания
включить прерывания
|
| EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP
AX, BX, CX, DX, SI, DI
AL, AH, BL, BH, CL, CH, DL, DH
| PUTREG/GETREG константы
32-битовые регистры
16-битовые регистры
8-регистры
|
Таблица 2: Новое в модуле ''SYSTEM'' для ''IA32''
==== 4.3 Нетрассируемые указатели (untraced pointers) ====
//Нетрассируемые указатели// --- это указатели, которые //не отслеживаются// сборщиком мусора. Структура или объект, на которые ссылаются только //нетрассируемые указатели//, могут быть в любой момент утилизированы сборщиком мусора.
Нетрассируемые указатели определяются при помощи модификатора ''UNTRACED''.
''TYPE Untraced = POINTER {UNTRACED} TO T;''
==== 4.4 Новое для IA32 ====
Функции из таблицы 2 были добавлены в компилятор для платформы **Intel IA32**.
''PUTx'' и ''GETx'' были добавлены ради безопасности, для работы с //нетипизированными константами//.
==== 4.5 Прочее ====
Некоторые расширения из **Oberon-2** были адаптированы для **Active Oberon**:
ASSERT
FOR
экспорт только для чтения
динамические массивы
Переменные указатели автоматически инициализируются значением ''NIL''.
===== Список литературы =====
[1] A. Beugnard, J.-M. Jґezґequel, N. Plouzeau, and D. Watkins. Making components contract aware. Computer, 32(7):38–45, July 1999.
[2] R. Brega. Real-time kernel for the Power-PC architecture. Master’s thesis, Institut fЁur Robotik, ETH ZЁurich, 1995.
[3] P. Brinch Hansen. Structured multiprogramming. Communications of the ACM, 15(7):574–578, July 1972. Reprinted in The Search for Simplicity, IEEE Computer Society Press, 1996.
[4] Microsoft Corporation. Microsoft C# Language Specifications. Microsoft Press, 2001.
[5] Edsger W. Dijkstra. The structure of the THE-multiprogramming system. Communications of the ACM, 11(5):341–346, May 1968.
[6] H. Eberle. Development and Analysis of a Workstation Computer. Dissertation 8431, ETH ZЁurich, 1987.
[7] P. FrЁohlich. Projekt Froderon: Zur weiteren Entwicklung der Programmiersprache Oberon-2. Master’s thesis, Fachhochschule MЁunchen, 1997.
[8] J. Gosling, B. Joy, and G. Steele. The Java Language Specification. The Java Series. Addison-Wesley, 1st edition, 1996.
[9] R. Griesemer. A Programming Language for Vector Computers. Dissertation 10277, ETH ZЁurich, 1993.
[10] J. Gutknecht. Do the fish really need remote control? A proposal for selfactive objects in Oberon. In Proc. of Joint Modular Languages Conference (JMLC). LNCS 1024, Linz, Austria, March 1997. Springer Verlag.
[11] J. Gutknecht and N. Wirth. Project Oberon - The Design of an Operating System and Compiler. Addison-Wesley, 1992.
[12] B. Heeb and C. Pfister. Chameleon: A workstation of a different colour. In Field-Programmable Gate Arrays: Architectures and Tools for Rapid Prototyping. Second International Workshop on Field Programmable Logic and Applications, pages 152–161, August 1992.
[13] C. A. R. Hoare. Monitors: An operating system structuring concept. Communications of the ACM, 17(10):549–557, October 1974. Erratum in Communications of the ACM, Vol. 18, No. 2 (February), p. 95, 1975. This paper contains one of the first solutions to the Dining Philosophers problem.
[14] International Organization for Standardization. ISO/IEC 8652:1995: Information technology — Programming languages — Ada. International Organization for Standardization, Geneva, Switzerland, 1995.
[15] P. Januschke. Oberon-XSC - Eine Programmiersprache und Arithmetikbibliothek fЁur das Wissenschaftliche Rechnen. PhD thesis, UniversitЁat Karlsruhe, 1998.
[16] K. Jensen and N. Wirth. PASCAL - User Manual and Report, volume 18 of Lecture Notes in Computer Science. Springer, 1974.
[17] S. E. Knudsen. Medos-2: A Modula-2 oriented operating system for the personal computer Lilith. Diss no. 7346, ETH ZЁurich, 1983.
[18] B. Meyer. Object-Oriented Software Construction. Prentice Hall, 2nd edition, 1997.
[19] H. MЁossenbЁock, J. Templ, and R. Griesemer. Object Oberon: An objectoriented extension of Oberon. Technical Report 1989TR-109, Department of Computer Science, ETH ZЁurich, June 1989.
[20] H. MЁossenbЁock and N. Wirth. The programming language Oberon-2. Structured Programming, 12(4):179–195, 1991.
[21] P.J. Muller. The Active Object System – Design and Multiprocessor Implementation. PhD thesis, ETH ZЁurich, 2002.
[22] R. Ohran. Lilith: A Workstation Computer for Modula-2. Dissertation 7646, ETH ZЁurich, 1984.
[23] A. Radenski. Introducing objects and concurrency to an imperative programming language. Information Sciences, an International Journal, 87(1- 3):107–122, 1995.
[24] A. Radenski. Module embedding. Software - Concepts and Tools, 19(3):122–129, 1998.
[25] B. A. Sanders and S. Lalis. Adding concurrency to the Oberon system. In Proceedings of Programming Languages and System Architectures, Lecture Notes in Computer Science (LNCS) 782. Springer Verlag, March 1994.
[26] C. Szyperski. Import is not inheritance – why we need both: modules and classes. In O. Lehrmann Madsen, editor, Proceedings, ECOOP 92, number 615 in Lecture Notes in Computer Science, pages 19–32. Springer-Verlag, 1992.
[27] Clemens Szyperski. Component Software: Beyond Object-Oriented Programming. ACM Press and Addison-Wesley, New York, NY, 1998.
[28] N. Wirth. MODULA : A language for modular multiprogramming. Software Practice and Experience, 7:3–35, 1977.
[29] N. Wirth. The programming language Oberon. Software Practice and Experience, 18(7):671–690, July 1988.
[30] N. Wirth and M. Reiser. Programming in Oberon - Steps Beyond Pascal and Modula. Addison-Wesley, 1992.
===== A Синтаксис Active Oberon =====
Module = MODULE ident ‘;’ [ImportList] {Definition} {DeclSeq} Body ident ‘.’.
ImportList = IMPORT ident [‘:=’ ident] {‘,’ ident [‘:=’ ident ]} ‘;’.
Definition = DEFINITION ident [REFINES Qualident] {PROCEDURE ident [FormalPars] ‘;’} END ident.
DeclSeq = CONST {ConstDecl ‘;’} | TYPE {TypeDecl ‘;’} | VAR {VarDecl ‘;’} | {ProcDecl ‘;’}.
ConstDecl = IdentDef ‘=’ ConstExpr.
TypeDecl = IdentDef ‘=’ Type.
VarDecl = IdentList ‘:’ Type.
ProcDecl = PROCEDURE ProcHead ‘;’ {DeclSeq} Body ident.
ProcHead = [SysFlag] [‘*’ | ‘&’] IdentDef [FormalPars].
SysFlag = ‘[’ ident ‘]’.
FormalPars = ‘(’ [FPSection {‘;’ FPSection}] ‘)’ [‘:’ Qualident].
FPSection = [VAR] ident {‘,’ ident} ‘:’ Type.
Type = Qualident
| ARRAY [SysFlag] [ConstExpr {‘,’ ConstExpr}] OF Type
| RECORD [SysFlag] [‘(’ Qualident ‘)’] [FieldList] END
| POINTER [SysFlag] TO Type
| OBJECT [[SysFlag] [‘(’ Qualident ‘)’] [IMPLEMENTS Qualident] {DeclSec} Body]
| PROCEDURE [SysFlag] [FormalPars].
FieldDecl = [IdentList ‘:’ Type].
FieldList = FieldDecl {‘;’ FieldDecl}.
Body = StatBlock | END.
StatBlock = BEGIN [‘{’IdentList‘}’] [StatSeq] END.
StatSeq = Statement {‘;’ Statement}.
Statement = [Designator ‘:=’ Expr
| Designator [‘(’ ExprList‘)’]
| IF Expr THEN StatSeq {ELSIF Expr THEN StatSeq}[ELSE StatSeq] END
| CASE Expr DO Case {‘|’ Case} [ELSE StatSeq] END
| WHILE Expr DO StatSeq END
| REPEAT StatSeq UNTIL Expr
| FOR ident ‘:=’ Expr TO Expr [BY ConstExpr] DO StatSeq END
| LOOP StatSeq END
| WITH Qualident ‘:’ Qualident DO StatSeq END
| EXIT
| RETURN [Expr]
| AWAIT ‘(’ Expr ‘)’
| StatBlock
].
Case = [CaseLabels { ‘,’ CaseLabels } ‘:’ StatSeq].
CaseLabels = ConstExpr [‘..’ ConstExpr].
ConstExpr = Expr.
Expr = SimpleExpr [Relation SimpleExpr].
SimpleExpr = Term {MulOp Term}.
Term = [‘+‘|’-’] Factor {AddOp Factor}.
Factor = Designator[‘(’ ExprList‘)’] | number | character | string
| NIL | Set | ‘(’Expr‘)‘|’ ’Factor.
Set = ‘{’ [Element {‘,’ Element}] ‘}’.
Element = Expr [‘..’ Expr].
Relation = ‘=’ | ‘#’ | ‘<’ | ‘<=’ | ‘>’ | ‘>=’ | IN | IS.
MulOp = ‘*’ | DIV | MOD | ‘/’ | ‘&’ .
AddOp = ‘+’ | ‘-’ | OR .
Designator = Qualident { ‘.’ ident | ‘[’ExprList‘]’ | ‘^’
| ‘(’ Qualident ‘)’ }.
ExprList = Expr {‘,’ Expr}.
IdentList = IdentDef {‘,’ IdentDef}.
Qualident = [ident ‘.’] ident.
IdentDef = ident [‘*‘|’-’].
===== B Примеры синхронизации =====
==== B.1 Читатели и писатели ====
MODULE ReaderWriter;
TYPE
RW = OBJECT
(* n = 0, пусто *)
(* n < 0, n писателей *)
(* n > 0, n читателей *)
VAR n: LONGINT;
PROCEDURE EnterReader*;
BEGIN {EXCLUSIVE}
AWAIT(n >= 0); INC(n)
END EnterReader;
PROCEDURE ExitReader*;
BEGIN {EXCLUSIVE}
DEC(n)
END ExitReader;
PROCEDURE EnterWriter*;
BEGIN {EXCLUSIVE}
AWAIT(n = 0); DEC(n)
END EnterWriter;
PROCEDURE ExitWriter*;
BEGIN {EXCLUSIVE}
INC(n)
END ExitWriter;
PROCEDURE & Init;
BEGIN n := 0
END Init;
END RW;
END ReaderWriter.
Образец ''Читатели --- Писатели'' регулирует доступ к данным в //критической секции//. Либо один ''Писатель'' (активность, изменяющая состояние объекта), либо несколько ''Читателей'' (активности, не изменяющие состояние объекта) допускаются в критическую секцию в предоставленное время.
==== B.2 Сигналы ====
TYPE
Signal* = OBJECT
VAR
in: LONGINT; (* следующий билет для выдачи *)
out: LONGINT; (* следующий билет для обслуживания *)
(* элементы с (out <= ticket < in) должны ждать *)
PROCEDURE Wait*;
VAR ticket: LONGINT;
BEGIN {EXCLUSIVE}
ticket := in; INC(in); AWAIT(ticket - out < 0)
END Wait;
PROCEDURE Notify*;
BEGIN {EXCLUSIVE}
IF out # in THEN INC(out) END
END Notify;
PROCEDURE NotifyAll*;
BEGIN {EXCLUSIVE}
out := in
END NotifyAll;
PROCEDURE & Init;
BEGIN in := 0; out := 0
END Init;
END Signal;
''Signal'' реализует примитивы для работы с сигналами **Active Oberon** подобно тому, как это сделано в **Java** и **Modula-2**. Он использует слегка измененный ''ticket-algorithm''. Как в некоторых магазинах, каждый покупатель получает занумерованый билет, это //гарантирует//, что покупатели будут обслужены в порядке их прибытия.
==== B.3 Повторно входимые блокировки ====
ReentrantLock* = OBJECT
VAR
lockedBy: PTR;
depth: LONGINT;
PROCEDURE Lock*;
VAR me: PTR;
BEGIN {EXCLUSIVE}
me := AosActive.CurrentThread();
AWAIT((lockedBy = NIL) OR (lockedBy = me));
lockedBy := me;
INC(depth)
END Lock;
PROCEDURE Unlock*;
BEGIN {EXCLUSIVE}
DEC(depth);
IF depth = 0 THEN lockedBy := NIL END
END Unlock;
END ReentrantLock;
''ReentrantLock'' позволяет блокировать объект его хозяином //более// одного раза. Клиенты этого объекта должны явно использовать ''Lock'' и ''Unlock'' вместо пометки защищаемого участка оператором ''EXCLUSIVE''.
==== B.4 Бинарный и общий семафоры ====
MODULE Semaphores;
TYPE
Sem* = OBJECT (* Бинарный семафор *)
VAR taken: BOOLEAN
PROCEDURE P*; (* войти *)
BEGIN {EXCLUSIVE}
AWAIT(~taken); taken := TRUE
END P;
PROCEDURE V*; (* войти *)
BEGIN {EXCLUSIVE}
taken := FALSE
END V;
PROCEDURE & Init;
BEGIN taken := FALSE
END Init;
END Sem;
GSem* = OBJECT (* Общий семафор *)
VAR slots: LONGINT;
PROCEDURE P*;
BEGIN {EXCLUSIVE}
AWAIT(slots > 0); DEC(slots)
END P;
PROCEDURE V*;
BEGIN {EXCLUSIVE}
INC(slots)
END V;
PROCEDURE & Init(n: LONGINT);
BEGIN slots := n
END Init;
END GSem;
END Semaphores.
Это хорошо известные синхронизирующие примитивы **Дейкстры** [5]. Заметим, что возможность реализовать семафоры показывает, что модель **Active Oberon** достаточно мощная для поддержки защиты и синхронизации параллельных процессов.
==== B.5 Барьеры ====
MODULE Barriers;
(*
Барьер используется для синхронизации N активностей.
*)
TYPE
Barrier = OBJECT
VAR in, out, N: LONGINT;
PROCEDURE Enter*;
VAR i: LONGINT;
BEGIN {EXCLUSIVE}
INC(in);
AWAIT (in >= N);
INC(out);
IF (out = N) THEN in := 0; out := 0 END;
END Enter;
PROCEDURE & Init (nofProcs: LONGINT);
BEGIN
N := nofProcs; in := 0; out := 0;
END Init;
END Barrier;
END Barriers.
Барьер используется для синхронизации активностей друг с другом. Если активности определены как
P = Phase ;P hase ;...P hase i i,0 i,1 i,n
то барьер используется для гарантии того, что все активности выполнят ''Phasei,j'' до начала ''Phasei,j+1''. Отдельный поток исполнения будет выглядеть подобно следующему:
FOR j := 0 TO N DO b
Phase(i, j); barrier.Enter
END;
Барьер сбрасывает счетчик ''in'' после выполнения условия чтобы избежать переполнения. Это возможно потому, что активности, повторно запрашивающие блокировку через инструкцию ''AWAIT'', имеют более высокий приоритет по сравнению с активностями, запрашивающими блокировку в первый раз в том же блоке ''EXCLUSIVE''.
==== B.6 Ограниченный буфер ====
MODULE Buffers;
CONST
BufLen = 256;
TYPE
(* Buffer - FIFO буфер *)
Buffer* = OBJECT
VAR
data: ARRAY BufLen OF INTEGER;
in, out: LONGINT;
(* Put - вставить элемент в буфер *)
PROCEDURE Put* (i: INTEGER);
BEGIN {EXCLUSIVE}
AWAIT ((in + 1) MOD BufLen # out); (*AWAIT ~полный *)
data[in] := i;
in := (in + 1) MOD BufLen
END Put;
(* Get - забрать элемент из буфера *)
PROCEDURE Get* (VAR i: INTEGER);
BEGIN {EXCLUSIVE}
AWAIT (in # out); (*AWAIT ~пустой *)
i := data[out];
out := (out + 1) MOD BufLen
END Get;
PROCEDURE & Init;
BEGIN
in := 0; out := 0;
END Init;
END Buffer;
END Buffers.
''Buffer'' реализует ограниченный кольцевой буфер. Методы ''Put'' и ''Get'' защищены от одновременного доступа; они так же проверяют наличие свободного места и данных соответственно, в противном случае активность приостанавливается до того, как место освободиться или поступят новые данные.
===== C Примеры активных объектов =====
==== C.1 Обедающие философы ====
MODULE Philo;
IMPORT Semaphores;
CONST
NofPhilo = 5; (* количество философов *)
VAR
fork: ARRAY NofPhilo OF Semaphores.Sem;
i: LONGINT;
TYPE
Philosopher = OBJECT
VAR
first, second: LONGINT;
(* вилки для философов *)
PROCEDURE & Init(id: LONGINT);
BEGIN
IF id # NofPhilo-1 THEN
first := id; second := (id+1)
ELSE
first := 0; second := NofPhilo-1
END
END Init;
BEGIN {ACTIVE}
LOOP
.... Думает....
fork[first].P; fork[second].P;
.... Ест ....
fork[first.V; fork[second].V
END
END Philosopher;
VAR
philo: ARRAY NofPhilo OF Philosopher;
BEGIN
FOR i := 0 TO NofPhilo DO
NEW(fork[i]);
NEW(philo[i]);
END;
END Philo.
==== C.2 Решето Эратосфена ====
MODULE Eratosthenes; (* prk 13.09.00 *)
IMPORT Out, Buffers;
CONST
N = 2000;
Terminate = -1; (* охранник *)
TYPE
Sieve = OBJECT (Buffers.Buffer)
VAR prime, n: INTEGER; next: Sieve;
PROCEDURE & Init;
BEGIN
Init^; (* вызывает инициализатор Buffer (суперклас) *)
prime := 0; next := NIL
END Init;
BEGIN {ACTIVE}
LOOP
Get(n);
IF n = Terminate THEN
(* прервать выполнение *)
IF next # NIL THEN next.Put (n) END;
EXIT
ELSIF prime = 0 THEN
(* первое число всегда простое *)
Out.Int(n, 0); Out.String(" простое"); Out.Ln;
prime := n;
NEW (next)
ELSIF (n MOD prime) # 0 THEN
(* передать дальше, если это не множитель простого *)
next.Put (n)
END
END
END Sieve;
PROCEDURE Start*;
VAR s: Sieve; i: INTEGER;
BEGIN
NEW(s);
FOR i := 2 TO N-1 DO s.Put (i) END;
s.Put(Terminate) (* использовать охранника для индикации выполнения *)
END Start;
END Eratosthenes.
''Eratosthenes'' использует //отсеивающий алгоритм// для поиска простых чисел. Каждое решето --- это активный объект, который передает все полученные значения, не являющиеся множителем первого полученного числа, на следующее решето. Синхронизация осуществляется в буфере.