(Original version)La Arquitectura de Componentes Autónomostranslated by Juan Martínez Romo, juaner@gsyc.es 13 de mayo de 2006 Este documento introduce la arquitectura de componentes autónomos (ACA). Comenzamos con nuestra motivación técnica para proponer otra arquitectura basada en componentes. A continuación veremos los detalles. Específicamente, introducimos la noción de componentes, puertos, y contratos, discutimos cómo se identifican los componentes y los puertos, cómo los componentes importan/exportan información en tiempo de ejecución, y una implementación de ACA para simulaciones, llamado J-Sim. En el documento titulado “Modelo Abstracto de Red y J-Sim,” nos centraremos en la simulación de red, y presentaremos un modelo generalizado de red de intercambio de paquetes, la plataforma de simulación entre redes(INET). Contenido
1 Motivación TécnicaEn el diseño moderno de los circuitos digitales, un sistema de hardware está ensamblado a partir de chips de circuitos integrados (IC) en una placa con un circuito impreso. Un chip con un IC es una caja negra en la que la especificación de su función y los patrones de la señal de entrada/salida se encuentran completamente especificados en su manual . Cambios en las señales de entrada accionan cierta función del IC, y cambia, después de un cierto retardo , sus salidas según la especificación del chip. El hecho de que un chip con un IC esté interconectado con otros chips/módulos/sistemas solamente a través de sus patillas (y se blinda además del resto del mundo) permite que los chips con IC sean diseñados, implementados y probados, independientemente de todo los demás. Creemos que éste es uno de los factores dominantes porqué la industria del IC es tan exitosa. Nuestra propósito de una arquitectura de componentes autónomos es una tentativa directa imitando el diseño del IC y el modelo de fabricación en términos de cómo se especifican, se diseñan, y están montados los componentes. Uno puede discutir que el paradigma de programación orientada a objetos (OO) fuera propuesto exactamente para el mismo propósito. Sí, pero esto no es bastante para alcanzar el objetivo. Echemos una ojeada cómo la función que calcula un x es implementada en el paradigma de programación OO. En el paradigma de programación procesal, el código puede parecerse a: double exp_a(double a, double x) En el paradigma de programación OO, uno puede empaquetar la función en una clase class ExtendedMath
Figura 1: Relación de las dos clases,
Como se muestra en la imagen 1, el modelo de programación OO posee
una cierta semejanza al diseño del IC y al modelo de fabricación: un
sistema de software se compone de componentes que interactúan
el uno con el otro. Como los datos se dan a la entrada, a, x, del componente La pregunta más interesante aquí es entonces: Si el comportamiento del software se puede describir exactamente igual que el hardware, ¿por qué el software no puede alcanzar tan buena modularidad como el hardware y además no sufre el fenómeno de los subsistemas de hyperspaghetti1? 1 El fenómeno de los subsistemas de hyperspaghetti se refiere a la situación en la cual los módulos en el sistema están tan firmemente acoplados que es imposible extraer algunos de los módulos del código para su reutilización o eliminar errores de un módulo del código sin afectar a otros módulos. Ver el artículo [Bruce F. Webster, "Pitfalls of Object-Oriented Development," M&T Books, New York, 1995] para más detalles. Conexión de ComponentesCreemos que la razón por la que el diseño del software no puede
alcanzar el mismo nivel de modularidad que el diseño del IC es
porque el paradigma de programación OO es fundamentalmente diferente del
diseño del hardware en la conexión de sus componentes. En el paradigma de
programación OO, una clase hace referencias directas a otras instancias de clases
y hace llamadas a las funciones expuestas por otras instancias de clase, e.g.,
Debido a las características antedichas, es difícil desarrollar y mantener un sistema de software OO con una colección grande de funciones y de clases. En el periodo de depuración, uno no puede obtener una vista clara de las relaciones de conexión sin adentrarse en los detalles de la implementación y las líneas de código línea por línea. Esto produce una imprevisión en el desarrollo del software y el alto coste de mantenimiento, y se llama generalmente como crisis del software. Separación del Contrato de Conexión de la Conexión del ComponenteLa conexión del componente es por tanto el problema. Entonces, ¿cómo está hecho en el diseño del IC? En el diseño del IC, las señales que fluyen hacia dentro y hacia fuera del chip del IC están especificadas en la definición de interfaz. Es decir, en fase de diseño, un IC está limitado a un cierto contrato (o en la jerga del diseño del IC, la especificación del IC en el libro de especificaciones), en vez de estas limitado a los componentes que interactúan con el. La conexión del componente es distinta al momento en que un sistema(e.g., ALU) esa siendo construido. Un contrato especifica cómo un iniciador (llamador) y un reactor (llamado) realiza cierta función. Especifica simplemente la causalidad del intercambio de información entre los componentes pero no los componentes que pueden participar en el intercambio de información. Dos componentes, actuando respectivamente como el iniciador y el reactor, están limitados en la fase de integración del sistema para satisfacer el contrato. Un sistema con todos los componentes limita a otro a que sea completo, si los iniciadores de todos los contratos implicados se satisfacen. En el ejemplo anterior, si Figura 2: Tres componentes (
Nosotros reclamamos que los contratos de conexión en la fase de diseño y los componentes en la fase de construcción del sistema eliminen el fenómeno del hyperspaghetti. La información necesitada para atar los contratos se define en el interfaz de un componente. De esta manera, la interconexión de componentes está bien especificada y los programadores no tienen que indagar en los detalles de la implementación para encontrar esa información. Esto evita que el fenómeno del hyperspaghetti suceda a nivel del componente y permite la composición de componentes de una manera muy similar al diseño del IC. Interfaz en RPC, CORBA o COM/COM+ es similar al contrato en nuestra discusión, pero el interfaz no es tan flexible como el contrato y la función real que ata en esos estándares no es tan directo como en una arquitectura basada en componentes. Con la discusión antedicha como motivación, presentaremos a continuación la arquitectura de componentes autónomos (ACA).
2 La Arquitectura de Componentes Autónomos(ACA)2.1 Componentes y puertosEn la arquitectura de componentes autónomos, una entidad básica es un componente. Cada componente posee uno o másendpoints, llamados puertos. El componente donde reside un puerto se llama el componente anfitrión del puerto. Dos componentes están conectados por sus puertos de una manera permanente. Cuando un componente envía datos a uno de sus puertos, el puerto retransmite los datos a los puertos que están conectados a él. Cuando los datos llegan a un puerto, el componente que posee el puerto procesa los datos inmediatamente en un nuevo contexto de ejecución (thread) y puede generar salidas en ciertos puertos según lo especificado en el contrato. La salida y la entrada de un puerto están separadas por cables. Cuando los datos se envían a un puerto, se envían por el alambre de salida del puerto y llegan a los puertos por su alambre de la entrada. La figura 3 representa posibles cableados en ACA. Particularmente en (c), el alambre azul es el alambre de salida del puerto A, C y D, y el alambre de la entrada de B, de D y del E. El alambre rojo es el alambre de salida del puerto B y E, y el alambre de la entrada de C. Por ejemplo, los datos enviados por el puerto A llegarán al puerto B, D y el E. ACA no permite el lazo de envío a uno mismo. Por ejemplo, los datos enviados en el puerto D llegarán al puerto B y E, pero no a D así mismo. Figura 3: Posible cableado entre puertos.
Un puerto se puede conectar con otro puerto de manera simplex o de manera duplex. De la manera simplex, el alambre de salida del primer puerto se ata con el alambre de la entrada del segundo puerto o decimos que los dos alambres están unidos por la conexión. De la manera simplex, el alambre de la entrada del primer puerto y el alambre de la salida del segundo puerto se unen también. La figura 4 demuestra un ejemplo del cableado duplex. Figura 4: Un ejemplo del cableado duplex. Conectando el puerto B (o E) y C en (a) resulta al unir el alambre azul marino y del alambre azul así como el alambre rojo oscuro y el alambre rojo, según las indicaciones de (b).
2.2 ContratoUn contrato especifica cómo un iniciador (llamador) y un reactor (llamado) satisface cierta tarea. Particularmente, especifica la causalidad de los datos enviados/recibidos entre los componentes pero no los componentes que participan en la comunicación. Los contratos se pueden clasificar más a fondo en dos categorías: contrato del puerto y contrato del componente. Un contrato del puerto está limitado específicamente a un puerto de un componente, mientras que un contrato de un componente describe cómo un componente responde a los datos que llegan a cada uno de sus puertos (e.g., cómo el componente procesa los datos, ciertas estructuras de datos son actualizadas, y generan salidas en ciertos puertos). 2.3 Componente compuestoACA también soporta la noción de componente compuesto. Un componente se puede componer de varios componentes y el sistema entero forma una jerarquía de componentes. Un componente compuesto es el componente padre de los componentes encapsulados, que se llaman a su vez componentes hijos. El componente compuesto permite organizar un sistema de software con una granularidad deseable. La figura 5 ilustra cómo el sistema de tres componentes mostrado en la Figura 2
se puede organizar en un componente compuesto. Observar que el
componente compuesto tiene un puerto A conectado con el puerto B del componente encapsulado
Figura 5: Encapsulación del sistema de tres componentes en la Figura 2.
2.4 Puerto del servidorUn componente puede proporcionar un servicio común en uno de sus puertos a otros componentes en el sistema. Cuando un componente envía una petición a este componente para un servicio, idealmente, el componente realiza el servicio y devuelve una contestación al componente que hizo la petición. Sin embargo, la arquitectura introducida en gran medida hace que la respuesta llegue a todos los componentes que estén conectados al mismo puerto, que no es el comportamiento deseado. Para superar este problema, ACA define un tipo especial de puerto, llamado puerto del servidor. Formalmente, un puerto del servidor está para que un componente proporcione un servicio común a otros componentes, y, específicamente, devuelva una respuesta solamente al componente que hizo la petición, sin importar que muchos componentes estén conectados con este puerto. Además, ACA especifica que el servicio esté guiado, y la respuesta se envíe, en el mismo contexto que la petición que se envió. Uno puede decir que el enviar la petición queda “bloqueado” hasta que la contestación retorna. El puerto A del componente 2.5 Cómo se identifican los componentes y los puertosCada componente y cada puerto en un sistema de software tienen que
ser identificados únicamente. Como la jerarquía de componentes en la
arquitectura de componentes autónomos es similar al sistema de ficheros en
un sistema operativo moderno, nosotros adoptamos un método de
nombramiento similar a ése en el sistema de ficheros de UNIX. Es
decir, un componente es identificado por el path formado recurrentemente concatenando la trayectoria del componente de padre, un separador “ Un sistema de software forma una jerarquía de componentes consigo mismo como raíz. El componente de la raíz tiene el path “ /prefijo/exp_a Entonces el path del componente hijo /prefijo/exp_a/exp_a Los puertos en un componente se
categorizan más a fondo en diversos grupos. Cada grupo tiene una
identificación única en un componente. Cada componente tiene un grupo de puertos
por defecto con una identificación nula. La identificación
de un puerto en un componente anfitrión es el encadenamiento de la
identificación del puerto, de un separador “ /prefijo/exp_a/A@ y el path del puerto B es /prefijo/exp_a/exp_a/B@a_x Nota que el separador “ 2.6 Exportando Información en Tiempo de EjecuciónPara los propósitos de diagnosis y configuración, un componente en ACA puede importar/exportar información en tiempo de ejecución a través de varios puertos señalados. La arquitectura define un puerto para tal uso, llamado puerto de información en un componente para exportar la información de diagnosis en tiempo de ejecución. También, un componente se puede equipar de uno o más puertos de eventos, que exportan un tipo específico de eventos en tiempo de ejecución. Describimos estos puertos y los formatos de la información exportada más adelante. Puerto de InformaciónCada componente se equipa de un puerto de información, llamado puerto de información. Cuatro tipos de información se pueden exportar espontáneamente en este puerto: mensaje de error, mensaje de basura, mensajes de depuración y mensajes de trazas. Toda la información exportada comparte un formato similar:
Los cuatro tipos de información que pueden ser exportados se enumeran abajo.
Mensaje de Evento y Puertos de EventosAdemás de la información antedicha que se puede exportar en puerto de información , un componente puede también exportar acontecimientos en los puertos señalados, llamados puertos de eventos. El formato de un mensaje de evento es:
Control de la Información ExportadaUno puede pedir específicamente a un componente, o no, exportar ciertos tipos de información. Esto es hecho enviando un flag de 6 bits en el puerto de información de un componente. El primer bit del flag es un indicador binario que especifica permitir o inhabilitar exportando los tipos especificados de información. Los bits restantes forman una máscara que especifica qué tipo de información se solicita. El siguiente diagrama da un ejemplo de habilitación de mensajes de basura y de depuración.
Propiedad del componenteCada componente puede exponer una colección de características. Una característica de un componente es definida por un par con un nombre y un valor. Uno puede preguntar todas las características de un componente, enviando una señal nula o una señal que contenga el nombre de la propiedad en el puerto de información del componente. El componente entonces contesta un mensaje con la propiedad que consiste en su valor. 3 Características de la Arquitectura de Componentes Autónomos
Figura 6: Analogía entre un chip con un IC y un componente.
4 J-SimJ-Sim es una implementación de ACA en Java. La razón de elegir Java como el lenguaje de programación es debido a muchas de sus características deseables, tales como independencia de la plataforma, la orientación a objetos pura, la sintaxis limpia del lenguaje, la incorporación de ejecución de threads, la capacidad de reflexión de Java, y la recolección automática de basura en tiempo de ejecución, que hacen la realización de ACA más fácil. El desafío más grande en el desarrollo de J-Sim es proporcionar eficientemente contextos independientes de la ejecución o threads para que los componentes manejen datos de entrada. J-Sim introduce una hebra de gestión en background llamada runtime que es la clave del funcionamiento de J-Sim. En las siguientes secciones, veremos el concepto de runtime en general para más adelante estudiar el runtime utilizado en J-Sim. La simulación es implementada en J-Sim como una extensión del runtime. Particularmente, el tiempo global de la simulación es observado por todos los threads activos en vez de que cada thread guarde un eje local del tiempo como el proceso lógico (LP) en paralelo y la simulación distribuida basada en eventos. Los detalles de cómo se hace la extensión se proporcionan en la sección de la Simulación de Tiempo Real Basada en Procesos. 4.1 Tiempo de EjecuciónPara proporcionar contextos independientes de la ejecución para los datos que llegan a diversos puertos de un componente en la arquitectura de componentes autónomos, es necesario un soporte especial en tiempo de ejecución. Particularmente, el runtime (en general) 2 tiene que crear un nuevo contexto de ejecución cuando los datos llegan al puerto de un componente. Representamos el proceso en la figura 7, y resumimos los pasos a continuación:
2Runtime es una colección de procesos en background (ocultos para las aplicaciones), como el recolector automático de basura en los lenguajes de programación modernos. Figura 7: Cómo el runtime maneja los datos de llegada.
Observa que el runtime tiene control completo para crear nuevos contextos de ejecución. Para asegurarse de que el sistema de software funcione de una manera controlada, el runtime impone un límite superior al número de contextos de la ejecución que pueden ser activos simultáneamente. Cuando se alcanza el límite superior, el runtime se retrasa para crear cualquier nuevo contexto hasta que un cierto contexto existente termine su ejecución. Esto previene un número excesivo de contextos que puede agotar recursos del sistema, o en el peor caso, finalizando el sistema inesperadamente. En J-Sim, los contextos de ejecución son implementados por
los threads de Java, con el planificador de threads en la
Máquina Virtual de Java(JVM). En la actual implementación, el tiempo de ejecución
se compone de dos clases,
Cuando los datos se envían a un puerto, el 4.2 Coste de la Comunicación Entre ComponentesSegún lo mencionado arriba, cuando los datos se entregan a un componente, el tiempo de ejecución ( Para mejorar aún más el funcionamiento, permitimos a un thread
anunciar su preparación (en tiempo de ejecución) antes del final de
su ejecución. Es bastante común que un componente envíe un cierto
resultado al final del procesado de datos. Puesto que el thread en
el componente que envía será reciclado después de que acabe el procesado
de los datos, es natural hacer que este thread continúe
sirviendo en el componente de recepción. Para poner esto en ejecución
en tiempo de ejecución, el thread en el componente emisor
debe notificar 4.3 Simulación de Tiempo Real Basada en ProcesosLa simulación es implementada como una extensión del runtime en J-Sim. Básicamente, se asegura de que el sistema esté siempre ocupado (con
Para alcanzar esto, guardamos tres variables:
El tiempo actual de la simulación se calcula entonces de la siguiente manera: current_simulation_time = (current_wall_time - last_time_updated)/time_scale + time_advances; Cuando el tiempo de la simulación avanza, se ponen al día las variables: time_advances += nearest_simulation_future_time - current_simulation_time; Con el mecanismo antedicho, una simulación funciona de manera semejante a un sistema real, en el sentido de que los eventos de ejecución son realizados en tiempo real en comparación a los puntos fijos de tiempo en simulaciones de eventos discretos (por eso es llamada simulacion de tiempo real basada en procesos). Las interacciones y las interferencias entre eventos, por lo tanto ocurren naturalmente como en sistemas verdaderos. Cuando no hay threads actualmente activos, el tiempo de ejecución realiza una operación de avance de tiempo al futuro más cercano en el cual por lo menos un thread puede ser activado. Esto preserva el comportamiento de sistemas verdaderos, y por lo tanto realza la fidelidad de la simulación, ya que siempre guarda el estado de la simulación. La variable Observar que la simulación basada en eventos de tiempo discretos es
un caso especial de la simulación de tiempo real basada en procesos con
~ FIN ~ |






