Oberón activo

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 9 de enero de 2018; las comprobaciones requieren 16 ediciones .
Oberón activo
clase de idioma imperativo , modular , orientado a objetos , multiproceso , estructural , tipo seguro
tipo de ejecución compilado , interpretado
Apareció en 1997
Autor Brinch Hansen, Jürg Gutknecht , Pieter Müller , Patrik Reali
extensión de archivo .Mod, mod
Liberar 2019
sistema de tipos estático , fuerte
Implementaciones principales Activa Oberon.Net, FOX, PACO, Ronin
Dialectos Zonnon
sido influenciado MATLAB , Modula-2 , Oberon , Objeto Oberon , Pascal
influenciado C# activo [1] , Composita, Go , Zonnon
Sitio web a2.inf.ethz.ch
Plataforma ARM , Celda , JVM , .NET , x86-32 , x86-64
sistema operativo A2 , Darwin , Linux , Solaris , ventanas

Active Oberon  es un lenguaje de programación de propósito general, con seguridad de tipos, modular  , orientado a objetos y multihilo desarrollado en 1996-1997 . un grupo del prof. Gutknecht en la ETH Zurich (ETHZ) con el objetivo de introducir en el lenguaje Oberon propiedades para expresar concurrencia a través de objetos activos [2] .

Características del lenguaje

El nombre Active Oberon refleja el concepto principal del lenguaje: el concepto de objetos activos , expresado en la implementación de mecanismos de sincronización y subprocesos múltiples a nivel del lenguaje.

Active Oberon amplía el lenguaje Oberon al introducir los conceptos de un objeto y una actividad asociada con un objeto. Tal relación se denomina Objeto activo y significa la capacidad de una instancia de objeto para tener una actividad: su propio hilo de ejecución [2] .

El lenguaje pertenece a los lenguajes de programación modulares con seguridad de tipos con fuerte tipado estático , lo que permite la conversión implícita de tipos escalares en expresiones sin pérdida de datos (por ejemplo, usando un número entero donde se suponía que se usaría un número de punto flotante).

Los módulos proporcionan no solo compilación separada , encapsulación , sino también la capacidad de implementar carga/descarga dinámica de módulos (objetos) compilados, que se usa, por ejemplo, en el sistema operativo A2 escrito en este lenguaje. De alguna manera, una biblioteca de enlace dinámico puede considerarse un análogo de un módulo .

En Active Oberon, como en otros sucesores de Modula-2 , al acceder a las entidades de un módulo conectado (importado), se requiere una calificación obligatoria del módulo conectado. Por ejemplo, si el módulo A incluye el módulo B y usa la variable del módulo v , entonces la referencia de la variable debe tener la forma Bv . En otras palabras, la importación en Active Oberon no permite por defecto importar desde el módulo conectado todas las entidades que exporta.

La encapsulación se basa en el concepto de un módulo: todos los tipos declarados en un módulo son completamente transparentes entre sí y se requieren especificadores de acceso para el acceso de clientes externos . Los especificadores de acceso le permiten exportar entidades con acceso completo (el identificador está marcado con un "*" (asterisco)) o con acceso de solo lectura (el identificador está marcado con un signo "-" (menos)). Por ejemplo, la construcción:

TIPO Ejemplo1 * = REGISTRO x * , y - , z : ENTRADA LARGA ; FIN ;

define el tipo de registro (REGISTRO) Ejemplo1 , exportado fuera del módulo y que tiene tres campos de tipo "entero largo", y el campo " x " se declara con el especificador de acceso "acceso completo", el campo " y " se declara con el especificador "acceso de solo lectura" y el campo ' z ' es un campo oculto, no accesible para clientes externos.

El lenguaje admite polimorfismo , sobrecarga de operadores (para tipos estructurales), delegados que son compatibles con métodos y procedimientos. Los tipos de objeto son RECORD y el tipo de referencia OBJECT . Pueden tener métodos y operaciones. Un tipo de OBJETO puede tener un cuerpo y una actividad. Todos los métodos son virtuales . No hay herencia múltiple , sino que se utiliza el concepto de herencia múltiple de interfaces ( DEFINICIÓN en términos de lenguaje).

Sintaxis y semántica

La sintaxis del lenguaje prácticamente no cambia durante el desarrollo: los desarrolladores prefieren refinar la semántica de las construcciones sintácticas existentes utilizando los modificadores semánticos introducidos , lo que permite eliminar una cantidad significativa de ediciones al introducir nuevas funciones, simplificar el compilador, haciendo que su código más accesible para su comprensión y modificación, y también proporciona facilidad de aprendizaje y uso del lenguaje. Los modificadores se encierran entre llaves {} después de un nombre de variable, tipo o palabra clave. Por ejemplo, la construcción:

