Sistemas Operativos Prólogo Las presentes notas son recopilaciones de diferentes libros básicos que se recomiendan a los estudiantes de nivel licenciatura para la materia de Sistemas Operativos, así como artículos, y manuales de programación que ayudan a ampliar la información. Se presentan también códigos básicos tomados de los libros base, así como algunas mejoras que se han llevado a cabo a lo largo de los cursos en los cuales he impartido la materia. Se recomienda tomar como punto de partida para la exploración el sistema operativo GNU/Linux, la distribución que considere pertinente, hago notar que los códigos presentados fueron probados bajo una distribución de Ubuntu. Nada de esto es comercial ni mucho menos para sacar un peso de algún lado, como les repito son recopilaciones de libros básicos como Sistemas Operativos de W. Stallings, Sistemas Operativos de M. Deitel, Sistemas Operativos de A. Tanenbaum, Sistemas Distribuidos de G. Coulouris, Sistemas Distribuidos de A. Tanenbaum, Sistemas Distribuidos de Mullender, Unix de C. Brown, Unix de F. M. Marquez, Unix de Robbins. Estas notas, digamos para colocarle un número de versión, son las 0.01, faltan demasiados detalles que pulir, figuras que colocar y tablas que actualizar, solo para justificar su primera liberación, diré a mi favor: necesito colocar estas notas para que las lean mis estudiantes, y no tengan excusa de no encontrar los libros en la biblioteca, pero si motivo de venir a verme y decirme que algo anda mal en estas páginas. Espero sea de utilidad! Hay golpes en la vida, tan fuertes... Yo no sé. Golpes como del odio de Dios; como si ante ellos, la resaca de todo lo sufrido se empozara en el alma... Yo no sé. Los heraldos negros. César Vallejo. 1 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Capítulo 1 Introducción a los Sistemas Operativos Un sistema operativo (SO) se encarga de controlar y administrar los recursos con los que cuenta un dispositivo de comunicación, brindando con una interfaz para comunicar al usuario con el hardware. 1.1 Sistema Operativo Veamos primero algunas definiciones de algunos autores sobre qué es un sistema operativo, para comprender mejor lo necesario que es un sistema operativo para que un hardware puede ser utilizable. Definiciones 1. El software que controla el hardware. 2. Es un programa de aplicación que controla la ejecución de los programas de aplicación y que actúa como interfaz entre el usuario de una computadora y el hardware de la misma. 3. Es un administrador de recursos del dispositivo, tales como, los procesadores, los medios de almacenamiento, los dispositivos de entrada/salida, los medios de comunicación y los datos; y proporciona la interfaz con el usuario. Aunque podemos decir que en la definición 1, actualmente existe una tendencia significativa a la transferencia de las funciones del software al firmware, es decir, colocar un microcódigo incrustado en el hardware. Un sistema operativo tiene tres objetivos: 1. Comodidad. Hace que una computadora sea más cómoda de utilizar. 2. Eficiencia. Permite que los recursos de un sistema de computo se aprovechen más eficientemente. 3. Capacidad de evolución. Debe construirse de modo que permita el desarrollo efectivo, la verificación y la introducción de nuevas funciones en el sistema y, a la vez, no interferir en los servicios que brinda. 2 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Jerarquía de elementos en una computadora El hardware y el software que se utilizan para proveer de aplicaciones a los usuarios pueden contemplarse de forma jerárquica (Figura 1-1). Usuario Final Aplicaciones Utilerías Sistema Operativo Hardware Figura 1-1. Jerarquía en el Sistema de Computo. Un sistema operativo ofrece servicios en las áreas siguientes: • • • • • • • Creación de programas. Ofrece un conjunto de programas que no forman parte del S.O. pero son accesibles a través de él como son: los editores y los depuradores. Ejecución de programas. Se encarga de administrar las instrucciones y los datos que se deben cargar en la memoria principal, los archivos y los dispositivos de E/ S. Acceso a los dispositivos de E/S. Cada dispositivo de E/S requiere un conjunto propio y peculiar de instrucciones o de señales de control para su funcionamiento. El S.O. tiene en cuenta estos detalles de modo que el programador solo piensa en forma de lecturas y escrituras simples. Acceso controlado a los archivos. El S.O. se encarga de los detalles del control del dispositivo de E/S y en el caso de sistemas con varios usuarios trabajando simultáneamente, el S.O. brinda los mecanismos de control para el acceso a los archivos. Acceso al sistema. Las funciones de acceso al sistema brindan protección a los recursos y a los datos, ante usuarios no autorizados y debe resolver los conflictos en la propiedad de los recursos. Detección y respuesta a errores. El S.O. debe dar una respuesta que elimine la condición de error con el menor impacto posible sobre las aplicaciones que están en ejecución. La respuesta puede ser desde terminar el programa que produjo el error, hasta reintentar la operación o, simplemente informar del error a la aplicación. Contabilidad. Un buen sistema operativo debe recoger estadística de utilización de los diversos recursos y supervisar los parámetros de rendimiento tales como el tiempo de respuesta. 3 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos 1.3 Clasificación de los sistemas operativos Con el paso del tiempo, los sistemas operativos fueron clasificándose de diferentes maneras, dependiendo del uso o de la aplicación que se les da. Hoy en día los sistemas los podemos clasificar en dos grandes grupos, los sistemas operativos para computadoras (ya sea para uno o varios procesadores) y los sistemas operativos para dispositivos móviles. A continuación se muestran una clasificación para los sistemas operativos de computadoras, y algunas de sus características. Sistemas Operativos por Lotes Los Sistemas Operativos por lotes, procesan una gran cantidad de trabajos con poca o ninguna interacción entre los usuarios y los programas en ejecución. Se reúnen todos los trabajos comunes para realizarlos al mismo tiempo, evitando la espera de dos o más trabajos como sucede en el procesamiento en serie. Estos sistemas son de los más tradicionales y antiguos, y fueron introducidos alrededor de 1956 para aumentar la capacidad de procesamiento de los programas. Cuando estos sistemas son bien planeados, pueden tener un tiempo de ejecución muy alto, porque el procesador es mejor utilizado y pueden ser simples, debido a la secuenciabilidad de la ejecución de los trabajos. Algunos ejemplos de Sistemas Operativos por lotes exitosos son el SCOPE, del DC6600, el cual está orientado a procesamiento científico pesado, y el EXEC II para el UNIVAC 1107, orientado a procesamiento académico. Algunas otras características son: • Requiere que el programa, datos y órdenes al sistema sean remitidos todos juntos en forma de lote. • Permiten poca o ninguna interacción usuario/programa en ejecución. • Mayor potencial de utilización de recursos que procesamiento serial simple en sistemas multiusuarios. • No conveniente para desarrollo de programas por bajo tiempo de retorno y depuración fuera de línea. • Conveniente para programas de largos tiempos de ejecución. • Se encuentra en muchos computadores personales combinados con procesamiento serial. • Planificación del procesador sencilla, típicamente procesados en orden de llegada. • Planificación de memoria sencilla, generalmente se divide en dos: parte residente del S.O. y programas transitorios. • No requieren gestión crítica de dispositivos en el tiempo. • Suelen proporcionar gestión sencilla de manejo de archivos: se requiere poca protección y ningún control de concurrencia para el acceso. Sistemas Operativos de Tiempo Real Los Sistemas Operativos de tiempo real son aquellos en los cuales no tiene importancia el usuario, sino los procesos. Por lo general, están subutilizados sus recursos con la finalidad de prestar atención a los procesos en el momento que lo requieran. Se utilizan en entornos donde son procesados un gran número de sucesos o eventos. Muchos Sistemas Operativos de tiempo real son construidos para aplicaciones muy específicas como control de tráfico aéreo, bolsas de valores, control de refinerías, control 4 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos de laminadores. También en el ramo automovilístico y de la electrónica de consumo. Otros campos de aplicación de los Sistemas Operativos de tiempo real son los siguientes: • Control de trenes. • Telecomunicaciones. • Sistemas de fabricación integrada. • Producción y distribución de energía eléctrica. • Control de edificios. • Sistemas multimedia. Algunos ejemplos de Sistemas Operativos de tiempo real son: VxWorks, Solaris, Lyns OS, Spectra, RTLinux. Los Sistemas Operativos de tiempo real, cuentan con las siguientes características: • Se dan en entornos en donde deben ser aceptados y procesados gran cantidad de sucesos, la mayoría externos al sistema computacional, en breve tiempo o dentro de ciertos plazos. • Se utilizan en control industrial, conmutación telefónica, control de vuelo, simulaciones en tiempo real, aplicaciones militares, etc. • Procesa ráfagas de miles de interrupciones por segundo sin perder un solo suceso. • Proceso se activa tras ocurrencia de suceso, mediante interrupción. • Proceso de mayor prioridad expropia recursos. • Por tanto generalmente se utiliza planificación expropiativa basada en prioridades. • Administración de memoria menos exigente que tiempo compartido, usualmente procesos son residentes permanentes en memoria. • Población de procesos estática en gran medida. • Poco movimiento de programas entre almacenamiento secundario y memoria. Sistemas Operativos de multiprogramación ( Multitarea) Se distinguen por sus habilidades para poder soportar la ejecución de dos o más trabajos activos al mismo tiempo. Esto trae como resultado que la Unidad Central de Procesamiento siempre tenga alguna tarea que ejecutar, aprovechando al máximo su utilización. Su objetivo es tener a varias tareas en la memoria principal, de manera que cada uno está usando el procesador, o un procesador distinto, es decir, involucra máquinas con más de una CPU. Todos los sistemas operativos actuales soportan multitareas. UNIX, MACOS, LINUX, Windows, solo por mencionar los más conocidos. Las características de un Sistema Operativo de multiprogramación o multitarea son las siguientes: • Mejora productividad del sistema y utilización de recursos. • Multiplexa recursos entre varios programas. • Generalmente soportan múltiples usuarios (multiusuarios). 5 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos • Proporcionan facilidades para mantener el entorno de usuarios individuales. • Requieren validación de usuario para seguridad y protección. • Proporcionan contabilidad del uso de los recursos por parte de los usuarios. Sistemas Operativos Distribuidos. Permiten distribuir trabajos, tareas o procesos, entre un conjunto de procesadores. Puede ser que este conjunto de procesadores esté en un equipo o en diferentes, en este caso es trasparente para el usuario. Existen dos esquemas básicos de éstos. Un sistema fuertemente acoplado es a es aquel que comparte la memoria y un reloj global, cuyos tiempos de acceso son similares para todos los procesadores. En un sistema débilmente acoplado los procesadores no comparten ni memoria ni reloj, ya que cada uno cuenta con su memoria local. Los sistemas distribuidos deben de ser muy confiables, ya que si un componente del sistema se compone otro componente debe de ser capaz de reemplazarlo. Entre los diferentes Sistemas Operativos distribuidos que existen tenemos los siguientes: Sprite, Solaris-MC, Mach, Chorus, Spring, Amoeba, Taos, etc. Características de los Sistemas Operativos distribuidos: • Colección de sistemas autónomos capaces de comunicación y cooperación mediante interconexiones hardware y software . • Transparentes. • Generalmente proporcionan medios para la compartición global de recursos. • Servicios añadidos: denominación global, sistemas de archivos distribuidos, facilidades para distribución de cálculos. Sistemas Operativos de Red Son aquellos sistemas que mantienen a dos o más computadoras unidas a través de algún medio de comunicación (físico o no), con el objetivo primordial de poder compartir los diferentes recursos y la información del sistema. Sistemas Operativos Paralelos En estos tipos de Sistemas Operativos se pretende que cuando existan dos o más procesos que compitan por algún recurso se puedan realizar o ejecutar al mismo tiempo. En UNIX existe también la posibilidad de ejecutar programas sin tener que atenderlos en forma interactiva, simulando paralelismo (es decir, atender de manera concurrente varios procesos de un mismo usuario). Así, en lugar de esperar a que el proceso termine de ejecutarse (como lo haría normalmente), regresa a atender al usuario inmediatamente después de haber creado el proceso. 6 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos 1.4 Sistemas Operativos para dispositivos móviles Por otra parte, se tiene actualmente los sistemas operativos de los dispositivos móviles, como son: Android, Iphone OS, BlackBerry OS, Symbian, PalmOS, aunque los dos últimos y han dejado de estar presente dado que fueron absorbidos por los tres primeros. Symbian Symbian es un SO que estaba incorporado en los celulares Nokia, su Core es común a todos los dispositivos, cuenta con: kernel, servidor de archivos, administración de memoria, y manejadores de drives. Cuenta con una capa de sistema de comunicación como: TCP/IP, IMAP4, SMS y un administrador de base de datos; Software para la interfaz con el usuario y un conjunto de aplicaciones. En Symbian cada proceso se ejecuta en un espacio de dirección protegido, así como también el kernel tiene su propio espacio protegido. Por lo cual permite que varios procesos sean ejecutados en forma concurrentes. Symbian está escrito en C++, así que es natural el desarrollo de aplicaciones en este lenguaje, aunque también admite el desarrollo en Java. El sistema es multitarea, y cuenta con un hilo principal, el cual se encarga de desprender nuevos hilos para las diferentes actividades a realizar. iPhone iPhone fue creado a partir del Mac OS X, para ser el sistema operativo nativo de los smartphone iPhone, dispositivos touch iPod, y después para las tables iPad. La arquitectura del model de iPhone está formada por los siguientes elementos: • Capa de aplicaciones, formada por las aplicaciones nativas (calendario, fotos, camara, etc..) y aplicaciones instaladas por el cliente, desarrolladas por un tercero. • Capa de Middleware, formada por tres subcapas o Cocoa Touch, este es un framework que proporciona infraestructura necesaria para los desarrolladores o Media, formada por aplicaciones graficas, framework de audio y video para crear aplicaciones multimedias. o Core Services, proporciona el sistema de servicio fundamental que todas las aplicaciones usan de forma directa o indirecta vía un framework de nivel alto. Los framework de address book, core data SQLite library, y Core Location son algunos de los componentes de esta capa. • Kernel, formado entre otras cosas por los drivers, framework de seguridad, CFNetwork. 7 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos BlackBerry El SO BlackBerry es una plataforma de software desarrollada por Research In Motion (RIM) para sus dispositivos smartphone liberados en 1999. El SO cuenta con las siguientes caracteisticas: • • • • Es multitareas, El kernel está basado en Java, Utiliza una arquitectura ARM, El administrador de memoria divide está en tres secciones: Memoria para aplicaciones, Memoria del dispositivo, Memoria de la tarjeta (opcional) Android Android es otro sistema operativo para dispositivos mooviles, el cual tiene las siguientes características: • Almacenamiento, usa SQLite para el alacenamiento de datos. • Conectividad, soporta GSM/EDGE, IDEN, CDMA, UMTS, Bluetooth, Wifi, LTE, WiMax. • Web Browser, Basado en WebKit open-source. • Media, incluye soporte de: H.263, H.264, MPEG-4 SP, AMR, AMR-WB, AAC, HE-AAC, MP3, MIDI, Ogg Vorbis, WAV, JPEG, PNG, GIF y BMP. • Soporte de Hardware, Sensor acelerometro, camara, brújula digital, sensor de aproximación, y GPS • Multi touch, pantalla multi touch • Multitareas, soporta aplicaciones multitareas. • Tethering o pasarela • En runtime contiene librerias Core y la máquina virtual Dalvik • En el kernel que es Linux contiene: Driver Display, Driver de Camara, Driver de Memoria Flash, Driver binder (IPC), Driver Keypad, Driver Wifi, Driver de Audio, Administrador de energía Android está basado en Linux, cuenta con un administrador de memoria, administrador de procesos, mecanismos IPC. En 2005 Google para entrar en la competencia de moviles, propone Androind, sistema operativo basado en una versión modificada de Linux, colocando así a Android como un sistema abierto y libre. Por lo que Android es liberado con una licencia Apache Open-Source. 8 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos 1.5 Partes claves de los sistemas operativos • • • • • Los procesos La gestión de memoria La seguridad y la protección de la información La planificación y la gestión de recursos La estructura del sistema Los errores frecuentes en los procesos son: • • • • Sincronización incorrecta: Es frecuente el caso en el que una rutina debe ser suspendida a la espera de un suceso en cualquier lugar del sistema. Por ejemplo, un programa inicia una lectura de E/S y debe esperar hasta que los datos están disponibles en un buffer antes de continuar. En tales casos se requiere alguna señal proveniente de alguna otra rutina. Fallos de exclusión mutua: Es habitual el caso en que más de un usuario o programa intenta a la vez hacer uso de un recurso compartido. Por ejemplo, en un sistema de reservas de líneas aéreas. Si no se controlan estos accesos, puede producirse un error. Debe existir algún tipo de mecanismo de exclusión mutua que permita que sólo una rutina a la vez pueda realizar una transacción sobre una determinada parte de los datos. Funcionamiento no determinista del programa: Los resultados de un programa en particular deben depender normalmente sólo de la entrada del programa y no de las actividades de otros programas en un sistema compartido. Pero cuando los programas comparten memoria y sus ejecuciones son intercaladas por el procesador, entonces pueden interferir con otros, sobre escribiendo zonas comunes de memoria de forma incierta. Así pues, el orden en que se organiza la ejecución de varios programas puede influir en los resultados de un programa en particular. Interbloqueos: Es posible que dos o más programas estén suspendidos a la espera uno del otro. El S.O. tiene cinco responsabilidades principales en la administración de almacenamiento: • • • • • Aislamiento del proceso: El sistema operativo debe procurar que cada proceso independiente no interfiera con los datos y la memoria de ningún otro. Asignación y gestión automática: A los programas se les debe asignar memoria dinámicamente en la jerarquía de memoria, según la vayan necesitando. Este proceso debe ser transparente para el programador. Soporte para la programación modular: Los programadores deben ser capaces de definir módulos de programa y de crear, destruir y alterar el tamaño de los módulos dinámicamente. Protección y control de acceso: El s.o. debe permitir que las secciones de memoria estén accesibles de varias maneras para los diversos usuarios. Almacenamiento a largo plazo: Muchos usuarios y aplicaciones necesitan medios para almacenar información por largos periodos de tiempo. Normalmente, los s.o. satisfacen estos requisitos mediante la memoria virtual y los servicios del sistema de archivos. La memoria virtual es un servicio que permite a los programas direccionar la memoria desde un punto de vista lógico, sin depender del tamaño de la memoria principal física disponible. 9 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos El crecimiento de la utilización de los sistemas de tiempo compartido y, las redes de computadoras ha traído consigo un aumento en las preocupaciones de seguridad y protección de información. Se han identificado cuatro clases de políticas generales de protección, en orden creciente de dificultad: • • • • • • • No compartición: Los procesos están completamente aislados uno del otro y cada proceso tiene control exclusivo sobre los recursos que le fueron asignados estática o dinámicamente. Con esta política, los procesos suelen compartir los programas o archivos de datos haciendo copias y pasándolas a su propia memoria virtual. Compartiendo los originales de los programas o archivos de datos: Una única copia física de un programa puede aparecer en varios espacios de memoria virtual como archivos de sólo lectura. Subsistemas confinados o sin memoria: En este caso, los procesos se agrupan en subsistemas para cumplir una política de protección en particular. Por ejemplo, un proceso “cliente” llama a un proceso “servidor” para llevar a cabo cierta tarea con los datos. El servidor se protegerá de que el cliente descubra el algoritmo con el cual se lleva a cabo su trabajo y el cliente se protegerá de que el servidor retenga alguna información sobre el trabajo que está llevando a cabo. Diseminación controlada de la información: A los usuarios y a las aplicaciones se les dan credenciales de seguridad de un cierto nivel, mientras que a los datos y a otros recursos (p.e. los dispositivos de E/S) se les dota de clasificaciones de seguridad. La política de seguridad hace cumplir las restricciones relativas a qué usuarios tiene acceso a qué clasificaciones. Control de acceso: Tiene que ver con la regulación del acceso del usuario al sistema completo, a los subsistemas y a los datos, así como a regular el acceso de los procesos a los recursos y objetos del sistema. Control del flujo de información: Regula el flujo de datos dentro del sistema y su distribución a los usuarios. Certificación: Es relativa a la demostración de que el acceso y los mecanismos de control del flujo se llevan a cabo de acuerdo a las especificaciones y a que estas cumplen las políticas de protección y seguridad deseadas. Una tarea clave del s.o. es administrar los recursos que tiene disponibles y planificar su utilización por parte de los diferentes procesos activos. Cualquier política de planificación y administración de recursos cuenta con los tres factores siguientes: • • • Equidad: Sería conveniente que a todos los procesos que compitan por el uso de un recurso se les otorgue un acceso igualitario y equitativo, especialmente si estos procesos pertenecen a la misma clase. Sensibilidades diferenciales: El sistema operativo debe intentar tomar decisiones de asignación y planificación que satisfagan la totalidad de los requisitos. El s.o. debe contemplar estas decisiones dinámicamente. Por ejemplo, si un proceso está esperando por el uso de un dispositivo de E/S, el s.o. puede querer planificar la ejecución de dicho proceso tan pronto como sea posible y así tener disponible al dispositivo para las demandas de otros procesos. Eficiencia: El s.o. debe intentar maximizar la productividad, minimizar el tiempo de respuesta y, en el caso de tiempo compartido, alojar a tantos usuarios como sea posible. 10 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos La tarea de planificación y gestión de recursos es básicamente un problema de investigación operativa, así que se pueden aplicar los resultados matemáticos de esta disciplina. Hablemos ahora de la parte clave de un sistema operativo, la Estructura del sistema. El tamaño de un sistema operativo completo y la dificultad de las tareas que lleva a cabo plantean tres problemas desafortunados pero demasiado habituales: • Los sistemas operativos cuando se entregan ya están cronológicamente retrasados. Esto conduce a nuevos sistemas operativos y actualizaciones de los anteriores. • Los sistemas tienen fallos latentes que se manifiestan en el terreno y que deben ser detectados y corregidos. • Su rendimiento no es a menudo el que se esperaba. Tabla 1-1. Jerarquía del Diseño de un Sistema Operativo. Nivel Nombre Objetos Ejemplos de operaciones 13 Shell 12 11 Procesos de usuario Directorios 10 Dispositivos 9 8 Sistema de archivos Comunicaciones 7 Memoria virtual 6 Almacenamiento secundario local Procesos primitivos. 5 4 Interrupciones 3 Procedimientos. 2 Conjunto de instrucciones 1 Circuitos electrónicos Entorno de programación Sentencias de un lenguaje de del usuario. shell Procesos de usuario. Salir, eliminar, suspender, reanudar. Directorios. Crear, destruir, conectar, desconectar, buscar, listar Dispositivos externos Crear, destruir, abrir, cerrar, tales como impresoras, leer, escribir pantallas y teclados. Archivos. Crear, destruir, abrir, cerrar, leer, escribir Tubos (pipes). Crear, destruir, abrir, cerrar, leer, escribir Segmentos, páginas. Leer, escribir, traer (fech) Bloques de datos, Leer, escribir, asignar, liberar canales de dispositivos. Procesos primitivos, Suspender, reanudar, esperar, semáforos, colas de señalizar. procesos listos. Programas de tratamiento Invocar, enmascarar, de interrupciones. desenmascarar, reintentar. Procedimientos, pila de Marcar la pila, llamar, retornar. llamadas, visualización. Evaluación de la pila, Cargar, almacenar, sumar, intérprete de restar, bifurcar. microprogramas, vectores de datos y escalares. Registros, puertas, buses, Borrar, transferir, activar, etc. completar. 11 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Capítulo 2 Procesos e Hilos Un proceso es una entidad en ejecución que tiene asociada un identificador para poder ser identificado. En el sistema se cuenta con procesos pesados, llamados hijos, y procesos ligeros, llamados hilos. 2.1 PROCESOS Todos los sistemas de multiprogramación están construidos en torno al concepto de procesos. El modelo más sencillo que puede construirse tiene en cuenta que, en un momento dado, un proceso puede estar ejecutándose en el procesador o no. Así pues, un proceso puede estar en uno de dos estados: Ejecución o No ejecución (Figura 2-1). Figura 2-1. Diagrama de transición de estados. Aún en este modelo tan simple se puede apreciar algunos de los elementos del diseño del sistema operativo, cada proceso debe representarse de forma que el sistema operativo pueda seguirle la pista. Esto es, debe haber información relativa a cada proceso, incluyendo su estado actual y su posición en memoria. Aquellos procesos que no están ejecutándose tiene que guardarse en algún tipo de cola, para que esperen su turno de ejecución (Figura 2-2). La vida de un proceso está limitada por su creación y su terminación. 12 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Figura 2-2. Diagrama de colas. Creación de procesos Cuando se añade un proceso a los que ya está administrando el sistema operativo hay que construir las estructuras de datos que se utilizan para administrar el proceso y asignar el espacio de direcciones que va a utilizar el proceso. Existen cuatro sucesos comunes que llevan a la creación de un proceso: • • • • En un entorno de trabajo por lotes, un proceso se crea como respuesta a la remisión de un trabajo. En un entorno interactivo, se crea un proceso cuando un nuevo usuario intenta conectarse. Proceso generado por un proceso existente. Creado por el S.O. para dar un servicio. Cuando un proceso genera otro, el proceso generador se conoce como proceso padre y el proceso generado es el proceso hijo. Normalmente, estos procesos “emparentados” necesitarán comunicarse y cooperar entre sí. Terminación de procesos En cualquier sistema, debe haber alguna forma de que un proceso pueda indicar que ha terminado (vea Tabla 2-1). Tabla 2-1. Terminación de proceso. Razones para terminación de un proceso Terminación normal Tiempo límite excedido No hay memoria disponible. Violación de límites. Error de protección. Error aritmético. El proceso ejecuta una llamada a un servicio del SO que indica que ha terminado de ejecutarse. El proceso se ha ejecutado por más del límite especificado. El proceso necesita más memoria de la que el sistema le puede proporcionar. El proceso trata de acceder a una posición de memoria a la que no le está permitido. El proceso intenta utilizar un recurso o un archivo que no le está permitido utilizar, o trata de utilizarlo de forma incorrecta, como escribir en un archivo que es sólo de lectura. El proceso intenta hacer un cálculo prohibido, o trata de almacenar un número mayor del que el hardware acepta. 13 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Tiempo máximo de espera rebasado. Fallo de E/S Instrucción privilegiada. Intervención del operador o del SO. Terminación del padre. Solicitud del padre. El proceso ha esperado más allá del tiempo máximo especificado para que se produzca cierto suceso. Se produce un error en la entrada o la salida, tal como no encontrar un archivo, un fallo de lectura o escritura después de un número máximo de intentos. El proceso intenta usar una instrucción reservada para el SO. Por alguna razón el operador o el sistema operativo terminan con el proceso. Cuando un proceso padre finaliza, el SO. Puede diseñarse para terminar automáticamente con todos sus descendientes. Un proceso padre tiene normalmente la autorización de terminar con cualquiera de sus descendientes. Identificadores de procesos Todo proceso tiene asociado un identificador (número positivo PID) y un padre con su respectivo identificador (número de proceso que ha creado al proceso actual PPID). Para obtener estos valores se utilizan los llamados getpid y getppid, respectivamente. Los procesos pueden estar agrupados en conjuntos que tienen alguna característica en común, como ejemplo el mismo padre. Para determinar a que grupo pertenece un proceso, se utiliza getpgrp, y para cambiar a un proceso como líder de un grupo se hace uso del llamado setpgrp. PROTOTIPO DE LA FUNCIÓN ! #include <sys/types.h> #include <unistd.h> ! ! pid_t getpgrp(void); pid_t setpgrp(void); Se debe tener en consideración que cuando el proceso padre llega a morir antes que los hijos, el PPID del proceso hijo toma el valor de 1, que es el proceso init. Estado de un proceso (modelo de cinco estados) Si todos los procesos estuvieran siempre listos para ejecutar, entonces la disciplina de cola propuesta anteriormente sería eficaz, sin embargo, aún en el simple ejemplo que se ha descrito, esta implementación no es adecuada: algunos procesos en el estado de No Ejecución están listos para ejecutar, mientras que otros están bloqueados, esperando a que termine una operación de E/S. Así pues, utilizando una cola sencilla, el distribuidor podría no seleccionar exactamente el proceso que está en el extremo más antiguo de la 14 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos cola. Más bien, el distribuidor tendría que recorrer la lista buscando el proceso que no esté “no bloqueado” y que lleve más tiempo en la cola. Una forma más natural de afrontar esta situación es dividir el estado de No Ejecución en dos estados: Listo y Bloqueado. De esta forma contamos ahora con cinco estados (Figura 2-3): • • • • • Ejecución. Proceso que está actualmente en ejecución. Listo. Proceso que está preparado para ejecutar, en cuanto se le dé la oportunidad. Bloqueado. Proceso que no puede ejecutarse hasta que se produzca cierto suceso, como la terminación de una operación de E/S. Nuevo. Proceso que se acaba de crear, pero que aún no ha sido admitido por el sistema operativo en el grupo de procesos ejecutables. Terminado. Proceso que ha sido excluido del grupo de procesos ejecutables, bien porque se detuvo o porque fue abandonado por alguna razón. Seleccionado para ejecución Proceso creado Nuevo Listo Tiempo terminado E/S completa Terminación normal/anormal Ejecutado Solicitud de E/S Hecho Bloqueado Figura 2-3. Diagrama de estado para un sistema operativo sencillo. Un proceso puede moverse voluntariamente al estado bloqueado haciendo una llamada a una función como sleep. _________________________________________________________________________ EJERCICIO PROPUESTO _________________________________________________________________________ jueves 27 de junio de 2013 1. Investigue los procesos activos en su sistema. a) ¿Que instrucción le permite visualizarlos? b) ¿Qué parámetros son utilizados con dicha instrucción? 2. En el sistema Linux, investigue el uso del comando ps, y del comando top a) ¿Cuál es la diferencia entre ellos? b) ¿Cuál es el proceso 1? 15 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos c) ¿Quién es el padre del proceso 1? _________________________________________________________________________ Creación de procesos en Linux Linux crea los procesos a través de una llamada fork al sistema, copiado la imagen en memoria que tiene el proceso padre. El nuevo proceso recibe una copia del espacio de direcciones del padre. Los procesos continúan su ejecución en la instrucción que está después del fork. PROTOTIPO DE LA FUNCIÓN ! ! ! #include <sys/types.h> #include <unistd.h> pid_t fork(void); La creación de dos procesos totalmente idénticos no es algo muy útil. El valor devuelto por fork es la característica distintiva, importante, que permite que el padre y el hijo ejecuten código distinto. El fork devuelve 0 al hijo y el ID del hijo al padre. EJEMPLO. En el siguiente fragmento de código tanto el padre como el hijo ejecutan la instrucción de asignación x =1 después del regreso de fork. #include <sys/types.h> #include <unistd.h> x = 0; fork(); x = 1; fork crea procesos nuevos haciendo una copia de la imagen del padre en la memoria. El hijo hereda la mayor parte de los atributos del padre, incluyendo el ambiente y los privilegios. El hijo también hereda algunos de los recursos del padre, tales como los archivos y dispositivos abiertos. No todos los atributos o recursos del padre son heredados por el hijo. Este último tiene un ID de proceso nuevo y, claro que es diferente del ID del padre. Los tiempos del hijo para el uso del CPU son iniciados a 0. El hijo no obtiene los 16 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos bloqueos que el padre mantiene. Si el padre ha colocado una alarma, el hijo no recibe notificación alguna del momento en que ésta expira. El hijo comienza sin señales pendientes, aunque el padre las tenga en el momento en que se ejecuta el fork. Aunque el hijo hereda la prioridad del padre y los atributos de la administración de procesos, tiene que competir con otros procesos, como entidad aparte, por el tiempo del procesador. El identificador del proceso se recupera por medio de la instrucción getpid(), y el identificador del proceso que es su padre por medio de la instrucción getppid(). Los prototipos de estas instrucciones son las siguientes. PROTOTIPO DE LA FUNCIÓN ! ! #include <sys/types.h> #include <unistd.h> pid_t getpid(void); PROTOTIPO DE LA FUNCIÓN ! ! #include <sys/types.h> #include <unistd.h> pid_t getppid(void); El tipo de dato pid_t representa el ID del proceso. De esta manera se puede obtener el ID del proceso invocando getpid; y la función getppid retorna el ID del proceso padre del proceso actual. El tipo de dato pid_t es un tipo entero con signo el cual representa el ID del proceso. En la librería GNU C, esto es un int. EJEMPLO. En el siguiente ejemplo después de la llamada fork, los procesos padre e hijo imprimen sus identificadores. #include <stdio.h> #include <sys/types.h> #include <unistd.h> 17 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos pid_t hijo; void main (void) { if ( (hijo = fork ( )) = = 0) { fprintf (stderr, “soy el hijo, ID= %ld\n”, (long)getpid()); /* Aquí coloca el código que deseas que realice el proceso hijo */ } else if (hijo > 0) { fprintf (stderr, “soy el padre, ID = %ld\n”, (long)getpid()); /* Aquí coloca el código que deseas que realice el proceso padre */ } } EJEMPLO. Con el siguiente fragmento de código se crea una cadena de n procesos. #include <stdio.h> #include <sys/types.h> #include <unistd.h> int i, n; pid_t hijo; void main (void) { for (i = 1; i < n; i++) { if (hijo = fork()) break; /* mientras no sea diferente de cero , es decir, no exista error */ fprintf (stderr, “Este es el proceso %ld con padre %ld \n”, (long)getpid(), (long)getppid()); } } EJEMPLO. El siguiente fragmento de código crea un abanico de procesos. #include <stdio.h> #include <sys/types.h> #include <unistd.h> int i, n; pid_t hijo; void main (void) { for (i = 1; i < n; i++) { if (hijo = fork() <=0) break; /* después de crear un proceso, el proceso creador termina */ fprintf (stderr, “Este es el proceso %ld con padre %ld \n”, (long)getpid(), (long)getppid()); } } 18 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos El sistema de llamada wait () ¿Qué sucede con el proceso padre después de que éste crea un hijo? Tanto el padre como el hijo continúan la ejecución desde el punto donde se hace la llamada a fork. Si el padre desea esperar hasta que el hijo termine, entonces debe ejecutar una llamada a wait o a waitpid. PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <sys/wait.h> pid_t wait (int *stat_loc) La llamada al sistema wait detiene el proceso que llama hasta que un hijo de éste termine o se detenga, o hasta que el proceso que la invocó reciba una señal. wait regresa de inmediato si el proceso no tiene hijos o si el hijo termina o se detiene y aún no se ha solicitado la espera. Si wait regresa debido a la terminación de un hijo, el valor devuelto es positivo e igual al ID de proceso de dicho hijo. De lo contrario, wait devuelve –1 y pone un valor en errno. Si errno es igual a ECHILD, indica que no existen procesos hijos a los cuales hay que esperar. Si errno es igual a EINTR, la llamada fue interrumpida por una señal. stat_loc es un apuntador a una variable entera. Después del llamado a wait, el estado de la información almacenada en la localidad apuntada por stat_loc puede ser analizada aplicando los siguiente macros: WIFEXITED (*stat_loc). Si en la evaluación el valor no es cero (true) entonces el proceso hijo termino normal. WEXITSTATUS (*stat_loc). Si el proceso hijo termina normalmente, este macro evalúa los 8 bits mas bajos del valor pasado por la función exit , _exit o return desde la función main. WIFSIGNALED (*stat_loc). Si en la evaluación el valor no es cero (true) el proceso hijo termino porque recibió una señal no controlada. WTERMSIG (*stat_loc). Si el proceso hijo finaliza por una señal que no fue capturada, este macro evalúa el número de la señal. El hijo regresa su estado llamando a exit, _exit o return. Por otra parte cuando se necesite esperar que un proceso especifico termine, se debe utilizar la función waitpid la cual suspende la ejecución del proceso en curso hasta que el hijo especificado por el argumento pid ha terminado, o hasta que se produce una señal 19 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos cuya acción es finalizar el proceso actual o llamar a la función manejadora de la señal. PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <sys/wait.h> pid_t waitpid (pid_t pid, int *status, int options) *status es la localidad en la cual se devuelve la información del estado de terminación del hijo, pid contiene un valor que puede ser, -1 para indicar que espera a cualquier hijo, positivo indica que debe esperar al proceso cuyo PID sea ese número, 0 para indicar que espere a cualquier hijo cuyo Process Group ID sea igual al del proceso que invoco el llamado, negativo indica que espere a cualquier proceso cuyo Process Group ID sea igual al valor absoluto de PID. En options se coloca una combinación de las siguientes banderas: WEXITED que espera por hijos que hayan terminado, WSTOPPED que espera por hijos que hayan sido parados por recibir una señal, WNOHANG que no espera por un hijo que esta en ejecución, WNOWAIT deja al hijo sin modificar ni marcar en la tabla de procesos, tal que una posterior llamada se comportaría como si no hubiésemos hecho wait por dicho hijo, WUNTRACED que no esperar si el hijo está parado a no ser que este siendo trazado, WCONTINUED volver si un hijo ha continuado ejecutándose tras mandarle la señal SIGCONT. Las banderas WUNTRACED y WCONTINUED solo son efectivas si SA_NOCLDSTOP no ha sido establecida para la señal SIGCHLD. Terminación de procesos Como se ha visto el proceso debe terminar de alguna manera, es decir, de forma normal o anormal. Lo que se desea es una terminación normal, en el mejor de los casos, para esto el proceso debe hacer un llamado a la función exit. Esta función se encarga de retirar los recursos que está utilizando el proceso, así como dejarlo preparado para su eliminación, quitarlo del planificador e indicar su terminación a su padre, por medio de la señal SIGCHLD. Para pasar de un estado a otro se define un estado transitorio que en el sistema se le llama zombie. En linux si el proceso que termina no tuviera padre, ya que este acabó antes que él, se eliminaría directamente del planificador en la llamada exit, y es adoptado por el proceso 1 (init). 20 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos PROTOTIPO DE LA FUNCIÓN #include <stdlib.h> void exit (int status) El llamado a exit () termina la ejecución del proceso y devuelve el valor de estatus al proceso padre. Desde el sistema en Linux se puede consultar lo que devuelve el último proceso que finaliza por medio de la variable de entorno “?”. Ejemplo. Programa que muestra el empleo de wait. #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> main() { pid_t hijo; int estado; if ( (hijo=fork()) == -1) { perror ("fallo el fork"); exit (1); } else if (hijo == 0) fprintf (stderr, "soy el hijo con pid = %ld \n", (long) getpid()); else if (wait(&estado) !=hijo) fprintf (stderr, "una señal debio interrumpir la espera \n"); else fprintf (stderr, "soy el padre con pid = %ld e hijo con pid = %ld\n", (long)getpid(),(long)hijo); exit(0); } Descripción de procesos El Sistema Operativo es el controlador de los sucesos que se producen en un sistema de computo. Es el Sistema Operativo el que planifica y expide a los procesos para su ejecución en el procesador, el que asigna los recursos a los procesos y el que 21 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos responde a las solicitudes de servicios básicos realizadas por los programas de usuario. Este concepto queda ilustrado en la Figura 2-4. Imagen del proceso Tabla de memoria Proceso 1 Tabla de E/S Memoria Dispositivos Tabla de archivos Archivos Procesos Tabla de procesos proceso 1 proceso 2 proceso 3 .. . Imagen del proceso Proceso n proceso n Figura 2-4. Procesos y recursos. 2.2 Estructuras de control del sistema operativo Si el Sistema Operativo va a administrar los procesos y los recursos, entonces tiene que disponer de información sobre el estado actual de cada proceso y de cada recurso. El método universal para obtener esta información es sencillo: el sistema operativo construye y mantiene tablas de información sobre cada entidad que esté administrando. P1 P2 Pn Memoria Virtual Recursos Procesador E/S E/S E/S Memoria Principal Figura 2-5. Procesos y recursos. Debemos tener claro que las tablas deben estar enlazadas o disponer de referencias cruzadas de alguna manera. La memoria, la E/S y los archivos son administrados en nombre de los procesos, por lo que debe haber alguna referencia directa o indirecta a estos recursos en la tablas de procesos. Los archivos que son referidos en las tablas de archivos son accesibles a través de un dispositivo de E/S y, algunas veces, estarán en memoria principal o en memoria virtual (Figura 2-5). 22 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Operaciones sobre procesos Los sistemas que administran procesos deben ser capaces de realizar ciertas operaciones sobre los procesos y con ellos. Tales operaciones incluyen: • • • • • • • • • Crear un proceso Destruir un proceso Suspender un proceso Reanudar un proceso Cambiar la prioridad de un proceso Bloquear un proceso Despertar un proceso Despachar un proceso Permitir que un proceso se comunique con otro (comunicación entre procesos) Crear un proceso implica operaciones tales como: • • • • • Dar un nombre al proceso Insertarlo en la lista de procesos conocidos del sistema (o tabla de procesos) Determinar la prioridad inicial del proceso Crear el bloque de control de proceso Asignar los recursos iniciales al proceso. 2.3 Sistema de llamada exec La llamada fork al sistema crea una copia del proceso que la llama. Muchas aplicaciones requieren que el proceso hijo ejecute un código diferente del de su padre. La familia exec de llamadas al sistema proporciona una característica que permite traslapar al proceso que llama con un módulo ejecutable nuevo. La manera tradicional de utilizar la combinación fork-exec es dejar que el hijo ejecute el exec para el nuevo programa mientras el padre continúa con la ejecución del código original. Existen seis variaciones de la llamada exec al sistema, las cuales se distinguen por la forma en que son pasados los argumentos de la línea de comando y el ambiente, y por si es necesario proporcionar la ruta de acceso y el nombre del archivo ejecutable. execl (execl, execlp y execle). Pasan los argumentos de la línea de comando como una lista y son útiles si se conoce el número de argumentos de la línea de comando en el momento de la compilación. 23 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos PROTOTIPO DE LA FUNCIÓN #include <unistd.h> int execl (const char *path, const char *arg0, … , const char *argn, char * / *NULL*/); int execle (const char *path, const char *arg0, … , const char *argn, char * /*NULL*/, const char *envp[]); int execlp (const char *file, const char *arg0, … ,const char *argn, char */*NULL*/); execv (execv, execvp y execve). Pasan los argumentos de la línea de comando en un arreglo de argumentos. PROTOTIPO DE LA FUNCIÓN #include <unistd.h> int execv (const char *path, const char *argv[]); int execvp (const char *file, const char *argv[]); int execve (const char *’path, const char *argv[], const char *envp[]); EJEMPLO. Programa que crea un proceso para ejecutar ls –l. #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> main( ) { pid_t hijo; int estado; if (( hijo = fork( )) = = -1) { perror (“Error al ejecutar fork”); exit (1); } 24 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos else if (hijo = = 0) { if (execl (“/usr/bin/ls”, “ls”, “-l”, NULL) < 0) { perror (“Falla en la ejecución de ls”); exit (1); } } else if (hijo ! = wait (&estado)) perror (“Se presento una señal antes de la terminación del hijo”); exit (0); } NOTA. La función perror muestra un mensaje con el error estándar seguido por el de la última llamada al sistema o la biblioteca que lo produjo. PROTOTIPO DE LA FUNCIÓN #include <stdio.h> int perror ( const char *s) El DIAGRAMA de estado para los procesos de UNIX es bastante complejo. • • • • • • • • • El proceso se está ejecutando en modo usuario. El proceso se está ejecutando en modo kernel. El proceso no se está ejecutando, pero está listo para ejecutarse tan pronto como el kernel lo ordene. El proceso está durmiendo cargado en memoria. El proceso está listo para ejecutarse, pero el swapper (proceso 0) debe cargar el proceso en memoria antes de que el kernel pueda ordenar que pase a ejecutarse. El proceso está durmiendo y el swappper ha descargado el proceso hacia una memoria secundaria (área de swap del disco) para crear espacio en la memoria principal donde poder cargar otros procesos. El proceso está volviendo del modo kernel al modo usuario, pero el kernel se apropia del proceso y hace un cambio de contexto, pasando otro proceso a ejecutarse en modo usuario. El proceso acaba de ser creado y está en un estado de transición; el proceso existe, pero ni está preparado para ejecutarse (estado 3), ni durmiendo (estado 4). Este estado es el inicial para todos los procesos, excepto el proceso 0. El proceso ejecuta la llamada exit y pasa al estado zombi. El proceso ya no existe, pero deja para su proceso padre un registro que contiene el código de salida y 25 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos algunos datos estadísticos tales como los tiempos de ejecución. El estado zombi es el estado final de un proceso. // Colocar figura de estados de los procesos en UNIX 2.4. Hilos Los hilos representan una manera de trabajar con diferentes procesos no emparentados. Para utilizarlos se necesita contar con la librería pthreads que es un estándar de POSIX (Portable Operating System Interface). Para los sistemas UNIX, un estándar de programación C para interface de programación de hilos se encuentra especificada por el estándar IEEE POSIX 1003.1c. Las implementaciones que se basan en este estándar son referenciados como POSIX threads o Pthreads. La diferencia entre un hilo y un proceso es que los procesos no comparten la misma memoria, mientras que los hilos sí comparten totalmente la memoria entre ellos. Para la creación de los hilos se usan las funciones de la librería pthread o de cualquier otra que soporte threads mientras que para crear procesos usaremos la llamada al sistema fork(). Para compilar programas que hagan uso de la librería pthread, usando GNU cc o GNU Compiler Collection gcc ejecutamos la orden: cc programa_con_pthreads.c -o programa_con_pthreads -lpthread o gcc programa_con_pthreads.c -o programa_con_pthreads -lpthread Tabla 2-2. Lista de llamados en POSIX 1.c. Descripción POSIX Gestión de hilos pthread_create pthread_exit pthread_kill pthread_join pthread_self Exclusión mutua pthread_mutex_init pthread_mutex_destroy pthread_mutex_lock pthread_mutex_trylock pthread_mutex_unlock 26 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos variables de condición pthread_cond_init pthread_cond_destroy pthread_cond_wait pthread_cond_timedwait pthread_cond_signal pthread_cond_broadcast Creación de hilos La función pthread_create es usada para crear un hilo, con ciertos atributos, y esté ejecutará una determinada función o subrutina con los argumentos que se le indique. PROTOTIPO DE LA FUNCIÓN #include <pthread.h> int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void* (*start_routine) (void *), void *arg) Los atributos para un proceso son especificados en attr, si attr es NULL, el atributo por omisión es usado. Si la función se realiza con éxito, se almacena el ID del hilo en la localidad referenciada por thread. El hilo que está creado ejecuta la función o rutina start_routine con arg como su argumento. Si necesitamos pasar o devolver más de un parámetro a la vez, se puede crear una estructura y colocar ahí los campos necesarios. Luego se pasa o se devuelve la dirección de esta estructura como único parámetro. Cuando finaliza start_routine se llama implícitamente a pthread_exit() o su equivalente. El estado de las señales del nuevo hilo será: • Se heredará la máscara de señales del hilo creador • El conjunto de señales pendientes del nuevo hilo estará vacío. Un hilo termina si: • Se llama a pthread_exit(), especificando un valor de estado final que es disponible para otros hilos en el mismo proceso llamando a pthread_join() • Realiza un return desde start_routine que es equivalente a pthread_exit() • Es cancelado pthread_cancel() • El hilo principal ejecuta un return desde main. Causando la terminación de todos los hilos. 27 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos pthread_create retorna 0 en caso de éxito, o un número de error en otro caso, y el contenido de *thread se indefine. Los errores que pueden retornados en errno son: • EAGAIN. Insuficiente recursos para crear un hilo, o el límite de los hilos del sistema fueron alcanzados (en Linux vea /proc/sys/kernel/threads-max) • EINVAL. Inválido el atributo. • EPERM. Política de planificación no permitida y los parámetros de attr. Terminación de un hilo La función pthread_exit() terminará la ejecución del hilo que haga su invocación y hará disponible el valor value_ptr para cualquier join con éxito con el hilo que hace la llamada. PROTOTIPO DE LA FUNCIÓN #include <pthread.h> int pthread_exit (void *value_ptr) Atributos de un hilo La función pthread_attr_init () se encarga de inicializar el objeto de atributos attr del hilo con los valores por defecto utilizado en una implementación. PROTOTIPO DE LA FUNCIÓN #include <pthread.h> int pthread_attr_init ( pthread_attr_t *attr) Destrucción de los atributos de un hilo La función pthread_attr_destroy () se encarga de destruir el objeto de atributos attr del hilo con valores no definidos en una implementación. Un objeto de atributos destruido con función podrá ser inicializado posteriormente con la función pthread_attr_init(). 28 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos PROTOTIPO DE LA FUNCIÓN #include <pthread.h> int pthread_attr_destroy (pthread_attr_t *attr) Espera la terminación de un hilo creado La función pthread_join() suspenderá la ejecución del hilo que hace la llamada, hasta que el hilo destino termine. Esta función es un mecanismo que permite la sincronización de hilos. PROTOTIPO DE LA FUNCIÓN #include <pthread.h> int pthread_join ( pthread_t thread, void **value_ptr) El valor que pase el thread destino con la función pthread_exit() estará disponible en la localidad de memoria a la que hace referencia value_ptr. La función espera hasta que termine el hilo especificado en thread. La función retorna 0 en caso de éxito o el número de error en otro caso. Los errores pueden ser: • EDEADLK. Un deadlock fue detectado, es decir, dos hilos esperan uno del otro. • EINVAL. thread no está unido a otro hilo. • ESRCH. El hilo con ID thread no fue encontrado. Hilo Origen Creando hilo pthread_create() Esperando pthread_join () Hilo Creado (en ejecución) Hilo Terminando pthread_exit() Hilo Origen Figura 2-6. Sincronización de hilos. 29 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos EJEMPLO. El siguiente código muestra la creación de un par de hilos. #include <stdio.h> #include <stdlib.h> #include <pthread.h> void *funcionmensaje(void *ptr); int main (void) { pthread_t hilo1, hilo2; char *mensaje1= "hilo 1"; char *mensaje2= "hilo 2"; int uno,dos; uno= pthread_create (&hilo1, NULL, funcionmensaje, (void *)mensaje1); dos= pthread_create (&hilo2, NULL, funcionmensaje, (void *)mensaje2); pthread_join (hilo1,NULL); pthread_join (hilo2,NULL); printf ("Hilo 1 retorna: %d\n",uno); printf ("Hilo 2 retorna: %d\n",dos); return(0); } void *funcionmensaje (void *ptr) { char *mensaje; } mensaje= (char *)ptr; printf("%s\n",mensaje); Referencia [1] Posix Threads Programming. https://computing.llnl.gov/tutorials/pthreads/ [2] K. A. Robbins, S. Robbins, UNIX Programación Práctica, Prentice Hall, Primera Edición, 1997. 30 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Capítulo 3 Administración de procesos Un proceso es una entidad en ejecución que tiene asociada un identificador para poder ser identificado. En el sistema se cuenta con procesos pesados, llamados hijos, y procesos ligeros, llamados hilos. 3.1 Concurrencia: Exclusión mutua y Sincronización Los puntos clave en los que se basan los Sistemas Operativos son los procesos. Todos los procesos cuentan con las siguientes dos características: • • Unidad de propiedad de los recursos: A cada proceso se le asigna un espacio de direcciones virtuales para almacenar la imagen del proceso, se le asigna memoria virtual y otros recursos, tales como canales de E/S, dispositivos de E/S y archivos. A esto se le suele llamar tarea. Unidad de expedición: Un proceso es una camino de ejecución a través de uno o más programas. Esta ejecución puede ser intercalada con la de otros procesos. De este modo, un proceso tiene un estado de ejecución (Ejecución, listo, etc.) y una prioridad de expedición. Cuando los procesos obtienen acceso a datos compartidos modificables, se debe poner en práctica una acción llamada exclusión mutua, y sólo cuando los procesos realizan operaciones que no entran en conflicto con otras, debe permitirse que procedan concurrentemente. Cuando n proceso obtiene acceso a datos compartidos modificables, se dice que se encuentran en una sección crítica (o región crítica). Es evidente que, para evitar problemas, los demás procesos (o al menos los que tengan acceso a los datos compartidos) no puedan entrar a sus propias secciones críticas. Las secciones críticas deben ejecutarse tan rápido como sea posible; un proceso no se debe bloquear dentro de su propia sección crítica y las secciones críticas deben codificarse con mucho cuidado (para evitar, por ejemplo, la posibilidad de ciclos infinitos). Si un proceso de una sección crítica termina, ya sea voluntaria o involuntariamente, el Sistema Operativo, al realizar su mantenimiento de terminaciones, debe liberar la exclusión mutua de manera que otros procesos puedan entrar en sus regiones críticas. 31 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Requisitos para la exclusión mutua El uso adecuado de la concurrencia entre procesos exige la capacidad de definir secciones críticas y hacer cumplir la exclusión mutua. Esto es fundamental para cualquier esquema de proceso concurrente. Cualquier servicio o capacidad que dé soporte para la exclusión mutua debe cumplir los requisitos siguientes: • • • • Los procesos que se encuentran fuera de su sección crítica no pueden impedir que otros procesos entren en sus propias secciones críticas. Un proceso permanece en su sección crítica sólo por un tiempo finito. No se pueden hacer suposiciones sobre la velocidad relativa de los procesos o su número. No se debe postergar indefinidamente la entrada de un proceso a su sección crítica. Típicamente en la teoría de sistemas operativos se tratan en primer lugar los algoritmos de Dekker y de Peterson, esto para mostrar al estudiante la manera que se ha intentado resolver el problema de que dos o mas procesos compartan un determinado recurso, y pongan en practica la exclusión mutua. Así que veamos primero los intentos de Dekker, de mostrar al lector la exclusión mutua con dos procesos concurrentes, y el algoritmo de Peterson para solucionar también la exclusión mutua de dos procesos. 32 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos 3.1 Algoritmos de DEKKER Primer intento: Especifica el código que permite poner en práctica la exclusión mutua en el contexto de un programa concurrente con dos procesos /* Variable global compartida */ int numero_proceso; /**** Inicia principal ***/ numero_proceso = 1; /** Inicia concurrente */ PROCESO_UNO ( ); PROCESO_DOS ( ); /** termina concurrencia ***/ /*** Termina principal ****/ Procesos Concurrentes PROCESO_UNO while true { while (numero_proceso == 2); < sección crítica uno > numero_proceso = 2; /* salir de la exclusión mutua */ otras_tareas_uno; } PROCESO_DOS while true { while (numero_proceso == 1); < sección crítica dos > numero_proceso = 1; /* salir de la exclusión mutua */ otras_tareas_dos; } NOTAS. • • • Como el procesador está en uso mientras PROCESO_DOS no hace nada (excepto verificar el valor de número_proceso), esto se conoce como espera activa. La espera activa se considera una técnica aceptable cuando las esperas anticipadas son cortas; de lo contrario, la espera activa puede resultar costosa. Los procesos deben entrar y salir de sus secciones críticas de manera estrictamente alternada. Si uno de los procesos necesita hacerlo con mucha mayor frecuencia que el otro, se verá restringido a operar con una velocidad mucho menor que la requerida. Un problema mucho más serio es que si un proceso falla, el otro proceso se bloquea permanentemente. 33 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Segundo intento /* Variable global compartida */ p1adentro, p2adentro: boolean; /**** Inicia principal ***/ p1adentro = false; p2adentro = false; /** Inicia concurrente */ PROCESO_UNO ( ); PROCESO_DOS ( ); /** termina concurrencia ***/ /*** Termina principal ****/ Procesos Concurrentes PROCESO_UNO while true { while (p2adentro); p1adentro = true; < sección crítica uno > p1adentro = false; /* salir de la exclusión mutua */ otras_tareas_uno; } PROCESO_DOS while true { while (p1adentro); p2adentro = true; < sección crítica dos > p2adentro = false; /* salir de la exclusión mutua */ otras_tareas_dos; } NOTAS. El problema que existe en esta versión es que ni siquiera se garantiza la exclusión mutua. Este problema se debe a que, entre el momento en que un proceso determina mediante la condición while que puede seguir adelante y el momento en que asigna el valor cierto a una bandera para indicar que se encuentra en su sección crítica, hay tiempo suficiente para que el otro proceso verifique su bandera y entre en su sección crítica. Por lo tanto, en el momento en que un proceso realiza su verificación deberá estar seguro de que el otro proceso no podrá pasar su verificación. El segundo intento falla porque un proceso puede cambiar su estado después de que el otro proceso lo ha comprobado pero antes de que pueda entrar en su sección crítica. Quizá se pueda arreglar este problema con un simple intercambio de dos líneas. 34 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Tercer intento /* Variable global compartida */ p1deseaentrar, p2deseaentrar: boolean; /**** Inicia principal ***/ p1deseaentrar = false; p2deseaentrar = false; /** Inicia concurrente */ PROCESO_UNO ( ); PROCESO_DOS ( ); /** termina concurrencia ***/ /*** Termina principal ****/ Procesos Concurrentes PROCESO_UNO PROCESO_DOS while true while true { { p1deseaentrar = true; p2deseaentrar = true; while (p2deseaentrar); while (p1deseaentrar); < sección crítica uno > < sección crítica dos > p1deseaentrar = false; /* salir de la p2deseaentrar = false; /* salir de la exclusión mutua */ exclusión mutua */ otras_tareas_uno; otras_tareas_dos; } } NOTAS. Esta solución garantiza la exclusión mutua, pero origina un problema más. Si ambos procesos ponen sus señales a “true” antes de que ambos hayan ejecutado la sentencia while, cada uno pensará que el otro ha entrado en su sección crítica. El resultado es un interbloqueo. En el tercer intento, un proceso fijaba su estado sin conocer el estado del otro. El interbloqueo se produce porque cada proceso puede insistir en su derecho para entrar en la sección crítica; no hay opción para volver atrás desde esta situación. Se puede intentar arreglar esto haciendo que los procesos sean más educados: deben activar su señal para indicar que desean entrar en la sección crítica, pero deben estar listos para desactivar la señal y ceder la preferencia al otro proceso. 35 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Cuarto intento /* Variable global compartida */ p1deseaentrar, p2deseaentrar: boolean; /**** Inicia principal ***/ p1deseaentrar = false; p2deseaentrar = false; /** Inicia concurrente */ PROCESO_UNO ( ); PROCESO_DOS ( ); /** termina concurrencia ***/ /*** Termina principal ****/ Procesos Concurrentes PROCESO_UNO PROCESO_DOS while true while true { { p1deseaentrar = true; p2deseaentrar = true; while (p2deseaentrar) while (p1deseaentrar) { { p1deseaentrar = false; p2deseaentrar = false; retraso (aleatorio, algunosciclos); retraso (aleatorio, algunosciclos); p1deseaentrar = true; p2deseaentrar = true; } } < sección crítica uno > < sección crítica dos > p1deseaentrar = false; /* salir de la p2deseaentrar = false; /* salir de la exclusión mutua */ exclusión mutua */ otras_tareas_uno; otras_tareas_dos; } } NOTAS. La exclusión mutua está garantizada y no se puede producir un bloqueo mutuo, pero puede originarse otro problema potencialmente grave: un aplazamiento indefinido. Todo puede ocurrir porque, dado que no suponemos nada acerca de las velocidades relativas de los procesos concurrentes asíncronos, han de considerarse todas las posibles secuencias de ejecución. Los procesos podrían ejecutarse en tándem (realizar la acción en forma común), por ejemplo: cada proceso puede asignar el valor “true” a su bandera, realizar la verificación, entrar en el cuerpo del ciclo while, asignar el valor “false” a su bandera, asignar el valor cierto a su bandera y después repetir la secuencia comenzando con la verificación. Mientras ocurre esto, las condiciones verificadas se siguen cumpliendo. Esta situación tiene pocas probabilidades de ocurrir, pero podría darse el caso. Si un sistema que usara este tipo de exclusión mutua se empleara para controlar un vuelo espacial, un marcapasos o un sistema de control de tráfico aéreo, estarían latente la posibilidad de un aplazamiento indefinido y la consiguiente falla del sistema. 36 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos A continuación se muestra el algoritmo de DEKKER que maneja la exclusión mutua entre dos procesos, sin ninguna instrucción especial de hardware. Algoritmo de DEKKER /* Variable global compartida */ p1deseaentrar, p2deseaentrar: boolean; proceso_favorecido /**** Inicia principal ***/ p1deseaentrar = false; p2deseaentrar = false; proceso_favorecido = primero; /** Inicia concurrente */ PROCESO_UNO ( ); PROCESO_DOS ( ); /** termina concurrencia ***/ /*** Termina principal ****/ Procesos Concurrentes PROCESO_UNO while true { p1deseaentrar = true; while (p2deseaentrar) if (proceso_favorecido == segundo ) { p1deseaentrar = false; while (proceso_favorecido == segundo) ; p1deseaentrar = true; } < sección crítica uno > proceso_favorecido = segundo; p1deseaentrar = false; /* salir de la exclusión mutua */ otras_tareas_uno; } PROCESO_DOS while true { p2deseaentrar = true; while (p1deseaentrar) if (proceso_favorecido == primero ) { p2deseaentrar = false; while (proceso_favorecido == primero) ; p2deseaentrar = true; } < sección crítica dos > proceso_favorecido = primero; p2deseaentrar = false; /* salir de la exclusión mutua */ otras_tareas_dos; } 37 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos 3.3 ALGORITMO DE PETERSON En 1981, G.L. Peterson publicó un algoritmo mucho más sencillo para manejar una exclusión mutua de dos procesos con espera activa. Algoritmo de PETERSON /* Variable global compartida */ p1deseaentrar, p2deseaentrar: boolean; proceso_favorecido /**** Inicia principal ***/ p1deseaentrar = false; p2deseaentrar = false; proceso_favorecido = primero; /** Inicia concurrente */ PROCESO_UNO ( ); PROCESO_DOS ( ); /** termina concurrencia ***/ /*** Termina principal ****/ Procesos Concurrentes PROCESO_UNO while true { p1deseaentrar = true; proceso_favorecido = segundo while (p2deseaentrar && proceso_favorecido == segundo ); < sección crítica uno > p1deseaentrar = false; /* salir de la exclusión mutua */ otras_tareas_uno; } PROCESO_DOS while true { p2deseaentrar = true; proceso_favorecido = primero while (p1deseaentrar && proceso_favorecido == primero ); < sección crítica dos > p2deseaentrar = false; /* salir de la exclusión mutua */ otras_tareas_dos; } 3.4 Exclusión mutua de N procesos Existen varios algunos para solucionar por software la exclusión mutua de n procesos entre los que se encuentran: • • • • Algoritmo desarrollado por Dijkstra. Publicado en: ”Solution of a problem in concurrent programming control” Algoritmo desarrollado por Knuth. Publicado en: “Additional comments on a problem in concurrent programming control” Algoritmo desarrollado por Eisenberg y McGuire. Publicado en : “Further comments on Dijkstra’s concurrent programming control problem” Algoritmo desarrollado por Lamport (Lamport´s Bakery Algorithm). Publicado en : “A new solution to Dijkstra’s concurrent programming problem”. 38 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos • • • Algoritmo desarrollado por Brich Hansen. Publicado en: “Distributed processes – a concurrent programming concept”. Algoritmo desarrollado por Burns. Publicado en : “ Data requeriments for implementation of n-process mtual exclusión using a single shared variable” Algoritmo desarrollado por Carvalho y Roucairol. Publicado en: “On mutual exclusión in computer networks”. 3.5 Exclusión Mutua: Soluciones por hardware En una máquina monoprocesador, la ejecución de procesos concurrentes no puede superponerse; los procesos sólo pueden intercalarse. Es más, un proceso continuará ejecutándose hasta que solicite un servicio del sistema operativo o hasta que sea interrumpido. Por lo tanto, para garantizar la exclusión mutua, es suficiente con impedir que un proceso sea interrumpido. Esta capacidad puede ofrecerse en forma de primitivas definidas por el núcleo del sistema para habilitar o inhabilitar las interrupciones. /***** Programa Principal ******/ ***** Concurrentes ***** P(1); P(2); ...... P(n); ***** Termina concurrentes ***** P (int i) While (true) do { entrada a region critica; <sección crítica > salida de región crítica; otras operaciones } Puesto que la sección crítica no puede ser interrumpida, la exclusión mutua está garantizada. Sin embargo, el precio de esta solución es alta. La eficiencia de la ejecución puede verse notablemente degradada debido a que se limita la capacidad del procesador para intercalar programas. Instrucciones especiales de la máquina. A nivel de hardware, como se ha mencionado los accesos a posiciones de memoria excluyen cualquier otro acceso a la misma posición. Con esta base, los diseñadores han propuesto varias instrucciones de máquina que realizan dos acciones atómicamente, tales como leer y escribir o leer y examinar, sobre una misma posición de memoria en un único ciclo de lectura de instrucción. Puesto que estas acciones se realizan en un único ciclo de instrucción, no están sujetas a injerencias por parte de otras instrucciones. 39 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos /************ Esto maneja Stallings ********/ Dos instrucciones implementadas más habitualmente son: Comparar y Fijar. La instrucción Comparar y Fijar (TS, Test and Set) puede definirse de la siguiente forma: Booleano TS (int i) { if (i==0) { I=1; TS = true; } else TS = false; } La función Comparar y Fijar se ejecuta en su totalidad, es decir, no está sujeta a interrupciones. /************************************************/ La clave del éxito aquí es contar con una sola instrucción de hardware que lea una variable, almacene su valor en un área segura y asigne a la variable cierto valor. Una vez que se inicia, esta instrucción, llamada frecuentemente probar y fijar (test and set), realizará todas esta funciones sin interrupción. Probaryfijar ( a, b ) a!b b ! true /** Variable global ***/ booleano : activo ; /** principal ****/ activo = false; /** Concurrente **/ PROCESO_UNO( ); PROCESO_DOS( ); /**** termina principal ****/ PROCESO_UNO { while true { uno_no_puede_entrar = true; while uno_no_puede_entrar <sección crítica > activo = false; otras_tareas_uno; } } probaryfijar (uno_no_puede_entrar, activo) 40 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos PROCESO_DOS { while true { dos_no_puede_entrar = true; while dos_no_puede_entrar <sección crítica > activo = false; otras_tareas_dos; } } probaryfijar (dos_no_puede_entrar, activo) Esta solución puede adolecer de aplazamiento indefinido, pero la probabilidad de que ocurra es muy pequeña, sobre todo si existen varios procesadores. En el instante en que un proceso abandona su sección crítica y asigna falso a activo, es muy probable que el otro proceso, al ejecutar probaryfijar “se apodere” de activo (asignándole el valor cierto) antes de que el primer proceso pueda repetir el ciclo y asigne el valor cierto. Propiedades de las soluciones con instrucciones de máquina. El uso de instrucciones especiales e la máquina para hacer cumplir la exclusión mutua tiene varias ventajas: • • • Es aplicable a cualquier número de procesos en sistemas con memoria compartida, tanto de monoprocesador como de multiprocesador. Es simple y fácil de verificar. Puede usarse para disponer de varias secciones crítica; cada sección crítica puede definirse con su propia variable. Algunas desventajas importantes son las siguientes: • • • Se emplea espera activa. Así, mientras un proceso está esperando para acceder a la sección crítica, continúa consumiendo tiempo del procesador. Puede producir inanición. Cuando un proceso abandona la sección crítica y hay más de un proceso esperando, la selección es arbitraria. Así, se podría denegar el acceso a algún proceso indefinidamente. Puede producir interbloqueo. Debido a estos inconvenientes tanto de las soluciones por software como por hardware, es necesario buscar otros mecanismos. 41 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos 3.6 Teoría de Semáforos El primer gran avance en la resolución de los problemas de procesos concurrentes llegó en 1965, con el tratado de Dijkstra sobre la cooperación entre procesos secuenciales [Cooperating Sequential Processes]. Dijkstra estaba interesado en el diseño de un sistema operativo como un conjunto de procesos secuenciales cooperantes y en el desarrollo de mecanismos eficientes y fiables para dar soporte a la cooperación. Los procesos de usuario podrán utilizar estos mecanismos en tanto que el procesador y el sistema operativo los hagan disponibles. El principio fundamental es el siguiente: dos o más procesos pueden cooperar por medio de simples señales, de forma que se pueda obligar a detenerse a un proceso en una posición determinada hasta que reciba una señal específica. Cualquier requisito complicado de coordinación puede satisfacerse por medio de la estructura de señales adecuada. Para la señalización, se usan variables especiales llamadas semáforos. Para transmitir una señal por el semáforo s, los procesos ejecutan la primitiva signal(s). Para recibir una señal del semáforo s, los procesos ejecutan la primitiva wait(s); si la señal correspondiente aún no se ha transmitido, el proceso es suspendido hasta que tenga lugar su transmisión. En el artículo original de Dijkstra y en gran parte de la bibliografía, se emplea la letra P para wait y la letra V para signal; estas son las iniciales de las palabras holandesas que significan comprobar (proberen) e incrementar (verhogen). Para lograr el efecto deseado, se pueden contemplar los semáforos como variables que tienen un valor entero sobre el que se definen las tres operaciones siguientes: 1. Un semáforo puede inicializarse con un valor no negativo. 2. La operación wait decrementa el valor del semáforo. Si el valor se hace negativo, el proceso que ejecuta el wait se bloquea. P(s) Si S > 0 Entonces S = S –1 Si no (esperar S) 3. La operación signal incrementa el valor del semáforo. Si el valor no es positivo, se desbloquea a un proceso bloqueado por una operación wait. V(s) Si (uno o más procesos esperan S) Entonces (dejar que prosiga uno de esos procesos) Si no S = S + 1 Un semáforo es una variable protegida cuyo valor sólo puede ser leído y alterado mediante las operaciones P- wait, y V – signal y una operación de asignación de valores iniciales que llamaremos inicia_semáforo. 42 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Uso de un semáforo y de las primitivas P y V para asegurar la exclusión mutua Muestra cómo pueden utilizarse los semáforos para implantar la exclusión mutua. Ahí P (activo) equivale a entrar a la exclusión mutua y V(activo) equivale a salir de la exclusión mutua. Versión del libro de Deitel { /* principal */ inicia_semáforo (activo, 1); /* en concurrentes */ proceso_uno; proceso_dos; } /* fin de principal */ Procesos Concurrentes proceso_uno { while true do { tareas_preliminares_uno; P (activo); Sección crítica uno; V (activo); Otras tareas uno; } } proceso_dos { while true do { tareas_preliminares_dos; P (activo); Sección crítica dos; V (activo); Otras tareas dos; } } Definición de las primitivas de los semáforos binarios (versión del libro de William Stalling) Versión del libro de William Stalling Type semáforo binario = record Valor : (0, 1); Cola : list of proceso; End; var s: semaforo binario; Procesos Concurrentes 43 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Versión del libro de William Stalling waitB (s): if s.valor = 1 then s.valor = 0; else begin poner este proceso en s.cola; bloquera este proceso; end; signal (s): if s.cola está vacia then s.valor = 1; else begin quitar un proceso P de s.cola; poner el proceso P en la cola de listos end; Tanto en los semáforos como en los semáforos binarios se emplea una cola para mantener los procesos esperando en el semáforo. La definición no dicta el orden en el que se quitan los procesos de dicha cola. La política más equitativa es FIFO: el proceso que ha estado bloqueado durante más tiempo se libera de la cola. La única exigencia estricta es que un proceso no debe quedar retenido en la cola de un semáforo indefinidamente porque otros procesos tengan preferencia. 3.7 Problema del productor-consumidor Problema del Productor-Consumidor var int buffer_número; semáforo número_depositado; semáforo número_recuperado; /* Principal */ inicia_semáforo (número_depositado, 0); inicia_semáforo (número_recuperado, 1); /* forma concurrente */ proceso_productor; proceso_consumidor; /* fin de concurrente */ /* fin principal */ Procesos Concurrentes proceso_productor while true { calcular_siguiente_resultado; P(número_recuperado); Buffer_número = siguiente_resultado; V (número_deposita); } proceso_consumidor while true { P(número_depositado); siguiente_resultado = buffer_número; V (número_recuperado); Escribir (siguiente_resultado); } El anterior programa concurrente utiliza operaciones de semáforo para poner en práctica una relación productor-consumidor. 44 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Se han empleado dos semáforos: el productor indica número_depositado (con V) y el consumidor lo verifica (con P); el consumidor no puede proseguir en tanto no se deposite un número en buffer_número. El consumidor indica número_recuperado con una operación V y el productor lo verifica con una operación P; el productor no puede proseguir hasta que se haya recuperado un número de buffer_número. Los valores inciales de los semáforos obligan al productor a depositar un valor en buffer_número antes de que el consumidor pueda proseguir. Obsérvese que el uso de los semáforos en este programa impone una sincronización en tándem; esto es aceptable porque existe solamente una variable compartida. En las relaciones productor-consumidor es muy común tener un buffer con espacio para varias variables. Con una disposición así no es necesario que el productor y el consumidor se ejecuten “a la misma velocidad” en la sincronización en tándem. Aunque, un productor rápido puede depositar muchos valores mientras el consumidor está inactivo y un consumidor rápido puede recuperar otros tantos mientras el productor está inactivo. Semáforos contadores. Los semáforos contadores son particularmente útiles cuando hay que asignar un recurso a partir de un banco de recursos idénticos. El semáforo tiene como valor inicial el número de recursos contenidos en el banco. Cada operación P decrementa en uno el semáforo, indicando que se ha retirado un recurso del banco y que lo está utilizando algún proceso. Cada operación V incrementa en uno el semáforo, lo que indica la devolución de un recurso al banco y que el recurso puede ser asignado a otro proceso. Si se intenta una operación P cuando el semáforo tiene un valor 0, el proceso deberá esperar hasta que se devuelva un recurso al banco mediante una operación V. Los semáforos pueden servir para llevar a la práctica un mecanismo de sincronización bloquear/despertar: un proceso se bloquea a sí mismo mediante P(S), con S = 0 inicialmente, para esperar que ocurra un evento; otro proceso detecta el evento y despierta al proceso bloqueado mediante V(S). En una relación productor-consumidor, un proceso, el productor genera información que utiliza un segundo proceso, el consumidor. Esto es un ejemplo de comunicación entre procesos. Si estos procesos se comunican por medio de un buffer compartido, el productor no debe producir cuando el buffer está lleno y el consumidor no debe consumir cuando el buffer está vacío. La implantación de estas restricciones es un ejemplo de la sincronización de procesos. Los semáforos contadores son particularmente útiles cuando un recurso debe asignarse a partir de un banco de recursos idénticos. Cada operación P indica que se ha asignado un recurso; cada operación V indica que se ha devuelto al banco un recurso. Las operaciones de semáforos pueden llevarse a la práctica con espera activa, pero hacerlo puede ser un desperdicio. Las operaciones de semáforos pueden incluirse en el núcleo para evitar la espera activa. En un sistema con un solo procesador, la indivisibilidad de P y V pueden asegurarse inhabilitando las interrupciones mientras P y V manipulan semáforos. En un sistema de múltiples procesadores, puede asignarse a un procesador la tarea de controlar a los demás y asegurar así la exclusión mutua. Esta técnica puede usarse también en sistemas distribuidos, pero es más común que cada procesador de un sistema distribuido tenga su propio núcleo. 45 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Desventajas • • • • Si se omite P, no se obtiene la exclusión mutua. Si se omite V, las tareas que están esperando por causa de operaciones P podrían quedar en bloqueo permanente. Una vez que comienza una operación P, el usuario no puede arrepentirse y seguir otro curso de acción mientras el semáforo esté en uso. Una tarea sólo puede esperar un semáforo a la vez. 3.8 Semáforos programación practica POSIX.1b Un semáforo es un número entero variable con dos operaciones atómicas: wait y signal. En la terminología de POSIX.1b, estas operaciones son llamadas cerrojo de semáforo y apertura de semáforo. A lo largo de esta sección de utilizan semáforos variables del tipo semaphore_t, los cuales siempre contienen un número entero variable que un proceso siempre está en posibilidad de probar, incrementar o reducir, a través de las operaciones asociadas de semáforo. void wait (semaphore_t *sp) { while (*sp <=0); (*sp)--; } void signal (semaphore_t *sp) { (*sp)++; } Ejemplo. El siguiente código protege una sección critica si el semáforo variable S es iniciado a 1. wait (&s) <inicia sección critica> signal (&s) <finaliza sección critica> Los procesos que utilizan semáforos deben cooperar para asegurar que se obtenga una independencia exclusiva. El código anterior protege a una sección crítica siempre y cuando todos los procesos ejecuten wait(&s) antes de entrar a sus respectivas secciones críticas, y siempre y cuando ejecuten la signal(&s) al salir. Ejemplo. ¿Qué sucedería si en el ejemplo anterior S inicialmente tuviera un valor de 0? ¿qué pasaría si S tuviera un valor inicial de 8? Respuesta: Si S inicialmente tuviera un valor de 0, entonces cada wait (&s) sería bloqueado y se establecería una espera indefinida (deadlock), a menos que algún otro proceso inicializara S a 1. Si S inicialmente tuviera un valor de 8, entonces sólo un máximo de ocho procesos se ejecutarían concurrentemente en sus secciones críticas. 46 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Ejemplo. ¿Qué sucedería si en el siguiente pseudocódigo los semáforos S y Q tuvieran inicialmente el valor de 1? Process 1 executes: for ( ; ; ) { wait (&S); a; signal (&Q); } Process 2 executes: for ( ; ; ) { wait (&Q); a; signal (&S); } Respuesta: Cualquiera de ambos procesos puede ejecutar su orden wait primero. Los semáforos se encargan de que cualquier proceso específico no éste más delante de una iteración que el otro proceso. Si un semáforo está inicialmente en 1 y el otro está en 0, los procesos procederán a ejecutarse en forma estrictamente alterna. Si los dos semáforos están inicialmente en 0, entonces ocurre una espera infinita (deadlock). Ejercicio. ¿Qué sucedería si S estuviera iniciado a 8 y Q a 0? Respuesta: El proceso uno siempre estará entre cero y ocho iteraciones adelante del proceso dos. Si el valor de S representa ranuras vacías y el valor de Q representa elementos en esas ranuras, el proceso uno adquiere ranuras y produce elementos, mientras que el proceso dos adquiere elementos y produce ranuras vacías. Esta generalización sincroniza el acceso a un almacenamiento temporal que puede acomodar no más de ocho elementos. Ejercicio. ¿Qué pasaría con el siguiente pseudocódigo si los semáforos S y Q tuvieran un valor inicial de 1? Process 1 executes: for (; ;) { wait (&Q); wait (&S); a; signal (&S); signal (&Q); } 47 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Process 2 executes: for (; ;) { wait (&S); wait (&Q); a; signal (&Q); signal (&S); } Respuesta: El resultado dependería del orden en que los procesos llegaran a la CPU. Este código debería trabajar la mayor parte del tiempo, pero si el proceso uno pierde a la CPU después de la ejecución del wait(&Q) y el proceso dos logra entrar, ambos procesos se bloquean en un segundo wait y ocurre una espera infinita (deadlock). 3.9 Monitores Un monitor es una construcción para concurrencia que contiene los datos y los procedimientos necesarios para asignar un recurso compartido específico reutilizable en serie o un grupo de estos recursos. El concepto de monitor fue propuesto por Dijkstra, luego por Brich Hansen y después fue mejorado por Hoare. Para ejecutar una función de asignación de recursos, un proceso debe llamar a una entrada del monitor específica. Algunas característica de los monitores son: • • • • • • • Muchos procesos pueden desear entrar en el monitor en diversos momentos, pero la exclusión mutua se mantiene, sólo se permite la entrada de un proceso a la vez. Los procesos que desean entrar en el monitor cuando ya está en uso deben esperar. Un monitor controla automáticamente la espera de los procesos. Desarrollo de la técnica ocultación de la información: los datos dentro del monitor pueden ser globales con respecto de todos los procedimientos del monitor o locales con respecto de un procedimiento específico. Si un proceso que está llamando a la entrada del monitor encuentra que el recurso requerido, ya ha sido asignado, el procedimiento monitor llama a la función “esperar”, y aguarda fuera del monitor hasta que se libere el recurso. El proceso que tiene un recurso llamará a una entrada del monitor para devolver el recurso al sistema, después de esto la entrada del monitor invoca a la función “señalar” para permitir que uno de los procesos en espera adquiera el recurso. La asignación del recurso se realiza por prioridad, el de mayor prioridad es el que tiene más tiempo esperando el recurso, esto para evitar el aplazamiento indefinido. Se asocia una variable condicional a cada una de las diversas razones por las que un proceso puede verse obligado a esperar. Así tenemos: Esperar (nombre de la variable condicional) Señalar (nombre de la variable condicional) Hagamos notar que a cada variable condicional que se crea se le asocia una cola. 48 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Asignación simple de recursos mediante monitores Monitor asignador_de_recursos recurso_en_uso : bolean; recurso_libre : condition: { recurso_en_uso = false; } /* El siguiente procedimiento es similar al procedimiento P para semáforos */ obtener_recurso { if recurso_en_uso esperar (recurso_libre); recurso_en_uso = true; } /* El siguiente procedimiento es similar al procedimiento V para semáforos */ devolver_recurso { recurso_en_uso = false; señalar (recurso_libre); } Ejemplo de monitor: el buffer circular Un mecanismo de buffer circular es adecuado para implantar el control de spool en los sistemas operativos. Un spooler es un proceso que produce líneas, y un despooler es un proceso que lee las líneas del buffer circular y las escribe en la impresora, este proceso trabaja a una velocidad de la impresora. Monitor monitor_buffer_circular variables buffer_circular /* arreglo de 0 a casilla-1 */ casilla_ene_uso /* de 0 a casilla */ siguiente_casilla_por_llenar /* de 0 a casilla-1 */ siguiente_casilla_por_vaciar /* de 0 a casilla-1 */ buffer_circular_tiene_datos : condition; buffer_circular_tiene_espacios : condition; { casilla_en_uso = 0; siguiente_casilla_por_llenar = 0; siguiente_casilla_por_vaciar = 0; } llenar_una_casilla { if casilla_en_uso == casillas esperar (buffer_circular_tiene_espacio) 49 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos buffer_circular [siguiente_casilla_por_llenar] = datos_casilla; casillas_en_uso ++; siguiente_casilla_por_llenar = (siguiente_casilla_por_llenar +1 ) mod casillas; señalar (buffer_circular_tiene_datos); } vaciar_una_casilla { if casilla_en_uso == 0 esperar (buffer_circular_tiene_datos); datos_casilla = buffer_circular [siguiente_casilla_por_vaciar]; casillas_en_uso--; siguiente_casilla_por_vaciar = (siguiente_casilla_por_vaciar +1 ) mod casillas; señalar (buffer_circular_tiene_espacio); } Ejemplo de monitor: lectores y escritores • • Los lectores no alteran el contenido de la base de datos, muchos de ellos pueden obtener acceso a la base de datos al mismo tiempo. Un escritor puede modificar los datos, por lo cual debe tener acceso exclusivo. Monitor lectores_y_escritores int lectores; bolean alguien_escribe; condition lectura_permitida, escritura_permitida; { lectores = 0; alguien_escribe = false; } comenzar_escribir { if lectores > 0 || alguien_escribe esperar (escritura_permitida); alguien_escribe = true; } escritura_terminada { alguien_escribe = false; if en_cola (lectura_permitida) señalar (lectura_permitida); else señalar (escritura_permitida); } comenzar_lectura { if alguien_escribe || en_cola (escritura_permitida) 50 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos esperar (lectura_permitida); lectores++; señalar (lectura_permitida); } lectura_terminada { lectores--; if lectores ==0 señalar (escritura_permitida); } • Es posible que un sistema que utilice monitores permita a los procedimientos de un monitor llamar a procedimientos que estén en otro, con lo que se originan llamadas anidadas en los monitores. 3.9. Buzones y Puertos Las comunicaciones a base de mensajes se pueden establecer directamente entre un transmisor y un receptor, o pueden utilizarse colas intermedias. Cuando se utiliza la cola intermedia el transmisor y el receptor especifican cual será la cola (en lugar del proceso) con que se van a comunicar. Un buzón es una cola de mensajes que puede ser utilizada por múltiples transmisores y receptores. En un sistema distribuidor, los receptores pueden estar situados en muchas computadoras, por lo cual requieren dos transmisiones para la mayor parte de los mensajes: una del transmisor al buzón y otra del buzón al receptor. El problema se resuelve dando a cada receptor un puerto. Un puerto se define como un buzón utilizado por múltiples transmisores y un solo receptor. _________________________________________________________________________ EJERCICIO PROPUESTO _________________________________________________________________________ 1. Investigue en los números de puertos en alguna distribución de GNU/Linux. a) ¿ Para qué se utilizan los puertos estáticos? b) ¿ Por qué se generan los puertos dinámicos? 2. ¿Cómo cerramos los puertos? 3. ¿ Es lo mismo un puerto y un servicio? _________________________________________________________________________ 51 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Capítulo 4 Mecanismos de IPC Los procesos necesitan en ocasiones comunicarse entre ellos, ya sean que sean parientes o entidades sin parentesco. Para esto el sistema brinda formas básicas de comunicación, como son las tuberías, con nombre o sin nombres, y además mecanismos implementados como unidades que necesitan una llave para poder proteger el acceso a los recursos que los procesos necesiten compartir. 4.1. Comunicación mediante tuberías La comunicación entre procesos es fundamental para que estos intercambien datos y se puedan sincronizar. Hay que tener en consideración en primer lugar si los procesos está comunicándose en la misma máquina, y estos estas emparentados o no, y segundo si estos están comunicándose en máquinas diferentes. A continuación se muestra los mecanismos clásicos de comunicación entre dos o más procesos como son las tuberías, basándonos en las facilidades IPC de los sistemas UNIX System V y derivados Tuberías sin nombre - PIPE Los pipes son viejas formas de IPC en UNIX y son proporcionados por todos los sistemas UNIX. Estos tiene dos limitaciones: 1. Los datos fluyen un una dirección. 2. Pueden usarse solo entre procesos que tienen un ancestro en común. Normalmente un pipe es creado por un proceso que llama a un fork, y el pipe es usado entre el padre y el hijo. Un pipe se crea llamando a la función pipe. 52 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos PROTOTIPO DE LA FUNCIÓN #include <unistd.h> int pipe (int filedes[2]) El valor retornado es 0 se todo esta bien y –1 si existe un error. Los dos descriptores son retornado a través del argumento filedes: Pipe filedes[0] es abierto para lectura filedes[1] es abierto para escritura La salida de filedes[1] es la entrada para filedes[0] Proceso de usuario Un pipe se crea llamando a la función pipe. fd[0] fd[1] ude <unistd.h> pe (int filedes[2]); Proceso de usuario or retornado es 0 se todo esta bien y -1 si existe un error. fd[0] fd[1] os descriptores son retornado a través del argumento filedes: pipe s[0] es abierto para lectura s [1] es abierto para escritura kernel lida de filedes[1] es la entrada para filedes[0] Figura 4-1. Dos forma de ver a un pipe de UNIX. Padre fd[0] fork fd[1] Hijo fd[0] fd[1] pipe Kernel Figura 4-2. Un pipe half-duplex después de un fork. 53 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Padre Hijo fd[1] fd[0] pipe Kernel Figura 4-3. Pipe entre un padre y un hijo. Ejemplo. Programa que muestra el código para crear un pipe desde el padre al hijo, y envía datos hacia abajo del pipe. #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> main () { int n, fd[2]; pid_t hijo; char linea [MAXLINEA] if (pipe(fd) < 0) fprintf (stderr,”error de pipe”); if ( (hijo = fork() ) < 0) fprintf (stderr, “error de fork”); else if (hijo> 0) { /* padre */ close (fd[0]); write (fd[1], “hola mundo\n”,12); } else { close (fd[1]); n = read (fd[0], linea, MAXLINEA); write (STDOUT_FILENO, linea, n); } exit (0); } Algunas veces el padre se ejecuta más rápido que el hijo, lo que podemos hacer entre muchas cosas es cambiar el orden. 54 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #define MAXLINE 80 main () { int n, fd[2]; pid_t hijo; char linea[MAXLINE]; int estado; if (pipe(fd) < 0) fprintf (stderr,"error de tuberia"); if ( (hijo=fork()) == 0) { close (fd[0]); write (fd[1], "hola mundo \n",12); } else { /* if (wait (&estado)==hijo) {*/ close (fd[1]); n = read (fd[0], linea, MAXLINE); write (STDOUT_FILENO, linea, n); printf("numero de lineas %d \n",n); /* }*/ close (fd[1]); n = read (fd[0], linea, MAXLINE); write (STDOUT_FILENO, linea, n); printf("numero de lineas %d \n",n); /* }*/ } exit (0); } 4.2. Mecanismos IPC El paquete de comunicación entre procesos de los sistema UNIX System V y derivados como Linux se compone de tres mecanismos: 1. Los semáforos, que van a permitir sincronizar procesos; 2. La memoria compartida, que va a permitir que los procesos compartan su espacio de direcciones virtuales; y 3. Las colas de mensajes, que posibilitan el intercambio de datos con un formato determinado. Estos mecanismos están implementados como una unidad y comparten características comunes, entre las que se encuentran: 55 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos • Cada mecanismo tiene una tabla cuyas entradas describen el uso que se hace del mismo. • Cada entrada de la tabla tiene una llave numérica elegida por el usuario. • Cada mecanismo dispone de una llamada “get” para crear una entrada nueva o recuperar alguna ya existente. Dentro de los parámetros de esta llamada se incluye una llave y una máscara de indicadores. El núcleo busca dentro de la tabla alguna entrada que se ajuste a la llave suministrada. • Cada entrada de la tabla tiene un registro de permisos que incluye: ID del usuario y grupo del proceso que ha reservado la entrada. • Cada entrada contiene información de estado, en la que se incluye el identificador del último proceso que ha utilizado la entrada. • Cada IPC tiene una llamada de “control” que permite leer y modificar el estado de una entrada reservada y también permite liberarla. Tabla 4-1. Resumen de los llamados IPC. Semafóros Memoria compartida Cola de mensajes Cabecera de archivo <sys/sem.h> <sys/shm.h> <sys/msg.h> llamado para crear o abrir semget shmget msgget Llamado para operaciones de control semctl shmctl msgctl Llamado para operaciones IPC semop shmat, shmdt msgsnd, msgrcv Llaves Una llave es una variable o constante del tipo key_t que se usa para acceder a los mecanismos IPC previamente reservados o para reservar otros nuevos. Los mecanismos que están siendo utilizados como parte de un mismo proyecto comparten la misma llave. En GNU C se crean las llaves utilizando la función ftok. PROTOTIPO DE LA FUNCIÓN #include <types.h> #include <sys/ipc.h> key_t ftok (char *path, int id); 56 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos 4.2.1 SEMÁFOROS El concepto de semáforo nace de la necesidad que en el sistema se pueda contar con procesos que trabajen de manera cooperativa, y para esto deben de coordinarse. No es un mecanismo de comunicación sino de sincronización y son utilizados para controlar el acceso a los recursos. Dijkstra (colocar referencia) define un semáforo como un objeto de tipo entero sobre el que se puede realizar dos operaciones atómicas, es decir, sin interrupción, llamadas: P y V. Espera (P): Se usa cuando un proceso quiere acceder a un recurso compartido y puede ocurrir: o Si la variable entera es positiva, adquiere el recurso y decrementa el valor. o En caso de que el valor sea nulo el proceso se duerme y espera a ser despertado. Liberar (V): Se utiliza para indicar que el recurso compartido esta libre y despierta a los procesos que estén esperando por el recurso. Los semáforos resuelven principalmente los problemas de: Exclusión mutua, y Sincronización. Crear o Acceder a un semáforo Mediante semget se puede crear o accesar a un conjunto de semáforos unidos bajo un identificador común. PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget (key_t key, int numsems, int semflg); Si se ejecuta correctamente retorna el ID para accesar a los semáforos, y en caso de error -1 y en errno el código del error producido. En este caso, la variable errno toma uno de los valores siguientes: • EACCES: Existe un grupo de semáforos para la clave, pero el proceso no tiene todos los derechos necesarios para acceder al grupo. 57 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos • EEXIST: Existe un grupo de semáforos para la clave, pero están activadas las operaciones IPC_CREAT e IPC_EXCL. • EIDRM: El grupo de semáforos ha sido borrado. • ENOENT: El grupo de semáforos no existe y no está activada la opción IPC_CREAT. • ENOMEN: El semáforo podría crearse pero el sistema no tiene más memoria para almacenar la estructura. • ENOSPC: El semáforo podría crearse pero se sobrepasarían los límites para el número máximo de grupos (SEMMNI) de semáforos o el número de semáforos (SEMMNS). key indica a qué grupo de semáforos se va a accesar. El núcleo busca dentro de la tabla alguna entrada que se ajuste a la llave suministrada. Si la llave suministrada toma el valor IPC_PRIVATE, el núcleo ocupa la primera entrada que se encuentra libre y ninguna otra llamada “semget” podrá devolver esta entrada hasta que la liberemos. Si dentro de la máscara de indicadores está activo el bit IPC_CREAT, el núcleo crea una nueva entrada en caso de que no haya ninguna que responda a la llave suministrada. Si la llave suministrada tiene activados IPC_CREAT e IPC_EXL devolverá error si ya existe la clave suministrada. Retorna un descriptor de semáforo. Si no existe el descriptor y se especifica IPC_CREAT creará uno. numsems es el total de semáforos que van a estar agrupados bajo el identificador devuelto. semflg es una máscara de bits para indicar el modo de adquisición del identificador. Esta formado por dos campos separados por un el simbolo (|). Los campos son: CAMPO I: IPC_CREAT, IPC_EXCL CAMPO II: 00X00 usuario, 000X0 grupo, 0000x otro if ( (llave=ftok (“auxiliar”, `k`)) == (ket_t) -1) { if ( (semid = semget ( llave, 4, IPC_CREATE| 0600))== -1) { ....... ....... } } 58 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Semop (operaciones sobre los semáforos) Para realizar las operaciones sobre los semáforos que hay asociados bajo un identificador (incremento, decremento o espera de nulidad). PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop (int semid, struct sembuf *sops, unsigned nsops); • semid: Identificador del semáforo (grupo sobre el cual las operaciones tendrán lugar). • sops: Puntero a un vector de operaciones (arreglo de estructuras que indica las operaciones a llevar a cabo sobre los semáforos). • nsops: Número de operaciones a realizar en esta llamada (en realidad, tamaño de elementos que tiene el arreglo de operaciones). Estructura sembuf La estructura involucrada en el llamado a semop es sembuf. Cada dato de este tipo especifica una operación a realizar sobre un semáforo particular dentro del conjunto. Este semáforo en particular es el especificado en sem_num. Este semáforo se puede Incrementar, decrementar o esperar un valor nulo. struct sembuf { ushort short short } sem_num; sem_op; /* operación */ sem_flg; Si sem_op es negativo, el valor del semáforo se decrementará sem_op, si no es es posible entonces bloquea el proceso. Si el valor alcanzado es cero entonces despierta a los procesos correspondientes. Si sem_op es positivo, el valor del semáforo se incrementará sem_op, y tiene como efecto el despertar de todos los procesos que esperan a que este valor aumente a dicho valor. Si sem_op es 0, no hay alteración sobre el semáforo. Si es Nulo se bloquea el proceso. 59 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Todas las operaciones deben efectuarse de manera atómica. En los casos en que esto no sea posible, o bien el proceso se suspende hasta que sea posible, o bien, si el identificador IPC_NOWAIT está activado (campo sem_flg de la estructura sembuf) por una de las operaciones, la llamada al sistema se interrumpe sin que se realice ninguna operación. Por cada operación efectuada, el sistema controla si el indicador SEM_UNDO está posicionado. Si es así, crea una estructura para conservar el rastro de esa operación y poder anularla y finalizar la ejecución del proceso. Si la llamada al sistema se desarrolla correctamente el valor devuelto es 0, si el proceso debe bloquearse devuelve 1, si no es –1 y errno toma uno de los valores siguientes: • E2BIG: El argumento nsops es mayor que SEMOPM, y se sobrepasa el número máximo de operaciones autorizadas para una llamada. • EACCES: El proceso que llama no tiene los derechos de acceso a uno de los semáforos especificados en una de las operaciones. • EAGAIN: El indicador IPC_NOWAIT está activado y las operaciones no han podido realizarse inmediatamente. • EFAULT: La dirección especificada por el campo sops no es válida. • EFBIG: El número del semáforo (campo sem_num) es incorrecto para una de las operaciones (negativo o superior al número de semáforos en el grupo). • EIDRM: El semáforo no existe. • EINTR: El proceso ha recibido una señal cuando esperaba el acceso a un semáforo. • EINVAL: O el grupo del semáforo solicitado no existe(argumento semid), o bien el número de operaciones a realizar es negativo o nulo(argumento nsops). • ENOMEN: El indicador SEM_UNDO está activado y el sistema no puede asignar memoria para almacenar la estructura de anulación. • ERANGE: El valor añadido al contador del semáforo sobrepasa el valor máximo autorizado para el contador SEMVMX. Semctl ( Información administrativa y de control de los semáforos) Esta función permite la consulta, modificación y eliminación de un grupo de semáforos. También permite inicializar los semáforos y obtener información sobre el número de semáforos. 60 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl (int semid, int semnum, int cmd, union semun arg); semid: Identificador de semáforo válido. Semnum: Representa el número de semáforos. cmd: Indica la operación a realizar. Según los valores de cmd se realizarán diferentes casos: • IPC_STAT. Permite obtener las informaciones respecto a un grupo de semáforo. Semmun se ignora, arg es un puntero a la zona que contiene las informaciones. El proceso debe tener derechos de lectura para realizar la operación. • IPC_SET. Permite modificar ciertos valores de la estructura del grupo de semáforos. Los campos modificables son sem_perm.uid, sem_perm.gid y sem_perm.mode. El campo sem_ctime se actualiza automáticamente. El proceso debe ser el creador o bien el propietario del grupo, o bien el superusuario. • IPC_RMID. Le indica al núcleo que debe borrar el conjunto de semáforos agrupados bajo el identificador semid. La operación de borrado no tendrá efecto mientras haya algún proceso que esté usando los semáforos. • GETPID: Permite devolver el PID del último proceso que actuó sobre el semáforo. El proceso debe tener derechos de acceso en lectura sobre el grupo. • GETNCNT. Permite leer el número de procesos que esperan que el contador del semáforo se incremente. El proceso debe tener el derecho de acceso en lectura sobre el grupo. • GETZCNT. Permite leer el número de procesos que esperan a que el semáforo tome el valor de 0. Este número es calculado por la función count_semzcnt(). • GETVAL. Se utiliza para leer el valor de un semáforo. El valor se retorna a través de la función. • GETALL. Permite leer el valor de todos los semáforos asociados al identificador semid. Estos valores se almacenan en arg. 61 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos • SETVAL. Permite inicializar un semáforo a un valor determinado que se especifica en arg. • SETALL. Inicializa el valor de todos los semáforos asociados al identificador semid. Los valores de inicialización deben estar en arg. Para arg se utiliza la estructura semun, la cual es una unión, se utilizada para almacenar o recuperar informaciones sobre los semáforos. union semun { int val; /* Valor para SETVAL */ struct semid_ds *buf; /* Memoria de datos para IPC_STAT e IPC_SET */ ushort *array; /* Tabla para GETALL y SETALL */ struct seminfo *_buf; /* Memoria de datos para IPC_INFO*/ } arg; La Estructura semid_ds, se trata de una estructura de control asociada a cada uno de los distintos conjuntos de semáforos existentes en el sistema. Contiene información del sistema, punteros a las operaciones a realizar sobre el grupo, y un puntero hacia las estructuras sem que se almacenan en el núcleo y contienen información de cada semáforo. Esta estructura está en desuso actualmente y ha sido sustituida por sem_array solo se utiliza para compatibilidades. La Estructura seminfo permite conocer los valores límite o actuales del sistema mediante una llamada a semctl. Estas llamadas no se realizan generalmente en modo directo, sino que están reservadas a las utilidades del sistema como el mandato ipcs. Si la llamada a semctl se realiza con éxito, la función devuelve un número cuyo significado dependerá del valor de cmd. Si el llamado falla devuelve -1 y errno tendrá el código de error: • EACCES: El proceso que llama no tiene derechos de acceso necesarios para realizar la operación. • EFAULT: La dirección especificada por arg.buf o arg.array no es válida. • EIDRM: El grupo de semáforos ha sido borrado. • EINVAL:El valor de semid o de cmd es incorrecto. • EPERM: El proceso no tiene los derechos necesarios para la operación (mandato IPC_SET o IPC_RMID). • ERANGE: cmd tiene el valor SETALL o SETVAL y el valor del contador de uno de los semáforos a modificar es inferior o superior a SEMVMX. EJEMPLO. Sincronización de procesos 62 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos /**** Programa para mostrar el uso de semáforos en la sincronización de procesos, tomado de la página 389 del libro de: UNIX Programación Avanzada. F.M. Márquez *******/ #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #define SEM_HIJO 0 #define SEM_PADRE 1 main (int argc, char *argv[]) { int i=0, semid, pid; struct sembuf operacion; key_t llave; llave= ftok (argv[0], `k`); if ( (semid = semget (llave,2,IPC_CREAT| 0600))==-1) { perror (“semget”); exit (-1); } semctl (semid, SEM_HIJO, SETVAL, 0); semctl (semid, SEM_PADRE, SETVAL, 1); if ( (pid = fork())==-1) { perror (“fork”); exit (-1); } else if (pid==0) { while (1) { /* Cerramos el semáforo del proceso hijo */ operacion.sem_num = SEM_HIJO; operacion.sem_op = -1; operacion.sem_flg = 0; semop (semid, &operacion, 1); 63 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos printf (“Proceso hijo:%d\n”, i--); /* Abrimos el semáforo del proceso padre */ operacion.sem_num = SEM_PADRE; operacion.sem_op = 1; semop (semid, &operacion, 1); } /* Borramos el semáforo*/ semctl (semid, 0, IPC_RMID,0); } else { while (1) { /* Cerramos el semáforo del proceso padre */ operacion.sem_num = SEM_PADRE; operacion.sem_op = -1; operacion.sem_flg = 0; semop (semid, &operacion, 1); printf (“Proceso padre:%d\n”, i--); /* Abrimos el semáforo del proceso hijo */ operacion.sem_num = SEM_HIJO; operacion.sem_op = 1; semop (semid, &operacion, 1); } /* Borramos el semáforo*/ semctl (semid, 0, IPC_RMID,0); } } 4.2.2 MEMORIA COMPARTIDA La forma más rápida para comunicar dos procesos es hacer que compartan una zona de memoria. shmget Crea una zona de memoria compartida o habilita el acceso a una ya creada. 64 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget ( key_t key, int size, int shmflg); Donde key. Es una llave para crear el mecanismo IPC. size. Es el tamaño en bytes de la zona de memoria a crear. shmflg. Es una máscara de bits. La llamada a shmget devuelve un ID asociado a la zona de memoria (entero), en otro caso retorna -1, y en errno el código del error. if ( ( shmid = shmget (IPC_PRIVATE, 4096, IPC_CREAT | 0600)) == -1) { ......... TRATAMIENTO DEL ERROR ........ } shmctl Esta función realiza operaciones de control sobre una zona de memoria previamente creada. PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmctl (int shmid, int cmd, struct shmid_ds *buf); Donde: shmid. Es el identificador válido devuelto por shmget. 65 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos cmd. Es el tipo de operación de control a reazliar, sus posibles valores son: IPC_STAT. Lee el estado de la estructura de control de la memoria y lo devuelve a través de la zona de memoria apuntada por buf. IPC_SET. Inicializa algunos de los campos de la estructura de control de la memoria compartida. Los valores los toma de buf. IPC_RMID. Borra del sistema la zona de memoria compartida identificada por shmid. Está debe estar liberada por todos los procesos. SHM_LOCK. Bloquea en memoria el segmento identificado por shmid. SHM_UNLOCK. Desbloquea el segmento de memoria compartido. La estructura shmid_ds se define struct shmid_ds { struct ipc_per shm_per; /* permisos */ int shm_segsz; /* tamaño del área de memoria compartida */ ushort shm_lpid; /* PID del último proceso que hizo la última operación con este segmento de memoria */ ushort shm_cpid; /* PID del proceso creador del segmento */ ushort shm_nattach; /* número de procesos unidos al seg. de memoria*/ time_t shm_atime; /* Fecha de la última unión al seg. de memoria */ time_t shm_dtime; /* Fecha de la última separación del seg. de memoria*/ time_t shm_ctime; /* Fecha del último cambio en el seg. de memoria*/ } La estructura ipc_perm (permisos) es la siguiente struct ipc_perm { ushort uid; ushort gid; ushort cuid; ushort cgid; ushort mode; ushort seg; keyt_t key; } /* ID del usuario propietario */ /* ID del grupo */ /* ID del usuario creador */ /* ID del grupo creador */ /* Modos de acceso */ /* Número de secuencia */ /* Llave */ Para borrar del sistema la zona de memoria compartida debemos escribir shmctl (shmid, IPC_RMID, 0); 66 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Operaciones shmat y shmdt Para unirse/atarse a un espacio de direcciones virtuales (segmento de memoria compartida) del proceso se utiliza shmat y para desatarse shmdt. PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> char *shmat (int shmid, char *shmaddr, int shmflg); int shmdt (char *shmaddr); El llamado shmat retorna la dirección inicial del segmento de memoria compartida. Las reglas para determinar estas direcciones son: • Si el argumento shmaddr es cero, el sistema selecciona la dirección para el llamado. • Si el argumento shmaddr no es cero, retorna la dirección dependiendo del valor de SHM_RND para el argumento shmflg: • Si el valor SHM_RND no es especificado, el segmento de memoria compartida es unida a la dirección especificada como argumento en shmaddr. • Si el valor de SHM_RND es especificado, el segmento de memoria compartida es unida a la dirección especificada como argumento en shmaddr, redondeando hacia abajo por la constante SHMLBA (lower boundary address) Referencia [1] F. M. Márquez, UNIX Programación Avanzada, AlfaOmega , Segunda Edición, 2001. 67 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Capítulo 5 Interbloqueo e Inanición Un proceso es una entidad en ejecución que tiene asociada un identificador para poder ser identificado. En el sistema se cuenta con procesos pesados, llamados hijos, y procesos ligeros, llamados hilos. Interbloqueo: [MAEKAMA] Se define como el bloqueo permanente de un conjunto de procesos que compiten por los recursos del sistema o bien se comunican unos con otros. Tabla 5-1. Resumen de los enfoques de detección, prevención y predicción del interbloqueo en los Sistemas Operativos 68 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Principio Política de reparto Esquemas de recursos Prevención Conservadora; los Solicitud de recursos están todos los poco ocupados recursos a la vez. Ventajas Desventajas • Funciona bien con procesos que realizan una sola ráfaga de actividad. • No es necesaria la apropiación. • Conveniente cuando se aplica a recursos cuyo Apropiación estado se puede salvar y restaurar fácilmente. • Aplicación factible Ordenación de por medio de recursos chequeos durante la compilación. • No hace falta procesamiento durante la ejecución, pues el problema se resuelve durante el diseño del sistema. Detección Muy liberal; los Comprobación • Nunca retrasa el recursos periódica del inicio de un solicitados se interbloqueo. proceso. • Facilita el manejo conceden cuando es posible. en línea. • Predicción A medio del Manipulación No es necesaria camino entre la para encontrar la apropiación. detección y la al menos un pevención. camino seguro. • Ineficiencia. • Retrasos en el inicio de los procesos. • Apropia más habitualmente de lo necesario. • Sujeto a reinicios cíclicos. • No permite las solicitudes incrementales de recursos. • Poco uso de la apropiación. • Pérdidas inherentes a la apropiación. • Deben conocerse las demandas futuras de recursos. • Los procesos pueden bloquearse durante largos periodos. Inanición, aplazamiento indefinido, bloqueo indefinido. Cuando un proceso, aún cuando no esté bloqueado, esta esperando un evento que quizá nunca ocurra debido a ciertas tendencias en las políticas de asignación de recursos del sistema. En cualquier sistema que mantenga los procesos en espera mientras se les asigna un recurso o se toman decisiones de planificación, la programación de un proceso puede postergarse indefinidamente mientras otro recibe la atención del sistema. 69 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Recurso A Solicitud P1 Retenido por P2 Retenido por Recurso B Solicitud Figura 5-1. Espera circular. Cuatro condiciones necesarias para que se produzca un bloqueo mutuo (interbloqueo) 1. Exclusión mutua. Sólo un proceso puede usar un recurso simultáneamente. 2. Retención y espera. Un proceso puede retener unos recursos asignados mientras espera que se le asignen otros. 3. No apropiación. Ningún proceso puede ser forzado a abandonar un recurso que retenga. 4. Espera circular. Existe una cadena cerrada de procesos, cada uno de los cuales retiene, al menos, un recurso que necesita el siguiente proceso de la cadena. (Figura 5-1) Las cuatro condiciones en conjunto constituyen una condición necesaria y suficiente para el interbloqueo. Prevención del bloqueo mutuo Definitivamente la técnica empleada con más frecuencia por los diseñadores para tratar el problema del bloqueo mutuo es la prevención. Havender, J. W. llegó a la conclusión de que si falta una de las cuatro condiciones necesarias no puede haber bloqueo mutuo, él sugiere las siguientes estrategias para negar varias de estas condiciones: • Cada proceso deberá pedir todos sus recursos al mismo tiempo y no podrá seguir la ejecución hasta haberlos recibido todos. 70 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos • Si a un proceso que tiene ciertos recursos se le niegan los demás, ese proceso deberá liberar sus recursos y, en caso necesario, pedirlos de nuevo junto con los recursos adicionales. • Se impondrá un ordenamiento lineal de los tipos de recursos en todos los procesos; es decir, si a un proceso le han sido asignados recursos de un tipo específico, en lo sucesivo sólo podrá pedir aquellos recursos que siguen en el ordenamiento (Figura 5-2). .. . R8 P1 P2 R7 P1 R6 R5 P1 El proceso P1 tiene los recursos R3, R4, R6 y R7, y pide el recurso R8 (línea punteada). No puede haber espera circular porque todas las flechas deben apuntar hacia arriba. R4 P2 P1 R3 R2 R1 Figura 5.2. Ordenamiento lineal. 71 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos 5.2 Algoritmo del banquero El algoritmo más famoso para evitar un bloqueo mutuo es el algoritmo del banquero de Dijkstra, cuyo interesante nombre se debe a que atañe de un banquero que otorga préstamos y recibe pagos a partir de una determinada fuente de capital. Ejemplo de estado seguro. Supóngase que un sistema tiene doce unidades de cinta y tres usuarios que las comparten, como en el Estado I. Estado I Préstamo actual Usuario (1) Usuario (2) Usuario (3) Necesidad máxima 1 4 5 Disponibles 4 6 8 2 Este es un estado “seguro” porque aún es posible que terminen los tres usuarios. De esta forma, la clave para que un estado sea seguro es que exista al menos una forma de que terminen todos los usuarios. Ejemplo de estado inseguro. Supóngase que las doce unidades de cinta de un sistema están asignadas como en el Estado II. Estado II Usuario (1) Usuario (2) Usuario (3) Disponibles Préstamo actual Necesidad máxima 8 2 1 10 5 3 1 Aquí, once de las doce unidades de cinta del sistema se encuentran en uso y solamente una está disponible para asignación. En este momento, sin importar cuál de los usuarios pida la unidad disponible, no se puede garantizar que terminen los tres usuarios. Es importante señalar que un estado inseguro no implica la existencia, ni siquiera eventual, de un bloqueo mutuo. Lo que sí implica un estado inseguro es simplemente que alguna secuencia desafortunada de eventos podría llevar al bloqueo mutuo. Ejemplo de transición de estado seguro a estado inseguro Saber que un estado es seguro no implica que serán seguros todos los estados futuros. La política de asignación de recursos debe considerar cuidadosamente todas las 72 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos peticiones antes de satisfacerlas. Por ejemplo, supóngase que el estado actual de un sistema como se muestra en el Estado III. Estado III Usuario (1) Usuario (2) Usuario (3) Préstamo actual Necesidad máxima 1 4 5 Disponibles 4 6 8 2 Ahora supóngase que el Usuario (3) pide un recurso más. Si el sistema satisface esta petición, el nuevo estado será el Estado IV. Estado IV Usuario (1) Usuario (2) Usuario (3) Préstamo actual Necesidad máxima 1 4 6 Disponibles 4 6 8 1 Ciertamente, el Estado IV no está necesariamente bloqueado, pero ha pasado de un estado seguro a uno inseguro. El estado IV caracteriza a un sistema en el cual no puede garantizarse la terminación de todos los procesos. Solamente hay un recurso disponible, pero deben estar disponibles al menos dos recursos para asegurar que Usuario(2) o Usuario(3) puedan terminar, devolver sus recursos al sistema y permitir que los otros usuarios terminen. Nota en el algoritmo del banquero se presenta los siguientes términos: • • • • • • • Están permitidas las condiciones de “espera circular”, “espera”, y “no apropiación”, pero los procesos sí exigen el uso exclusivo de los recursos que requieren. Los procesos pueden conservar recursos mientras piden y esperan recursos adicionales. Los recursos no pueden arrebatarse a los procesos que los tienen. Los usuarios facilitan el trabajo al sistema pidiendo un solo recurso a la vez. El sistema puede satisfacer o rechazar una petición. Si una petición es rechazada, el usuario conserva los recursos que ya tenga asignados y espera un tiempo finito a que se satisfaga la petición. El sistema sólo satisface peticiones que llevan a estados seguros. Defectos del algoritmos del banquero. • • El algoritmo requiere un número fijo de recursos asignables. El algoritmo requiere una población de usuarios constante. 73 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos • • • El algoritmo requiere que el banquero satisfaga todas las peticiones en un tiempo finito. El algoritmo requiere que los clientes salden todos sus préstamos en un tiempo finito. El algoritmo requiere que los usuarios declaren por anticipado sus necesidades máximas. Detección del boqueo mutuo La detección del bloqueo mutuo es el proceso de determinar si realmente existe un bloqueo mutuo e identificar los procesos y recursos implicados en él. Los algoritmos de detección de bloqueos mutuos determinan por lo general si existe una espera circular. En control del interbloqueo puede llevarse a cabo tan frecuentemente como las solicitudes de recursos o con una frecuencia menor, dependiendo de la probabilidad de que se produzca el interbloqueo. La comprobación en cada solicitud de recurso tiene dos ventajas: 1. Conduce a una pronta detección, y 2. El algoritmos es relativamente simple, puesto que está basado en cambios incrementales del estado del sistema. Por otra parte, las frecuentes comprobaciones consume un tiempo de procesador considerable. Una vez detectado el interbloqueo, hace falta alguna estrategia de recuperación. 1. Abandonar todos los procesos bloqueados. Es la más común en los sistemas operativos. 2. Retroceder cada proceso interbloqueado hasta algún punto de control definido previamente y volver a ejecutar todos los procesos. El riesgo de esta solución radica en que puede repetirse el interbloqueo original. Sin embargo, el no determinismo del procesamiento concurrente asegura, en general, que esto no va a pasar. 3. Abandonar sucesivamente los procesos bloqueados hasta que deje de haber interbloqueo. El orden en el que se seleccionan los procesos a abandonar seguirá un criterio de mínimo coste. Después de abandonar cada proceso, se debe ejecutar de nuevo el algoritmo de detección para ver si todavía existe bloqueo. 4. Apropiarse de recursos sucesivamente hasta que deje de haber interbloqueo. Un proceso que pierde un recurso por apropiación debe retroceder hasta el momento anterior a la adquisición de ese recurso. Para los puntos 3 y 4, el criterio de selección del proceso podría ser uno de los siguientes: • • • • • Escoger el proceso con la menor cantidad de tiempo de procesador consumido. Escoger el proceso con el menor número de líneas de salida producidas. Escoger el proceso con el mayor tiempo restante. Escoger el proceso con el menor número total de recursos asignados. Escoger el proceso con la prioridad más baja. 74 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Predicción del interbloqueo La predicción del interbloqueo necesita conocer las peticiones futuras de los recursos. Para realizar la predicción del interbloqueo se deben tener en cuenta los siguientes dos enfoques: • • No iniciar un proceso si sus demandas pueden llevar a interbloqueo. No conceder una solicitud de incrementar los recursos de un proceso si esta asignación puede llevar a interbloqueo. 75 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Capítulo 6 Administración de memoria Un proceso es una entidad en ejecución que tiene asociada un identificador para poder ser identificado. En el sistema se cuenta con procesos pesados, llamados hijos, y procesos ligeros, llamados hilos. Introducción Una de las tareas más importantes y complejas de un sistema operativo es la gestión de memoria. La administración de memoria implica tratar la memoria principal como un recurso para asignar y compartir entre varios procesos activos. Para un uso eficiente del procesador y de los servicios de E/S, resulta interesante mantener en memoria principal tantos procesos como sea posible. Además, es deseable poder liberar a los programadores de las limitaciones de tamaño en el desarrollo de los programas. Las herramientas básicas de la gestión de memoria son la paginación y la segmentación. En la paginación, cada proceso se divide en páginas de tamaño constante y relativamente pequeño. La segmentación permite el uso de partes de tamaño variable. También es posible combinar la segmentación y la paginación en un único esquema de gestión de memoria. La parte del sistema operativo que administra la memoria se llama administrador de memoria. Su labor consiste en llevar un registro de las partes de memoria que se estén utilizando y aquellas que no, con el fin de asignar espacio en memoria a los procesos cuando estos la necesiten y liberarlo cuando terminen, así como administrar el intercambio entre la memoria principal y el disco, en los casos en que la memoria principal no pueda albergar a todos los procesos. Para realizar lo anterior se pueden utilizar diferentes esquemas de administración de memoria, desde los muy sencillos hasta los más sofisticados. 76 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Administración de memoria sin intercambio o paginación. Los sistemas de administración de la memoria se pueden clasificar en dos tipos: Los que desplazan los procesos de la memoria principal al disco y viceversa durante la ejecución, y aquellos que no los desplazan. El esquema más sencillo de administración de la memoria es aquel en el que sólo se tiene un proceso en memoria en cada instante. El usuario carga toda la memoria con un programa del disco y utiliza la máquina. 0xFF... Programa del usuario Sistema Operativo en RAM Sistema Operativo en ROM Programa del usuario Controladores de dispositivos en ROM Programa del usuario Sistema Operativo en RAM Figura 6-1. Propuestas de organizar la memoria, con un sistema operativo y un proceso de usuario. Modelos de multiprogramación El uso de la CPU se puede mejorar mediante la multiprogramación. En teoría, si el proceso promedio hace cálculos sólo durante 20% del tiempo que permanece en la memoria y tiene cinco procesos al mismo tiempo en memoria, la CPU debería estar ocupada todo el tiempo. Sin embargo, esto es un optimismo irreal, puesto que supone que los cinco procesos nunca esperan la E/S al mismo tiempo. Un mejor modelo consiste en analizar el uso de la CPU desde un punto de vista probabilístico. Supongamos que un proceso ocupa una fracción p de su tiempo en el estado de espera de E/S. Si n procesos se encuentran en la memoria al mismo tiempo, la probabilidad de que los n procesos esperen por E/S (en cuyo caso la CPU estaría inactiva) sería pn. El uso de la CPU está dado entonces por la fórmula Uso de la CPU = 1 - pn Figura 6-2. Gráfica de rendimiento Análisis del rendimiento de un sistema con multiprogramación Figura 6-3. (a) Arribo y requerimientos de trabajo de cuatro tareas. (b) El uso del CPU de 1 a 4 trabajos con 80% de espera por E/S. (c) Secuencia de eventos. 77 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Multiprogramación con particiones fijas Hasta este momento nos hemos dado cuenta de la utilidad de tener más de un proceso en la memoria, pero ¿cómo debemos organizar la memoria para poder lograr esto?. La forma más sencilla es dividir la memoria en n partes (que podrían ser de tamaños distintos). En la figura 6.4 (a) vemos este sistema de particiones fijas y colas de entrada independientes. La desventaja del ordenamiento de las tareas que llegan a la memoria en colas independientes es evidente cuando la cola de una partición grande está vacía, la cola de una partición pequeña está completamente ocupada. (b). Otro tipo de ordenamiento es el que mantiene una sola cola, como en la figura 6.4 Cada vez que se libere una partición, se podría cargar y ejecutar en ella la tarea más cercana al frente de la cola que se ajuste a dicha partición. Puesto que no es deseable que se desperdicie una partición de gran tamaño con una tarea pequeña, otra estrategia consiste en buscar en toda la cola de entrada el trabajo más grande que se ajuste a la partición recién liberada. Observe que este último algoritmo discrimina a las tareas pequeñas, negándoles la importancia suficiente como para disponer de toda una partición, mientras que lo recomendable es que se les dé a las tareas pequeñas el mejor servicio y no el peor. Una forma de salir del problema es tener siempre una pequeña partición por ahí. Tal partición permitiría la ejecución de las tareas pequeñas sin tener que asignarles una partición de gran tamaño. Otro punto de vista es obedecer como regla que un trabajo elegible para su ejecución no sea excluido más de k veces. Cada vez que se le excluya, obtiene un punto. Cuando adquiera k puntos, ya no podrá ser excluido de nuevo. Varias colas de entrada Partición 4 Partición 4 8 GB Partición 3 Una cola de entrada Partición 3 5 GB Partición 2 Partición 2 3 GB Partición 1 Partición 1 2 GB Sistema Operativo Sistema Operativo 0 GB (a) (b) Figura 6-4. (a) Colas de entradas independientes, (b) Ordenamiento en una cola. 78 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Reasignación y protección La multiprogramación presenta dos problemas esenciales por resolver: la reasignación y la protección. Observe la figura 3-4. Ahí es evidente que las diversas tareas se ejecutarán en direcciones distintas. Cuando un programa se liga, el ligador debe conocer la dirección donde comienza el programa en memoria. Por ejemplo, supongamos que la primera instrucción es una llamada a un procedimiento que se encuentra en la dirección relativa 100, dentro del archivo en binario generado por el ligador. Si este programa se carga en la partición 1, esa instrucción saltará a la dirección absoluta 100, la cual está dentro del sistema operativo. Lo que se necesita es hacer una llamada a 100k + 100. Si el programa se carga en la partición 2, debe llevarse a cabo como una llamada a 200k + 100, etc. Este problema se conoce como el problema de reasignación. La reasignación durante el proceso de cargado no resuelve el problema de la protección. Una alternativa para resolver este problema es la de utilizar dos registros especiales de hardware, llamados el registro base y el registro límite. Cuando se planifica la ejecución de un proceso, el registro base se carga con la dirección del inicio de la partición, mientras que el registro límite se carga con al longitud de la partición. A cada dirección de memoria generada en forma automática se le añade el contenido del registro base antes de ser enviado a memoria. Así, si el registro base tiene un valor de 100k, una instrucción CALL 100 se interpreta como una instrucción CALL 100k + 100, sin que la propia instrucción de vea modificada. También se verifican las direcciones, con el fin de no rebasar el registro límite y se dirijan a una parte de la memoria fuera de la partición activa. Intercambio A diferencia de un sistema por lotes en un sistema de tiempo compartido por lo general existen más usuarios de los que puede mantener la memoria, por lo que es necesario mantener el exceso de los procesos en el disco. El traslado de los procesos de la memoria principal al disco y viceversa se llama intercambio. En principio, las particiones fijas se podrían utilizar para un sistema con intercambio. Cada vez que se bloqueara un proceso, se podría trasladar al disco y llevar otro proceso a la partición ocupada por el primero. En la práctica, las particiones fijas no son muy atractivas si se dispone de poca memoria, puesto que la mayor parte de ésta se desperdicia con programas menores que sus particiones. En vez de esto, se utiliza otro algoritmo de administración de la memoria conocido como particiones variables. El hecho de no estar sujeto a un número fijo de particiones que pudieran ser muy grandes o demasiados pequeñas mejora el uso de la memoria pero también hace más compleja la asignación y reasignación de la memoria, así como también complica el mantener un registro de esto. Es posible combinar todos los huecos en uno grande, si se mueven todos los procesos hacia la parte inferior, mientras sea posible. Esta técnica se conoce como compactación de la memoria. Por lo general, no se lleva a cabo, porque consume mucho tiempo de CPU. Otra forma de asignación de memoria, es dándole a cada proceso un espacio mayor para permitirle crecer cuando sea necesario. 79 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Figura 6-5. (a) Asignación de espacio a un segmento creciente de datos. (b) Asignación de espacio a una pila y un segmento de datos crecientes. En términos generales, existen tres formas utilizadas por los sistemas operativos para llevar un registro del uso de la memoria: mapas de bits, listas y sistemas amigables. Administración de la memoria con mapas de bits Con un mapa de bits, la memoria se divide en unidades de asignación, las cuales pueden ser tan pequeñas como unas cuantas palabras o tan grandes como varios kilobytes. A cada unidad de asignación le corresponde un bit en el mapa de bits, el cual toma el valor de 0 si la unidad está libre y 1 si está ocupada (o viceversa). Figura 6-6. Mapa de bits El tamaño de la unidad de asignación es un aspecto importante del diseño. Mientras más pequeña sea esta unidad, más grande será el mapa de bits. Una memoria de 32n bits utilizará n bits del mapa, de forma que dicho mapa sólo ocupa 1/33 de la memoria. Si la unidad de asignación es grande el mapa de bits será pequeño, pero se podría desperdiciar una parte valiosa de la memoria en la última unidad si el tamaño del proceso no es un múltiplo exacto de la unidad de asignación. Un mapa de bits es una forma sencilla para llevar un registro de las palabras de la memoria en una cantidad fija de memoria, puesto que el tamaño del mapa sólo depende del tamaño de la memoria y del tamaño de la unidad de asignación. El problema principal de esto es que, cuando se decide traer a la memoria un proceso de k unidades, el administrador de la memoria debe buscar en el mapa una cadena de k ceros consecutivos. La búsqueda en un mapa de bits de ciertas cadenas es una operación lenta, por lo que los mapas no se utilizan con frecuencia. Administración de la memoria con listas ligadas Cada entrada de la lista especifica un hueco (H) o un proceso (P), la dirección donde comienza, su longitud y un apuntador a la siguiente entrada. La lista de segmentos está ordenada por direcciones. Este orden tiene la ventaja de que al terminar o intercambiar un proceso, la actualización de la lista es directa. Cuando los procesos y los huecos se mantienen en una lista ordenada por direcciones, se pueden utilizar diversos algoritmos para asignar la memoria para un proceso de reciente creación o intercambio Algoritmo primero en ajustarse. El administrador de la memoria revisa toda la lista de segmentos hasta encontrar un espacio lo suficientemente grande. El espacio se divide entonces en dos partes, una para el proceso y otra para la memoria no utilizada, excepto por el caso poco probable de un ajuste exacto. Este algoritmo es rápido, puesto que busca lo menos posible. 80 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Cabe hacer nota que este método es el que utiliza UNIX para la asignación de memoria. Algoritmo el siguiente en ajustarse. Funciona de la misma forma que el anterior, con la diferencia de que mantiene un registro del lugar donde encuentra un hueco adecuado. La siguiente vez que se le llama, comienza a buscar desde el punto donde se detuvo, en vez de comenzar siempre desde el principio, como el caso del algoritmo anterior. Algoritmo del mejor ajuste. El cual busca en toda la lista y toma el mínimo hueco adecuado. En vez de asignar un hueco grande que podría necesitarse más adelante, el mejor en ajustarse intenta encontrar un hueco más cercano al tamaño real necesario. Algoritmo del peor ajuste. Este método toma siempre el hueco más grande disponible, de forma que el hueco obtenido sea suficientemente grande para ser útil. Este algoritmo da la vuelta de encontrar un ajuste casi exacto en un proceso y un hueco demasiado pequeño. Estos cuatro algoritmos pueden agilizarse si se tienen dos listas independientes, una para los procesos y otra para los huecos. De esta forma, todos ellos pueden dedicarse a inspeccionar los huecos, no los procesos. El precio que se paga por ese aumento de velocidad al momento de asignar la memoria es la complejidad adicional y disminución de la velocidad al liberar la memoria, puesto que un segmento liberado debe ser eliminado de la lista de procesos e insertarse en la lista de huecos. Memoria virtual La idea fundamental detrás de la memoria virtual es que el tamaño combinado del programa, los datos y la pila puede exceder la cantidad de memoria física disponible para él. El S.O. mantiene aquellas partes del programa que se utilicen en cada momento en la memoria principal y el resto permanece en el disco. La mayoría de los sistemas con memoria virtual utilizan una técnica llamada paginación. En las computadoras que no tienen memoria virtual, la dirección virtual se coloca en forma directa dentro del bus de la memoria, lo cual hace que se pueda leer o escribir en la palabra de la memoria física que tenga la misma dirección. Al utilizar la memoria virtual, las direcciones virtuales (direcciones generadas por los programas) no pasan de forma directa al bus de la memoria, sino que van a una unidad de administración de la memoria (MMU), un chip o conjunto de chips que asocian las direcciones virtuales con las direcciones de la memoria física. Figura 6-7. Memoria Virtual. Los huecos de direcciones virtuales se dividen en unidades llamadas páginas. Las unidades correspondientes en la memoria física se llaman marcos para página. Las páginas y los marcos tienen siempre el mismo tamaño. Las transferencias entre la memoria y el disco son siempre por unidades de página. Figura 6-8. Marco de página. 81 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Por ejemplo, cuando el programa intenta tener acceso a la dirección 0, mediante la instrucción: MOV REG, 0 La dirección virtual 0 se envía a la MMU. La MMU ve que esta dirección virtual cae dentro de la página 0 (o a 4095), la cual, de acuerdo con su regla de correspondencia, está en el marco 2 (8192, 12287). Transforma entonces la dirección en 8192 y sólo ve una solicitud de lectura o escritura de la dirección 8192, a la que da paso. Así, la MMU ha asociado todas las direcciones virtuales entre 0 y 4095 con las direcciones físicas 8192 a 12287. En forma análoga, la instrucción MOV REG, 8192 se transforma en MOV REG, 24576 Puesto que la dirección virtual 8192 está en la página virtual 2 y esta página está asociada con el marco físico 6 (direcciones físicas 24576 a 28671). Tabla 6-1. Ejemplos de tamaños de página. Computadora Atlas Tamaño de página 512 Honeywell-Multics 1024 Familia IBM 370 2048 ó 4096 Palabra de 48 bits Palabra de 36 bits 8 bits IBM 370/XA y 370/ESA 4096 8 bits Familia VAX 512 8 bits IBM AS/400 512 8 bits Intel 486 (IBM PC) 4096 8 bits 6 8 0 4 0 4096 8 bits Motorola (Macintosh) Unidad Figura 6-9. Traducción de direcciones en un sistema de paginación. 82 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Capítulo 7 Arquitectura del sistema de archivos Un proceso es una entidad en ejecución que tiene asociada un identificador para poder ser identificado. En el sistema se cuenta con procesos pesados, llamados hijos, y procesos ligeros, llamados hilos. Las características que posee el sistema de archivos de UNIX son las siguientes: • • • • • • Posee una estructura jerárquica. Realiza un tratamiento consistente de los datos de los archivos. Puede crear y borrar archivos Permite el crecimiento dinámico de los archivos Protege los datos de los archivos. Trata los dispositivos y periféricos (terminales, unidades de cinta, etc.) como si fuesen archivos El kernel del sistema trabaja con el sistema de archivos a un nivel lógico y no trata directamente con los discos a nivel físico. Cada dispositivo es considerado como un dispositivo lógico que tiene asociados unos números de dispositivo (minor number y major number). Estos números se utilizan para indexar dentro de una tabla de funciones, los cuales tenemos que emplear para manejar el driver del disco. El driver del disco se va a encargar de transformar las direcciones lógicas del sistema de archivos a direcciones físicas del disco. Estructura del sistema de archivos Bloque de boot superbloque lista de inodes bloque de datos 1. Bloque de boot. Se localiza típicamente en el primer sector, y puede contener el código de boot o de arranque. Este código es un pequeño programa que se encarga de buscar el sistema operativo y cargarlo en memoria para inicializarlo. 83 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos 2. Superbloque. Describe el estado del sistema de archivos. Contiene información acerca de su tamaño, total de archivos que puede contener, qué espacio queda libre, etc. 3. Lista de nodos índice (inodes). Esta lista tienen una entrada por cada archivo, donde se guarda una descripción del mismo; situación del archivo en el disco, propietario, permisos de acceso, fecha de actualización, etc. 4. Bloque de datos. Ocupa el resto del sistema de archivos. En esta zona es donde se encuentra situado el contenido de los archivos a los que hace referencia la lista de inodes. El superbloque El superbloque contiene, entre otras cosas, la siguiente información: • • • • • • • • • Tamaño del sistema de archivos. Lista de bloques libres disponibles. Índice del siguiente bloque libre en la lista de bloques libres. Tamaño de la lista de inodes. Total de inodes libres. Lista de inodes libres. Índice del siguiente inode libre en la lista de inodes libres. Campos de bloqueo de elementos de las listas de bloques libres y de inodes libres. Estos campos se emplean cuando se realiza una petición de bloqueo o de inode libre. Flag para indicar si el superbloque ha sido modificado o no. En la memoria del sistema se cuenta que una copia del superbloque y de la lista de inodes, para realizar de forma eficiente el acceso a disco. Existe un demonio (syncer) que se encarga de realizar la actualización en disco de los datos de administración que se encuentran en memoria, este demonio se levanta al iniciar el sistema. Naturalmente antes de apagar el sistema también hay que acualizar el superbloque y las tablas de inodes del disco, el encargado de llevar a cabo lo anterior es el programa shutdown. Nodos índices (inodes) Cada archivo en un sistema UNIX tiene asociado un inode. El inode contiene información necesaria para que un proceso pueda acceder al archivo. Esta información incluye: propietario, derechos de acceso, tamaño, localización en el sistema de archivos, etc. La lista de inodes se encuentra situada en los bloques que hay a continuación del superbloque. Durante el proceso de arranque del sistema, el kernel lee la lista de inodes del disco y carga una copia en memoria, conocida como tabla de inodes. Las manipulaciones que haga el subsistema de archivos sobre los archivos van a involucrar a la tabal de inodes pero no a la lista de archivos, ya que la tabla de inodes está cargada siempre en memoria. La actualización periódica de la lista de inodes del disco la realiza un demonio del sistema. Los campos que componen un inode son los siguientes: • Identificador del propietario del archivo. La posesión se divide entre un propietario individual y un grupo de propietarios y define el conjunto de usuarios 84 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos • • • • • que tienen derecho de acceso al fichero. El superusuario tiene derecho de acceso a todos los ficheros del sistema. Tipo de archivo. Los archivos pueden ser ordinarios de datos, directorios, especiales de dispositivos y tuberías. Tipo de acceso al archivo. Da información sobre la fecha de la última modificación del archivo, la última vez que se accedió a él y la última vez que se modificaron los datos de su inode. Número de enlaces del archivo. Representa el total de los nombres que el archivo tiene en la jerarquía de directorios. Entradas para los bloques de dirección de los datos de un archivo. Si bien los usuarios tratan los datos de un archivo como si fuesen una secuencia de bytes contiguos, el kernel puede almacenarlos en bloques que no tienen por qué ser contiguos. En los bloques de dirección es donde se especifican los bloques de disco que contienen los datos del archivo. Tamaño del archivo. Los bytes de un archivo se pueden direccionar indicando un offset a partir de la dirección de inicio del archivo (offset 0) . Hay que hacer notar lo siguiente: 1. El nombre el archivo no queda especificado en su inode. 2. Existe una diferencia entre escribir el contenido de un inode en disco y escribir el contenido del archivo. El contenido del archivo (sus datos) cambia sólo cuando se escribe en él. El contenido de un inode cambia cuando se modifican los datos del archivo o la situación administrativa del mismo (propietario, permisos, enlaces, etc.). La tabla de inode contiene la misma información que la lista de inodes, además de la siguiente información: • • • • • El estado del inode, que indica o Si el inode está bloqueado; o Si hay algún proceso esperando a que el inode quede desbloqueado; o Si la copia del inode que hay en memoria difiere de la que hay en el disco; o Si la copia de los datos del archivo que hay en memoria difieren de los datos que hay en el disco (caso de la escritura en el archivo a través del buffer caché). El número de dispositivo lógico del sistema de archivos que contiene al archivo. El número de inode. Punteros a otros inodes cargados en memoria. El kernel enlaza los inodes sobre una cola hash y sobre una lista libre. Un contador que indica el número de copias del inode que están activas (por ejemplo, porque el archivo está abierto por varios procesos). Figura 7-1. Tabla de direcciones de bloque de un inodo. Tipos de archivos en UNIX En UNIX existen cuatro tipos de archivos: • Archivos ordinarios, también llamados archivos regulares o de datos. 85 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos • • • Directorios Archivos de dispositivos, conocidos también como archivos especiales Archivos de comunicación, tales como tuberías o pipes Archivos ordinarios Los archivos ordinarios contienen bytes de datos organizados como un arreglo lineal. Las operaciones que se pueden hacer sobre los datos de uno de estos archivos son: • Leer o escribir cualquier byte de este archivo. • Añadir bytes al final del archivo, aumentando su tamaño. • Truncar el tamaño de un archivo a cero bytes, esto es como si borrásemos el contenido del archivo. Las operaciones siguientes no están permitidas: • • • Insertar bytes en un archivo, excepto al final. Borrar bytes de un archivo, excepto el borrado de bytes con la puesta a cero de los que ya existen. Truncar el tamaño de un archivo a un valor distinto de cero. Los archivos ordinarios, como tales, no tienen nombre y el acceso a ellos se realiza a través de los inodes. Directorios Los directorios son los archivos que nos permiten darle una estructura jerárquica a los sistemas de archivos de UNIX. Su función fundamental consiste en establecer la relación que existe entre el nombre de un archivo y su inode correspondiente. En algunas versiones de UNIX, un directorio es un archivo cuyos datos están organizados como una secuencia de entradas, cada una de las cuales contiene un número de inode y el nombre de un archivo que pertenece al directorio. Al par inode-nombre de archivo se le conoce como enlace (link). Offset 0 inode nombre del archivo 16 32 48 64 .... ...... ....... Figura. Ejemplo de estructura del directorio /etc para UNIX System V El kernel maneja los datos de un directorio con los mismos procedimientos con que se manejan los datos de los archivos ordinarios, usando la estructura inode y los bloques de acceso directo e indirectos. 86 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Los procesos pueden leer el contenido de un directorio como si se tratase de un archivo de datos, sin embargo no pueden modificarlos. El derecho de escritura en un directorio está reservado al kernel. Los permisos de acceso a un directorio tiene los siguientes significados: • • • Permiso de lectura. Permite que un proceso pueda leer ese directorio. Permiso de escritura. Permite a un proceso crear una nueva entrada en el directorio o borrar alguna ya existente. Esto se puede realizar a través de las llamadas: creat, mknod, link o unlink. Permiso de ejecución. Autoriza a un proceso para buscar el nombre de un archivo dentro del directorio. Desde el punto de vista del usuario, vamos a referenciar los archivos mediante su path name. El kernel es quien se encarga de transformar el path name de un archivo a su inode correspondiente. Archivos especiales Los archivos especiales o archivos de dispositivos permiten a los procesos comunicarse con los dispositivos periféricos (discos, cintas, impresoras, terminales, redes, etc.). Existen dos tipos de archivos de dispositivos: archivos de dispositivos modo bloque y archivos de dispositivos modo carácter. Los archivos de dispositivos modo bloque se ajustan a un modelo concreto: el dispositivo contiene un arreglo de bloques de tamaño fijo (generalmente múltiplo de 512 bytes) y el kernel gestiona un buffer caché (implementado vía software) que acelera la velocidad de transferencia de los datos; ejemplos típicos de estos dispositivos son: los discos y las unidades de cinta. En los archivos de dispositivos modo carácter la información es vista por el kernel o por el usuario como una secuencia lineal de bytes; la velocidad de transferencia de los datos entre el kernek y el dispositivo se realiza a baja velocidad, dado que no se involucra al buffer caché; ejemplos típicos de estos dispositivos son: las terminales serie y las líneas de impresora. Los módulos del kernel que gestionan la comunicación con los dispositivos se conocen como drivers de dispositivos (device drivers). El sistema también puede soportar dispositivos software (o seudo dispositivos) que no tienen asociados un dispositivo físico. Por ejemplo, si una parte de la memoria del sistema se gestiona como un dispositivo, los procesos que quieran acceder a esa zona de memoria tendrán que usar las mismas llamadas al sistema que hay para el manejo de archivos, pero sobre el archivo de dispositivo /dev/mem (archivo de dispositivo genérico para acceder a memoria). En esta situación, la memoria es tratada como un periférico más. Como ya hemos visto anteriormente, los archivos de dispositivos, al igual que el resto de los archivos, tienen asociado un inode. En el caso de los archivos ordinarios o de los directorios, el inode nos indica los bloques donde se encuentran los datos de los archivos, pero en el caso de los archivos de dispositivos no hay datos a los que refernciar. En su lugar, el inode contiene dos números conocidos como major number y minor number. El major number indica el tipo de dispositivo de que se trata (disco, cinta, terminal, etc.) y el 87 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos minor number indica el número de unidad dentro del dispositivo. En realidad, estos números los utiliza el kernel para buscar dentro de unas tablas una colección de rutinas que permiten manejar el dispositivo. Esta colección de rutinas constituyen realmente el driver del dispositivo. Archivos de comunicación (tubería o pipe) Un pipe es un archivo con una estructura similar a la de un archivo ordinario. La diferencia principal con éstos es que los datos de un fifo son transitorios. Los fifos se utilizan para comunicar dos procesos. Lo normal es que un proceso abra el fifo para escribir en él y otro para leer de él. Los datos escritos en el fifo se leen en el mismo orden en el que fueron escritos (de ahí su nombre fifo – first in first out). La sincronización del acceso al fifo es algo de lo que se encarga el kernel. El almacenamiento de los datos en un fifo se realiza de la misma forma que en un archivo ordinario, excepto que el kernel sólo utiliza entradas directas de la tabla de direcciones de bloque del inode del fifo. Por lo tanto, un fifo podrá almacenar 10 Kbytes a lo más. 88 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Lectura de directorios Veamos primero algunas funciones que necesitaremos para programar, basados en el Manual del Programador de Linux. PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <dirent.h> DIR *opendir(const char *nombre); La función opendir() proporciona un identificador de bloque al directorio utilizado por las demás funciones de directorio. Devuelve un apuntador al flujo de directorio o NULL si ocurre un error. El flujo se sitúa en la primera entrada del directorio. ERRORES EACCES Permiso denegado. EMFILE El proceso está usando demasiados descriptores de fichero. ENFILE Hay demasiados ficheros abiertos en el sistema. ENOENT El directorio no existe o nombre es una cadena vacía. ENOMEM Memoria insuficiente para completar la operación. ENOTDIR El nombre no es un directorio. PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <dirent.h> struct direct *readdir (DIR *dirp) Cada llamada subsecuente a readdir devuelve un apuntador a una estructura que contiene información sobre la siguiente entrada del directorio. La función readdir devuelve NULL cuando llega al final del directorio. Se debe utilizar rewinddirpara volver a empezar, o closedir para terminar. La estructura dirent se declara como sigue: struct dirent 89 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos { } long d_ino; /* número de nodo-i */ off_t d_off; /* ajuste hasta el dirent */ unsigned short d_reclen; /* longitud del d_name */ char d_name [NAME_MAX+1]; /* nombre fichero (acabado en nulo) */ d_ino es un número de nodo-i. d_off es la distancia desde el principio del directorio hasta este dirent. d_reclen es el tamaño de d_name, sin contar el carácter nulo del final. d_name es un nombre de fichero, una cadena de caracteres terminada en nulo. Ejemplo. Programa para imprimir la lista de archivos contenidos en un directorio. #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dirent.h> #include <errno.h> void main (int argc, char *argv[ ]) { DIR *directorio; struct dirent *entradadir; if (argc !=2 ) { fprintf (stderr, “Use: %s nombre_directorio \n”, argv[0]); exit (1); } if ( (directorio = opendir (argv[1]) )== NULL) { fprintf (stderr, “No puedo abrir el directorio %s. Error %s\n”, argv[1], strerror(errno)); exit(1); } while ( (entrada_dir = readdir (directorio) ) ! = NULL) printf (“%s\n”, entradadir ->d_name); } closedir (directorio); exit(0); 90 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Acceso a archivos especiales Entrada/salida sobre terminales Los terminales son dispositivos especiales que trabajan en modo carácter. Todo programa que se ejecuta en UNIX tiene asociados 3 descriptores de archivo que le dan acceso a su terminal de control. Estos descriptores son: 0 para la entrada estándar, 1 para la salida estándar y 2 para la salida estándar de errores. El archivo de dispositivo que permite a un proceso acceder a su terminal de control es dev/tty. Si el sistema no reservase de forma automática los descriptores anteriores, podríamos hacerlo nosotros mediante las siguientes llamadas: close (O); open ("/dev/tty", O_RDONLY); /* Reserva del descriptor 0. */ close (l); open ("/dev/tty", O_WRONLY); /* Reserva del descriptor 1. */ close (2); open ("/dev/tty", O_WRONLY); /* Reserva del descriptor 2. */ En el sistema hay un terminal especial llamado consola (dispositivo /dev/console) que es empleado durante el arranque para sacar los mensajes relativos a este proceso. Cada usuario que inicia una sesión de trabajo interactiva, lo hace a través de un terminal. Este terminal tiene asociado un archivo de dispositivo que localmente se puede abrir como /dev/tty, pero que visto por otros usuarios tiene la forma /dev/ttyXX, donde XX representa dos dígitos. Como ejemplo para ilustrar el acceso a terminales, vamos a escribir una versión simplificada de la orden write. Esta orden se emplea para enviar mensajes a los usuarios que hay conectados al sistema. Su forma de uso es: $ write usuario Línea de texto 1 Línea de texto 2 . . Línea de texto n ^D [Fin de archivo] Esta secuencia hará que usuario reciba, a través de su terminal, las n líneas de texto que le enviamos. Para poder enviar el mensaje, tenemos que saber si el usuario existe y si tiene iniciada sesión de trabajo. También hay que conocer cuál es el archivo de dispositivo que tiene asociado su terminal. Para obtener respuesta a estas dos preguntas, hay que consultar el archivo /etc/utmp. Este archivo es gestionado por el sistema y contiene información administrativa de los procesos que hay ejecutándose en un instante determinado. Para hacer la lectura de utmp independiente de su estructura, vamos a emplear la función estándar getutent. Su declaración es: 91 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos PROTOTIPO DE LA FUNCIÓN #include <sys/types.h> #include <sys/utmp.h> struct utmp *getutent ( ) Con cada llamada a getutent se lee un registro del archivo /etc/utmp. Si el archivo no está abierto, la llamada se encarga de abrirlo. Después de leer un registro, la función devuelve un puntero a una estructura del tipo utmp, definida en el archivo de cabecera <sys/ utmp.h>. Cuando getutent llega al final del archivo, devuelve un puntero NULL. La definición de la estructura utmp es la siguiente: struct utmp { char char char pid_t short ut_user[8]; /* Nombre del usuario. */ ut_id[4]; /* Identificador de /etc/inittab. ut-line[12], /* Nombre delarchivo de dispositivo asociado (console, ttyXX, lnNXX, etc ... ) */ ut_pid /*ldentificador del proceso. */ ut_type; /* Tipo de entrada: EMPTY RUN_LVL BOOT TIME OLD_TIME NE W_TIME IN1T_PROCESS LOGIN_PROCESS USER_PROCESS DEAD_PROCESS ACCOUNTING */ struct exit_status { short e_terminatio; /*Estado de terminación del proceso.*/ short e_exit; /*Estado de salida del proceso. */ }ut_time; /* Se aplica a las entradas cuyo tipo es DEAD_PROCESS.*/ unsigned short ut_reserved1; /* Reservada para usos futuros.*/ char ut_host[16]; /* Nombre del host. */ unsigned long ut_addr; /* Dirección internet del host. */ }; La forma de averiguar si un usuario está o no conectado al sistema, es buscar una entrada del archivo utmp cuyo campo ut_user coincida con el nombre del usuario que buscamos. Para saber 92 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos cuál es el archivo de dispositivo que tiene asociado su terminal, tenemos que utilizar el campo ut_line. A continuación mostrarnos el código del programa mensaje_para, que es equivalente a la orden estándar write. EJEMPLO. Envío de mensajes a un usuario (mensaje_para.c). #include <stdio.h> #include <fcntl.h> #include <utmp.h> main (int argc, char *argv[ ]) int tty; char terminal [20], mensaje [256], *logname; struct utmp *utmp, *getutent( ); if (argc != 2) { fprintf (stderr, "Forma de uso: %s usuario\n", argv[0]); exit (-1); } /* Consultamos si el usuario está en sesión. */ while (( utmp = getutent ( )) != NULL && strncmp (utmp->ut_user, argv[1], 8) != 0); if (utmp = = NULL) { printf ("EL USUARIO %s NO ESTÁ EN SESIÓN.\n", argv[0]); exit (0); } /* Apertura del terminal del usuario. */ sprintf (terminal, "/dev/%s", utmp->ut_line); if ((tty = open (terminal, O_WRONLY))= = -1) { perror (terminal); exit (-1); } /* Lectura de nuestro nombre de usuario. */ logname = getenv ("LOGNAME"); /* Aviso al usuario que va a recibir nuestro mensaje. */ sprintf (mensaje, "\n\t\tMENSAJE PROCEDENTE DEL USUARIO %s\07\07\07\n", logname); write (tty, mensaje, strlen (mensaje)); /* Envío del mensaje.*/ 93 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos while (gets (mensaje) != NULL) { write (tty, mensaje, strlen (mensaje)); sprintf (mensaje, "\n"); write (tty, mensaje, strlen (mensaje)); } sprintf (mensaje, "\n<FIN DEL MENSAJE>\n"); write (tty, mensaje, strlen (mensaje)); close (tty); exit (0); } 94 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Capítulo 8 Entrada y Salida Un proceso es una entidad en ejecución que tiene asociada un identificador para poder ser identificado. En el sistema se cuenta con procesos pesados, llamados hijos, y procesos ligeros, llamados hilos. Entrada/Salida a disco En los últimos años, el crecimiento en la velocidad de los procesadores y de la memoria principal ha dejado muy atrás la velocidad de acceso a disco. Como el rendimiento del disco está estrechamente relacionado con las cuestiones de diseño, analicemos el por qué de estas bajas velocidades y veamos las políticas de planificación de los discos. Parámetros de rendimiento de discos Los detalles reales de las operaciones de E/S con los discos dependen de la computadora, el sistema operativo y la naturaleza del canal de E/S y del hardware que controla el disco. Cuando la unidad de disco está operando, el disco gira a una velocidad constante. Para leer o escribir, la cabeza debe posicionarse en la pista deseada, al comienzo del sector pertinente. Si el sistema es de cabezas móviles, hay que mover la cabeza para elegir la pista. Si el sistema es de cabezas fijas, habrá que seleccionar electrónicamente una de ellas. En un sistema de cabezas móviles, el tiempo que se tarda en ubicar la cabeza en la pista se llama tiempo de búsqueda. En cualquier caso, una vez que se ha seleccionado la pista, el controlador del disco esperará hasta que el sector apropiado se alinee con la cabeza en su rotación. El tiempo que tarda el comienzo del sector en llagar hasta la cabeza se conoce como retardo de giro, o latancia de giro. La suma del tiempo de búsqueda y el retardo de giro es el tiempo de acceso, es decir, el tiempo que se tarda en llegar a la posición de lectura o escritura. Una vez que la cabeza está ubicada, se puede llevar a cabo la operación de Lectura o Escritura a medida que el sector se mueve bajo la cabeza; está es la parte de transferencia real de datos de la operación. Además del tiempo de acceso y del tiempo de transferencia, en una operación de E/S intervienen algunos retardos. Cuando un proceso emite una petición de E/S, primero debe esperar en una cola a que el dispositivo esté disponible. En este momento, el dispositivo queda asignado al proceso. Si el dispositivo comparte un único canal de E/S o un conjunto de canales con otras unidades de disco, puede producirse una espera adicional 95 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos hasta que el canal esté disponible. En este punto se realizará la búsqueda con que comienza el acceso al disco. En algunos sistemas grandes se emplea una técnica conocida como detección posicional de giro (RPS). Esta técnica funciona de la siguiente forma: Cuando se ejecuta la orden de búsqueda, se libera el canal para que pueda realizar otras operaciones de E/S. Cuando la búsqueda termine, el dispositivo debe averiguar el instante en que los datos van a pasar bajo la cabeza. A medida que el sector se aproxima a la cabeza, el dispositivo intenta restablecer la vía de comunicación con la computadora central. Si la unidad de control o el canal están ocupados con otra operación de E/S, el intento de reconexión no tendrá éxito y el dispositivo debe dar una vuelta completa antes de intentar la reconexión, lo que se denomina una falta de RPS. Espera de Dispositivo Espera de Canal Búsqueda Retardo de Giro Transferencia de Datos Dispositivo Ocupado Tiempo de búsqueda El tiempo de búsqueda es el tiempo necesario para mover el brazo del disco hasta la pista solicitada. El tiempo de búsqueda depende de dos componentes claves: el tiempo de arranque inicial y el tiempo que se tarda en recorrer los cilindros. Por desgracia, este tiempo de recorrido no es lineal, pero nos podemos aproximar por medio de la fórmula: Donde Ts = m × n + s T = tiempo de búsqueda estimada. n = número de pistas recorridas. m = constante que depende de la unidad de disco. s = tiempo de arranque. Tiempo de transferencia El tiempo de transferencia con el disco depende de la velocidad de rotación de la forma siguiente: T = b / (rN) Donde T = tiempo de transferencia. b = número de bytes a transferir. N = número de bytes por pista. r = velocidad de rotación en revoluciones por segundo. Por lo tanto, el tiempo medio de acceso total puede expresarse como Ta= Ts + (1 / 2r) + (b/ rN) 96 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Donde Ts es el tiempo medio de búsqueda. Políticas de planificación de Discos. Como podemos observar la razón de la diferencia en el rendimiento puede encontrarse en el tiempo de búsqueda, entonces se queremos mejorar debemos reducir el tiempo medio gastado en las búsquedas. FIFO La planificación más sencilla de planificación es la de “Primero en entrar, primero en salir” (FIFO). Esta estrategia tiene la ventaja de ser justa porque las peticiones son servidas en el orden en que llegaron. Prioridad Con un sistema de prioridad (PRI), el control de la planificación queda aislado del control del software gestor del disco. Los trabajos por lotes que sean cortos y los trabajos interactivos reciben frecuentemente una prioridad más alta que trabajos mayores que realizan largas operaciones. Esta práctica permite que el sistema haga salir más rápidamente a muchos trabajos cortos y pueda proporcionar un buen tiempo de respuesta interactiva, sin embargo, los trabajos mayores pueden tener que esperar excesivamente. Este tipo de política tiende a ser poco favorable para sistemas de bases de datos. LIFO En los sistemas de proceso de transacciones, conceder el dispositivo al último usuario acarrea pocos o nulos movimientos del brazo al recorrer un archivo secuencial. El provecho de etsa cercanía mejora la productividad y reduce la longitud de las colas. A medida que un trabajo utiliza de forma activa el sistema de archivos, va procesándose tan rápido como es posible. Sin embargo, si el disco está ocupado con una carga de trabajo larga, existe la posibilidad inconfundible de inanición. Una vez que un trabajo ha lanzado una petición de E/S a la cola y haya abandonado la cabeza, no podrá volver a ganar la cabeza a menos que se vayan todos los que estén por delante. La política FIFO, la de prioridades y el esquema LIFO se basan únicamente en las propiedades de la cola o del proceso demandante. Si la posición de la pista actual es conocida por el planificador, puede emplearse una planificación en función del elemento demandado. Primero el más corto La política de “primero el más corto” (SSTF) es elegir la solicitud de E/S a disco que requiera el menor movimiento posible del brazo del disco desde su posición actual. De este modo, siempre se elige procurando el mínimo tiempo de búsqueda. SCAN El algoritmo SCAN es una alternativa que previene la inanición, con el SCAN, el brazo sólo se puede mover en un sentido, resolviendo todas las peticiones pendientes de su ruta, hasta que alcance la última pista o hasta que no haya más peticiones en esa dirección. Se cambia entonces la dirección de servicio y el rastreo sigue en sentido opuesto, volviendo a recoger todas las peticiones en orden. 97 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos C-SCAN La política del C-SCAN restringe el rastreo a una sola dirección. Así cuando se haya visitado la última pista en un sentido, el brazo vuelve al extremo opuesto del disco y comienza a recorrerlo de nuevo, lo que reduce el retardo máximo sufrido por las nuevas peticiones. Con el SCAN, si t es el tiempo esperado de un recorrido desde la pista más interior a la más exterior, entonces el intervalo de servicio esperado para los sectores de los extremos es de 2t. Con el C-SCAN, el intervalo es de orden de t + Smax, donde Smax es el tiempo de búsqueda máximo. SCAN de N pasos y FSCAN La política del SCAN de N pasos divide la cola de peticiones del disco en subcolas de longitud N. Las subcolas se procesan una a una mediante un SCAN. Mientras se procesa una cola, se añadirán nuevas peticiones a las otras. SI hay menos de N peticiones disponibles al final del rastreo, entonces todas serán procesadas en el siguiente recorrido. Para valores grandes de N, el rendimiento del SCAN de N pasos se aproxima al del SCAN; con un valor de N = 1, se está adoptando la política de FIFO. La política de FSCAN emplea dos subcolas. Cuando comienza un rastreo, todas las peticiones están en una de las colas y la otra permanece vacía. Durante el recorrido, todas las peticiones nuevas se colocan en la cola que inicialmente estaba vacía. De este modo, el servicio de nuevas peticiones se retrasará hasta que se hayan procesado las viejas. Nombre Tabla 8-1. Algoritmos de planificación de discos. Descripción Comentarios Selección en función del demandante: RSS Planificación aleatoria Para análisis y simulación. FIFO Primero en entrar, primero El más justo de todos. en salir PRI Prioridad del proceso El control se lleva aparte de la gestión de la cola del disco LIFO Último en entrar, primero en Maximiza la utilización de salir recursos y aprovecha la cercanía. Selección en función del elemento solicitado: SSTF Primero el más corto Gran aprovechamiento y colas pequeñas. SCAN Recorrer el disco de un lado Mejor distribución del servicio. a otro. C-SCAN Recorrer el disco en un solo M e n o r v a r i a b i l i d a d e n e l sentido. servicio. SCAN de N pasos SCAN de N registros a la Garantía de servicio. vez. FSCAN SCAN de N pasos, con N = Sensible a la carga. longitud de la cola al comienzo del ciclo del SCAN. 98 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Capítulo 9 Señales Las señales son interrupciones de software que pueden ser enviadas a un proceso para informarle de algún evento asíncrono o situación especial. El término señal se emplea también para referirse al evento Introducción Los procesos pueden enviarse señales unos a otros a través de la llamada kill y es frecuente que durante su ejecución, un proceso reciba señales procedentes del kernel. Cuando un proceso recibe una señal, puede proceder de tres diferentes formas (Figura 9-1): 1. Ignorar la señal, con la cual es inmune a la misma. 2. Invocar a la rutina de tratamiento por defecto. Esta rutina no la codifica el programador, sino que la aporta el kernel. Según el tipo de señal, la rutina de tratamiento por defecto va a realizar una acción u otra. Por lo general, suele provocar la terminación del proceso mediante una llamada a exit. Algunas señales no sólo provocan la terminación del proceso, sino que además hacen que el kernel genere en el directorio actual del proceso un archivo llamado core que contiene un volcado de memoria del contexto del proceso. Este mecanismos es muy útil a la hora de depurar programas que contienen errores de manejo de los números en coma flotante, instrucciones ilegales, acceso a direcciones fuera de rango, etc. 3. Invocar a una rutina propia que se encarga de tratar la señal. Esta rutina es invocada por el kernel en si esta se encuentra montada y es responsabilidad del programador codificarla para que tome las acciones pertinentes. 99 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Figura 9-1. Tratamiento de una señal. Tipos de señales Cada señal tiene asociado un número entero positivo, que es el intercambiado cuando uno de ellos envía una señal a otro. En UNIX System V hay definidas 19 señales, y en el 4.3BSD, 30. Las señales las podemos clasificar en los siguientes grupos: • • • • • • Señales relacionadas con la terminación de procesos. Señales relacionadas con las excepciones inducidas por los procesos. Por ejemplo, el intento de acceder fuera del espacio de direcciones virtuales, los errores producidos al manejar números en coma flotante, etc. Señales relacionadas con los errores irrecuperables originados en el transcurso de una llamada al sistema. Señales originadas desde un proceso que se está ejecutando en modo usuario. Por ejemplo, cuando un proceso envía una señal a otro vía kill, cuando un proceso activa un temporizador y se queda en espera de la señal de alarma, etc. Señales relacionadas con la interacción con la terminal. Por ejemplo, pulsar la tecla break. Señales para ejecutar un programa paso a paso. En el archivo de cabecera <signal.h> están definidas las señales que puede manejar el sistema y sus nombres. Las 19 señales del UNIX System V son: SIGHUP (1) SIGINT (2) SIGQUIT (3) Hangup. Es enviada cuando un terminal se desconecta de todo proceso del que es terminal de control. También se envía a todos los procesos de un grupo cuando el líder del grupo termina su ejecución. La acción por defecto de esta señal es terminar la ejecución del proceso que la recibe. Interrupción. Se envía a todo proceso asociado con un terminal de control cuando se pulsa la tecla de interrupción. Su acción por defecto es terminar la ejecución del proceso que la recibe. Salir. Similar a SIGINT, pero es generada al pulsar la tecla de salida (Control-\). Su acción por defecto es generar un archivo core y terminar el proceso. 100 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos SIGILL (4) SIGTRAP (5) SIGIOT (6) SIGMT (7) SIGFPE (8) SIGKILL (9) SIGBUS (10) SIGSEGV (11) SIGSYS (12) SIGPIPE (13) SIGALRM (14) SIGTERM (15) SIGUSR1 (16) SIGUSR2 (17) SIGCLD (18) SIGPWR (19) Instrucción ilegal. Es enviada cuando el hardware detecta una instrucción ilegal. Los programas que manejan punteros a funciones que no han sido correctamente inicializados producen este tipo de error. Su acción por defecto es generar un archivo core y terminar el proceso. Trace trap. Es enviada después de ejecutar cada instrucción, cuando el proceso se está ejecutando paso a paso. Su acción por defecto es generar un archivo core y terminar el proceso. I/O trap instruction. Se envía cuando se da un fallo de hardware. La naturaleza de este fallo depende de la máquina, aquí también se genera un archivo core. Emulator trap instruction. También indica un fallo de hardware. Raras veces se utiliza. Su acción por defecto es generar un archivo core y terminar el proceso. Error en coma flotante. Es enviado por el hardware cuando detecta un error en coma flotante, como el uso de número en coma flotante con unformato desconocido, errores de overflow o underflow, etc. Su acción por defecto es generar un archivo core y terminar el proceso. Kill. Esta señal provoca irremediablemente la terminación del proceso. Genera un archivo core y termina el proceso. Bus error. Se produce cuando se da un error de acceso a memoria. Las dos situaciones típicas que la provocan son: intentar acceder a una dirección que físicamente no existe o intentar acceder a una dirección impar. Su acción por defecto es generar un archivo core y terminar el proceso. Violación de segmento. Es enviada a un proceso cuando intenta acceder a datos que se encuentran fuera de su segmento de datos. Su acción por defecto es generar un archivo core y terminar el proceso. Argumento erróneo en una llamada al sistema. No se usa. Intento de escritura en una tubería de la que no hay nadie leyendo. Esto suele ocurrir cuando el proceso de lectura termina de una forma anormal. Su acción por defecto es terminar el proceso. Alarm clock. Es enviada a un proceso cuando alguno de sus temporizadores descendentes llega a cero. Su acción por defecto es terminar el proceso. Finalización software. Es la señal utilizada para indicarle a un proceso que debe terminar su ejecución. Esta señal no es tajante como SIGKILL y puede ser ignorada. Esta señal es enviada a todos los procesos durante el shutdown. Su acción por defecto es terminar el proceso. Señal número 1 de usuario. Esta señal esta reservada para uso del programador. Su acción por defecto es terminar el proceso. Señal número 1 de usuario. Su significado es idéntico al de SIGUSR1. Muerte del proceso hijo. Es enviada al proceso padre cuando alguno de sus procesos hijos termina. Esta señal es ignorada por defecto. Fallo de alimentación. 101 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Tabla 9-1. Señales generales. Acción por defecto Nombre Número Generar core Terminar Ignorar SIGHUP 01 * SIGINT 02 * SIGQUIT 03 * * SIGILL 04 * * SIGTRAP 05 * * SIGIOT 06 * * SIGEMT 07 * * SIGFPE 08 * * SIGKILL 09 SIGBUS 10 * * SIGSEGV 11 * * SIGSYS 12 * * SIGPIPE 13 * SIGALRM 14 * SIGTERM 15 * SIGUSR1 16 * SIGUSR2 17 * SIGCLD 18 * SIGPWR 19 * * En Linux, se pueden encontrar las señales en el archivo /usr/include/asm-generic/signal.h, Nombre Número Tabla 9-2. Señales en Linux. Descripción SIGHUP 01 Termina el proceso líder SIGINT 02 Tecla Ctrl C pulsada SIGQUIT 03 Tecla Ctrl \ pulsada termina terminal SIGILL 04 Instrucción Ilegal SIGTRAP 05 Traza de los programas S I G A B R T / 06 SIGIOT SIGBUS 07 Terminación anormal SIGFPE 08 Error de aritmético, coma flotante SIGKILL 09 Elimar procesos incondicionalmente SIGUSR1 10 Señal definida por el usuario Error de bus 102 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos SIGSEGV 11 Violación de segmento SIGUSR2 12 Señal definida por el usuario SIGPIPE 13 Escritura de pipe sin lectores SIGALRM 14 SIGTERM 15 Señal enviada por el kernel cuando es fin del reloj ITIMER_REAL Señal de terminación del software SIGTKFLT 16 Desbordamiento de coprocesador matemático SIGCHLD 17 SIGCONT 18 SIGSTOP 19 Señal enviada por el núcleo a un padre cuando este hace un wait, para avisarle que un hijo ha terminado con un exit. Señal cuando el proceso se lleva a segundo o primer plano Suspensión de un proceso SIGTSTP 20 Suspensión debido a Ctrl Z SIGTTIN 21 SIGTTOU 22 Suspensión de un proceso en segundo plano que trata de leer en la terminal Suspensión de un proceso en segundo plano que trata de escribir en la terminal Datos urgentes para los sockets Sobre pasado el límite de tiempo en el CPU Sobre pasado el tamaño del archivo Fin del temporizador ITIMER_VIRTUAL SIGURG 23 SIGXCPU 24 SIGXFSZ 25 SIGVTALAR 26 M SIGPROF 27 SIGWICH 28 SIGIO 29 SIGPWR 30 S I G S Y S / 31 SIGUNUSED SIGRTMIN 32 Fin del temporizador ITIMER_PROF Cambio de tamaño de una ventana usado por X11 Datos disponibles para una entrada/salida Fallo de alimentación Error de argumento en una llamada Marca el límite de señales en tiempo real 103 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Señales en Linux Para enviar una señal desde un proceso a otro o a un grupo de procesos, se emplea la llamada a kill. Su declaración es: PROTOTIPO DE LA FUNCIÓN #include <signal.h> int kill (pid, sig) pid_t pid; Pid identifica al conjunto de procesos que queremos enviarle la señal. Pid es un número entero y los distintos valores que puede tomar tienen los siguientes significados: Pid > 0 Es el PID del proceso al que le enviamos la señal. Pid = 0 La señal es enviada a todos los procesos que pertenecen al mismo grupo que el proceso que la envía. Pid = -1 La señal es enviada a todos aquellos procesos cuyo ID real es igual al ID efectivo del proceso que la envía. Si el proceso que la envía tiene ID efectivo de superusuario, la señal es enviada a todos los procesos, excepto al proceso 0 (swapper) y al proceso 1 (INIT). Pid < -1 La señal es enviada a todos los procesos cuyo ID de grupo coincide con el valor absoluto de pid. En todos los casos, si el ID efectivo del proceso no es el del superusuario o si el proceso que envía la señal no tiene privilegios sobre el proceso que la va a recibir, la llamada a kill falla. Sig es el número de la señal que queremos enviar. Si el envío se realiza satisfactoriamente, kill devuelve 0; en caso contrario, devuelve –1 y en errno estará el código del error producido. Tratamiento de señales. Para especificar qué tratamiento debe realizar un proceso al recibir una señal, se emplea la llamada signal. Su declaración es la siguiente: 104 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos PROTOTIPO DE LA FUNCIÓN #include <signal.h> void *signal (sig, accion) (); int sig; void (*accion) ( ); Sig es el número de la señal sobre la que queremos especificar la forma de tratamiento. Accion es la acción que queremos que se inicie cuando se reciba la señal. Accion puede tomar tres tipos de valores: SIG_DFL SIG_IGN Dirección Indica que la acción a realizar cuando se recibe la señal es la acción por defecto asociada a la señal. Indica que la señal se debe ignorar. Es la dirección de la rutina de tratamiento de la señal. La declaración de esta función debe ajustarse a: PROTOTIPO DE LA FUNCIÓN #include <signal.h> void handler (sig [, code, scp]) int sig, code; struct sigcontext *scp; Cuando se recibe la señal sig, el kernel se encarga de llamar a la rutina hndler pasándo los parámetros sig, code y scp. Sig es el número de la señal, code contiene información sobre el hardwar en el momento de invocar a handler y scp contiene información de contexto definida en <signal.h>. code y scp son opcionales. La llamada a la rutina handler es asíncrona, es decir, que pude darse en cualquier instante de la ejecución del programa. #include <stdio.h> #include <signal.h> #include <stdlib.h> void sigint_handler (); int main () { if (signal (SIGINT, sigint_handler) = = SIG_ERR) 105 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos { } perror (“señal”); exit (-1); while (1) { printf (“En espera de Ctrl-C \n”); sleep (99); } } void sigint_handler (int sig) { static cont = 0; printf (“señal número %d recibida \n”, sig); if (cont < 20) printf (“Contador = %d\n”, cont++); else exit (0); if (signal (SIGINT, sigint_handler) == SIG_ERR) { perror (“Señal”); exit (-1); } } Saltos globales. Setjmp y longjmp La rutina de tratamiento de una señal puede hacer que el proceso vuelva a alguno de los estados por los que ha pasado con anterioridad. Esto no sólo es aplicable a las rutinas de tratamiento de señales sino que se puede extender a cualquier función. Para realizar esto nos valemos de las funciones estándar de librería setjmp y longjmp. PROTOTIPO DE LA FUNCIÓN #include <setjmp.h> int setjmp (jmp_buf env); void longjmp (jmp_buf env, int val); Setjmp guarda el entorno de pila en env para su uso posterior de longjmp. Setjmp devuelve el valor 0 en su primera llamada. Longjmp restaura el entorno guardado en env por una llamada previa a setjmp. Después de haberse ejecutado la llamada a longjmp, el flujo de la ejecución del programa vuelve al punto donde se hizo la llamada a setjmp, pero en este caso setjmp devuelve el valor val que hemos pasado mediante longjmp. Esta es la forma de saber si setjmp está saliendo 106 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos de una llamada para guardar el entorno 0 de una llamada de longjmp. Longjmp no puede hacer que setjmp devuelva 0, ya que en el caso de que val valga 0, setjmp va a devolver 1. jmp_buf entorno; int valor; .... .... .... valor = setjmp (entorno); (valor = 0) (valor =10) longjmp (entorno, 10); /*** Programa de ejemplo del uso de las funciones setjmo y longjmp forma de uso: $ salta & kill –10 pid Cada vez que le enviemos la señal SIGUSR1, el proceso va a reanudar su ejecución en un punto de fallback. *****/ #include <stdio.h> #include <signal.h> #include <setjmp.h> #include <stdlib.h> jmp_buf env; void sigusr1_handler ( ); int main ( ) { int i; signal (SIGUSR1, sigusr1_handler); for (i=0 ; i < 100; i++) { if (setjmp (env) = =0) printf (“Punto de regreso en el estado %d\n”,i); else /* esta parte se ejecuta cuando se efectúa una llamada a longjmp */ printf (“Regreso al punto de fallback del estado %d\n”,i); 107 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos sleep (10); } exit (0); } void sigusr1_handler (int sig) { signal (SIGUSR1, sigusr1_handler); longjmp (env, 1); } REFERENCIA 1. UNIX Programación Avanzada. F. M. Marquez. Alfaomega 2. Lección 4. Señales. http://sopa.dis.ulpgc.es/ii-dso/leclinux/interrupciones/senales/ lec4_senales.pdf 108 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Capítulo 10 Sistema Distribuido Las señales son interrupciones de software que pueden ser enviadas a un proceso para informarle de algún evento asíncrono o situación especial. El término señal se emplea también para referirse al evento. 1. Introducción a los sistemas distribuidos Los sistemas de cómputo han sufrido y están sufriendo una revolución, todo esto debido a dos avances tecnológicos, el primero de ellos fue el desarrollo de poderosos microprocesadores, y el segundo fue la creación de redes de área local (LAN). El resultado de estas tecnologías hace posible reunir sistemas de cómputo compuestos por un gran número de CPU, conectados mediante una red de alta velocidad, que recibe el nombre genérico de sistemas distribuidos. En base a esto podemos dar nuestra primera definición de sistema distribuido: Un sistema distribuido es una colección de computadoras independientes, que trabajan como una única computadora. Cabe observar que construir sistemas distribuidos, no significa necesariamente una buena idea se necesita analizar las ventajas o desventajas que el sistema presentara en comparación con los sistemas centralizados (sistemas con sólo un procesador). Ventajas 1. Económicas. Permite compartir dispositivos. 2. Velocidad. Un sistema distribuido puede tener mayor poder de cómputo, difundiendo la carga de trabajo entre las máquinas en forma eficiente. 3. Distribución inherente. Algunas aplicaciones utilizan máquinas que están separadas a cierta distancia, permitiendo que varios usuarios tengan acceso a una base de datos común. 4. Confiabilidad. Si una máquina se descompone, el sistema puede sobrevivir como un todo. 5. Crecimiento por incrementos. Se puede añadir poder de cómputo en pequeños incrementos. Desventajas 1. Redes. Las redes se pueden saturar o pueden perder mensajes. 2. Seguridad. Personas no autorizadas pueden ganar acceso a recursos no autorizados. 109 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos 1.1 Conceptos de Hardware Aunque todos los sistemas distribuidos constan de varios CPU, existen diversas formas de organizar la interconexión y la comunicación. La taxonomía más citada para llevar a cabo lo anterior es la taxonomía de Flynn (1972). Flynn, clasifico las arquitecturas de acuerdo a los flujos de datos y a los flujos de instrucciones. El concepto de flujos de datos se refiere al número de operandos que se pueden procesar en un tiempo y el de flujos de instrucciones se refiere a cuantos programas se pueden ejecutar en un tiempo. De acuerdo a su clasificación existen cuatro tipos de computadoras: SISD, SIMD, MISD y MIMD (Tabla 10-1). En la figura 10-1 se muestran las tres categorías aplicadas en la industria. Figura 10-1. Categoría de Flynn utilizadas en la industria. Tabla 10-1. Taxonomía de Flynn. Categoría SISD (Single instruction stream, single data stream) SIMD (Single instruction stream, multiple data stream MISD (Multiple instruction stream, single data stream) MIMD (Multiple instruction stream, multiple data stream) Un solo flujo de instrucciones y un solo flujo de datos. Un solo flujo de instrucciones y varios flujos de datos. Varios flujos de instrucciones y un solo flujo de datos. Varios flujos de instrucciones y varios flujos de datos. La primera categoría (SISD) se refiere a las maquinas de arquitectura Von Newmann como las computadoras personales (PC’s) que manejamos comúnmente, como sabemos este tipo de maquinas tienen limites físicos que limitan su capacidad de procesamiento; 110 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos esto motivó la investigación de maquinas de arquitectura en paralelo para obtener un mayor rendimiento. La máquina SIMD se conoce como procesador de arreglos, dispositivo que realiza en esencia la misma operación simultáneamente en cada elemento de un arreglo. En esta categoría se incluyen los procesadores vectoriales y los procesadores de canalización. Los procesadores de arreglos ejecutan la misma instrucción en forma simultánea sobre todos los elementos de un arreglo. Los procesadores de arreglos no son útiles en los ambientes de cómputo de propósito general, caracterizados por la necesidad de realizar tareas independientes en paralelo. Uno de los primeros procesadores de arreglos fue IILIAC IV, desarrollado en la University of Illinois. El procesador en paralelo a gran escala MPP (Massive Parallel Processor) es una máquina SIMD con 16 384 procesadores. Puede realizar más de 6 mil millones de operación de 8 bits segundo, fue diseñado para la NASA por Goodyear Aerospace para realizar tareas de procesamiento de imágenes. Esta máquina ha sido superada por la máquina de conexión CM (Conecction Machine) de 65 536 procesadores, un procesador de arreglos construido por Thinking Machines Corp. La canalización es una técnica para mejorar el rendimiento que permite que varias instrucciones en lenguaje de máquina estén en diferentes etapas de ejecución al mismo tiempo. Los sistemas con canalización comienzan a trabajar con la siguiente instrucción o con varias de las siguientes instrucciones, mientras se está ejecutando la instrucción actual. El procesamiento vectorial requiere específicamente instrucciones con vectores en lenguaje de máquina. Una instrucción vectorial indica la operación que se va a realizar y especifica la lista de operandos (denominada vector) sobre la que se operará. Cuando se ejecuta una operación vectorial, se alimentan los elementos del vector a la canalización apropiada uno a la vez, retrasados por el tiempo que toma completar una etapa en la canalización. Los compiladores “vectorizan” los programas para que se ejecuten eficientemente en los procesadores de vectores. El procesador de vectores Cray-1 tiene 13 canalizaciones que pueden trabajar en paralelo; están dedicados a operaciones tales como suma de punto flotante, multiplicación de punto flotante, aproximación recíproca, suma de punto fijo, etc. La categoría MIMD se dividen en dos grupos: aquellas que tienen memoria compartida, generalmente llamadas multiprocesadores, y las que cuentan con memoria privada, llamadas multicomputadoras. Cada uno de estos grupos se divide en dos categorías: bus (ejemplos estaciones de trabajo en una LAN) y conmutador (ejemplos Ultracomputadora RP3, transputer). En la figura 10-2 se describe estas dos categorías. En la categoría de sistemas de bus existe una red, cable u otro medio que conecta todas las máquinas, y los sistemas con conmutador cuentan con cables individuales para unir las máquinas y diferentes patrones de cableado. Los mensajes se mueven a través de los cables y se toma una decisión explícita de conmutación en cada etapa, para dirigir el mensaje a lo largo de uno de los cables de salida. 111 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Taxonomía de S. D. y Paralelos Sistemas Operativos MIMD Computadoras paralelas y distribuidas Débilmente acopladas Fuertemente acopladas Multiprocesadores (memoria compartida) Bus Secuencia, repetición Multiprocesadores (memoria privada) Con conmutador Ultracomputadora, RP3 Con conmutador Bus Estación de trabajo en una LAN Hipercubo, Transputer Figura 10-2. Una taxonomía de los sistemas de cómputo paralelos y distribuidos. Las computadoras paralelas y distribuidas forman un sistema es fuertemente acoplado, cuando el retraso que se experimenta al enviar un mensaje de una computadora a otra es corto y la tasa de transmisión es alta; y un sistema débilmente acoplado cuando el retraso de los mensajes entre las máquinas es grande y la tasa de transmisión de los datos es baja. Uno de los atractivos de los sistemas de multiprocesamiento consiste en que si falla un procesador, casi siempre pueden continuar trabajando los procesadores restantes. Los sistemas multiprocesadores tienden a utilizarse como sistemas paralelos, y los sistemas multicomputadoras como sistemas distribuidos, aunque esto no siempre es cierto. 1.2 Esquemas de interconexión de procesadores. Uno de los problemas fundamentales en el diseño de los sistemas de multiprocesamiento es determinar la forma de conectar los múltiples procesadores y los procesadores de entrada/salida con las unidades de almacenamiento. Algunos de estos esquemas son: ducto compartido o basado en bus, matriz de conmutadores cruzados o conmutador de punto de cruce e hipercubo. Ducto compartido Esta organización de multiprocesadores, la cual se muestra en la figura 10-3, utiliza una sola trayectoria de comunicación entre todos los procesadores, las unidades de almacenamiento y los procesadores de E/S. El esquema de red de área local Ethernet utiliza un ducto compartido. El procesador o procesadores de E/S que desee transferir datos debe comprobar primero la disponibilidad del ducto y de la unidad de destino, e iniciar después la transferencia de datos. Las unidades receptoras deben ser capaces de reconocer los mensajes del ducto 112 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos destinados a ellas y deben interpretar y reconocer las señales de control que se reciban de la unidad transmisora. Ventajas Desventajas Facilita la adición de nuevas unidades El ducto sólo puede manejar una transmisión a la vez. El sistema entero falla si el ducto falla. La velocidad está limitada por la velocidad de transmisión del ducto. Puede bajar el rendimiento si el ducto es muy utilizado. Interconexión de procesadores Procesador Procesador Memoria Memoria Procesador E/S Procesador E/S Ducto Memoria Procesador E/S Memoria Procesador E/S Procesador Procesador Canal compartido Figura 10-3. Organización de multiprocesadores con ducto compartido. Ventajas Desventajas Facilita la adición de El canal sólo puede manejar una transmisión a la vez. Matriz de conmutadores cruzados. nuevas unidades El sistema entero falla si el canal falla. La velocidad estáde limitada por la hasta velocidad de Si se aumenta el número de ductos en un sistema ducto compartido igualar el transmisión número de unidades de almacenamiento, se del creacanal. una organización de multiprocesadores denominada matriz de conmutadores cruzados, en la cual sihay una trayectoria distinta Puede bajar el rendimiento el canal es muy utilizado. ! ! ! ! ! hacia cada unidad de almacenamiento. Sistemas Distribuidos M.C. Gabriel Gerónimo Castillo Ventajas Desventajas Puede manejar transmisiones simultáneas a todas las unidades de almacenamiento. Hardware muy complejo, debe ser capaz de resolver conflictos de acceso a la misma unidad de almacenamiento. El costo del conmutador crece en proporción al producto del número de unidades funcionales por el número de unidades de memoria. Figura 10-4. Organización de multiprocesadores con matriz de conmutadores cruzados. Hipercubo La red de interconexión hipercubo hace posible conectar un gran número de procesadores. Un hipercubo de dos dimensiones no es más que un cuadrado; un 113 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos hipercubo de tres dimensiones se forma conectando los elementos correspondientes de dos hipercubos bidimensionales. Un hipercubo de cuatro dimensiones se forma conectando los elementos correspondientes de dos hipercubos tridimensionales, y así sucesivamente. La máquina de conexión (CM) utiliza un esquema de conexión de hipercubo de 16 dimensiones para conectar entre sí 65 536 procesadores. Figura 10-5. Redes de interconexión de hipercubo. 1.3 Sistemas débilmente acoplados y Sistemas fuertemente acoplados. El multiprocesamiento débilmente acoplado implica conectar dos o más sistemas de cómputo independientes mediante un enlace de comunicación (Figura 10-6). Cada sistema tiene su propio sistema operativo y su almacenamiento. Los sistemas pueden funcionar en forma independiente y se pueden comunicar entre sí. Los sistemas individuales pueden tener acceso a los archivos de otros por medio del enlace de comunicación, y en algunos casos los sistemas pueden enviar tareas a los procesadores que no estén muy cargados para equilibrar la carga de trabajo. La comunicación entre los procesadores se realiza por medio de transferencia de mensajes o de llamadas a procedimientos remotos. Sistema débilmente acoplado Almacenamiento Almacenamiento Enlace de comunicación Procesador Procesador E/S E/S Figura 10-6. Multiprocesamiento débilmente acoplado. El multiprocesamiento fuertemente acoplado utiliza un almacenamiento único compartido por los diferentes procesadores y un solo sistema operativo que controla todos los procesadores y el hardware del sistema (Figura 10-7). La comunicación se realiza mediante memoria compartida. El BBN Butterfly y el IBM 3090 son ejemplos de sistemas fuertemente acoplados. 114 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos Procesador E/S Almacenamiento Procesador E/S Figura10-7. Multiprocesamiento fuertemente acoplado. 1.4 Organizaciones de los sistemas operativos para multiprocesadores. Una de las diferencias principales entre los sistemas operativos para multiprocesadores y los de un solo procesador es la organización y la estructura del sistema. Las organizaciones básicas de los sistemas operativos para los multiprocesadores son maestro/esclavo, ejecutivos individuales y simétrico. 1.4.1 Maestro/esclavo. En la organización de múltiples procesadores maestro/esclavo, un procesador se designa como maestro o amo y los otros son los esclavos. Los esclavos pueden ejecutar eficientemente los trabajos limitados por un CPU, pero los trabajos limitados por E/S que se ejecuten en los esclavos ocasionarán llamados frecuentes a servicios que sólo puede realizar el maestro. Desde un punto de vista de confiabilidad, si falla un esclavo se pierde parte de la capacidad de cómputo, pero puede seguir el sistema funcionando, pero si falla el maestro, el sistema no podrá realizar operaciones de E/S. La desventaja principal de esta organización es la asimetría del hardware. Sólo el maestro puede ejecutar el sistema operativo. Un procesador esclavo sólo puede ejecutar programas de usuario. 1.4.2 Ejecutivos individuales. En la organización de ejecutivos individuales cada procesador tiene su propio sistema operativo y responde a las interrupciones de los usuarios que estén ejecutando sus procesos en ese procesador. Un proceso asignado para ejecutarse en un procesador específico se ejecuta hasta terminar en ese procesador. La organización de ejecutivos individuales es más confiable que la organización maestro/ esclavo; es poco probable que la falla de un solo procesador cause una falla catastrófica al sistema. Cada procesador controla sus propios recursos dedicados, como archivos y dispositivos de E/S. Existe una contención mínima por los datos del sistema operativo porque los datos están distribuidos entre los sistemas operativos individuales para su propio uso. Los procesadores no cooperan en la ejecución de un proceso individual. 115 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos La desventaja que presenta esta organización es que algunos procesadores pueden permanecer sin trabajo mientras otro ejecuta un proceso muy largo. 1.5 Organización simétrica. El multiprocesamiento simétrico es la organización más compleja al llevarla a cabo, pero su tarea es recompensaba al ser la más confiable; una falla en un procesador hace que el sistema operativo expulse el procesador de su conjunto procesadores disponibles y tome nota del problema. El sistema puede seguir funcionando a niveles menores (es decir, una degradación paulatina) mientras se realizan las reparaciones. Todos los procesadores son idénticos, así el sistema operativo puede utilizar cualquiera de ellos para controlar cualquier dispositivo de E/S o para hacer referencia a cualquier unidad de almacenamiento. Como muchos procesadores pueden estar ejecutando el sistema operativo al mismo tiempo, se requiere código reentrante y exclusión mutua. Debido a la simetría, es posible equilibrar la carga de trabajo con mayor precisión que con otras organizaciones. El hardware y el software juegan un papel importante para resolver los conflictos que se presenten, como por ejemplo el hardware se encarga de resolver el conflicto entre los procesadores que intentan obtener acceso a la misma zona de almacenamiento al mismo tiempo, y el software soluciona los conflictos relacionados con el acceso a los datos de todo el sistema en general. Un proceso que se ejecute en un multiprocesador simétrico puede ser ejecutado en diferentes ocasiones por cualquiera de los procesadores equivalentes: el sistema operativo flota de un procesador a otro. El procesador encargado en un momento dado de los datos y funciones del sistema se denomina procesador ejecutivo, y sólo un procesador puede ser ejecutivo a la vez, con lo cual se evitan conflictos sobre la información global del sistema. Los problemas de contención pueden ser graves, sobre todo porque varios procesadores pueden estar al mismo tiempo en el estado supervisor. El diseño cuidadoso de las estructuras de datos del sistema resulta esencial para evitar una excesiva exclusión por bloqueo. Una forma reducir la contención es dividir las estructuras de datos del sistema en entidades individuales e independientes que se puedan bloquear en forma individual. 1.6 Aspectos en el diseño de un sistema operativo distribuido. En las secciones siguientes se muestran los aspectos claves que se deben tener presente cuando se contruyen sistemas operativos distribuidos. 1.6.1 Transparencia. El aspecto de transparencia se refiere al hecho de que los usuarios del sistema distribuido piensen que toda la colección de máquinas que forman al sistema, es solo un sistema de tiempo compartido de un procesador. La transparencia se aplica a varios aspectos de un sistema distribuido como son: localización, migración, réplica, concurrencia y paralelismo. La transparencia de localización se refiere al hecho de que los usuarios no deben indicar la localización de los recursos de hardware y software, como los CPU, impresoras, archivos y bases de datos. 116 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos La transparencia de migración significa que los recursos se pueden mover a voluntad del usuario sin cambiar sus nombres, por ejemplo los directorios servidores se podrán montar en los lugares que el usuario desee dentro de la jerarquía de sus directorios. La transparencia por réplica se refiere al hecho de que el sistema operativo es libre de fabricar copias adicionales de los archivos y otros recursos sin que lo noten los usuarios. En la transparencia con respecto a la concurrencia los usuarios pueden compartir de forma automática los recursos del sistema. Por último tenemos la más difícil, la transparencia con respecto al paralelismo esta transparencia permite que las actividades puedan ocurrir en paralelo sin que el usuario se entere. 1.6.2 Flexibilidad. Antes de hablar de flexibilidad, primero veamos las dos formas de estructurar los sistemas distribuidos, la primera de ellas es por medio de un núcleo monolítico, el cual dice que cada máquina debe ejecutar un núcleo tradicional que proporcione la mayoría de los servicios, y que aumente su poder con las capacidades de red y la integración de servicios remotos; el segundo modelo es el micronúcleo, el cual sostiene que el núcleo debe proporcionar lo menos posible de servicios y que el grueso de los servicios del sistema operativo se obtenga a partir de los servicios a nivel de usuario. En el núcleo monolítico las llamadas al sistema se realizan mediante señalamientos al núcleo, en donde se realiza el trabajo, para que después el núcleo regrese el resultado deseado al proceso de usuario, esta es la única ventaja potencial de este núcleo su rendimiento, ya que los señalamientos al núcleo y la realización de todo el trabajo puede ser más rápido que el envío de mensajes a los servidores remotos, aunque estudios han demostrado que en la practica esta ventaja no existe. En los sistemas micronúcleo para buscar un nombre, leer un archivo u obtener algún otro servicio, el usuario envía un mensaje al servidor apropiado, el cual realiza el trabajo y regresa el resultado. La ventaja de este método es que es altamente modular, existe una interfaz definida con cada servicio y cada servicio es igual de accesible para todos los clientes. Además es fácil implantar, instalar y depurar nuevos servicios, puesto que la adición o modificación de un servicio no requiere el alto total del sistema y el arranque de un nuevo núcleo, como en el caso del núcleo monolítico, otro punto a su favor es que los usuarios que no estén satisfechos con alguno de los serviciso oficiales son libres de escribir su propio servicio. Es precisamente esta capacidad de añadir, eliminar y modificar servicios lo que da al micronúcleo su flexibilidad. 1.6.3 Confiabilidad. La confiabilidad involucra disponibilidad, consistencia, seguridad y tolerancia a fallas; la disponibilidad se refiere a la fracción de tiempo en la cual se puede utilizar el sistema, una manera de mejorar la disponibilidda es la redundancia, se pueden duplicar piezas de hardware y de software, de modo que si una falla, las otras pueden llenar su hueco, pero aquí entra el segundo aspecto, la consistencia, si los archivos se almacenan de manera redundante en varios servidores, todas las copias deben ser consistentes. Aspecto de seguridad se debe considerar para que los archivos y los recursos sean protegidos contra acceso o uso no autorizado, otro aspecto relacionado con la confiabilidad es la tolerancia 117 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos a fallas, por ejemplo si el servidor falla y vuelve a arrancar de manera súbita, este debe recuperar todas las actividades que estaban en proceso. 1.6.4 Desempeño. Cuando se ejecuta una aplicación en un sistema distribuido, esta no debe parecer peor que su ejecución en un procesador. En el desempeño se pueden analizar diferentes métricas entre las cuales se encuentran: el tiempo de respuesta, el rendimiento (número de trabajos por hora) y el uso del sistema. El problema del desempeño se complica por el hecho de que la comunicación, factor esencial en un sistema distribuido (y ausente en un sistema con un procesador) es algo lenta por lo general. El envío y la obtención de un mensaje en una LAN tarda unos milisegundos, debido a los protocolos de comunicación entre el emisor y el receptor, más el tiempo que tardan los bits en el cable de red. Así, para optimizar el desempeño, con frecuencia se minimizar el número de mensajes. La dificultad con esta estrategia es que la mejor forma de mejorar el desempeño es tener muchas actividades en paralelo, pero la desventaja es que esto requiere el envío de muchos mensajes. Una posible solución a esta desventaja es verificar el tamaño de grano de todos los cálculos; los trabajos que implican gran número de pequeños cálculos, en particular aquellos que interactúan en gran medida con otros, pueden ser la causa de algunos problemas en los sistemas distribuidos con una comunicación lenta. Se dice que tales trabajos exhiben un paralelismo de grano fino. Por otro lado, los trabajos que implican grandes cálculos y bajas tasas de interacción, así como pocos datos, muestran un paralelismo de grano grueso, y estos son los que ayudan a resolver esta desventaja. La tolerancia a fallas también tiene su precio. Algunas veces, una buena confiabilidad se logra mejor con varios servidores que cooperen en forma cercana en una solicitud. Por ejemplo, si una solicitud llega a un servidor, éste podría enviar de manera inmediata una copia del mensaje a uno de sus colegas, de forma que si falla antes de terminar, el colega se puede encargar de la solicitud, y si concluye con esta, debe informar al colega que ha terminado el trabajo, lo que representa otro mensaje. Así, tenemos al menos dos mensajes adicionales, que en el caso normal, cuestan tiempo y capacidad de red, y no producen una ganancia notable. 118 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos DEFINICIONES Concurrencia. La concurrencia se refiere al hecho de compartir recursos en el mismo marco de tiempo. Esto por lo común significa que varios procesos comparten la misma CPU o la memoria o un dispositivo de E/S. Paralelismo. Consiste en la distribución de la carga de trabajo entre varios procesadores. De esta manera se consigue un aumento de velocidad notable en procesos que no son suficientemente rápidos. Multiprogramación. Es la gestión de varios procesos dentro de un sistema monoprocesador. Multiproceso. Es la gestión de varios procesos dentro de un sistema multiprocesador. Proceso distribuido. Es la gestión de varios procesos que se ejecutan en sistemas de computadoras múltiples y remotas. Proceso. Es una entidad activa. Es una instancia de un programa cuya ejecución aún no ha terminado. Un programa se convertir en un proceso si se tiene lo siguientes: a) el sistema operativo le asigna memoria a la imagen del programa, b) le asigna un ID (identificar de proceso) para distinguir entre varios procesos, el estado del proceso indica el estado de ejecución, c) el sistema operativo mantiene una lista de los ID de los procesos así como de los estados correspondientes, y utiliza esta información para asignar y administrar los recursos del sistema, también mantiene una lista de la memoria ocupada por los procesos y de la que está disponible (administrador de memoria), d) cuando el sistema operativo agrega la información apropiada en las estructuras de datos del núcleo (kernel) y asigna los recursos necesarios para ejecutar el código del programa , éste se convierte entonces en un proceso. Sistema Operativo de red. Es la configuración en la cual existe una red de máquinas de aplicación, generalmente puestos de trabajo (workstations) monousuario y una o más máquinas “servidoras”. Los servidores ofrecen servicios de red o aplicaciones, como almacenamiento de archivos y gestión de impresoras. Cada computadora posee su propio sistema operativo. El sistema operativo de red es simplemente un añadido al sistema operativo local que permite a las máquinas de aplicación interactuar con los servidores. El usuario siempre esta consciente de que existen múltiples computadoras independientes y que opera con ellas explícitamente. Sistema operativo distribuido. Es un sistema operativo común compartido por una red de computadoras. Aparece ante los usuarios como un sistema operativo centralizado ordinario pero que ofrece al usuario un acceso transparente a los recursos de un conjunto de máquinas. Un sistema operativo distribuido puede descansar sobre una arquitectura de comunicaciones para las funciones básicas de comunicación; es más frecuente que se extraiga un conjunto de las funciones de comunicación y se incorporen al sistema operativo para darle mayor eficacia. 119 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo Sistemas Operativos BIBLIOGRAFÍA [1] Sistemas Operativos Modernos; A. S. Tanenbaum; Pearson; ISBN-10: 6074420467. [2] Sistemas Operativos; William Stallings; Pearson;ISBN-10: 8420544620. [3] Distributed Systems: Concepts and Design; George F. Coulouris, Jean Dollimore; Addison-Wesley; 2 edition; ISBN-10: 0273760599. [4] Distributed Systems, Sape Mullender, Segunda Edición, Addison-Wesley. [5] Distributed Systems: Principles and Paradigms; Andrew S. Tanenbaum; Prentice Hall; 2 edition (October 12, 2006);ISBN-10: 0132392275; ISBN-13: 978-0132392273. [6] Sistemas Operativos; H.M. Deitel; Addison-Wesley; Segunda Edición. [7] UNIX. Distributed Programming; Chris Brown; Prentice-Hall; Primera Edición. [8] UNIX Programación Avanzada; F. M. Márquez; Alfaomega; Segunda Edición. [9] UNIX Programación Práctica; K. A. Robbins, S. Robbins; Prentice Hall; Primera Edición. 120 ________________________________________________________________________________________ M.C. Gabriel Gerónimo Castillo