Programación de aplicaciones en T.R. TEMA 2 Lenguajes para aplicaciones en tiempo real Programación a pequeña escala Programación a gran escala Programación concurrente Sistemas operativos Programación de aplicaciones en tiempo real 1 2 Proyecto. Conjunto de tareas y actividades, limitadas en el tiempo, encaminadas a alcanzar un objetivo bien definido, en un plazo determinado y con unos recursos dados (humanos, materiales, presupuestarios, etc.). Etapas en la gestión de proyectos: Lenguajes para aplicaciones en tiempo real Iniciación del proyecto: objetivos y plazo de ejecución. Calificación del proyecto: evaluación global Estimación de cargas y coste Estimación de riesgos Aceptación o rechazo de emprender el proyecto Desarrollo del proyecto: realización y control del desarrollo Planificación y descomposición en etapas y tareas Lanzamiento del proyecto: ejecución y desarrollo Seguimiento y control del proyecto. Cierre de etapas 3 4 Cierre del proyecto. Proyectos informáticos. La aplicación de los métodos de ingeniería a los proyectos informáticos ha dado lugar a un conjunto de técnicas conocidas como Ingeniería del Software: Aplicación práctica del conocimiento científico al diseño y construcción de programas, y la documentación necesaria para el desarrollo, operación y mantenimiento de los mismos. Etapas en la gestión de proyectos informáticos: ANÁLISIS EL CICLO DE VIDA CODIFICACIÓN MODELOS DE CICLO DE VIDA: Definición: especificaciones y requisitos. Diseño: preliminar, detallado y técnico. Desarrollo: codificaciones y pruebas. Integración: implantación, operación y mantenimiento. CASCADA PROTOTIPOS VERSIONES SUCESIVAS 5 TRANSFORMACIONES EN ESPIRAL 6 1 SISTEMA REQUISITOS DEFINICIÓN SOFTWARE DISEÑO PRELIMINAR DISEÑO DE GESTIÓN ESPECIFICACIÓN ANALISIS DISEÑO DETALLADO DISEÑO DE DATOS DISEÑO DISEÑO PROGRAMAS DISEÑO ARQUITECTÓNICO DISEÑO TÉCNICO DISEÑO DE PROCEDIMIENTOS CODIFICACION DESARROLLO DISEÑO DE INTERFACES PRUEBAS INTEGRACIÓN IMPLANTACIÓN OPERACIÓN 7 8 Lenguajes para aplicaciones T.R. La elección de un lenguaje tiene implicaciones en la seguridad, fiabilidad y coste del sistema final. Coste o esfuerzo Coste total del sw Coste de las interfaces Coste por módulo Región de coste mínimo nº de módulos Requisitos de un buen lenguaje para desarrollo de aplicaciones en tiempo real Características que ayudan a la construcción de software seguro y fiable. Técnicas de programación modular. Facilidades para soportar concurrencia. 9 Lenguajes para aplicaciones T.R. 10 Lenguajes para aplicaciones T.R. Los sistemas de tiempo real frecuentemente son sistemas grandes y complejos, lo cual implica que su desarrollo y mantenimiento sea costoso. Además, estos sistemas tienen que responder a eventos externos garantizando un mínimo de tiempo de respuesta, utilizando un amplio rango de dispositivos de interfaz, incluyendo dispositivos que no son estándar. En muchas aplicaciones, la eficiencia en la utilización del hardware del computador es vital para conseguir la velocidad de operación necesaria. Para conseguir una utilización más eficiente de la CPU y acceso a los dispositivos de interfaz e interrupciones, los primeros sistemas de tiempo real fueron programados utilizando lenguajes a nivel de ensamblador. Estos aún se sigue utilizando, sobre todo para sistemas pequeños con requisitos de velocidad de cálculo altos. 11 12 Pero la programación en ensamblador no es nada atractiva, de forma que la insatisfacción con los lenguajes ensambladores supuso el desarrollo de lenguajes de alto nivel. De esta forma, se desarrollaron lenguajes como FORTRAN, RTL, PASCAL, MODULA o C. Pero la limitación de todos ellos es que, esencialmente, estaban diseñados para producir programas secuenciales. 2 Lenguajes para aplicaciones T.R. Lenguajes para aplicaciones T.R. Existen especificaciones relativas a los STR que deben ser cumplidas por estos lenguajes. Técnicas para que la aplicación sea separada en módulos. Facilidades relacionadas con el soporte de la concurrencia. El ciclo de vida en el desarrollo de una aplicación tiene las siguientes etapas: Análisis. Diseño e implantación. Hay que detectar los errores en las primeras etapas de desarrollo. Participan muchas personas en su desarrollo por lo que resulta interesante la separación de la aplicación en módulos que serán desarrollados por equipos, teniendo en cuenta su relación e interacción con otros módulos. En los STR vamos a tener varias tareas y tenemos que que representar la concurrencia de estas tareas. Para ello necesitamos soporte para la construcción de módulos, para la construcción y manejo de tareas, interrupciones, gestión de excepciones,… Mantenimiento. 13 Lenguajes. Elementos básicos 14 Lenguajes. Soporte de concurrencia Variables y constantes. Tipos de datos. Estructuras de control. Reglas de ámbito y visibilidad. Métodos de modularidad y compilación. Gestión de excepciones. Construcción de módulos Creación y gestión de tareas Gestión de interrupciones y dispositivos Comunicación entre tareas Exclusión mútua. Gestión de excepciones. 15 Lenguajes. Características deseables 16 Seguridad La seguridad de un lenguaje se mide en términos de la efectividad en la detección automática de errores. Hay una serie de características del lenguaje que ayudan a detectar los errores : Seguridad Legibilidad Flexibilidad Simplicidad Portabilidad Eficiencia soporte de modularidad independiente) (compilación y verificación declaración forzada de variables un rango de tipos de datos adecuado, incluyendo tipos subrango tipado de variables sintaxis no ambigua. 17 Los errores pueden ser lógicos o de sintaxis. Los segundos pueden ser detectados por el compilador. 18 3 Seguridad Legibilidad No es posible probar exhaustivamente el software, por lo que la seguridad intrínseca del lenguaje es básica para el desarrollo de programas fiables. Desarrollo cruzado: el desarrollo (edición, compilación y depuración) se realiza en un computador diferente al de ejecución Económicamente es importante detectar errores en la etapa de compilación en vez de en tiempo de ejecución. Es la medida de la facilidad con que se puede entender la operación de un programa sin comentarios adicionales y sin tener que recurrir a documentación externa, como diagramas o descripciones en el lenguaje natural. La idea es que los programas se van a escribir una vez pero se van a leer muchas veces para que se puedan adaptar a las necesidades. Las comprobaciones realizadas en tiempo de compilación no generan sobrecargas en tiempo de ejecución. (El programa se va a ejecutar muchas más veces de lo que se ha compilado). 19 Legibilidad 20 Legibilidad Las ventajas que se obtienen haciendo programas fácilmente entendibles son: Reducción de los costes de documentación. Mucha de la documentación está dada por el propio código. Se ha de tener en cuenta que, además, las personas que van a realizar el mantenimiento de la aplicación no suelen ser los mismos que la desarrollaron. Una detección de errores más fácil. Un mantenimiento más fácil. Para conseguir un programa entendible es necesario la colaboración del programador, ya que es posible escribir programas ilegibles en cualquier lenguaje. El problema de la legibilidad es que cada programador tiene su forma de escribir programas. Por lo tanto se necesita que el lenguaje sea muy estricto y que exista una planificación que ponga énfasis en la estructura y en la elección de los nombre de variables. Otro inconveniente de la legibilidad es que el código fuente se hace grande, y cuesta más tiempo escribirlo, pero es un pequeño coste que hay que pagar por la seguridad y el mantenimiento del software. 21 Flexibilidad Un lenguaje debe proporcionar todas las características necesarias para expresar todas las operaciones que se requieran sin tener que utilizar complicadas construcciones o recurrir a código ensamblador. La flexibilidad es una medida de esta capacidad. Para sistemas de tiempo real es particularmente importante ya que es normal tener que controlar dispositivos de entrada/salida no estándar. La flexibilidad y la seguridad suelen entrar en conflicto. En los lenguajes modernos este compromiso se consigue proporcionando alta flexibilidad y, por medio del concepto de módulo o paquete, se proporciona un medio por el cual las operaciones a bajo nivel (es decir, la no seguras) se pueden ocultar en un número limitado23 de secciones autocontenidas del programa. 22 Simplicidad Al igual que en otras áreas, lo simple es preferido a lo complejo. La simplicidad contribuye a la seguridad. Cuanto más simple es un lenguaje, más fácil es entender cada una de las instrucciones del programa, menor es el coste de aprendizaje y más difícil es cometer errores, por lo que se incrementa la seguridad. Además reduce el coste y los errores de programación, así como el tamaño del compilador, el cual proporciona un código objeto más eficiente. La simplicidad no tiene que llevar a inconsistencias que reduzcan la seguridad. Un buen lenguaje no debe imponer restricciones arbitrarias en la utilización de ninguna de sus características 24 4 Portabilidad Portabilidad La portabilidad es difícil de conseguir en la práctica, aunque es deseable como un medio de acelerar el desarrollo de aplicaciones, reduciendo el coste y aumentando la seguridad. Consiste en que una aplicación se pueda trasladar de una máquina a otra sin o con mínimos cambios. No solamente ha de ser portable en código fuente sino que hay que tener en cuenta las características de hardware de la máquina. En forma de código fuente es posible transferir un programa de un computador a otro, compilando y ejecutándolo en el computador al cual ha sido transferido. Pero aún así hay problemas, por ejemplo, la longitud de palabra de la dos máquinas pueden ser diferentes y pueden dar problemas con el rango y la precisión con la que son representados los números. Otro problema es el direccionamiento de información (convenios little endian y big endian). 25 Portabilidad 26 Eficiencia En los sistemas de tiempo real la portabilidad es aún más difícil de conseguir, ya que con frecuencia hay que utilizar características específicas del hardware del computador y del sistema operativo. En los STR, generalmente las aplicaciones no son portables. Una solución utilizada en la práctica es aceptar que un sistema de tiempo real no es directamente portable y restringir las partes no portables a unos módulos. Las aplicaciones se desarrollan pensando que se van a ejecutar en una máquina virtual y se desarrollan las partes de aplicación dependientes del hardware en forma de módulos dependientes del hardware, que se añaden enlazándolos a la aplicación. Define las prestaciones de un STR. En sistemas de tiempo real, que deben garantizar un cierto funcionamiento junto con unas restricciones temporales, la eficiencia es importante. En los primeros computadores para sistemas de control, el mayor énfasis se hizo en la eficiencia del código, tanto en términos de tamaño del código objeto, como en la velocidad de operación. Pero al bajar el costo del hardware y aumentar la velocidad de cálculo, los criterios de eficiencia han cambiado. 27 Eficiencia 28 Herramientas de desarrollo En muchas aplicaciones de tiempo real el concepto de lenguaje eficiente ha cambiado para incluir consideraciones de la seguridad y coste de escritura y mantenimiento del programa, quedando como de segunda importancia en muchas aplicaciones la velocidad y compactación del código. Aunque hay aún áreas de aplicaciones donde sigue siendo primordial la velocidad y compactación, por ejemplo en el control de sistemas electromecánicos, control aeronáutico y en general en el campo de procesamiento de señales, en las que todavía es importante el desarrollo en ensamblador de ciertas partes muy relacionadas con el hardware. 29 Ensamblador Lenguajes de alto nivel Montador Cargador Puesta a punto 30 5 ENSAMBLADOR ENSAMBLADOR Es un programa que acepta código fuente y produce código máquina. Ensambladores absolutos El fichero fuente con directiva de localización Produce código ejecutable que se puede cargar en el procesador de destino Pb: fichero fuente con todo el código o bien cálculo de ocupaciones y direcciones Relación 1:1 Instrucción fuente - instrucción micro Campos en el código fuente ensamblador Etiqueta: Referencia simbólica de una posición en memoria Código de operación Ensambladores relocalizables Ficheros fuente sin directivas de localización La salida no es código ejecutable → fichero objeto Necesidad de un montador de enlaces Mnemónico de una instrucción Directiva del ensamblador (p.e. ORG) Operandos Nombre de registros Constantes numérica o simbólicas Etiquetas Expresiones agebraicas 31 32 Comentarios LENGUAJES DE ALTO NIVEL LENGUAJES DE ALTO NIVEL Lenguajes de alto nivel Requisitos de un lenguaje para la programación de μC Desarrollo más rápido (↑ abstracción) Codificación más rápida y segura Puesta a punto de la lógica del programa más sencilla => disminución de costes Acceso directo a memoria y hardware para lectura y escritura Programación de periféricos ↓ eficiencia ↑ ocupación en memoria Reescritura de algunas partes críticas en ensamblador Posibilidad de llamar a rutinas en ensamblador o insertar código máquina Conexión directa con interrupciones La mayoría de la aplicaciones basarán gran parte de su funcionamiento en interrupciones Prestaciones, carácter asíncrono de los eventos Disminución de la ocupación de memoria Aumento de las prestaciones Generación de código eficiente en ocupación de memoria y velocidad de ejecución 33 LENGUAJES DE ALTO NIVEL Ada95 Incluye concurrencia, tiempo real, acceso a recursos de bajo nivel y un potente tratamiento de las excepciones Transportabilidad, Legibilidad, Eficiencia, Seguridad (chequeos del compilador) Genera código excesivamente extenso En el futuro se dispondrá de compiladores que contemplen un perfil mínimo del lenguaje Se dispone de poca memoria 34 LENGUAJES DE ALTO NIVEL, EL C C ANSI C -> estándar Alto nivel que retiene el contacto con el hardware ( ↓ abstracción, facilidad programación periféricos) Generación de código muy optimizable Extensiones para cumplir todos los requisitos Concurrencia => kernel Sistema empotrado ≠ computador con SO + E/S estándar No funciones de E/S No printf No ficheros ... Código y datos en posiciones concretas de memoria ROM, RAM, EEPROM,... No código ejecutable relocalizable (<= no SO) Directivas de posicionamiento + Linker Actualmente la industria emplea mayoritariamente el C para el desarrollo de sistemas empotrados Programación de periféricos Acceso a registros físicos Servicio de interrupciones 35 36 6 LENGUAJES DE ALTO NIVEL, EL C EL MONTADOR Acceso al bajo nivel Sólo necesario cuando el compilador genera código relocalizable Funciones Enlace con ensamblador P.e. CLI Manejo de bits Fuente Ensamblador Fuente C Compilador Cruzado Ensamblador Cruzado Ejecución sobre máquina desnuda Establecer las referencia cruzadas entre módulos Enlazar con librerías Asignar valor absoluto a símbolos, etiquetas Establecer las direcciones de carga Valor inicial del puntero de pila Se viste la máquina mediante ficheros de cabecera Funciones específicas de acceso al hardware Directivas Expansión en línea (inline) Intercalado de instrucciones en ensamblador Rutinas de servicio de interrupción Posicionamiento absoluto Objeto Relocalizable Librerías Objeto Relocalizable Fichero Configuración Montador de Enlaces Ejecutable No Relocalizable Cargador Máscara Microcontrolador 37 EL CARGADOR 38 PUESTA A PUNTO Carga del programa ejecutable en la memoria del microcontrolador Debuggers Dos posibles niveles Código fuente Código máquina Sistema operativo Carga por línea serie, ethernet, ... Paso a paso Posibilidades Visualizado de los efectos de la ejecución Monitor Hardware Breakpoints Memoria RAM Memoria EPROM/EEPROM Detención de la ejecución del programa -> control al debugger La instrucción donde debe detenerse el programa es cambiada por un interrupción software Existen micros que se autograban Grabador de la memoria PROM Máscara Visualizado y alteración de registros y memoria Un fichero de listado es imprescindible 39 Código fuente + ensamblador generado + Tabla de 40 PUESTA A PUNTO Posibilidades clasificadas por potencia: Simuladores La memoria y registros pueden ser observados y alterados No interrupciones, ni hardware adicional. No tiempo real Programación a pequeña escala Debuggers residentes Programa de aplicación y monitor conviven en el μC No ejecución simultánea Visualización y actualización de memoria, breakpoints, … Emuladores Hardware que “emula” al μC y además permite obtener información y actuar sobre la aplicación sin gastar recursos del μC ni alterar la evolución temporal Analizadores lógicos 41 42 7 Programación a pequeña escala Entre las características de los lenguajes de alto nivel podemos distinguir aquellas que ayudan al proceso de descomposición y aquellas que facilitan la programación de componentes bien definidos. Estas dos conjuntos se describen como: Cuestiones a tener en cuenta cuando se escriben programas: Recuerda que tu código puede ser analizado por otras personas (al menos un 40% de las líneas de un programa deben ser comentarios) •Soporte para la programación a gran escala. Usa nombres de variables con significado. •Soporte para la programación a pequeña escala. Los comentarios incrementan la comprensión de un programa. 43 Puntos básicos 44 Sintaxis y legibilidad BEGIN NST = TICKS ( ) + ST; T = TICKS ( ) + ST; LOOP WHILE TICKS ( ) < NST DO (*NADA*) END; T := TICKS ( ); CC; NST : = T + ST; IF KEYPRESSED ( ) THEN EXIT; END; END; END; Sintaxis y legibilidad Declaración e inicialiación de variables y constantes Modularidad y variables Compilación y programas modulares Tipos de datos Estructuras de control del flujo de ejecución 45 Sintaxis y legibilidad BEGIN NextSampleTime = TICKS ( ) + SampleTime; Time = TICKS ( ) + SampleTime; LOOP WHILE TICKS ( ) < NextSampleTime DO (*NADA*) END; Time := TICKS ( ); ControlCalculations; NextSampleTime : = Time + SampleTime; IF KEYPRESSED ( ) THEN EXIT; END; END; END; 46 Declaración e inicialización Declaración de entidades: Necesidades de almacenamiento Nombres explícitos 100 ERROR=0 ..... 200 IF X=Y THEN GOTO 300 250 EROR = 1 300 ... ... 400 IF ERROR=0 GOTO 1000 ... 47 48 8 Ejemplo de Fortran (mala convención léxica) DO 20 I = 1, 100 Declaración e inicialización Inicialización de entidades: Bucle iterando I desde 1 hasta 100 Dar un valor inicial cuando es declarada No deja al compilador tomar la decisión. En la sonda Viking Venus, un programador escríbió DO 20 I = 1. 100 El compilador interpretó esto como una sentencia de asignación pues, ignorando los espacios, se tiene: Constantes DO20I = 1.100 En Fortran, las variables no necesitan declararse, y aquellas que empiezan por D se asumen como de tipo realy y 1.100 es, literalmente, un número real. Valores de entidades físicas o matemáticas deben permanecer fijos en la ejecución del programa. Inicializar una variable no es seguro. Puede modificarse sin querer en otro punto del probrama. const en C 49 Ambito y visibilidad 50 Asignación dinámica de memoria El ámbito de una variable se define como la región de un programa en la que la variable es potenciable accesible o modificable. La región donde es realmente accesible o modificable define la visibilidad. Problemas: las variables declaradas en un procedimiento no pueden usarse para mantener valores en la siguiente entrada al procedimiento”. En C existen las variables static. Si un procedimiento se llama recursivamente es posible que el programa pueda fallar debido a que no haya memoria disponible. Crecimiento incontrolado de la pila malloc() y free() 51 Variables globales y locales 52 Tipos de datos Existen argumentos a favor y en contra de ambas. Variables locales: es una buena práctica declarar entidades cerca de donde van a ser usadas, y se limita el ámbito y la visibilidad de la entidad. Variables globales: es la única forma de garantizar la consistencia y el control del nombre de entidades en sistemas grandes que son desarrollados por equipos de programadores. Compromiso: declarar como globales a las entidades que están directamente relacionadas con el mundo exterior, y locales al resto. 53 La asignación de un tipo a una entidad define: El conjunto de valores que puede tomar la entidad. El conjunto de operaciones que se puede realizar con la entidad. La riqueza de tipos y la rigurosidad con que se impone la compatibilidad de tipos en un lenguaje tienen una gran influencia en la seguridad. Fuertemente tipados: imponen rigurosamente la compatibilidad de tipos. Débilmente tipados: no imponen compatibilidad 54 de tipos. 9 Tipificación de Datos Conversión de Tipos en el Lenguaje C Conversión Automática de Tipos: Cuando un operador tiene operandos de tipos diferentes, éstos se convierten a un tipo común “razonable”: Un lenguaje con tipificación de datos requiere que cada variable y/o constante sea de un tipo específico (entero, real, carácter, etc.) declarado antes de usar la variable. char Diferentes niveles de comprobación de tipos: Sin tipos (Ej: BASIC) Conversión automática de tipos (Ej: C) Comprobación de tipos fuerte (Ej: ADA) int float double long double short Dentro de los lenguajes con tipificación de datos, puede realizarse: comprobación estática de tipos (en tiempo de compilación) comprobación dinámica de tipos (en tiempo de ejecución) Las reglas se complican al considerar enteros y chars con y sin signo (resultado depende muchas veces de la máquina). 55 Conversión de Tipos en el Lenguaje C 56 El lenguaje C Es secuencial La principal unidad de estructuración son las funciones (aunque los ficheros pueden usarse para ayudar a la compilación separada) Estructurado en bloques (llamados declaraciones compuestas) Ejemplos de conversión automática: Operaciones int i,j; double k; i = j + k; k = i + j; Llamada a funciones función definida como: double sqrt(double) invocación: k = sqrt(i); { < parte declarativa > Casting: Conversión Explícita de Tipos (es bueno explicitarla siempre!) < secuencia de sentencias > } 57 Ejemplo en C La parte declarativa no puede contener funciones La secuencia de sentencias puede contener 58 declaraciones compuestas Tipos Discretos int mayor(vector X, int len) Ada Java C { int max = 0; int i; Asignación es = Integer Igualdad es = = for (i = 0; i < len; i++) { int int short short long long if(X[i] > max) max = X[i]; byte } Boolean return max; } boolean Character 59 char Wide_Character char wchar_t Enumeration types None typedef enum 60 10 Tipos Discretos Tipos Discretos • Ada y Java son fuertemente tipados, y soportan la conversión explícita de tipos; sin embargo, C no es tán seguro en cuanto a tipos, por ejemplo: int a; float b; a = b; /* en C */ Typedef int newint; Typedef enum {xplane, yplane, xzplane} dimension /* en Ada */ Type Dimension is (Xplane, Yplane); Type map is (Xplane, Yplane) Line, Force : Dimension, Grid : Map; •C y Ada permiten duplicación de tipos o la definición de nuevos tipos basada en tipos básicos. 61 Tipos de datos estructurados 62 Estructuras de Control Estructuras secuenciales Java soporta arrays, En Java, C, y Ada una secuencia se indica con {} En Ada es necesario establecer explícitamente una secuencia vacía: begin –Ada null; end; Esto no es necesario en C y Java Ada y C soportan arrays y registros (estructuras) --Ada Max: Const Integer:=10; Type Reading_T is array(0..Max-1) of Float; Size: Const Integer:=Max-1; Type Switches_T is array(0..Size, 0..Size) of Boolean; Reading: Reading_T; Switches: Switches_T; 63 Estructuras de Control Estructuras de Control Estructuras de decisión : If, if then else Bucles Iteración Recursión For --Ada for I in 0..9 loop A(I):=I, end loop; 64 while --Ada /* C and Java*/ while<expresión booleana >loop <sentencias> end loop; while (expresion){ <sentencias> } /* C y Java*/ for (i=0;i<=9;i++){ A[i] = i; } 65 66 11 Programar Programación estructurada TRANSFORMAR EL DISEÑ DISEÑO EN UN PROGRAMA EJECUTABLE EN UNA PLATAFORMA ESPECÍ ESPECÍFICA Características de una buena programación: la estructura arquitectónica definida durante el diseño es fácilmente reconocible en el código los niveles de abstracción del diseño se conservan las interfaces entre partes del programa son descritas explícitamente la consistencia de objetos y operaciones puede ser probada por el propio compilador Alcanzar estas características depende de: la programación estructurada la elección del lenguaje de programación la elección del entorno de programación el estilo de programación Dijkstra: “GOTO statement considered harmful” Calidad de un programa es inversamente proporcional al número de sentencias GOTO utilizadas. Programa Estructurado programa = secuencia de construcciones lógicas construcciones lógicas = secuencia, condición, repetición cada construcción tiene 1 entrada y 1 salida 67 Programación estructurada 68 Reglas para estructurar un diagrama de flujo 1) Mover el punto de destino del salto (donde se reunifica el flujo) y duplicar las acciones que quedan en medio. Características del código resultante número mínimo de GOTO’s utiliza bloques para estructurar minimiza el número de variables no locales BEGIN BEGIN A1 A1 Técnicas para eliminar los GOTO’s duplicación de código introducción de procedimientos utilización de variables auxiliares utilización de banderas 69 Reglas para estructurar un diagrama de flujo 2) Mover el punto de destino del salto más allá de los chequeos de condiciones añadiendo instrucciones adicionales que hagan innecesario el chequeo de condiciones. BEGIN BEGIN A1 A1 A3 A3 B1 F B1 T F A2 A2 A2 B1 B1 A3 A3 B2 B2 A4 A4 END END A1 70 Alternativa a los diagramas de flujo Diagramas de Flujo fomentan uso de “GOTO” impiden expresar estructuras de control de alto nivel Diagrama de Nassi-Schneiderman o de cajas impiden el uso de “GOTO” se determina fácilmente el ámbito de datos locales o globales recursividad se representa fácilmente T A2 B2:= TRU B2 F T B2 F T END END 71 72 12 Alternativa a los diagramas de flujo SECUENCIA Alternativa a los diagramas de flujo IF-THEN-ELSE: acción 1 SELECCIÓN (“switch-case”) cond. F T parte parte else then acción 2 ... caso de condición acción n cond. de bucle parte do-while valor valor ... parte case parte case ... REPETICIÓN Principales desventajas parte repeatuntil difíciles de modificar” “Fundamentalista”: no permiten expresar ningún GOTO cond. de bucle 73 74 Inconvenientes Alternativa a los diagramas de flujo el código resultante puede ser más largo el código resultante puede ser menos eficiente algún “gurú” se puede reir de nuestros programas /*Ejemplo: itoa: convierte n a caracteres en texto */ void itoa(int n, char texto[]) { int i, signo; if ((signo = n) < o) /*guardo el signo */ n = -n; /*hago n positivo */ i = 0; do { /* genero dígitos en orden inverso */ texto[i++] = n % 10 + ‘0’; } while (( n /=10 ) >0); if ( signo <0) /*agrego signo de menos? */ texto[i++] = ‘-’; texto[i] = ‘\0’; invertir(texto); } 75 GOTOs útiles y ventajas más entendible; más fácil de leer mayor confiabilidad más fácil de modificar mayor localidad más fácil de chequear quien ríe último, ríe mejor! ES MUCHO MÁ MÁS FÁ FÁCIL OPTIMIZAR UN PROGRAMA CORRECTO QUE CORREGIR UN PROGRAMA OPTIMIZADO QUE CONTIENE ERRORES 76 GOTOs útiles Para realizar estructuras de control no disponibles (por ejemplo en ensamblador) Para interrumpir iteraciones “break” y “continue” en el lenguaje C: Para salir rápidamente de dentro de varios niveles de iteración buscar un elemento en una matriz y abandonar la búsqueda inmediatamente después de encontrado /*trim: elimina blancos, tabuladores y NL al final*/ int trim(char texto[]) { int n; for (n = strlen(texto)-1; n >=0; n --) if (texto[n] !=‘ ‘ && texto[n] !=‘\t’ && texto[n] !=‘\n’) break; texto[n+1] = ‘\0’; return n; } Para optimizar programas estructurados 77 78 13 Estilo de programación Estilo de programación Estructura Los programas se escriben una sola vez, pero se leen (y modifican) muchas veces... ⇒ Vale la pena escribirlos de modo que sean entendibles! elegir instrucciones adecuadas Ejemplo: Tres instrucciones equivalentes en “C”: n = n * 8; n *= 010; /* constante en octal */ n <<=3; Elementos de un buen estilo: Estructura Elocuencia Forma externa Uso de comentarios en lo posible proceder de acuerdo a los criterios de la prog. estructurada (... pero sin fundamentalismos!) 79 Estilo de programación 80 Estilo de programación Elocuencia: Elocuencia (cont.) Elección de nombres apropiados para objetos y operaciones Ser elocuente y consecuente, aún cuando los nombres sean largos Utilizar abreviaturas usuales Ejemplo: CBCPJP = Controlador de la Bomba de Calor, Programador: Juan Pérez No mezclar distintos idiomas Utilizar palabras principales para valores (ancho, tecla,....) expr. con verbos para actividades (calc_ancho, lee_tecla...) expr. con adjetivos para condiciones (valido, muy_alto,...) Utilizar convenciones Ejemplo: Tipos definidos por el usuario: comenzar con “T_”, TODO MAYÚSCULA: T_PERSONA Constantes: TODO_ MAYÚSCULA Variables globales: comienzan con “gbl_”: gbl_unEjemplo Punteros: comenzar con “ptr_”: ptr_unPuntero Variables locales y funciones: todo minúscula Nombres compuestos por varias palabras: separarConMayuscula 81 Estilo de programación 82 Comentarios Comentarios de prólogo Encabezado de cada módulo Formato Forma externa separación de declaraciones e instrucciones declaraciones con una estructura sistemática: 1. Propósito, función del módulo 2. Descripción de la interfaz, incluyendo: constantes, tipos de datos variables organización de la descripción de la interfaz (lista de parámetros) en ejemplo de una “secuencia de llamada” descripción de cada uno de los argumentos lista de todos los módulos subordinados variables de entrada variables de salida variables de entrada/salida 3. Explicaciones pertinentes, como por ejemplo variables importantes y su uso modo de funcionamiento (algoritmos) restricciones y limitaciones separación de textos de programa y comentarios utilizar indentación para resaltar estructura del programa 4. Historia del desarrollo, incluyendo 83 autor, revisor y fecha. fecha y descripción de las modificaciones 84 14 Comentarios Desde código fuente en C hasta el ejecutable Comentarios descriptivos Deben proporcionar información adicional (si no, más vale ahorrárselos!) Deben ser correctos... un comentario no actualizado puede hacer perder mucho tiempo! En general, es mejor comentar bloques que líneas de código. Headers Headersdel del Sistema Sistema Código Fuente pre-procesador Código Fuente C preprocesado compilador Headers Headersdel del Usuario Usuario inclusión de archivos substitución de macros compilación condicional una o dos pasadas Código fuente en ensamblador ensamblador Código objeto Código objeto Bibiliotecas Bibiliotecasdel del Sistema Sistema Otros Otrosarchivos archivosde de código códigoobjeto objeto link-loader EJECUTABLE 85 Dicen los que saben... Bibiliotecas Bibiliotecasdel del usuario usuario 86 Dicen los que saben... Que es bueno evitar “programar astutamente” Que hay que evitar las variables globales pueden ser manipuladas desde cualquier lado una modificación puede tener efectos inesperados Que es mejor evitar los “efectos colaterales” Que los programas son en primer lugar un medio de comunicación con otros programadores primero correcto, después eficiente primero pensar, después compilar antes de compilar, “ejecutar mentalmente” el programa realizar inspecciones, explicarle a otros Que es posible escribir programas de buena calidad usando lenguajes no estructurados Que es posible escribir programas malos utilizando lenguajes buenos Que hay que evitar muchos niveles de if-then-else máximo 3 niveles Que los programadores buenos lo son independientemente del lenguaje de programación Que los programadores malos lo son independientemente del lenguaje de programación 87 88 Descomposición y Abstracción Decomposición — división sistemática de un sistema complejo en partes cada vez más pequeñas hasta que los componentes sean aislados y se puedan entender y manipular por individuos o pequeños grupos. TOP DOWN DESIGN Programación a gran escala Abstracción — especificación de la parte esencial del componente para una posterior consideración de los detalles del mismo BOTTOM UP DESIGN 89 90 15 Modulos Ocultación de Información Colección de objetos y operaciones lógicamente relacionados. Encapsulación — técnica para aislar una función del sistema dentro de un módulo con una especificación precisa del interfaz ocultación de información compilación separada tipos de datos abstractos ¿Como deberían descomponerse grandes sistemas en módulos? La respuesta está en la Ingeniería del Software! 91 Tipos abstractos de datos Una estructura modular soporta visibilidad reducida permitiendo que la información sea ocultada en su cuerpo. La especificación y el cuerpo de un módulo puede darse por separado. Idealmente, la especificación debería ser compilable sin haber escrito el cuerpo P. Ej., en Ada hay una especificación de package y un cuerpo de package; relación formal; errores en tiempo de compilación. En C, los módulos no están tan bien formalizados. Habitualmente, los programadores crean un fichero .h que contiene el interfaz a un módulo, y un fichero .c para el cuerpo. No hay relación formal. Los errores se detectan en tiempo de enlace (link) 92 Tipos abstractos de datos Ejemplo de especificación de un tipo abstracto de datos: Existen lenguajes de programación que permiten crear nuevos tipos de datos, más específicos que los tipos de datos generales previstos en el lenguaje. Un tipo abstracto de datos se especifica indicando: su dominio (es decir, los datos que lo integran) los servicios disponibles para operar sobre la estructura de datos las propiedades de dichos servicios. TIPO: STACK[X] FUNCIONES: empty: STACK[X] -> BOOLEAN new: -> STACK[X] push: X x STACK[X] -> STACK[X] pop: STACK[X] -> STACK[X] PRECONDICIONES: pre pop(s: STACK[X]) = not empty(s) AXIOMAS: Para todo x:X, s: STACK[X]: empty(new()) not empty(push(x,s)) pop(push(x,s)) = s 93 Tipos abstractos de datos: Ejemplos (1) 94 Tipos abstractos de datos: Ejemplos (1) Definición del tipo de datos “COMPLEJO” en ADA: Definición del tipo de datos “COMPLEJO” en ADA (cont.): package NUMEROS_COMPLEJOS is type COMPLEJO is record REAL: FLOAT:= 0.0; IMAG: FLOAT:= 0.0; end record; function EQUAL (X,Y: COMPLEJO) return BOOLEAN; function ¨+¨ (X,Y: COMPLEJO) return COMPLEJO; function ¨-¨ (X,Y: COMPLEJO) return COMPLEJO; function ¨*¨ (X,Y: COMPLEJO) return COMPLEJO; function ¨/¨ (X,Y: COMPLEJO) return COMPLEJO; end; -- fin de la especificación del tipo 95 package body NUMEROS_COMPLEJOS is function EQUAL (X,Y: COMPLEJO) return BOOLEAN is begin if (X.REAL = Y.REAL) and (X.IMAG = Y.IMAG) then return TRUE; else return FALSE; end if; end EQUAL; ... siguen las demás operaciones.... 96 16 Tipos abstractos de datos: Ejemplos (2) Tipos abstractos de datos: Ejemplos (2) Definición del tipo de datos “COMPLEJO” en ANSI C: Definición del tipo de datos “COMPLEJO” en ANSI C: archivo complejos.h: archivo complejos.c: typedef struct #include “complejos.h” { float real; float imag; } COMPLEJO; int c_equal(COMPLEJO x, COMPLEJO c_add(COMPLEJO COMPLEJO c_sub(COMPLEJO COMPLEJO c_mul(COMPLEJO COMPLEJO c_div(COMPLEJO /* fin de complejos.h */ int c_equal(COMPLEJO x, COMPLEJO y) { return ((x.real == y.real) && (x.imag == y.imag)); } /* c_equal */ ... siguen las demás operaciones.... COMPLEJO y); x, COMPLEJO y); x, COMPLEJO y); x, COMPLEJO y); x, COMPLEJO y); 97 Mecanismos de paso de parámetros Recursividad por valor: antes - llamada - después A: -5 b: 0 b=abs(A) A: -5 b: 5 Mecanismo por el cual un procedimiento puede autoreferenciarse. , al Ejemplo: er subrutina: function abs(x: integer) begin if x < 0 then return -x else return x end A: -5 b: 0 b=abs(A) A: 5 b: 5 subrutina: function abs(var x: integer) begin if x < 0 then return -x else return x end 99 Rutinas Re-entrantes o tiv ra ite o te t n ie len im iva ed qu c e o Pr 100 Consiste en la capacidad de asignar memoria a un proceso durante la ejecución del mismo. Necesaria para la construcción y mantenimiento de los stacks necesarios en cualquier SO de tiempo real. Alternativa: stacks de tamaño fijo: debo conocer de antemano su tamaño máximo. Compromiso: USO EFICIENTE DEL RECURSO MEMORIA int flag=0; */ var. global */ int no_reentrante() { printf(¨flag vale %i¨, flag); if (flag == 0) func1(); flag = 1; else func2(); flag = 0; } void mcd(int x,y); { int z; while (y != 0){ z=y; y=x % y; x=z; } printf(”mcd = %d”,x); } Asignación dinámica de memoria Una rutina re-entrante es aquella que puede ser utilizada por varias tareas que se ejecutan en forma concurrente, en un sistema multitarea. Un lenguaje de programación permite escribir rutinas re-entrantes, pero que una rutina lo sea o no depende del programador! Ejemplo : n ge ! en te! o, ien r pe fic te ine an uy g e m El void mcd(int x,y); { if (y == 0) printf(”mcd = %d”,x); else mcd(y, (x % y) ); } por referencia o por dirección: antes - llamada - después 98 101 vs. USO EFICIENTE DEL RECURSO CPU 102 17 Asignación dinámica de memoria Modularidad Funciones en C para reserva y liberación de memoria: malloc() reserva memoria free() la libera PASCAL: sentencias NEW y DISPOSE Alternativa a la administración manual de memoria: “Garbage Collection”, usual en lenguajes orientados a objetos tales como Eiffel, JAVA y Smalltalk 103 Modularidad Consiste en dividir al software en componentes con nombres y ubicaciones determinados, que se denominan módulos y que se integran para satisfacer los requisitos del problema. Sea: C(p): complejidad del problema p E(p): esfuerzo requerido para resolver el problema p Evidencias empíricas: C(p1) > C(p2) => E(p1) > E(p2) C(p1 + p2) > C(p1) + C(p2) Si el número de módulos crece mucho, entonces el esfuerzo asociado con el manejo de sus interfaces compensa la ventaja de particionar el problema en 104 módulos! Orientación a Objetos Modularidad y Lenguajes de Programación: Un Módulo debe ser compilable separadamente Un Módulo consta de: una interfaz pública de pue es una realización privada ón d aci ieda ho de m p ro ec gra Ejemplo: “Package” de ADA p r o a s p lo h ! ! en l so rlo! de a je r b u r o e g r a ngu logra d, pe ara lo e l Un dar a larida za p n ayu modu alca d e r lo n o a us 105 Manejo de excepciones Definición: Un sistema de software orientado a objetos es aquel que está construido como una colección estructurada de realizaciones de tipos abstractos de datos. cada mó módulo será será la realizació realización de un tipo abstracto de datos, que se denomina CLASE. Las clases son unidades autó autónomas, que colaboran para lograr satisfacer los requerimientos del sistema Existen relaciones entre las clases, a saber: • CLIENTECLIENTE-SERVIDOR • HERENCIA Polimorfismo: posibilidad de definir una función que tenga distintos efectos según el tipo de objeto a que se le aplique. Ejemplos : C++ Smalltalk Eiffel Java Ada95 OO y Tiempo Real: Real • dynamic binding • garbage collection 106 1.- Lenguaje Ensamblador (Assembler) Existen lenguajes que ofrecen facilidades para expresar cómo debe reaccionarse frente a un error u otra condición anormal que se dé durante la ejecución del programa. Estas situaciones se llaman EXCEPCIONES El código invocado si ocurre una excepción se llama rutina de atención a la excepción (“exception handler”) Algunas excepciones están previstas (y son detectadas) por el propio microprocesador. El programador debe suministrar la rutina de atención a la excepción. Ejemplo: división por cero Ejemplo de lenguajes que tienen previsto el tratamiento de excepciones: ADA, Eiffel, JAVA. 107 No posee casi ninguna de las características discutidas, que son propias de los lenguajes de alto nivel no estructurado inherentemente no portable difícil de aprender, tedioso, favorece errores La existencia de buenos compiladores hace que en general se obtengan programas más eficaces si se escriben en lenguajes de alto nivel que “optimizando assembler a mano” Provee control directo sobre el hardware Puede ser la única manera de optimizar al máximo una pequeña rutina que tiene gran incidencia en la respuesta temporal del sistema. En sistemas muy pequeños (ej: Microcontroladores de 8 bits), puede ocurrir que los recursos necesarios para usar un lenguaje de alto nivel no estén disponibles. 108 18 2.- Lenguaje PASCAL (estándar) Diseñado en 1968 para enseñar programación, no para uso profesional!!! No posee variables estáticas Standard I/O es defectuosa y no se puede sustituir (el estándar es cerrado) No posee elementos que permitan la construcción de programas grandes. En particular, carece de la noción de módulo compilable separadamente No es fácilmente extensible 3.- Lenguaje C Lenguaje Elegante y Simple, ideal para enseñar programación estructurada. Estándar ANSI/IEEE tipificación de datos fuerte recursión estructuras de datos dinámicas tipos enumerados fomenta la estructuración de los programas (“GOTO” considered harmful) (estas son algunas de las críticas formuladas por Kernigham, uno de los No ayuda a seguir los principios de la ingeniería de software: en C “está todo permitido” Manejo de punteros es difícil de aprender y provoca numerosos errores, aún entre programadores experimentados. No existen chequeos automáticos (por ejemplo, del índice de un array) control de tipos muy débil 109 autores del lenguaje C) 4.- Lenguaje ADA Sólo es utilizable en sistemas que disponen de una cierta cantidad de recursos No existen compiladores de costo accesible para todas las plataformas Lenguaje más bien extenso, difícil de comprender y aprender en su totalidad La introducción de la orientación a objeto en ADA95 aparece como un tanto forzada La condesa Ada Augusta Byron (Ada Lovelace) fue matemática y la única hija legítima del poeta Lord Byron. Trabajó con Charles Babbage en su Máquina de Diferencias, y es considerada la primera programadora de la historia. Lady Lovelace murió en 1852 a la edad de 36 años. Estandarizado, existen compiladores de dominio público para cualquier plataforma hardware importante No es paternalista: en C “está todo permitido” asignación dinámica de memoria compilación separada de módulos Actualmente, es “el” lenguaje de programación para aplicaciones de tiempo real no militares. 110 5.- Lenguaje JAVA Estandarizado Promueve la creación de programas confiables: Confiabilidad tipificación fuerte run-time checkings Soporta muy bien la modularidad separación de especificación y realización de los módulos módulos y packages compilables por separado Promueve un buen estilo de programación: Tipos abstractos de datos, Manejo de excepciones Específicamente diseñado para aplicaciones de tiempo real. Primitivas de sincronización de 111 tareas Solo es utilizable en sistemas que disponen de una cierta cantidad de recursos La “máquina virtual de JAVA” no está disponible (aún?) para todas las plataformas Los compiladores actuales producen código muy ineficiente Orientado a objetos, Simple Ambientes de desarrollo en el dominio público Suprime aspectos más polémicos de C++ manteniendo su sintaxis básica Lenguaje de la “era internet” tipificación mucho más fuerte que C, C++ run-time checkings aborda el problema de privacidad multi-tareas, sincronización y comunicación entre tareas manejo de excepciones garbage collection 112 Programación Concurrente Nombre dado a la notación y técnicas de programación que permiten expresar el paralelismo potencial y resolver los problemas resultante de sincronización y comunicación. Programación concurrente La implementación (hardware y software) del paralelismo es un tema esencialmente independiente de la programación concurrente. 113 La programación concurrente proporciona una visión abstracta para estudiar el paralelismo sin entrar en los detalles de su implementación. 114 19 ¿Porqué es necesaria? ¿Porqué es necesaria? Para modelar el paralelismo en el mundo real Para permitir la expresión del paralelismo potencial de tal forma que se pueda emplear más de un computador para resolver el problema. Virtualmente, todos los sistemas de tiempo real son concurrentes por naturaleza. Las actividades en el mundo real evolucionan simultáneamente. Para maximizar la utilización del procesador Esta es la principal razón para usar concurrencia 115 116 Terminología ¿Porqué es necesaria? Una alternativa consiste en utilizar técnicas de programación secuencial El programador debe construir el sistema de tal manera que implique la ejecución cíclica de una secuencia de programa para gestionar varias actividades concurrentes. Esto complica la ya de por sí difícil tarea del programador e implica la consideración de estructuras que son irrelevantes para el control de las actividades que tiene entre manos. Los programas resultantes son más oscuros y menos elegantes Complica la descomposición del problema La ejecución paralela del programa en más de un procesador podría ser mucho más difícil de conseguir. La escritura de código para el tratamiento de fallos es más problemático. Procesos concurrentes: Se dice que dos o más procesos son concurrentes si pueden ejecutarse en paralelo, de forma que alguno de ellos comience a ejecutarse antes que termine algún otro. Programa concurrente: Conjunto de procesos secuenciales autónomos, que se ejecutan (lógicamente) en paralelo o, de forma equivalente, un programa cuya ejecución se realiza según varios flujos de control que avanzan en paralelo. Cada proceso tiene su propio flujo de control. A veces se habla de procesos con varios flujos de control (threads) Las instrucciones de los procesos se ejecutan intercalándose unas con otras (paralelismo lógico) 117 Terminología 118 Concurrencia La ejecución de la colección de procesos concurrentes se puede realizar de tres formas: Los elementos empleados en la programación concurrente deben permitir: la creación de procesos concurrentes la sincronización de procesos la comunicación entre procesos Multiprogramación: Un único procesador va alternando la ejecución de los diversos procesos (entrelazado) Multiprocesamiento: Cada proceso se ejecuta en un procesador diferente con acceso a una zona de memoria común (datos comunes). Sistema fuertemente acoplado En función de la interacción (sincronización y comunicación ) entre procesos estos pueden ser: Procesamiento distribuido: Los procesos multiplexan su ejecución en varios procesadores que no comparten memoria. 119 Independientes : no se comunican ni sincronizan Cooperativos: para realizar alguna acción común Competitivos: para acceder a recursos compartidos Los procesos que cooperan o compiten necesitan 120 comunicarse y sincronizar sus actividades 20 Ejecución concurrente Ejecución concurrente Características del modelo de concurrencia: Características del modelo de concurrencia: Estructura Inicialización (información relacionada con su ejecución) Estática: nº de procesos fijo conocido a priori en tiempo de compilación creación dinámica en tiempo de ejecución Dinámica: Nivel de paralelismo Anidado: los procesos se definen en cualquier nivel. Se pueden definir procesos dentro de otros procesos, lo que permite crear jerarquías de procesos los procesos se definen en el nivel más externo del programa. Plano: Granularidad Grueso: pocos procesos de larga duración con gran variedad de acciones muchos procesos sencillos y efímeros Fino: Por paso de parámetros en la creación del proceso Comunicación explícita (IPC) con el proceso una vez creado Finalización del proceso Por llegar al final del cuerpo del proceso Suicidio por ejecución de una sentencia de autofinalización Aborto mediante una acción explícita de otro proceso Ocurrencia de una condición de error (excepción) sin manejar Nunca (bucle infinito) Cuando ya no son necesarios (si no tiene procesos dependientes de él) 121 122 Ejecución concurrente Jerarquía y relaciones entre procesos Para un proceso, es útil distinguir entre el proceso (o bloque) que es responsable de su creación, y el proceso (o bloque) que es afectado por su finalización. Relación padre/hijo Un proceso (padre) crea a otro (hijo) El padre ha de esperar mientras el hijo se crea e inicializa Sincronización y comunicación Relación tutor/pupilo o guardián/dependiente Un proceso (pupilo) puede depender del propio proceso tutor o de un bloque interno de éste. El tutor no puede terminar hasta que todos los procesos dependientes él (pupilos) hayan terminado El tutor puede ser: el padre, otro proceso o un bloque interno del padre o de otro proceso. 123 Sección Crítica y Exclusión Mutua Propiedades de la Sincronización Ausencia de deadlocks (bloqueos) Condiciones necesarias para un deadlock: Cola de Impresión: Proc A 124 abc.txt 4 5 ejemp.ps 6 ejemp2.ps 7 los procesos pretenden acceso exclusivo a los recursos (mutual exclusion condition) los procesos mantienen los recursos obtenidos mientras esperan por otros (wait for condition; only serially reusable resources) a los procesos no se les puede quitar un recurso hasta que ellos lo liberen voluntariamente (no preemption condition) existe una cadena cerrada de procesos y recursos, en la cual los procesos esperan por recursos que están siendo ocupados por otro proceso (circular wait condition) out = 4 in = 7 Proc B SECCIÓN CRÍTICA: secuencia de instrucciones que debe ser ejecutada indivisiblemente EXCLUSIÓN MUTUA: sincronización necesaria para proteger una sección crítica 125 126 21 Propiedades de la Sincronización Propiedades de la Sincronización Ausencia de livelocks (inanición) Ejemplo: Se inician las tareas en orden A, B, C. Los recursos se obtienen todos juntos, o no se obtienen el proceso B tiene un livelock por la conspiración de A y C: Ejemplo de Deadlock: Cruce de dos calles • sin semáforos • única regla de preferencia: “pasa primero el que viene por la derecha” Tarea A Tarea B Tarea C Solicitar Lector Solicitar Lector e Impresora Solicitar Impresora Lector Asignado Lector e Impresora Asignados Impresora Asignada Imprimir 10 Formularios Leer 100 Unidades Leer 200 Unidades Liberar Lector Imprimir 5 Formularios Liberar Impresora Liberar Lector e Impresora 127 Respetar las capacidades límites – no sacar, cuando está vacío – no poner, cuando está lleno (PROBLEMA PRODUCTOR/CONSUMIDOR) R1 R1 P1 P1 R2 R2 P2 P3 128 P2 R3 P3 R3 129 Entrelazado 130 Entrelazado Entrelazado y operaciones atómicas Proceso P ; x,y: entero ; Proceso Q ; z,u: entero ; P1: x:=1 ; P2: y:=x+2 ; Q1: z:=3 ; Q2: u:=z+1 ; fin P ; fin Q ; Cada instrucción de alto nivel: varias instrucciones código máquina. Por ejemplo: x := y + z ; copiar copiar sumar copiar Posibles ejecuciones: (P1; P2; Q1; Q2) (P1; Q1; P2; Q2) (Q1; P1; P2; Q2) ... y, r1 z, r2 r1, r2 r2, x Operaciones atómicas. 131 132 22 Comunicación y Sincronización Compartición de una variable La dificultad de la programación concurrente estriba en las interacciones de los procesos: Ejemplo: dos procesos (contador y escritor) comparten una variable n. Cooperación para un fin común Competencia por el uso de recursos VARIABLE COMPARTIDA n : entero := 0; Son necesarias operaciones de comunicación y sincronización entre procesos: proceso contador; principio repetir esperar pulso; n:=n+1; fin repetir; fin; Sincronización: cumplir restricciones sobre el orden en el que se ejecutan sus acciones Comunicación: paso de información de un proceso a otro proceso escritor; principio repetir esperar 1 hora; escribir n; n:=0; fin repetir; fin; Hay dos formas de realizarlo: ERROR: el resultado depende del orden en que se intercalen las instrucciones Datos compartidos Paso de mensajes 133 Compartición de una variable Compartición de una variable Posibles trazas: Sección crítica: Se garantiza que dos procesos no estarán ejecutando a la vez una misma región crítica (escribir n; n:=0; n:=n+1) (escribir n; n:=n+1; n:=0) --> Pérdida de pulso (n:=n+1; escribir n; n:=0) n := n + 1 ; copiar sumar copiar n, r1 r1, 1 r1, n 134 Monitores, mutex, Test_and_set Ejemplo: dos procesos (contador y escritor) comparten una variable n. VARIABLE COMPARTIDA n : entero := 0; / (escritor) n:=0; => Pérdida de la puesta a cero de n Problema: entrelazado de las instrucciones en el acceso a la variable común. Solución: garantizar la exclusión mutua en el acceso al elemento compartido. 135 Exclusión mutua proceso contador; principio repetir esperar pulso; region n hacer n:=n+1; fin; fin repetir; fin; proceso escritor; principio repetir esperar 1 hora; region n hacer escribir n; n:=0; fin; fin repetir; fin; 136 Sección crítica no expulsable Evitar expulsiones cuando se ejecuta una sección crítica Dos procesos compiten cuando comparten: Enmascarar interrupciones un recurso una variable (comunicación) No entra el núcleo, ni el reloj, ... Elevar al máximo la prioridad del código Posibilidad de cambiar en tiempo de ejecución la prioridad de un tarea El acceso al recurso o a la variable debe ser en exclusión mutua. Sección crítica: secuencia de instrucciones que debe ejecutarse en exclusión mutua Mecanismos de sincronización Espera ocupada (busy waiting) Semáforos Monitores 137 void Servicio (...) { Mask_all_Interrupts () ; Service_Code() ; /* sección critica */ Unmask_all_Interrupts () ; return ; void Servicio (...) { } Nominal = Get_Priority () ; Set_Priority (HIGH) ; Service_Code() ; /* sección critica */ Set_Priority (Nominal) ; return ; } 138 23 Espera ocupada (Busy waiting) Exclusión Mutua - “busy waiting” Puede usarse un indicador compartido si el acceso al mismo es atómico Problema: Test_and_Set: operación atómica que bloquea un indicador y devuelve el valor que tenía antes INDICADOR COMPARTIDO proceso P1; Bloqueado: booleano:= false; principio repetir mientras Test_and_Set(Bloqueado) nada; fin mientras; < sección crítica > Bloqueado := false ; < otras cosas > Test_and_Set: fin repetir; load r1, 1 atómica fin ; swap r1, flag return r1 139 Exclusión Mutua - “busy waiting” Características de la solución no asumir orden fijo de ejecución un proceso fuera de su sección crítica no debe bloquear a otros procesos ningún proceso debe esperar indefinidamente para entrar en su sección crítica 140 Exclusión Mutua - “busy waiting” Ejemplo: Solución 1: Quien está saliendo iza una bandera con el número de la persona que puede entrar 2 personas quieren acceder a un ÁREA CRÍTICA con exclusividad. Desde fuera no se puede saber si el área crítica está ocupada, pero existen garitas desde las cuales se pueden ver banderas que se utilizan dentro del área crítica para pasar información. Garita 1 Análisis de la solución Satisface requerimiento de exclusión mutua No hay posibilidad de deadlocks No hay posibilidad de livelocks (asumiendo estancia finita en área crítica) Procesos fuertemente entrelazados (se “van pasando” el derecho a entrar) persona1 ÁREA CRÍ CRÍTICA Garita 2 loop <prot. entrada> <sección crít.> <prot. salida> <sección no crítica> ventanas secuencia estricta de entrada: una vez cada uno si un proceso termina, el otro queda en deadlock. 141 Exclusión Mutua - “busy waiting” 142 Exclusión Mutua - “busy waiting” Solución 1: Solución 2: Quien está saliendo iza una bandera con el número de la persona que puede entrar Objetivo: Evitar los problemas de solución 1 Cada persona tiene su propia bandera, para que la ponga en la garita. Para entrar P1: Análisis de la solución Loop de persona1: flag !=1 ÁREA CRÍ CRÍTICA esperar <sección crít.> flag = 2 <sección no crítica> 1/2 Garita 1 Dos tareas requieren la utilización exclusiva de un recurso ¿Cómo garantizar que en el tiempo que transcurre desde que una tarea consulta si el recurso está siendo utilizado hasta que lo comienza a utilizar, este recurso no es tomado por la otra tarea? alza su bandera (1) para indicarlo LUEGO chequea el estado de la bandera del otro lado (2) si (2) está baja, entra al área crítica, y al salir baja su bandera. si (2) está alta, se queda en la garita esperando hasta que P2 salga, y entonces entra Análisis de la solución La solución no está libre de deadlocks! ¿En qué caso? Garita 2 143 144 24 Exclusión Mutua - “busy waiting” Exclusión Mutua - “busy waiting” Solución 2: Solución 3 (Algoritmo de Dekker): Objetivo: Evitar el deadlock de la solución anterior Se agrega una bandera de prioridad que se usa solo en el caso que ambas personas soliciten entradas simultáneamente (en otro caso, vale la solución 2). La bandera de prioridad indica cuál de las 2 personas tiene prioridad para insistir. Análisis de la solución La solución no está libre de deadlocks! ¿En qué caso? Loop de persona1: Análisis de la solución flag1 = UP flag2 == UP esperar <sección crít.> flag1 = DOWN <sección no crítica> ÁREA CRÍ CRÍTICA 1 entra a la zona crítica. al salir cambia el No. en la bandera de prioridades. Solución elegante de la exclusión mutua, sin utilizar primitivas especiales. Protocolos difíciles de diseñar, entender y verificar (probar extender esto para más de 2 tareas) Busy wait ... 2 Garita 1 Garita 2 145 Exclusión Mutua - “busy waiting” Semáforo Solución 3 (Algoritmo de Dekker): Es una variable que toma valores enteros no negativos (counting semaphore) Análisis de la solución ... proceso “perverso” puede utilizar mal las variables compartidas y corromper todo el sistema Se ha asumido que entre chequear el estado de una bandera y entrar al área de exclusión no ocurre nada! Loop de persona1: prio = 2 ÁREA CRÍ CRÍTICA 1 1 Bandera de prioridad 2 Garita 2 esperar <sección crít.> flag1 = DOWN <sección no crítica> 147 Sincronización condicional 148 La exclusión mutua puede asegurarse con un semáforo binario, inicializado a uno mutex (MUTual EXclusion) mutex: semaphore := 1 ; proceso P1; proceso P2; principio principio repetir repetir Wait(mutex) ; Wait(mutex) ; <sección crítica> <sección crítica> Signal(mutex) Signal(mutex) <sección no crítica> <sección no crítica> fin repetir; fin repetir; fin P1 ; fin P2 ; condicion: semaphore := 0 ; proceso P2; -- avisa principio repetir <parte 2a> Signal(condicion) <parte 2b> fin repetir; fin P2 ; La parte 1b no se ejecuta hasta que P2 avisa que se cumple la condición necesaria Las operaciones signal y wait son atómicas. Los semáforos tienen asociada una cola de procesos suspendidos en espera. Los semáforos son gestionados por el núcleo de ejecución Semáforo: exclusión mútua Sincronización condicional: una acción de un proceso sólo se puede ejecutar si otro proceso está en un cierto estado o si ha ejecutado ciertas acciones. Un semáforo binario inicializado a cero sirve para comunicar que se cumple la condición proceso P1; --espera principio repetir <parte 1a> Wait(condicion) ; <parte 1b> fin repetir; fin P1 ; S : semaphore := valor_inicial ; wait(S): si S > 0, S := S - 1 si no, suspender el proceso signal(S): si hay procesos esperando, pasar uno de ellos a preparado si no, S := S + 1 flag1 = UP flag2 == UP && prio ==2) Garita 1 146 149 CUIDADO: si un proceso olvida liberar el mutex, el recurso queda bloqueado 150 25 Semáforo: exclusión mútua Cuidado con los bloqueos Cuando se comparten recursos entre procesos, es posible que aparezcan bloqueos mutuos: SNC recurso1, recurso2: semaphore := 1 ; proceso P1; proceso P2; SNC wait wait SC SC Wait(recurso1) ; Wait(recurso2) ; .... Signal(recurso2) ; Signal(recurso1) ; Wait(recurso2) ; Wait(recurso1) ; .... Signal(recurso1) ; Signal(recurso2) ; Bloqueo mutex proceso P1; signal proceso P2; signal SNC Wait(recurso1) ; Wait(recurso2) ; .... Signal(recurso2) ; Signal(recurso1) ; SNC 151 Comunicación Wait(recurso1) ; Wait(recurso2) ; .... Signal(recurso2) ; Signal(recurso1) ; 152 Correcto Productor/consumidor Buffer Ilimitado Buffer Limitado Productor Consumidor buffer 153 Productor/consumidor 154 Lectores/escritores Buffer Limitado Productor Consumidor Lector1 2 Escritor1 2 2 buffer Lector2 Escritor2 2 tamaño 155 Problema : inanición de los escritores 156 26 Lectores/escritores Semáforos: RESUMEN Ventajas Escritor1 Mecanismo simple y eficiente Permite exclusión mutua y sincronización condicional Evita las esperas ocupadas (busy waiting) Lector1 Inconvenientes 2 Mecanismo de bajo nivel: poco estructurado Su uso queda disperso por los procesos Es fácil cometer errores: un solo wait o signal mal colocado puede ser fatal Es mejor utilizar mecanismos más abstractos y fiables como los monitores Lector2 2 Escritor2 157 Monitor 158 Monitor Es un módulo que encapsula las secciones críticas asociadas a una variable o un dispositivo físico en forma de procedimientos que son llamados por los procesos Sólo se puede acceder al elemento compartido a través de los procedimientos del monitor Las llamadas a los procedimientos del monitor se ejecutan en exclusión mutua. Monitor Contador; n: entero := 0 ; Hay lenguajes que soportan monitores ej: protected de Ada 95 Pueden programarse mediante semáforos /* fichero contador.h */ procedimiento incrementa; principio n:=n+1; fin; void incrementa(void) ; void escribe_borra(void) ; procedimiento escribe_borra; principio escribir n; n:=0; fin; fin Contador ; 159 Exclusión mutua e interrupciones /* fichero contador.c */ #include “contador.h” #include <semaphore.h> /* variables privadas del monitor */ static semaphore mutex_contador ; static int n = 0 ; void incrementa(void) { wait(mutex_contador) ; n:=n+1; signal(mutex_contador) ; } void escribe_borra(void) { wait(mutex_contador) ; escribir(n); n:=0; signal(mutex_contador) ; } 160 Comunicación mediante mensajes Las rutinas de servicio de interrupciones (ISR) se ejecutan en concurrencia con el resto de procesos. Las tareas se pueden comunicar o sincronizar mediante paso de mensajes No requiere memoria compartida Exclusión mutua => inhibir interrupciones Puede usarse en sistemas distribuidos static int n = 0 ; Según el tipo de sincronización emisor-receptor: void rutina_interrupcion(void) { n:=n+1; } Comunicación asíncrona (buzón, semáforo) El emisor envía el mensaje y continúa Comunicación síncrona (cita) void escribe_borra(void) { inhibir_interrupciones() ; escribir(n); n:=0; activar_interrupciones() ; } El emisor espera a que el receptor reciba el mensaje Invocación remota (cita extendida) El emisor espera a que el receptor reciba el mensaje, lo procese y envíe una respuesta 161 162 27 Comunicación Buzón Comunicación asíncrona o por mensajes Un proceso P produce y envía una secuencia de datos a otro proceso C que los recibe y consume. Los datos son transmitidos en porciones discretas denominadas mensajes. Es posible que P produzca un mensaje cuando C no esté en disposición de recibirlo. Para evitar que P espere se introduce un área de almacenamiento de mensajes donde P puede colocar sus mensajes hasta que C los lea: BUZON o COLA DE MENSAJES Emisor Así Asíncrona Receptor Síncrona P Invocació Invocación remota 163 Buzón B C Es posible que un buzón tenga varios procesos emisores y 164 varios receptores. Buzón: ejemplos de uso Manejo de buzones: Desacoplar una tarea rápida de una tarea lenta Buzón B : buffer send(M,B) max of T Tarea Control receive(M,B) La política de manejo de un buzón puede ser: Tarea Pantalla Servidor con varios clientes Si al enviar un mensaje el buzón está lleno: Las peticiones indican el buzón de respuesta Buzones el emisor espera hasta que haya espacio el emisor espera, con un tiempo máximo el mensaje se descarta se descarta otro mensaje (p.ej. el más antiguo) Respuestas Tarea Cliente 1 Si al solicitar un mensaje el buzón está vacío: el receptor espera hasta que haya mensaje el receptor espera, con un tiempo máximo se le indica que no hay mensaje y puede continuar Avisos Peticiones Tarea Servidor Tarea Cliente 2 165 Respuestas 166 Uno de los aspectos más relevantes que debemos tener en cuenta a la hora de desarrollar sistemas de tiempo real es el sistema operativo o núcleo ejecutivo. El sistema operativo debe proporcionar soporte básico para tareas de tiempo real, tolerancia a fallos, predictibilidad, etc. Sistemas operativos 167 168 28 Los actuales sistemas operativos de tiempo real: Para garantizar las especificaciones temporales: poseen un cambio de contexto rápido, poseen un reloj de tiempo real, poseen un tamaño adaptable a la aplicación que se desea desarrollar pudiendo llegar a un sistema mínimo con una funcionalidad restringida, proporcionan mecanismos de planificación basados en prioridades, proporcionan alarmas y timeouts, y las tareas pueden hacer uso de llamadas para bloquearse durante determinados intervalos de tiempo. responden a las interrupciones externas de una forma rápida, minimizan los tiempos en los que las interrupciones están deshabilitadas, proporcionan esquemas de gestión de memoria basados en particiones fijas o particiones variables (nunca memoria virtual para tareas con restricciones estrictas de tiempo real) proporcionan archivos especiales que son capaces de almacenar datos a gran velocidad. 169 Componentes del sistema informático 170 Componentes del sistema informático Sistema Informático de propósito general S.O. Aplicaciones del usuario Hardware - proveé los componentes básicos de cómputo (CPU, memoria, dispositivos de E/S). Sistema Operativo - controla y coordina el uso del hardware Soporte entre los varios programas de aplicación para los diferentes usuarios. Programas de Aplicación - define las formas en que los recursos del sistema son utilizados para resolver los problemas de cómputo de los usuarios (compiladores, bases de datos, juegos de video, programas de negocios). Usuarios (gente, máquinas, otras computadoras). de lenguaje SW HW Utilidades Gestión de ficheros PlanifiSubsist. Despa- Gestión cador E/S chador de INT CPU 171 172 E/S Objetivos del S.O. Gestión de tareas: (Scheduling) asignación de tiempo de procesador a las tareas. Decide que tarea pasa a ejecutarse. Gestión de memoria: control de asignación de memoria. Gestión de recursos: Controla los recursos compartidos diferentes a memoria y tiempo de procesador. Almacena la información asociada a los recursos compartidos por parte de las tareas. Se refiere a los canales de comunicación entre tareas. Sistemas operativos de tiempo real (SOTR) Gestión y comunicación entre tareas. Suministra los mecanismos que dan soporte a la comunicación segura entre tareas y a la sincronización de sus actividades. Creación de tareas y mantenimiento de la información asociada a tareas, así como la eliminación de esta información. Una tarea puede crearse por invocación de otras tarea 173 174 29 Sistema operativo mínimo No se trata de que los SOTR sean sistemas rá rápidos sino de que sean fiables. S.O. debe satisfacer las restricciones temporales explícitas de modo que si no se cumpliesen, se darían en el sistema consecuencias de riesgo severo incluso el fallo total diseñar el SOTR pensando más en el peor caso que en optimizar el Software de aplicación rendimiento medio. atender con una alta prioridad a las señales externas (interrupciones) provenientes del sistema , ya que pueden informarnos de un cambio de estado en el sistema. minimizar todo aquello que conlleve un alto precio en el tiempo de la CPU Kernel o núcleo SW HW CPU E/S 175 El núcleo (kernel) de un SOTR El núcleo (kernel) de un SOTR Requisitos generales para el kernel subyacente en un SOTR: Funcionalidades generales incluidas en los kernels de sistemas empotrados para aplicaciones específicas de TR: Multitarea. Planificación (Scheduling) por desalojo. Cambio de contexto rápido. Tamaño pequeño. Rapidez y flexibilidad en la comunicación y sincronización entre tareas. Facilidad de comunicación entre tareas y niveles de interrupción. Capacidad de responder rápidamente a interrupciones externas. Límite de ejecución. Dotación de particiones fijas o variables para la gestión de memoria Minimizar los intervalos durante los que las interrupciones están deshabilitadas. Capacidad de bloquear acceso al código o a datos de memoria. El kernel ha de mantener un reloj de tiempo real. 177 Requisitos actuales de los SOTR Determinismo Capacidad para responder a sucesos rápidamente Control del sistema por parte del usuario Gestión de prioridades Fiabilidad Gestión de Memoria Comunicación entre tareas Código Reentrante Tamaño reducido de código SOTR distribuidos Configurabilidad Adaptabilidad Gestión eficiente de recursos. Planificación (Scheduling) de tareas y comunicaciones. Multitarea con baja sobrecarga por cambios de contexto. Equilibrado de carga de trabajo. Reconfiguración y recuperación dinámica. Operación de dispositivos. E/S síncrona y asíncrona. Respuesta rápida a interrupciones externas. Primitivas IPC (memoria compartida, semáforos, eventos asíncronos). Posibilidad de bloqueo y desbloqueo de memoria. Uso limitado de memoria virtual. Soporte de tiempo real para cumplimiento de plazos de respuesta: planificación basada en prioridades, relojes de tiempo real, alarmas especiales, primitivas para retardar, parar o reanudar tareas, etc. Tamaño ajustable a las necesidades de empotramiento. 176 178 Requisitos actuales de los SOTR Determinismo tendencia que tiene el sistema a realizar una determinada acción en un tiempo predefinido. El minimizar el tiempo de respuesta a interrupciones garantiza un mayor nivel de determinismo. Capacidad para responder a eventos rápidamente distinguiendo entre sucesos síncronos y asíncronos. Necesita una rutina de interrupciones para dar una respuesta “inmediata” a los sucesos asíncronos. Requiere que el desalojo y realojamiento de procesos de la CPU se haga con rapidez. Control del sistema por parte del usuario 179 de modo que los usuarios puedan conmutar entre distintos modos de ejecución. Por ejemplo, un operario que acciona un botón de emergencia de una máquina. 180 30 Requisitos actuales de los SOTR Requisitos actuales de los SOTR Gestión de prioridades Gestión de Memoria En los sistemas operativos tradicionales la prioridad es dinámica, el propio sistema operativo va incrementando o decrementando la prioridad conforme pasa el tiempo. En los SOTR la prioridad debe ser estática con prioridades determinadas, al menos para varios procesos especialmente críticos. Las interrupciones procedentes del exterior tienen una prioridad fija, que no depende de tiempos de espera o ejecución. Además, se ha de analizar si la tarea se realiza según los plazos (seguimiento preventivo y predictivo) y cómo interfiere con las demás ha de ser más estricta que en los sistemas operativos tradicionales. Cuanta mayor facilidad se trata de dar al usuario mayor va a ser el kernel. En los SOTR el kernel debe ser lo menor posible. Comunicación entre tareas debe ser muy rápida. Suele ser explícita, la tiene que hacer el usuario. Código Reentrante se refiere a la posibilidad de que dos procesos puedan emplear un mismo código de programa, sin tener que tener una copia del mismo para cada uno y sin que haya problemas de interferencias entre ellos al manejar el mismo código. Fiabilidad en caso de fallo hardware ha de haber una solución de tipo software. Deben estar contempladas todas las respuestas que daríamos a cada uno de los posibles fallos que se pudiesen dar. 181 Requisitos actuales de los SOTR 182 Requisitos actuales de los SOTR Tamaño reducido de código Configurabilidad conviene que el repertorio de rutinas empleado sea lo menor posible, a costa de mayor coste de programación por parte del usuario, de modo que se minimice el número de primitivas y el kernel. Consiste en que el SOTR sirva para una amplia gama de sistemas: desde pequeños sistemas empotrados hasta sistemas donde cada nodo de la red es un supercomputador. Adaptabilidad SOTR distribuidos necesidad de adaptación a cambios que se producen en el entorno de operación del sistema de tiempo de ejecución. Los tipos básicos de adaptación son la preventiva y la reactiva. La adaptación preventiva trata de garantizar un nivel de prestaciones y funcionalidad del software de operación haciendo hipótesis sobre el comportamiento futuro del sistema basándose en su comportamiento presente. La adaptación reactiva se realiza en respuesta a eventos inesperados. Se trata de minimizar los tiempos de respuesta por parte de la red: buses de campo, redes industriales, han de minimizar el tiempo desde que se recoge algo en un sensor hasta que llega a un actuador para ejecutar la orden que corresponda, pasando por el gestor de la red. Otros problemas son considerar que puede haber sobrecarga en la red y problemas de sincronización de los relojes de los distintos elementos del sistema distribuido. 183 Sistemas Operativos de Tiempo Real Military; Aerospace Automotive; Medical Telecom; Datacom; Office Products Household Appliances; Consumer Electronics HARD RTOS 184 Hard Real-Time vs. Soft Real-Time Commercial Personal Computers SOFT RTOS Hard Real Time Wind River Systems Integrated Development Environment (format of embedded software) Complex • real time • deterministic • time critical • failure can be catastrophic Tornado, VxWorks Integrated Systems pRISM+; pRISM+; MATRIXx Microsoft Lynx QNX 3COM Windows CE Palm Computing Microware OSOS-9 RTOS Windows 98, NT, XP Soft Real Time Symbian • less real time • less deterministic • not as time critical • failure can be overcome EPOC16 RTOS Microtec VRTX JChorusOS Sun Microsystems JavaOS SONY Lucent Nano OS, Aperios Inferno • Lynx • TRON • Microware • Microtec • Venturcom In-house RTOS QNX Software LynxOS • Wind River • Integrated Systems • QNX • Symbian • Lucent General Purpose OS Commercial • Microsoft (CE) • Sun Microsystems (Java) • Geoworks Simple Low Footprint (Memory Size) High 185 Source: Lehman Brothers 186 Source: Lehman Brothers 31 Planificación cíclica Las tareas se van ejecutando de forma cíclica, de acuerdo con una cola de tareas. Estrategias de planificación Tarea A completa Tar. B comp. Tarea C completa ··· Tarea N complet Tarea A completa ··· Ventana de Tiempo 187 Planificación cíclica 188 Planificación por desalojo (preemptive) Las ventajas son que se minimizan los cambios de contexto y que es fácil diseñar un planificador para una tarea conociendo el caso peor. La ventana de tiempo tiene que ser suficientemente pequeña para que su constante de tiempo sea menor que la de la planta pero suficientemente grande para contener las tareas a ejecutar. Las tareas se van ejecutando siempre en el mismo orden. Cada ventana de tiempo se repite indefinidamente, como por ejemplo, en los autómatas programables. Entre los inconvenientes está el que es difícil la comunicación entre tareas. No hay posibilidad de comunicación con eventos externos, por lo que no es posible el tratamiento por interrupciones: la interrupción ha de esperar a su ventana de tiempo. No hay desalojo, por lo que si una tarea entra en un bucle infinito, no desocupará nunca la CPU. Otro inconveniente es que si se cambian las especificaciones, hay que modificar la ventana de tiempos. Se trata de una estrategia no única, puede tener variantes. Las tareas son desalojadas en el momento en que haya otra tarea con mayor prioridad. Se produce por invocación de nuevas tareas, por interrupción o por temporización. Cuando entra el planificador, comprueba si la tarea que se está ejecutando tiene menos prioridad que los que están en espera. En este caso, la desaloja. Por lo tanto, una tarea puede ser desalojada antes de terminar su ejecución • round-robin Mecanismos de planificación por desalojo • Con asignación de prioridades • estática • dinámica 189 190 Planificación por desalojo (preemptive) El planificador tiene que activarse cada cierto tiempo. Cuando se activa, comprueba si la tarea actual debe seguir ejecutándose en función de su prioridad. En cada intervalo de tiempo puede activarse una nueva tarea con una prioridad mayor o menor que las actuales. El tiempo del planificador es tiempo no útil para las tareas. Sea cual sea la estrategia de planificación escogida, el sistema de gestión de tareas ha de permitir el tratamiento de interrupciones. Éstas pueden ser interrupciones hardware causadas por eventos externos, o interrupciones software generadas por una tarea que se está ejecutando. Una interrupción fuerza el cambio de contexto. El proceso de tratamiento de interrupciones pasa a ejecución desbancando a la tarea que se está ejecutando. Debido a esto, es lógico pensar que en manipulador de interrupciones ha de ser breve. Una vez que ha terminado se restaurará la tarea que fue desalojada de la CPU o bien el planificador decidirá qué tarea pasa a ejecución (esto depende de cómo se haya implementado el191 SOTR). Estructuras de prioridad 192 32 Niveles de prioridad Nivel de interrupción En este nivel de prioridad se encuentran las rutinas de atención o de servicio para las tareas o dispositivos que requieren una respuesta muy rápida (en torno a milisegundos). Una de éstas tareas es el planificador de tareas del Nivel de Reloj. niveles de prioridad NIVEL INTERRUPCIÓN Generalmente las rutinas de atención tendrán una prioridad superior al resto de las tareas puesto que lo que se pretende es que la interrupción sea atendida rápidamente. Las interrupciones pueden tener la misma o distinta prioridad (cada interrupción tendría un nivel de prioridad diferente), dependiendo del sistema. En este último caso, una interrupción puede desalojar a otra. PRIORIDAD prioridad del planificador del Nivel de Reloj niveles de prioridad NIVEL DE RELOJ prioridad del planificador del Nivel Base niveles de prioridad La rutina de atención a la interrupción tiene que tener un código optimizado de forma que consuma el mínimo tiempo posible de CPU, ya que si una rutina de interrupción es llamada muchas veces, puede introducir un retardo importante. NIVEL BASE 193 Nivel de reloj 194 Nivel de reloj Tareas que tiene alguna restricción temporal. Estas tareas se pueden activar de forma periódica. Por ejemplo, muestreo de señales, de control,… Encontraríamos tareas estrictas y no estrictas. El planificador de tareas decide qué tarea debe ejecutarse en función de las prioridades. Dentro de las tareas de reloj, encontramos dos tipos: Cíclicas. Son las tareas que precisan una sincronización de elevada precisión con el mundo exterior. Su mayor requisito es que su activación periódica se ejecute con precisión. De esta forma, cada tarea cíclica tiene asociado un periodo exacto para su activación. Se puede retardar la ejecución dentro de su periodo si es bloqueada por otra más prioritaria. El planificador sólo considera que cada tarea se tiene que ejecutar dentro de cada periodo. cíclicas de retardo. activación 195 Nivel de reloj 196 Nivel de reloj Ej. Tres tareas cíclicas A(20, 5), B(40, 5), C(80, 5): Ej. Tres tareas cíclicas A(20, 1), B(40, 6), C(80, 25) con planificación basada en prioridad sin desalojo: Prioridad A > B > C : Prioridad A > B > C : 20 C B 20 A C Prioridad C > A > B : B C A B A 197 198 33 Nivel de reloj Nivel de reloj Ej. Tres tareas cíclicas A(20, 1), B(40, 6), C(80, 25) con planificación basada en prioridad con desalojo: De retardo. La tarea tiene asociado un retardo desde que termina hasta que comienza. El planificador tiene que disponer una lista de tareas teniendo en cuenta cuando cada tarea debe activarse. Cuando una tarea pasa de ready a delayed, entonces el watch-dog del sistema sabe cuando debe despertarse. Puede ser o no periódica ya que la activación se va a producir un intervalo después de su finalización. De esta forma, desde una ejecución a la siguiente se garantiza un tiempo durante el cual no se va a ejecutar la tarea. Los instantes de activación no tiene porque estar desplazados el mismo tiempo. Controlando este tiempo de retardo, la tarea se puede convertir en periódica. Prioridad A > B > C : 20 C B A activación 199 200 Nivel base No hay ninguna restricción temporal, sólo se requiere que se ejecuten correctamente. Se activan bajo demanda en vez de a intervalos de tiempo predeterminados. Hay varias formas de planificar tareas de nivel base. Una de ellas sería utilizar un algoritmo round-robin con rodajas de tiempo. Otros planificadores: FIFO (First In, First Out), SJF (Shortest Job First).... Gestión de tareas La mayoría de los sistemas de tiempo real emplean estrategias de prioridad incluso para las tareas de nivel base. La prioridad puede ser fija (con el inconveniente de determinar las prioridades correctas para una operación satisfactoria), o dinámica (que permitiría la adaptación a circunstancias particulares). La asignación dinámica de prioridades puede realizarse mediante un planificador de alto nivel, o bien ad-hoc desde las propias tareas. 201 Gestión de tareas 202 Estados de las tareas Hay tareas que pueden estar o no activas, con prioridades o no, que tenemos que ejecutar con un esquema periódico o con retardo. El módulo gestor de tareas es el encargado de realizar todo esto. Sus funciones básicas son: Conocer el estado de cada tarea Planificar la asignación de tiempo de CPU a cada tarea Iniciar Existente Preparada Terminar Crear Activar Desactivar Realizar el cambio de contexto entre tareas DESPACHADOR (Dispatcher) Suspendida Destruir No existente Suspender Desactivar 203 Ejecutar Suspender PLANIFICADOR (Scheduler) En ejecución 204 34 Estados de las tareas Estados de las tareas En ejecución (active, running): la tarea tiene el control de la CPU y del resto de los recursos que necesite. Normalmente será la tarea con mayor prioridad de las que están preparadas para ejecutarse. Existente (existent, dormant, off): El sistema operativo es consciente de la existencia de esta tarea pero aún no se le ha asignado una prioridad y no se ha convertido en ejecutable. Preparada (ready, runnable, on): Puede haber más de una tarea en este estado. La tarea no ha de estar esperando por ningún recurso. No existente (non-existent, terminated): El sistema operativo no es consciente de la existencia de estas tareas, a pesar de que pueden estar residiendo en la memoria del computador. Es decir, el código está en memoria esperando a que se le asigne una zona de datos. Suspendida (suspended, locked out, waiting, delayed): La ejecución de las tareas que se encuentran en este estado ha sido suspendida porque la tarea ha solicitado algún recurso que no se encuentra disponible, la tarea está esperando alguna señal de la planta (p.ej. una entrada de un convertidor A/D), la tarea está esperando el transcurso del tiempo. en general se puede decir que están a la espera de un evento. 205 Descriptor de tareas 206 Planificador Descriptor de tareas (TD), descriptor de procesos (PD), bloque de control de tareas (TCB)... El planificador (scheduler) se activa en los siguientes casos: Identificación de la tarea (ID). Interrupción del reloj de tiempo real y cualquier interrupción que indique la finalización de una petición de E/S. Prioridad de cada tarea(P). Suspensión de una tarea debido a: Estado de la tarea retardo de tarea. Zona de almacenamiento del entorno volátil. finalización de tarea. solicitud de una transferencia E/S. Puntero a siguiente tarea en la lista de tareas En ambos casos, el planificador busca la tarea más prioritaria entre las que estén listas para ejecución. 207 208 35