VAR Ejemplo 2 : PROCEDIMIENTO { REALTIME , C } ( VAR bajo , alto : INT.LARGO ): BOOLEAN ;

declara una variable de procedimiento Example2 que apunta a un procedimiento en tiempo real con la convención de llamada CCALL que toma dos parámetros de tipo entero largo y devuelve un valor booleano.

La descripción del objeto, en general, corresponde a la descripción del módulo , excepto por la sintaxis del encabezado y la ausencia de la sección IMPORT. Los métodos se describen completamente dentro de la descripción del objeto; las operaciones, con raras excepciones, se pueden describir fuera del cuerpo del objeto. Un objeto puede tener un número arbitrario de inicializadores y no más de un finalizador . El NUEVO procedimiento integrado , que se utiliza para crear variables de un tipo de referencia, llama a un inicializador elegido por el compilador en función de la firma de los parámetros. El inicializador está marcado con & delante del nombre del método. Un finalizador, un método sin parámetros precedidos por un signo ~ , se llama automáticamente cuando se desecha un objeto.

Una secuencia de sentencias encerradas entre paréntesis de sentencias BEGIN END se denomina bloque de sentencias . El bloque de instrucciones también puede contener una lista de modificadores y una sección de finalización garantizada ( FINALMENTE ).

Una variable, así como un campo de un registro o un objeto, se puede inicializar con una expresión constante cuando se declara:

TIPO Punto = REGISTRO x := 0 , y := 0 : ENTRADA LARGA ; FIN ; VAR i := 0 , j := 10 , k := 100 : ENTERO ; Punto : Punto ; (* los campos x, y del registro se inicializan a 0 *)

Tipos de datos

El lenguaje ofrece un rico conjunto de tipos integrados:

  • Tipos basicos
    • booleano: BOOLEANO ;
    • carácter: CHAR8 , CHAR16 , CHAR32 y el alias CHAR para el tipo de carácter predeterminado;
    • enteros con signo: SIGNED8 , SIGNED16 , SIGNED32 , SIGNED64 y alias SMALLINT , INTEGER , LONGINT , HUGEINT ;
    • enteros sin signo: UNSIGNED8 , UNSIGNED16 , UNSIGNED32 , UNSIGNED64 ;
    • real: REAL , LARGO REAL ;
    • complejo: COMPLEJO , LONGCOMPLEX ;
    • dependiente de la máquina: TAMAÑO , DIRECCIÓN ;
    • conjunto: CONJUNTO, CONJUNTO8, CONJUNTO16, CONJUNTO32, CONJUNTO64 ;
    • enumeraciones expandibles: ENUM ;
  • estructural
    • arreglos: ARRAY  - estático, dinámico, abierto, matemático;
    • estructuras extensibles: RECORD ;
    • objeto: OBJETO ;
  • Especial
    • procesal;
    • delegados: variables procedimentales compatibles tanto con procedimientos como con métodos;
    • punteros escritos a tipos estructurales;
    • punteros genéricos: ANY , OBJECT , ARRAY ;
  • sistema: SISTEMA.BYTE

Multiproceso

Para Active Oberon, se implementan dos modelos de subprocesos múltiples, basados ​​en el trabajo de Brinch Hansen y Tony Hoare [3] :

El código fuente escrito con la sintaxis primitiva de sincronización de bloqueo de Active Oberon se puede usar para ambos modelos de subprocesos múltiples: el compilador generará el código necesario para un modelo en particular. Con este enfoque, no hay necesidad de volver a escribir el software para diferentes modelos. Solo pequeñas porciones de código fuente requieren adaptación (como el manejo de interrupciones) para suprimir la generación automática de interruptores en una porción dada de código de máquina. Para hacer esto, el bloque de instrucciones se marca con el modificador {UNCOOPERATIVE} .

Objetos activos

Un hilo está encapsulado en un objeto y, al ser parte integral del mismo, se crea en el momento en que se instancia el objeto activo . Para indicar la actividad de un objeto, su cuerpo se marca con el modificador ACTIVO .

Una vez asignada la instancia del objeto , se ejecuta el inicializador (si lo hay), y luego el cuerpo del objeto (si lo hay). Si el objeto se marca como activo, se crea una actividad en la que el cuerpo del objeto se ejecuta de forma asíncrona, de lo contrario, la ejecución se realiza de forma síncrona en el hilo en el que se creó la instancia.

