Active Oberon

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку
Active Oberon
Дата появи 1997

Active Oberon — типобезпечна модульна об'єктно-орієнтована багатопотокова мова програмування загального призначення, яку розробила в 1996—1997 роках група професора Гуткнехта[de] у Швейцарській вищій технічній школі Цюриха (ETHZ) з метою введення в мову Оберон властивостей для вираження паралелізму за допомогою активних об'єктів[1].

Особливості мови[ред. | ред. код]

Назва Active Oberon відбиває основну концепцію мови — концепцію активних об'єктів, виражену в реалізації багатопотоковості та механізмів синхронізації на рівні мови.

Active Oberon розширює мову Оберон, вводячи поняття об'єкт (Object) та активність (Activity), пов'язана з об'єктом. Такий зв'язок називають активним об'єктом (Active Object), що означає здатність примірника об'єкта мати активність — власний потік виконання[1].

Мова належить до модульних типобезпечних мов програмування зі сильною статичною типізацією, допускаючи у виразах неявне зведення скалярних типів без втрати даних (наприклад, використання цілого числа там, де передбачалося використання числа з рухомою комою).

Модулі забезпечують не тільки розділену компіляцію, інкапсуляцію, але й можливість реалізації динамічного завантаження/вивантаження скомпільованих (об'єктних) модулів, що використовується, наприклад, у операційній системі A2, написаній цією мовою. Певною мірою, аналогом модуля можна вважати динамічно приєднувану бібліотеку.

В Active Oberon, як і в інших спадкоємцях Модули-2 при зверненні до сутностей приєднаного (імпортованого) модуля потрібно обов'язково кваліфікувати приєднаний модуль. Наприклад, якщо модуль A приєднує модуль B і використовує змінну цього модуля, то звернення до змінної повинне мати форму B.v. Інакше кажучи, імпорт не дозволяє за замовчанням імпортувати з приєднаного модуля всі сутності, які він експортує.

Інкапсуляція побудована на концепції модуля — всі типи, оголошені в модулі, повністю прозорі один для о́дного, а для доступу зовнішніх клієнтів потрібні специфікатори доступу. Специфікатори доступу дозволяють експортувати сутності або з повним доступом (ідентифікатор позначається знаком «*» (зірочка)), або з доступом «тільки для читання» (ідентифікатор позначається знаком «-» (мінус)). Наприклад, конструкція:

VAR
 Example2 : PROCEDURE {REALTIME, C} ( VAR low, high: LONGINT ): BOOLEAN;

визначає тип запису (RECORD) Example1, експортований за межі модуля, що має три поля з типом «довге ціле», причому поле «x» оголошено зі специфікатором доступу «повний доступ», поле «y» — зі специфікатором «доступ тільки для читання» і поле z є прихованим полем, недоступним зовнішнім клієнтам.

Мова підтримує поліморфізм, перевантаження операцій (для структурних типів), делегати, сумісні як із методами, так і з процедурами. Об'єктними типами є RECORD і посилальний тип OBJECT. Вони можуть мати методи та операції. У типу OBJECT можуть бути тіло та активність. Усі методи є віртуальними. Множинне успадкування відсутнє, замість нього використовують концепцію множинного успадкування інтерфейсів (DEFINITION у термінах мови).

Синтаксис та семантика[ред. | ред. код]

Синтаксис мови в процесі розвитку практично не змінюється — розробники воліють уточнювати семантику вже наявних синтаксичних конструкцій за допомогою введення семантичних модифікаторів, що дозволяє виключити значний обсяг змін при введенні нової функціональності, спростити компілятор, зробивши його код доступнішим для розуміння і модифікації, а також забезпечити легкість вивчення та використання мови. Модифікатори беруть у фігурні дужки {} після імені змінної, типу чи ключового слова. Наприклад, конструкція:

VAR
 Example2 : PROCEDURE {REALTIME, C} ( VAR low, high: LONGINT ): BOOLEAN;

оголошує процедурну змінну Example2, що вказує на процедуру реального часу, з домовленістю про виклик CCALL, яка приймає два параметри типу довге ціле і повертає значення логічного типу.

Опис об'єкта, в цілому, відповідає опису модуля, за винятком синтаксису заголовка та відсутності секції IMPORT. Методи повністю описуються всередині опису об'єкта, операції, за рідкісним винятком, можуть описуватися поза тілом об'єкта. Об'єкт може мати довільну кількість ініціалізаторів і не більше одного фіналізатора. Вбудована процедура NEW, що використовується для створення змінних посилального типу, викликає ініціалізатор, який компілятор вибирає за сигнатурою параметрів. Ініціалізатор позначають символом & перед назвою методу. Фіналізатор — метод без параметрів, перед іменем якого стоїть знак ~, — автоматично викликається для утилізації об'єкта.

Послідовність операторів, укладену в операторні дужки BEGIN END, називають блоком операторів. Блок операторів також може містити список модифікаторів та секцію гарантованого завершення (FINALLY).

Змінну, а також поле запису або об'єкта, можна ініціалізувати константним виразом при оголошенні:

TYPE
  Point = RECORD
    x := 0,
    y := 0 : LONGINT;
  END;

VAR
  i := 0, j := 10, k := 100 : INTEGER;
  Point : Point; (* поля x, y запису ініціалізовано значенням 0 *)

Типи даних[ред. | ред. код]

Мова пропонує багатий набір вбудованих типів:

Багатопотоковість[ред. | ред. код]

В Active Oberon реалізовано дві моделі багатопотоковості, засновані на роботах Брінча Гансена та Тоні Гоара[2]:

Сирцевий код, написаний з використанням синтаксису блокувальних примітивів синхронізації мови Active Oberon, можна використовувати для обох моделей багатопотоковості — компілятор генеруватиме код, потрібний для конкретної моделі. За такого підходу немає потреби у переписуванні програмного забезпечення під різні моделі. Лише невеликі ділянки сирцевого коду вимагають адаптації (наприклад, обробка переривань), щоб придушити автоматичне генерування перемикань у цій ділянці машинного коду. Для цього блок операторів позначають модифікатором {UNCOOPERATIVE}.

Активні об'єкти[ред. | ред. код]

Потік інкапсульовано в об'єкті і, як його невід'ємна частина, він створюється в момент інстанціювання активного об'єкта. Для вказівки на активність об'єкта його тіло позначають модифікатором ACTIVE.

Після розміщення примірника об'єкта виконується ініціалізатор (якщо є), потім тіло об'єкта (якщо є). Якщо об'єкт позначено активним, створюється активність, у якій асинхронно виконується тіло об'єкта, в іншому разі виконання відбувається синхронно в потоці, в якому створено примірник.

Активність об'єкта завершується після виконання тіла об'єкта. Поки виконується тіло, об'єкт продовжує існувати, навіть якщо на нього немає посилань. Після цього об'єкт стає пасивним і може бути утилізований відповідно до звичайних правил.

Приклад опису та використання активного об'єкта:

MODULE Example3;

TYPE
 ActiveObject = OBJECT
 VAR state : SET;

  PROCEDURE &New;
  BEGIN
   state := {};
  END New;

  PROCEDURE &Init*( state : SET );
  BEGIN
   SELF.state := state;
  END Init;

  PROCEDURE ~Finalize;
  BEGIN
  ...
  END Finalize;

 BEGIN {ACTIVE}
  ...
 END ActiveObject;

VAR
 object : ActiveObject;

BEGIN
 NEW( object );
 object.Init( {0..7, 9, 12, 30..31} );
 NEW( object, {} );
END Example3.

Міжпроцесова взаємодія[ред. | ред. код]

Виклики методів спільно використовуваних активних та неактивних об'єктів діють як комунікаційний механізм між активними об'єктами. Оскільки активні об'єкти існують у багатопотоковому середовищі, надається механізм для координування паралельного доступу до їхнього стану. Взаємодія через обмін повідомленнями може здійснюватися з використанням спеціальних програмних каркасів[4].

Захист від одночасного виконання[ред. | ред. код]

Блок операторів, позначений модифікатором EXCLUSIVE, називають монопольною ділянкою (exclusive region). Монопольна ділянка в Active Oberon відповідає концепції критичної ділянки Гансена[5]. Якщо монопольна ділянка охоплює все тіло методу, його називають монопольним методом (exclusive method) і він поєднує концепцію Гансена з процедурою монітора Гоара[6][7]. На відміну від процедури монітора, метод об'єкта може бути монопольним — у такому разі він може спостерігати неузгоджені стани об'єкта. У монопольній ділянці може бути не більше однієї активності одночасно.

Таким чином, модель захисту в Active Oberon — монітор, розміщений у примірнику об'єкта (instance-based monitor). Модуль вважають об'єктним типом з єдиним примірником (singleton instance) і його процедури теж можуть бути монопольними, захищаючи модуль загалом.

Головна ідея монітора (і активного об'єкта) у тому, що з монітором пов'язується якийсь інваріант — вираз, що визначає несуперечливий внутрішній стан об'єкта і доводить правильність його поведінки[8]. Ініціалізатори та монопольні методи є інструментами, які мова надає для підтримки інваріантів об'єкта та приховування його внутрішнього стану. Ініціалізатор об'єкта встановлює його інваріант, а монопольні методи його підтримують. Коли поняття монітора об'єднано з поняттям модуля, формується потужний механізм для структурування операційних систем[9][10][11].

Приклад використання монопольної секції:

(* Процедури 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;

Синхронізація[ред. | ред. код]

На відміну від більшості реалізацій моніторів, що використовують для синхронізації умовні змінні Гоара[7] (на основі черг подій Брінча Гансена[5]), синхронізацію активностей забезпечує оператор AWAIT, що приймає як аргумент логічний вираз — умову продовження виконання програми в тілі об'єкта. Щоб гарантувати правильність синхронізації, AWAIT має міститись у монопольній ділянці. У разі невиконання умови продовження AWAIT призупиняє (suspend) активність і, якщо міститься в монопольній ділянці, відпускає захоплену ділянку на час припинення, що дозволяє іншим активностям змінити стан об'єкта і зробити умову продовження істинною. Зупинена активність продовжить роботу лише в тому разі, якщо зможе повторно увійти до монопольної ділянки.

Приклад синхронізації всередині розділюваного буфера:

TYPE
 Synchronizer = OBJECT
  VAR awake: BOOLEAN

  PROCEDURE Wait;
  BEGIN {EXCLUSIVE}
   AWAIT(awake);
   awake := FALSE;
  END Wait;

  PROCEDURE WakeUp;
  BEGIN {EXCLUSIVE}
   awake := TRUE;
  END WakeUp;
 END Synchronizer;

Обробка помилок та виняткових ситуацій[ред. | ред. код]

В Active Oberon відсутні засоби структурної обробки виняткових ситуацій — їх обробляє централізоване середовище часу виконання.

Оператор ASSERT приймає як обов'язковий аргумент логічний вираз, у разі порушення якого відбувається переривання програми і, як і при виконанні оператора безумовної зупинки HALT, керування передається в централізований обробник винятків. Якщо умову оператора ASSERT можна обчислити на етапі компіляції, то, в разі невиконання умови, генерується помилка компіляції. Оператор ASSERT і HALT можуть мати необов'язковий параметр — специфікатор винятку, який можна проаналізувати в обробнику.

Після обробки винятку керування передається в найближчу за стеком викликів секцію гарантованого завершення FINALLY, якщо вона є. Потім, якщо тіло активного об'єкта позначено модифікатором SAFE, активність буде перезапущено, інакше її робота завершується.

Середовище часу виконання[ред. | ред. код]

Інформація про типи часу виконання[ред. | ред. код]

Тільки структурні типи (масиви, записи, об'єкти) мають інформацію про типи часу виконання (метаінформацію). Метаінформація зберігається у спеціальній структурі, званій дескриптором типу. Дескриптор типу містить дані про типи та назви змінних і полів, таблицю успадкування, таблицю віртуальних методів та таблиці реалізованих інтерфейсів.

Керування пам'яттю[ред. | ред. код]

В Active Oberon застосовується автоматичне керування пам'яттю з використанням переривного (витискального) збирача сміття реального часу[12], заснованого на методі позначок (Mark and Sweep). Збирач сміття виконується в окремому потоці, і активності (потоки), з вищим пріоритетом, ніж пріоритет активності збирача сміття, можуть призупинити його виконання. При цьому дерево об'єктів заморожується. На даний момент тільки сутності реального часу можуть переривати активність збирача сміття, в них заборонено динамічне виділення пам'яті, за цим стежить компілятор.

Керування пам'яттю ґрунтується на використанні типізованих ділянок пам'яті. Така ділянка зберігає вказівник на дескриптор типу. Використовуючи інформацію про типи часу виконання, розміщену в дескрипторі типу, збирач сміття знаходить змінні і поля посилального типу і позначає блоки, на які вони вказують. Іншими словами, збирачеві сміття немає необхідності перевіряти кожне значення, схоже на вказівник, чи воно є коректним вказівником у купі — використовуючи інформацію, яку надає дескриптор типу, він точно знає, які елементи обробляти, що істотно збільшує швидкість і точність роботи і знижує навантаження на процес збирання сміття. Для прискорення виділення пам'яті вільні ділянки поміщаються в списки вільних блоків, які містять блоки пам'яті певних розмірів.

Змінні посилального типу, позначені модифікатором UNTRACED належать до нетрасованих вказівників[en]. Такі вказівники не відстежує збирач сміття і ділянки пам'яті, на які вони посилаються, можуть бути утилізовані в будь-який момент часу, в разі, якщо їх виділило середовище часу виконання та на них немає досяжних посилань, які враховує збирач сміття. Часто такі модифікатори використовують для роботи з ділянками пам'яті, виділеними поза середовищем часу виконання Active Oberon, або з небезпечними вказівниками.

Керування активністю об'єктів[ред. | ред. код]

Середовище часу виконання відповідає за розподіл процесорного часу, гарантує (разом з компілятором), що в монопольній ділянці міститься не більше однієї активності, забезпечує своєчасну перевірку умов оператора AWAIT і відновлення роботи призупинених активностей. Вирази умов продовження всередині об'єкта перераховуються в усіх точках виходу з монопольних ділянок. Це означає, що зміна стану об'єкта поза монопольною ділянкою не призводить до перерахунку умов. Коли кілька активностей змагаються за ту саму монопольну ділянку, то активності з виконаними умовами розглядаються раніше від тих, які тільки хочуть увійти в захищену ділянку[2][13].

Підключення та ініціалізація модулів[ред. | ред. код]

В Active Oberon відсутні засоби для прямого керування динамічним завантаженням та вивантаженням модулів. Мова пропонує лише секцію імпорту (IMPORT), у якій вказано список модулів, що підключаються, і секцію ініціалізації модуля. Середовище часу виконання має забезпечити підключення та ініціалізацію модулів до ініціалізації поточного модуля. Динамічне підключення модуля здійснюється через механізм динамічного зв'язування. Модуль не можна вивантажити доти, доки на нього є посилання зі списку підключення іншого завантаженого модуля. Таким чином, щоб унеможливити проблему циклічних посилань, модулі слід вивантажувати в порядку, зворотному до порядку завантаження.

Обробка помилок та виняткових ситуацій[ред. | ред. код]

Модуль Traps забезпечує централізовану обробку виняткових ситуацій. Параметри, які приймають оператори ASSERT і HALT, можуть використовуватися для класифікації виняткової ситуації.

Після обробки виняткової ситуації в модулі Traps середовище часу виконання здійснює пошук секцій гарантованого завершення FINALLY та передає на них керування для виконання завершальних операцій.

У випадку, якщо активність позначена модифікатором SAFE і в тілі об'єкта відсутня секція FINALLY, відбувається перезапуск активності, інакше виконання активності завершується.

Програмний інтерфейс середовища виконання надає можливість встановлення власного обробника виняткових ситуацій.

Приклади програм[ред. | ред. код]

Hello, World![ред. | ред. код]

MODULE HelloWorld;
IMPORT KernelLog;

BEGIN
 KernelLog.String( "Hello, World!" );
END HelloWorld.

Розв'язок класичної задачі постачальника та споживача[ред. | ред. код]

MODULE BoundedBuffers;
TYPE
 Item* = OBJECT;

 Buffer* = OBJECT
  VAR
   h, n: INTEGER;
   B: ARRAY * OF Item;

  PROCEDURE Get*(): Item;
  VAR x: Item;
  BEGIN {EXCLUSIVE}
   AWAIT(n # 0); (* буфер не порожній *)
   x := B[h]; h := (h+1) MOD LEN(B); DEC(n);
   RETURN x
  END Get;

  PROCEDURE Put*(x: Item);
  BEGIN {EXCLUSIVE}
   AWAIT(n # LEN(B)); (* буфер не повний *)
   B[(h+n) MOD LEN(B)] := x; INC(n)
  END Put;

  PROCEDURE &Init(max: INTEGER);
  BEGIN (* ініціалізатор *)
   NEW(B, max); h := 0; n := 0
  END Init;
 END Buffer;

END BoundedBuffers.

Див. також[ред. | ред. код]

Примітки[ред. | ред. код]

  1. а б J. Gutknecht. Do the Fish Really Need Remote Control? A Proposal for Self-Active Objects in Oberon., 1997.
  2. а б в P.J. Muller. Active Object System. Design and Multiprocessor Implementation. — ETH Zurich, 2002., Diss. ETH № 14755.
  3. Florian Negele. Combining Lock-Free Programming with Cooperative Multitasking for a Portable Multiprocessor Runtime System, ETH Zurich, 2014, Diss. ETH №. 22298
  4. Florian Negele. A2 Concurrency Framework, ETH Zurich, June 3, 2009
  5. а б P. Brinch Hansen. Structured Multiprogramming. Communications of the ACM, 15(7), July 1972
  6. P. Brinch Hansen. Operating System Principles. Prentice-Hall, 1973.
  7. а б C.A.R. Hoare. Monitors: An Operating System Structuring Concept. Communications of the ACM, 17(10):549-557, October 1974.
  8. O. J. Dahl. Monitors Revisited. In A.W. Roscoe, editor, A Classical Mind — Essays in Honour of C.A.R. Hoare. Prentice-Hall,
    1994.
  9. J.L. Keedy. On Structuring Operating Systems With Monitors. ACM Operating Systems Review, 13(1), January 1979.
  10. N. Wirth. Modula: A Language for Modular Multiprogramming. Software — Practice and Experience, 7:3-35, 1977.
  11. N. Wirth. The Use of Modula. Software — Practice and Experience, 7:37-65, 1977.
  12. Ulrike Glavitsch. Real-time Garbage Collection in A2. Institute of Computer Systems, ETH Zurich
  13. P. Reali. Active Oberon Language Report. — ETH Zurich, 2004.

Посилання[ред. | ред. код]