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] .
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).
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 *)El lenguaje ofrece un rico conjunto de tipos integrados:
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 activosUn 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 procesosLas 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 concurrenciaUn 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ónA 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 ;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á.
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 .
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.
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] .
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.
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.
Lenguajes de programación | |
---|---|
|