La actividad de un objeto finaliza cuando se completa la ejecución del cuerpo del objeto. Mientras se ejecuta el cuerpo, el objeto continúa existiendo, incluso si no hay referencias a él. Después de eso, el objeto se vuelve pasivo y puede eliminarse de acuerdo con las reglas normales.

Un ejemplo de descripción y uso de un objeto activo:

MÓDULO Ejemplo3 ; ESCRIBE ObjetoActivo = OBJETO Estado VAR : ESTABLECER ; PROCEDIMIENTO & Nuevo ; EMPEZAR estado := {}; FIN Nuevo ; PROCEDIMIENTO & Init * ( estado : SET ); EMPEZAR AUTO . estado := estado ; FIN Inicial ; PROCEDIMIENTO ~ Finalizar ; EMPEZAR ... FIN Finalizar ; COMENZAR { ACTIVO } ... FIN ObjetoActivo ; VAR objeto : objeto activo ; EMPEZAR NUEVO ( objeto ); objeto _ Inicial ( { 0 .. 7 , 9 , 12 , 30 .. 31 } ); NUEVO ( objeto , {} ); FIN Ejemplo3 . Comunicación entre procesos

Las llamadas a métodos de objetos activos e inactivos compartidos actúan como un mecanismo de comunicación entre objetos activos. Dado que los objetos activos existen en un entorno de subprocesos múltiples, se proporciona un mecanismo para coordinar el acceso simultáneo a su estado. La interacción a través de mensajes se puede llevar a cabo utilizando marcos de software especiales [5] .

Protección de concurrencia

Un bloque de declaraciones marcadas con el modificador EXCLUSIVO se denomina región exclusiva . El área exclusiva en Active Oberon corresponde al concepto de área crítica de Hansen [6] . Si el ámbito exclusivo cubre todo el cuerpo del método, entonces se denomina método exclusivo (método exclusivo) y combina el concepto de Hansen con el procedimiento de monitor Hoare [7] [8] . A diferencia de un procedimiento de monitor, un método de objeto no tiene que ser exclusivo, en cuyo caso puede observar estados de objeto inconsistentes. Un ámbito exclusivo puede tener como máximo una actividad a la vez.

Por lo tanto, el modelo de seguridad en Active Oberon es un monitor colocado en un monitor basado en instancias . Un módulo se considera un tipo de objeto de instancia singleton y sus procedimientos también pueden ser exclusivos, protegiendo al módulo como un todo.

La idea principal de un monitor (y un objeto activo) es que un cierto invariante  está asociado con el monitor, una expresión que determina el estado interno constante del objeto y prueba la corrección de su comportamiento [9] . Los inicializadores y los métodos exclusivos son herramientas proporcionadas por el lenguaje para mantener las invariantes de un objeto y ocultar su estado interno. El inicializador de un objeto establece su invariante y los métodos exclusivos lo admiten. Cuando la noción de monitor se combina con la noción de módulo, se forma un poderoso mecanismo para estructurar los sistemas operativos [10] [11] [12] .

Un ejemplo del uso de la sección exclusiva:

(* Los procedimientos de configuración y reinicio son mutuamente excluyentes *) ESCRIBE MiContenedor = OBJETO VAR x , y : ENTRADA LARGA ; (* Invariante: y = f(x) *) PROCEDIMIENTO Establecer ( x : INT.LARGO ); BEGIN { EXCLUSIVO } (* cambiando x e y atómicamente *) AUTO . x := x ; y := f ( x ) FIN Establecer ; PROCEDIMIENTO Restablecer ; EMPEZAR ... BEGIN { EXCLUSIVO } (* cambiando x e y atómicamente *) x := x0 ; y := y0 ; FIN ; .... FIN Restablecer ; FIN MiContenedor ; Sincronización

A diferencia de la mayoría de las implementaciones de monitores que utilizan las variables condicionales de Hoare [8] para la sincronización (basadas en las colas de eventos de Brinch Hansen [6] ), el operador AWAIT proporciona la sincronización de actividades , que toma como argumento una expresión lógica, una condición para continuar el programa . ejecución en el cuerpo de un objeto. Para garantizar una sincronización adecuada, AWAIT debe estar en el ámbito exclusivo. Si no se cumple la condición de continuación , AWAIT suspende la actividad y, si está en un ámbito exclusivo, libera el área capturada mientras dure la suspensión, lo que permite que otras actividades cambien el estado del objeto y hagan que la condición de continuación sea verdadera. Una actividad suspendida solo continuará ejecutándose si puede volver a ingresar al ámbito exclusivo.

Un ejemplo de sincronización dentro de un búfer compartido:

