Oberon family programming languages were not destined to break into the mainstream, although they left a significant mark in the IT-industry. However, both the operating systems written in these languages (being both software frameworks of various solutions and development environments), and the programming languages themselves are used in educational, research and industrial fields to this day, forcing people to create and experiment, to develop and absorb industry trends and influencing it.
With this review article, I open a series of articles devoted to the
Active Oberon language and the A2 operating system written in this language.
So, meet - Active Oberon
The first publication on Active Oberon appeared in 1997, but it is clear that the language and its implementation appeared somewhat earlier. Over the years, there have been many changes in the language, the runtime environment has been reworked, the A2 operating system has been written ...
A feature of the language that distinguishes it from other programming languages is the concept of Active Objects - objects that encapsulate the flow of execution. Active Oberon expands the Oberon language by introducing the object type Object, the concept of Activity associated with an object (stream), as well as synchronization and protection mechanisms against simultaneous access. The implementation of such mechanisms at the language level simplifies the development and implementation of multi-threaded applications and reduces the number of errors associated with the problem of protection and synchronization.
')
In the import section, plug-ins can have aliases, different modules can have the same alias, forming some kind of namespace from several modules. In such modules, the names of the exported entities should not overlap.
For example:
IMPORT Example := Example1, Example := Example2;
In Active Oberon, improved arrays (mathematical and tensor), structural arrays (objects compatible with improved arrays), complex types, operations for objects, improved arrays and complex types, delegates that are compatible with both module procedures and object methods are implemented. Instead of using multiple inheritance, the concept of multiple inheritance of interfaces is used.
The syntax of the language in the development process is practically unchanged - instead, semantic modifiers are introduced, changing the semantics of the syntactic structures present in the language. The list of modifiers is enclosed in {}.
So, the DELEGATE procedure modifier is used to describe the delegate:
Example1 : PROCEDURE ( VAR parameter : LONGINT ) : BOOLEAN;
The example describes the procedural variable Example1, which points to the delegate — a real-time procedure or method, which accepts a long integer variable by reference and returns a boolean type value.
Methods are described inside the object. All methods in Active Oberon are virtual. Methods whose name is preceded by the & sign are constructors and are called automatically when an object is created. The compiler chooses the required constructor for the signature of calling the built-in procedure NEW, designed to create reference type entities. Objects and methods can also have ABSTRACT and FINAL modifiers with clear semantics. Operations (operators) can be described both at the object level and at the module level, with the exception of some operators described only at the object level (at least in the current implementation).
Active Objects and multithreading model
An object can have a body — the final block of statements executed after the object is initialized. If the object body is marked with the ACTIVE modifier (possibly with a priority), then such an object is called active. As already mentioned, the activity (stream) is encapsulated in the object and is created at the time of instantiating the active object. Activity exists while the body of the active object is running. The active object continues to exist as long as its activity exists, even if it is not referenced, after which it becomes passive and can be disposed of in the usual way.
Communication between activities is performed through calls to methods of active and non-active objects.
Protection against simultaneous access to the states of an object is provided by support at the level of exclusive regions (exclusive regions) —sequences of statements enclosed in BEGIN ... END operator brackets, marked with the EXCLUSIVE modifier — corresponding to the concept of critical Brinch Hansen areas. If the monopole region covers the entire body of an object, it is called monopoly and combines the concept of Hansen’s critical regions with the concept of the Hoare monitor procedure. Unlike Hoar’s monitor procedures, object methods do not have to be exclusive, and can observe non-synchronized object states. A module is considered an object with one copy. The compiler and the runtime environment ensure that there can be no more than one activity in the exclusive area at a time.
Thus, the security model in Active Oberon is a monitor located in an instance-based monitor.
The main idea of the monitor is that an invariant is associated with it - an expression that defines an internal, non-contradictory state and proves the correctness of its behavior. Object initializers set an invariant, and exclusive methods support it.
The AWAIT operator is used for synchronization in Active Oberon, whereas most implementations of monitors use Hoare condition variables based on Hansen queues. The AWAIT operator takes as its argument the logical condition for the continuation of execution, in the case of non-observance of which the activity is suspended, and the captured monopole region is freed until the moment when the expression condition becomes true. Activity will continue to work when it can again enter the exclusive monopoly section. Continuation conditions are recalculated only when any activity leaves the monopoly section.
Example of describing the active object and using the exclusive section:
TYPE Timer* = OBJECT VAR timer: Objects.Timer; handler: Objects.EventHandler; timeout, running: BOOLEAN; PROCEDURE &Init*; BEGIN NEW(timer); timeout := FALSE; running := TRUE; END Init; PROCEDURE SetTimeout*(h: Objects.EventHandler; ms: LONGINT); BEGIN handler := h; Objects.SetTimeout(timer, HandleTimeout, ms) END SetTimeout; PROCEDURE CancelTimeout*; BEGIN Objects.CancelTimeout(timer); END CancelTimeout; PROCEDURE HandleTimeout; BEGIN timeout := TRUE END HandleTimeout; PROCEDURE Finalize*; BEGIN Objects.CancelTimeout(timer); running := FALSE END Finalize; BEGIN WHILE running DO LOOP BEGIN AWAIT(timeout OR ~running); IF ~running THEN EXIT END; timeout := FALSE END; handler() END END END Timer;
Memory management
Active Oberon uses automatic memory management using a real-time garbage collector. This means that real-time activities can pause the garbage collection process. In areas of the real-time code, memory allocation operations are prohibited; the compiler monitors this. Procedures, methods and real-time activities are marked with the REALTIME modifier. Not all compiler and runtime implementations support real-time processes.
Variables marked with the UNTRACED modifier refer to untrassible pointers and are not tracked by automatic memory management mechanisms.
The language supports the syntax of the operator DISPOSE, but its implementation may be missing.
This concludes the review article in the Active Oberon language, I recommend starting the acquaintance with the classic Oberon.
References:
Contents of the series