Sistemas en tiempo real y Lenguajes de Programación Objetivos del curso: • Comprender las características de los sistemas en tiempo real. • Aplicar los conceptos en el dominio industrial • Investigación Requisitos • Conocimientos de un lenguaje de programación secuencial. • Ingeniería de Software. • Introducción a los conceptos básicos del control automático. Tema 1 • Definición de los sistemas en tiempo real. • Ejemplos. Qué es un sistema en tiempo real? • Definición: “ Cualquier sistema en el que el tiempo en el que se produce la salida es significativo.” La entrada al sistema corresponde a un estímulo externo y se producirá una salida relacionada con esta entrada en un período de tiempo finito y determinado. • Control en tiempo real. El tiempo es crucial en cualquier sistema embebido. • El programador debe lograr que los programas sincronicen con el tiempo. • Especificar los tiempos en los que deben ser realizadas las acciones y completadas. Se deben responder a situaciones en las que no todos los requisitos temporales se deben satisfacer. • Responder a situaciones en las que los requisitos temporales cambian dinámicamente. Qué es un sistema en tiempo real? • La corrección de un sistema de tiempo real depende no solo del resultado lógico sino tambien del tiempo en el que se producen los resultados. • Sistemas embebidos: Cuando el commputador es un componente del proceso de información en un sistema de ingeniería más grande. Terminología • Hard real-time — Sistemas de tiempo real estrictos. En estos sistemas el tiempo de respuesta es crucial y debe producirse dentro del límite especificado. Ejemplo: Sistemas de control de vuelo. • Soft real-time — Sistemas de tiempo real no estrictos. En estos sistemas el tiempo de respuesta es importante pero el sistema seguirá funcionando correctamente aunque los tiempo límite no se cumplan ocasionalmente. Ejemplo: Sistemas de adquisición de datos. • Real real-time — Sistemas de tiempo real son sistemas en los cuales el tiempo de respuesta es muy corto. • Firm real-time — Sistemas de tiempo real firme son sistemas de tiempo real no estricto pero el tiempo límite puede no cumplirse ocasionalmente pero no hay beneficio por la entrega retrasada. Ejemplos de sistemas en tiempo real • Ejemplos: • Control de procesos • Fabricación: mantener los costes bajos y el nivel de productividad. • Comunicación, mando y control: Reserva de plazas de una compañía aérea, las funcionalidades médicas para cuidado automático del paciente, control de tráfico aéreo, contabilidad bancaria remota. • Sistema de computador embebido generalizado: siempre interacciona con el equipamiento físico en el mundo real. • Dispositivos del mundo real se debe muestrear la información a intervalos de tiempo regulares. • Reloj de tiempo real. • Consola de operador para la intervención manual. • Displays de diferentes tipos incluyendo gráficos. A simple fluid control system Interfase Tubería Lectura del Flujo de entrada Medidor de Flujo Procesado Ángulo de la válvula de salida Tiempo Ordenador Válvula Sistema de Control de Procesos Consola del operador Ordenador del cotrol Del proceso Producto final Materia prima PLANTA Un sistema de mando y control Puesto de mando Ordenador de mando y control Temperatura, Presión, Potencia, etc. Terminales Sensores/Actuatores Sistema Típico Embebido Reloj de tiempo real Algoritmos para Control Digital Registro de datos Base de Datos Recuperación y Presentación de datos Consola del operador Interfaz Del operador Interfaz Dispositivos de Presentación Sistema de Ingeniería Sistema de monitorización remota Ordenador tiempo real Características de los sistemas embebidos • Los sistemas embebidos deben responder a eventos del mundo real. • Los sistemas de tiempo real precisan mantenimiento constante y mejoras durante sus ciclos de vida. • Deben ser extensibles. • Los sistemas de tiempo real son a menudo complejos Tema 2: Características de un RTS(Sistema en tiempo real) Características de un RTS • Grande y complejo: Los programas deben cambiar continuamente. Precisan de mantenimiento constante y mejoras durante su ciclo de vida. Se pueden dividir en componentes más pequeños más fáciles de gestionar. • El software y el hardware deben ser fiables y seguros. • Control concurrente de los distintos componentes separados del sistema. Un sistema embebido suele constar de ordenadores y otros elementos externos con los que los programas deben interactuar simultámeamente. La naturaleza de estos elementos externos del mundo real suelen existir en paralelo. Cuando los elementos externos se encuentran distribuidos geográficamente se deben considerar sistemas embebidos distribuidos o multiprocesadores. Características de un RTS • Control en tiempo real: Los sistemas de tiempo real se construyen habitualmente utilizando procesadores con considerable capacidad adicional garantizando el comportamiento en el peor de los casos. El programador debe especificar los tiempos en los que deben ser realizadas las acciones, especificar los tiempos en los que las acciones deben ser completadas, responder a situaciones en las que no todos los requisitos temporales se pueden satisfacer y responder a situaciones en las que los requisitos temporales cambian dinámicamente ( modos de cambio). El programador debe sincronizar el programa con el tiempo. Caracterísricas de un RTS • Interacción con interfaces hardware. • Especificación de registro de dispositivos y control de interrupciones. • Lenguajes de alto nivel, niveles de calidad. • Control directo y evitar las arquitecturas con una capa de funciones del sistema operativo. • Implementación eficiente y entorno de ejecución. • Evaluar el coste de las características de implementación. Resumen • Clasificación de los sistemas de tiempo real: • Sistemas en tiempo real crítico • Sistemas en tiempo real acrítico • Características de un sistema de tiempo real o embebido: • Grandeza y complejidad, • Fiabilidad y seguridad extrema, • Control concurrente de componentes separados del sistema, • Control en tiempo real, • Interacción con interfaces hardware, • Implementación eficiente, Paradigmas de Programación • Un paradigma de programación representa un enfoque particular para la construcción del software. • Según las especificaciones del sistema un paradigma resulta más apropiado que otro. • Ejemplos de paradigmas: • Imperativo o por procedimientos: C • Funcional: Lisp • Lógico: PROLOG • Orientado a Objetos: Phyton, Java, C++ Imperativo o por procedimientos • Se describe la programación en términos del estado del programa y secuencias que cambian dicho estado. • Los programas son una serie de instrucciones que indican al ordenador cómo realizar una tarea. • Programación alto nivel y bajo nivel, la diferencia es el tipo de sentencias y variables. • Ejemplos: • C,C++, Java Programación dirigida por eventos • La estructura como la ejecución de los programas van determinados por los sucesos que ocurran en el sistema o que ellos mismos provoquen. • A diferencia de la programación estructurada es el usuario o lo que esté accionando el programa el que dirija el flujo del programa. • La programación orientada a eventos permite interactuar con el usuario en cualquier momento de la ejecución. Los programas creados bajo esta arquitectura se componen por un bucle exterior permanente encargado de recoger los eventos y distintos procesos que se encargan de tratarlos. Descomposición y Abstracción • Programación Estructurada: • Diseño descendente • Abstracción - Diseño Inductivo • Módulos: conjunto de objetos y operaciones relacionadas lógicamente. • La técnica de encapsulamiento sirve para aislar una función en un módulo. • La estructura Módulo permite: • Ocultación de Información • Compilación por Separado: permite la construcción de bibliotecas de componentes precompilados. Favorece la reutilización. • Tipos abstractos de datos uso de estructuras • ADA, Java – paquetes • C archivos separados la compilación (estático) • Java – clases y objetos Técnicas • En la programación estructurada se utilizan estructuras de tipo secuencial, condicional e iterativa. • La programación modular consiste en dividir un programa en módulos o subprogramas con el fin de hacerlo más legible y manejable. Es una evolución de de la programación estructurada para solucionar problemas de programación más grandes y complejos. Un problema complejo debe de ser dividido en varios subproblemas más simples, repitiendo este proceso hasta obtener subproblemas simples como para poder ser resueltos por un lenguaje de programación. Este proceso se llama análisis descendente (Top-Down). En la práctica suelen representarse los módulos como procedimientos o funciones. Si un módulo necesita de otro se debe definir una interfaz de comunicación. Lenguajes de programación RTL • Lenguajes de programación secuenciales: Soporte del sistema operativo: • RTL/2, Coral 66, Jovial, C. • Sin soporte del sistema operativo: lenguajes de alto nivel concurrentes: Ada, Chill, Modula-2, Mesa, Java. • • • • Java/Real-Time Java C and Real-Time POSIX (Laboratorios) Ada 95 Also Modula-1 for device driving Sistemas de Tiempo Real y Sistemas Operativos User Programs Operating Hardware System Configuracón de un Sistema Operativo User Program Including Operating Hardware System Components Configuración de un Sistema Embebido Diseño de sistemas de Tiempo Real Lo más importante del desarrollo de un sistema de tiempo real es la generación de un diseño consistente que satisfaga una especificación acreditada de los requisitos. Fases: – – – – – – Especificación de requisitos Diseño arquitectónico Diseño detallado Implementación Prueba Otras actividades: • Prototipado previo a la implementación final • Diseño de la interfaz hombre-máquina • Criterios para la evaluación de los lenguajes de implementación Diseño de Sistemas en tiempo real Niveles de notación: Métodos McDermid(1989) – Informal – lenguaje natural y – Estructurada – Formal Tema 2: Características de los Sistemas en tiempo real Fiabilidad y Tolerancia a Fallos 1 Fiabilidad, fallos y defectos • Para cualquier lenguaje de programación de tiempo real es requisito tener las herramientas necesarias para faciliar la construcción de sistemas altamente fiables. Por ejemplo: herramientas de gestión de excepciones. • Podemos definir la fiabilidad como: … una medida del éxito con el que el sistema se ajusta a alguna especificación definitiva de su comportamiento. Randell et al. (1978). • Cuando el comportamiento de un sistema se desvía del especificado para él, se dice que es un fallo. Randel et al. (1978) 2 Fiabilidad, fallos y defectos • Los fallos que analizamos están relacionados con el comportamiento de un sistema. Los fallos son el resultado de problemas internos no esperados que el sistema manifiesta eventualmente en su comportamiento externo. Los problemas se denominan errores y sus causas mecánicas o algorítmicas defectos. • Un componente defectuoso de un sistema es un componente que reproducirá un error bajo un conjunto concreto de circunstancias durante la vida del sistema. 3 Fiabilidad, fallos y defectos • Un fallo en un sistema puede inducir un defecto en otro, el cual puede acabar en un error y en un fallo potencial de ese sistema…puede continuar y producir un defecto en cualquier sistema relacionado, según se ilustra en le siguiente diagrama: Fallo Defecto Error Fallo 4 Fiabilidad, fallos y defectos • Tipos de Fallos: – Fallos transitorios: Comienza en un instante de tiempo concreto, se mantiene en el sistema durante algún período de tiempo y luego desaparece. Muchos de los fallos de los sitemas de comunicación son transitorios. – Fallos permanentes: comienzan en un instante determinado y permanecen en el sistema hasta que son separados, p.e.cable roto, error de diseño del software. – Fallos intermitentes: Son fallos transitorios que ocurren de vez en cuando. Un ejemplo es un componente hardware sensible al calor que funciona durante un rato deja de funcionar, se enfría y entonces empieza a funcionar de nuevo. 5 Modo de fallo Dominio del valor Error de Límites Valor erroneo Dominio del tiempo Adelantado Fallo de Silencio: a partir de un momento falla el servicio Omission: El servicio no es entregado Fallo de parada Es un fallo silencioso pero los otros sistemas detectan el fallo Arbitrario (Valor y Tiempo) Retraso:error De prestaciones Fallo controlado: Falla de una forma Controlada y especificada 6 Aproximaciones para Diseñar Sistemas Fiables • Prevención de Fallos: se refiere al intento de impedir que cualquier posibilidad de fallo se cuele en el sistema antes de que esté operativo. • Tolerancia a Fallos: hace posible que el sistema continúe funcionando incluso ante la presencia de fallos. Ambas intentan producir sistemas con modos de fallo bien definidos 7 Prevención de Fallos • Fases en la prevención de fallos: evitación y eliminación – Fase Evitación se intenta limitar la introducción de componentes potencialmente defectuosos durante la construcción del sistema. • Hardware: – Componentes más fiables dentro de las restricciones de coste y prestaciones dadas. – Técnicas exhaustivamente refinadas para la interconexión de componentes y el ensamblado de subsistemas. – Aislamiento para protegerlo de formas de interferencia esperadas. 8 Prevención de Fallos – Fase Evitación Cont. • Software: – Especificación de requisitos rigurosas, si no formales. – Metodología de diseño. – Lenguajes que faciliten la abstracción de datos y la modularidad. – Herramientas de ingeniería de software para ayudar en la manipulación de los componentes de software y en la gestión de complejidad. 9 Prevención de Fallos • Fase Eliminación de Fallos: – Procedimientos para encontrar y eliminar las causas de los errores. • Revisores de diseño. • Verificación de programas • Inspecciones del código. • Pruebas del sistema. 10 Prevención de Fallos Nota: Como los componentes de HW pueden fallar la prevención de fallos puede ser inapropiada cuando la frecuencia o la duración de los tiempos de reparación resulten inaceptables o cuando no se pueda acceder al sistema para actividades de mantenimiento y reparación. 11 Tolerancia a fallos. • Niveles de tolerancia a fallos de un sistema: – Tolerancia total frente a fallos: el sistema continua en funcionamiento en presencia de fallos, aunque por un período limitado, sin una pérdida significativa de funcionalidad o prestaciones. (Sistemas de seguridad) – Degradación controlada( o caída suave) el sistema continua en operación. Si los sistemas son muy complejos y deben funcionar de forma continua con requisitos de alta disponibilidad. – Fallo seguro: el sistema cuida de su integridad durante el fallo acaptando una parada temporal de su funcionamiento. 12 Degradación controlada y recuperación en el sistema de control del tráfico aéreo. Funcionalidad completa dentro del tiempo de respuesta especificado Funcionalidad mínima Precisa para mantener el control aéreo básico Funcionalidad de emergencia sólo para dar separación Entre aviones. Mecanismo de respaldo adyacente: cuando ocurre un fallo catastrófico ( terremoto) 13 Redundancia • Todas las técnicas utilizadas para conseguir tolerancia a fallos se basan en añadir elementos extra al sistema para que detecte y se recupere de los fallos, estos componentes no son necesarios para el normal funcionamiento del sistema (Redundancia protectora) • Tipos: Hw y SW 14 Redundancia HW • Estática o Enmascarada: – TMR-Triple Modular Redundancy consiste en tres componentes o idénticos y en circuitos de votación por mayoría. Los circuitos comparan la salida de todos los componentes, y si alguna difiere de las otras dos es bloqueada. En este caso se supone que el fallo se debe a un error transitorio o al deterioro de algún componente. – NMR- redundancia N modular • Dinámica: es la redundancia aportada dentro de un componente que hace que el mismo indique que la salida es errónea. ( dotar de detección de errores: la suma de comprobación en las transmisiones de comunicaciones y las paridades de bits en las memorias 15 Software- Tolerancia de fallos (causa-errores de diseño) • Dinámica: – Detección y recuperación de errores: similar al enfoque de redundancia dinámica de HW, en el sentido de que se activan los procedimientos de recuperación después de haberse detectado el error. • Estática - Programación de N-versiones: similar al de redundancia enmascarada del HW 16 Programación de N-versiones Versión 1 Versión 2 estatus Versión 3 estatus estatus voto vote voto Director 17 Programación de N-versiones Aspectos principales T1 T2 > Tth T3 no > Tth si > Tth Cada versión produce un resultado diferente pero correcto(comparación consistente) si P1 P2 P3 no > Pth > Pth > Pth si 18 V1 V2 V3 Programación de N-versiones • Aspectos principales: – Especificación Inicial Un error en la especificación permanecerá en las Nversiones. – Independencia en el diseño: Un sistema de tres versiones es más fiable que un sistema de alta calidad de versión única. – Presupuesto adecuado: con la mayoría de los sistemas embebidos el principal coste se debe al software. 19 Redundancia de software dinámica • Detección de errores • Confinamiento y valoración de daños. – Construcción de cortafuegos • Recuperación del error – Diagnosis del error. – Existe un intervalo de tiempo entre el fallo y la manifestación del error asociado. – Estrategias: recuperación hacia delante (excepción asíncrona) y hacia atrás (bloques de recuperación). • Tratamiento del fallo y continuación del servicio. 20 Redundancia de software dinámica • Técnicas de detección de errores – En el entorno – HW ( violación de protección, desbordamiento aritmético) SW (valor fuera de rango, referencua a apuntador nulo) – En la aplicación • • • • • • • Comprobación de réplicas Comprobaciones temporales Comprobaciones inversas Una entrada y una salida. Códigos de comprobación checksum, corrupción de datos Comprobaciones de racionalidad (aserciones) Comprobaciones estructurales Comprobaciones de racionalidad dinámica 21 Mecanismo del bloque de recuperación Restaura punto de recuperación fallo Establece punto de recuperación Alguna alternativa Si Ejecuta siguiente alternativa Pasa Evalúa test De aceptación Descarta punto de recuperaciónint No Falla bloque De recuperación 22 Redundancia dinámica y excepciones • Una excepción es la ocurrencia de un error. • Generar una excepción es mostrar la condición de excepción que ha causado dicha excepción. • La respuesta es la captura de la excepción. 23 Manejo de excepciones • Representación de excepciones: de forma explícita o implícita. • Dominio de un manejador de excepciones: con cada manejador viene asociado un dominio que significa la región cómputo durante la cual se activará el manipulador si aparece una excepción. El dominio se encuentra asociado a un bloque, un subprograma o una sentencia. 24 Ejemplos • C if(llamada_funcion(parametros) == Un_Error) { /* código para el manejo de errores */ } else { /*código para el retorno normal*/ }; 25 Ejemplos • C++ - definición del dominio try { // sentencias que pueden generar una //excepción } catch (ExceptionType e) { // manejador para e e } 26 Manejo de excepciones • Propagación de excepciones: Cuando se lance una excepción, puede que no exista ningún manejador de excepciones en el dominio que la encierra. En este caso podrá propagarse la excepción hacia el siguiente nivel de dominio que lo envuelve, o podrá considerarse como un error de programación. 27 Modelo de Reanudación Hq P invoca Q Hr genera la excepción q P Hq reanuda Hr 4 5 1 Hr Q invoca R R genera la excepción r Q Hq reanuda R 2 6 3 R 28 Modelo deTerminación Procedimiento P 1 P invoca Q Procedimiento Q 3 Q invoca R Procedimiento R 5 2 4 Generada la excepción r 8 Manejador visto 6 Termina el procedimiento 7 Manejador para r 29 Manejo de excepciones • El modelo de reanudación o de terminación determina la acción a tomar tras el manejo de una excepción. Con el modelo de reanudación el invocador de la excepción se reanuda en la sentencia posterior a la que provocó la excepción. Con el modelo de terminación el bloque o procedimiento que contiene el manejador es terminado, y se pasa el control al bloque o procedimiento que lo llamó. 30 Tema 3: Concurrencia Programación Concurrente Teoría y ejemplos de implementación 1 Programación Concurrente • Denominamos programación concurrente a la notación y técnicas de programación que expresan el paralelismo potencial y que resuelven los problemas resultantes de la sincronización y la comunicación. La implementación del parlelismo es un tema de los sistemas informáticos (SW y HW) La importancia de la programación concurrente está en que proporciona un entorno abstracto donde estudiar el paralelismo sin tener que enfrescarse en los detalles de implementación. (Ben-Ari, 1982) 2 Programación Concurrente • Los sistemas operativos proporcionan mecanismos para crear procesos concurrentes. Cada proceso se ejecuta en su propia máquina virtual, para evitar interferencias con otros procesos no relacionados. • Los sistemas operativos modernos permiten crear procesos dentro del mismo programa accediendo de modo compartido, y sin restricciones, a la memoria común ( estos procesos suelen llamarse hilos o hebras). 3 Multiprogramación • Los procesos son ejecutados, concurrentemente, compartiendo uno o más procesadores (c/u con su memoria) • El kernel del sistema operativo multiplexa los procesos en los procesadores Proc7 Proc1 Proc2 M1 CPU1 Proc6 M2 CPU2 Proc5 Proc3 Proc4 4 Multiprocesamiento • Se ejecuta un proceso en cada procesador • Los procesos comparten una memoria en común Proc1 CPU1 Proc2 Proc3 Proc4 CPU2 CPU3 CPU4 Memoria común 5 Procesamiento distribuido • Los procesos están conectados a través de una red de comunicación • Cada procesos cuenta con su memoria Proc1 Proc2 Proc3 Proc4 CPU1 CPU2 CPU3 CPU4 M1 M2 M3 M4 red de comunicación 6 Diagrama de estados sencillo para un proceso NOexistente existente NO Noexistente existente No Creado Creado Inicialización Inicialización Terminado Terminado Ejecutable Ejecutable 7 Programación concurrente • Los sistemas operativos que se ajustan a POSIX se debe distinguir entre concurrencia de programas (procesos) y la concurrencia dentro de un programa (hilos). 8 Programación concurrente • Servicios fundamentales: – La expresión de ejecución concurrente mediante la noción de proceso. – La sincronización de procesos. – La comunicación entre procesos. 9 Programación concurrente • Interacción entre procesos: – Independiente: no se comunican o sincronizan entre sí. – Cooperativo: Se comunican con regularidad y sincronizan sus actividades para realizar alguna operación común. – Competitivo: Para que los procesos obtengan una proporción justa de recursos deben competir entre sí. 10 Hilos • Hilos: permiten múltiples flujos de control que se ejecutan de manera concurrente dentro de uno de sus programas. Los hilos permiten que su programa emprenda varias tareas de cómputo al mismo tiempo, una característica que da soporte al programa orientado a eventos. 11 Hilos y Programación Concurrente • Un ordenador tiene un CPU o elemento de procesamiento (PE Processing Element) que ejecuta un programa a la vez. • Un programa es un proceso que se ejecuta una sola vez. • Dentro de un proceso el control suele seguir a un solo hilo de ejecución que por lo general empieza con el primer elemento de main, recorriendo una secuencia de instrucciones y terminando cuando se regresa a main. Programación de un solo hilo. 12 Hilos y Programación Concurrente • Los lenguajes de programación como el C tienen un único hilo (hebra) de control. Existe una única traza. • El término concurrente indica paralelismo potencial. • C/C++ POSIX permite múltiples tareas y programación concurrente. 13 Hilos y Programación Concurrente • Los programas de un solo hilo son buenos para cálculos sencillos. • Los programas dinámicos, interactivos, controlados por eventos, suelen incluir varias partes activas que se ejecutan de manera independiente e interactúan o cooperan de alguna manera para alcanzar las metas. 14 Hilos y Programación Concurrente • Se pueden utilizar hilos para desacoplar actividades con velocidades de procesamiento muy diferentes. • Un programa de procesamiento multihilos tiene que coordinar varias actividades independientes y evitar la posibilidad de que se encimen entre sí. 15 Programa de Varios Hilos Main() start() run() run() run() 16 Pthreads – hilos en C/C++ • Un hilo es un flujo de control separado que ejecuta su propio código. En C/C++ el hilo es un objeto de la clase Pthread. 17 Pthreads – hilos en C/C++ • Lanzando Hilos. Un hilo en ejecución puede lanzar o crear otros hilos creando objetos tipo hilo e invocando sus métodos start para que empiecen a ejecutarse independientemente. 18 Pthreads – hilos en C/C++ • Control de hilo. Un hilo padre da lugar a un hilo hijo al crear un objeto de Thread e invocar a su método start, el cual causa que el objeto se vuelva un nuevo hilo listo para ejecutarse. 19 Hilos y Programación Concurrente • Estos programas incluyen cuatro aspectos nuevos e importantes que no incluyen los programas de un solo hilo: – – – – Exclusión mutua Sincronización Calendarización Punto muerto 20 Hilos y Programación Concurrente Exclusión mutua • Los hilos de un programa suelen necesitar ayuda para lograr de una cierta tares. Esta cooperación generalmente implica el acceso de diferentes hilos a las mismas construcciones del programa. • Cuando varios hilos comparten un recurso común (campo, arreglo u otro objeto) puede darse el acceso simultáneo de más de un hilo. • Es necesario organizar un acceso mutuamente exclusivo a a cantidades compartidas. Solo un hilo puede accesar al mismo tiempo la cantidad protegida por exclusión mutua (mutex). 21 Tema 4: Tareas Exclusión Mutua Mutex 1 Guía • • • Interacción y sincronización procesos Corutinas, fork-join y cobegin-coend Exclusión mutua, sección crítica y condición de competencia • Soluciones problema exclusión mutua 2 Características procesos concurrentes • Los procesos son concurrentes si existen simultáneamente • Pueden funcionar en forma totalmente independiente, unos de otros • Pueden ser asíncronos lo cual significa que en ocasiones requieren cierta sincronización y cooperación 3 Interacción entre procesos • Para poder cooperar, procesos concurrentes deben comunicarse y sincronizarse • Comunicación permite ejecución de un proceso para influenciar ejecución de otro • Comunicación entre procesos esta basada en el uso de variables compartidas o envío de mensajes 4 Representación de procesos • La estructura del programa permite localizar los segmentos que pueden ejecutarse concurrentemente • Mecanismos básicos para representar la ejecución concurrente: • corutinas • fork-join • el enunciado co-begin • Pueden usarse para especificar un número estático o dinámico de procesos 5 Corutinas • Propuestas por Conway en 1963 • Corutinas son subrutinas que permiten una transferencia de control de una forma simétrica más que jerárquica • Cada corutina puede ser vista como la implementación de un proceso • Bien usadas, son un medio para organizar programas concurrentes que comparten un mismo procesador 6 Elementos corutinas • El enunciado resume – transfiere control a la corutina mencionada – guarda información necesaria para controlar la ejecución de regreso • El enunciado call – inicializa el cálculo de la corutina • El enunciado return – transfiere el control de regreso al procedimiento 7 que realizó un call Comentarios corutinas • La ejecución, por parte de un proceso, de resume provoca una sincronización • No son adecuadas para un verdadero procesamiento paralelo • Son procesos concurrentes en el que el “switcheo de procesos” ha sido completamente especificado y no dejado al kernel o a la implementación • Lenguajes: SIMULA I y SL5 8 Enunciados fork-join • Enunciado fork especifica que una rutina puede empezar su ejecución • La rutina invocada y la rutina invocadora proceden concurrentemente • Para sincronizar invocada e invocadora, esta última puede ejecutar un join • Enunciado join retrasa ejecución rutina invocadora hasta que la rutina invocada termine 9 Comentarios fork-join • Enunciados fork-join puede aparecer en condicionales y ciclos – es necesario entender bien la ejecución del programa, para saber que rutinas se van a ejecutar concurrentemente • Cuando se usa de forma disciplinada los enunciados son prácticos y poderosos – fork proporciona un mecanismo para la creación dinámica de procesos – enunciados similares también están incluidos en PL/I y Mesa 10 El enunciado cobegin • Es una forma estructurada de denotar una ejecución concurrente • Por ejemplo: cobegin S1 || S2 || … || Sn coend – denota una ejecución concurrente de S1, S2, … Sn – cada uno de los Si’s puede ser cualquier enunciado incluyendo un cobegin o un bloque con declaraciones locales 11 Características cobegin • La ejecución de un cobegin solo termina cuando la ejecución de todos los Si’s terminó • No es tan potente como fork-join, pero es suficiente para especificar la mayor parte de los cálculos concurrentes • Sintaxis hace explícito cuales rutinas son ejecutadas concurrentemente • Variantes implementadas en ALGOL68, CSP, Edison y Argus 12 Ejecución concurrente: Tareas/Procesos • Declaración explícita de los procesos: task body Process is begin . . . end; Ejemplo: ADA 13 Ejecución Concurrente Posix • Mecanismos: fork y pthreads. • fork crea un nuevo proceso • pthreads son una extensión POSIX y permite que un hilo/hebra sean creados. • Todos los hilos/hebras tienen atributos que permiten su manipulación. 14 Ejemplo 15 Brazo del Robot en POSIX const int WORK=1000000; const int MAXREGS=50; pthread_attr_t attributes; pthread_t xp, yp, zp; time_t inicio, tiempo; int new_setting(int *D); void move_arm(int *D, int P); void *controller(void * dim); 16 int main() { int X, Y, Z, rc; void *result; printf("Programa ejecutandose...Para finalizar, cierre la ventana. \n"); inicio=time(NULL); // cogemos tiempo de referencia X = 1, Y = 2; Z = 3; /* se establecen los atributos por defecto */ rc=pthread_attr_init(&attributes); checkResults("pthread_attr_init()", rc); rc=pthread_create(&xp, &attributes, controller, (void *)&X); checkResults("pthread_create(X)", rc); rc=pthread_create(&yp, &attributes, controller, (void *)&Y); checkResults("pthread_create(Y)", rc); rc=pthread_create(&zp, &attributes, controller, (void *)&Z); checkResults("pthread_create(Z)", rc); pthread_join(xp, (void **)&result); /* Necesario para bloquear el programa principal La rutina pthread_join espera a que finalizen las hebras, con lo que se queda en espera pues las hebras no finalizan */ exit(EXIT_FAILURE); /* El programa no debería terminar con lo que si finaliza se devuelve un error */ } 17 void *controller(void * dim) { int position, setting, j; double result; int *eje; eje=(int*)dim; position = 0; while (1) { /* Bucle para que la CPU este trabajando y las hebras se puedan ir alternando */ result=0; for (j=0; j < WORK; j++) result = result + j/2; move_arm(eje, position); setting = new_setting(eje); position = position + setting; } /* Notar que la hebra no finaliza */ } 18 /* Rutina de simulacion del movimiento del brazo. Se graban mensajes en un fichero para cada eje de movimiento */ void move_arm(int *D, int P) { FILE * pFile; char c[50]; long t; if (P<MAXREGS ) // ponemos condición para grabar en fichero sino se grabaría indefinidamente { if (P==0) // creamos los ficheros al inicio del movimiento { if (*D==1) pFile = fopen ("ejex.txt","w"); if (*D==2) pFile = fopen ("ejey.txt","w"); if (*D==3) pFile = fopen ("ejez.txt","w"); } else { if (*D==1) pFile = fopen ("ejex.txt","a+"); if (*D==2) pFile = fopen ("ejey.txt","a+"); if (*D==3) pFile = fopen ("ejez.txt","a+"); } tiempo=time(NULL); t=tiempo-inicio; sprintf(c, "[clock: %d seg.] Valor de posicion P=%d \n",t,P); fputs (c,pFile); fclose (pFile); } } 19 A implementar new_setting 20 Comunicación y Sincronización • El comportamiento correcto de un programa concurrente depende estrechamente de la sincronización y la comunicación entre procesos. • Sincronizar es satisfacer las restricciones en el entrelazado de las acciones de diferentes procesos. Una acción particular de un proceso sólo ocurre después de una acción específica de otro proceso. • La sincronización debe llevar simultáneamente a dos procesos a estados predefinidos. • Comunicar es pasar información de un proceso a otro. Los dos conceptos están ligados, puesto que algunas formas de comunicación requieren sincronización y la sincronización puede ser considerada como comunicación sin contenido. 21 La exclusión mutua • Garantizar que si un proceso utiliza una variable o algún recurso compartido, los demás no podrán usarlos al mismo tiempo • Conocida como mutex • Cada proceso debe verificar que durante cierta parte del tiempo, puede tener acceso a la memoria compartida de archivos o realizando labores críticas que pueden llevar a conflictos 22 Exclusión Mutua • La comunicación entre procesos se basa normalmente o en el uso de variables compartidas o en el paso de mensajes. • Aunque las variables compartidas parecen una forma directa de pasar información entre procesos su uso debe ser restringido. Debido a los problemas de actualización. 23 Exclusión mutua • Una secuencia de sentencias que debe aparecer como ejecutada indivisiblemente se denomina sección crítica. • La sincronización que se precisa para proteger una sección crítica se conoce como exclusión mutua. 24 Sección crítica • Parte del código en el cual se tiene acceso a una variable o archivo compartido • Corolario: – si dos procesos no están al mismo tiempo en sección crítica, podemos evitar las condiciones de competencia 25 Asignación recursos • Sean n procesos que entran en conflicto por el acceso a un recurso único no compartible • Recurso a usar en sección crítica • Necesario usar un protocolo formado de tres partes: protocolo de adquisición <uso del recurso en sección crítica> protocolo de liberación 26 Gestión de Mutex 27 Gestión de Mutex Introducción • • • Mutex es una abreviación de "mutual exclusion". Las rutinas Mutex ofrecen una de las principales herramientas para implementar sistemas de sincronización de hebras y de protección de variables compartidas cuando se accede en modo escritura desde varios hilos de forma simultánea. Cuando hablamos de mutex nos referimos en realidad a la variable asociada al mutex o “mutex variable” El mutex actúa como una llave, protegiendo el acceso a recursos compartidos. El concepto básico subyacente a las rutinas mutex usadas en Pthread es que únicamente 1 hebra puede tener el control o bloquear una variable mutex en un instante del tiempo, es decir, si varias hebras intentan simultáneamente tomar el control de la variable, únicamente una de ellas lo conseguirá, poniéndose las demás hebras en ‘cola de espera’ hasta que el mutex sea desbloqueado por la hebra propietaria. Los mutex se pueden utilizar para evitar situaciones inconsistentes por ejemplo en una situación en que se accede simultaneamente a una variable que acumula el saldo de una cuenta podríamos encontrarnos con la siguiente situación: Tiempo T1 Hebra 1 Saldo leído: 8000 T2 T3 T6 • • Saldo 8000 Saldo leído: 8000 Ingresa: 200 T4 T5 Hebra 2 8000 8000 Ingresa: 200 Actualiza Saldo: 8000+200 8000 8200 Actualiza Saldo: 8000+200 8200!! En este ejemplo, la hebra que actualiza el saldo debe bloquear el mutex antes de la lectura del saldo y desbloquearlo una vez actualizado su valor. A menudo las acciones que efectúa una hebra propietaria de un mutex es actualizar variables globales. Esta es la manera de garantizar que el resultado de la actualización será el mismo que se hubiera producido si solamente tuviéramos una hebra en ejecución. La secuencia típica de instrucciones de un mutex es la siguiente: o Inicializar y crear el mutex o Varias hebras intentan bloquear el mutex o Unicamente una de ellas lo consigue, pasando a ser la hebra propietaria o La hebra propietaria efectúa una serie de acciones o La hebra propietaria desbloquea el mutex o Otra hebra toma el control del mutex y se repite la secuencia o Finalmente se destruye el mutex. Creación y destrucción de Mutex • POSIX define las siguientes rutinas para la inicialización y destrucción de MUTEX: int pthread_mutex_init(pthread_mutex_t *mutex, const thread_mutexattr_t *attr); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_mutex_destroy(pthread_mutex_t *mutex); int pthread_mutexattr_init( pthread_mutexattr_t *attr ); int pthread_mutexattr_destroy( pthread_mutexattr_t *attr ); Pthread_mutex_init: • Las varibales Mutex deben declarase con el tipo pthread_mutex_t, y deben inicializarse antes de poder trabajar con ellas. Cuando se crea, la variable mutex está en estado ‘desbloqueada’. Hay 2 maneras de inicializar una variable Mutex: 1. Estáticamente en el momento en que se declara la variable, mediante la macro PTHREAD_MUTEX_INITIALIZER. En este caso se inicializa con los valores por defecto: pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER; 2. Dinámicamente con la rutina pthread_mutex_init() routine. Este • método permite especificar los atributos del mutex, attr. La variable attr apunta al objeto que se utiliza para establecer las propiedades del mutex y debe ser del tipo pthread_mutexattr_t. Si se deja a NULL, el mutex se inicializa con las propiedades por defecto con lo que el resultado es equivalente a inicializarlo estáticamente. Pthread_mutex_destroy: • La rutina pthread_mutex_destroy() debe usarse para destruir un mutex no bloqueado que no deba volver a utilizarse en el programa. Pthread_mutexattr_init/destroy: • • • Las rutinas pthread_mutexattr_init() y pthread_mutexattr_destroy() se usan para crear y destruir respectivamente los objetos ‘atributo de mutex’ pthread_mutexattr_init() inicializa el objeto apuntado por attr con los valores por defecto. Estos atributos actuan como parámetros adicionales que se pasan en la creación del mutex. Cuando un mutex es inicializado con sus atributos, dichos atributos se copian dentro de mutex por lo que, una vez inicializado el mutex, los atributos ya no son necesarios para el resto del programa y se pueden destruir. Si se desean crear los mutex con los atributos por defecto, no es necesario inicializar explícitamente con pthread_mutexattr_init(). Bloqueo y desbloqueo de Mutex • POSIX define las siguientes rutinas para la bloqueo y desbloqueo de MUTEX: int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); Pthread_mutex_lock: • La rutina pthread_mutex_lock() se utiliza para que una hebra solicite la propiedad del mutex especificado por la variable mutex. Si el mutex esta bloqueado en ese instante por otra hebra, la llamada a la rutina bloqueará la ejecución de la hebra solicitante hasta que el mutex se desbloquee por la hebra propietaria. Pthread_mutex_trylock: • pthread_mutex_trylock() intenta bloquear el mutex especificado por la variable mutex. Sin embargo, si el mutex está bloqueado, la rutina retorna de forma inmediata a la hebra solicitante (sin esperar a su desbloqueo) pero con un código de retorno que indica que el mutex está bloqueado. Esta rutina puede ser útil para evitar situaciones de “deadlock” en que conditions, as in a priorityinversion situation. Pthread_mutex_unlock: • • • • • • • pthread_mutex_unlock() desbloquea el mutex si es llamado por la hebra que lo ha bloqueado (hebra propietaria del mutex). Se debe llamar a esta rutina una vez la hebra ha completado las instrucciones que debían ser protegidas de accesos concurrentes. Se produce un error si el mutex ya está desbloqueado o bien la hebra no es propietaria del mutex. Cuando varias hebras estan esperando el desbloqueo de un mutex para adquirirlo como propietarias, a menos que se haya aplicado algún mecanismo de planificación (priorización), la asignación del mutex a una u otra hebra se delega al sistema operativo, el cual, lo hará de forma más o menos aleatoria. Es responsabilidad del programador hacer buen uso de la utilización del mutex. En el siguiente ejemplo (en el cual hay una hebra para la cual no se ha especificado mutex) se ha producido un error de lógica de programación ya que el resultado final será aleatorio: Hebra 1(*) Lock A = 2 Unlock Hebra 2(*) Hebra 3(***) Lock A = A+1 A = A*B Unlock (*) prioridad alta, (**) prioridad media, (***) prioridad baja Ejemplo: Uso de Mutex Programa de ejemplo - Mutex En este programa se muestra la utilización de mutex para garantizar la exclusividad en la actualización de variables globales. Se trata de un ejercicio a efectos meramente didácticos. Se toma el valor de la variable 'saldo' al inicio del bucle que simula que la CPU está trabajando y se incrementa el saldo con el valor 'ingreso' Deberemos observar el comportamiento del programa con mutex y sin mutex.. #include #include #include #include #include <pthread.h> <windows.h> <stdio.h> <stdlib.h> "check.h" int const NUMTHRDS=4; int const INGRESO=1; int const WORK=1000; int sw_mutex; no // variable para indicar al programa si utiliza mutex o /* Definimos variables globales y variables mutex */ int num_thrd[NUMTHRDS]; double Saldo; pthread_t callThd[NUMTHRDS]; pthread_mutex_t mutexsum; void *acumsaldo(void *arg); /* El programa principal crea las hebras que actualizan el saldo. Antes de crear las hebras se inicializa la variable saldo. Para evitar que las hebras actualizen simultaneamente la variable debe usarse mutex. El programa principal debe esperar a que acaben todas las hebras antes de presentar el resultado final. */ int main (int argc, char *argv[]) { int i, rc; void *status; pthread_attr_t attr; sw_mutex=-1; while (sw_mutex<0 || sw_mutex>1) { cout<<"Con mutex, entre 1, sin mutex, entre 0: "; cin>>sw_mutex; } if (sw_mutex==0) cout<<endl<<"Sin mutex"<<endl<<endl; else cout<<endl<<"Con mutex"<<endl<<endl; Saldo=0; rc=pthread_mutex_init(&mutexsum, NULL); checkResults("pthread_mutex_init()", rc); rc=pthread_attr_init(&attr); checkResults("pthread_attr_init()", rc); // creamos la hebra en modo "joinable" a efectos de portabilidad rc=pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); checkResults("pthread_attr_setdetachstate()", rc); for(i=0;i<NUMTHRDS;i++) { // asignamos valores a la variable pasada por parametro. Identificará el mutex num_thrd[i]=i+1; // creamos la 4 hebras rc=pthread_create( &callThd[i], &attr, acumsaldo, (void *)&num_thrd[i]); checkResults("pthread_create()", rc); } rc=pthread_attr_destroy(&attr); checkResults("pthread_attr_destroy()", rc); /* Esperamos la finalización de las 4 hebras */ for(i=0;i<NUMTHRDS;i++) { rc=pthread_join( callThd[i], &status); checkResults("pthread_join()", rc); } /* Despues del join, presentamos el resultado */ printf ("Saldo = %f \n", Saldo); pthread_mutex_destroy(&mutexsum); system("pause"); pthread_exit(NULL); } void *acumsaldo(void *arg) { int i,j, *hebra; double saldo,k; hebra = (int *) arg; // Bloqueo del mutex antes de actualizar la variable. if (sw_mutex==1) pthread_mutex_lock (&mutexsum); saldo=Saldo; printf ("Saldo inicio hebra %d %f \n",*hebra, saldo); for (i=1; i<=WORK ; i++) { saldo += INGRESO; } // Sleep(1); // para simular consumo CPU for (i=1; i<=2*WORK ; i++) for(j=1; j<=WORK ; j++) k=i/j; Saldo += saldo; printf ("Saldo final hebra %d %f \n",*hebra, Saldo); // Desbloqueo del mutex antes de actualizar la variable. if (sw_mutex==1) pthread_mutex_unlock (&mutexsum); pthread_exit((void*) 0); }