ESCRIBE Sincronizador = OBJETO Estela VAR : BOOLEAN PROCEDIMIENTO Espere ; COMENZAR { EXCLUSIVO } ESPERAR ( despierto ); despierto := FALSO ; FIN Esperar ; PROCEDIMIENTO Despertar ; COMENZAR { EXCLUSIVO } despierto := VERDADERO ; FIN Despertar ; FIN Sincronizador ;

Manejo de errores y excepciones

Active Oberon carece de un manejo de excepciones estructurado: el entorno de tiempo de ejecución las maneja de forma centralizada.

La declaración ASSERT toma como argumento obligatorio una expresión lógica, en caso de violación de la cual el programa se interrumpe y, al igual que cuando se ejecuta la declaración de parada incondicional HALT , el control se transfiere al controlador de excepción centralizado. Si la condición de la declaración ASSERT se puede evaluar en tiempo de compilación, se genera un error de compilación si no se cumple la condición. Las declaraciones ASSERT y HALT pueden tener un parámetro opcional, un especificador de excepción, que el controlador puede analizar.

Una vez que se ha manejado la excepción, el control se transfiere a la siguiente sección de finalización garantizada FINALMENTE en la pila de llamadas , si existe. Luego, si el cuerpo del objeto activo está marcado con el modificador SAFE , la actividad se reiniciará; de lo contrario, la actividad se terminará.

Entorno de tiempo de ejecución

Información sobre los tipos de tiempo de ejecución

Solo los tipos estructurales (matrices, registros, objetos) tienen información de tipo de tiempo de ejecución (metainformación). La metainformación se almacena en una estructura especial llamada Descriptor de tipo . El descriptor de tipo contiene datos sobre los tipos y nombres de variables y campos, una tabla de herencia, una tabla de métodos virtuales y tablas de interfaces implementadas .

Gestión de memoria

Active Oberon utiliza la gestión automática de la memoria mediante un recolector de basura en tiempo real interrumpible (apropiable) [13] , basado en el método Mark and Sweep. El recolector de basura se ejecuta en un subproceso separado y las actividades (subprocesos) que tienen una prioridad superior a la prioridad de la actividad del recolector de basura pueden pausar su ejecución. En este caso, el árbol de objetos está congelado. Actualmente, solo las entidades en tiempo real pueden interrumpir la actividad del recolector de basura, la asignación de memoria dinámica está prohibida en ellos y el compilador monitorea esto.

La gestión de la memoria se basa en el uso de áreas de memoria tipificadas . Tal sección almacena un puntero a un descriptor de tipo . Usando la información de tipo en tiempo de ejecución que se encuentra en el descriptor de tipo, el recolector de elementos no utilizados encuentra las variables y campos del tipo de referencia y marca los bloques a los que apuntan. En otras palabras, el recolector de basura no necesita verificar cada valor similar a un puntero para ver si es un puntero válido en el montón  ; al usar la información proporcionada por el descriptor de tipo, sabe exactamente qué elementos procesar, lo que aumenta significativamente el velocidad y precisión del trabajo y reduce la carga en el proceso de recolección de basura. Para acelerar la asignación de memoria, se colocan áreas libres en las listas libres que contienen bloques de memoria de ciertos tamaños.

Las variables de tipo de referencia marcadas con el modificador UNTRACED son punteros no rastreables . Dichos punteros no son rastreados por el recolector de elementos no utilizados y las ubicaciones de memoria a las que hacen referencia se pueden reclamar en cualquier momento si fueron asignados por el entorno de tiempo de ejecución y no hay referencias accesibles a ellos que sean consideradas por el recolector de elementos no utilizados. A menudo, dichos modificadores se utilizan para tratar con la memoria asignada fuera del tiempo de ejecución de Active Oberon o con punteros inseguros.

Administrar la actividad de los objetos

El entorno de tiempo de ejecución es responsable de asignar tiempo de CPU, asegura (junto con el compilador) que no haya más de una actividad en el alcance exclusivo, asegura que las condiciones de AWAIT se verifiquen de manera oportuna y que las actividades suspendidas se reanuden. Las expresiones de condición de continuación dentro de un objeto se vuelven a evaluar en todos los puntos de salida de los ámbitos exclusivos. Esto significa que cambiar el estado de un objeto fuera del alcance exclusivo no hace que se vuelvan a calcular las condiciones. Cuando varias actividades compiten por una misma área exclusiva, entonces las actividades con condiciones cumplidas son consideradas antes que aquellas que solo quieren ingresar al área protegida [3] [14] .

Conexión e inicialización de módulos

