Sistemas operativos TEMA 1 INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS Tema 1 Introducción a los sistemas operativos 1. Definición de sistema operativo 2. Visión histórica 3. Los servicios que ofrece el sistema operativo 1. DEFINICIÓN DE SISTEMA OPERATIVO Es muy difícil dar una definición exacta y precisa del término sistema operativo, por lo que nos será más sencillo formular esta definición a partir de la descripción de las funciones que éste cumple. En el esquema superior de un computador, la capa inferior corresponde al hardware, sobre ésta encontramos justamente el sistema operativo, cuyas funciones son: Gestión eficiente de los recursos del sistema: El sistema operativo controla el acceso eficiente a los recursos del computador: memoria, tiempo en la CPU, dispositivos, etc, se encarga de trabajos de protección y de utilización eficiente del sistema. Es decir, el sistema operativo reparte el tiempo de CPU entre los diferentes programas que concurren en un mismo computador (incluso de distintos usuarios), consigue una ejecución concurrente, protege el acceso a memoria y coordina el acceso a dispositivos compartidos e incluso a los no compartidos. Presentación a los usuarios de una máquina virtual mucho más sencilla de utilizar: El sistema operativo proporciona un entorno de trabajo al usuario y a los programas de aplicación que permite utilizar el computador de forma más fácil e intuitiva; es decir, se proporciona una máquina virtual mucho más fácil de entender y utilizar por el hecho de que oculta la complejidad del hardware. Como vemos en el dibujo superior, por encima del núcleo del sistema operativo tenemos el software de sistema (shell, compiladores, editores…) y por encima de éste, el software de aplicación (hojas de cálculo, editores de texto…). 2. VISIÓN HISTÓRICA Los sistemas operativos y la evolución del hardware corren íntimamente relacionadas en su evolución histórica. Es lógico pensarlo así, esencialmente porque los sistemas operativos han evolucionado en función de los cambios tecnológicos y a la vez intentaban sacar el mayor provecho de estas nuevas tecnologías. 2 · Sistemas operativos Vamos a estudiar someramente la evolución de estos sistemas operativos divididos en 4 generaciones: Primera generación: Los primeros ordenadores. Solo se contaba en los momentos iniciales con el hardware del ordenador. Se trataba de válvulas de vacío que obligaban a fabricar aparatos mostruosamente grandes que se “alimentaban” de cintas de papel y tarjetas perforadas y cuyo uso estaba reservado al ámbito militar para efectuar cálculos matemáticos como trayectorias balísticas y tablas de senos y cosenos. En esta época se consideraba que no existía sistema operativo y que el usuario interaccionaba directamente con el computador. Segunda generación: Con la aparición del transistor en 1950 ya se podían fabricar computadores más pequeños, pero muy caros y solo al alcance de unas pocas empresas grandes. Aparecen nuevos dispositivos de entrada/salida, unidades de almacenamiento (disco y cintas magnéticas), aparece el primer software (ensambladores, librerías de funciones matemáticas…) Su modo de funcionamiento era simple. Se cargaba todo el código del “sistema operativo” y después se realizaban cálculos en lote. Se le pedía un cálculo y cuando terminaba otro y después otros, todos de forma lineal; esto hacía perder mucho tiempo de CPU, dado que aunque las CPUs eran lentas, más lentos aún eran los dispositivos que introducían la información a evaluar (cintas perforadas). Para intentar reducir el tiempo que estaba parada la CPU apareció el trabajo off-line, con lo que se intentaban solapar las operaciones de entrada/salida con la ejecución de programas distintos que se habían cargado en la CPU a través de una unidad de cinta auxiliar. Luego se idearon técnicas como el almacenamiento en memoria intermedia, que consistía en solapar las operaciones de entrada/salida de un programa con las de cálculo de ese mismo programa. Luego llegó la gestión de colas que permitían solapar las operaciones de entrada/salida de distintos trabajos mientras se ejecutaban otras operaciones. Tercera generación: Surge a mediados de los años 60 y se basa en circuitos integrados. Son mucho más pequeñas que las anteriores y más rápidas, apareciendo mecanismos de interrupciones, protección, etc, y además se desarrollan periféricos más rápidos y efectivos, terminales remotos (acceso a bancos de datos) y se estandarizan lenguajes de alto nivel ya existentes como Fortran, algol, cobol. Aparece una nueva forma de trabajar para la CPU que es la programación concurrente o multiprogramada, donde se solapan tiempos de mayor entrada/salida con tiempos de cálculo intensivo de la CPU para distintas operaciones. Cuantos más programas solapemos tendremos un mayor grado de multiprogramación; debido a esto varios usuarios pueden conectarse a una misma CPU y “compartirla”, pero para evitar que un usuario la colapse demasiado tiempo seguido, el programa de un usuario determinado deja de ejecutarse cuando su programa ha finalizado, cuando se programa una operación esencialmente de entrada/salida o cuando ha pasado un quantum de ejecución (tiempo determinado) para que todos tengan acceso a esa CPU, de esta forma los diferentes usuarios conectados al sistema progresan en sus operaciones concurrentemente y cada uno de ellos tiene la impresión de que es el único que utiliza el sistema. Generación Tecnología Programación Vel. operación Dispositivos Ámbito de uso Primera 1925-1957 Válvulas de vacío Lenguaje binario o máquina Milisegundos Cintas de papel Tarjetas perforadas Militar Segunda 1958-1964 Transistores Memorias de ferrita Fortran, Cobol, Pascal, Ensambladores Microsegundos Lectores de tarjetas Impresoras Unidades de disco Grandes corporaciones Gobiernos Universidades Tercera 1964-1974 Circuitos integrados Sistemas con multiprogramación Nanosegundos Periféricos más rápidos y efectivos Empresas medianas Cuarta LSI/VLSI Interconexión con Redes Doméstico Sistemas operativos · 3 1974 – ¿? Memoria de semiconductores o burbujas bases de memoria Cuarta generación hasta hoy día: En 1974 se inicia la era de los computadores personales con la integración a gran escala; y seguramente su aportación más importante es la red de computadores (internet e intranet). Aparecen los sistemas operativos en red y distribuidos que permiten utilizar recursos de máquinas remotas (discos, impresoras…) y aparece también el sistema operativo en tiempo real (sistemas que deben procesar muchos datos en un tiempo limitado y lo más rápidamente posible). 3. LOS SERVICIOS QUE OFRECE EL SISTEMA OPERATIVO El sistema operativo ofrece una gran variedad de servicios, pero que podemos agrupar en dos bloques: Llamadas al sistema: Ofrecen soluciones básicas para poder utilizar todos los recursos de forma correcta y controlada. Encontramos entre estas llamadas al sistema: gestión de procesos, señalización de procesos, gestión de dispositivos de entrada/salida, gestión del sistema de archivos y funciones de tiempo. Intérprete de órdenes: Es el encargado de traducir y comunicar al sistema operativo lo que quiere hacer el usuario del sistema. Este programa reconoce un conjunto limitado de órdenes que, básicamente, permiten al usuario acceder, modificar, crear y proteger la información. 4 · Sistemas operativos TEMA 2 EL SISTEMA OPERATIVO: UNA MÁQUINA VITUAL Tema 2 Una introducción a la máquina virtual 1. UNA INTRODUCCIÓN A LA MÁQUINA VIRTUAL 1.1. El concepto de máquina virtual El sistema operativo define una máquina virtual sobre la cual los usuarios pueden trabajar con una mayor comodidad de la que lo harían si trabajasen directamente sobre los elementos que componen el sistema; es decir el sistema operativo esconde el hardware mediante una capa de software que tiene dos objetivos principales: La abstracción del sistema como un hardware determinado para tener una visión global y sencilla de la máquina. Proporcionar un funcionamiento nuevo y seguro, adaptado al usuario que accede al sistema y además protegido para evitar corromper el funcionamiento correcto de la máquina. Las capas del sistema que aparecen las podemos observar en la figura adjunta y se definen en 3 niveles: Capas del sistema operativo Intérprete de órdenes: Aquí se introducen órdenes directamente que son interpretadas por el sistema de manera inmediata. Herramientas del sistema y librerías: Accesibles mediante el intérprete de ordenes y también para el programador como herramientas de apoyo a su labor. Núcleo del sistema operativo: Contiene rutinas de gestión del sistema más relacionadas con el hardware (del que se encuentra muy muy cerca). El código que se ejecuta a este nivel lo hace con privilegios de sistema, es decir como supervisor. 1.2. El concepto de sesión de trabajo En los sistemas multiusuario para poder utilizar el sistema se tiene que iniciar una sesión de trabajo. Es un proceso de identificación que tiene 3 fases: Entrada en el sistema: o fase de login, donde se le pide al usuario su nombre de usuario (username) y su contraseña correspondiente (password). Cuando se introduce el hombre el sistema operativo lo busca en la lista de usuarios autorizados (primer nivel de seguridad), cuando lo encuentra compara la contraseña con la introducida por el usuario (segundo nivel) y si coinciden le asigna el entorno propio de ese usuario y se inicia la sesión de trabajo. Uso del sistema: El usuario interacciona con el intérprete de órdenes, uso normal del sistema. Salida del sistema: o logout, el usuario declara que ha terminado la sesión de trabajo y desea abandonarla, de esta forma se liberan los recursos que se le tiene asignados y, por otro lado al cerrar la sesión ningún otro usuario puede usar la cuenta abierta (la cuenta es el entorno de trabajo y los privilegios a que tiene acceso por ser ese usuario). Entrada en el sistema 1. Una introducción a la máquina virtual 2. Fases de ejecución de un programa 3. Los espacios de direcciones de un proceso 4. Los mecanismos de entrada al sistema operativo: traps, excepciones e interrupciones Sistemas operativos · 5 1.3. El intérprete de órdenes Esquema de funcionamiento sacar prompt leer orden Mientras la orden no sea “salir” hacer ejecutar la orden sacar prompt leer nueva orden fin mientras El intérprete de órdenes es un programa que se encarga de interpretar y comunicar al sistema lo que el usuario desea hacer; este programa al iniciar una sesión de trabajo se carga en memoria y no se descarga hasta que no se acaba la sesión de trabajo. Su esquema de funcionamiento lo vemos en la figura lateral. De este modo de funcionamiento se desprende que su misión es solicitar de manera cíclica, mediante un bucle una orden para ejecutar hasta que tenga lugar la de salir. Existen dos tipos de órdenes: las internas (forman parte del núcleo del sistema operativo) y las externas (se suelen encontrar en un directorio concreto del sistema de ficheros o sigue una ruta concreta –path-). La forma en la que el sistema indica que ya está preparado para recibir órdenes es escribir en pantalla una combinación de caracteres o prompt, que por ejemplo en MS-Dos es C:\>. En la actualidad el intérprete de órdenes ha quedado un poco disimulado por el uso de entornos gráficos basados en ventanas. Prompt en Ms-Dos y en Windows XP Las modalidades de ejecución del intérprete de órdenes son: Primer plano: o foreground: el intérprete de órdenes pasa el control al programa que corresponde y no lo recupera hasta que finaliza por completo la ejecución. Segundo plano: o background: se inicia la ejecución de la orden solicitada pero concurrentemente se sigue ejecutando el intérprete de órdenes. Diferidas: Se admite la ejecución de órdenes en un momento especificado previamente. Por ejemplo los cálculos intensivos o largas simulaciones durante la noche. El intérprete de órdenes también se puede utilizar como lenguaje de programación a través de ficheros de texto (por ejemplo los batch en Ms-Dos) en los que se pueden guardar una secuencia de órdenes; y también se puede utilizar como personalizador del entorno de trabajo, ya que cuando un usuario se identifica (logon) el intérprete de órdenes habilita por defecto las herramientas, programas, path, etc, que el usuario ha decidido previamente. 1.4. El sistema operativo desde el punto de vista del programador El usuario se fija en su sistema operativo sobre todo en los programas que tiene accesibles y en el intérprete de órdenes. En cambio el programador ve los recursos físicos del sistema y no tanto la capa de software colocada encima. El programador suele utilizar principalmente las librerías del sistema (procedimientos de uso común que se añade a sus aplicaciones y que hace disminuir el código en toda programación) y las llamadas al sistema. 6 · Sistemas operativos 2. LAS FASES DE EJECUCIÓN DE UN PROGRAMA 2.1. El concepto de proceso Procesos: Programas en ejecución Existen varias definiciones distintas para el concepto de proceso, pero una de las más aceptadas es la de “un programa en ejecución”. Un programa es la descripción detallada para la resolución de un problema de manera general, en abstracto; mientras que un proceso es la aplicación en concreto del programa a un caso particular y en un momento determinado, con la asignación de unos recursos concretos. Un proceso determinado es único e irrepetible, mientras que un programa se puede ejecutar múltiples veces. Por ejemplo, en la figura lateral se pueden observar parte de los procesos abiertos en este momento por mi sistema; cada proceso en ejecución es irrepetible, porque cerrar un proceso y volverlo a abrir ya supone una instancia diferente del mismo. 2.2. El lenguaje informático Los programadores escriben sus programas en ficheros de texto que siguen unas normas de un lenguaje informático determinado, y que se intenta acercar en la medida de lo posible al lenguaje natural (más cercano a las personas de habla inglesa que al resto de lenguajes naturales). Este lenguaje de programación es una convención de sentencias y estructuras de datos que pueden convertirse en lenguaje máquina (lenguaje que entiende el computador) y que se pueden ejecutar. Clasificaremos los distintos tipos de lenguajes en: Lenguaje de alto nivel: Escribimos las instrucciones en un formato texto muy cercano al lenguaje natural, aunque con reglas más estrictas. Este lenguaje luego será traducido por un compilador para pasarlo a lenguaje máquina. Lenguaje ensamblador: Para simplificar el uso directo del lenguaje máquina (lenguaje binario de 1 y 0) se han diseñado lenguajes en los que cada instrucción máquina se corresponde con una instrucción en un lenguaje mnemotécnico más inteligible. Aun así el lenguaje ensamblador cada día es más sofisticado y casi resulta inviable programarlo a mano, por lo que esta tarea tan compleja queda reservado a compiladores y optimizadotes de código. Código máquina: Las únicas instrucciones que reconoce un procesador son éstas, en código binario. 2.3. El proceso de creación de un programa ejecutable En la página siguiente puede observarse una gráfica con todo el proceso de creación de un programa ejecutable. Vamos a estudiar los distintos pasos: Edición En esta fase el programador escribe todas las instrucciones del programa en un fichero fuente que no es más que un fichero de texto. Para ello puede utilizar cualquier editor de texto más o menos complejo para su edición. Hay editores específicos para lenguajes determinados que ayudan a programar, asignando colores distintos a diferentes partes del programa o comandos, etc. Compilación (Ensamblaje) Es el proceso de traducción de un programa escrito en alto nivel a un equivalente en lenguaje máquina; es decir, desde un fichero fuente, obtenemos un fichero objeto. A partir de esta fase, independientemente del lenguaje de alto nivel utilizado C, Java, etc, los ficheros objetos ya estarán escritos en un mismo “idioma”. Pero el programa no está terminado ya que el fichero objeto no suele ser único sino que está dividido normalmente en varias partes que facilitan su Sistemas operativos · 7 manipulación y edición y porque los programas más pequeños de uso común como cálculos matemáticos y operaciones de entrada/salida se encuentran generalmente compilados y programados en las librerías. Proceso de creación de un programa ejecutable Montaje (Enlazado) El montaje es el proceso que se encarga de agrupar todos los módulos correspondientes a un programa, así como las librerías (si hacen falta) para construir un único programa ejecutable. También se llaman enlazadores porque su principal misión es resolver las direcciones externas entre los módulos, así como con la librería; con lo que obtenemos un programa ejecutable definitivo con un espacio lógico de direcciones que está completamente construido. El encabezamiento de este fichero contiene información relativa al procedimiento que hay que seguir para cargarlo en memoria. Carga La principal tarea del cargador es buscar los recursos que solicita el programa ejecutable (incluida la memoria). Así que el cargador adapta las direcciones lógicas de los programas ejecutables a direcciones físicas de la memoria real del sistema. Además transfiere el control a la primera instrucción del programa y marca el proceso como preparado. Ejecución (Depuración) Los programas depuradores controlan la ejecución del programa, por lo que el programador puede fijar los puntos de detención de la ejecución y estudiar si se va desarrollando la ejecución adecuadamente, así pueden limitarse los puntos en los que aparecen errores e intentar subsanarlos. 2.4. Las librerías de sistema En el proceso de creación del programa ejecutable hemos hecho varias veces referencia a las librerías del sistema; éstas son un conjunto de módulos objeto organizadas de manera adecuada y suministradas por el fabricante del sistema operativo. Contienen las denominaciones de procedimientos, parámetros necesarios y formas en las que pasarlos para poder disponer de muchos más métodos para ejecutar nuestros programas ejecutables y, de paso, ahorrarnos líneas de código. Existen tres grandes familias de librerías a nuestra disposición: Librerías estándar: Son las más genéricas y sus áreas más comunes son la gestión de ficheros, dispositivos de entrada/salida, funciones matemáticas, manejo de ficheros, etc. Librerías especializadas: Son opcionales y se añaden al sistema para resolver problemas específicos, por ejemplo librerías de cálculos matemáticos complejos, de cálculos estadísticos y se suelen emplear específicamente en entornos industriales. Librerías de llamadas al sistema operativo: Adaptamos nuestro programa ejecutable al formato de llamadas, parámetros, etc del sistema operativo que utilizamos. Eso sí, solo podremos hacer esas llamadas que el sistema operativo que se está ejecutando nos permita. 8 · Sistemas operativos 3. LOS ESPACIOS DE DIRECCIONES EN UN PROCESO Un programa reubicable es aquel que se puede ejecutar en diferentes direcciones que le son asignadas por el cargador del programa. Así podemos distinguir dos tipos de direcciones de memoria: Direcciones lógicas: Son las referenciadas por el programador. Son por tanto referencias locales y no reales. Direcciones físicas: Son las direcciones asignadas en tiempo de carga, es decir, el lugar donde realmente residirá el proceso durante su ejecución. Obviamente la propiedad de la reubicación hace referencia a la capacidad de cargar y después ejecutar un programa en cualquier posición de la memoria. Ello hace necesario un mecanismo de traducción entre las direcciones lógicas y físicas en la memoria y se puede llevar a cabo a través de dos sistemas: Reubicación estática: Se realiza antes o justo durante la carga del programa en la memoria. La línea inicial de carga suele reverenciarse como 0 y a partir de ella el resto de direcciones del programa serán direcciones lógicas relativas a esta línea inicial. Tras ser cargado ya no hay diferencia entre direcciones lógicas y físicas; el problema de esto es que si el proceso por alguna razón tiene que salir del espacio físico asignado, o bien vuelve luego exactamente al mismo sitio o tiene que volverse a cargar desde el principio. A causa de este problema este formato de reubicación se limita a “ciertos” sistemas operativos. Reubicación dinámica: La transformación entre las direcciones físicas y lógicas se efectúa en tiempo de ejecución y no en tiempo de carga. Se puede por tanto cargar en cualquier posición de memoria y una parte del hardware es el encargado de hacer el trabajo de reubicación. Para ello suele utilizar los registros de base más la dirección lógica y resulta completamente transparente para el programador. 4. LOS MECANISMOS DE ENTRADA AL SISTEMA OPERATIVO: TRAPS EXCEPCIONES E INTERRUPCIONES Vamos a estudiar en este punto la llamada a los servicios del sistema operativo en tiempo de ejecución. Lo que se hace es ceder el control de la ejecución a una parte del código que se ejecuta en el núcleo (kernel o corazón) del sistema operativo. Es como si hiciéramos una llamada a cualquier subrutina de una aplicación pero ejecutada en modo sistema; porque así tiene una mayor capacidad para gestionar sus propios recursos del sistema, el código está mucho más depurado y resulta más fiable, ya que es código del propio sistema operativo el que estamos utilizando. 4.1. Las rutinas de servicio del sistema operativo Las rutinas de servicio son funcionalidades que puede ejecutar el procesador y que hace lleva a cabo al hardware operaciones realmente complicadas pero con la seguridad de que es el propio sistema operativo el que las realiza y gestiona. Los servicios dependen un poco de cada sistema operativo pero en general podemos dividirlos en 7 grandes grupos: Gestión de procesos: Creación, eliminación, suspensión, reanimación y asignación de procesos, así como la cuota de CPU asignado a cada uno de ellos. Señalización entre procesos: En un sistema multitarea el nexo natural de unión entre las aplicaciones en ejecución, evidentemente es el propio sistema operativo. Señales, semáforos, regiones críticas… Gestión de dispositivos de entrada/salida: Crear, abrir y cerrar canales de entrada/salida, así como leerlos y escribirlos. Gestión directa de los recursos del sistema: Dependen un poco de cada sistema operativo, pero suele contar entre otros con la gestión de la memoria del sistema. Sistemas operativos · 9 Gestión del sistema de archivos: Crear, eliminar, copiar, cambiar de nombre ficheros y directorios. Protecciones: Determina si un servicio determinado se puede dar a un usuario o no (por ejemplo compartir una impresora o leer un determinado fichero o escribir sobre él), es más utilizado por los administradores de sistemas que por los usuarios. Funciones de tiempo: Referencias temporales de los procesos. 4.2. Los mecanismos de acceso a los servicios Estudiamos aquí como podemos, voluntariamente desde una aplicación, acceder a estos servicios del sistema operativo, es decir, como transferir el control de la CPU al sistema operativo (pasamos de modo usuario normalmente, a modo supervisor con el consecuente cambio de privilegios). Hay dos métodos: Puntos fijos: Se realizan las llamadas de manera idéntica a cualquier procedimiento de usuario, porque entendemos que en el núcleo del sistema operativo están así codificadas, como meros procedimientos. El problema es que como el núcleo del sistema operativo se carga en memoria al arrancar el PC, puede haber problemas para vincular los nombres simbólicos de los procedimientos con los nombres reales que encontrarán los procesos durante su actividad; podemos intentar solucionarlo de dos formas: o Se activa el procedimiento a través de su dirección real, lo cual representa muchos inconvenientes. o Se utilizan nombres simbólicos y se suministran las direcciones para las librerías del sistema. Ambas soluciones son sensibles a los cambios de ubicación en los puntos de entrada al sistema operativo, lo que no garantiza la portabilidad de las aplicaciones durante las distintas versiones de un mismo sistema operativo o a causa de diferentes configuraciones de software. Llamadas al supervisor o traps: Se transfiere el control indirectamente para la utilización de una tabla de direcciones de las rutinas de servicio. En el momento de la carga el memoria del núcleo del sistema operativo, las entradas de la tabla de direcciones de las rutinas de servicio se llenan con las direcciones reales de cada rutina. Si no varían estos números, estaremos garantizando la portabilidad del código a las diferentes versiones del sistema operativo o a diferentes configuraciones hardware. Las llamadas al supervisor son instrucciones hardware especializadas en transferir el control de la ejecución al sistema operativo (es una clase concreta dentro del conjunto general de interrupciones del sistema). 4.3. Los mecanismos básicos de transferencia El control de la ejecución de un código al sistema operativo puede transferirse por voluntad del proceso en ejecución o por cualquier otro motivo. Existen tres mecanismos básicos para llevarlo a cabo: Llamadas explícitas al sistema operativo: Son puntos concretos del código de usuario donde se realiza una transferencia explícita del control al sistema operativo, son acontecimientos síncronos. Excepciones: Son roturas de secuencias no previstas pero provocadas directamente por la ejecución del mismo código del usuario en curso (por ejemplo, la división por cero). La interrupción llama a una rutina de servicio y se intenta solucionar el problema o se informa al sistema operativo de la situación. No podemos generalizar la situación posterior pero a veces no queda más remedio que suspender la ejecución del proceso. Interrupciones: Son sucesos asíncronos totalmente ajenos al proceso en ejecución, pero su resultado es la transferencia del control de la CPU al sistema operativo. 10 · Sistemas operativos 4.4. El núcleo del sistema operativo El núcleo (o kérnel) es la parte del sistema operativo formada por un conjunto de procesos ubicados siempre en la memoria principal. Se encarga de gestionar el hardware y de implementar las funciones más básicas de la gestión de procesos, memoria y entrada/salida. Entre ellas destaca la gestión de las interrupciones, excepciones y llamadas al supervisor. El núcleo en sí es solo una pequeña parte del sistema operativo, pero es el código más utilizado y por ello se carga en la memoria, no todo el sistema operativo tiene que cargarse en la memoria. En la tabla siguiente se muestra un diagrama de las posibles formas en las que se puede ejecutar el núcleo. Gestión de interrupciones Es conveniente observar que el núcleo del sistema operativo es la parte del software que guarda una relación mucho más directa y estrecha con el hardware y por ello este núcleo utiliza todas las herramientas que el hardware puede ofrecernos. Los mecanismos más significativos que utiliza son los siguientes: Ejecución en modo privilegiado: Le permite acceder a todos los registros de control de la máquina y ejecutar instrucciones que en modo normal provocarían una excepción. Podemos pasar de modo usuario a sistema (como ya sabemos haciendo una llamada a sistema –por un código en ejecución- o por una interrupción de hardware), de sistema a usuario (cuando ha acabado la rutina de servicio del núcleo que habíamos invocado) o de sistema a sistema (cuando se atiende una interrupción mientras se está ejecutando código del sistema). Jerarquización de interrupciones mediante prioridades: Para poder organizar de manera correcta la urgencia de los servicios que se solicitan al núcleo es imprescindible que se organicen por prioridades. En la tabla lateral podemos observar un ejemplo típico de organización de interrupciones. Prioridad en interrupciones Sistemas operativos · 11 TEMA 3 LA GESTIÓN DE LA MEMORIA Tema 3 La gestión de la memoria 1. Las funciones de traducción de direcciones 2. Los modelos de gestión de la memoria de asignación contigua 3. Los modelos de gestión de la memoria de asignación no contigua 4. La memoria virtual La gestión de la memoria se encarga básicamente de asignar la memoria física del sistema, que es finita, a los procesos que lo soliciten. Ningún programa que se encuentre en el interior del sistema puede ser ejecutado si no se le ha asignado memoria principal, de ahí su importancia. Además la gestión de la memoria debe satisfacer dos operaciones que pueden ser conflictivas: la protección del espacio de direcciones, es decir, que ningún proceso pudiese acceder al espacio de memoria asignado a otro proceso; y la compartición de memoria, permitiendo espacios comunes de memoria para todos los procesos. 1. LAS FUNCIONES DE TRADUCCIÓN DE DIRECCIONES Recordemos que en un proceso el espacio lógico del mismo es la estructura que contiene el código, los datos y la pila; mientras que el espacio físico hace referencia a las direcciones de la memoria donde se carga el espacio lógico señalado anteriormente, es decir, es el espacio real. También debemos distinguir lo que es el espacio de direcciones del procesador que son las direcciones que genera el procesador a la hora de acceder al código, datos y pila del proceso; este espacio además limita el tamaño de la memoria, ya que determina el número de posiciones de memoria a las que puede remitir el procesador. Podemos encontrar los siguientes esquemas de reubicación en memoria Reubicaciones estáticas Reubicación estática en tiempo de compilación/montaje: Si es posible saber de antemano en qué lugar de la memoria física se cargará el proceso, el compilador genera un código ejecutable que contiene esas direcciones físicas reales. Así, las direcciones que genera el procesador, coinciden con las direcciones físicas. En el ejemplo de la figura lateral, podemos observar en el ejecutable (a) que como sabemos que el proceso se va a cargar a partir de la dirección @64000 de la memoria física, el compilador se encarga ya de asociar a la variable la dirección @64066 (ya que ocupaba la dirección @66 en el código fuente). Lo malo es esta reubicación es que si quisiéramos que el proceso se cargase a partir de otra dirección de memoria distinta habría que compilar de nuevo todo el programa. Reubicación estática en tiempo de carga: Al no ser posible conocer por anticipado el espacio real de memoria donde se cargará el proceso, el compilador solo puede contener direcciones lógicas; pero en tiempo de carga sí se conoce la dirección y se produce la traducción de direcciones lógicas a físicas. Al igual que antes, el proceso lanza direcciones físicas, con la diferencia de que si quisiéramos situar el proceso en otro lugar de la memoria, no sería necesario volver a compilar todo el proceso, aunque sí es necesario volver a cargarlo. Por tanto, es igual al modelo anterior, con la diferencia d que el fichero ejecutable contiene direcciones lógicas, en el ejemplo, observamos el ejecutable (b). Reubicación dinámica en tiempo de ejecución: Implica que la correspondencia entre el espacio de direcciones lógico y el espacio de direcciones físico se lleva a cabo 12 · Sistemas operativos Reubicación dinámica en tiempo de ejecución; ello permite que un proceso tenga la posibilidad de moverse durante su ejecución de un lugar de la memoria a otro. También es necesario un hardware añadido para traducir las direcciones, generalmente consiste en registros de base que indican la dirección de la memoria a partir de la cual se ha cargado el proceso de memoria (puede observarse en la figura de la tabla lateral). Como notas positivas decir que permite llevar a cabo una gestión dinámica de los procesos en memoria, permite reubicarla en otro lugar de la misma y compactarla, permite el intercambio, la partición del espacio lógico del proceso, etc. 2. LOS MODELOS DE GESTIÓN DE LA MEMORIA DE ASIGNACIÓN CONTIGUA En un principio no existía gestión alguna de la memoria, es decir, el usuario tenía el control completo de la misma; ello era viable en sistemas que se dedican a entornos de trabajo muy específicos; pero completamente inviable en sistemas complejos y compartidos por una gran cantidad de usuarios. La primera estructuración de la memoria consiste en dividirla en dos partes, una partición destinada al sistema operativo y protegida para evitar accesos no controlados, y otra dedicada al usuario para que pudiese ejecutar los programas. Debido a que generalmente solo se cargaba un proceso en la memoria, la compartición no tenía demasiado sentido y la reubicación solía ser estática, dado que el proceso no dejaba la memoria hasta que no finalizaba su ejecución. 2.1. Las particiones fijas El primer esquema que se propone para la gestión en la memoria en sistemas multiprogramados es la partición de la memoria en varios fragmentos (generalmente de tamaños diferentes): Estas particiones se crean cuando la máquina arranca y no se puede modificar su tamaño. Una vez definidas las particiones el sistema necesita guardar la información asociada a cada una de ellas, como es el inicio, tamaño y estado de ocupación de la misma, y ello lo hace en la tabla de descripción de particiones (véase tabla lateral). Cuando se quiere ejecutar un proceso que no está en memoria, se busca una partición libre y se carga el proceso allí; existen dos políticas de asignación de memoria: Cuando hay particiones de memoria libres, se puede asignar a la partición que se ajusta mejor al tamaño del proceso (recordemos que las particiones suelen ser de tamaños distintos) o se le asigna la primera partición libre en la que quepa el proceso. La primera opción optimiza lógicamente el uso del sistema, pero es más lenta que la segunda. Cuando no hay particiones de memoria libres, se puede dejar el proceso pendiente de ejecución y esperar que se libere otro proceso que deje una partición de memoria lo bastante grande o se puede obligar a un proceso que está en memoria a dejar libre su partición para que se puede cargar el nuevo proceso. Esto último es lo que se denomina intercambio del proceso o swapping. A la hora de volverlo a cargar si la ubicación es estática se hará en el mismo espacio de memoria que ocupaba anteriormente o se podrá realizar en una partición cualquiera (en la que quepa, claro está). Queda claro que en este sistema de particiones fijas, el número de particiones determina el número máximo de procesos que se pueden tener Particiones fijas Sistemas operativos · 13 cargados en memoria simultáneamente y además el tamaño de una partición determina también el espacio lógico máximo que puede ocupar un proceso; además un proceso solo podrá ejecutarse si queda libre alguna partición de memoria con el espacio suficiente para poder cargar en ella el código del proceso. El principal problema en este tipo de sistemas es la fragmentación interna, sucede cuando un proceso no utiliza todo el espacio de partición de memoria para él asignado y por tanto, está haciendo mal uso de la misma. Por ejemplo, si un proceso ocupa 10K y se carga en una partición de 15K, se forma una fragmentación interna dentro de la partición de 5K. Este sistema de particiones fijas es uno de los métodos más sencillos que soportan la multiprogramación dado que los algoritmos de gestión y asignación de la memoria son sencillos, rápidos y fiables. Es un método adecuado para los sistemas en los que se conoce la carga de trabajo y no se produce desarrollo de nuevos programas, aunque la mayor parte de sus inconvenientes proviene de la falta de flexibilidad para adaptarse a las necesidades variables del sistema. 2.2. Las particiones variables Debido a los problemas que generan las particiones fijas, se implementan las particiones variables para intentar solucionarlos. En este tipo las particiones variables se crean y se destruyen en función de las variaciones en la carga del sistema. Particiones variables Por ejemplo, cuando se intenta ejecutar un proceso y hay que asignarle espacio en memoria, es necesario primero saber qué parte de la memoria está ocupada (inicialmente existe una única partición libre con toda la memoria de la que pueden disponer los programas de usuario); hecho esto se intenta encontrar un espacio contiguo de memoria donde quepa el proceso y una vez encontrado se crea una partición introduciendo la dirección de inicio (base), el tamaño y el estado en una tabla de descripción de particiones como podemos observar en la figura lateral. Cuando un proceso finaliza o se retira de la memoria, el sistema libera la partición e invalida la entrada correspondiente en la tabla de particiones del sistema. En cuanto a las políticas de asignación de la memoria, existen varias de ellas como puede ser asignar la primera partición libre donde el proceso pueda caber, la menor partición de la memoria libre donde cabe el proceso –ajustando el tamaño lo máximo posible- etc. Las ventajas que aporta el sistema de particiones variables son: Queda prácticamente eliminada la fragmentación interna. Se puede asignar la totalidad de la memoria a un único proceso y así ejecutar procesos que requieren mucha memoria. Podemos soportar y adaptarnos al crecimiento dinámico de un proceso. Por el contrario, también existen desventajas: Aparece lo que se llama fragmentacion externa, dado que a medida que finaliza la ejecución de diferentes procesos, la memoria queda fraccionada de 14 · Sistemas operativos modo que el espacio libre no es contiguo. Por ejemplo, han finalizado dos procesos no contiguos de 300 y 100 k respectivamente, pero necesitamos cargar en memoria un proceso de 350 K. Hay espacio suficiente, pero al no ser contiguo no lo podemos utilizar. Esto se intenta resolver con la compactación de la memoria, consistente en reorganizar los procesos residentes en memoria, de modo que todo el espacio libre quede en una única partición. Esto requiere que los programas cargados en memoria sean de reubicación dinámica (lógicamente, dado que de lo contrario no los podríamos desplazar en tiempo de ejecución), y por otro lado la compactación es un proceso muy costoso en esfuerzo del procesador y en tiempo. Requiere protocolos de asignación y mantenimiento de las particiones más complejos que consumen más tiempo y espacio del sistema operativo. 3. LOS MODELOS DE GESTIÓN DE LA MEMORIA DE ASIGNACIÓN NO CONTIGUA Hasta ahora, los modelos que hemos estudiado eran de asignación contigua, es decir, cada proceso ocupaba un espacio contiguo de memoria, no había saltos ni particiones entre los procesos. Pero para una mejor gestión de la memoria y una mejor optimización de la misma caben dos nuevos modelos, la segmentación y la paginación. 3.1. La segmentación En la segmentación, el espacio lógico del proceso se parte en unidades lógicas denominados segmentos. En la tabla lateral podemos observar como un mismo proceso ha sido dividido en 4 segmentos: el código, los datos, la pila y el segmento común, cada uno de ellos con un tamaño distinto. Cada segmento se compila por Proceso de segmentación separado y cualquier objeto dentro del segmento se identifica por el número de segmento al que pertenece y el desplazamiento relativo dentro del segmento. Se crea una tabla de segmentos y también una tabla de descriptores de segmento asociada a cada proceso para poder luego acceder a su espacio real en memoria. Así para acceder a la subturina2 se identifica con el segmento al que pertenece y se busca en la tabla de descriptores de segmento (segmento común: 3) y la posición relativa que ocupa en ese segmento (100) nos dará la dirección física real del mismo. Además observamos un proceso de filtrado o búsqueda de errores. Si el segmento al que se quiere acceder entra dentro de la longitud permitida o no. Además podemos añadir en esta tabla otros campos para permitir el tipo de acceso: permitido, compartido, etc. Podemos compartir código de manera sencilla y útil. Por ejemplo si varios usuarios utilizan un mismo editor, en lugar de cargar el código del editor varias veces en memoria se carga una única vez y se comparte entre todos, eso sí, la parte del editor que sea específica de un usuario en concreto no se podrá compartir, y se tendrá que duplicar. Como desventaja, decir que la tabla de descriptores generalmente no se implementa en hardware, sino en software,, lo que hace la gestión de segmentos algo más lenta. Sistemas operativos · 15 3.2. La paginación La paginación es gestionada por el sistema operativo y lo que hace es dividir el espacio lógico del proceso en partes de tamaño fijo, que reciben el nombre de páginas. La memoria principal también se dividirá en trozos del tamaño de una página, denominados frames. Por ejemplo, partimos de un sistema de de 10 Kbytes de memoria. El tamaño de una página es de 1 K; de modo que la memoria posee un total de 10 frames. El espacio lógico del proceso que queremos añadir a la memoria tiene un máximo de 4 K, por lo tanto cuatro páginas, y ocupará un máximo de 4 frames. Supongamos que nos encontramos con un proceso que ocupa 2536 Kbytes, en cuyo caso ocupará completamente las dos primeras páginas y una porción de la tercera, dejando la cuarta completamente vacía. El sistema busca 3 frames libres de la memoria (que no tienen que ser contiguos) y establece la correspondencia entre página y frame, en la llamada tabla de páginas. Cada entrada de la tabla de páginas tiene asociado un bit que se conoce como bit de validez V, que indica si la página contiene información válida para el proceso o no. En nuestro ejemplo las tres primeras páginas tendrán bit V a 1, y la última estará a 0. Paginación de un proceso en 4 páginas Igualmente, para saber si un frame de memoria está libre o no, el sistema dispone de una tabla de frames donde se indica su estado y otra serie de bits para indicar información referente a punteros, protección, compartición, etc. Esta tabla de páginas se implementa por software y por lo tanto necesita dos accesos a memoria: un primer acceso para examinar la tabla de páginas y obtener la dirección física del objeto que se quiere consultar y un segundo acceso para llegar al objeto en cuestión. Este sistema al igual que los anteriores tiene ventajas y desventajas: Se evita la fragmentación externa, aunque observamos que se genera cierta fragmentación interna. Normalmente el último frame de un proceso no estará lleno del todo –es muy difícil que coincida el tamaño de un proceso con múltiplos de 1024 bits-. Eso sí, el problema es mucho menor que en particiones variables dado que el espacio despreciado siempre será muy pequeño. Cuanto menor sean las páginas/frames en tamaño, menos espacio se desperdiciará, pero a cambio aumentará en memoria el sistema para las tablas de páginas de todos los procesos, lo que lo puede hacer algo lento. Con bits de acceso controlado en la tabla de páginas, podemos conseguir un acceso controlado, compartido, permitido, etc., a cada página, pero como la división en páginas se hace de manera mecánica –por tamaño- y no por tipo de datos (código, pila, etc) se complica en modo extremo. Habría que conseguir que los datos, el código, y demás, ocuparan las mismas páginas lógicas y que no se produjesen mezclas, lo cual complica bastante la protección de la información. 16 · Sistemas operativos 4. LA MEMORIA VIRTUAL Hasta ahora todos los procesos estudiados se cargaban completamente en la memoria para poder ejecutarse. Con la memoria virtual lo que vamos a pretender es que un proceso solo se cargue parcialmente en la memoria, mientras que se mantiene una imagen del espacio lógico del proceso en la memoria secundario, preferentemente el disco duro. El sistema operativo toma la decisión de qué parte del proceso resulta imprescindible cargar para su ejecución y lo hará en función de la carga y disponibilidad. La memoria se puede implementar igual que la segmentación o paginación en memoria que hemos visto en los puntos anteriores, la única diferencia es que hay que añadir algunos campos más en cada entrada de la tabla de páginas o segmentos para indicar si la página o el segmento se encuentra cargado o no en la memoria principal, estos campos son el bit de presencia P que indica si la página está cargada en la memoria física (bit 1) o no está cargada (0); lo habitual es que no haya ninguna página cargada y lo vayan haciendo a medida que se van necesitando. Previamente a esto se estudia el bit de validez V, para saber si la dirección que se ha dado es válida. También es necesaria una tabla de páginas de la memoria secundaria para poder ser consultada. En fin, el proceso sería como sigue y como podemos observar en la figura siguiente: Carga de páginas en la memoria virtual Se observa el bit de validez para ver si la dirección es correcta. Se observa el bit de presencia P para saber si está o no cargada en memoria. En caso de que no está cargada debe buscar en el disco y en la tabla de páginas de memoria secundaria para cargarla. La política de carga de páginas determina si debemos cargar las páginas solo cuando se han solicitado y no se encuentran en la memoria principal (bajo demanda), o también se puede adelantar trabajo aprovechando la localización espacial de los programas. Para cargarla es necesario que existan frames libres, de no ser así dependiendo de la política de sustitución de páginas habrá que desalojar alguna de la memoria principal para subir la que necesitamos en ese momento. Sistemas operativos · 17 TEMA 4 LOS DISPOSITIVOS DE ENTRADA/SALIDA Tema 4 Los dispositivos de entrada/salida 1. El concepto de dispositivo de entrada/salida 2. Las características de los dispositivos 3. Los dispositivos reales 4. La independencia de los dispositivos 5. La gestión de dispositivos en UNIX 1. EL CONCEPTO DE DISPOSITIVO DE ENTRADA/SALIDA El concepto de dispositivo de entrada/salida se asocia al de periférico, como por ejemplo los teclados, pantallas, discos o módems. Pero un dispositivo de entrada/salida es algo más, es un objeto gestionado por el sistema operativo sobre el cual los procesos pueden realizar operaciones de lectura/escritura con la finalidad de obtener, almacenar, mostrar o transferir información. Dispositivos de entrada/salida El sistema operativo se encarga de gestionar tales dispositivos a través de: Herramientas: Que desarrollan políticas de acceso en función de las necesidades de los usuarios. Interfaz de acceso que es independiente de las particularidades de cada uno de los dispositivos pero a la vez permite explotar todas las características del mismo. Entorno portable que permite que las aplicaciones trabajen con los dispositivos sin tener que modificarlas. 2. LAS CARACTERÍSTICAS DE LOS DISPOSITIVOS Los dispositivos presentan características singulares que el sistema operativo tiene que gestionar de diferentes modos. Los hemos clasificado ateniéndonos a sus características físicas, de acceso y de control, pero es una clasificación más de las muchas que podríamos haber hecho, debido a la pluralidad de los dispositivos. Debido a ello también hemos de tener en cuenta que accedemos a los dispositivos y los manipulamos con operaciones distintas y con parámetros distintos; además estos dispositivos según el tipo pueden producir distintos resultados e incluso dar errores diferentes frente a situaciones análogas. El sistema operativo es el encargado de independizarnos de todos estos casos diferentes mediante una gestión de la máquina virtual que hace que toda esta variedad sea transparente para el usuario. Las características físicas de los dispositivos son las siguientes: Extraíbles o fijos: Hay dispositivos fijos como los discos duros y otros extraíbles como los discos compactos. Los dispositivos extraíbles necesitan un tratamiento adicional por parte del sistema y de los usuarios, ya que debe asignarse el dispositivo a un único usuario al mismo tiempo y hay que informar al sistema del hecho de que se ha insertado o se procederá a extraer el dispositivo extraíble. Capacidad de almacenamiento: Se caracteriza por la geometría u organización del espacio de almacenamiento en sectores, pistas y caras (discos duros), en bloques (cintas), etc.; también reconocer que el tipo de almacenamiento puede ser permanente o temporal: permanente como un disco duro donde queda la información hasta que explícitamente se borra o temporal como puede ser un fichero temporal (por ejemplo la cola de impresión de un documento) que cuando acaba su función deja de existir y se borra. Tipo de unidad de transferencia: Encontramos los dispositivos que transfieren caracteres como puede ser un terminal o dispositivos que transfieren bloques de caracteres como es un disco que transfiere como mínimo 512 caracteres. Velocidad de transferencia; Suele ser menor que la del procesador, por lo que éste aplica técnicas que intenten reducir el tiempo de espera: 18 · Sistemas operativos Buffering o almacenamiento en memoria intermedia: tiene la función de suavizar la diferencia de velocidades entre el procesador y los dispositivos, almacenando en una memoria entre el dispositivo y el procesador los datos que se van a procesar (por ejemplo en la grabación de un CD). o Spoolin o gestión de colas: Permite que la entrada/salida de un proceso tenga un paso intermedio por dispositivos de gran capacidad de almacenamiento como pueden ser el disco duro. Por ejemplo a la hora de imprimir un documento relativamente largo se perderá el control del PC hasta que se haya impreso completamente; como la impresora es más lenta esto supone un tiempo de desfase enorme, por lo que se gestiona la impresión mediante la cola de impresión. Tipo de codificación de la información: Tiene que seguir unas normas de codificación que dependan de cada dispositivo. Por ejemplo un terminal orientado a caracteres debe seguir el código ASCII de 7 u 8 bits. Estructura de la información: Condiciona esto las operaciones que podemos llevar a cabo con dicha información. Por ejemplo podemos compilar un fichero fuente, y podemos ejecutar un fichero ejecutable, pero no al revés. o Entre las características de acceso se encuentran: Acceso a dispositivos de entrada/salida: Los dispositivos pueden ser de entrada, salida o ambos a la vez, y en función de ello tendrá sentido o no una determinada operación de acceso. Por ejemplo podemos hacer una salida por impresora, pero es absurdo hacerla sobre un ratón. Acceso compartido o exclusivo: Un ejemplo de dispositivo exclusivo es la impresora. Es evidente que no se puede imprimir más de un documento al mismo tiempo; pero el propio sistema se encarga de proporcionar mecanismos de exclusión como los semáforos o la compartición que solucionan el problema. Acceso secuencial, indexado o directo: Según el dispositivo se tendrá acceso secuencial (una cinta), directo (disco duro) o indexado. Acceso síncrono o asíncrono: La mayor parte de los dispositivos tienen un acceso síncrono, es decir, si la información no se encuentra disponible en ese momento, el proceso que efectúa el acceso esperará hasta que lo esté. Si es asíncrono, en cambio, el proceso informa que no hay información y continúa su ejecución (también puede advertir cuando los datos ya estén disponibles). Otras características: Como son el activar o no el modo de echo, por ejemplo lo habitual es ver que se introducen los caracteres que se introducen por el teclado (echo activado) excepto en las password donde no se ve (echo desactivado). También es útil el modo de edición cooked en el que determinados caracteres de entrada se interpretan como órdenes y no como simples caracteres. Y por último destacan las características de control: Características del controlador: El sistema controla los periféricos mediante una serie de puertos que pertenecen al hardware denominado controlador. Pueden ser muy distintos, por DMA, por interrupciones, por encuesta… El sistema operativo esconde a los usuarios todas estas particularidades. Lenguajes de órdenes: Algunos dispositivos utilizan lenguajes de control específicos como son el lenguaje PostScript para determinadas impresoras 3. LOS DISPOSITIVOS REALES Un dispositivo real es un dispositivo que existe realmente y que es la combinación de diferentes elementos de hardware y software, como se puede observar en la imagen de la página siguiente. Los dispositivos pueden ser físicos, lógicos o virtuales. Un dispositivo físico es un dispositivo que existe físicamente y está formado por el periférico + su Sistemas operativos · 19 hardware de control (parte física) y por el software que lo gestiona (driver). Tipos de estos dispositivos son las impresoras, teclados, pero también los discos duros, la memoria RAM, etc. Estos dispositivos son los más evidentes en un sistema y rápidamente los identificamos como tales. No sucede lo mismo con un dispositivo lógico, pues no existe físicamente sino como el resultado de un software del sistema que crea este dispositivo, están formados únicamente por su driver. Un ejemplo puede ser la unidad virtual de DVD/CD que puede crear el software de grabación Nero para poder montar imágenes de grabaciones. Otro dispositivo lógico es la ventana, que combina cuatro dispositivos físicos para existir: pantalla, memoria, teclado y ratón; pero en sí la ventana es un área gráfica almacenada en la memoria que se representa total o parcialmente sobre una ventana. Dispositivos físicos, lógicos y virtuales 4. LA INDEPENDENCIA DE LOS DISPOSITIVOS Como programadores nos interesa poder realizar programas portables, que se adapten a diferentes situaciones y esta necesidad por supuesto, incluye el entorno de entrada/salida donde se van a ejecutar. Para ello debemos independizar el código de los programas con respecto a los dispositivos que se van a utilizar. El primer paso para esta independización es la utilización de los dispositivos virtuales. Un dispositivo virtual es un dispositivo que, a priori, no se encuentra asociado a ningún dispositivo real, lo utilizará el programador pero sin saber en principio, sobre qué dispositivo en concreto se van a llevar a cabo las operaciones que se especifican en él. Más tarde, en la ejecución del programa, tendrá lugar la asociación entre el dispositivo real y el virtual de dos formas: Asociación implícita: El sistema y el proceso que ha iniciado la ejecución del programa se encarga de efectuar la asociación. En general, los dispositivos virtuales asociados implícitamente se denominan dispositivos estándar y contamos con el estándar de entrada, el de salida y el de error. Asociación explícita: Se efectúa la asociación por el mismo programa durante la ejecución. A partir del momento en que se ha realizado la asociación, el programa redirigirá todas las entrada/salidas del dispositivo mediante el dispositivo virtual. Pero no solo con los dispositivos virtuales conseguimos la independencia de los programas, ya que si se requieren operaciones específicas de los dispositivos reales con los cuales están asociados, no hemos ganado nada. Necesitamos operaciones uniformes es decir, conocer cuales son las operaciones más utilizadas por los programas y las más comunes en los dispositivos, para poder redefinirlas como un conjunto de operaciones independientes de las características de los dispositivos. Encontramos dos grandes tipos de operaciones: las básicas de acceso y las de control. Operaciones básicas de acceso: Son dos: leer y escribir y nos tienen que permitir acceder a la información con independencia del dispositivo. Para que sean realmente compatibles con todos los dispositivos, debe incluir estas operaciones el tipo de acceso, el tipo y la estructura de datos que se tiene que transferir. Las operaciones de acceso leer y escribir quedan así definidas: o o Estado = leer (disp, buff, cont). Estado = escribir (disp, buff, cont). 20 · Sistemas operativos Donde disp es el dispositivo virtual sobre el que se quiere trabajar, buff es la variable desde la cual se extraerán o se introducirán los datos y cont es el número de datos que hay que transferir. Si no añadimos más parámetros queda claro que accedemos al mismo tipo de datos, generalmente es el más elemental y es el byte. El motivo de ello es que no se pueden reconocer todos los tipos posibles, por más tipos que el sistema reconociese, siempre surgirían aplicaciones con tipos nuevos; además el Sistema operativo a través de librerías y herramientas puede hacer que posteriormente esta política se adapte a situaciones individuales. Operaciones básicas de control: Hay 3 que son posicionar, abrir y cerrar. La operación posiciona nos permite efectuar accesos directos dentro de la información en aquellos dispositivos que lo soporten. En general el orden que seguimos en una sesión de acceso a un dispositivo es: o o o Así o o o Asociar un dispositivo real a uno virtual de manera explícita. Inicializar las estructuras de datos internos del sistema operativo que sean necesarias para realizar los accesos (secuencial y directo). Verificar los derechos de acceso que tiene el proceso sobre el dispositivo. las operaciones de control podrían ser: Estado = posicionar (disp, pos). Disp = abrir (nombre, op). Estado = cerrar (disp). Donde op es el tipo de acceso que se quiere efectuar y nombre es el nombre del dispositivo real. 5. LA GESTIÓN DE DISPOSITIVOS EN UNIX UNIX como todos los sistemas operativos, ofrece una visión de los dispositivos que hace que las características propias de cada uno sean transparentes para el usuario. El sistema UNIX reconoce dos tipos de dispositivos: Dispositivos de bloques: Como los discos, cintas y ficheros; permiten el acceso directo y utiliza una memoria de cachés de bloques con la finalidad de mejorar el rendimiento en sus accesos. Dispositivos de caracteres: Como terminales, impresoras, ratones… los accesos se realizan de manera secuencial Internamente los diferentes tipos de dispositivos se identifican por su tipo básico (bloques o caracteres), por un número denominado major (tipo de dispositivo) y un número minor (dispositivo distinto dentro del mismo tipo major). UNIX gestiona los dispositivos mediante drivers asociados a cada tipo de dispositivo como se muestra en la figura de la página siguiente. El usuario que quiere dirigirse a un dispositivo real de UNIX debe hacerlo por medio del sistema de ficheros mediante el cual se nombran ficheros especiales. Por ejemplo en UNIX la impresora es /dev/1p0 y el disco duro /dev/hda. UNIX utiliza los file descriptor que son números locales en el proceso que forma parte den entorno de ejecución de un dispositivo. Encontramos 3 file descriptors: el 0 o estándar de entrada, el 1 o estándar de salida y el 2 o estándar de error. En la página siguiente también podemos apreciar las entrada/salida estándar de los procesos. Un caso especial de dispositivo lo constituyen las pipes. Una pipe es un dispositivo lógico destinado a comunicar procesos. Su funcionamiento es el de una cola de caracteres con una longitud fija en la que los procesos pueden escribir y Disp. bloques / caracteres Sistemas operativos · 21 Esquema de las entradas/salidas en UNIX Entrada/salida estándar de los procesos leer. Cuando se intentar leer sobre una pipe vacía, se quedará bloqueado hasta que otro proceso escriba en ella los caracteres suficientes como para que se pueda efectuar una lectura. Otro caso es intentar escribir sobre una pipe llena; en este caso hasta que otro proceso no haya leído los suficientes caracteres no se producirá la escritura. Existen las named pipe que es un dispositivo normal que forma parte del sistema de ficheros y su comportamiento es idéntico al de cualquier dispositivo. Y luego la pipe que es un dispositivo que se crea en el momento en que un proceso lo abre y se destruye cuando se cierra el último proceso que lo tiene abierto. Una vez creada, los diferentes procesos lo asocian a un dispositivo virtual de manera implícita, por ello las pipes no tienen asociado ningún nombre que aparezca en el sistema de ficheros. El tema más interesante de UNIX desde el punto de vista de la entrada/salida es la facilidad con que se pueden redireccionar los files descriptors estándares de los procesos desde el shell. El símbolo < se utiliza para redireccionar el estándar de entrada, y el símbolo > el de salida; así por ejemplo: $ps > nombre guarda el estado de los procesos actuales en un fichero nombre. Otro ejemplo más: $ tail < nombre muestra por la salida estándar actual las diez últimas líneas del fichero nombre. Otra función muy interesante es el posible encadenamiento de la ejecución de más de un programa mediante pipes. Esto se consigue redireccionando la salida estándar de un proceso y la entrada estándar de otro a una pipe; para indicar la conexión de dos procesos se utiliza el símbolo |. Por ejemplo: $ps –aux | sort +2 -3 | tail > nombra Envía al fichero nombre la información de los nueve procesos que tuilizan durante más tiempo la CPU. 22 · Sistemas operativos TEMA 5 EL SISTEMA DE FICHEROS 1. EL CONCEPTO DE FICHERO El sistema de ficheros es el encargado de gestionar el conjunto de ficheros contenidos en un mismo dispositivo de almacenamiento. Se encarga de proporcionar un espacio de nombres y un control de acceso a todos los dispositivos. Ahora bien, un fichero es un dispositivo lógico formado por una agrupación lógica de información almacenada en un dispositivo físico, por ejemplo, un disco, una cinta o la memoria, que se puede manipular como un todo. La información que incluye tiene en común un conjunto de propiedades que la caracterizan, y son: La información relativa al contenido del fichero y a su modificación: es decir, tamaño, fecha de creación, última modificación, tipo de información. La ubicación de esta información dentro del dispositivo de almacenamiento, así el sistema podrá localizar el fichero dentro del dispositivo. La accesibilidad del fichero: es decir la protección del mismo, quien puede realizar operaciones, escribirlo, borrarlo, quien es el usuario propietario… Así pues podemos encontrar ficheros ejecutables, que el sistema operativo es capaz de ejecutar (dado que tienen una estructura y un encabezado que así se lo indican), otras veces abren un programa gráfico si el fichero es una imagen… y un caso especial es el del fichero directorio, que es una lista de parejas de nombres y números cuya finalidad es dar nombre a los ficheros y permitir su localización. 2. EL ESPACIO DE NOMBRES En este apartado vamos a tratar dos aspectos relacionados con el espacio de nombres: La estructura de los espacios de nombres y las operaciones que podemos realizar sobre la misma. ¿Por qué necesitamos nombres? Esta bien claro que dentro del sistema se identifica cada objeto (entendiendo por objeto cualquier fichero, directorio, dispositivo, proceso, etc.) por un nombre interno que consiste en direcciones de memoria; pero claro a los usuarios nos resulta más fácil recordar que el dispositivo con el nombre impresora es la impresora, que recordar un número que suele tener varias cifras. Una de las funciones del sistema de ficheros es facilitar una traducción entre unos nombres a los que estamos acostumbrados los usuarios y los nombres internos del sistema. El sistema operativo además implementa la Estructura de un fichero directorio función de traducción de los nombres mediante estructuras de datos llamadas directorios, los cuales a su vez se implementan mediante ficheros en los que el sistema almacena la estructura de datos, consistente en una tabla que relaciona los nombres que contiene el directorio con los nombres internos del sistema operativo (ver figura lateral). Un sistema operativo puede ofrecer una visión de un único espacio de nombres para todos los objetos y el sistema de ficheros (así lo hace UNIX que no diferencia distintas unidades) o un espacio de nombres separado e independiente para cada tipo de objeto (Como Ms-Dos que el disquete y su contenido es A:, el disco duro C:, LPT1, etc). Tema 5 El sistema de ficheros 1. El concepto de fichero 2. El espacio de nombres 3. La protección 4. El sistema de ficheros y la protección en UNIX Sistemas operativos · 23 La estructura de los espacios de nombres puede ser de tres tipos: espacio lineal, espacio jerárquico en árbol y espacio jerárquico en grafo. Distintas estructuras de los espacios de nombres La estructura en espacio lineal es la más sencilla, ya que solo tiene una dimensión en la que todos los nombres se encuentran en el mismo nivel (como vemos en la tabla lateral. Esta estructura es adecuada para espacios con pocos objetos, en sistema monousario ya que se trata de una estructura que permite tener una buena visión de conjunto y conseguir una localización rápida de cualquier objeto; pero resulta inadecuado cuando el número de objetos comienza a ser elevado, ya que el significado que para el usuario puedan tener los nombres que nos vemos obligados a poner a los dispositivos para no repetir, puede llegar a quedar desvirtuado. Por ello y porque nos gustaría poder agrupar los ficheros por tipos, trabajos o cualquier otra clasificación que nos resulte útil, tenemos las estructuras jerárquicas. Para conseguir un espacio jerárquico tratamos los directorios como objetos con un nombre y así pueden formar parte de los objetos que agrupan otros directorios. La estructura jerárquica en árbol parte de un directorio raíz del cual cuelgan otros directorios y/o ficheros que configurarán las hojas del árbol. El nombre absoluto es el formado por la ruta que va desde la raíz, pasando por los diferentes subdirectorios, hasta llegar al fichero (separado cada nivel por el carácter delimitador /). Tenemos un directorio inicial y un directorio de trabajo. Los nombres relativos serán entonces los que forma el recorrido que va desde el directorio de trabajo hasta el fichero en cuestión. Así varios ficheros pueden tener el mismo nombre relativo siempre que se obtengan a partir de diferentes directorios de trabajo, pues los nombres absolutos no son iguales. Con este sistema ya podemos clasificar los ficheros en directorios distintos, pero el problema de esta estructura es que no permite compartir de una manera sencilla ficheros entre diferentes usuarios ni permite moverse por el árbol de directorios en sentido ascendente, cambiando el directorio de trabajo. Para ello tenemos la estructura jerárquica en grafo dirigido el cual permite direccional al mismo tiempo con los nombres absolutos y relativos. Así podemos acceder a un objeto mediante más de un nombre. Por ejemplo, en UNIX y en DOS en cada directorio figuran dos nombres especiales: “.” y “..”. El primero hace referencia al directorio que lo contiene, así podemos referenciar el directorio en el que nos encontramos sin conocer su nombre absoluto, y el “..” es el nombre del padre del directorio, que nos permite ascender por la estructura. Así solucionamos todos los problemas que nos habíamos planteado anteriormente, pero eso sí, el inconveniente que surgirá después (ya lo veremos) es la gestión de estos grafos. Las operaciones que podemos realizar sobre estas estructuras son varias: 24 · Sistemas operativos Operaciones de manipulación del sistema de ficheros: o Crear_SF: El sistema de ficheros se incluye dentro de un sistema de almacenamiento. Por ello debemos dotar a cada dispositivo de un conjunto de estructuras de datos que permitan definir los ficheros, los directorios, etc, y así poder transportarlo sin pérdida de información en el caso que se tratase de un dispositivo extraíble. o Montar_SF: En algunos sistemas operativo s, se tiene que informar al mismo antes de acceder a una nuevo SF, tanto si se acaba de introducir un nuevo volumen (CD) como si se ha creado una estructura nueva, esto se requiere para poder seguir ofreciendo un único espacio de nombres. o Desmontar_SF: Al contrario de lo anterior, por ejemplo cuando extraemos un CD. o Verificar_SF: Podemos verificar las estructuras de datos que configuran un SF; por norma general esto no suele formar parte del núcleo del sistema operativo, sino que son aplicaciones y utilidades. Operaciones de manipulación de directorios: o Localizar_objeto: Consiste en que dado un nombre absoluto o relativo, se localiza el objeto al que hace referencia. No es en sí una operación única, sino que suele formar parte de multitud de llamadas y procedimientos. o Modificar_nombre: Se localiza el directorio, se verifica que el nombre es único y se cambia por el nuevo. o Crear_nombre: Se crea un nombre para un objeto en concreto. Si el objeto ya existía, se establece un enlace nuevo entre el nombre y el objeto al que hace referencia (enlace físico). Es lo contrario a un enlace simbólico que es un enlace a otro fichero que contiene el nombre del objeto (vínculo). La existencia de estos dos tipos de enlaces hace que la operación localizar_objeto se vea obligada a saber de qué tipo de enlace se trata y a actuar en consecuencia. Si el objeto no existía y se crea a la vez que el nombre, debe proporcionar además del fichero en sí, su tabla de traducción y los enlaces “.” y “..” pertinentes. o Destruir_nombre: Se localiza un directorio y se destruye. Si el fichero al que hacía referencia este nombre no contiene ningún otro, el sistema operativo lo destruirá y liberará los recursos que ocupo. Los enlaces físicos son fácilmente detectables, pero los simbólicos no tanto, ya que estos enlaces simbólicos pueden no estar en el mismo sistema o no hallarse activos en ese momento; como resultado de ello puede ser que aparezcan enlaces simbólicos que no señalen a ningún lado o, lo que es peor aún, que apunten a un fichero que ha reutilizado un nombre antiguo. Para evitar esto, el sistema detecta los enlaces simbólicos que ya no apuntan a ningún fichero mediante la operación localizar_objeto. o Ver_nombres: Permite consultar el contenido de un directorio. Operaciones de manipulación del directorio de trabajo: o Cambio_de_directorio o Directorio_actual: estas dos operaciones nos indican en qué directorio estamos trabajando y cómo podemos cambiar el directorio de trabajo. 3. LA PROTECCIÓN Un sistema operativo gestiona los recursos y objetos sobre los cuales se pueden desarrolla acciones. Pero es responsabilidad del sistema operativo: Sistemas operativos · 25 La protección, es decir, impedir que la dinámica de unos usuarios se vea afectada por las acciones de otros. Esta protección se consigue autorizando o denegando los accesos que en un instante concreto soliciten los usuarios. La seguridad del sistema, es decir, impedir que la dinámica de unos usuarios se vea afectada por las acciones de agentes externos al sistema computador. Se centra esta responsabilidad en cuestiones como la identificación de los usuarios. El concepto que vamos a estudiar en este apartado es el primero, el de protección, y aunque protección y seguridad sean conceptos muy relacionados, supondremos que los usuarios han sido debidamente identificados y por tanto son en realidad quienes dicen que son. El concepto de protección se refiere al control que lleva a cabo el sistema operativo sobre las diferentes formas con que cuentan los usuarios para acceder a los objetos del sistema. Hay dos casos en los que no se necesita de protección: Cuando solo tenemos un usuario no hace falta control a los archivos, puesto que todos pertenecen al mismo usuario y cuando el sistema es multiusuario pero todos tienen acceso a todos los ficheros, pues no hay concepto de propiedad de fichero. Los elementos que intervienen en la protección son: Los objetos: son aquellos elementos que gestiona el sistema operativo sobre los cuales se pueden efectuar diferentes acciones: ficheros, dispositivos, directorios, procesos… Los dominios son los diferentes agentes activos del sistema que pueden actuar sobre un objeto. Así son dominios los procesos que pertenecen a un mismo usuario o a un mismo grupo de usuarios. Los derechos son acciones sobre un objeto permitidas por parte de un dominio: leer, escribir, ejecutar, crear, destruir… La matriz de accesos está formada por tantas filas como dominios hay y tantas columnas como objetos, como vemos en el ejemplo inferior: Matriz de accesos En esta matriz anterior, el dominio Juan tiene derechos de lectura y escritura sobre el objeto ejercicio1.txt, pero en cambio solo tiene derechos de lectura sobre el objeto ejercicioB.txt. En la tabla observamos que los dominios aparecen como objetos debido a que un dominio también es un objeto sobre el 26 · Sistemas operativos cual se pueden llevar a cabo acciones. Por ejemplo, el dominio Profesores tiene derecho de cambio sobre el dominio Estudiantes. La matriz de accesos es una buena forma de contemplar las relaciones que se establecen entre los diferentes elementos, pero no es un buen instrumento de trabajo, dado que su volumen crece rápidamente a medida que aumenta el número de usuarios y ficheros del sistema y además el número de objetos sobre los cuales un usuario tiene derechos es pequeño en comparación con el número total de objetos del sistema, con lo que la matriz se convertirá en una estructura muy grande pero con la mayor parte de las celdas vacías. Para evitar este problema existen tres tipos de protecciones: las listas de control de acceso, las listas de capabilities y los modelos combinados: Listas de control de acceso son listas de parejas (derechos, dominio) asociadas a un objeto y que se obtienen como resultado de dividir la matriz de accesos es columnas y eliminar todas las celdas vacías (se ahorra espacio y la estructura es más dinámica). Así las listas de control de acceso se encuentran asociadas a los objetos y no a los procesos, de manera que cada vez que un proceso quiere llevar a cabo un acceso a un objeto hay que verificar si tiene derecho en la LCA. En una sesión de trabajo con un fichero se verifican los derechos en cada operación de lectura o escritura que se quiere realizar, hecho que representará un volumen de sobrecarga importante para el sistema; aunque a cambio de ello como todos los derechos de los dominios sobre un objeto se encuentran en un mismo lugar, la asignación y revocación de los mismos por parte del propietario se efectuará con total sencillez. Listas de capabilities (capacidades): Es una lista de parejas (objeto, derechos) asociada a un dominio, cada una de las cuales se denomina capability. Las listas resultan de dividir la matriz en fijas y eliminar las celdas vacías; así estas listas son estructuras de datos asociadas a procesos (que pueden estar protegidas en un espacio determinado o cifradas para evitar modificar su contenido). Así cuando un proceso accede a un objeto, tiene que presentarle al sistema la capability que le da derecho a llevarlo a cabo. El sistema verifica su validez y efectúa el acceso, es más ágil que el método anterior; en cambio la modificación de los derechos no es trivial, ya que éstos se encuentran repartidos por todos los procesos del sistema y para revocar un derecho hay que recorrer todos los procesos para eliminar las capabilities asociadas. Modelos combinados: Para solucionar los problemas que presentan uno y otro modelo, los sistemas utilizan una combinación de los dos que permite disfrutar de las ventajas de los dos esquemas. Las LCA se utilizan como mecanismo básico y se suelen presentar realizando una reducción de todos los dominios posibles a unos cuantos. Por ejemplo en Unix hay tres grupos de dominio: el propietario, todos los dominios asociados al grupo de trabajo del propietario y el resto de dominios del sistema. El primer acceso a cada objeto se verifica mediante las LCA. Tras haber verificado el derecho, el sistema genera una capability que se utilizará durante la sesión de trabajo; esta capability se asocia al dispositivo virtual que se genera durante la operación abrir y se destruye con la operación cerrar. Así los accesos que se lleven a cabo durante la sesión de trabajo no necesitará acceder a la LCA. Otro sistema es el llamado llave y cerrojo. Cada capability tiene asociado un número denominado llave y los objetos tienen asociada una colección de números denominados cerrojos; para que una capability sea correcta, la llave tiene que coincidir con alguno de los cerrojos que posee el objeto; para revocar el conjunto de derechos el propietario de objeto solo debe eliminar uno o más cerrojos. No es tan flexible como la LCA, pero soluciona parcialmente el problema de tener que recorrer todos los dominios para eliminar las capabilities asociadas. Sistemas operativos · 27 5. EL SISTEMA DE FICHEROS Y LA PROTECCIÓN EN UNIX En Unix encontramos los ficheros ordinarios, los ficheros directorio y los ficheros especiales o dispositivos. El sistema Unix, reconoce un solo tipo de fichero ordinario y lo trata como una secuencia de bytes; a excepción del fichero ejecutable, que se caracteriza evidentemente por poderse ejecutar y por tener una marca especial en los primeros bytes del archivo. El no encontrar esta secuencia especial de bytes asegura que no es un ejecutable, pero el encontrarla no asegura que sea ejecutable, pues puede ser un fichero ordinario que casualmente tiene la secuencia de bytes ejecutable. LCA de Unix Recordemos que el sistema de ficheros es de grafo dirigido organizado en directorios y a pesar de que cada dispositivo contiene un sistema de ficheros con su propia estructura de directorios, Unix da una visión del espacio totalmente conectado porque dispone de un Sistema de ficheros permanentemente accesible sobre el cual se irán añadiendo el resto de sistema de ficheros antes de poder acceder a ellos (justo en el momento del montaje). Las protecciones de Unix, se basan en un modelo mixto; un usuario se identifica dentro del sistema mediante un nombre de usuario y contraseña; una vez dentro, todos los procesos asociados tendrán asociados los dominios de usuario y de grupo de usuarios al que pertenezca. En cada momento determinado un usuario solo puede pertenecer a un grupo. Para determinar los derechos de un usuario sobre un fichero, Unix utiliza una LCA con tres dominios: el usuario propietario, los usuarios que pertenecen al mismo grupo que el propietario y por último, cualquier otro usuario y para cada uno prevé tres derechos diferentes: de lectura, de escritura y de ejecución. La visualización de las LCA se realiza mediante tres grupos de caracteres, como se puede observar en la figura lateral. El significado de los derechos r (lectura), w (escritura) y x (ejecución) varía si el fichero es un directorio. En este caso, r permite visualizar los nombres contenidos en el directorio, w permite añadir nombres nuevos al directorio y x hace posible que el usuario utilice el directorio como un directorio de trabajo. 28 · Sistemas operativos TEMA 6 LA GESTIÓN DE PROCESOS 1. EL PROCESO: UNA MIRADA DESDE EL INTERIOR DEL SISTEMA Tema 6 La gestión de procesos Un proceso es básicamente un entorno formado por todos los recursos necesarios para ejecutar programas. Desde el punto de vista del sistema operativo, un proceso es un objeto más que hay que gestionar y al cual hay que dar servicio. 1. El proceso: una mirada desde el interior del sistema 2. El ciclo de vida de un proceso 3. Las excepciones y las señales de software 4. La gestión de procesos en Unix Para gestionar este conjunto de recursos como un todo, el sistema reúne información de éstos en una estructura denominada bloque de control de procesos o PCB. Los campos más importantes que configuran el PCB son: El identificador de proceso: Es el código que identifica de manera biunívoca cada uno de los diferentes procesos que se encuentran en ejecución. El estado del proceso: Indica el estado del proceso en el momento actual dentro de unas posibilidades determinadas: run, ready, wait… El contador del programa: Señala la instrucción que estaba a punto de ejecutarse justo en el momento en que se produce una interrupción. Cuando el proceso pueda continuar, la hará exactamente en este punto. Los registros internos de la CPU: Como son registros utilizados por todos los procesos hay que guardar su valor para retornarlo cuando un proceso continúe de manera que se encuentre en un entorno idéntico a cuando lo abandonó. El estado de la memoria: Cantidad de memoria asignada, lugar donde se encuentra, etc. Contabilidad y estadísticas: Información de valor para los administradores de sistema pero poco útil para el usuario. El estado de los dispositivos de entrada/salida: Los dispositivos asignados, las solicitudes pendientes, ficheros abiertos… El dominio de protección: Dominios a los cuales pertenece el proceso y los derechos asociados. La planificación de la CPU: Información a la forma como el proceso accede al procesador en concurrencia con otros procesos. La ejecución concurrente da al usuario la impresión de que es él solo el que accede a la maquina e interactúa con el sistema de forma apropiada (figura a). Pero si estudiamos con mayor zoom la ejecución concurrente de procesos (figura b) apreciamos una multiplexación del procesador. Para conseguir el efecto de ejecución concurrente se lleva a cabo una conmutación entre los procesos que se reparten el tiempo del procesador. Estas conmutaciones se denominan cambios de contexto. En la figura c observamos un cambio de contexto a mayor escala de tiempo y podemos apreciar que es necesario guardar el estado del procesador en el proceso 1, localizar el proceso 2 y restaurar todo el estado del procesador a como lo dejó el estado 2 cuando lo abandonó para poder dar el efecto de ejecución concurrente de procesos. Ejecución concurrente de procesos Los estados de un proceso existen porque en un sistema monoprocesador solo puede haber un proceso en ejecución y el resto debe esperar su “turno” de entrada en el procesador. Cuando el sistema acaba de crear un proceso, éste se encuentra en el estado inicial Ready. Desde este estado puede evolucionar hacia la finalización del proceso (en la figura de la página siguiente, esquema 2) porque otro proceso provoque la finalización del mismo; o puede ejecutarse, pasar al estado Run porque el gestor le asigna la CPU (3). Sistemas operativos · 29 Desde el estado Run puede evolucionar hacia 3 direcciones: El proceso ejecuta la última línea de código y finaliza (4); el proceso debe esperar un acontecimiento externo, por ejemplo cuando se solicita una operación de entrada/salida, como pulsación de una tecla por parte del usuario (5) y pasa al estado Wait; o el proceso ha superado su cuota máxima de tiempo en la modalidad de tiempo compartido (6) y debe volver al estado ready. Hay que apreciar que cuando un proceso sale del estado run para pasar a ready o wait, se produce un cambio de contexto. Diagrama de estados Desde el estado wait, el proceso puede evolucionar hacia el estado ready, cuando finaliza la operación por la cual estaba esperando (7); o puede finalizar (8) debido a un acontecimiento externo al propio proceso. Para saber qué hace cada uno de los procesos y así poder controlar sus recursos, el sistema operativo mantiene unas colas de procesos en función de su estado. Hay una cola de procesos preparados (ready), otra cola de procesos en espera (wait), pero o hay cola de procesos en ejecución, pues solo puede haber uno cada vez. 2. EL CICLO DE VIDA DE UN PROCESO Los procesos son elementos dinámicos y como tales operan durante un intervalo de tiempo en el que tiene lugar: La creación del proceso La destrucción del proceso La herencia entre procesos La sincronización y el estado de finalización en la destrucción de procesos. Los cambios en el entorno de ejecución 2.1. La creación del proceso La creación de un proceso nuevo es el resultado de la ejecución de una llamada al sistema del tipo crear_proceso, que es invocada, como todas las llamadas, por un proceso ya existente. Debe contener los siguientes elementos: La memoria donde residirán el programa, el código y los datos. El punto de entrada desde donde se ejecutará el programa que contenga la memoria El entorno de entrada/salida con el cual el proceso se comunica con el exterior. Los atributos relacionados con los dominios de protección. Elementos de la creación de un entorno de ejecución La especificación de estos elementos puede realizarse de manera explícita con los parámetros de la llamada al sistema con que se crea el proceso; o de manera implícita haciendo que tome valores por defecto; el sistema operativo combina las dos alternativas con unos parámetros específicos de llamada y otros valores se dejan por defecto. Tras haber creado el proceso, el sistema le otorga un nombre mediante el cual se podrá referenciar, este nombre es único para cada proceso durante toda la vida del sistema; ya que si dos procesos están sincronizados A y B; y se destruye B pero se reutiliza inmediatamente su 30 · Sistemas operativos nombre, A estará sincronizado con un proceso no previsto y lo más probable es que no funcione correctamente o, lo que es peor, se abra un agujero de seguridad. 2.2. Destrucción de procesos La destrucción de un proceso comporta la destrucción del entorno que lo constituye y la liberación de los recursos que tenía asignado. Puede tener lugar por alguna de estas situaciones: La ejecución de una llamada destruir_proceso, específica para tal finalidad. Una vez acabada la ejecución del proceso sin incidentes, tiene que ser destruido. El mal funcionamiento del proceso destruido. Si el sistema detecta que le proceso no funciona correctamente y efectúa operaciones no permitidas (como la división por cero, raíces cuadradas de números negativos…), entonces lo destruye. El efecto lateral de la ejecución por parte de otro proceso, de una llamada al sistema diferente a la de destruir_proceso que provoca una excepción sobre el proceso que se destruye. 2.3. La herencia entre procesos Los procesos son creados por el sistema operativo a petición de otros procesos. Esta situación hace que podamos ver los procesos desde el punto de vista de la descendencia, en la que los procesos mantienen relaciones de parentesco como la de padre e hijo. Existen tres tipos de herencia: Compartición: El proceso padre y el hijo comparten un mismo elemento y por ello las manipulaciones de este elemento afectarán a ambos procesos. Copia: El sistema operativo crea los elementos que configuran el entorno del proceso hijo a semejanza de los del padre, pero a partir del momento de la creación evolucionarán en dos entornos distintos y no se verán afectados los cambios de uno en el otro. Valores nuevos: A través de parámetros en la creación, el sistema operativo crea los nuevos elementos del proceso hijo. Ahora estudiaremos como estos tipos de herencia pueden afectar a cada uno de los elementos del entorno del proceso: La memoria y su contenido: Tenemos tres segmentos: el código, los datos y la pila. El código y los datos pueden compartirse; esto es muy útil cuando comparten segmentos del programa cuya información debe ser leída y así evitamos la duplicidad; pero cuando se comparten datos hay que saber que afectarán a ambos procesos, aunque suele usarse para compartir información. La pila jamás puede compartirse, ya que refleja el estado de llamadas a procedimientos y a variables locales de cada proceso. Cuando se utiliza la copia, se copia todos los segmentos, incluida la pila; la pila también se copia cuando se comparte la memoria, por lo que ya hemos avanzado anteriormente. También, por supuesto, se pueden dar valores nuevos. El punto de inicio de ejecución dentro de la memoria: Solo tiene cabida cuando se ha copiado o compartido el código. Puede copiarse, con lo que normalmente el valor de retorno son diferentes de la llamada de creación del sistema; o pueden darse valores nuevos, o bien por parámetros del sistema operativo, o por el fichero ejecutable que definirá el contenido de la memoria. El entorno de entrada/salida: Es independiente de la memoria y tiene 3 modalidades. Puede compartirse con lo que las modificaciones que uno de los procesos realice sobre estas sesiones de trabajo afectarán al otro. Eso sí, las sesiones de acceso a los dispositivos que abran a partir del momento de la creación serán independientes en cada proceso. Pueden copiarse: el hijo encuentra abierta las mismas sesiones de acceso a dispositivos que el padre, pero las acciones no afectarán unos a otros. O por supuesto, valores nuevos. Elementos del entorno del proceso La memoria y su contenido: Compartición: solo puede compartirse segmento y datos, la pila se copia. Copia: de todos los elementos Valores nuevos Punto inicio de la memoria: Copia: con distinto valor de retorno. Valores nuevos. Entorno de entrada/salida: Compartición: la sesión inicial entre ambos se ve afectada, las nuevas son independientes. Copia: Mismos dispositivos pero diferentes sesiones desde el inicio. Valores nuevos. Dominio de protección: Compartición: mismo dominio del mismo usuario, por tanto mismos derechos. Valores nuevos. Sistemas operativos · 31 Dominio de protección: La lista de capabilities puede ser perfectamente copiada, por lo que aquí nos referiremos a los atributos de dominio, que no pueden ser copiados. Encontramos dos modalidades: Compartición: el proceso hijo pertenece al mismo dominio (mismo usuario) que el padre por lo que tiene los mismos derechos; o valores nuevos. 2.4. La sincronización y el estado de finalización en la destrucción de procesos Modalidades de ejecución Existe una necesidad a veces imperiosa de sincronización entre procesos padre e hijo. Cuando hay modalidades de ejecución en primer plano, de fondo y diferidas. Por ejemplo: En primer plano, el intérprete de órdenes espera a que finalice la orden antes de solicitar una nueva. En esta situación el proceso padre tiene que esperar a que el proceso hijo finalice y claro, para ello el sistema operativo debe congelar la ejecución del proceso padre hasta que el hijo sea destruido. En ejecución de fondo el intérprete de órdenes no espera a que finalice la orden, sino que puede ejecutar más. En esta situación, el proceso padre solo necesita que el sistema lo retenga mientras crea el nuevo proceso, con el fin de retornarle el identificador del proceso si la creación ha sido correcta o un error si no ha sido así. Modalidad mixta: El proceso padre crea un proceso hijo en modalidad de fondo y a partir de un determinado instante en su ejecución, decide esperar a que finalice uno de sus procesos hijo. Con todo ello, el sistema operativo puede ofrecer dos modelos de llamadas al sistema: introducir un parámetro modo_ejecución que indique si el proceso padre tiene que esperar la destrucción del hijo o no; y conseguir que para crear no sea necesario esperar nunca la finalización de un proceso. También es necesario para el proceso padre muchas veces conocer el punto donde la aplicación ha finalizado o el motivo de esta finalización. Ya que a veces un proceso hijo puede finalizar felizmente pero en otras ocasiones por ejecuciones incorrectas o por efectos laterales de la ejecución de otras llamadas al sistema puede finalizar abruptamente. 2.5. Los cambios en el entorno de ejecución Los sistemas operativos tienen que permitir que los procesos carguen nuevos programas en la memoria para ser ejecutados. Los pueden hacer de dos maneras: al mismo tiempo que crea un nuevo proceso; a posteriori, una vez creado el proceso, mediante la invocación de la llamada específica cargar. Ésta última es la que nos interesa; lo que ocurre en este caso es que el intérprete de órdenes obtiene la orden cargar a través de la entrada estándar, analiza la existencia de algún ejecutable y crea un nuevo proceso igual que él; este nuevo proceso al ejecutarse, cargaría la aplicación que da servicio a la orden recibida. La carga del nuevo ejecutable provoca la reconfiguración total del espacio lógico del proceso, de manera que todos los valores de variables y constantes, los procedimientos y las funciones que se encontraban dentro del espacio lógico antes de la carga, desaparecen; pero para que el nuevo código pueda utilizar información anterior a su carga deben existir mecanismos o dispositivos de almacenamiento que sirvan de puente entre ambos. A pesar de poder guardarse en el sistema de ficheros, también el sistema operativo ofrece la posibilidad de pasar la información 32 · Sistemas operativos a través de parámetros de llamada, los cuales son recibidos por el nuevo programa como parámetros de entrada de la función principal. 3. LAS EXCEPCIONES Y LAS SEÑALES DE SOFTWARE Los procesos pueden necesitar ser informados de acontecimientos que suceden de manera imprevista en cualquier instante de la ejecución de un programa. Por ejemplo, un proceso puede necesitar saber si se ha producido un error en el acceso a la memoria para solicitar que se aumente el área asignada a una determinada variable; o también puede necesitar saber si un cierto terminal sobre el que trabaja se ha apagado, para acabar la aplicación correctamente. Las señales de software son la herramienta que proporciona el sistema operativo para trasladar el mecanismo de las interrupciones al ámbito del proceso. Igual que sucede en el ámbito del hardware, los procesos tienen que reconocer ciertos eventos y atenderlos con urgencia. La procedencia de las señales de software nos permite clasificarlas en: Dispositivos de entrada/salida: Como por ejemplo la desconexión de un terminal, la pulsación de una tecla de control por parte del usuario… Excepciones: Provocadas por el proceso de manera involuntario durante la ejecución de una instrucción, porque es errónea, porque el elemento hardware al que hace referencia está saturado o por violación de un segmento. Llamadas explícitas al sistema: Son señales entre procesos del mismo dominio de protección que pueden informar sobre eliminaciones de proceso, sincronizaciones, etc. El reloj del sistema: Cuando un proceso tiene que realizar acciones a intervalos regulares de tiempo; por ejemplo un módem que espera cierto lapso de tiempo para detectar si la línea se ha cortado o no. Efecto lateral de una llamada al sistema: Un proceso padre, por ejemplo, puede recibir una señal de que uno de sus procesos hijo ha sido destruido. Ahora bien, una vez recibidas estas señales, puede tener uno de los siguientes tratamientos: Tratamiento definido por el mismo usuario. Ignorar el acontecimiento. Tratamiento definido por el sistema operativo: Si por ejemplo existe un mal funcionamiento de un proceso y no se han especificado un tratamiento para ese caso particular, el sistema operativo tiene que proporcionar uno, y por norma general será la destrucción del proceso. En este caso, el sistema operativo se encarga de enviar el estado de finalización al proceso padre para indicarle el motivo de la destrucción. 4. LA GESTIÓN DE PROCESOS EN UNIX En Unix, un proceso es un entorno de ejecución identificado por un número denominado PID que es el identificador del proceso y que es único durante toda la vida del sistema operativo. Los principales elementos que constituyen un proceso en Unix (y que podemos observar en la imagen de la página siguiente son: El espacio de memoria, formado por el segmento de código, datos y pila. El segmento de código puede ser compartido por otros procesos. Y entre el de datos y pila hay una porción libre que puede aumentar el segmento de datos con la llamada malloc. Sistemas operativos · 33 Proceso en Unix El entorno de entrada/salida formado por los file descriptors locales a cada proceso. Cada entrada de la tabla apunta a otra tabla, en este caso de carácter global para el sistema y contienen los punteros, el modo de acceso, etc. El UID y GID que corresponden al número de identificador de usuario y al de grupo de usuario para determinar el dominio de protección de los procesos. El estado de los registros del procesador que refleja en qué estado se encuentra la ejecución del programa que tiene el proceso. La información estadística que presenta informaciones tales como el tiempo consumido de UCP, el volumen consumido de memoria o el número de procesos hijo generados. A continuación inherentes a los procesos, como son: Creación y destrucción de procesos Cambios de entorno Jerarquía y señales en Unix estudiaremos 3 características 4.1. Creación y destrucción de procesos Ya sabemos que las operación son crear_proceso (fork), Destruir_proceso (exit) y Esperar_finalización (wait). Creación de un proceso en Unix La llamada fork no tiene ningún parámetro y crea un nuevo proceso hijo que es un duplicado del padre. El PCB hijo es una copia del padre, al igual que el espacio de memoria, y las entradas de la tabla file descriptor. Pertenecen ambos procesos al mismo usuario y grupo de usuarios; pero a partir de la orden fork cada proceso continúa su ejecución de manera independiente. El proceso hijo recibe un valor 0 y el padre recibe el identificador PID del hijo para poder ser distinguidos. Además Unix no permite que la última entrada de la tabla de procesos y el último espacio de memoria libre sean ocupados por un usuario normal, ya que estos espacios se destinan para iniciar algún proceso del sistema de manera urgente, como puede ser el proceso de detención del sistema shutdown. La llamada exit destruye el proceso, y es el sistema el que se encarga de destruir el proceso que se ejecuta. Se pasa un parámetro al sistema operativo que el proceso padre podrá recoger mediante la orden wait para ser notificado del estado de finalización. La llamada wait bloquea el proceso que lo ha llamadao hasta que alguno de sus procesos hijo haya sido destruido. Cuando esto ocurre, el sistema le retorna el PID del proceso destruido y como parámetro de salida, el estado en que ha finalizado. Unix guarda el estado de finalización de los procesos destruidos en su PCB y espera que el proceso padre lo recoja mediante la llamada wait. Así que el proceso no es liberado en el momento de su destrucción, están a la espera de la recogida de su PCB, se encuentran entonces en el llamado estado zombie, hasta que la llamada wait permita 34 · Sistemas operativos destruirlos definitivamente. Para evitar que se acumulen demasiados procesos en este estado, está limitado el número de procesos zombie que puede tener cada usuario en cada momento y si un proceso es destruido más tarde que su proceso padre, entonces pasan a ser considerados hijos del primer proceso del sistema (el proceso INIT que es el único proceso en Unix que no se crea con la llamada fork, tiene PID 0) que será el encargado de recoger su estado de finalización. 4.2. Cambios de entorno Cualquier cambio que se quiera llevar a cabo en un proceso de tiene que realizar después de que éste haya sido creado. El cambio de ejecutable y los cambios en las entrada/salida permiten hacer el programa controlable y flexible. El cambio de ejecutable realizada con la llamada exec al sistema de Unix, permite cambiar la imagen o el ejecutable que contiene el proceso. Normalmente solo afecta a la programación de señales y al dominio de protección. Se suele cambiar al dominio del usuario propietario o al grupo del usuario propietario del fichero ejecutable, lo que permite construir aplicaciones que accedan de manera controlada a bases de datos, etc. La Manipulación de los file descriptors a través de la llamada dup que permite duplicar el valor de una entrada concreta de la tabla de file descriptors en la primera posición libre que se encuentre dentro de la tabla. El proceso es como explicamos a continuación: En primer lugar, el intérprete crea el proceso hijo que tendrá que ejecutar la orden; cuando se ha creado hay que redireccionar la entrada estándar para que cuando se haya cargado el programa orden se encuentre el redireccionamiento efectuado. Para ello se crea un file descriptor vinculado a “fichero” con la llamada open (desFichero) con lo cual se ocupa la primera entrada libre de la tabla de file descriptors. (primera imagen lateral) Para redireccionar la entrada estándar desde fichero hay que hacer que el file descriptor 0 se encuentre asociado al fichero, por lo que desasignamos el dispositivo real asociado al file descriptor 0 y mediante la llamada dup (segunda imagen lateral), lo volvemos a signar, copiando sobre su entrada (STdin) la entrada asociada a descFichero. Por último, falta eliminar mediante la llamada close (tercera imagen de la figura lateral) el file descriptor DescFichero para conseguir el entorno de entrada/salida que debe encontrar la orden que hay que ejecutar. Después, el proceso hijo puede cargar el nuevo ejecutable mediante la llamada exec. Cambio de entorno en Unix Sistemas operativos · 35 Funcionalidad de las pipes Las pipes son un dispositivo lógico destinado a comunicar procesos con una relación de parentesco. Funcionan como una cola de caracteres con una longitud fija, donde los procesos pueden escribir y leer. Se crean en el momento en que se abre mediante la llamada pipe y se destruye cuando el último proceso que lo tiene abierto lo cierra. El dispositivo que se crea con pipe tiene asociados dos file descriptors, uno de entrada y otro de salida; pero no se pueden abrir con la orden open, sino que los procesos que lo quiera utilizar necesitan heredar los file descriptors de su proceso padre. Funciona por sincronización: cuando un proceso intenta leer sobre una pipe vacia, se queda bloqueado hasta que otro proceso escribe sobre ella y ya hay material suficiente para leer; cuando un proceso intenta escribir sobre una pipe completa, se queda bloqueado hasta que algún otro proceso lee la cantidad suficiente de la pipe como para que el proceso bloqueado pueda efectuar su operación de escritura En la figura lateral podemos ver un ejemplo de utilización de pipes. El dibujo superior es la situación tras haber creado la pipe, con lo cual ésta tiene los file descriptors de lectura y escritura en el mismo proceso. En cambio la pipe es más útil cuando se lleva a cabo la llamada fork al sistema, y el proceso hijo hereda del padre los file descriptors, entre los cuales se encuentran los creados con la llamada pipe. Tras la creación del hijo y para mantener la comunicación, ambos cierran el canal del sentido de la comunicación que no van a utilizar, para permitir la detección del proceso de comunicación (queda unidireccional); como podemos observar en la parte inferior de la imagen. 4.3. Jeraquia y señales en Unix Ya sabemos que el proceso INIT lo inicia el propio sistema operativo, no está creado por la orden fork. Sus principales funciones son las de inicializar todos los procesos del sistema, como los procesos servidores de red, liberación de los procesos zombies que ya no tienen proceso padre y poner en funcionamiento el proceso getty. Este proceso se encarga de esperar que se pongan en marcha los terminales. Cuando esto ocurre, el proceso carga el programa login, para indentificar al usuario, si es correcto posteriormente carga el shell e inicia una sesión de trabajo con ese usuario. Cuando el usuario finaliza, el proceso shell se destruye e INIT crea un nuevo proceso getty que lo sustituye. Las señales en Unix pueden ser generados por los dispositivos de entrada/salida, excepciones, llamadas explícitas al sistema o por el reloj del sistema. En cualquier caso, los tratamientos posibles son los ya conocidos: el que da el sistema operativo por defecto según el tipo de señal; una segunda posibilidad es ignorar el acontecimiento (excepto la señal SigKill que no puede ser ignorada); y por último el que haya definido particularmente el usuario. 36 · Sistemas operativos TEMA 7 LA CONCURRENCIA Y LA COMUNICACIÓN 1. INTRODUCCIÓN En sistemas concurrentes, los procesos cooperan en el cálculo y la realización de diferentes tareas y comparten recursos (como los dispositivos, código, variables…); así los procesos deben contar con la posibilidad de intercambiar información entre sí, a través de dos procesos principalmente: Sincronización: Permite el acceso concurrente de los procesos a objetos del sistema que son básicamente secuenciales. Todos los procesos que deseen compartir algún objeto deben ponerse de acuerdo para saber qué acceso se va a llevar a cabo y éste sea coherente y libre de errores. Veremos en los puntos siguientes diversas soluciones de software y los semáforos. Comunicación: Muchas veces es necesario intercambiar resultados parciales entre procesos concurrentes o información en general. Hay varias formas a su vez de hacerlo: o Paso de mensajes: Integra tareas de comunicación y a la vez de sincronización. o Memoria compartida. o Señales: Interrupciones de software que pueden recibir los procesos para indicar que ha tenido lugar un determinado suceso. o Mecanismos de comunicación remota. 2. LA SINCRONIZACIÓN DE PROCESOS Antes de nada deberíamos preguntarnos porqué es necesaria la sincronización de procesos. Y esto lo vamos a ver con un ejemplo; para ello utilizaremos el dispositivo cola de impresión. Este dispositivo lo gestionan los usuarios (cuando quieren imprimir un trabajo, las tares pendientes en la cola de impresión aumentan en 1) y el gestor de impresora (que decrementa en 1 las tareas cuando ha finalizado la impresión de un documento). Bien, en principio, tareas pendientes es una variable compartida por todos los usuarios y por el gestor de impresora. Pero cuando el acceso es concurrente, la simple orden de un usuario al mandar imprimir un trabajo sería: Tarea_pendiente = tarea_pendiente + 1 Aunque parezca así de sencillo, en código máquina quedaría: LOAD R0, tarea_pendiente; INC R0 STORE tarea-pendiente, R0 Sucede que la utilización de registros, puede hacer que si una tarea es interrumpida en el proceso INC o STORE y estos no se completan (interrumpida por un cambio de contexto porque ha expirado su quantum, por interrupción de dispositivo, etc.); otro procesos concurrentes pueden hacer cambiar el valor de tarea_pendiente, siendo el resultado erróneo. Por ejemplo, si un usuario es interrumpido cuando ha enviado un trabajo a imprimir, ocurrirá que luego la impresora intentará imprimir un trabajo inexistente; mientras que si el gestor de impresoras es interrumpido en la actualización de la variable, el resultado final es que una tarea se quedará sin imprimir. Estos problemas han dado lugar a la llamada sección crítica, que es una secuencia bien delimitada (con inicio y fin) de instrucciones que modifican una o más variables compartidas. Esta sección crítica debe mimarse especialmente, dado que las principales causas de estos problemas de sincronización tienen lugar por: La aparición de copias temporales La posibilidad de que un proceso acceda al contenido de una variable compartida en cualquier instante, antes de asegurarse de que todas las peticiones de modificación previas han finalizado. Tema 7 La concurrencia y la comunicación 1. Introducción 2. La sincronización de procesos 3. Las soluciones de software para la exclusión mutua 4. Los semáforos 5. El soporte de hardware para la exclusión mutua 6. Procesos productores y consumidores 7. La comunicación entre procesos: el paso de mensajes 8. Problemas de coordinación entre procesos (deadlock) Sistemas operativos · 37 Por ello, cuando un proceso entra en una sección crítica, tiene que completar todas las instrucciones del interior de la sección antes de que cualquier otro proceso pueda acceder a ésta; es por tanto un acceso a la sección crítica en exclusión mutua y tiene que cumplir los siguientes requisitos: Asegurar la exclusión mutua entre los procesos a la hora de acceder al recurso compartido. No hacer ningún tipo de suposición de la velocidad de los procesos ni del orden en que éstos se ejecutarán. Garantizar que si un proceso finaliza por cualquier razón dentro de la zona de sección crítica, no afectará al resto de procesos que quieran acceder a la misma. Permitir que todos los procesos que están esperando acceder a la sección crítica, puedan hacerlo en un tiempo finito (starvation). Garantizar que solo uno de los procesos, como mucho, de los que quieran acceder a la sección crítica, acabará entrando (deadlock). Así, si la sección crítica está ocupada ya por un proceso, el resto de procesos se bloqueará (durmiéndose o poniéndose en alguna cola de espera) o entrarán en espera activa (consultado constantemente el estado del recurso). Por el contrario, si la sección está libre, el proceso que pide paso entrará, pero cerrará el acceso a cualquier otro proceso, hasta que haya utilizado el recurso compartido y libere la exclusión mutua. 3. LAS SOLUCIONES DE SOFTWARE PARA LA EXCLUSIÓN MUTUA Para que se cumplan los requisitos anteriores se han diseñado (entre otros remedios) soluciones de software. Estudiamos a continuación tres algoritmos. Algoritmos Primer Algoritmo Código que ejecuta P0 While (cierto) {/*Bucle infinito*/ While (turno ==P1) {intentarlo de nuevo} Seccion critica Turno = P1 /*Otras operaciones de P0*/ } /*Fin while {cierto}*/ Código que ejecuta P1 While (cierto) {/*Bucle infinito*/ While (turno ==P0) {intentarlo de nuevo} Seccion critica Turno = P0 /*Otras operaciones de P1*/ } /*Fin while {cierto}*/ __________ Segundo algoritmo While (cierto) {/*Bucle infinito*/ While (P1dentro==cierto) {reintentar} P0dentro=cierto Seccion critica P0dentro=falso /*Otras operaciones de P0*/ } /*Fin while {cierto}*/ __________ Tercer algoritmo While (cierto) {/*Bucle infinito*/ P0dentro = cierto Turno = P1 While (Pdentro==cierto y turno==P1) {reintentar} Seccion critica P0dentro=falso /*Otras operaciones de P0*/ } /*Fin while {cierto}*/ En un primer algoritmo, sincronizamos los dos procesos P0 y P1, definiéndose una variable global denominada turno. Si turno es igual a P0, el proceso P0 puede acceder a la sección crítica; si es igual a P1, será este proceso el que accederá; pero presenta varios problemas: Hay que conocer los procesos. Es difícil generalizar el protocolo de sincronización para un número variable de procesos, ya que añadir o quitar procesos implica modificar el código de otros procesos que ya existían. Este protocolo obliga a una alternancia estricta en el orden de acceso a la sección crítica: P0, P1, P0, P1… si por ejemplo el proceso 1 es más rápido porque tiene una mayor velocidad, el acceso ya no es equitativo. Si uno de los dos procesos aborta o muere el otro proceso no podrá acceder al a sección crítica y se quedará de manera indefinida en un bucle while esperando su turno. Un segundo algoritmo pretende resolver estos problemas, con el identificador Pxdentro evitamos la alternancia estracita en el acceso a la zona crítica y además sin un proceso muere, el otro puede acceder a la sección crítica sin ningún tipo de restricción. Pero aun así tenemos todavía algunos problemas sin resolver: Es difícil también generalizar para un número variable de procesos. Podría ocurrir que ambos procesos pudieran estar dentro de la sección crítica al mismo tiempo. Si no ha llegado a ejecutarse la tercera línea del algoritmo que 38 · Sistemas operativos todavía indica que la sección crítica está libre y es consultada la variable por el segundo proceso, podrá acceder junto al primer –que ya tenía permiso- a la zona crítica. En un tercer algoritmo identificamos las variables Pxdentro a falso y no inicializamos la variable turno; así si ambos procesos quieren entrar en la zona crítica al mismo tiempo, accederá el que indique la variable turno. Todos estos algoritmos que hemos visto solo tienen en cuenta dos procesos, debemos ser conscientes que existen para mayor número de los mismos, pero no se describirán por la complicación que ello supone y porque existe un método mucho más elegante y sencillo para resolver el problema: los semáforos, solución que estudiaremos a continuación. 4. LOS SEMÁFOROS Los algoritmos de software anteriores son complejos y a menudo difíciles de generalizar para más de dos procesos. Una solución posible a este problema puede ser el uso de semáforos. Un semáforo es una variable entera s que solo se puede incrementar mediante la operación signal y decrementar con la operación wait. El proceso wait (s) decremente la variable s y hace pasar el proceso a la sección crítica cuando S es mayor que o, el proceso signal incrementa el valor de la vairable s=s+1. Evidentemente, para que esto funcione las señales wait y signal deben tener un funcionamiento atómico. Podemos encontrarnos con semáforos binarios que solo puede tomar los valores o y 1 y, que por tanto, únicamente permiten tener un proceso dentro de la sección crítica; o podemos encontrarnos semáforos n-arios que permiten tomar valores entre 0 y n y por tanto dejan incluir hasta n procesos dentro de la sección crítica. Igualmente podemos tener semáforos de baja granularidad que comporta tener diferentes semáforos para distintas zonas críticas, lo que aumenta el grado de concurrencia entre procesos y suele interesarnos; o de alta granularidad con lo que se utiliza un mismo semáforo para distintas zonas críticas. La utilización de los semáforos sigue el siguiente proceso: Se inicializa el semáforo (S>=1). Este valor inicial debe ser mayor o igual que 1 para indicar que no hay ningún proceso dentro de la sección crítica y así permitir paso al primer proceso que lo pida. Se ejecuta la operación wait: un proceso quiere acceder al a zona de exclusión mutua; entonces se cambia el valor de s a s-1 y no se permite el paso de ningún proceso más. Se ejecuta la operación signal y se incrementa el valor de la variable con lo cual queda liberada la zona de exclusión y puede entrar otro proceso distinto. Algoritmos de los semáforos /*Definición de la variable e inicialización*/ Variable semáforo: exclusión; … exclusión =1; /*Inicialización*/ /*Codigo procesos usuario*/ while(cierto){ /*Generar fichero*/ wait (exclusión); tarea_pendiente=tarea_pendiente + 1; signal (exclusión); } /* fin del while En la tabla lateral se expone el ejemplo de los algoritmos de inicialización, código de procesos usuario y código de proceso gestor de la /*Código proceso gestor impresora*/ while(cierto){ impresora para la compartición del recurso impresora. while (tarea_pendiente==0); En la página siguiente se observa la evolución de 3 tareas de /*Enviar trabajo a la impresora*/ impresión P1, P2 y P3 y el proceso gestor de la impresora G1, y como wait(exclusión); tarea_opendiente=tarea_pendiente – 1; consiguen acceder a la sección crítica. Vemos en el tiempo T1 que todos los signal(exclusión); procesos están esperando pero no existen tareas de impresión pendientes. } /*Fin while (cierto)*/ En un tiempo T2 el proceso P1 consigue entrar en la zona de exclusión mutua, aumenta el número de tareas pendientes y luego libera el semáforo mediante signal. Ahora bien, cualquiera de los procesos que están esperando Sistemas operativos · 39 puede entrar en la zona de exclusión, dado que no hay un criterio para la entrada y observamos que es el proceso P3 el que consigue entrar, aumentando las tareas pendientes del gestor de impresión a 2. Ejemplo de acceso a exclusión mutua Como observamos los semáforos son un mecanismo fácil y muy útil para sincronizar procesos concurrentes, pero podemos observar que existen dos problemas que pueden llegar a ser muy graves: Un proceso utiliza ciclos del procesador en un trabajo que no es útil: la consulta constante de una variable. Estos ciclos se podrían dedicar a otros procesos. Un proceso puede quedar esperando indefinidamente su entrada en la sección crítica, ya que la implementación anterior no permite ningún tipo de control sobre los procesos que están esperando. Así en el ejemplo anterior si constantemente llegan procesos para ser impresos y el proceso gestor G1 tiene la mala suerte de no entrar en la zona de exclusión mutua, podría llegarse a no imprimir ningún trabajo y colapsarse la memoria del sistema a causa de la cantidad de tareas pendientes por imprimir. Estos problemas pueden solucionarse mediante la implementación de los semáforos con cola de espera: El nuevo código de las operaciones wait y signal quedan de la siguiente manera: /* Código de la operación wait*/ If (S<=0) then /* Ponemos el proceso en la cola*/ Else S = S – 1 /* Código de la operación signal*/ If (Cola no vacia) then /* Sacamos un proceso de la cola*/ Else S = S + 1 A cada semáforo se le asigna una cola en la que los procesos pueden colocarse. Así cuando un proceso solicita paso a una sección crítica ocupada, en lugar de quedarse activamente consultando el valor de la variable, suspende su ejecución y se pone en la cola del semáforo. Cuando un proceso libera la sección crítica, se da paso al siguiente proceso en la cola según política de asignaciones varias: por prioridades, por tiempo de espera, FIFO (First in, first out), etc. 40 · Sistemas operativos 5. EL SOPORTE HARDWARE PARA LA EXCLUSIÓN MUTUA Para implementar los semáforos que hemos visto es la sección anterior, es necesario asegurar que estas señales son atómicas, es decir que se ejecutan indivisiblemente; esto tiene lugar gracias a las tres soluciones de hardware que proponemos a continuación: La inhibición de interrupciones ofrece la posibilidad de deshabilitar las mismas. De esta manera si un proceso las desactivas, éste proceso siempre tendrá asignado el procesador hasta que no las vuelva a activar; así con el siguiente algoritmo quedaría solucionado el problema: Inhibir {desactivar las interrupciones} Sección crítica Desinhibir {activar de nuevo las interrupciones} Estas interrupciones son peligrosas y no podemos hacer un uso indebido o malintencionado de ellas, por lo que lo más habitual es que sean utilizadas no por los programadores, sino por el sistema operativo. La operación test and set es una segunda posibilidad hardware para la exclusión mutua. La idea básica es definir una variable de control asociada a la sección crítica que determine si es posible el acceso a la misma o no. Cada proceso que quiere acceder al recurso ejecuta la operación test and set (TS) y le pasa por parámetro la variable de control asociada al recurso. Así un semáforo es espera activa como vemos en el algoritmo lateral hace que TS observe el estado de S, si está libre lo pone a ocupado e inmediatamente modifica los flags para indicar que S estaba libre. La instrucción BNF (Branco if not free) salta a la etiqueta wait si S estaba ocupado, pero dado que está libre el proceso retorna de la llamada wait y accede al recurso compartido. Para liberar el recurso compartido, damos la señal de libre, tal como se ve en el algoritmo lateral. El intercambio o swap es una instrucción de lenguaje máquina que intercambia atómicamente el valor de dos palabras de memoria, como podemos observar en el primer algoritmo de la tabla lateral. Lo que hacemos es definir una variable global que denominamos lock, la cual puede tomar valores cierto o falso; la variable se inicializa a falso. Cada proceso tiene además una variable local que recibe el nombre de clave y que también puede tomar los valores cierto o falso; todo proceso que quiera acceder a una zona crítica deberá ejecutar el código inferior de la tabla lateral, que es de por si, bastante explicativo. 6. LA COMUNICACIÓN ENTRE PROCESOS: EL PASO DE MENSAJES El hecho de que varios procesos llevan a cabo actividades conjuntamente, puede plantearnos la necesidad de sincronizar y comunicar datos entre ellos, ya sean resultados finales, datos parciales, etc. Esto podemos realizarlo a través del paso de mensajes, un mecanismo sencillo e intuitivo que permite la sincronización y la comunicación entre procesos. El mensaje es un conjunto de información que, aunque puede ser de formato flexible, suele tener dos partes: el encabezamiento que contiene la información necesaria para que pueda llegar a su destino correctamente, y el cuerpo que es el mensaje propiamente dicho. Los mensajes son soportados por dos tipos de operaciones básicas: enviar_mensaje (send) y recibir_mensaje (receive). El mecanismo de paso de los mensajes puede ser: directo/indirecto, síncrono/asíncrono. Pasamos a verlos: Algoritmos test and set /*Semáforo espera activa wait: TS s BNF wait Return /* Señal de libre */ signal: mov S, libre Algoritmos swap /*Algoritmo swap */ swap (A, B) { temp = A; A = B ; B = temp ; } /* Fin swap*/ /* Codigo de procesos */ while (cierto){ clave = cierto ; Do Swap (lock, clave); Until (clave = falso) Seccion critica Lock = falso } /* fin while (cierto) */ Sistemas operativos · 41 Paso de mensajes /* Mensajes directos */ Proceso A …send (B, mensaje) Proceso B …receive (A, mensaje) /* Mensajes indirectos */ Proceso A …send (Buzon1, mensaje) Proceso B …receive (buzon1, mensaje) Semáforo binario con mensajes While (cierto){ Receive (buzon, mensaje); Seccion crítica; Send (buzon, mensaje); {/* Fin while (cierto)*/ Los mensajes directos, son aquellos en los que el proceso emisor tiene que identificar al proceso receptor y a la inversa. También se denomina comunicación simétrica ya que cada emisor tiene que conocer todos los posibles receptores y a la inversa. Los mensajes indirectos no se envían a un receptor en concreto, sino a un buzón, y es el proceso receptor el que lo busca no es un emisor en concreto sino en un buzón; así que se definen estos buzones como estructuras intermedias para el paso de mensajes. Ello permite la comunicación de uno a uno, uno a muchos, y de muchos a muchos. El sistema UNIX ofrece un tipo de comunicación indirecta, mediante unos buzones llamados pipes, y que hace posible la comunicación entre procesos con parentesco. La comunicación síncrona se basa en un intercambio síncrono de mensajes. Esto implica que el emisor y el receptor acaben la transferencia de la información en el mismo momento. Por tanto, la operación send es bloqueadora y hasta que el receptor no efectúa la operación complementaria receive, el emisor queda bloqueado. Esta forma de comunicación permite establecer un punto de sincronismo entre procesos, aunque en algunos servidores puede suponernos un problema. La comunicación asíncrona no bloque el proceso y el sistema guarda el mensaje temporalmente en la memoria hasta que se lleva a cabo la operación receive complementaria. Esto hace aumentar el grado de concurrencia del sistema, pero también hay que gestionar los vectores de memoria intermedia que almacenan los mensajes. Un método muy sencillo para implementar la ejecución concurrente en exclusión mutua es mediante el paso de mensajes indirectos. Para solicitar acceso a la sección crítica, el proceso lee del buzón con la operación receive. Si el buzón está vacío, el proceso queda bloqueado hasta que alguien escriba un mensaje; si está lleno, el proceso lee el mensaje. Eso sí nos interesa que le primer proceso que pida acceso pueda pasar, para ello es necesario que el buzón se inicialice introduciendo un mensaje en su interior; de esta forma, el buzón desarrolla el papel equivalente al de un semáforo binario. También podríamos implementar semáforos n-arios con buzones en los que se permitiese hasta n mensajes. 7. LOS PROBLEMAS DE COORDINACIÓN ENTRE PROCESOS (DEADLOCK) Un bloqueo es una situación en la que un grupo de procesos permanece bloqueado de manera indefinida sin ninguna posibilidad de que continúe su ejecución. Suele suceder porque unos procesos han adquirido un conjunto de recursos necesarios que otros procesos necesitan para continuar. Por ejemplo, si un proceso P1 pidiese acceso a dos dispositivos en exclusión mutua como son disco e impresora por este orden, y otro proceso P2 hiciera lo propio pero en orden contrario; nos podríamos encontrar en un bloqueo si a P1 se le hubiese concedido el disco y antes de concedérsele la impresora, ésta ya se hubiera concedido a P2; a su vez P2 no puede continuar porque necesita acceso a disco que tiene P1. Como ninguno de los procesos libera sus recursos, la situación se puede prolongar indefinidamente, es decir, tenemos un bloqueo. Para que esto suceda deben cumplirse cuatro requisitos: Exclusión mutua: Hay que acceder a los recursos en exclusión mutua. Retención y espera: Cada proceso que tiene ya asignados recursos no los libera, sino que espera a tener los que necesita. No expropiación: Los recursos que un proceso ya tiene solo se liberan a petición explícita del proceso, el sistema no se los puede quitar. Espera circular: Los procesos bloqueados forman una cadena circular como en el ejemplo anterior. 42 · Sistemas operativos Podemos encontrar varias soluciones a este tipo de problemas, clasificadas en función de su objetivo en: Prevenir el bloqueo: Se ordena de manera lineal los recursos del sistema, así se obliga a los procesos a solicitar los recursos en un orden creciente. Evitar el bloqueo: Solo se asignan los recursos disponibles que no pueden generar ningún tipo de bloqueo; esto requiere un control detallado de los recursos asignados y a qué procesos y qué recursos esperan qué procesos. Detectar y recuperar el bloqueo: Se basa en comprobar si se han producido bloqueos mediante la observación de algún ciclo en el grafo de recursos asignados. Si se detectase alguno, el sistema reinicia algunos de los procesos bloqueados para liberar este ciclo. Sistemas operativos · 43 TEMA 8 EL ESTADO ACTUAL DE LOS SISTEMAS OPERATIVOS Tema 8 El estado actual de los sistemas operativos 1. Conceptos generales 2. Los sistemas operativos de tiempo real 3. Los sisemas operativos multiprocesador 4. Los sistemas operativos en red y los sistemas operativos distribuidos. Modelos cliente/servidor 1. CONCEPTOS GENERALES La evolución de los sistemas operativos se ha visto muy influenciada por dos conceptos que procedemos a estudiar a continuación: el modelo cliente/servidor y los sistemas multiflujo. En el modelo cliente/servidor, las llamadas al sistema, la invocación de procedimientos y la solicitud de ejecución de órdenes son consideradas como la petición de un servicio (por ejemplo una llamada al sistema), de un cliente (ente que realiza la petición) hacia un servidor (ente que ofrece el servicio). La figura lateral muestra las 3 posibles situaciones que pueden darse entre el cliente y el servidor: El cliente es un proceso y el servidor es su sistema operativo, como hemos estado viendo hasta ahora. El cliente y el servidor son servicios del sistema operativo; se utilizan las llamadas al sistema para poner el contacto al cliente y al servidor. El cliente y el servidor son procesos de sistemas operativos diferentes, y se utiliza en red para comunicar máquinas distintas. Este último caso es el más interesante, pues permite ofrecer servicios desde cualquier punto de la red, y es la idea base de los sistemas abiertos. Los mecanismos de comunicación que podemos hallar en estos casos son 3: Paso de mensajes: Es el caso más elemental; cliente y servidor tienen que ser conscientes de la ubicación de cada uno y tienen que presentar toda la información de manera explícita, así como los parámetros la respuesta de su solicitud. Procedimientos remotos: El cliente considera el servicio como un procedimiento más de la librería y el sistema operativo se encarga de esconder al usuario las particularidades de esta comunicación. Objetos distribuidos: Se adopta la semántica de la programación orientada a objetos. El usuario ve el servicio como la llamada de un método del objeto al que quiere acceder. Pero también es cierto que la relación cliente/servidor también aporta una serie de incertidumbres que necesitan respuesta, por ejemplo: localizar un servidor o un servicio concreto dentro de los servidores, como recuperar errores cuando algo falla, etc, problemas que no se daban en un entorno de trabajo aislado como teníamos antes. Hay dos posibilidades para dar solución a estos y otros problemas: o bien tenemos más de un servidor repartido en máquinas diferentes para implementar políticas tolerantes a fallos (y de paso ser más eficientes); o bien nos centramos en la estructura del propio servidor, tolerando o no más de un cliente a la vez, dando comunicaciones síncronas o asíncronas, etc. El segundo concepto a estudiar son los sistemas multiflujo, que consideran un proceso como una máquina virtual completa, con memoria, dispositivos de entrada/salida, etc. Ahora al tener más de un procesador, se plantea la necesidad también de tener más de un flujo de ejecución por proceso (antes solo teníamos un flujo por proceso porque análogamente solo contábamos con un procesador). Además podemos ejecutar aplicaciones paralelas que se comunican y sincronizan a través de la memoria compartida. 44 · Sistemas operativos 2. LOS SISTEMAS OPERATIVOS EN TIEMPO REAL Los sistemas operativos en tiempo real tienen que ofrecer servicio con unas restricciones temporales muy concretas, en general deben garantizar el inicio de la ejecución antes de un cierto intervalo de tiempo y debe finalizar antes que la acción que hemos iniciado deje de tener sentido. Por ejemplo, para evitar el desbordamiento de una presa frente a una riada, debemos iniciar la apertura de las compuertas, teniendo además en cuenta el tiempo que tardan en abrirse, para que la presa no reviente bajo la presión del agua. Si no podemos abrir las compuertas asegurando esto, debemos dedicar los recursos a otras cosas como avisar a la población y a los sistemas de emergencias. Por ello, la gestión del sistema debe: Evitar los mecanismos de gestión que no pueden ofrecer servicio en un intervalo de tiempo conocido o que lo hacen con un coste de tiempo excesivo. Proporcionar herramientas que permitan ordenar los procesos en función de su urgencia. Facilitar mecanismos de comunicación rápidos y flexibles. En la figura lateral se observa un ejemplo de aplicación de tiempo real, con unos procesos ordenados por prioridades. Por todo lo anterior, los principales puntos de acción son: Aplicación de tiempo real Gestión de procesos: La planificación del procesador es externa a él y se basa en la determinación de prioridades para los diferentes procesos; y el procesador admite los procesos en función de esta prioridad. Gestión de memoria: No es deseable la memoria virtual ya que introduce un grado de variabilidad temporal nada bueno. Así que en los sistemas de tiempo real algunos procesos deben estar de forma permanente residentes en memoria. Gestión de la entrada/salida: El sistema proporciona herramientas para que los usuarios puedan definir los tratamientos de las interrupciones de determinados dispositivos de entrada/salida y evitar la ejecución del código genérico del sistema operativo. Comunicación entre procesos: Los mecanismos de señales también deben tener asociada una prioridad para su ejecución. Para mejorar además la comunicación entre procesos se definen regiones de memoria compartidas para que los procesos puedan comunicarse sin costes de tiempo añadidos. Gestión del sistema de ficheros: Se suele utilizar la memoria compartida y por ello también se podría usar esta misma memoria como dispositivo de almacenamiento. 3. LOS SISTEMAS OPERATIVOS MULTIPROCESADOR Como cada vez se desarrollan más aplicaciones software que deben soportar mayor volumen de trabajo y de cálculo, se hace necesario mejorar el hardware que utilizamos con él, hay varias soluciones: el simple avance tecnológico que hace evolucionar todo el hardware, soluciones arquitectónicos que permiten las ejecuciones concurrentes en paralelo, mejorar el diseño del procesador, o combinar varios procesadores en un mismo sistema; esto último suele ser económico y más viable que el resto. Podemos encontrar dos sistemas multiprocesador principalmente (en la tabla de la página siguiente mostramos una imagen aclaratoria): Sistemas operativos · 45 Multiprocesadores acoplados fuertemente, o conocidos como sistemas de memoria compartida, en los que cada procesador ve y puede acceder de forma directa a la totalidad de la memoria del sistema. Multiprocesadores acoplados débilmente: también denominados sistemas de memoria distribuida, en los que cada procesador solo puede acceder a una memoria privada y se comunican entre sí por el paso de mensajes. Modelos cliente/servidor Las ventajas del sistema multiprocesador son varias: Tolerancia a fallos: Se pueden diseñar de forma que funcionen aunque alguno de los procesadores que lo integran falle; e incluso puede reemplazarse el componente fallido sin tener que parar el sistema. Rendimiento y potencia de cálculo: Al disponer de varios procesadores las aplicaciones se ejecutan en paralelo, e incluso una misma aplicación se divide en partes más pequeñas que se ejecutan en procesadores distintos. Crecimiento modular: Un mismo sistema puede trabajar con mayor o menor cantidad de procesadores, así puede crecer según la cantidad de trabajo que se requiera realizar, sin tener que renovar todo el sistema. Especialización funcional: Los procesadores que integran un mismo sistema pueden ser muy heterogéneos en sí mismos. Flexibilidad: Todo lo anterior hacen que estos sistemas sean muy flexibles y adaptables. En general con estos sistemas queremos o bien aumentar la productividad (el usuario tendría una visión idéntica a la de un sistema monoprocesador, pero obviamente más rápido) o bien a aumentar la velocidad (el usuario trabaja en manera diferida). Así clasificamos los sistemas operativos multiprocesador en: Supervisores separados: Cada procesador tiene un sistema operativo independiente que funciona casi como un sistema aislado; casi, porque cada sistema operativo dispone de algunos servicios como el de comunicación entre procesos de diferentes procesadores. Además suelen existir herramientas para repartir la carga de trabajo de manera homogénea entre procesadores. Su objetivo básico es el de aumentar la velocidad de ciertas aplicaciones. Maestro/esclavo: Un procesador es el maestro que se encarga de ejecutar el sistema operativo, mientras que el resto (los esclavos), ejecutan los procesos que el maestro les encarga. Aunque los esclavos pueden ejecutar llamadas elementales al sistema, habitualmente el maestro es el que se encarga de realizar los servicios directamente. Este modelo permite asignar de mejor forma los procesos a los distintos procesadores, aunque hay que reconocer que no es válido para sistemas con muchos procesadores, pues el acceso al maestro puede convertirse en un cuello de botella para todo el sistema; por ello suele gestionar multiprocesadores de memoria compartida con un número pequeño de procesadores. Simétrico: Todos los procesadores tienen las mismas competencias y los recursos del sistema se encuentran a disposición de todos los procesos. Como el sistema se ejecuta en paralelo, requiere herramientas de sincronización y exclusión mutua para gestionar sus recursos sin conflictos. Este modelo está especialmente indicado para sistemas de memoria compartida, en los que el objetivo es la productividad. 46 · Sistemas operativos 4. LOS SISTEMAS OPERATIVOS EN RED Y LOS SISTEMAS OPERATIVOS DISTRIBUIDOS El entorno distribuido se define como varios sistemas interconectados con una red con la capacidad de cooperarse y comunicarse, además se caracterizan por no tener memoria compartida, el estado del sistema está repartido entre los diferentes componentes y tienen retrasos y son frágiles por los propios condicionamientos de la red de interconexión. Eso sí, aportan innumerables ventajas: Compartición de recursos caros, como pueden ser periféricos muy especializados. Comunicación y compartición de información: que a su vez hace posible la cooperación de personas en un mismo trabajo y la ejecución de aplicaciones en paralelo. Fiabilidad, disponibilidad y tolerancia a fallos: Se pueden duplicar cálculos en más de un equipo, duplicación de discos duros y sistemas enteros por si uno de ellos falla, etc. Crecimiento progresivo: La flexibilidad de la red hace poder conectar de manera gradual distintos elementos que actualicen y se adapten a las circunstancias puntuales. Rendimiento: puede ser elevado el rendimiento global. Los sistemas operativos distribuidos son sistemas operativos que engloban y gestionan un entorno distribuido de manera transparente para el usuario. Es decir tenemos toda la potencia del conjunto de elementos que integra la red pero con una máquina que esconde las particularidades de su gestión. A continuación exponemos la gradación en el nivel de transparencia e integración en varios apartados del sistema operativo: Ejecución de procesos: En un sistema cerrado no nos preocupa donde se ejecutará un proceso ni cual será su entorno. Pero esto no es tan obvio en un sistema en red y las situaciones que nos podemos encontrar (de menos a más complejas e integradas) son las siguientes: o Para ejecutar una aplicación en una máquina remota desde la máquina local hacemos un telnet que nos abre la sesión en la máquina remota, y comporta un cambio completo de entorno. o Podemos ejecutar desde la máquina local una aplicación concreta en una máquina remota. Pero ambos entornos, el de la aplicación y el del usuario son diferentes y disyuntos. o El propio sistema local tiene una llamada al sistema crear_proceso con un parámetro para especificar la máquina donde se puede ejecutar. La principal diferencia con el sistema anterior es que la distribución la realiza directamente el sistema operativo, mientras que antes se hacía mediante una aplicación externa al sistema operativo. o El sistema local tiene una llamada crear_proceso que localiza automáticamente la máquina del sistema más adecuada para realizar la ejecución y el proceso. El entorno de ejecución es el mismo que si fuese una aplicación local. El sistema de ficheros: o Si se quiere compartir un fichero remoto del sistema, el usuario tiene que copiarlo dentro de su sistema local de ficheros y para ello requiere una aplicación específica como por ejemplo ftp. Por ello, los sistemas de ficheros local y remoto son totalmente disyuntos. o El sistema operativo tiene un espacio de nombres que incluye el número de la máquina remota. El usuario ya no tiene que ejecutar una aplicación externa para llevar a cabo la transferencia de ficheros, sino que lo hace el propio sistema operativo. o El sistema ofrece al usuario un único espacio de nombres que integra los sistemas de ficheros de las máquinas remotas y no tiene que transferir los ficheros a su propia máquina local. El usuario Sistemas operativos · 47 desconoce donde se ubican los ficheros y como están repartidos entre todas las máquinas del sistema. o El usuario finalmente puede tener la misma visión que en un sistema aislado. El sistema se encarga de distribuir los ficheros entre los discos de la red, replicarlos o moverlos si conviene por seguridad o eficiencia en el acceso. La protección: En un sistema aislado cuando hacíamos login y entrábamos en el sistema ya no debíamos preocuparnos por el dominio y nuestros derechos pues el propio sistema operativo se encargaba de ello. Ahora en un sistema distribuido el saber en qué dominio estamos dependerá del grado de distribución del sistema: o Las máquinas tienen dominios de protección totalmente separados. Así cada máquina tiene usuarios con derechos independientes de las del resto. Así para acceder a otra máquina debemos hacer un telnet o rlogin (remote login) y tender un nombre de usuario y una contraseña. o Los sistemas pueden poseer tablas de conversión entre dominios de máquinas diferentes. Así cuando el usuario accede a un sistema remoto no necesita identificarse de nuevo ya que el sistema local informa al remoto del usuario de que se trata. o En un sistema distribuido los usuarios tienen un único dominio, independientemente de la máquina del sistema en la que estén trabajando; así con un único nombre de usuario es lícito entrar en todas las máquinas del sistema con los mismos derechos. Tipos de sistemas operativos distribuidos Existen dos tipos de sistemas operativos distribuidos que son el modelo monolítico y el micronúcleo. El modelo monolítico es un sistema en el que los servicios son gestionados por servidores que en su mayoría forman parte del núcleo del propio sistema y por tanto se encuentran dentro de su espacio protegido. En cada nodo de su red se ejecuta el sistema completo e internamente los diferentes núcleos se coordinan para llevar a cabo la gestión de los recursos. Es el sistema tradicional y su principal ventaja es la integración en un único espacio de todos los servicios lo que permite llevarlo a la práctica con un coste reducido (no son necesarios cambios de espacios de direcciones ni transferencias de información entre procesos). Por otro lado, los modelos micronúcleo tienen un sistema operativo que se reduce a un núcleo que proporciona y gestiona lo más básico: memoria, procesos, comunicación entre procesos; mientras que el resto de los servicios viene dado por los servidores externos que se pueden crear y destruir de manera dinámica y se pueden situar en cualquier nodo de la red. Así pues el micronúcleo es una capa de software que se encuentra en todas las máquinas del sistema y que cubre el hardware proporcionando un entorno básico. Sus principales ventajas se centran en que son sistemas muy abiertos que permiten al usuario adaptarse y evolucionar; utilizan servidores externos que hacen que puedan emular modelos de sistema y tienen una dimensión reducida que hace posible desarrollarlos y depurarlos con facilidad; ello hace que también puedan transportarse a plataformas diferentes con mayor facilidad. Texto elaborado a partir de: Sistemas operativos Teodor Jové Lagunas, Dolors Royo Vallés, Josep Lluís Marzo i Làzaro Junio 2004