Fundamentos básicos de
los Sistemas Operativos
Índice de contenido
Fundamentos básicos de los sistemas operativos
Capítulo 1. Consideraciones generales de los sistemas operativos...7
Objetivos...7
Servicios de un sistema operativo...7
Evolución histórica...7
Tipos de sistemas operativos...8
Llamadas al sistema...9
Estructura del núcleo de un sistema operativo...9
Capítulo 2. Descripción y control de procesos...11
Espacio de direcciones de memoria lógica o virtual de un proceso...11
Tipos de procesos...11
Estados de un proceso...11
Control de los procesos...12
Estructuras de control del sistema operativo...12
Creación de procesos...12
Terminación de procesos...13
Cambio de proceso...13
Ejecución del sistema operativo...13
Procesos multihilo...13
Control de hilos...14
Tipos de hilos...14
Principales configuraciones en función del número y tipo de hilos soportados por un sistema operativo...15
Capítulo 3. Planificación de procesos...17
Niveles de planificación...17
Planificador a corto plazo...17
Planificador a medio plazo...18
Planificador a largo plazo...18
Criterios generales considerados en la planificación de procesos...18
Criterios considerados en la planificación del procesador...18
Expropiabilidad del procesador...19
Algoritmos de planificación...19
Primero en llegar – primero en ser servido – FCFS...19
Primero el proceso más corto – SJF...19
Menor tiempo restante...20
Planificación de turno rotatorio...20
Planificación basada en prioridades...20
Planificación basada en múltiples colas de prioridad...21
Múltiples colas de prioridad y realimentación...21
Planificación por tiempo límite...22
Elección del algoritmo de planificación...22
Planificación de hilos...22
Capítulo 4. Sincronización y comunicación de procesos...23
Exclusión mutua...23
Interacción entre procesos concurrentes...23
Condiciones de carrera...23
Secciones críticas...23
Descripción de la exclusión mutua...23
Soluciones software a la exclusión mutua...24
Soluciones a la exclusión mutua con apoyo del hardware...25
Semáforos...25
Implementación...25
Utilidad de los semáforos...27
Sincronización de procesos...27
Problemas potenciales asociados a un mal uso de los semáforos...27
Problemas clásicos de sincronización de procesos concurrentes...28
El problema de los productores y los consumidores...28
El problema de los lectores y los escritores...28
Monitores...29
Definición...29
Estructura y declaración de un monitor...29
Uso de los monitores...30
Paso de mensajes...30
Definición...30
Especificación de la fuente y el destino del mensaje...31
Esquemas de sincronización...31
Formato y almacenamiento de los mensajes...32
Uso del paso de mensajes...32
Capítulo 5. Interbloqueo...33
Introducción...33
Definición y condiciones del interbloqueo...33
Grafos de asignación de recursos...33
Prevención de interbloqueos...34
Eliminación de la condición de exclusión mutua...34
Eliminación de la condición de retención y espera...34
Eliminación de la no existencia de expropiación...34
Eliminación de la espera circular...34
Evitación de interbloqeos...34
Estado de la asignación de recursos: estados seguros y estados inseguros...35
Denegación de asignación de recursos: el algoritmo del banquero...35
Denegación de la iniciación de un proceso...35
Detección y recuperación de interbloqueos...36
Algoritmos de detección de interbloqueos...36
Frecuencia de invocación del algoritmo de detección de interbloqueos...36
Técnicas de recuperación del interbloqueo...36
Ventajas e inconvenientes...36
Otras estrategias de tratamiento de interbloqueos...37
Capítulo 6. Administración de memoria...38
Espacio de núcleo y espacio de usuario...38
Área de intercambio en memoria secundaria...38
Asignación de memoria en sistemas monoprogramados...39
Particionamiento fijo...40
Particiones de igual tamaño...40
Particiones de distinto tamaño...40
Ventajas e inconvenientes...41
Particionamiento dinámico...41
Asignación de espacio de memoria principal...41
Traducción de direcciones y protección...42
Ventajas e inconvenientes...42
Paginación simple...42
Traducción de direcciones...43
Tablas de páginas paginadas...44
Tablas de páginas invertidas...45
Protección...46 Compartición de páginas...46 Ventajas e inconvenientes...46 Segmentación simple...46 Traducción de direcciones...47 Protección...47 Compartición de segmentos...47 Ventajas e inconvenientes...47
Segmentación con paginación simple...48
Capítulo 7. Memoria virtual...49
Paginación por demanda...49
Reinicio de instrucciones...50
Localización de las páginas en memoria secundaria...50
Bloqueo de marcos de página...50
Tratamiento de un fallo de página...50
Conjunto de trabajo de un proceso...51
Reemplazamiento de páginas...51
Algoritmo de reemplazamiento óptimo...52
Algortimo de reemplazamiento LRU...52
Algoritmo de reemplazamiento mediente envejecimiento...52
Algoritmo de reemplazamiento FIFO...52
Algoritmo de reemplazamiento de la segunda oportunidad (algoritmo del reloj) ...53
Algoritmo de reemplazamiento del reloj considerando el conjunto de trabajo.53 Conclusiones sobre los algoritmos de reemplazamiento...54
Asignación de memoria principal...54
Control de carga...55
Copia en memoria secundaria de páginas modificadas...55
Consideraciones adicionales sobre la paginación por demanda...55
Tamaño de página...55
Paginación por adelantado...56
Reserva de marcos libres...56
Capítulo 8. Gestión de la Entrada/Salida...57
Peticiones de E/S...57
Capas del núcleo de un sistema operativo encargadas de la E/S...57
Subsistema de E/S...57
Drivers de dispositivos de E/S...58
Manejador de las interrupciones...58
Buffering...58
Estrategias...59
Caché de buffers de bloques de disco...59
Spooling...59
Detalles de la gestión de E/S de algunos dispositivos...59
Relojes...59
Discos duros...60
Dispositivos de E/S adaptados al usuario...62
Capítulo 9. Gestión de archivos...64
Archivos...64
Tipos de archivos...64
Atributos de un archivo...64
Estructura interna de un archivo...64
Métodos de acceso a un archivo...65
Operaciones sobre archivos...65
Directorios...66
Estructura de los directorios...66
Operaciones sobre directorios...67
Sistemas de archivos...67
Estructura de un sistema de archivos...67
Montaje de un sistema de archivos...68
Asignación de espacio...68
Gestión del espacio libre...69
Implementación de directorios...70 Consistencia...70 Recuperación de archivos...71 Copias de seguridad...71 Instantáneas...71 Eficiencia...72
Capítulo 1. Consideraciones generales de los sistemas
operativos
Un sistema operativo es una capa de software cuya función es administrar todos los dispositivos hardware del computador y suministrar para usuarios y programas una interfaz apropiada con el hardware.
Objetivos
Gestionar eficientemente los dispositivos hardware
• Tiempo de procesador a los programas de usuario: planificador. • Ocupación/gestión de memoria principal
• Gestión de memoria secundaria: área de intercambio (zona donde se almacenan los programas que hay que ejecutar pero que aún no disponen de espacio en memoria principal) y sistema de archivos y resto de dispositivos E/S.
Ofrecer una interfaz cómoda a los usuarios. El SO proporciona una máquina virtual que envuelve al hardware facilitando su programación. Capas:
• Hardware
• Núcleo (kernel), proporciona un conjunto de instrucciones como llamadas al sistema. • Utilidades del SO (intérpretes de comandos, sistemas de ventanas, compiladores, etc). • Programas de aplicación.
Servicios de un sistema operativo
• Ejecución de programas.
• Acceso a los dispositivos de E/S. • Manipulación del sistema de archivos • Comunicación y sincronización. • Detección y respuesta a errores • Protección y seguridad.
• Contabilidad.
Evolución histórica
• Procesamiento serie
• Procesamiento por lotes (batch)
• Multiprogramación (multitarea). Aprovechamiento de operaciones de E/S de un proceso para ejecutar otro.
• Multiprogramación por tiempo compartido. Debe gestionar los recursos asignándolos de manera equitativa.
Tipos de sistemas operativos
En función del número de usuarios simultáneos • Monousuario y multiusuario.
Según el número de programas cargados en memoria principal: • Monoprogramado y multiprogramado.
• Se denomina grado de multiprogramación el número de programas cargados en memoria principal. Para implementar multiprogramación se precisan algoritmos de planificación, mecanismos de sincronización y de asignación y protección de memoria principal y secundaria. El hardware también debe soportar protección de la memoria, E/S por interrupciones y DMA. Un SO puede ser multiacceso (acceso desde dos o más terminales) pero no necesariamente multitarea (sistema de venta de billetes, por ejemplo). Un SO multiprocesamiento coordina la actividad de varios procesadores. Son multiprogramados por definición.
En función de los requisitos temporales de los programas a ejecutar:
• Sistemas por lotes o batch, planificación del procesador simple; suele utilizarse E/S por
programa; gestión sencilla de la memoria principal: bloque del SO y bloque de programas • Sistemas de tiempo compartido o sistemas interactivos. Sistemas multiusuario con
multiprogramación. El planificador debe asegurar el reparto equitativo del tiempo de procesador así como poseer mecanismos de protección de MP, MS y acceso concurrente a archivos.
• Sistemas de tiempo real. Pueden ser en tiempo real estricto o suave. Las primeras requieren
un tiempo límite preestablecido de respuesta. Utilizan un sistema de planificación de tipo expropiativo, en el que el proceso con más prioridad toma el control. la gestión de la memoria es menos exigente. (VxWorks, QNX)
• Sistemas híbridos. Soportan tanto trabajos por lotes como aplicaciones interactivas o
aplicaciones suaves en tiempo real. Prioridad de ejecución mínima para trabajos por lotes, media para interactivos y máxima para aplicaciones en tiempo real. (UNIX, LINUX)
En función de la finalidad del computador. • Sistemas para macrocomputadores.
• Sistemas operativos para servidores de red.
• Sistemas operativos para computadoras personales. • Sistemas para computadoras de mano.
• Sistemas integrados. Otros tipos importantes de SO
• Sistemas paralelos o multiprocesador. Comparten bus, reloj, memoria, E/S. Se les suele
denominar como sistemas fuertemente acoplados. Los SO paralelos más comunes utilizan multiprocesamiento simétrico. Cada procesador ejecuta su propia copia del SO, que se comunican entre ellas cuando es necesario.
• Sistemas distribuidos. Se ejecutan en sistemas informáticos distribuidos implementados
mediante redes. Se les suele denominar sistemas débilmente acoplados. El usuario no sabe si el proceso se ejecuta en su propio procesador o en el de otra máquina de la red.
• Sistemas operativos de red. Posibilita el acceso a contenido y recursos entre computadoras
mediante el paso de mensajes.
Llamadas al sistema
La mayoría de procesadores disponen de dos modos de operación: modo núcleo y modo usuario. Los programas de usuario deben hacer uso de llamadas al sistema para poder utilizar los recursos hardware a través del SO.
• Mediante el uso de librerías de llamadas al sistema, de forma similar a como se invoca cualquier otra función.
• De forma directa, mediante programas en lenguaje ensamblador.
La llamada al sistema provoca una trampa (tramp), haciendo que el procesador conmute a modo núcleo. Para poder acceder a los parámetros de la llamada al sistema se utiliza:
• Cargar los parámetros en registros. Método sencillo, puede que no se tengan registros suficientes.
• Almacenamiento en un bloque o tabla de memoria. Sólo es necesario conocer la dirección de comienzo del bloque.
• Almacenamiento en una pila.
Las llamadas al sistema, atendiendo a su funcionalidad, se pueden agrupar en: • Control de programas en ejecución (procesos).
• Administración de archivos y directorios. • Comunicaciones.
• Gestión de dispositivos.
• Gestión de información del sistema.
Estructura del núcleo de un sistema operativo
La forma más eficiente de diseñar un SO consiste en descomponerlo en varios componentes o subsistemas. La mayoría disponen de:
• Subsistema de control de procesos.
• Subsistema de control de la memoria principal. • Subsistema de gestión de archivos.
• Subsistema de E/S.
El núcleo de un sistema operativo se puede considerar como un conjunto lógico de módulos software. El nucleo puede tener
• Estructura monolítica o simple. Todos los subsistemas y las estructuras de datos del núcleo se ubican en un mismo módulo lógico. El software del núcleo está escrito como un conjunto de procedimientos. Un procedimiento es visible por todos los demás. (MSDOS, UNIX tradicional).
• Estructura en módulos. Cada módulo con una interfaz bien definida. De fácil mantenimiento con una ligera merma en el rendimiento.
capas: capa 0, controladores hardware; capa 1, control, sincronización y comunicación de procesos; capa 2, gestión de memoria; capa 3, comunicaciones entre procesos y el operador de consola; capa 4, gestión de E/S; capa 5, programas de usuario.
• Estructura extensible. Puede considerarse como un caso particular de la estructura modular. Posee un módulo principal, el núcleo extensible o micronúcleo, y varios módulos como
extensiones del núcleo. El micronúcleo realiza los servicios absolutamente esenciales, los
que dependen estrechamente de la arquitectura de la máquina como la gestión de interrupciones, comunicación entre procesos, gestión de memoria a bajo nivel y gestión de E/S. El resto de las funciones se realizan en modulos de extensión del núcleo. Los procesos de usuarios se comunican con las extensiones del núcleo utilizando un mecanismo cliente (applicación de usuario) – servidor (extensión del núcleo).
La comunicación cliente/servidor se realiza mediante el paso de mensajes. Las ventajas de un núcleo extensible son:
Manejabilidad. Cada extensión del núcleo se encarga de un servicio del sistema. ◦ Extensibilidad. Añadir nuevas características añadiendo un nuevo módulo.
◦ Fiabilidad. Las extensiones se ejecutan como procesos de usuario. Ante el fallo de un servicio, el resto puede seguir disponible.
◦ Soporte simultáneo de múltiples sistemas operativos. Sobre un mismo micronúcleo pueden implementarse diferentes extensiones.
◦ Portabilidad. Solo es necesario reescribir el micronúcleo al ser la única parte dependiente del hardware.
Su principal desventaja es un menor rendimiento debido al paso de mensajes (creación, envío, procesamiento).
Capítulo 2. Descripción y control de procesos
Un proceso es un programa en ejecución. También puede definirse como la entidad que se puede asignar y ejecutar en un procesador: es la unidad básica de trabajo de un sistema informático.
Si un programa se ejecuta más de una vez, cada copia tendrá su propio proceso asignado.
Cuando un proceso puede interactuar con los usuarios se dice que está en primer plano (foreground), en caso contrario un proceso se encuentra en segundo plano (background).
Espacio de direcciones de memoria lógica o virtual de un proceso.
El espacio de direcciones de memoria lógica o virtual de un proceso (imagen del proceso o espacio de direcciones de usuario) se divide en varias regiones: la región de código (o región de texto), la región de datos y la región de pila.
La región de datos puede dividirse en región de datos inicializados, de tamaño fijo, y región de datos no inicializados, de tamaño variable.
La región de pila se crea automáticamente y su tamaño se ajusta dinámicamente en tiempo de ejecución. El registro puntero de pila almacena la próxima entrada libre o la última utilizada, dependiendo de la arquitectura del procesador.
Puesto que la región de pila y la de datos no inicializados puede variar durante la ejecución de un proceso se deben reservar regiones libres adyacentes.
A parte de las regiones del espacio de direcciones de memoria lógica a las que se accede en modo usuario, el SO ubica estructuras de datos relativas al proceso en curso que le permiten controlar su ejecución y que son accesibles en modo supervisor o núcleo.
Tipos de procesos
• Procesos de usuario. Pueden ejecutarse en primer o segundo plano. Se ejecutan en modo usuario.
• Procesos demonio. Procesos no asociados a ningún usuario. Realizan tareas relacionadas periódicas de administración del sistema. Se ejecutan en modo usuario generalmente en segundo plano.
• Procesos del sistema operativo. Realizan tareas de administración (como el intercambio de procesos entre memoria principal y secundaria) del sistema en modo supervisor y, generalmente, en segundo plano.
Estados de un proceso
Aunque el número y el nombre depende de cada sistema operativo, algunos de los estados más habituales son: • Nuevo. • Preparado. • Ejecutándose. • Bloqueado. • Terminado.
• Preparado en memoria secundaria. • Bloqueado en memoria secundaria.
Control de los procesos
Estructuras de control del sistema operativo
El sistema operativo debe mantener información sobre el estado de cada proceso y cada recurso. Esta información se guarda en tablas en memoria
• Tabla de procesos. Cada entrada en la tabla almacena un PCB (Process Control Block) con diversa información:
◦ Identificador del proceso. ◦ Identificador del usuario. ◦ Estado del proceso.
◦ Contenido de algunos registros del procesador. ◦ Información de planificación del proceso.
◦ Información de localización de la memoria principal asociada al proceso. ◦ Información de tipo estadístico.
◦ Información de estado de E/S.
◦ Información sobre el evento por el que el proceso ha entrado en modo bloqueado. • Tablas de memoria, con información sobre espacio libre y asignado.
• Tablas de E/S, con información sobre disponibilidad de dispositivos y quien lo usa. • Tablas de archivos, con información sobre archivos abiertos por los procesos.
Además, podemos encontrar las pilas de control del sistema o pilas del sistema operativo, así como
pilas de interrupciones, colas de procesos preparados y bloqueados, etc.
Creación de procesos
• Comprobar si el proceso puede ser creado.
• Asignar una nueva entrada de la tabla de procesos para el nuevo proceso. • Reservar espacio en memoria para el proceso.
• Inicializar el bloque de control del proceso. • Establecer los enlaces adecuados.
Las causas que originan la creación de un proceso pueden ser: • El arranque del sistema operativo.
• Interacción del usuario mediante intérprete de comandos o entorno de ventanas. • Inicio de un trabajo por lotes.
• Un proceso en ejecución invoca una llamada al sistema para crear otro proceso.
◦ UNIX y Linux, mediante llamada a fork. Crea una copia exacta del proceso padre.
◦ En Windows, createprocess utiliza otro espacio de direcciones distinto para el proceso
hijo.
Terminación de procesos
Cuando se produce una excepción en su ejecución. Cuando finaliza la tarea para la que ha sido creado
• En UNIX, llamada al sistema exit.
• En Windows llamada a ExitProcess.
Cuando un proceso privilegiado detenga otro. • En UNIX, llamada al sistema kill.
• En Windows, llamada al sistema TerminateProcess.
Una vez finalizado el proceso, hay que liberar los recursos que utilizaba. El PCB no se elimina inmediatamente, sino después de haber auditado los datos.
Cambio de proceso
Un proceso A se interrumpe para iniciar o continuar la ejecución de otro proceso B: cambio de
contexto o cambio de proceso. Causas más frecuentes para un cambio de contexto son:
• El proceso en ejecución pasa al estado bloqueado.
• La terminación voluntaria o forzada del proceso en ejecución.
• El sistema operativo termina de atender una interrupción y existe un proceso B en estado
preparado con mayor prioridad que el proceso actual.
• El proceso en ejecución ha excedido el tiempo máximo de ejecución ininterrumpida. Para proceder a un cambio de contexto, el sistema operativo sigue los pasos siguientes:
• Salva el contexto del proceso A en su PCB.
• Se ejecuta el algoritmo de planificación para seleccionar el proceso B. • Cargar el PCB del proceso B seleccionado.
El tiempo de conmutación es el tiempo que se tarda en completar todo el proceso debido a las limitaciones físicas de los componentes.
Ejecución del sistema operativo
En función de la tarea o servicio realizado, el tiempo utilizado por el sistema operativo puede ser contabilizado a un determinado proceso. El tiempo de uso de las tareas y servicios propios de la administración del sistema no se contabilizan a ningún proceso de usuario; este tiempo no dedicado a tareas de usuario se denomina sobrecarga del sistema (overhead).
El tiempo de determinadas tareas del sistema operativo, como la atención a las interrupciones hardware, no es sencillo asignarlas a un proceso en particular dado su carácter asíncrono. UNIX, por ejemplo, se las anota al proceso ejecutándose en el momento de producirse la interrupción, aunque no sea este el causante.
Procesos multihilo
• Conjunto de recursos asignados: espacio de direcciones, archivos abiertos, etc.
• Traza de ejecución, hilo de control, hilo o hebra (thread). Hace referencia a las instrucciones que ejecuta el proceso durante su tiempo de vida.
Los sistemas operativos modernos introducen el modelo de proceso multihilo: un proceso puede estar formado por múltiples hilos. Estos sistemas operativos utilizan los procesos como unidad de asignación del procesador que sigue una determinada traza de ejecución y tiene asignado una pila de usuario, espacio de almacenamiento y un bloque de control del hilo. Este bloque de control contiene el identificador numérico del hilo, el estado del hilo, su prioridad de ejecución y los valores de los registros. A un hilo se le llama a veces proceso ligero (lightweigth process).
Cada hilo perteneciente a un proceso comparte con los restantes hilos del mismo proceso los mismos recursos bloque de control del proceso, etc.
Ventajas de procesos multihilos respecto de múltiples procesos: • Aumento del rendimiento del sistema.
• Ahorro de recursos.
• Comunicación más eficiente.
• Mayor aprovechamiento de las arquitecturas multiprocesador. • Simplificación de la estructura de las aplicaciones.
El principal inconveniente es la necesidad de sincronización de acceso a las estructuras de datos que comparten los hilos.
Control de hilos
Como sucede con los procesos, un hilo puede encontrarse durante su tiempo de vida en diferentes estados. En un determinado instante, un proceso se encontrará en un estado determinado que no tiene por que coincidir con el estado de sus hilos. Obviamente, el estado del proceso limita el estado en el que pueden encontrarse sus hilos: si un proceso se encuentra en estado preparado ninguno de sus hilos puede estar ejecutándose.
La planificación de la cola de hilos se realiza a nivel de hilos. Cuando un hilo termina su ejecución simplemente desaparece, ya no es planificable.
El poder realizar un cambio de hilo dentro de un mismo proceso cuando un hilo cambia al estado bloqueado depende del tipo de implementación de hilos que soporte el sistema operativo.
En el momento que un hilo finaliza su ejecución sus recursos asignados son liberados. Un proceso no puede entrar en estado terminado hasta que no hayan finalizado todos sus hilos.
Tipos de hilos
Hilos a nivel de usuario. O, simplemente, hilos de usuario, son implementados por una biblioteca
de hilos que se ejecuta a nivel de usuario. Inicialmente, una aplicación comienza con un solo hilo.
Este proceso puede iniciar otro hilo invocando a la función apropiada de la biblioteca de hilos del SO.
Cada proceso requiere una tabla de hilos, en la que cada entrada, denominada bloque de control del
hilo, mantiene la información asociada al hilo (contador del programa, registro de pila, etc.) Esta
tabla es similar a la tabla de procesos.
La realización del cambio de hilo dentro de un mismo proceso se realiza sin necesidad de realizar un cambio de modo y un cambio de contexto. Toda esta actividad tiene lugar en modo usuario dentro del proceso en ejecución, sin intervención del sistema operativo. Esto proporciona las siguientes ventajas:
• Portabilidad. Al no requerir la intervención del SO, sólo de la biblioteca de hilos que puede estar disponible para múltiples SO.
• Mejora del rendimiento del sistema. Al realizarse toda la gestión de hilos en modo usuario. • Planificación independiente. Cada proceso puede emplear un algoritmo de planificación
distinto y estos a su vez distintos del que emplee el sistema operativo.
La principal desventaja se da en aquellos sistemas operativos que solo soportan un hilo a nivel de núcleo: cuando un hilo de un proceso entra en estado bloqueado, todo el proceso se bloquea: el sistema operativo desconoce la existencia de hilos de usuario. Otra desventaja es que cuando un hilo se está ejecutando no se puede planificar otro hilo de usuario del mismo proceso a no ser que el primero ceda voluntariamente el uso del procesador.
Hilos a nivel de núcleo
Los hilos del núcleo son implementados y gestionados directamente por el núcleo del SO. • No precisan biblioteca de hilos.
• El sistema mantiene una única tabla de hilos.
• Si uno se bloquea se puede planificar otro del mismo o distinto proceso. • En sistemas multiprocesador puede ejecutarse un hilo en cada procesador. • Su gestión contribuye a la sobrecarga del sistema.
• Algunos SO, por ello, limitan el número de hilos del núcleo.
• Para ahorrar en tiempo, cuando un hilo se destruye se marca como no planificable pero su estructura de datos no se elimina, se reutiliza para otros hilos.
Principales configuraciones en función del número y tipo de hilos soportados por un sistema operativo
Dependiendo del número y tipo de hilos soportados por un sistema operativo, se pueden distinguir las siguientes configuraciones:
• Múltiples hilos de usuario sin soporte de hilos del núcelo.
◦ El sistema planifica procesos, no hilos.
◦ La gestión de hilos la realiza una biblioteca de hilos.
◦ Cuando un hilo realiza una llamada al sistema, comienza a ejecutarse el SO. ◦ Si la llamada requiere el bloqueo del hilo, se bloquea el proceso completo.
◦ No pueden ejecutarse varios hilos al mismo tiempo, ya que sólo uno puede acceder al SO.
• Un hilo del núcleo por cada hilo de usuario.
◦ No se precisa biblioteca de hilos, solo acceder a las utilidades de hilos del núcleo. ◦ Si un hilo de usuario se bloquea se puede planificar otro del mismo proceso. ◦ La planificación la realiza el SO.
◦ Debido a la sobrecarga, se limita el número de hilos que es posible crear. • Menor número de hilos del núcleo que hilos de usuario.
◦ Los hilos de usuario se asocian a un número <= de hilos del núcleo.
◦ Es el programador de la aplicación quien se encarga de establecer el número de hilos del núcleo que necesita.
Capítulo 3. Planificación de procesos
En una máquina con multiprogramación pueden ejecutarse concurrentemente varios procesos. Si la máquina dispone de un único procesador, en un determinado instante de tiempo sólo un proceso o hilo puede usarlo. El sistema operativo debe decidir qué proceso es el que va a ser ejecutado.
Niveles de planificación
Colas de procesos para planificar los recursos del procesador: • Cola de procesos en estado preparado.
• Cola de procesos en estado preparado en memoria secundaria. • Cola de procesos en estado bloqueado.
• Cola de procesos en estado bloqueado en memoria secundaria. • Cola de trabajos por lote o cola de entrada.
Un proceso puede pasar por varias colas. La actividad de determinar cuándo se entra en una cola o se pasa a otra se denomina planificación de procesos y al componente que la realiza planificador. Se distinguen tres niveles de planificación:
• Planificación a corto plazo, que decide qué proceso será ejecutado a continuación.
• Planificación a medio plazo, de determina qué proceso en cola de memoria principal pasa a cola de memoria secundaria y viceversa.
• Planificación a largo plazo, determina qué trabajo por lotes pasa a ser ejecutado mediante la creación de un proceso.
Además, se puede distinguir un cuarto nivel de planificación, la planificación de E/S. Cada nivel de planificación es implementado por su propio planificador.
Planificador a corto plazo
Desde el punto de vista lógico, todo planificador se puede dividir en tres componentes o elementos: • Encolador (enqueuer), encargado de incluir al proceso en la cola de procesos preparados y
de asignarle una prioridad.
• Conmutador de contexto (context switcher), encargado de guardar el contexto del proceso a ser desalojado y cargar el contexto del proceso a ser ejecutado.
• Distribuidor o despachador (dispatcher), encargado de seleccionar un proceso de la cola de procesos preparados y cederle el control del procesador. El tiempo que transcurre entre la detención de un proceso y la ejecución de otro se llama tiempo de despacho. Algunos SO no permiten la expropiación de un proceso que se ejecuta en modo núcleo que está realizando una llamada al sistema. Estos sistemas se llaman de nucleo no expropiable.
Algunas circunstancias que hacen necesaria una llamada al planificador a corto plazo son: • Cuando se ha terminado de crear un nuevo proceso hijo.
• Cuando un proceso entra en estado bloqueado en espera de que se producza algún evento. • Cuando se termina de atender una interrupción.
Planificador a medio plazo
También conocido como planificador de memoria o intercambiador. Decide qué procesos de memoria principal se pasa a memoria secundaria y viceversa. Se ejecuta con menor frecuencia que el planificador a corto plazo. Puede ser invocado cuando queda espacio libre en la memoria principal o porque el número de procesos en el estado preparado cae por debajo de algún límite prefijado.
Planificador a largo plazo
También se denomina planificador de trabajos o planificador de admisión. Decide qué trabajo de los pendientes en la cola trabajos por lotes será admitido para ser ejecutado.
La admisión de un trabajo consiste en la creación de un proceso para el procesamiento de dicho trabajo.
El planificador a largo plazo permite regular el nivel de multiprogramación del sistema, invocándolo cuando se termina un trabajo o tras cierto tiempo de inactividad del procesador.
A tener en cuenta es que la ejecución de un proceso comienza con una ráfaga de CPU, esto es, utilizando el procesador de forma continuada durante un cierto periodo de tiempo. Así nos podemos encontrar con procesos limitados por CPU, en los que éste se encuentra la mayor parte del tiempo utilizando la CPU y poco los recursos de E/S y los procesos limitados por E/S. El planificador debe intentar seleccionar los trabajos de forma que exista un equilibrio entre procesos de un tipo y de otro.
Algunos sistemas operativos como UNIX no disponen de planificador a largo plazo.
Criterios generales considerados en la planificación de procesos
• Equidad. Requisistos similares → tiempo de uso de los recursos similares. • Previsibilidad. Características similares → tiempo de ejecución similares.
• Uso equilibrado de los recursos. Mantener todos los recursos ocupados / optimizados.
• Proporcionalidad. Peticiones sencillas → mejor tiempo de respuesta que peticiones complejas.
Criterios considerados en la planificación del procesador
El planificador del procesador elige el próximo proceso que pasará a ser ejecutado en el procesador en base a la optimización simultánea de alguno o alguno de los siguientes criterios:
• Utilización del procesador o eficacia. Porcentaje de tiempo del procesador activo.
• Productividad (throghput) o rendimiento. Número de trabajos completados por u.t. (1 hora) • Tiempo de entrega (turnaround time), también denominado tiempo de retorno o tiempo de
estancia. Tiempo desde que se lanza un proceso hasta que finaliza. • Tiempo de espera. Tiempo total de estancia en las colas de espera.
• Tiempo de respuesta (response time). Tiempo desde que se lanza una orden hasta que se obtiene el resultado.
• Plazo de finalización (deadline). Tiempo máximo que un proceso tiene para ser completado en un sistema de tiempo real.
La elección de los criterios a optimizar depende del tipo de sistema operativo. En un sistema por lotes debe intentar maximizar la utilización del procesador y la productividad, uno de tiempo compartido el tiempo de respuesta y uno de tiempo real el tiempo de entrega.
Normalmente, el planificador optimiza los valores medios de los criterios que considera.
Es más importante minimizar la varianza en el tiempo de respuesta que minimizar el tiempo de respuesta promedio.
Cuando se intentan optimizar varios criterios simultáneamente, se debe tener en cuenta que pueden ser incompatibles entre sí.
Expropiabilidad del procesador
Planificación del procesador no expropiativa (non preemptive) si permite que un proceso pueda estar ejecutándose en el procesador ininterrumpidamente.
• Utilizada en sistemas por lotes.
• También se utiliza en sistemas en tiempo real, en el que los procesos no utilizan el procesador por largos períodos de tiempo.
Planificación del procesador expropiativa (preemptive) si el proceso que se está ejecutando puede ser interrumpido en cualquier momento.
• Mayor sobrecarga en el sistema
• Proporciona mejor servicio a la población de procesos
• Requiere que el núcleo disponga de mecanismos de sincronización • Utilizada en sistemas de tiempo compartido o interactivos.
El tiempo máximo de ejecución ininterrumpida se denomina quantum (cuanto).
Algoritmos de planificación
Primero en llegar – primero en ser servido – FCFS
Abreviadamente FCFS (First Come First Served).
• Cede el uso del procesador al primer proceso que lo solicite. • No expropiativo.
• Requiere de una cola FIFO de procesos preparados. • Tiempo de espera promedio bastante grande. • Mejor para procesos largos.
• Favorece a los procesos limitados por el procesador frente a los limitados por E/S.
• Se desaconseja su uso como principal algoritmo de planificación. Útil en combinación con otros.
Primero el proceso más corto – SJF
Abreviadamente SJF, Shortest Job First o SPN Shortest Job Next.
• Se ejecuta primero el proceso con tiempo de procesamiento más corto. • No expropiativo.
• Si dos o más procesos tienen el mismo tiempo de procesamiento se aplica FCFS. • Minimiza el tiempo de espera promedio de un conjunto de procesos.
• Se suele utilizar en SO por lotes, tanto en el planificador a largo como a corto plazo. • Se precisa conocer por adelantado el tiempo de procesamiento de cada proceso.
• En SO de tiempo compartido, se puede estimar la duración de las siguientes ráfagas utilizando la duración de las anteriores. Se suele utilizar un preditor promedio exponencial.
Mn+ 1=α⋅tn+ (1−α)⋅Mn. donde Mn+1 es el promedio estimado para la próxima ráfaga; tn es
la duración de la última ráfaga, Mn el promedio estimado para la última ráfaga y α el peso
que se le asigna entre 0 y 1.
• Al tener que calcular estimaciones, la sobrecarga se hace mayor cuanto mayor es la precisión del cálculo.
• Los procesos largos pueden sufrir inanición.
Menor tiempo restante
Abreviadamente SRT, Shortest Remaining Time next, versión expropiativa del SFJ. • Algoritmo expropiativo.
• Siempre selecciona al proceso con el menor tiempo restante de ejecución. • Suele utilizarse en sistemas de procesos por lotes.
• Requiere una estimación de los tiempos restantes de ejecución de la población de procesos. • Puede producir inanición a los procesos largos.
• A igualdad de tiempo de servicio restante se aplica FCFS.
Planificación de turno rotatorio
Round Robin o cíclico, similar al FCFS pero expropiatorio.
• Asigna el uso ininterrumpido del procesador durante un periodo de tiempo determinado llamado cuanto, quantum o time slice.
• Al finalizar el cuanto, se interrumpe el proceso actual y se ejecuta el siguiente en la cola. El proceso interrumpido pasa al final de la cola.
• Si un proceso finaliza antes que su cuanto, se planifica el siguiente proceso preparado sin esperar a la finalización del cuanto.
• Si finaliza el cuanto de un proceso y no existen procesos en cola, vuelve a planificarse el mismo proceso.
• Elemento clave para el rendimiento del algoritmo es la elección del tamaño del cuanto. • Se recomienda un cuanto ligeramente mayor que el tiempo promedio de duración de una
ráfaga de procesador.
• Algoritmo diseñado para los sistemas de uso compartido.
• El tiempo de respuesta promedio, el tiempo de retorno promedio y el tiempo de espera promedio pueden ser largos.
Planificación basada en prioridades
Asigna a cada proceso una determinada prioridad en el momento de su creación. • Siempre se planifica para ejecución el proceso de mayor prioridad.
• La asignación de prioridades a los procesos puede realizarse en función de factores internos del sistema, como requerimientos de memoria, duración promedio de la ráfaga.. o a factores externos, como la importancia del usuario.
• Si la prioridad asignada no se modifica durante el tiempo de vida del proceso, se denomina asignación de prioridades estática; dinámica si se modifica.
• Puede ser expropiativo y no expropiativo. Cuando es de tipo expropiativo se dice que está
guiado por eventos (event-driven).
• Un proceso con baja prioridad puede sufrir inanición. • Técnicas para evitar la inanición:
◦ Envejecimiento de procesos (aging), aumentando la prioridad conforme aumenta el tiempo de espera.
◦ Disminuir la prioridad del proceso en ejecución en cada interrupción de reloj. ◦ Limitar el tiempo de ejecución.
• Puede darse el fenómeno de inversión de prioridad que ocurre cuando un proceso más prioritario debe esperar a que un recurso se libere por otro proceso con menor prioridad. Puede resolverse mediante el traspaso o herencia de prioridad.
Planificación basada en múltiples colas de prioridad
Se disponen varias colas cada una de las cuales dispone únicamente de procesos con un determinado nivel de prioridad o dentro de un rango de prioridades.
• Siempre se ejecuta un proceso de la cola de mayor prioridad. • Sólo cuando esa cola se vacía, se ejecutan los de la siguiente cola. • Un proceso sólo puede pertenecer a una cola.
• Para planificar cada cola puede usarse FCFS, SJF, etc.
• Puede producirse inanición de los procesos de las colas de menor prioridad. Para evitarlo, puede asignarse a los procesos de cada cola un porcentaje de tiempo de procesador
Múltiples colas de prioridad y realimentación
Se permite que un proceso pueda ir cambiando de cola de prioridad durante su existencia.
• Requiere definir un mecanismo que regule cómo se modifica la prioridad de los procesos, esto es, cómo y cuándo se produce el paso de una cola a otra.
• Un mecanismo puede consistir en reducir la prioridad del proceso en cada cuanto de ejecución hasta que llega a la cola de menor prioridad, donde se queda, siendo entonces esta cola de turno rotatorio.
◦ Con este mecanismo se favorecen los procesos nuevos y cortos frente a los viejos y largos.
• Para evitarlo puede asignarse un cuanto de mayor tamaño conforme disminuye la prioridad de las colas.
◦ Esta técnica no evita compeltamente la inanición, por lo que puede combinarse con la de envejecimiento de procesos.
Planificación por tiempo límite
En los sistemas de tiempo real estricto el tiempo de servicio de algunos procesos debe completarse en un determinado plazo o tiempo límite (deadline).
• El planificador sólo admite en la cola de procesos preparados aquellos a los que pueda garantizar que su tiempo de servicio se atenderá dentro de su tiempo límite, en función de los tiempos de espera existentes en la cola.
• Debe conocerse por adelantado el tiempo de servicio y deadline de cada proceso
Elección del algoritmo de planificación
En primer lugar deben fijarse los criterios de selección y a continuación evaluar los diferentes algoritmos de planificación usando algún método de evaluación.
• Modelado determinista. Evalua cada algorimo candidato usando una carga de trabajo predeterminada e idéntica para todos ellos. Es difícil establecer esta carga de manera realista y manejable.
• Modelo analítico mediante sistema de colas. Se obtiene un modelo analítico matemático. Cada recurso hardware del sistema y cada cola software asociada se modela como una estación de servicio que consta de un servidor y una cola de espera. Se establece para cada estación de servicio una determinada tasa de llegada y una tasa de servicio de peticiones. El conjunto formado por las estaciones de servicio y sus clientes (los usuarios) forma un sistema de colas que puede ser analizado usando la teoría de colas.
• Simulación. Se construye un programa que reproduce el comportamiento temporal del sistema basándose en sus estados y sus transiciones.
Planificación de hilos
La planificación de hilos depende de los tipos de hilos soportados por el SO.
• Si sólo se soportan hilos a nivel de usuario el SO no es consciente de su existencia, debiendo ser el proceso que los lanza quien debe encargarse de su planificación.
• Si se soportan hilos del núcleo el sistem operativo los planifica utilizando alguno de los métodos vistos anteriormente.
◦ El planificador debe tener en cuenta la sobrecarga que puede producir en el sistema el cambio de contexto entre hilos de distintos procesos.
Capítulo 4. Sincronización y comunicación de procesos
La ejecución concurrente de procesos presenta varios problemas, todos ellos asociados al hecho de que no es posible predecir la velocidad relativa de ejecución de un proceso. Dicha velocidad es función de la política de planificación del sistema operativo, de la forma en que se tratan las interrupciones y de la actividad de los otros procesos en el sistema.
Uno de los principales problemas de la ejecución concurrente de procesos es que el acceso a recursos globales compartidos (memoria, dispositivos de E/S, etc) debe controlarse de alguna forma si se desea que el resultado de la ejecución de un proceso sea independiente de la velocidad de ejecución del proceso con respecto a los otros procesos concurrentes.
Exclusión mutua
Interacción entre procesos concurrentes
En un sistema con multiprogramación se ejecutan múltiples procesos de manera concurrente. Podemos distinguir
• Procesos independientes, que se ejecutan sin tener en cuenta la existencia de otros procesos. • Procesos cooperantes, que intercambian información o colaboran entre sí para compartir un
recurso o realizar alguna tarea.
◦ Cooperación directa: los procesos conocen la existencia e identidad de los otros procesos con los que colaboran.
◦ Cooperación indirecta, los procesos no conocen la existencia e identidad de los otros procesos y, en consecuencia, no se comunican con ellos pero deben garantizar que los recursos queden en un estado coherente.
Condiciones de carrera
El problema denominado condición de carrera (race condition) surge cuando múltiples procesos independientes se ejecutan concurrentemente y acceden para leer o escribir en un recurso compartido. En ese caso el resultado final de la ejecución depende del órden en que se hayan planificado los procesos.
Secciones críticas
Sección crítica o región crítica de un proceso es una instrucción o conjunto de instrucciones secuenciales de su código que requieren manipular un recurso compartido (variable, dato, fichero, …) con otros procesos. Dentro del código de un proceso pueden existir varias regiones críticas.
Descripción de la exclusión mutua
Las condiciones de carrera se evitarían si el uso de un recurso por parte de un proceso excluye su uso para los restantes. Este mecanismo se denomina de exclusión mutua.
Sin más requisitos adicionales la exclusión mutua puede producir problemas como interbloqueos o inanición de procesos.
• El interbloqueo o bloqueo mutuo de dos o más procesos se produce cuando dos procesos A y B necesitan dos recursos R1 y R2 para realizar una cierta función. Si A posee R1 y B posee R2, ambos se quedarán esperando por el recurso que necesitan; no pueden liberar el recurso que poseen porque están esperando el otro recurso para realizar la función correspondiente.
• La inanición de un proceso se produce cuando este no puede progresar al necesitar un recurso al que nunca llega a acceder porque el sistema operativo siempre da prioridad a otros procesos.
Para evitar esto, los mecanismos de exclusión mutua deben cumplir cinco requisitos: 1. Sólo un proceso puede estar a la vez en una sección crítica asociada a un recurso R.
2. No pueden realizarse suposiciones sobre la velocidad relativa y prioridad de ejecución de los procesos concurrentes.
3. Un proceso que no está ejecutando una sección crítica asociada a un recurso R no está impidiendo o bloqueando el uso de R a otros procesos.
4. Debe evitarse la inanición de procesos. Un proceso no debe permanecer en su sección crítica por tiempo ilimitado.
5. Si ningún proceso está en una sección crítica asociada a R, debe concederse rápidamente el acceso a esa sección crítica al proceso que lo solicite.
Soluciones software a la exclusión mutua
No presuponen el apoyo del sistema operativo o del hardware del ordenador para su implementación.
Hacen uso de un bucle de comprobación del valor de una o varias variables cerrojo (lock). La comprobación del valor en un bucle se denomina cerrojo con vuelta (spinlock). Este tipo de espera se denomina espera activa (busy waiting) o espera cíclica (spin waiting).
• Uso de cerrojo y alternancia estricta. Esta solución considera la existencia de una variable
global de acceso que se usa como cerrojo y que puede tomar como valor el identificador numérico de un proceso.
Este método posee dos inconvenientes: la velocidad de ejecución se ve limitada por el proceso más lento. Además, si un proceso falla antes de cambiar el valor de la variable global el otro proceso quedará bloqueado indefinidamente.
• Algortimo de Peterson. Simplificado para dos procesos, se dispone de un vector donde cada
proceso establece a TRUE su deseo de acceder al recurso. Además, se guarda en una variable
global el identificador del otro proceso. Un bucle de espera cíclica comprueba que la petición pueda ser atendida y que el recurso no está siendo utilizado por el otro proceso.
Cada proceso llama a acceso_sc antes de acceder a su sección crítica y al salir llamaría a salida_sc, que establece a FALSE su entrada en peticion_rec. Este algoritmo garantiza la
exclusión mutua ya que el proceso que primero ejecuta la instrucción
peticion_rec[pid] = TRUE obtiene el acceso al recurso. Se evita la posibilidad de
interbloqueo al almacenarse en turno el valor del pid del otro proceso.
#define FALSE 0 #define TRUE 1 int turno;
int peticion_rec[2]; void acceso_sc(int pid) {
int otro_pid; otro_pid = 1 – pid; peticion_rec[pid] = TRUE; turno = otro_pid;
while(turno == otro_pid && peticion_rec[otro_pid == TRUE): }
Soluciones a la exclusión mutua con apoyo del hardware
• Uso de instrucciones máquina especiales que bloquean el uso del bus. Se caracterizan por
ser atómicas, es decir, se ejecutan en un único ciclo de instrucción que no puede ser interrumpido. Una instrucción típica en muchos procesadores es probar y configurar (test and set): ccc r1, cerrojo, que copiaría el contenido de la posición de memoria compartida cerrojo en el registro r1 y escibe en cerrojo un valor distinto de cero.
El principal inconveniente es que también hay que realizar una espera activa para comprobar el valor del cerrojo. También pueden producirse interbloqueos.
• Bloqueo de las interrupciones. Antes de entrar en una región crítica, puede ejecutarse una
instrucción especial que bloquea el mecanismo de interrupciones del sistema, restaurándolo al salir de la sección crítica. Pero el sistema se puede degradar bastante al no permitirse atender las interrupciones mas prioritarias en el momento en que llegan, Además, se deja en manos del proceso la decisión de ceder el procesador, pudiendose quedar el sistema colgado si este nunca lo devuelve. Esta solución no sirve en el caso de multiprocesadores.
Semáforos
Son un mecanismo de sincronización de procesos concurrentes gestionados por los sistemas operativos. Un semáforo es una variable que almacena un valor entero sobre el que se pueden realizar tres operaciones básicas:
• Asignar un valor inicial entero no negativo. S = N
• P(S). [wait]. Disminuir en una unidad el valor del semáforo. S = S – 1. Si S se hace
negativo, el proceso que ha llamado al semáforo pasa a la cola de procesos bloqueados asociada al semáforo.
• V(S). [signal]. Incrementa en una unidad el valor del semáforo, S = S + 1. Si S es menor o
igual a cero, se eliminar uno de los procesos bloqueados la cola y se pasa al estado preparado.
Los nombres P y V, se suelen encontrar como wait y signal. En este texto wait_sem y signal_sem.
El anterior es un semáforo general o semáforo con contador. La versión de semáforo que sólo puede tomar los valores 0 y 1 se denomina semáforo binario o mutex funcionando de la siguiente manera:
• wait_sem(S). Si S = 0, el proceso pasa a cola de bloqueados, si S = 1, pone el semáforo a
cero y continúa su ejecución.
• signal_sem(S). Si la cola de bloqueados está vacía, pone el semáforo a 1 y continúa. Si no
está vacía, se elimina un proceso de la cola de bloqueados y pasa al estado preparado.
Implementación
El sistema operativo se encarga de asignar las estructuras de datos necesarias para los semáforos y de realizar las operaciones sobre ellos.
Las operaciones wait_sem y signal_sem son implementadas por el núcleo como primitivas o
funciones atómicas, esto es, que se ejecutan como un único ciclo de instrucción que no puede ser interrumpido.
En la implementación de estas primitivas en el núcleo se ha de resolver un problema de exclusión mutua.
En sistemas monoprocesadores la exclusión mutua se garantiza bloqueando las interrupciones cuando se está ejecutando una operación sobre el semáforo.
En sistemas multiprocesador no puede emplearse la técnica de bloqueo de interrupciones, sino que se utilizan instrucciones especiales del procesador.
Si el procesador no dispone de ninguna instrucción especial, debe emplearse algún algoritmo como el de Peterson.
La cola puede gestionarse con cualquier algoritmo, siendo FIFO el más utilizado.
struct semaforo { int contador;
/* Otras variables para implementar el semáforo y su cola */ /* que dependen del sistema operativo */
}
void init_sem(semaforo S, int valor) S.contador = valor; } void wait_sem(semaforo S) { /* Bloqueo de interrupciones */ S.contador = S.contador – 1; if (S.contador < 0) {
/* Añadir el proceso a la cola del semáforo */ /* Bloquear el proceso */
/* Desbloquear las interrupciones */ } else {
/* Desbloquear las interrupciones */ }
}
void signal_sem(semaforo S) {
/* Bloqueo de las interrupciones */ S.contador = S.contador + 1;
if (contador <= 0) {
/* Eliminar un proceso P de la cola del semáforo */
/* Añadir el proceso P a la cola de procesos preparados */ }
/* Desbloqueo de las interrupciones */ }
struct semaforo { int contador; int ocupado;
/* Otras variables para implementar el semáforo y su cola */ /* que dependen del sistema operativo */
}
void init_sem(semaforo S, int valor) S.contador = valor;
}
void wait_sem(semaforo S) {
while (!test_set_maquina(S.ocupado)) // Bucle de espera activa S.contador = S.contador – 1;
if (S.contador < 0) {
/* Añadir el proceso a la cola del semáforo */ /* Bloquear el proceso */
S.ocupado = 0; }
void signal_sem(semaforo S) {
while (!test_set_maquina(S.ocupado)) // Bucle de espera activa S.contador = S.contador + 1;
if (contador <= 0) {
/* Eliminar un proceso P de la cola del semáforo */
/* Añadir el proceso P a la cola de procesos preparados */ }
S.ocupado = 0; }
Utilidad de los semáforos Obtención de exclusión mutua
Se utilizan tantos semáforos como recursos distintos se compartan, protegiendo el acceso a los recursos mediante secciones críticas.
Sincronización de procesos
En este caso basta con que el proceso que espera la ocurrencia de un suceso ejecute wait_sem para
pasar al estado bloqueado. El proceso que lanza la operación ejecuta signal_sem para notificar que
el suceso se ha producido, provocando que el proceso que espera prosiga su ejecución.
Problemas potenciales asociados a un mal uso de los semáforos
Una mala colocación de las operaciones signal_sem y wait_sem puede provocar errores difíciles de
detectar. En los siguientes ejemplos el semáforo acceso se utiliza para garantizar exclusión mutua.
En este caso se ha invertido el órden correcto de las operaciones, por lo que varios procesos podrían estar accediendo al mismo tiempo a la sección crítica.
En este ejemplo se escibe un segundo wait_sem en lugar del correcto signal_sem para indicar la
liberación del recurso. Por tanto, el proceso nunca desbloqueará el recurso, ya que una vez
#define TRUE 1 semaforo S void proceso_X() { while(TRUE) { /* Sección no crítica */ wait_sem(S);
/* Sección crítica asociada al recurso a utilizar */ signal_sem(S); /* Sección no crítica*/ } } wait_sem(acceso); // Sección crítica wait_sem(acceso) #define TRUE 1 semaforo S void procesoA() { while(TRUE) { // . . .
wait_sem(S); // Se bloquea en espera de un evento // . . . } } void procesoB() { while(TRUE) { // . . .
signal_sem(S); // Notifica la ocurrencia de un evento // . . .
} }
void main() {
init_sem(S, 0); // Inicia el semáforo binario S ejecución_concurrente(procesoA, procesoB); }
signal_sem(acceso); // Sección crítica wait_sem(acceso);
finalizada la sección crítica vuelve a bloquearse. El resto de procesos que intenten acceder a su sección crítica también serán bloqueados ya que el recurso nunca se ha dado por liberado produciéndose un interbloqueo.
Problemas clásicos de sincronización de procesos concurrentes
El problema de los productores y los consumidores
Un proceso (o varios) generan datos y otro proceso (o varios) los consumen. Se utiliza un buffer (el recurso compartido a proteger), en el que los productores escriben, si no está lleno, y los consumidores leen, si no está vacío.
Se utilizan un semáforo binario para acceder al buffer y dos semáforos para indicar el espacio disponible y el ocupado en el buffer, que serán utilizados por los consumidores y productores.
El problema de los lectores y los escritores
Un ejemplo típico de acceso a bases de datos. Múltiples lectores acceden a los registros, un escritor sólo puede acceder si no hay ningún lector accediendo. Los lectores sólo pueden acceder si no se está escribiendo. Múltiples lectores pueden estar accediendo al mismo tiempo. Sólo un escritor puede acceder.
Para llevar la cuenta del número de lectores se utiliza una variable global contador cuyo acceso se
protege mediante un semáforo binario. Otro semáforo binario controla el acceso a los datos.
#define TRUE 1
#define N 100 // Tamaño del buffer semaforo_binario acceso;
semaforo ocupado, disponible; void productor() {
int dato; // Dato a grabar en el buffer while (TRUE) {
dato = generar_dato();
wait_sem(disponible); // Espera/comprueba que el buffer tenga espacio libre wait_sem(acceso); // Espera a que el buffer esté disponible
escribir_buffer(dato); // Entrar en sección crítica: escribir en el buffer signal_sem(acceso); // Liberar el buffer
signal_sem(ocupado); // Avisa al consumidor de la existencia de datos }
}
void consumidor() {
int dato; // Dato a leer del buffer while (TRUE) {
wait_sem(ocupado); // Espera a que haya datos disponibles en el buffer wait_sem(acceso); // Espera a que el buffer esté disponible
leer_buffer(dato); // Leer dato del buffer / Sección crítica signal_sem(acceso); // Liberar el buffer
signal_sem(disponible); // Informa que tras la lectura, hay espacio en el buffer }
}
void main() {
init_sem(acceso, 1); // Inicializa el semáforo binario init_sem(disponible, N); // Indica el espacio disponible init_sem(ocupado, 0); // Indica el espacio ocupado ejecución_concurrente(productor, consumidor);
Monitores
Definición
Un monitor, es un módulo software que consta de un conjunto de procedimientos, variables y estructuras de datos que posee la propiedad especial de que solo permite a un único proceso simultáneamente ejecutar alguno de sus procedimientos. Las variables contenidas en un monitor sólo son accedidas por los procedimientos del monitor y no por procedimientos externos.
Para la sincronización de procesos, el monitor dispone de las variables de condición, cada una de ellas asociadas a una cola de procesos bloqueados en espera de que esa condición se cumpla.
Sobre una variable de condición se pueden realizar dos operaciones, en este texto:
• wait_mon(X). El proceso que realiza esta operación queda suspendido en la cola de procesos
bloqueados asociada a la condición X. Al contrario que en los semáforos, esta operación siempre produce el bloqueo del proceso que la invoca.
• signal_mon(X). Comprueba si en la cola de procesos bloqueados asociada a la variable X
existe algún proceso. En ese caso, se desbloquea un proceso. Esta operación no opera sobre un contador. Si no hay ningún proceso en cola, esta operación no tiene ningún efecto y la señal de aviso se pierde. Según la propuesta de B. Hansen (1975) el proceso que invoca esta operación sale del monitor inmediatamente, siendo por tanto la sentencia final del procedimiento de un monitor.
Estructura y declaración de un monitor
Un monitor consta de N procedimientos, M variables de condición, un conjunto de datos y algunas rutinas de inicialización del monitor. En el monitor sólo puede haber a la vez un proceso ejecutando alguno de sus procedimientos.
Cuando un proceso entra en el monitor permanece hasta que invoque wait_mon(X). En ese momento
el proceso pasa a la cola de bloqueados X pudiendo entrar otro proceso al monitor. Si un proceso en
#define TRUE 1
semaforo_binario S1, S2; int contador = 0; void lector() {
while (TRUE) {
wait_sem(S2); // Intenta acceder a la variable contador contador = contador + 1; // Hay un lector más accediendo
if (contador == 1) wait_sem(S1); // Si es el 1er lector, bloquea el acceso al escritor signal_sem(S2); // Se libera el acceso a la variable contador
leer_dato();
wait_sem(S2); // Solicitud de acceso a la variable contador contador = contador – 1; // Hay un lector menos
if (contador == 0) signal_sem(S1); // No hay lectores, se permite el acceso al escritor signal_sem(S1); // Libera el acceso a la variable contador
procesar_dato(); }
}
void escritor() { producir_dato();
wait_sem(S1); // Espera a tener acceso de escritura escribir_dato();
signal_sem(S1); // Libera el recurso } void main() { init_sem(S1, 1); init_sem(S2, 1); contador = 0; }
el monitor invoca signal_mon(X) un proceso de la cola de bloqueados es desbloqueado o, si
estuviera vacía, entraría un nuevo proceso al monitor en caso de que hubiese procesos esperando en la cola de entrada.
Uso de los monitores
Los monitores también pueden utilizarse para garantizar la exclusión mutua y la sincronización entre procesos.
Por la propia definición del monitor la exclusión mutua está garantizada defininiendo el acceso a los recursos compartidos dentro del monitor.
Mediante el uso de las variables de condición y las operaciones wait_mon y signal_mon se puede
proporcionar la sincronización de procesos.
La solución a los productores y consumidores utilizando monitores sería:
Paso de mensajes
Definición
El paso de mensajes es un mecanismo de sincronización y comunicación entre procesos soportado por los sistemas operativos tanto de sistemas centralizados como distribuidos. Un mensaje es un conjunto de información que puede ser intercambiada entre un proceso emisor y un proceso receptor.
Las operaciones básicas son:
#define TRUE 1
#define N 100 // Capacidad del buffer monitor mon1 // Definición del monitor
condicion lleno vacio; // Variables de condición int contador; // Variables locales char buffer[N];
void escribir(int dato) { // Procedimiento del monitor
if (contador == N) wait_mon(lleno); // Si buffer está lleno, bloquea escritor insertar_dato(buffer, dato);
contador = contador + 1;
if (contador == 1) signal_mon(vacio); // Si buffer no vacío, desbloquea lector }
void leer(int dato) {
if (contador == 0) wait_mon(vacio); // Si buffer vacío, se bloquea lector extraer_dato(buffer, dato);
contador = contador – 1;
if (contador == N-1) signal_mon(lleno); // Si no lleno, desbloquea escritor }
{
contador = 0; // Inicialización del monitor } end monitor void productor() { while (TRUE) { dato = producir_dato(); mon1.escribir(dato); } } void consumidor() { while (TRUE) { mon1.leer(dato); consumir_dato(dato); } }
send(destino, mensaje) receive(fuente, mensaje)
El argumento mensaje es la dirección de memoria de la estructura de datos que contiene el mensaje
a enviar o donde se almacena el mensaje a recibir. Los aspectos básicos a considerar son
• La especificación de la fuente y el destino del mensaje. • El esquema de sincronización.
• El formato del mensaje. • El medio de almacenamiento.
Especificación de la fuente y el destino del mensaje Comunicación directa
Emisor y receptor del mensaje especifican explícitamente a qué proceso va dirigido o de quién se espera el mensaje. Otra posibilidad es que el receptor especifique implícitamente el proceso emisor del mensaje, pudiendo llegar éste de cualquier emisor.
La comunicación directa explícita es útil para procesos concurrentes cooperantes, la comunicación directa implícita cuando un proceso realiza una operación en función del mensaje enviado por un proceso que no se conoce a priori (cola de impresión...).
Comunicación indirecta
El proceso emisor envía el mensaje a una estructura de datos compartida, el buzón, que se implementa como una cola de mensajes.
• Un emisor – un receptor: para comunicación privada entre procesos.
• Un emisor – varios receptores: para difundir determinada información entre varios procesos. • Varios emisores – un receptor: típico en aplicaciones cliente – servidor.
• Varios emisores – varios receptores: varios servidores atienden concurrentemente a varios clientes.
Esquemas de sincronización
Pueden distinguirse varios casos en función de si el proceso emisor o receptor pueden pasar al estado bloqueado al realizar una operación send o receive.
• Send con bloqueo: el emisor pasa al estado bloqueado hasta que el mensaje sea recibido por
el receptor o por el buzón.
• Send sin bloqueo: el emisor envía el mensaje al buzón o al destinatario y sigue con su
ejecución. Al no garantizarse su recepción deben emplearse mensajes de respuesta. El emisor también podría estar intentando enviar el mensaje repetidamente, degradando el rendimiento
• Receive con bloqueo: el receptor permanece en estado bloqueado en espera un mensaje. Si
este nunca llega, el receptor quedaría indefinidamente bloqueado.
• Receive sin bloqueo. El receptor obtiene un mensaje de un buzón o un emisor y prosigue su
ejecución. El mensaje puede ser nulo o no.
El esquema más utilizado es el envío sin bloqueo y la recepción con bloqueo. El envio y recepción con bloqueo se conoce como cita (rendezvous) entre emisor y receptor.
Formato y almacenamiento de los mensajes
El mensaje está formado por una cabecera y un cuerpo.
La longitud del mensaje puede ser fija o variable. Los mensajes de longitud fija son más eficientes para el sistema operativo, siendo los de longitud variable más sencillos para el programador de los procesos emisores.
Dependiendo de la capacidad de las colas de mensajes tenemos
• Mecanismo de mensajes sin buffer, en el que los mensajes nunca pueden esperar y el proceso
emisor se bloquea en espera de su recepción.
• Mecanismo de mensajes con buffer, en el que los mensajes se almacenan en un buffer
normalmente gestionado por un algoritmo de tipo FIFO.
Uso del paso de mensajes
Se pueden utilizar para intercambiar información entre procesos así como para garantizar la exclusión mutua y la sincronización.
Ejemplo de exclusión mutua utilizando un buzón y send sin bloqueo y receive con bloqueo. El
mensaje se utiliza como paso de testigo para ir pasando la utilización del recurso entre procesos.
#define TRUE 1
#define N 5 // Número de procesos void proceso(int i) { mensaje testigo; while (TRUE) { // Sección no crítica receive(buzon1, testigo); // Sección crítica send(buzon1, testigo); // Sección no crítica } } void main() { mensaje nulo; crear_buzon(buzon1); send(buzon1, nulo); ejecucion_concurrente(proceso(1),..., proceso(N)); }