Active Oberon carece de los medios para controlar directamente la carga y descarga dinámica de módulos. El idioma solo ofrece una sección de importación ( IMPORT ), que contiene una lista de complementos y una sección de inicialización del módulo. El tiempo de ejecución debe garantizar que los complementos estén conectados e inicializados antes de inicializar el módulo actual. El enlace dinámico de un módulo se realiza a través del mecanismo de enlace dinámico . Un módulo no se puede descargar hasta que se haga referencia a él desde la lista de conexiones de otro módulo cargado. Por lo tanto, para eliminar el problema de las referencias circulares, los módulos deben descargarse en el orden inverso al de carga.

Manejo de errores y excepciones

El módulo Traps proporciona un manejo centralizado de excepciones. Los parámetros aceptados por las declaraciones ASSERT y HALT se pueden usar para clasificar la excepción.

Después de manejar la excepción en el módulo Traps , el tiempo de ejecución busca secciones de finalización FINALMENTE garantizadas y les transfiere el control para realizar las operaciones finales.

Si la actividad está marcada con el modificador SAFE y no hay una sección FINALLY en el cuerpo del objeto , la actividad se reinicia; de lo contrario, la actividad finaliza.

La API de tiempo de ejecución brinda la capacidad de configurar su propio controlador de excepciones.

Extensiones de idioma

Ejemplos de programas

Hola Mundo!

MÓDULO Hola Mundo ; IMPORTKernelLog ; _ EMPEZAR KernelLog . String ( "¡Hola, mundo!" ); FIN Hola mundo .

Resolviendo el problema clásico de proveedor y consumidor

MÓDULO Búferes delimitados ; ESCRIBE Artículo * = OBJETO ; Búfer * = OBJETO VAR h , n : ENTERO ; B : MATRIZ * DE Ítem ; PROCEDIMIENTO Get * (): Item ; VAR x : Artículo ; COMENZAR { EXCLUSIVO } ESPERAR ( n # 0 ); (*el búfer no está vacío*) x := segundo [ h ]; h := ( h + 1 ) MOD LEN ( B ); DICIEMBRE ( n ); VOLVER x FIN Obtener ; PROCEDIMIENTO Poner * ( x : Item ); COMENZAR { EXCLUSIVO } ESPERAR ( n # LEN ( B )); (* búfer no lleno *) B [( h + n ) MOD LEN ( B )] := x ; INC ( n ) FIN Poner ; PROCEDIMIENTO & Inicial ( max : INTEGER ); BEGIN (*inicializador*) NUEVO ( B , máx .); h : = 0 norte := 0 FIN Inicial ; FIN del búfer ; END Búferes delimitados .

Enlaces

Véase también

Notas

  1. Página del proyecto C# activo Archivado el 4 de septiembre de 2011.
  2. ↑ 1 2 J. Gutknecht. ¿Los peces realmente necesitan control remoto? Una propuesta de objetos autoactivos en Oberon., 1997.
  3. ↑ 1 2 3 PJ Müller. Sistema de objetos activos. Diseño e Implementación de Multiprocesadores. — ETH Zúrich, 2002., Dis. Número ETH 14755.
  4. Florián Negele. Combinación de programación sin bloqueo con multitarea cooperativa para un sistema de tiempo de ejecución de multiprocesador portátil, ETH Zurich, 2014, Diss. ETH no. 22298
  5. Florián Negele. Marco de concurrencia A2, ETH Zúrich, 3 de junio de 2009
  6. ↑ 1 2 P. Brinch Hansen. multiprogramación estructurada. Comunicaciones de la ACM, 15(7), julio de 1972
  7. P. Brinch Hansen. principios del sistema operativo. Prentice Hall, 1973.
  8. ↑ 1 2 C.AR Hoare. Monitores: un concepto de estructuración del sistema operativo. Comunicaciones de la ACM, 17(10):549-557, octubre de 1974.
  9. DO Dahl. Monitores revisados. En AW Roscoe, editor, A Classical Mind - Ensayos en honor de CAR Hoare. Prentice Hall,
    1994.
  10. J. L. Keedy. Sobre la estructuración de sistemas operativos con monitores. ACM Operating Systems Review, 13(1), enero de 1979.
  11. N. With. Modula: un lenguaje para multiprogramación modular. Software: práctica y experiencia, 7:3-35, 1977.
  12. N. With. El uso de Modula. Software: práctica y experiencia, 7:37-65, 1977.
  13. Ulrike Glavitsch. Recolección de basura en tiempo real en A2. Instituto de Sistemas Informáticos, ETH Zurich
  14. P. Reali. Informe de idioma oberon activo. — ETH Zúrich, 2004.