Subido por tonyflowtrujillo

Sistemas Operativos William Stallings

Anuncio
0-Primeras
12/5/05
17:09
Página iii
Sistemas operativos
Aspectos internos y principios de diseño
Quinta Edición
WILLIAM STALLINGS
Traducción y revisión técnica
José María Peña Sánchez
Fernando Pérez Costoya
María de los Santos Pérez Hernández
Víctor Robles Forcada
Francisco Javier Rosales García
Departamento de Arquitectura y Tecnología de Sistemas Informáticos
Facultad de Informática
Universidad Politécnica de Madrid
Madrid • México • Santafé de Bogotá • Buenos Aires • Caracas • Lima
Montevideo • San Juan • San José • Santiago • Sâo Paulo • White Plains
0-Primeras
12/5/05
17:09
Página iv
Datos de catalogación bibliográfica
SISTEMAS OPERATIVOS
William Stallings
Pearson Educación, S.A., Madrid, 2005
ISBN: 978-84-205-5796-0
Materia: Informática 681.3
Formato: 195 x 250 mm.
Páginas: 872
Todos los derechos reservados.
Queda prohibida, salvo excepción prevista en la Ley, cualquier forma
de reproducción, distribución, comunicación pública y transformación
de esta obra sin contar con autorización de los titulares de propiedad
intelectual. La infracción de los derechos mencionados puede ser
constitutiva de delito contra la propiedad intelectual
(arts. 270 y sgts. Código Penal).
DERECHOS RESERVADOS
© 2005 respecto a la primera edición en castellano por:
PEARSON EDUCACIÓN, S.A.
C/ Ribera del Loira, 28
28042 Madrid (España)
SISTEMAS OPERATIVOS
William Stallings
ISBN: 84-205-4462-0
Depósito Legal:
PEARSON PRENTICE HALL es un sello editorial autorizado de PEARSON EDUCACIÓN S.A.
Authorized translation from the English language edition, entitled OPERATING SYSTEMS, 5th
Edition by STALLINGS, WILLIAM, published by Peason Education, Inc, publishing as Prentice
Hall, Copyright© 2005.
ISBN: 0-13-147954-7
All rights reserved. No part of this book may be reproduced or transmitted in any from or by any
means, elecronic or mechanical, including photocopying, recording or by any information
storage retrieval system, without permission from Pearson Education, Inc.
Equipo editorial
Editor: Miguel Martín-Romo
Técnico editorial: Marta Caicoya
Equipo de producción
Director: José A. Clares
Técnico: Isabel Muñoz
Diseño de cubierta
Equipo de diseño de Pearson Educación, S.A.
Impreso por
IMPRESO EN ESPAÑA - PRINTED IN SPAIN
Este libro ha sido impreso con papel y tintas ecológicos
0-Primeras
vi
12/5/05
17:09
Página vi
Contenido
PÁGINA WEB PARA SISTEMAS OPERATIVOS:
ASPECTOS INTERNOS Y PRINCIPIOS DE DISEÑO,
QUINTA EDICIÓN
La página web en WilliamStallings.com/OS/OS5e.html proporciona apoyo a profesores y estudiantes
que utilicen este libro. Incluye los siguientes elementos.
MATERIAL PARA APOYO DE CURSOS
El material para apoyo de los cursos incluye:
• Copia de las figuras del libro en formato PDF.
• Copia de las tablas del libro en formato PDF.
• Un conjunto de transparencias PowerPoint para utilizarlas como ayuda en clase.
• Notas de clase en HTML que pueden servir como material de ayuda para el estudio.
• Página de Recursos del Estudiante de Informática (Computer Science Student Resource
Site): contienen gran número de enlaces y documentos que los estudiantes pueden encontrar útiles para su formación en informática. Esta página incluye una revisión de las matemáticas básicas relacionadas; consejos para la búsqueda de información, redacción, y realización de problemas en casa; enlaces a repositorios de información de informática, tales como informes y
bibliografías; y otros enlaces de interés.
• Una hoja de erratas del libro, actualizada casi mensualmente.
DOCUMENTOS COMPLEMENTARIOS
Los documentos complementarios incluyen:
• Una copia en PDF de todos los algoritmos del libro en un pseudo-código de tipo Pascal de fácil
lectura.
• Material del libro relativo a Windows, UNIX, y Linux; reproducido en tres documentos PDF de
fácil referencia.
• Varios documentos que amplían lo tratado en el libro. Incluye aspectos relativos a la complejidad de los algoritmos, estándares de Internet y Sockets.
CURSOS DE SISTEMAS OPERATIVOS
La página web de OS5e incluye enlaces a otras páginas de cursos impartidos usando este libro. Estas
páginas pueden proporcionar guías útiles sobre cómo planificar y ordenar los temas tratados, así como
un gran número de anotaciones y material diverso.
0-Primeras
12/5/05
17:09
Página vii
Contenido
vii
PÁGINAS WEB ÚTILES
La página web de OS5e incluye también enlaces a otras páginas de interés. Los enlaces cubren un amplio espectro de temas y permitirán a los estudiantes explorar aspectos concretos con gran profundidad.
LISTA DE CORREO ELECTRÓNICO
Se mantiene una lista de correo para que los profesores que utilicen este libro puedan intercambiar información, sugerencias, y preguntas entre ellos y con el autor. La información de suscripción se proporciona
en la página web del libro.
PROYECTOS DE SISTEMAS OPERATIVOS
La página web incluye enlaces a las páginas de Nachos y BACI, que son dos paquetes software que sirven como entornos para implementación de proyectos. Cada página incluye software para descargar
con información de apoyo. Véase el Apéndice C para más información.
0-Primeras
12/5/05
17:09
Página ix
Contenido
ix
Contenido
Prólogo
Capítulo 0
0.1
0.2
0.3
xvii
Guía del lector
1
Organización del libro 2
Orden de presentación de los temas 3
Recursos en Internet y en la Web 4
PRIMERA PARTE: ANTECECENTES
Capítulo 1
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
Capítulo 2
2.1
2.2
2.3
2.4
2.5
2.6
2.7
2.8
2.9
2.10
7
Introducción a los computadores
9
Elementos básicos 10
Registros del procesador 11
Ejecución de instrucciones 14
Interrupciones 17
La jerarquía de memoria 27
Memoria cache 30
Técnicas de comunicación de E/S 34
Lecturas y sitios web recomendados 37
Términos clave, cuestiones de repaso y problemas 38
Apéndice 1A Características de rendimiento de las memorias de dos niveles
Apéndice 1B Control de procedimientos 48
Introducción a los sistemas operativos
53
Objetivos y funciones de los sistemas operativos 54
La evolución de los sistemas operativos 58
Principales logros 67
Desarrollos que han llevado a los sistemas operativos modernos
Descripción global de Microsoft Windows 82
Sistemas UNIX tradicionales 91
Sistemas UNIX modernos 94
Linux 95
Lecturas y sitios web recomendados 101
Términos clave, cuestiones de repaso y problemas 103
79
41
0-Primeras
x
12/5/05
17:09
Página x
Contenido
SEGUNDA PARTE: PROCESOS
Capítulo 3
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
105
Descripción y control de procesos
107
¿Qué es un proceso? 108
Estados de procesos 110
Descripción de los procesos 126
Control de procesos 135
Gestión de procesos en UNIX SVR4 143
Resumen 149
Lecturas recomendadas 149
Términos clave, cuestiones de repaso y problemas
150
Proyecto de programación uno. Desarrollo de un intérprete de mandatos
Capítulo 4
4.1
4.2
4.3
4.4
4.5
4.6
4.7
4.8
4.9
Capítulo 5
5.1
5.2
5.3
5.4
5.5
5.6
5.7
5.8
5.9
Capítulo 6
6.1
6.2
Hilos, SMP y micronúcleos
154
157
Procesos e hilos 158
Multiprocesamiento simétrico 172
Micronúcleos 176
Gestión de hilos y SMP en Windows 181
Gestión de hilos y SMP en Solaris 187
Gestión de procesos e hilos en Linux 193
Resumen 196
Lecturas recomendadas 196
Términos clave, cuestiones de repaso y problemas
Concurrencia. Exclusión mutua y sincronización
Principios de la concurrencia 203
Exclusión mutua: soporte hardware 212
Semáforos 215
Monitores 229
Paso de mensajes 235
El problema de los Lectores/Escritores 241
Resumen 245
Lecturas recomendadas 247
Términos clave, cuestiones de repaso y problemas
Concurrencia. Interbloqueo e inanición
Fundamentos del interbloqueo 258
Prevención del interbloqueo 267
257
197
201
248
0-Primeras
12/5/05
17:09
Página xi
Contenido
6.3
6.4
6.5
6.6
6.7
6.8
6.9
6.10
6.11
6.12
6.13
Predicción del interbloqueo 269
Detección del interbloqueo 273
Una estrategia integrada de tratamiento del interbloqueo 277
El problema de los filósofos comensales 277
Mecanismos de concurrencia de UNIX 280
Mecanismos de concurrencia del núcleo de Linux 284
Funciones de sincronización de hilos de Solaris 291
Mecanismos de concurrencia de Windows 294
Resumen 296
Lecturas recomendadas 297
Términos clave, cuestiones de repaso y problemas 297
TERCERA PARTE: MEMORIA
Capítulo 7
7.1
7.2
7.3
7.4
7.5
7.6
7.7
Capítulo 8
8.1
8.2
8.3
8.4
8.5
8.6
8.7
8.8
305
Gestión de memoria
Requisitos de gestión de memoria 308
Particionamiento de la memoria 311
Paginación 321
Segmentación 325
Resumen 327
Lecturas recomendadas 327
Términos clave, cuestiones de repaso y problemas
Apéndice 7A Carga y enlace 331
Memoria virtual
9.1
9.2
328
339
Hardware y estructuras de control 340
Software del sistema operativo 358
Gestión de memoria de UNIX y Solaris 378
Gestión de memoria en Linux 384
Gestión de memoria en Windows 386
Resumen 388
Lectura recomendada y páginas web 389
Términos clave, cuestiones de repaso y problemas
Apéndice 8A Tablas Hash
395
CUARTA PARTE: PLANIFICACIÓN
Capítulo 9
307
399
Planificación uniprocesador
401
Tipos de planificación del procesador
Algoritmos de planificación 406
402
390
xi
0-Primeras
xii
12/5/05
17:09
Página xii
Contenido
9.3
9.4
9.5
9.6
Planificación UNIX tradicional 427
Resumen 429
Lecturas recomendadas 431
Términos clave, cuestiones de repaso y problemas
Apéndice 9A Tiempo de respuesta 436
Apéndice 9B Sistemas de colas 438
Proyecto de programación dos. El planificador de HOST
431
444
Capítulo 10
Planificación multiprocesador y de tiempo real
451
10.1
10.2
10.3
10.4
10.5
10.6
10.7
10.8
Planificación multiprocesador 452
Planificación de tiempo real 463
Planificación en Linux 477
Planificación en UNIX SVR4 480
Planificación en Windows 482
Resumen 484
Lecturas recomendadas 485
Términos clave, cuestiones de repaso y problemas
485
QUINTA PARTE: ENTRADA/SALIDA Y FICHEROS
Capítulo 11
11.1
11.2
11.3
11.4
11.5
11.6
11.7
11.8
11.9
11.10
11.11
11.12
11.13
Capítulo 12
12.1
12.2
489
Gestión de la E/S y planificación del disco
491
Dispositivos de E/S 492
Organización del sistema de E/S 493
Aspectos de diseño del sistema operativo 496
Utilización de buffers de E/S 500
Planificación del disco 503
RAID
511
Cache de disco 520
E/S de UNIX SVR4 522
E/S de Linux 527
E/S de Windows 530
Resumen 532
Lecturas y sitios web recomendados 532
Términos clave, cuestiones de repaso y problemas 534
Apéndice 11A Dispositivos de almacenamiento en disco
Gestión de ficheros
547
Descripción básica 548
Organización y acceso a los ficheros
553
537
0-Primeras
12/5/05
17:09
Página xiii
Contenido
12.3
12.4
12.5
12.6
12.7
12.8
12.9
12.10
12.11
12.12
Directorios 559
Compartición de ficheros 563
Bloques y registros 564
Gestión de almacenamiento secundario 566
Gestión de ficheros de UNIX 574
Sistema de ficheros virtual Linux 578
Sistema de ficheros de Windows 582
Resumen
587
Lecturas recomendadas 588
Términos clave, cuestiones de repaso y problemas
SEXTA PARTE: SISTEMAS DISTRIBUIDOS Y SECURIDAD
Capítulo 13
13.1
13.2
13.3
13.4
13.5
13.6
13.7
Capítulo 14
14.1
14.2
14.3
14.4
14.5
14.6
14.7
14.8
14.9
14.10
Capítulo 15
15.1
15.2
15.3
Redes
589
591
595
La necesidad de una arquitectura de protocolos 597
La arquitectura de protocolos TCP/IP 599
Sockets 605
Redes en Linux 609
Resumen 611
Lecturas y sitios web recomendados 611
Términos clave, cuestiones de repaso y problemas 612
Apéndice 13A El Protocolo simple de transferencia de ficheros
Procesamiento distribuido, cliente/servidor y clusters
Computación cliente/servidor 620
Paso de mensajes distribuido 630
Llamadas a procedimiento remoto 633
Clusters 636
Servidor Cluster de Windows 642
Sun Cluster
643
Clusters de Beowulf y Linux 646
Resumen 648
Lecturas recomendadas y sitios web
648
Términos clave, cuestiones de repaso y problemas
Gestión de procesos distribuidos
Migración de procesos 654
Estados globales distribuidos 660
Exclusión mutua distribuida 665
653
650
619
614
xiii
0-Primeras
xiv
12/5/05
17:09
Página xiv
Contenido
15.4
15.5
15.6
15.7
Capítulo 16
16.1
16.2
16.3
16.4
16.5
16.6
16.7
16.8
16.9
APÉNDICES
Apéndice A
A.1
A.2
A.3
A.4
Apéndice B
B.1
B.2
B.3
B.4
B.5
Apéndice C
C.1
C.2
C.3
C.4
C.5
Interbloqueo distribuido 675
Resumen 685
Lecturas recomendadas 685
Términos clave, cuestiones de repaso y problemas
Seguridad
686
689
Amenazas de seguridad 690
Protección 695
Intrusos 701
Software malicioso 713
Sistemas confiables 722
Seguridad en Windows 725
Resumen 731
Lecturas recomendadas y sitios web
732
Términos clave, cuestiones de repaso y problemas
Apéndice 16A Cifrado 736
733
743
Temas de concurrencia
743
Exclusión mutua. Técnicas de software 744
Condiciones de carrera y semáforos 748
El problema de la barbería 758
Problemas 763
Diseño orientado a objetos
765
Motivación 766
Conceptos de orientación a objetos 767
Beneficios del diseño orientado a objetos 771
CORBA
772
Lecturas y sitios web recomendados 775
Proyectos de programación y de sistemas operativos
Proyectos para la enseñanza de sistemas operativos
NACHOS 779
Proyectos de investigación 780
Proyectos de programación 780
Tareas de lectura y de análisis 781
778
777
0-Primeras
13/5/05
17:21
Página xv
Contenido
Apéndice D
OSP. Un entorno para proyectos de sistemas operativos
D.1
D.2
D.3
Introducción 784
Aspectos innovadores de OSP 785
Comparación con otras herramientas docentes de sistemas operativos
Apéndice E
BACI. El Sistema de programación concurrente de Ben-Ari
E.1
E.2
E.3
E.4
E.5
Introducción 790
BACI 790
Ejemplos de programas BACI 793
Proyectos BACI 797
Mejoras al Sistema BACI 800
Glosario
801
Referencias
811
Acrónimos
827
Índice
783
829
789
786
xv
0-Primeras
12/5/05
17:09
Página xvi
0-Primeras
12/5/05
17:09
Página xvii
Contenido
xvii
Prólogo
OBJETIVOS
Este libro se ocupa de los conceptos, la estructura y los mecanismos de los sistemas operativos. Su propósito es presentar, de la manera más clara y completa posible, la naturaleza y las características de los
sistemas operativos de hoy en día.
Esta tarea es un reto por varios motivos. En primer lugar, los computadores para los que se diseñan
los sistemas operativos presentan una enorme variedad. Esta diversidad incluye desde estaciones de
trabajo y computadores personales para un único usuario, pasando por sistemas compartidos de tamaño medio, hasta grandes sistemas mainframe y supercomputadores, así como máquinas especializadas
tales como los sistemas de tiempo real. La variedad no está sólo en la capacidad y la velocidad de las
máquinas, sino también en los requisitos de las aplicaciones y del sistema. En segundo lugar, el rápido
ritmo de cambios que ha caracterizado siempre a los sistemas informáticos continúa sin remitir. Diversas áreas fundamentales en el diseño de sistemas operativos son de reciente aparición, estando todavía
activa la investigación sobre las mismas, así como sobre otras nuevas áreas.
A pesar de esta variedad y de este ritmo de cambios incesante, ciertos conceptos fundamentales siguen siendo aplicables en todo momento. Evidentemente, su aplicación depende del estado actual de la
tecnología y de los requisitos particulares de la aplicación. El objetivo de este libro es proporcionar un
estudio profundo de los fundamentos del diseño de sistemas operativos y relacionarlos con aspectos de
diseño contemporáneos y con las tendencias actuales en el desarrollo de sistemas operativos.
SISTEMAS DE EJEMPLO
Este libro está destinado a dar a conocer al lector los principios de diseño y los aspectos de implementación de los sistemas operativos contemporáneos. Por consiguiente, un tratamiento puramente conceptual o teórico sería inadecuado. Para mostrar los conceptos y asociarlos a alternativas de diseño del
mundo real, se han seleccionado tres sistemas operativos como ejemplos reales:
• Windows XP y Windows 2003. Un sistema operativo multitarea para computadores personales, estaciones de trabajo y servidores. Al tratarse de un nuevo sistema operativo, incorpora de
una manera nítida muchos de los últimos desarrollos en la tecnología de sistemas operativos.
Además, Windows es uno de los primeros sistemas operativos comerciales importantes que está
estrechamente basado en principios de diseño orientado a objetos. Este libro se ocupa de la tecnología utilizada en las versiones más recientes de Windows, XP para estaciones de trabajo y computadores personales, y 2003 para servidores.
• UNIX. Un sistema operativo multiusuario, originalmente destinado a minicomputadores, pero
implementado en un amplio rango de máquinas desde poderosos microcomputadores a supercomputadores. Se incluyen dos versiones de UNIX. UNIX SVR4 es un sistema muy usado que
incorpora muchas características avanzadas. Solaris es la versión comercial más utilizada de
UNIX. Incluye procesamiento multihilo y otras características que no se encuentran en SVR4 ni
en la mayoría de las otras versiones de UNIX.
• Linux. Una versión de UNIX cuyo código fuente está disponible libremente, que es muy utilizada actualmente.
0-Primeras
12/5/05
xviii
17:09
Página xviii
Prólogo
Estos sistemas se seleccionaron por su relevancia y representatividad. El estudio de los sistemas de
ejemplo se distribuye a lo largo del texto en vez de agruparlos en un solo capítulo o apéndice. Así, durante el estudio de la concurrencia, se describen los mecanismos de concurrencia de cada sistema de ejemplo, y se explica la motivación de las diversas opciones de diseño individuales. Con este enfoque, los
conceptos de diseño estudiados en cualquier capítulo se refuerzan inmediatamente con ejemplos del
mundo real.
AUDIENCIA A LA QUE ESTÁ DESTINADO
Este libro está destinado tanto a una audiencia de carácter académico como a una de perfil profesional. Como libro de texto, está pensado para un curso de sistemas operativos de un semestre para las
titulaciones de Informática, Ingeniería de Computadores e Ingeniería Eléctrica. Incluye los temas recomendados por el Computer Curricula 2001 para programas universitarios de informática, realizado
por el equipo de trabajo conjunto para planes de estudio de informática (Joint Task Force on Computing Curricula) de la sociedad informática (Computer Society) de IEEE y ACM. El libro también trata los temas recomendados por Guidelines for Associate-Degree Curricula in Computer Science 2002,
también del equipo de trabajo conjunto para planes de estudio de informática de la sociedad informática de IEEE y ACM. El libro sirve igualmente como un volumen de referencia básico, adecuado para
el estudio personal.
ORGANIZACIÓN DEL LIBRO
Este libro se divide en seis partes (véase el Capítulo 0 para una visión general):
• Antecedentes.
• Procesos.
• Memoria.
• Planificación.
• Entrada/salida y ficheros.
• Sistemas distribuidos y seguridad.
Este libro incluye diversas características pedagógicas, como el uso de numerosas figuras y tablas
para facilitar el estudio. Cada capítulo incluye una lista de términos clave, preguntas de repaso, problemas,
propuestas de lecturas adicionales y direcciones de sitios web relevantes. Además, está disponible para
los profesores una batería de preguntas de test.
SERVICIOS DE INTERNET PARA PROFESORES Y ESTUDIANTES
Hay un sitio web asociado a este libro que proporciona apoyo a los estudiantes y a los profesores. El sitio incluye enlaces a otros sitios relevantes, copias originales de las transparencias de las figuras y tablas del libro en formato PDF (Adobe Acrobat), transparencias en PowerPoint e información para darse de alta en la lista de correo de Internet del libro. La página web está en
WilliamStallings.com/OS/OS5e.html. Véase la Sección “Sitio web de sistemas operativos. Aspectos internos y principios de diseño” anterior a este prólogo para más información. Se ha establecido una lista de correo para que los profesores que usan este libro puedan intercambiar información, sugerencias
y preguntas entre sí y con el propio autor. En cuanto se descubran errores tipográficos o de otro tipo, se
0-Primeras
12/5/05
17:09
Página xix
Prólogo
xix
publicará una lista de erratas en WilliamStallings.com. Por último, hay que resaltar que el autor mantiene un sitio para el estudiante de informática en WilliamStallings.com/StudentSupport.html.
PROYECTOS DE SISTEMAS OPERATIVOS
Para muchos instructores, un elemento importante de un curso de sistemas operativos es un proyecto o
un conjunto de proyectos mediante los cuales el estudiante obtiene una experiencia práctica que le permite reforzar los conceptos del libro. Este libro proporciona un incomparable grado de apoyo en ese aspecto, incluyendo un componente de proyectos en el curso. En el interior del libro se definen dos proyectos de programación principales. El sitio web del profesor ofrece referencias en línea que pueden utilizar
los estudiantes para abordar estos proyectos de forma gradual. Se proporciona información sobre tres
paquetes de software que sirven como entornos de trabajo para la implementación de proyectos: OSP
y NACHOS para desarrollar componentes de un sistema operativo, y BACI para estudiar los mecanismos de concurrencia. Además, el sitio web del profesor incluye una serie de pequeños proyectos de
programación, cada uno pensado para desarrollarse en una o dos semanas, que cubre un amplio rango
de temas y que pueden implementarse en cualquier lenguaje apropiado y sobre cualquier plataforma, así
como proyectos de investigación y tareas de lectura y análisis. Véase los apéndices para más detalles.
NOVEDADES DE LA QUINTA EDICIÓN
En esta nueva edición, el autor ha intentado recoger las innovaciones y mejoras que ha habido en esta disciplina durante los cuatro años que han transcurrido desde la última edición, manteniendo un tratamiento amplio y completo de esta materia. Asimismo, varios profesores que imparten esta disciplina, así como
profesionales que trabajan en este campo, han revisado en profundidad la cuarta edición. Como consecuencia de este proceso, en muchas partes del libro, se ha mejorado la claridad de la redacción y de las
ilustraciones que acompañan al texto. Además, se han incluido varios problemas de carácter realista.
Además de mejoras pedagógicas y en su presentación de cara al usuario, el contenido técnico del
libro se ha actualizado completamente, para reflejar los cambios actuales en esta excitante disciplina.
El estudio de Linux se ha extendido significativamente, basándose en su última versión: Linux 2.6. El
estudio de Windows se ha actualizado para incluir Windows XP y Windows Server 2003. Se ha revisado y extendido el material dedicado a la concurrencia para mejorar su claridad, moviendo parte del mismo a un apéndice, e incluyendo un estudio sobre condiciones de carrera. El tratamiento de la planificación en esta nueva versión incluye un estudio de la inversión de prioridades. Hay un nuevo capítulo
sobre redes, presentándose el API de Sockets. Además, se ha ampliado el tratamiento del diseño orientado a objetos.
AGRADECIMIENTOS
Esta nueva edición se ha beneficiado de la revisión realizada por diversas personas, que aportaron generosamente su tiempo y experiencia. Entre ellos se incluyen Stephen Murrell (Universidad de Miami),
David Krumme (Universidad de Tufts), Duncan Buell (Universidad de Carolina), Amit Jain (Universidad de Bosie State), Fred Kuhns (Universidad de Washington, St.Louis), Mark McCullen (Universidad
de Michigan State), Jayson Rock (Universidad de Wisconsin-Madison), David Middleton (Universidad
de Arkansas Technological) y Binhai Zhu (Universidad de Montana State), todos revisaron la mayor parte o todo el libro.
El autor da las gracias también a mucha gente que realizó revisiones detalladas de uno o más capítulos: Javier Eraso Helguera, Andrew Cheese, Robert Kaiser, Bhavin Ghandi, Joshua Cope, Luca Ve-
0-Primeras
xx
12/5/05
17:09
Página xx
Prólogo
nuti, Gregory Sharp, Marisa Gil, Balbir Singh, Mrugesh Gajjar, Bruce Janson, Mayan Moudgill, Pete
Bixby, Sonja Tideman, Siddharth Choudhuri, Zhihui Zhang, Andrew Huo Zhigang, Yibing Wang, Darío Álvarez y Michael Tsai. Asimismo, al autor le gustaría agradecer a Tigran Aivazian, autor del documento sobre los aspectos internos del núcleo de Linux (Linux Kernel Internals), que es parte del proyecto de documentación de Linux (Linux Documentation Project), por su revisión del material sobre Linux
2.6. Ching-Kuang Shene (Universidad de Michigan Tech) proporcionó los ejemplos usados en la sección sobre condiciones de carrera y revisó dicha sección.
Asimismo, Fernando Ariel Gont contribuyó con diversos ejercicios para el estudiante y llevó a cabo
revisiones detalladas de todos los capítulos.
El autor querría también dar las gracias a Michael Kifer y Scott A. Smolka (SUNY–Stony Brook)
por contribuir al Apéndice D, a Bill Bynum (College of William and Mary) y Tracy Camp (Colorado
School of Mines) por prestar su ayuda en el Apéndice E; Steve Taylor (Worcester Polytechnic Institute) por colaborar en los proyectos de programación y en las tareas de lectura y análisis del manual del
profesor, y al profesor Tan N. Nguyen (Universidad de George Mason) por contribuir a los proyectos
de investigación del manual del profesor. Ian G.Graham (Universidad de Griffith) colaboró con los dos
proyectos de programación del libro. Oskars Rieksts (Universidad de Kutztown) permitió de forma generosa que se hiciera uso de sus notas de clase, ejercicios y proyectos.
Por último, el autor querría dar las gracias a las numerosas personas responsables de la publicación
del libro, todas realizaron como de costumbre un excelente trabajo. Esto incluye al personal de Prentice Hall, particularmente a los editores Alan Apt y Toni Holm, su ayudante Patrick Lindner, la directora de producción Rose Kernan, y la directora de suplementos Sarah Parker. Este agradecimiento se extiende también a Jake Warde de Warde Publishers que dirigió el proceso de revisión, y a Patricia M. Daly
que realizó la edición de la copia.
00-Capitulo 0
12/5/05
16:15
Página 1
CAPÍTULO
0
Guía del lector
0.1.
Organización del libro
0.2.
Orden de presentación de los temas
0.3.
Recursos en Internet y en la Web
00-Capitulo 0
2
12/5/05
16:15
Página 2
Sistemas operativos. Aspectos internos y principios de diseño
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
Este libro, junto con su sitio web asociado, cubre una gran cantidad de material. A continuación, se
le proporciona al lector una visión general del mismo.
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
0.1. ORGANIZACIÓN DEL LIBRO
El libro está organizado en siete partes:
Primera parte. Antecedentes. Proporciona una introducción a la arquitectura y organización
del computador, haciendo énfasis en aquellos aspectos relacionados con el diseño de sistemas
operativos, presentando, asimismo, una visión general de los temas de sistemas operativos tratados en el resto del libro.
Segunda parte. Procesos. Presenta un análisis detallado de los procesos, el procesamiento
multihilo, el multiprocesamiento simétrico (Symmetric Multiprocessing, SMP) y los micronúcleos. En esta parte se estudian también los aspectos principales de la concurrencia en un sistema uniprocesador, haciendo hincapié en los temas de la exclusión mutua y de los interbloqueos.
Tercera parte. Memoria. Proporciona un extenso estudio de las técnicas de gestión de memoria, incluyendo la memoria virtual.
Cuarta parte. Planificación. Ofrece un estudio comparativo de diversas estrategias de planificación de procesos. Se examinará también la planificación de hilos, de SMP y de tiempo
real.
Quinta parte. Entrada/salida y ficheros. Examina los aspectos involucrados en el control de
las operaciones de E/S por parte del sistema operativo. Se dedica especial atención a la E/S del
disco, que es fundamental para el rendimiento del sistema. Asimismo, proporciona una visión
general de la gestión de ficheros.
Sexta parte. Sistemas distribuidos y seguridad. Estudia las principales tendencias en redes
de computadores, incluyendo TCP/IP, procesamiento cliente/servidor y clusters. Asimismo,
describe algunas áreas de diseño fundamentales en el desarrollo de los sistemas operativos distribuidos. El Capítulo 16 proporciona un estudio de las amenazas y los mecanismos para proporcionar seguridad al computador y a la red.
Este libro está dedicado a dar a conocer a los lectores los principios de diseño y los aspectos de
implementación de los sistemas operativos contemporáneos. Por tanto, sería inadecuado un tratamiento puramente teórico o conceptual. Para mostrar los conceptos y asociarlos a opciones de diseño que se deben tomar en la vida real, se han seleccionado dos sistemas operativos como ejemplos
reales:
• Windows. Un sistema operativo multitarea diseñado para ejecutar en diversos computadores
personales, estaciones de trabajo y servidores. Es uno de los pocos sistemas operativos comerciales recientes diseñado esencialmente desde cero. Debido a esto, está en una buena posición
para incorporar de una manera nítida los más recientes desarrollos en la tecnología de sistemas
operativos.
• UNIX. Un sistema operativo multitarea destinado originalmente a minicomputadores pero implementado en un amplio rango de máquinas desde poderosos microprocesadores a supercomputadores. Dentro de esta familia de sistemas operativos, se incluye Linux.
00-Capitulo 0
12/5/05
16:15
Página 3
Guía del lector
3
El estudio de los sistemas de ejemplo está distribuido a través del libro en vez de agrupado en un
único capítulo o apéndice. Así, durante el estudio de la concurrencia, se describe el mecanismo de
concurrencia de cada sistema de ejemplo y se discute la motivación de las opciones de diseño particulares. Con esta estrategia, los conceptos de diseño estudiados en un determinado capítulo son inmediatamente reforzados con los ejemplos del mundo real.
0.2. ORDEN DE PRESENTACIÓN DE LOS TEMAS
Sería natural que los lectores cuestionaran el orden particular de presentación de los temas en este libro. Por ejemplo, el tema de planificación (Capítulos 9 y 10) está muy relacionado con los dedicados
a la concurrencia (Capítulos 5 y 6) y el tema general de procesos (Capítulo 3), por lo que podría ser
razonable tratarlo inmediatamente después de estos temas.
La dificultad reside en que los diversos temas están estrechamente interrelacionados. Por ejemplo, para tratar la memoria virtual, es útil hacer referencia a los aspectos de planificación relacionados con un fallo de página. Por otro lado, también es útil referirse a algunos aspectos de gestión de
memoria cuando se estudian decisiones de planificación. Este tipo de ejemplo se puede repetir indefinidamente: El estudio de la planificación requiere algunos conocimientos de la gestión de E/S
y viceversa.
La Figura 0.1 sugiere algunas relaciones importantes entre los temas. Las líneas continuas indican relaciones muy estrechas, desde el punto de vista de las decisiones de diseño y de implementación. Basados en este diagrama, es razonable comenzar con una discusión básica de procesos, que corresponde con el Capítulo 3. Después de eso, el orden puede ser un poco arbitrario. Muchos tratados
de sistemas operativos reúnen todo el material sobre procesos al principio y después tratan otros te-
Descripción
y control
de procesos
Gestión
de memoria
Planificación
Concurrencia
Gestión de
ficheros
Gestión
de E/S
Redes
Seguridad
Figura 0.1.
Temas de sistemas operativos.
00-Capitulo 0
4
12/5/05
16:15
Página 4
Sistemas operativos. Aspectos internos y principios de diseño
mas. Esto es ciertamente válido. Sin embargo, la importancia fundamental de la gestión de memoria,
que en opinión del autor es tan importante como la gestión de procesos, ha llevado a la decisión de
presentar este tema antes de profundizar en la planificación.
La solución ideal es que el estudiante, después de completar los Capítulos del 1 al 3 en ese orden,
lea y asimile los capítulos siguientes en paralelo: el 4 seguido (opcionalmente) del 5; el 6 seguido por
el 7; el 8 seguido (opcionalmente) del 9; y el 10. Por último, se pueden estudiar los siguientes capítulos en cualquier orden: el 11; el 12 seguido del 13; el 14; y el 15. Sin embargo, aunque el cerebro humano puede llevar a cabo un procesamiento paralelo, al estudiante le resulta imposible (y caro) trabajar con éxito simultáneamente con cuatro copias del mismo libro abiertas en cuatro capítulos
diferentes. Dada la necesidad de un orden lineal, el autor considera que el orden utilizado en este libro es el más efectivo.
0.3. RECURSOS EN INTERNET Y EN LA WEB
Hay diversos recursos disponibles en Internet y en la Web para apoyar a este libro y ayudar al lector a
mantenerse al día con los avances en este campo.
SITIOS WEB DE ESTE LIBRO
Se ha creado una página web especial para este libro en WilliamStallings.com/OS/OS5e.html. Consulte el diagrama de dos páginas al principio de este libro para obtener una descripción detallada de este sitio web. De especial interés son los dos documentos disponibles en el sitio web para el estudiante:
• Pseudo-código. Para los lectores no acostumbrados al lenguaje C, se reproducen todos los algoritmos también en un pseudo-código similar al Pascal. Este lenguaje de pseudo-código es
intuitivo y particularmente fácil de seguir.
• Descripciones de Windows, UNIX y Linux. Como se ha mencionado previamente, se utilizan Windows y diversas versiones de UNIX como ejemplos de casos reales, estando este estudio distribuido a través del texto en vez de agrupado en un único capítulo o apéndice. Algunos
lectores preferirían tener todo este material en un único sitio para usarlo como referencia. Por
tanto, todo el material de Windows y UNIX del libro se reproduce en tres documentos en el sitio web.
En cuanto se detecte cualquier error tipográfico o de otro tipo, se publicará una lista de erratas de
este libro en el sitio web. Por favor, informe de cualquier error que detecte en el libro. En William
Stallings.com se encuentran las hojas de erratas de los otros libros publicados por el autor, así como
información sobre descuentos en pedidos de libros.
También se mantiene un sitio con recursos para el estudiante de informática (Computer Science
Student Resource Site), en WilliamStallings.com/StudentSupport.html; el objetivo de este sitio es
proporcionar documentos, información y enlaces para estudiantes de informática. Los enlaces se organizan en cuatro categorías:
• Matemáticas. Incluye un repaso sobre matemáticas básicas, una introducción al análisis de
colas y a los sistemas númericos, así como enlaces a numerosos sitios con información sobre
matemáticas.
• How-to. Aconseja y guía al estudiante para resolver sus ejercicios, escribir informes técnicos y
preparar presentaciones técnicas.
00-Capitulo 0
12/5/05
16:15
Página 5
Guía del lector
5
• Recursos de investigación. Proporciona enlaces a recopilaciones importantes de artículos, informes técnicos y referencias bibliográficas.
• Misceláneos. Incluye diversos documentos y enlaces útiles.
OTROS SITIOS WEB
Hay numerosos sitios web que proporcionan información relacionada con los temas tratados en este
libro. En los siguientes capítulos, pueden encontrarse referencias a sitios web específicos en la Sección «Lecturas recomendadas». Debido a que el URL de un sitio web particular puede cambiar, este
libro no incluye direcciones URL. En el sitio web de este libro puede encontrarse el enlace apropiado
de todos los sitios web nombrados en el libro.
GRUPOS DE NOTICIAS DE USENET
Diversos grupos de noticias de USENET se dedican a algún tema relacionado con los sistemas operativos o con un determinado sistema operativo. Como ocurre prácticamente con todos los grupos de
USENET, hay un alto porcentaje de ruido en la señal, pero es un experimento valioso comprobar si
alguno satisface las necesidades del lector. Los más relevantes son los siguientes:
• comp.os.research. El grupo que más interesa seguir. Se trata de un grupo de noticias moderado que se dedica a temas de investigación.
• comp.os.misc. Un foro de discusión general sobre temas de sistemas operativos.
• comp.unix.internals
• comp.os.linux.development.system
00-Capitulo 0
12/5/05
16:15
Página 6
01-Capitulo 1
16/5/05
17:03
Página 7
PA RT E
I
ANTECEDENTES
E
n esta primera parte se proporcionan los antecedentes necesarios y se establece el contexto
para el resto de este libro, presentando los conceptos fundamentales sobre arquitectura de computadores y sobre los aspectos internos de los sistemas operativos.
GUÍA DE LA PRIMERA PARTE
CAPÍTULO 1. INTRODUCCIÓN A LOS COMPUTADORES
Un sistema operativo hace de intermediario entre, por un lado, los programas de aplicación, las herramientas y los usuarios, y, por otro, el hardware del computador. Para apreciar cómo funciona el sistema operativo y los aspectos de diseño involucrados, se debe tener algún conocimiento de la organización y la arquitectura de los computadores. El Capítulo 1 proporciona un breve estudio del
procesador, la memoria y los elementos de E/S de un computador.
CAPÍTULO 2. INTRODUCCIÓN A LOS SISTEMAS OPERATIVOS
El tema del diseño de un sistema operativo (S.O.) abarca un enorme campo, resultando fácil enredarse en los detalles, perdiendo el contexto general durante el estudio de un tema en particular. El Capítulo 2 proporciona una visión general a la que el lector puede volver en cualquier punto del libro para
recuperar el contexto global. Se comienza con una exposición de los objetivos y funciones del sistema operativo. A continuación, por su relevancia histórica, se describen algunos sistemas y funciones
del S.O. Este estudio permite presentar algunos principios de diseño del S.O. fundamentales en un
entorno sencillo, de manera que queden claras las relaciones entre varias funciones del S.O. A continuación, el capítulo resalta las características más importantes de los sistemas operativos modernos.
A lo largo de este libro, cuando se presentan diversos temas, es necesario hablar tanto de principios
fundamentales y bien consolidados como de las más recientes innovaciones en el diseño de SS.OO.
El análisis de este capítulo hace notar al lector que se debe de abordar esta mezcla de técnicas de diseño ya consolidadas con otras recientes. Finalmente, se presenta una introducción de Windows y
UNIX; este estudio establece la arquitectura general de estos sistemas, proporcionando un contexto
para las discusiones detalladas que se realizan más adelante.
01-Capitulo 1
16/5/05
17:03
Página 8
01-Capitulo 1
16/5/05
17:03
Página 9
CAPÍTULO
1
Introducción a
los computadores
1.1.
Elementos básicos
1.2.
Registros del procesador
1.3.
Ejecución de instrucciones
1.4.
Interrupciones
1.5.
La jerarquía de memoria
1.6.
Memoria cache
1.7.
Técnicas de comunicación de E/S
1.8.
Lecturas y sitios web recomendados
1.9.
Términos clave, cuestiones de repaso y problemas
Apéndice 1A Características de rendimiento de las memorias de dos niveles
Apéndice 1B Control de procedimientos
01-Capitulo 1
10
16/5/05
17:03
Página 10
Sistemas operativos. Aspectos internos y principios de diseño
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
Un sistema operativo explota los recursos hardware de uno o más procesadores para proporcionar
un conjunto de servicios a los usuarios del sistema. El sistema operativo también gestiona la memoria secundaria y los dispositivos de E/S (entrada/salida) para sus usuarios. Por tanto, es importante
tener algunos conocimientos del hardware del computador subyacente antes de iniciar el estudio de
los sistemas operativos.
Este capítulo proporciona una visión general del hardware del computador. En la mayoría de las
áreas, el estudio es breve, asumiendo que el lector ya está familiarizado con este tema. Sin embargo, se estudiarán con cierto detalle varios aspectos por su repercusión en los temas tratados más
adelante en el libro.
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
1.1. ELEMENTOS BÁSICOS
A
l más alto nivel, un computador consta del procesador, la memoria y los componentes de E/S,
incluyendo uno o más módulos de cada tipo. Estos componentes se interconectan de manera
que se pueda lograr la función principal del computador, que es ejecutar programas. Por tanto,
hay cuatro elementos estructurales principales:
• Procesador. Controla el funcionamiento del computador y realiza sus funciones de procesamiento de datos. Cuando sólo hay un procesador, se denomina usualmente unidad central de
proceso (Central Processing Unit, CPU).
• Memoria principal. Almacena datos y programas. Esta memoria es habitualmente volátil; es
decir, cuando se apaga el computador, se pierde su contenido. En contraste, el contenido de la
memoria del disco se mantiene incluso cuando se apaga el computador. A la memoria principal se le denomina también memoria real o memoria primaria.
• Módulos de E/S. Transfieren los datos entre el computador y su entorno externo. El entorno
externo está formado por diversos dispositivos, incluyendo dispositivos de memoria secundaria (por ejemplo, discos), equipos de comunicaciones y terminales.
• Bus del sistema. Proporciona comunicación entre los procesadores, la memoria principal y los
módulos de E/S.
La Figura 1.1 muestra estos componentes de más alto nivel. Una de las funciones del procesador
es el intercambio de datos con la memoria. Para este fin, se utilizan normalmente dos registros internos (al procesador): un registro de dirección de memoria (RDIM), que especifica la dirección de
memoria de la siguiente lectura o escritura; y un registro de datos de memoria (RDAM), que contiene los datos que se van a escribir en la memoria o que recibe los datos leídos de la memoria. De manera similar, un registro de dirección de E/S (RDIE/S) especifica un determinado dispositivo de E/S,
y un registro de datos de E/S (RDAE/S) permite el intercambio de datos entre un módulo de E/S y el
procesador.
Un módulo de memoria consta de un conjunto de posiciones definidas mediante direcciones numeradas secuencialmente. Cada posición contiene un patrón de bits que se puede interpretar como
una instrucción o como datos. Un módulo de E/S transfiere datos desde los dispositivos externos hacia el procesador y la memoria, y viceversa. Contiene buffers (es decir, zonas de almacenamiento internas) que mantienen temporalmente los datos hasta que se puedan enviar.
01-Capitulo 1
16/5/05
17:03
Página 11
Introducción a los computadores
CPU
11
Memoria principal
PC
RDIM
IR
RDAM
0
1
2
Bus del
sistema
Instrucción
Instrucción
Instrucción
RDI E/S
Unidad de
ejecución
RDA E/S
Datos
Datos
Datos
Datos
Módulo de E/S
Buffers
Figura 1.1.
n2
n1
PC Contador de programa
IR Registro de instrucción
RDIM Registro de dirección de memoria
RDAM Registro de datos de memoria
RDI E/S Registro de dirección de entrada/salida
RDA E/S Registro de datos de entrada/salida
Componentes de un computador: visión al más alto nivel.
1.2. REGISTROS DEL PROCESADOR
Un procesador incluye un conjunto de registros que proporcionan un tipo de memoria que es más rápida y de menor capacidad que la memoria principal. Los registros del procesador sirven para dos
funciones:
• Registros visibles para el usuario. Permiten al programador en lenguaje máquina o en ensamblador minimizar las referencias a memoria principal optimizando el uso de registros. Para
lenguajes de alto nivel, un compilador que realice optimización intentará tomar decisiones inteligentes sobre qué variables se asignan a registros y cuáles a posiciones de memoria principal. Algunos lenguajes de alto nivel, tales como C, permiten al programador sugerir al compilador qué variables deberían almacenarse en registros.
• Registros de control y estado. Usados por el procesador para controlar su operación y por rutinas privilegiadas del sistema operativo para controlar la ejecución de programas.
No hay una clasificación nítida de los registros entre estas dos categorías. Por ejemplo, en algunas máquinas el contador de programa es visible para el usuario, pero en muchas otras no lo es. Sin
embargo, para el estudio que se presenta a continuación, es conveniente utilizar estas categorías.
01-Capitulo 1
12
16/5/05
17:03
Página 12
Sistemas operativos. Aspectos internos y principios de diseño
REGISTROS VISIBLES PARA EL USUARIO
A un registro visible para el usuario se puede acceder por medio del lenguaje de máquina ejecutado
por el procesador que está generalmente disponible para todos los programas, incluyendo tanto programas de aplicación como programas de sistema. Los tipos de registros que están normalmente disponibles son registros de datos, de dirección y de códigos de condición.
El programador puede utilizar los registros de datos para diversas funciones. En algunos casos, son, en esencia, de propósito general y pueden usarse con cualquier instrucción de máquina
que realice operaciones sobre datos. Sin embargo, frecuentemente, hay restricciones. Por ejemplo, puede haber registros dedicados a operaciones de coma flotante y otros a operaciones con
enteros.
Los registros de dirección contienen direcciones de memoria principal de datos e instrucciones, o una parte de la dirección que se utiliza en el cálculo de la dirección efectiva o completa.
Estos registros pueden ser en sí mismos de propósito general, o pueden estar dedicados a una forma, o modo, particular de direccionamiento de memoria. A continuación, se incluyen algunos
ejemplos:
• Registro índice. El direccionamiento indexado es un modo común de direccionamiento que
implica sumar un índice a un valor de base para obtener una dirección efectiva.
• Puntero de segmento. Con direccionamiento segmentado, la memoria se divide en segmentos, que son bloques de palabras1 de longitud variable. Una referencia de memoria
consta de una referencia a un determinado segmento y un desplazamiento dentro del segmento; este modo de direccionamiento es importante en el estudio de la gestión de memoria
que se realizará en el Capítulo 7. En este modo de direccionamiento, se utiliza un registro
para mantener la dirección base (posición de inicio) del segmento. Puede haber múltiples
registros; por ejemplo, uno para el sistema operativo (es decir, cuando el código del sistema
operativo se está ejecutando en el procesador) y otro para la aplicación que se está ejecutando actualmente.
• Puntero de pila. Si hay direccionamiento de pila2 visible para el usuario, hay un registro dedicado que apunta a la cima de la pila. Esto permite el uso de instrucciones que no contienen
campo de dirección, tales como las que permiten apilar (push) y extraer (pop).
En algunas máquinas, una llamada a una subrutina o a un procedimiento implica salvar automáticamente todos los registros visibles para el usuario, que se restaurarán al retornar. El procesador realiza estas operaciones de salvar y restaurar como parte de la ejecución de las instrucciones de llamada
y de retorno. Esto permite que cada procedimiento use estos registros independientemente. En otras
máquinas, el programador es el responsable de guardar el contenido de los registros visibles para el
usuario antes de una llamada a un procedimiento, incluyendo instrucciones para ello en el programa.
Por tanto, las funciones de salvar y restaurar se pueden realizar en hardware o en software, dependiendo del procesador.
1
No hay una definición universal del término palabra. En general, una palabra es un conjunto ordenado de bytes o bits que es
la unidad normal con la que se almacena, transmite, u opera la información dentro de un determinado computador. Normalmente, si
un computador tiene un juego de instrucciones de longitud fija, la longitud de las instrucciones es igual a la de la palabra.
2
Una pila se almacena en la memoria principal y es un conjunto secuencial de posiciones a las que se hace referencia de manera similar a como ocurre con una pila física de papeles, insertando y extrayendo elementos de la cima de la misma. Véase el Apéndice 1B donde se incluye una explicación sobre la gestión de la pila.
01-Capitulo 1
16/5/05
17:03
Página 13
Introducción a los computadores
13
REGISTROS DE CONTROL Y ESTADO
Se emplean varios registros del procesador para controlar el funcionamiento del mismo. En la mayoría de las máquinas, muchos de ellos no son visibles para el usuario. A algunos de ellos se puede acceder mediante instrucciones de máquina ejecutadas en lo que se denomina modo de control o de sistema operativo.
Por supuesto, diferentes máquinas tendrán distintas organizaciones de registros y utilizarán diferente terminología. A continuación, se proporcionará una lista razonablemente completa de tipos de
registros, con una breve descripción de cada uno de ellos. Además de los registros RDIRM, RDAM,
RDIE/S y RDAE/S mencionados anteriormente (Figura 1.1), los siguientes son esenciales para la ejecución de instrucciones:
• Contador de programa (Program Counter, PC). Contiene la dirección de la próxima instrucción que se leerá de la memoria.
• Registro de instrucción (Instruction Register, IR). Contiene la última instrucción leída.
Todos los diseños de procesador incluyen también un registro, o conjunto de registros, conocido
usualmente como la palabra de estado del programa (Program Status Word, PSW), que contiene información de estado. La PSW contiene normalmente códigos de condición, además de otra información de estado, tales como un bit para habilitar/inhabilitar las interrupciones y un bit de modo usuario/supervisor.
Los códigos de condición (también llamados indicadores) son bits cuyo valor lo asigna normalmente el hardware de procesador teniendo en cuenta el resultado de las operaciones. Por ejemplo, una operación aritmética puede producir un resultado positivo, negativo, cero o desbordamiento. Además de almacenarse el resultado en sí mismo en un registro o en la memoria, se fija también
un código de condición en concordancia con el resultado de la ejecución de la instrucción aritmética. Posteriormente, se puede comprobar el código de condición como parte de una operación de
salto condicional. Los bits de código de condición se agrupan en uno o más registros. Normalmente, forman parte de un registro de control. Generalmente, las instrucciones de máquina permiten
que estos bits se lean mediante una referencia implícita, pero no pueden ser alterados por una referencia explícita debido a que están destinados a la realimentación del resultado de la ejecución de
una instrucción.
En máquinas que utilizan múltiples tipos de interrupciones, se puede proporcionar un conjunto de
registros de interrupciones, con un puntero a cada rutina de tratamiento de interrupción. Si se utiliza
una pila para implementar ciertas funciones (por ejemplo llamadas a procedimientos), se necesita un
puntero de pila de sistema (véase el Apéndice 1B). El hardware de gestión de memoria, estudiado en
el Capítulo 7, requiere registros dedicados. Asimismo, se pueden utilizar registros en el control de las
operaciones de E/S.
En el diseño de la organización del registro de control y estado influyen varios factores. Un aspecto fundamental es proporcionar apoyo al sistema operativo. Ciertos tipos de información de control son útiles específicamente para el sistema operativo. Si el diseñador del procesador tiene un conocimiento funcional del sistema operativo que se va a utilizar, se puede diseñar la organización de
registros de manera que se proporcione soporte por parte del hardware de características particulares
de ese sistema operativo, en aspectos tales como la protección de memoria y la multiplexación entre
programas de usuario.
Otra decisión de diseño fundamental es el reparto de la información de control entre los registros
y la memoria. Es habitual dedicar las primeras (las de direcciones más bajas) cientos o miles de palabras de memoria para propósitos de control. El diseñador debe decidir cuánta información de control
01-Capitulo 1
14
16/5/05
17:03
Página 14
Sistemas operativos. Aspectos internos y principios de diseño
debería estar en registros, más rápidos y más caros, y cuánta en la memoria principal, menos rápida y
más económica.
1.3. EJECUCIÓN DE INSTRUCCIONES
Un programa que va a ejecutarse en un procesador consta de un conjunto de instrucciones almacenado en memoria. En su forma más simple, el procesamiento de una instrucción consta de dos pasos: el procesador lee (busca) instrucciones de la memoria, una cada vez, y ejecuta cada una de
ellas. La ejecución del programa consiste en repetir el proceso de búsqueda y ejecución de instrucciones. La ejecución de la instrucción puede involucrar varias operaciones dependiendo de la naturaleza de la misma.
Se denomina ciclo de instrucción al procesamiento requerido por una única instrucción. En la Figura 1.2 se describe el ciclo de instrucción utilizando la descripción simplificada de dos pasos. A estos dos pasos se les denomina fase de búsqueda y de ejecución. La ejecución del programa se detiene
sólo si se apaga la máquina, se produce algún tipo de error irrecuperable o se ejecuta una instrucción
del programa que para el procesador.
BÚSQUEDA Y EJECUCIÓN DE UNA INSTRUCCIÓN
Al principio de cada ciclo de instrucción, el procesador lee una instrucción de la memoria. En un procesador típico, el contador del programa (PC) almacena la dirección de la siguiente instrucción que se
va a leer. A menos que se le indique otra cosa, el procesador siempre incrementa el PC después de
cada instrucción ejecutada, de manera que se leerá la siguiente instrucción en orden secuencial (es decir, la instrucción situada en la siguiente dirección de memoria más alta). Considere, por ejemplo, un
computador simplificado en el que cada instrucción ocupa una palabra de memoria de 16 bits. Suponga que el contador del programa está situado en la posición 300. El procesador leerá la siguiente instrucción de la posición 300. En sucesivos ciclos de instrucción completados satisfactoriamente, se
leerán instrucciones de las posiciones 301, 302, 303, y así sucesivamente. Esta secuencia se puede alterar, como se explicará posteriormente.
La instrucción leída se carga dentro de un registro del procesador conocido como registro de instrucción (IR). La instrucción contiene bits que especifican la acción que debe realizar el procesador.
El procesador interpreta la instrucción y lleva a cabo la acción requerida. En general, estas acciones
se dividen en cuatro categorías:
• Procesador-memoria. Se pueden transferir datos desde el procesador a la memoria o viceversa.
• Procesador-E/S. Se pueden enviar datos a un dispositivo periférico o recibirlos desde el mismo, transfiriéndolos entre el procesador y un módulo de E/S.
Inicio
Fase de búsqueda
Fase de ejecución
Busca la
siguiente
instrucción
Ejecuta la
instrucción
Figura 1.2.
Ciclo de instrucción básico.
Parada
01-Capitulo 1
16/5/05
17:03
Página 15
Introducción a los computadores
15
• Procesamiento de datos. El procesador puede realizar algunas operaciones aritméticas o lógicas sobre los datos.
• Control. Una instrucción puede especificar que se va a alterar la secuencia de ejecución. Por
ejemplo, el procesador puede leer una instrucción de la posición 149, que especifica que la siguiente instrucción estará en la posición 182. El procesador almacenará en el contador del programa un valor de 182. Como consecuencia, en la siguiente fase de búsqueda, se leerá la instrucción de la posición 182 en vez de la 150.
Una ejecución de una instrucción puede involucrar una combinación de estas acciones.
Considere un ejemplo sencillo utilizando una máquina hipotética que incluye las características
mostradas en la Figura 1.3. El procesador contiene un único registro de datos, llamado el acumulador
(AC). Tanto las instrucciones como los datos tienen una longitud de 16 bits, estando la memoria organizada como una secuencia de palabras de 16 bits. El formato de la instrucción proporciona 4 bits para el
código de operación, permitiendo hasta 24 = 16 códigos de operación diferentes (representados por un
único dígito hexadecimal3). Con los 12 bits restantes del formato de la instrucción, se pueden direccionar directamente hasta 212 = 4.096 (4K) palabras de memoria (denotadas por tres dígitos hexadecimales).
La Figura 1.4 ilustra una ejecución parcial de un programa, mostrando las partes relevantes de la
memoria y de los registros del procesador. El fragmento de programa mostrado suma el contenido de
la palabra de memoria en la dirección 940 al de la palabra de memoria en la dirección 941, almacenando el resultado en esta última posición. Se requieren tres instrucciones, que corresponden a tres
fases de búsqueda y de ejecución, como se describe a continuación:
0
3 4
15
Código-de-op
Dirección
(a) Formato de instrucción
0
15
1
S
Magnitud
(b) Formato de un entero
Contador de programa (PC) = Dirección de la instrucción
Registro de instrucción (IR) = Instrucción que se está ejecutando
Acumulador (AC) = Almacenamiento temporal
(c) Registros internos de la CPU
0001 = Carga AC desde la memoria
0010 = Almacena AC en memoria
0101 = Suma a AC de la memoria
(d) Lista parcial de códigos-de-op
Figura 1.3.
Características de una máquina hipotética.
3
Puede encontrar un repaso básico de los sistema numéricos (decimal, binario y hexadecimal) en el Computer Science Student
Resource Site en WilliamStallings.com/StudentSupport.html.
01-Capitulo 1
16
16/5/05
17:03
Página 16
Sistemas operativos. Aspectos internos y principios de diseño
Fase de búsqueda
Fase de ejecución
Memoria
Registros de la CPU
Memoria
Registros de la CPU
300 1 9 4 0
300 1 9 4 0
3 0 0 PC
3 0 1 PC
301 5 9 4 1
AC 301 5 9 4 1
0 0 0 3 AC
302 2 9 4 1
1 9 4 0 IR 302 2 9 4 1
1 9 4 0 IR
940 0 0 0 3
941 0 0 0 2
940 0 0 0 3
941 0 0 0 2
Paso 1
Paso 2
Memoria
Memoria
Registros de la CPU
Registros de la CPU
300 1 9 4 0
300 1 9 4 0
3 0 1 PC
3 0 2 PC
301 5 9 4 1
0 0 0 3 AC 301 5 9 4 1
0 0 0 5 AC
302 2 9 4 1
5 9 4 1 IR 302 2 9 4 1
5 9 4 1 IR
940 0 0 0 3
941 0 0 0 2
940 0 0 0 3
941 0 0 0 2
Paso 3
Paso 4
3+2=5
Memoria
Memoria
Registros de la CPU
Registros de la CPU
300 1 9 4 0
300 1 9 4 0
3 0 2 PC
3 0 3 PC
301 5 9 4 1
0 0 0 5 AC 301 5 9 4 1
0 0 0 5 AC
302 2 9 4 1
2 9 4 1 IR 302 2 9 4 1
2 9 4 1 IR
940 0 0 0 3
941 0 0 0 2
940 0 0 0 3
941 0 0 0 5
Paso 5
Paso 6
Figura 1.4. Ejemplo de ejecución de un programa
(contenido de la memoria y los registros en hexadecimal).
1. El PC contiene el valor 300, la dirección de la primera instrucción. Esta instrucción (el valor 1940 en hexadecimal) se carga dentro del registro de instrucción IR y se incrementa el
PC. Nótese que este proceso involucra el uso del registro de dirección de memoria (RDIM)
y el registro de datos de memoria (RDAM). Para simplificar, no se muestran estos registros
intermedios.
2. Los primeros 4 bits (primer dígito hexadecimal) en el IR indican que en el AC se va a cargar
un valor leído de la memoria. Los restantes 12 bits (tres dígitos hexadecimales) especifican la
dirección de memoria, que es 940.
3. Se lee la siguiente instrucción (5941) de la posición 301 y se incrementa el PC.
4. El contenido previo del AC y el contenido de la posición 941 se suman y el resultado se almacena en el AC.
5. Se lee la siguiente instrucción (2941) de la posición 302 y se incrementa el PC.
6. Se almacena el contenido del AC en la posición 941.
En este ejemplo, se necesitan tres ciclos de instrucción, de tal forma que cada uno consta de una
fase de búsqueda y una fase de ejecución, para sumar el contenido de la posición 940 al contenido de
la 941. Con un juego de instrucciones más complejo, se necesitarían menos ciclos de instrucción. La
mayoría de los procesadores modernos incluyen instrucciones que contienen más de una dirección.
Por tanto, la fase de ejecución de una determinada instrucción puede involucrar más de una referencia a memoria. Asimismo, en vez de referencias a memoria, una instrucción puede especificar una
operación de E/S.
01-Capitulo 1
16/5/05
17:03
Página 17
Introducción a los computadores
17
SISTEMA DE E/S
Se pueden intercambiar datos directamente entre un módulo de E/S (por ejemplo, un controlador de
disco) y el procesador. Al igual que el procesador puede iniciar una lectura o una escritura en memoria, especificando la dirección de una posición de memoria, también puede leer o escribir datos en un
módulo de E/S. En este caso, el procesador identifica un dispositivo específico que está controlado por
un determinado módulo de E/S. Por tanto, podría producirse una secuencia de instrucciones similar a
la de la Figura 1.4, con instrucciones de E/S en vez de instrucciones que hacen referencia a memoria.
En algunos casos, es deseable permitir que los intercambios de E/S se produzcan directamente
con la memoria para liberar al procesador de la tarea de E/S. En tales casos, el procesador concede a
un módulo de E/S la autorización para leer o escribir de la memoria, de manera que la transferencia
entre memoria y E/S puede llevarse a cabo sin implicar al procesador. Durante dicha transferencia, el
módulo de E/S emite mandatos de lectura y escritura a la memoria, liberando al procesador de la responsabilidad del intercambio. Esta operación, conocida como acceso directo a memoria (Direct Memory Access, DMA) se examinará al final de este capítulo.
1.4. INTERRUPCIONES
Prácticamente todos los computadores proporcionan un mecanismo por el cual otros módulos (memoria y E/S) pueden interrumpir el secuenciamiento normal del procesador. La Tabla 1.1 detalla los
tipos más comunes de interrupciones.
Básicamente, las interrupciones constituyen una manera de mejorar la utilización del procesador.
Por ejemplo, la mayoría de los dispositivos de E/S son mucho más lentos que el procesador. Supóngase que el procesador está transfiriendo datos a una impresora utilizando el esquema de ciclo de instrucción de la Figura 1.2. Después de cada instrucción de escritura, el procesador debe parar y permanecer inactivo hasta que la impresora la lleve a cabo. La longitud de esta pausa puede ser del orden de
muchos miles o incluso millones de ciclos de instrucción. Claramente, es un enorme desperdicio de la
capacidad del procesador.
Tabla 1.1.
Clases de interrupciones.
De programa
Generada por alguna condición que se produce como resultado de la ejecución de una instrucción, tales como un desbordamiento aritmético, una división por cero, un intento de ejecutar una instrucción de máquina ilegal, y
las referencias fuera del espacio de la memoria permitido para un usuario.
Por temporizador
Generada por un temporizador del procesador. Permite al sistema operativo realizar ciertas funciones de forma regular.
De E/S
Generada por un controlador de E/S para señalar la conclusión normal de
una operación o para indicar diversas condiciones de error.
Por fallo del hardware
Generada por un fallo, como un fallo en el suministro de energía o un
error de paridad en la memoria.
Para dar un ejemplo concreto, considere un computador personal que operase a 1GHz, lo que le
permitiría ejecutar aproximadamente 109 instrucciones por segundo4. Un típico disco duro tiene una
4
Una discussion de los usos de prefijos numéricos, tales como giga y tera, está disponible en un documento de apoyo en el
Computer Science Student Resource Site en WilliamStallings.com/StudentSupport.html.
01-Capitulo 1
18
16/5/05
17:03
Página 18
Sistemas operativos. Aspectos internos y principios de diseño
velocidad de rotación de 7200 revoluciones por minuto, que corresponde con un tiempo de rotación
de media pista de 4 ms., que es 4 millones de veces más lento que el procesador.
La Figura 1.5a muestra esta cuestión. El programa de usuario realiza una serie de llamadas de
ESCRITURA intercaladas con el procesamiento. Los segmentos de código 1, 2 y 3 se refieren a secuencias de instrucciones que no involucran E/S. Las llamadas de ESCRITURA invocan a una rutina
de E/S que es una utilidad del sistema que realizará la operación real de E/S. El programa de E/S
consta de tres secciones:
• Una secuencia de instrucciones, etiquetada como 4 en la figura, para preparar la operación real
de E/S. Esto puede incluir copiar los datos de salida en un buffer especial y preparar los parámetros de un mandato para el dispositivo.
• El mandato real de E/S. Sin el uso de interrupciones, una vez que se emite este mandato, el
programa debe esperar a que el dispositivo de E/S realice la función solicitada (o comprobar
periódicamente el estado, o muestrear, el dispositivo de E/S). El programa podría esperar simplemente realizando repetidamente una operación de comprobación para determinar si se ha
realizado la operación de E/S.
• Una secuencia de instrucciones, etiquetada como 5 en la figura, para completar la operación.
Esto puede incluir establecer un valor que indique el éxito o el fallo de la operación.
Debido a que la operación de E/S puede tardar un tiempo relativamente largo hasta que se
completa, el programa de E/S se queda colgado esperando que se complete; por ello, el programa
de usuario se detiene en el momento de la llamada de ESCRITURA durante un periodo de tiempo
considerable.
Programa de
usuario
Programa Programa de
de E/S
usuario
4
1
Mandato
de E/S
ESCRITURA
1
ESCRITURA
Programa Programa de
de E/S
usuario
4
Mandato
de E/S
1
ESCRITURA
Programa
de E/S
4
Mandato
de E/S
5
2a
FIN
2
2
2b
ESCRITURA
ESCRITURA
3a
3
Manejador de
interrupción
Manejador de
interrupción
5
ESCRITURA
FIN
5
FIN
3
3b
ESCRITURA
ESCRITURA
(a) Sin interrupciones
Figura 1.5.
ESCRITURA
(b) Interrupciones; espera de E/S breve (c) Interrupciones; espera de E/S larga
Flujo de programa del control sin interrupciones y con ellas.
01-Capitulo 1
16/5/05
17:03
Página 19
Introducción a los computadores
19
INTERRUPCIONES Y EL CICLO DE INSTRUCCIÓN
Gracias a las interrupciones, el procesador puede dedicarse a ejecutar otras instrucciones mientras
que la operación de E/S se está llevando a cabo. Considere el flujo de control mostrado en la Figura
1.5b. Como anteriormente, el programa de usuario alcanza un punto en el que hace una llamada al
sistema que consiste en una llamada de ESCRITURA. El programa de E/S que se invoca en este caso
consta sólo del código de preparación y el mandato real de E/S. Después de que se ejecuten estas pocas instrucciones, se devuelve el control al programa de usuario. Mientras tanto, el dispositivo externo está ocupado aceptando datos de la memoria del computador e imprimiéndolos. La operación de
E/S se lleva a cabo de forma concurrente con la ejecución de instrucciones en el programa de usuario.
Cuando el dispositivo externo está listo para ser atendido, es decir, cuando está preparado para
aceptar más datos del procesador, el módulo de E/S de este dispositivo externo manda una señal de
petición de interrupción al procesador. El procesador responde suspendiendo la ejecución del programa actual, saltando a la rutina de servicio específica de este dispositivo de E/S, conocida como manejador de interrupción, y reanudando la ejecución original después de haber atendido al dispositivo. En
la Figura 1.5b se indican con una X los puntos en los que se produce cada interrupción. Téngase en
cuenta que se puede producir una interrupción en cualquier punto de la ejecución del programa principal, no sólo en una determinada instrucción.
De cara al programa de usuario, una interrupción suspende la secuencia normal de ejecución.
Cuando se completa el procesamiento de la interrupción, se reanuda la ejecución (Figura 1.6). Por
tanto, el programa de usuario no tiene que contener ningún código especial para tratar las interrupciones; el procesador y el sistema operativo son responsables de suspender el programa de usuario y,
posteriormente, reanudarlo en el mismo punto.
Para tratar las interrupciones, se añade una fase de interrupción al ciclo de instrucción, como se
muestra en la Figura 1.7 (compárese con la Figura 1.2). En la fase de interrupción, el procesador
comprueba si se ha producido cualquier interrupción, hecho indicado por la presencia de una señal de
interrupción. Si no hay interrupciones pendientes, el procesador continúa con la fase de búsqueda y
lee la siguiente instrucción del programa actual. Si está pendiente una interrupción, el procesador suspende la ejecución del programa actual y ejecuta la rutina del manejador de interrupción. La rutina
del manejador de interrupción es generalmente parte del sistema operativo. Normalmente, esta rutina
Programa de usuario
Manejador de interrupción
1
2
i
La interrupción
se produce
en este punto
i1
M
Figura 1.6.
Transferencia de control mediante interrupciones.
01-Capitulo 1
20
16/5/05
17:03
Página 20
Sistemas operativos. Aspectos internos y principios de diseño
Fase de búsqueda
Fase de ejecución
Fase de interrupción
Interrupciones
inhabilitadas
Inicio
Búsqueda de
la siguiente
instrucción
Ejecuta la
instrucción
Comprueba si hay
una interrupción;
inicia el manejador
Interrupciones de interrupción
habilitadas
Parada
Figura 1.7.
Ciclo de instrucción con interrupciones.
determina la naturaleza de la interrupción y realiza las acciones que se requieran. En el ejemplo que
se está usando, el manejador determina qué módulo de E/S generó la interrupción y puede dar paso a
un programa que escriba más datos en ese módulo de E/S. Cuando se completa la rutina del manejador de interrupción, el procesador puede reanudar la ejecución del programa de usuario en el punto
de la interrupción.
Es evidente que este proceso implica cierta sobrecarga. Deben ejecutarse instrucciones adicionales (en el manejador de interrupción) para determinar la naturaleza de la interrupción y decidir sobre
la acción apropiada. Sin embargo, debido a la cantidad relativamente elevada de tiempo que se gastaría simplemente a la espera de una operación de E/S, el procesador se puede emplear mucho más eficientemente con el uso de interrupciones.
Para apreciar la ganancia en eficiencia, considere la Figura 1.8, que es un diagrama de tiempo basado en el flujo de control de las Figuras 1.5a y 1.5b. Las Figuras 1.5b y 1.8 asumen que el tiempo requerido para la operación de E/S es relativamente corto: inferior al tiempo que tarda en completarse la
ejecución de instrucciones entre las operaciones de escritura del programa de usuario. El caso más típico, especialmente para un dispositivo lento como una impresora, es que la operación de E/S tarde mucho más tiempo que la ejecución de una secuencia de instrucciones de usuario. La Figura 1.5c ilustra
este tipo de situación. En este caso, el programa de usuario alcanza la segunda llamada de ESCRITURA antes de que se complete la operación de E/S generada por la primera llamada. El resultado es que
el programa de usuario se queda colgado en ese punto. Cuando se completa la operación de E/S precedente, se puede procesar la nueva llamada de ESCRITURA y se puede empezar una nueva operación
de E/S. La Figura 1.9 muestra la temporización de esta situación con el uso de interrupciones o sin
ellas. Se puede observar que hay una ganancia en eficiencia debido a que parte del tiempo durante el
que se realiza la operación de E/S se solapa con la ejecución de las instrucciones del usuario.
PROCESAMIENTO DE INTERRUPCIONES
La aparición de una interrupción dispara varios eventos, tanto en el hardware del procesador como en
el software. La Figura 1.10 muestra una secuencia típica. Cuando un dispositivo de E/S completa una
operación de E/S, se produce la siguiente secuencia de eventos en el hardware:
1. El dispositivo genera una señal de interrupción hacia el procesador.
2. El procesador termina la ejecución de la instrucción actual antes de responder a la interrupción, como se indica en la Figura 1.7.
01-Capitulo 1
16/5/05
17:03
Página 21
Introducción a los computadores
21
Tiempo
1
1
4
4
Espera del
procesador
Operación
de E/S
5
2a
Operación
de E/S
5
2b
2
4
4
Espera del
procesador
3a
Operación
de E/S
5
Operación
de E/S
5
3b
(b) Con interrupciones
(los números con un círculo
se refieren a los usados
en la Figura 1.5b)
3
(a) Sin interrupciones
(los números con un círculo
se refieren a los usados
en la Figura 1.5a)
Figura 1.8.
Temporización del programa: espera breve de E/S.
3. El procesador comprueba si hay una petición de interrupción pendiente, determina que hay
una y manda una señal de reconocimiento al dispositivo que produjo la interrupción. Este reconocimiento permite que el dispositivo elimine su señal de interrupción.
4. En ese momento, el procesador necesita prepararse para transferir el control a la rutina de interrupción. Para comenzar, necesita salvar la información requerida para reanudar el programa
actual en el momento de la interrupción. La información mínima requerida es la palabra de estado del programa (PSW) y la posición de la siguiente instrucción que se va a ejecutar, que
está contenida en el contador de programa. Esta información se puede apilar en la pila de control de sistema (véase el Apéndice 1B).
5. A continuación, el procesador carga el contador del programa con la posición del punto de entrada de la rutina de manejo de interrupción que responderá a esta interrupción. Dependiendo
de la arquitectura de computador y del diseño del sistema operativo, puede haber un único
programa, uno por cada tipo de interrupción o uno por cada dispositivo y tipo de interrupción.
Si hay más de una rutina de manejo de interrupción, el procesador debe determinar cuál invocar. Esta información puede estar incluida en la señal de interrupción original o el procesador
01-Capitulo 1
22
16/5/05
17:03
Página 22
Sistemas operativos. Aspectos internos y principios de diseño
Tiempo
1
1
4
4
Espera del
procesador
Operación
de E/S
2
Operación
de E/S
Espera del
procesador
5
5
2
4
4
3
Espera del
procesador
Operación
de E/S
Operación
de E/S
Espera del
procesador
5
5
(b) Con interrupciones
(los números con un círculo
se refieren a los usados
en la Figura 1.5c)
3
(a) Sin interrupciones
(los números con un círculo
se refieren a los usados
en la Figura 1.5a)
Figura 1.9.
Temporización del programa: espera larga de E/S.
puede tener que realizar una petición al dispositivo que generó la interrupción para obtener
una respuesta que contiene la información requerida.
Una vez que se ha cargado el contador del programa, el procesador continúa con el siguiente ciclo de instrucción, que comienza con una lectura de instrucción. Dado que la lectura de la instrucción
está determinada por el contenido del contador del programa, el resultado es que se transfiere el control al programa manejador de interrupción. La ejecución de este programa conlleva las siguientes
operaciones:
6. En este momento, el contador del programa y la PSW vinculados con el programa interrumpido
se han almacenado en la pila del sistema. Sin embargo, hay otra información que se considera
parte del estado del programa en ejecución. En concreto, se necesita salvar el contenido de los
registros del procesador, puesto que estos registros los podría utilizar el manejador de interrup-
01-Capitulo 1
16/5/05
17:03
Página 23
Introducción a los computadores
Hardware
23
Software
El controlador de dispositivo
u otro sistema hardware
genera una interrupción
Salva el resto de la
información de estado
del proceso
El procesador termina
la ejecución de la
instrucción actual
Procesa la interrupción
El procesador indica
el reconocimiento
de la interrupción
Restaura la información
de estado del proceso
El procesador apila
PSW y el PC en la
pila de control
Restaura los antiguos
PSW y PC
El procesador carga
un nuevo valor en el PC
basado en la interrupción
Figura 1.10.
Procesamiento simple de interrupciones.
ciones. Por tanto, se deben salvar todos estos valores, así como cualquier otra información de estado. Generalmente, el manejador de interrupción comenzará salvando el contenido de todos los
registros en la pila. En el Capítulo 3 se estudiará qué otra información de estado debe salvarse.
La Figura 1.11a muestra un ejemplo sencillo. En este caso, un programa de usuario se interrumpe después de la instrucción en la posición N. El contenido de todos los registros, así como la dirección de la siguiente instrucción (N + 1), un total de M palabras, se apilan en la pila de control.
El puntero de pila se actualiza para que señale a la nueva cima de la pila, mientras que el contador de programa quedará apuntando al principio de la rutina de servicio de interrupción.
7. El manejador de interrupción puede en este momento comenzar a procesar la interrupción.
Esto incluirá un examen de la información de estado relacionada con la operación de E/S o
con otro evento distinto que haya causado la interrupción. Asimismo, puede implicar el envío
de mandatos adicionales o reconocimientos al dispositivo de E/S.
8. Cuando se completa el procesamiento de la interrupción, se recuperan los valores de los registros salvados en la pila y se restituyen en los registros (como ejemplo, véase la Figura 1.11b).
9. La última acción consiste en restituir de la pila los valores de la PSW y del contador del programa. Como resultado, la siguiente instrucción que se va ejecutar corresponderá al programa
previamente interrumpido.
Es importante salvar toda la información de estado del programa interrumpido para su posterior
reanudación. Esto se debe a que la interrupción no es una rutina llamada desde el programa. En su lu-
01-Capitulo 1
24
16/5/05
17:03
Página 24
Sistemas operativos. Aspectos internos y principios de diseño
gar, la interrupción puede suceder en cualquier momento y, por tanto, en cualquier punto de la ejecución de un programa de usuario. Su aparición es imprevisible.
MÚLTIPLES INTERRUPCIONES
El estudio realizado hasta el momento ha tratado solamente el caso de que se produzca una única interrupción. Supóngase, sin embargo, que se producen múltiples interrupciones. Por ejemplo, un programa puede estar recibiendo datos de una línea de comunicación e imprimiendo resultados al mismo
tiempo. La impresora generará una interrupción cada vez que completa una operación de impresión.
El controlador de la línea de comunicación generará una interrupción cada vez que llega una unidad
de datos. La unidad podría consistir en un único carácter o en un bloque, dependiendo de la naturaleza del protocolo de comunicaciones. En cualquier caso, es posible que se produzca una interrupción
de comunicación mientras se está procesando una interrupción de la impresora.
Se pueden considerar dos alternativas a la hora de tratar con múltiples interrupciones. La primera
es inhabilitar las interrupciones mientras que se está procesando una interrupción. Una interrupción
inhabilitada significa simplemente que el procesador ignorará cualquier nueva señal de petición de
interrupción. Si se produce una interrupción durante este tiempo, generalmente permanecerá pendiente de ser procesada, de manera que el procesador sólo la comprobará después de que se rehabiliten las
interrupciones. Por tanto, cuando se ejecuta un programa de usuario y se produce una interrupción, se
inhabilitan las interrupciones inmediatamente. Después de que se completa la rutina de manejo de la
interrupción, se rehabilitan las interrupciones antes de reanudar el programa de usuario, y el procesador comprueba si se han producido interrupciones adicionales. Esta estrategia es válida y sencilla,
puesto que las interrupciones se manejan en estricto orden secuencial (Figura 1.12a).
La desventaja de la estrategia anterior es que no tiene en cuenta la prioridad relativa o el grado de
urgencia de las interrupciones. Por ejemplo, cuando llegan datos por la línea de comunicación, se
puede necesitar que se procesen rápidamente de manera que se deje sitio para otros datos que pueden
llegar. Si el primer lote de datos no se ha procesado antes de que llegue el segundo, los datos pueden
perderse porque el buffer del dispositivo de E/S puede llenarse y desbordarse.
Una segunda estrategia es definir prioridades para las interrupciones y permitir que una interrupción de más prioridad cause que se interrumpa la ejecución de un manejador de una interrupción de
menor prioridad (Figura 1.12b). Como ejemplo de esta segunda estrategia, considere un sistema con
tres dispositivos de E/S: una impresora, un disco y una línea de comunicación, con prioridades crecientes de 2, 4 y 5, respectivamente. La Figura 1.13, basada en un ejemplo de [TANE97], muestra una
posible secuencia. Un programa de usuario comienza en t = 0. En t = 10, se produce una interrupción
de impresora; se almacena la información de usuario en la pila del sistema y la ejecución continúa en
la rutina de servicio de interrupción (Interrupt Service Routine, ISR) de la impresora. Mientras todavía
se está ejecutando esta rutina, en t = 15 se produce una interrupción del equipo de comunicaciones.
Debido a que la línea de comunicación tiene una prioridad superior a la de la impresora, se sirve la petición de interrupción. Se interrumpe la ISR de la impresora, se almacena su estado en la pila y la ejecución continúa con la ISR del equipo de comunicaciones. Mientras se está ejecutando esta rutina, se
produce una interrupción del disco (t = 20). Dado que esta interrupción es de menor prioridad, simplemente se queda en espera, y la ISR de la línea de comunicación se ejecuta hasta su conclusión.
Cuando se completa la ISR de la línea de comunicación (t = 25), se restituye el estado previo del
proceso, que corresponde con la ejecución de la ISR de la impresora. Sin embargo, antes incluso de
que pueda ejecutarse una sola instrucción de esta rutina, el procesador atiende la interrupción de disco de mayor prioridad y transfiere el control a la ISR del disco. Sólo cuando se completa esa rutina
(t = 35), se reanuda la ISR de la impresora. Cuando esta última rutina se completa (t = 40), se devuelve finalmente el control al programa de usuario.
01-Capitulo 1
16/5/05
17:03
Página 25
Introducción a los computadores
TM
TM
Y
Pila de
control
Pila de
control
25
N1
T
T
YL1
N+1
Contador de
programa
Y
Inicio
Y L Retorno
Rutina de
servicio de
interrupción
Registros
generales
T
Puntero
de pila
Contador de
programa
Inicio
Y
Y L Retorno
Rutina de
servicio de
interrupción
Registros
generales
TM
Puntero
de pila
Procesador
Procesador
TM
N
N1
Programa
de usuario
Memoria
principal
N
N1
Programa
de usuario
Memoria
principal
(a) La interrupción se produce después
de la instrucción en la posición N
Figura 1.11.
T
(b) Retorno de interrupción
Cambios en la memoria y en los registros durante una interrupción.
MULTIPROGRAMACIÓN
Incluso utilizando interrupciones, puede que el procesador siga sin utilizarse eficientemente. Por
ejemplo, considérese la Figura 1.9b, que demuestra una mejor utilización del procesador. Si el tiempo requerido para completar una operación de E/S es mucho mayor que el código de usuario entre
las llamadas de E/S (una situación habitual), el procesador estará parado la mayor parte del tiempo.
Una solución a este problema es permitir que múltiples programas de usuario estén activos al mismo
tiempo.
Supóngase, por ejemplo, que el procesador tiene que ejecutar dos programas. Uno de ellos simplemente se dedica a leer datos de la memoria y copiarlos a un dispositivo externo; el otro es algún
tipo de aplicación que implica mucho cálculo. El procesador puede empezar con el programa que
01-Capitulo 1
26
16/5/05
17:03
Página 26
Sistemas operativos. Aspectos internos y principios de diseño
Programa
de usuario
Manejador de
interrupción X
Manejador de
interrupción Y
(a) Procesamiento secuencial de interrupciones
Programa
de usuario
Manejador de
interrupción X
Manejador de
interrupción Y
(b) Procesamiento anidado de interrupciones
Figura 1.12.
Transferencia de control con múltiples interrupciones.
genera salida, emitir un mandato de escritura al dispositivo externo y, a continuación, empezar la
ejecución de la otra aplicación. Cuando el procesador trata con varios programas, la secuencia en la
que se ejecutan los programas dependerá de su prioridad relativa, así como de si están esperando la
finalización de una operación de E/S. Cuando se interrumpe un programa y se transfiere el control a
un manejador de interrupción, una vez que se ha completado la rutina del manejador de interrupción, puede ocurrir que no se le devuelva inmediatamente el control al programa de usuario que estaba en ejecución en ese momento. En su lugar, el control puede pasar a algún otro programa pendiente de ejecutar que tenga una prioridad mayor. Posteriormente, se reanudará el programa de
usuario interrumpido previamente, en el momento en que tenga la mayor prioridad. Este concepto
de múltiples programas que ejecutan en turnos se denomina multiprogramación y se estudiará más
adelante en el Capítulo 2.
01-Capitulo 1
16/5/05
17:03
Página 27
Introducción a los computadores
Rutina de servicio
de interrupción
de la impresora
Programa
de usuario
27
Rutina de servicio
de interrupción
de comunicación
t0
t
t
10
15
t 25
t
40
t2
5
t
Figura 1.13.
Rutina de servicio
de interrupción
del disco
35
Ejemplo de secuencia de tiempo con múltiples interrupciones.
1.5. LA JERARQUÍA DE MEMORIA
Las restricciones de diseño en la memoria de un computador se pueden resumir en tres preguntas:
¿cuál es su capacidad? ¿Cuál es su velocidad? ¿Cuál es su coste?
La pregunta sobre cuánta debe ser su capacidad es algo que no tiene límite. Si se dispone de una
determinada capacidad, probablemente se desarrollarán aplicaciones que la usarán. La cuestión acerca de la velocidad tiene, hasta cierto tiempo, una respuesta más fácil. Para alcanzar un rendimiento
máximo, la memoria debe ser capaz de mantener el ritmo del procesador. Es decir, según el procesador va ejecutando instrucciones, no debería haber pausas esperando que estén disponibles las instrucciones o los operandos. Se debe considerar también la última pregunta. Para un sistema práctico, el
coste de la memoria debe ser razonable en relación con los otros componentes.
Como se podría esperar, hay un compromiso entre las tres características fundamentales de la memoria: a saber, coste, capacidad y tiempo de acceso. En cualquier momento dado, se utilizan diversas
tecnologías para implementar los sistemas de memoria. En todo este espectro de tecnologías, se cumplen las siguientes relaciones:
• Cuanto menor tiempo de acceso, mayor coste por bit.
• Cuanto mayor capacidad, menor coste por bit.
• Cuanto mayor capacidad, menor velocidad de acceso.
Queda claro el dilema al que se enfrenta el diseñador. A él le gustaría utilizar tecnologías
que proporcionen una memoria de gran capacidad, tanto porque se necesita esa capacidad como
porque su coste por bit es bajo. Sin embargo, para cumplir con los requisitos de rendimiento,
el diseñador necesita utilizar memorias de capacidad relativamente baja con tiempos de acceso
rápidos.
01-Capitulo 1
28
16/5/05
17:03
Página 28
Sistemas operativos. Aspectos internos y principios de diseño
La solución a este dilema consiste en no basarse en un único componente de memoria o en una
sola tecnología, sino emplear una jerarquía de memoria. En la Figura 1.14 se muestra una jerarquía
típica. Según se desciende en la jerarquía, ocurre lo siguiente:
a) Disminución del coste por bit.
b) Aumento de la capacidad.
c) Aumento del tiempo de acceso.
d) Disminución de la frecuencia de acceso a la memoria por parte del procesador.
Por tanto, las memorias más rápidas, caras y pequeñas se complementan con memorias más lentas, baratas y grandes. La clave para el éxito de esta organización es el último aspecto: la disminución
de la frecuencia de acceso. Este concepto se examinará con mayor detalle más adelante en este mismo capítulo, cuando se estudie la cache, y en posteriores capítulos del libro, cuando se presente la
memoria virtual. De todos modos, se proporcionará una breve explicación en ese momento.
Supóngase que el procesador tiene acceso a dos niveles de memoria. El nivel 1 contiene 1.000
bytes y tiene un tiempo de acceso de 0.1 ms; el nivel 2 contiene 100.000 bytes y tiene un tiempo de
acceso de 1ms. Asuma que si un byte que se va a acceder está en el nivel 1, el procesador lo hace di-
Me
m
int oria
ern
a
Al
ma
ce
ext nami
ern ent
o
o
Al
m
fue acena
ra
m
de ient
lín
ea o
Figura 1.14.
Re os
tr
gis
che
Ca
ria
mo pal
e
M nci
pri
co
éti
gn
a
m
M
sco RO
Di CD- -RW
CD D-RW M
DV D-RA
DV
ca
éti
agn
m
ta
O
Cin M RM
O
W
La jerarquía de memoria.
16/5/05
17:03
Página 29
Introducción a los computadores
29
rectamente. Sin embargo, si está en el nivel 2, primero se transfiere el byte al nivel 1 y, a continuación, el procesador lo accede. Para simplificar, se ignorará el tiempo requerido por el procesador para
determinar si el byte está en el nivel 1 o en el 2. La Figura 1.15 muestra la forma general de la curva
que representa esta situación. La figura muestra el tiempo de acceso medio a una memoria de dos niveles como una función de la tasa de aciertos A, donde A se define como la fracción de todos los accesos a memoria que se encuentran en la memoria más rápida (por ejemplo, la cache), T1 es el tiempo
de acceso al nivel 1 y T2 al nivel 25. Como se puede observar, para porcentajes elevados de accesos al
nivel 1, el tiempo medio total de acceso está mucho más próximo al correspondiente al nivel 1 que al
del nivel 2.
En el ejemplo, se supone que el 95% de los accesos a memoria se encuentran en la cache
(A = 0,95). Por tanto, el tiempo medio para acceder a un byte se puede expresar como:
(0,95)(0,1 ms) + (0,05)(0,1 ms + 1 ms) = 0,095 + 0,055 = 0,15 ms
El resultado está próximo al tiempo de acceso de la memoria más rápida. Por tanto, en principio,
la estrategia de utilizar dos niveles de memoria funciona, pero sólo si se cumplen las condiciones de
la (a) a la (d). Mediante el empleo de diversas tecnologías, existe un rango de sistemas de memoria
que satisfacen las condiciones de la (a) a la (c). Afortunadamente, la condición (d) también es generalmente válida.
La validez de la condición (d) está basada en un principio conocido como la proximidad de referencias [DENN68]. Durante el curso de ejecución de un programa, las referencias de memoria del
procesador, tanto a instrucciones como a datos, tienden a agruparse. Los programas contienen habitualmente diversos bucles iterativos y subrutinas. Una vez que se inicia un bucle o una subrutina, hay
referencias repetidas a un pequeño conjunto de instrucciones. Del mismo modo, las operaciones con
tablas y vectores involucran accesos a conjuntos agrupados de bytes de datos. En un periodo de tiem-
T1 T2
T2
Tiempo medio de acceso
01-Capitulo 1
T1
0
1
Fracción de accesos que involucra sólo al nivel (tasa de aciertos)
Figura 1.15.
Rendimiento de una memoria simple de dos niveles.
5
Si la palabra accedida se encuentra en la memoria más rápida, a esto se le define como un acierto, mientras que un fallo sucede cuando la palabra accedida no está en esta memoria más rápida.
01-Capitulo 1
30
16/5/05
17:03
Página 30
Sistemas operativos. Aspectos internos y principios de diseño
po largo, las agrupaciones que se están usando van cambiando, pero en un periodo corto, el procesador está principalmente trabajando con grupos fijos de referencias a memoria.
Por consiguiente, es posible organizar los datos a través de la jerarquía de manera que el porcentaje de accesos a cada nivel sucesivamente más bajo es considerablemente menor que al nivel inferior.
Considere el ejemplo de dos niveles presentado previamente. Supóngase que la memoria de nivel 2
contiene todos los datos e instrucciones del programa. Las agrupaciones actuales se pueden almacenar
temporalmente en el nivel 1. De vez en cuando, una de las agrupaciones en el nivel 1 tendrá que ser
expulsada al nivel 2 para hacer sitio a una nueva agrupación que llega al nivel 1. De media, sin embargo, la mayoría de las referencias corresponderá con instrucciones y datos contenidos en el nivel 1.
Este principio se puede aplicar a más de dos niveles de memoria. El tipo de memoria más rápida, pequeña y costosa consiste en los registros internos del procesador. Normalmente, un procesador contendrá
unas pocas docenas de ese tipo de registros, aunque algunas máquinas contienen cientos de registros.
Descendiendo dos niveles, está la memoria principal que es el sistema de memoria interna fundamental
del computador. Cada posición de memoria principal tiene una única dirección. La mayoría de las instrucciones de máquina hacen referencia a una o más direcciones de memoria principal. La memoria principal se amplía usualmente con una cache, más pequeña y de mayor velocidad. La cache normalmente no
es visible al programador o, incluso, al procesador. Se trata de un dispositivo que controla el movimiento
de datos entre la memoria principal y los registros de procesador con objeto de mejorar el rendimiento.
Las tres formas de memoria descritas son, normalmente, volátiles y emplean tecnología de semiconductores. El uso de tres niveles explota el hecho de que la memoria de semiconductores se presenta en diversos tipos, que se diferencian en velocidad y coste. Los datos se almacenan de forma más
permanente en los dispositivos de almacenamiento masivo externos, de los cuales los más comunes
son los discos duros y los dispositivos extraíbles, tales como los discos extraíbles, las cintas y el almacenamiento óptico. La memoria no volátil externa se denomina también memoria secundaria o
memoria auxiliar. Se usa para almacenar los ficheros de programas y datos, siendo usualmente visible al programador sólo en términos de ficheros y registros, en contraposición a bytes o palabras individuales. El disco se utiliza también para proporcionar una extensión de la memoria principal conocida como memoria virtual, que se estudiará en el Capítulo 8.
De hecho, se pueden añadir niveles adicionales a la jerarquía por software. Por ejemplo, se puede
utilizar una parte de la memoria principal como una zona de almacenamiento intermedio para guardar
temporalmente datos que van a ser leídos del disco. Esta técnica, denominada a veces cache de disco
(que se estudia en detalle en el Capítulo 11), mejora el rendimiento de dos maneras:
• Las escrituras en el disco pueden agruparse. En vez de muchas transferencias de datos de pequeño tamaño, se producen unas pocas transferencias de gran tamaño. Esto mejora el rendimiento del disco y minimiza el grado de implicación del procesador.
• Un programa puede acceder a algunos datos destinados a ser escritos antes del siguiente volcado al disco. En ese caso, los datos se recuperan rápidamente de la cache software en vez de
lentamente como ocurre cuando se accede al disco.
El Apéndice 1A estudia las implicaciones en el rendimiento de las estructuras de memoria de
múltiples niveles.
1.6. MEMORIA CACHE
Aunque la memoria cache es invisible para el sistema operativo, interactúa con otros elementos del
hardware de gestión de memoria. Además, muchos de los principios usados en los esquemas de memoria virtual (estudiados en el Capítulo 8) son aplicables también a la memoria cache.
01-Capitulo 1
16/5/05
17:03
Página 31
Introducción a los computadores
31
MOTIVACIÓN
En todos los ciclos de instrucción, el procesador accede a memoria al menos una vez, para leer la instrucción y, con frecuencia, una o más veces adicionales, para leer y/o almacenar los resultados. La velocidad a la que el procesador puede ejecutar instrucciones está claramente limitada por el tiempo de
ciclo de memoria (el tiempo que se tarda en leer o escribir una palabra de la memoria). Esta limitación
ha sido de hecho un problema significativo debido a la persistente discrepancia entre la velocidad del
procesador y la de la memoria principal; a lo largo de los años, la velocidad del procesador se ha incrementado constantemente de forma más rápida que la velocidad de acceso a la memoria. El diseñador
se encuentra con un compromiso entre velocidad, coste y tamaño. Idealmente, se debería construir la
memoria principal con la misma tecnología que la de los registros del procesador, consiguiendo tiempos de ciclo de memoria comparables a los tiempos de ciclo del procesador. Esta estrategia siempre ha
resultado demasiado costosa. La solución consiste en aprovecharse del principio de la proximidad utilizando una memoria pequeña y rápida entre el procesador y la memoria principal, denominada cache.
FUNDAMENTOS DE LA CACHE
El propósito de la memoria cache es proporcionar un tiempo de acceso a memoria próximo al de las
memorias más rápidas disponibles y, al mismo tiempo, ofrecer un tamaño de memoria grande que
tenga el precio de los tipos de memorias de semiconductores menos costosas. El concepto se muestra
en la Figura 1.16. Hay una memoria principal relativamente grande y lenta junto con una memoria
cache más pequeña y rápida. La cache contiene una copia de una parte de la memoria principal.
Cuando el procesador intenta leer un byte de la memoria, se hace una comprobación para determinar
si el byte está en la cache. Si es así, se le entrega el byte al procesador. En caso contrario, se lee e introduce dentro de la cache un bloque de memoria principal, que consta de un cierto número fijo de
bytes, y, a continuación, se le entrega el byte pedido al procesador. Debido al fenómeno de la proximidad de referencias, cuando se lee e introduce dentro de la cache un bloque de datos para satisfacer
una única referencia de memoria, es probable que muchas de las referencias a memoria en el futuro
próximo correspondan con otros bytes del bloque.
La Figura 1.17 representa la estructura de un sistema de memoria cache/principal. La memoria
principal consta de hasta 2n palabras direccionables, teniendo cada palabra una única dirección de nbits. A efectos de correspondencia entre niveles, se considera que esta memoria consta de un número
de bloques de longitud fija de K palabras cada uno. Es decir, hay M = 2n/K bloques. La cache consiste en C huecos (denominados también líneas) de K palabras cada uno, tal que el número de huecos es
considerablemente menor que el número de bloques de la memoria principal (C << M)6. Algunos sub-
Transferencias de bloques
Transferencias de palabras
Cache
CPU
Figura 1.16.
6
Memoria principal
Cache y memoria principal.
El símbolo << significa mucho menor que. De manera similar, el símbolo >> significa mucho mayor que.
01-Capitulo 1
32
16/5/05
17:03
Página 32
Sistemas operativos. Aspectos internos y principios de diseño
conjuntos de los bloques de memoria principal residen en los huecos de la cache. Si se lee una palabra de un bloque de memoria que no está en la cache, se transfiere ese bloque a uno de los huecos de
la cache. Dado que hay más bloques que huecos, no se puede dedicar un hueco individual de forma
única y permanente a un determinado bloque. Por tanto, cada hueco incluye una etiqueta que identifica qué bloque en concreto se almacena actualmente en él. La etiqueta corresponde normalmente con
varios bits de la parte de mayor peso de la dirección y hace referencia a todas las direcciones que comienzan con esa secuencia de bits.
Como un ejemplo sencillo, supóngase una dirección de seis bits y una etiqueta de 2 bits. La etiqueta 01 se refiere al bloque de posiciones con las siguientes direcciones: 010000, 010001, 010010,
010011, 010100, 010101, 010110, 010111, 011000, 011001, 011010, 011011, 011100, 011101,
011110 y 011111.
La Figura 1.18 muestra la operación de lectura. El procesador genera la dirección DL de la palabra que pretende leer. Si la cache contiene la palabra, se la entrega al procesador. En caso contrario,
se carga en la cache el bloque que contiene esa palabra, proporcionando al procesador dicha palabra.
DISEÑO DE LA CACHE
Un estudio detallado del diseño de la cache queda fuera del alcance de este libro. A continuación, se
resumen brevemente los elementos fundamentales. Se comprobará más adelante que hay que afrontar
Número
de línea Etiqueta
0
1
2
Dirección
de memoria
0
1
2
3
Bloque
Bloque
(K palabras)
C1
Longitud de bloque
(K palabras)
(a) Cache
Bloque
2n 1
Longitud
de palabra
(b) Memoria principal
Figura 1.17.
Estructura de cache/memoria principal.
01-Capitulo 1
16/5/05
17:03
Página 33
Introducción a los computadores
33
aspectos de diseño similares al tratar el diseño de la memoria virtual y de la cache de disco. Se dividen en las siguientes categorías:
• Tamaño de la cache.
• Tamaño del bloque.
• Función de correspondencia.
• Algoritmo de remplazo.
• Política de escritura.
Se ha tratado ya el tema del tamaño de la cache, llegándose a la conclusión de que una cache de
un tamaño razonablemente pequeño puede tener un impacto significativo en el rendimiento. Otro aspecto relacionado con la capacidad de la cache es el tamaño del bloque: la unidad de datos que se
intercambia entre la cache y la memoria principal. Según el tamaño del bloque se incrementa desde
muy pequeño a tamaños mayores, al principio la tasa de aciertos aumentará debido al principio de la
proximidad: la alta probabilidad de que accedan en el futuro inmediato a los datos que están en la
proximidad de una palabra a la que se ha hecho referencia. Según se incrementa el tamaño de bloque,
se llevan a la cache más datos útiles. Sin embargo, la tasa de aciertos comenzará a decrecer cuando el
tamaño del bloque sigua creciendo, ya que la probabilidad de volver a usar los datos recientemente
Inicio
DL — dirección de
lectura
Recibe la dirección
DL de la CPU
¿Está en la cache
el bloque que
contiene DL?
No
Accede a la memoria
principal buscando el
bloque que contiene DL
Sí
Reserva el hueco en
la cache para el bloque
de memoria principal
Lee la palabra DL
y la entrega
a la CPU
Carga el bloque de
memoria principal en
el hueco de la cache
Entrega la palabra
DL a la CPU
Completado
Figura 1.18.
Operación de lectura de cache.
01-Capitulo 1
34
16/5/05
17:03
Página 34
Sistemas operativos. Aspectos internos y principios de diseño
leídos se hace menor que la de utilizar nuevamente los datos que se van a expulsar de la cache para
dejar sitio al nuevo bloque.
Cuando se lee e incluye un nuevo bloque de datos en la cache, la función de correspondencia
determina qué posición de la cache ocupará el bloque. Existen dos restricciones que afectan al diseño
de la función de correspondencia.
En primer lugar, cuando se introduce un bloque en la cache, se puede tener que remplazar
otro. Sería deseable hacer esto de manera que se minimizara la probabilidad de que se remplazase
un bloque que se necesitara en el futuro inmediato. Cuanto más flexible es la función de correspondencia, mayor grado de libertad a la hora de diseñar un algoritmo de remplazo que maximice
la tasa de aciertos. En segundo lugar, cuanto más flexible es la función de correspondencia, más
compleja es la circuitería requerida para buscar en la cache y determinar si un bloque dado está
allí.
El algoritmo de remplazo selecciona, dentro de las restricciones de la función de correspondencia, qué bloque remplazar cuando un nuevo bloque va a cargarse en la cache y ésta tiene todos los
huecos llenos con otros bloques. Sería deseable remplazar el bloque que menos probablemente se va
a necesitar de nuevo en el futuro inmediato. Aunque es imposible identificar tal bloque, una estrategia
razonablemente eficiente es remplazar el bloque que ha estado en la cache durante más tiempo sin haberse producido ninguna referencia a él. Esta política se denomina el algoritmo del menos recientemente usado (Least Recently Used, LRU). Se necesitan mecanismos hardware para identificar el bloque menos recientemente usado.
Si se altera el contenido de un bloque en la cache, es necesario volverlo a escribir en la memoria
principal antes de remplazarlo. La política de escritura dicta cuando tiene lugar la operación de escritura en memoria. Una alternativa es que la escritura se produzca cada vez que se actualiza el bloque. Otra opción es que la escritura se realice sólo cuando se remplaza el bloque. La última estrategia
minimiza las operaciones de escritura en memoria pero deja la memoria principal temporalmente en
un estado obsoleto. Esto puede interferir con el modo de operación de un multiprocesador y con el
acceso directo a memoria realizado por los módulos hardware de E/S.
1.7. TÉCNICAS DE COMUNICACIÓN DE E/S
Hay tres técnicas para llevar a cabo las operaciones de E/S:
• E/S programada.
• E/S dirigida de interrupciones.
• Acceso directo a memoria (Direct Memory Access, DMA).
E/S PROGRAMADA
Cuando el procesador ejecuta un programa y encuentra una instrucción relacionada con la E/S, ejecuta esa instrucción generando un mandato al módulo de E/S apropiado. En el caso de la E/S programada, el módulo de E/S realiza la acción solicitada y fija los bits correspondientes en el registro de estado de E/S, pero no realiza ninguna acción para avisar al procesador. En concreto, no interrumpe al
procesador. Por tanto, después de que se invoca la instrucción de E/S, el procesador debe tomar un
papel activo para determinar cuándo se completa la instrucción de E/S. Por este motivo, el procesador
comprueba periódicamente el estado del módulo de E/S hasta que encuentra que se ha completado la
operación.
01-Capitulo 1
16/5/05
17:03
Página 35
Introducción a los computadores
35
Con esta técnica, el procesador es responsable de extraer los datos de la memoria principal en una
operación de salida y de almacenarlos en ella en una operación de entrada. El sotfware de E/S se escribe de manera que el procesador ejecuta instrucciones que le dan control directo de la operación de
E/S, incluyendo comprobar el estado del dispositivo, enviar un mandato de lectura o de escritura, y
transferir los datos. Por tanto, el juego de instrucciones incluye instrucciones de E/S de las siguientes
categorías:
• Control. Utilizadas para activar un dispositivo externo y especificarle qué debe hacer. Por
ejemplo, se le puede indicar a una unidad de cinta magnética que se rebobine o avance un
registro.
• Estado. Utilizadas para comprobar diversas condiciones de estado asociadas a un módulo de
E/S y sus periféricos.
• Transferencia. Utilizadas para leer y/o escribir datos entre los registros del procesador y los
dispositivos externos.
La Figura 1.19a proporciona un ejemplo del uso de E/S programada para leer un bloque de datos de un dispositivo externo (p. ej. un registro de cinta) y almacenarlo en memoria. Los datos se
leen palabra a palabra (por ejemplo, 16 bits). Por cada palabra que se lee, el procesador debe permanecer en un bucle de comprobación del estado hasta que determina que la palabra está disponible
en el registro de datos del módulo de E/S. Este diagrama de flujo subraya las desventajas principales de esta técnica: es un proceso que consume un tiempo apreciable que mantiene al procesador
ocupado innecesariamente.
Envía el mandato
de lectura al
módulo de E/S
CPU
Lee el estado
del módulo
de E/S
E/S
E/S
Envía el mandato
de lectura al
módulo de E/S
CPU
Lee el estado
del módulo
de E/S
CPU
Hace otra cosa
Interrupción
E/S
No
listo
Condición
de error
Comprueba
el estado
Listo
Lee una
palabra del
módulo de E/S
Escribe la
palabra en
memoria
No
E/S
CPU
Envía el mandato
de lectura de bloque
al módulo de E/S
Lee el estado
del módulo
de DMA
Lee una
palabra del
módulo de E/S
CPU
CPU
No
¿Completado?
Sí
Siguiente instrucción
(a) E/S programada
Escribe la
palabra en
memoria
memoria
E/S
CPU
CPU
memoria
¿Completado?
Sí
Siguiente instrucción
(b) E/S dirigida por interrupciones
Figura 1.19.
Tres técnicas para leer un bloque de datos.
DMA
Hace otra cosa
Interrupción
DMA
Siguiente instrucción
Condición (c) Acceso directo a memoria
de error
Comprueba
el estado
Listo
E/S
CPU
CPU
01-Capitulo 1
36
16/5/05
17:03
Página 36
Sistemas operativos. Aspectos internos y principios de diseño
E/S DIRIGIDA POR INTERRUPCIONES
El problema de la E/S programada es que el procesador tiene que esperar mucho tiempo hasta que el
módulo de E/S correspondiente esté listo para la recepción o la transmisión de más datos. El procesador, mientras está esperando, debe comprobar repetidamente el estado del módulo de E/S. Como resultado, el nivel de rendimiento de todo el sistema se degrada gravemente.
Una alternativa es que el procesador genere un mandato de E/S para un módulo y, acto seguido, continúe realizando algún otro trabajo útil. El módulo de E/S interrumpirá más tarde al procesador para solicitar su servicio cuando esté listo para intercambiar datos con el mismo. El procesador ejecutará la transferencia de datos, como antes, y después reanudará el procesamiento
previo.
Considere cómo funciona esta alternativa, primero desde el punto de vista del módulo de E/S.
Para una operación de entrada, el módulo de E/S recibe un mandato de LECTURA del procesador.
El módulo de E/S pasa entonces a leer los datos de un periférico asociado. Una vez que los datos
están en el registro de datos del módulo, el módulo genera una interrupción al procesador a través
de una línea de control. El módulo entonces espera hasta que el procesador pida sus datos. Cuando
se hace la petición, el módulo sitúa sus datos en el bus de datos y ya está listo para otra operación
de E/S.
Desde el punto de vista del procesador, las acciones correspondientes a una operación de lectura son las que se describen a continuación. El procesador genera un mandato de LECTURA.
Salva el contexto (por ejemplo, el contador de programa y los registros del procesador) del programa actual y lo abandona, pasando a hacer otra cosa (por ejemplo, el procesador puede estar
trabajando en varios programas diferentes a la vez). Al final de cada ciclo de instrucción, el procesador comprueba si hay interrupciones (Figura 1.7). Cuando se produce la interrupción del módulo de E/S, el procesador salva el contexto del programa que se está ejecutando actualmente y
comienza a ejecutar un programa de manejo de interrupción que procesa la interrupción. En este
caso, el procesador lee la palabra de datos del módulo de E/S y la almacena en memoria. A continuación, restaura el contexto del programa que había realizado el mandato de E/S (o de algún otro
programa) y reanuda su ejecución.
La Figura 1.19b muestra el uso de la E/S dirigida por interrupciones para leer un bloque de datos.
La E/S dirigida por interrupciones es más eficiente que la E/S programada ya que elimina la espera
innecesaria. Sin embargo, la E/S dirigida por interrupciones todavía consume mucho tiempo de procesador, puesto que cada palabra de datos que va desde la memoria al módulo de E/S o desde el módulo de E/S hasta la memoria debe pasar a través del procesador.
Casi invariablemente, habrá múltiples módulos de E/S en un computador, por lo que se necesitan
mecanismos para permitir que el procesador determine qué dispositivo causó la interrupción y para
decidir, en caso de múltiples interrupciones, cuál debe manejar primero. En algunos sistemas, hay
múltiples líneas de interrupción, de manera que cada módulo de E/S usa una línea diferente. Cada línea tendrá una prioridad diferente. Alternativamente, puede haber una única línea de interrupción,
pero se utilizan líneas adicionales para guardar la dirección de un dispositivo. De nuevo, se le asignan
diferentes prioridades a los distintos dispositivos.
ACCESO DIRECTO A MEMORIA
La E/S dirigida por interrupciones, aunque más eficiente que la E/S programada simple, todavía requiere la intervención activa del procesador para transferir datos entre la memoria y un módulo de
E/S, ya que cualquier transferencia de datos debe atravesar un camino a través del procesador. Por
tanto, ambas formas de E/S sufren dos inconvenientes inherentes:
01-Capitulo 1
16/5/05
17:03
Página 37
Introducción a los computadores
37
1. La tasa de transferencia de E/S está limitada por la velocidad con la que el procesador puede
comprobar el estado de un dispositivo y ofrecerle servicio.
2. El procesador está involucrado en la gestión de una transferencia de E/S; se deben ejecutar
varias instrucciones por cada transferencia de E/S.
Cuando se van a transferir grandes volúmenes de datos, se requiere una técnica más eficiente: el
acceso directo a memoria (Direct Memory Access, DMA). La función de DMA puede llevarla a cabo
un módulo separado conectado en el bus del sistema o puede estar incluida en un módulo de E/S. En
cualquier caso, la técnica funciona como se describe a continuación. Cuando el procesador desea
leer o escribir un bloque de datos, genera un mandato al módulo de DMA, enviándole la siguiente
información:
• Si se trata de una lectura o de una escritura.
• La dirección del dispositivo de E/S involucrado.
• La posición inicial de memoria en la que se desea leer los datos o donde se quieren escribir.
• El número de palabras que se pretende leer o escribir.
A continuación, el procesador continúa con otro trabajo. Ha delegado esta operación de E/S
al módulo de DMA, que se ocupará de la misma. El módulo de DMA transferirá el bloque completo de datos, palabra a palabra, hacia la memoria o desde ella sin pasar a través del procesador.
Por tanto, el procesador solamente está involucrado al principio y al final de la transferencia (Figura 1.19c).
El módulo de DMA necesita tomar el control del bus para transferir datos hacia la memoria o
desde ella. Debido a esta competencia en el uso del bus, puede haber veces en las que el procesador necesita el bus y debe esperar al módulo de DMA. Nótese que esto no es una interrupción; el
procesador no salva un contexto y pasa a hacer otra cosa. En su lugar, el procesador se detiene durante un ciclo de bus (el tiempo que se tarda en transferir una palabra a través del bus). El efecto
global es causar que el procesador ejecute más lentamente durante una transferencia de DMA en
el caso de que el procesador requiera acceso al bus. Sin embargo, para una transferencia de E/S de
múltiples palabras, el DMA es mucho más eficiente que la E/S dirigida por interrupciones o la
programada.
1.8. LECTURAS Y SITIOS WEB RECOMENDADOS
[STAL03] cubre en detalle los temas de este capítulo. Además, hay muchos otros libros sobre arquitectura y organización de computadores. Entre los textos más notables están los siguientes: [PATT98]
es un estudio general; [HENN02], de los mismos autores, es un libro más avanzado que enfatiza sobre aspectos cuantitativos de diseño.
HENN02 Hennessy, J., y Patterson, D. Computer Architecture: A Quantitative Approach. San Mateo,
CA: Morgan Kaufmann, 2002.
PATT98 Patterson, D., y Hennessy, J. Computer Organization and Design: The Hardware/Software Interface. San Mateo, CA: Morgan Kaufmann, 1998.
STAL03 Stallings, W. Computer Organization and Architecture, 6th ed. Upper Saddle River, NJ: Prentice Hall, 2003.
01-Capitulo 1
38
16/5/05
17:03
Página 38
Sistemas operativos. Aspectos internos y principios de diseño
SITIOS WEB RECOMENDADOS
• WWW Computer Architecture Home Page. Un índice amplio de información relevante
para los investigadores en arquitectura de computadores, incluyendo grupos y proyectos de
arquitectura, organizaciones técnicas, bibliografía, empleo, e información comercial.
• CPU Info Center. Información sobre procesadores específicos, incluyendo artículos técnicos, información de productos y los últimos anuncios.
1.9. TÉRMINOS CLAVE, CUESTIONES DE REPASO Y PROBLEMAS
TÉRMINOS CLAVE
acceso directo a memoria (DMA)
marco de pila
proximidad temporal
bus del sistema
memoria cache
puntero de pila
ciclo de instrucción
memoria principal
puntero de segmento
código de condición
memoria secundaria
registro
contador de programa
módulo de E/S
registro de datos
E/S dirigida por interrupciones
multiprogramación
registro de dirección
E/S programada
pila
registro índice
entrada/salida (E/S)
procedimiento reentrante
registro de instrucción
hueco de cache
procesador
tasa de aciertos
Instrucción
proximidad
unidad central de proceso (CPU)
Interrupción
proximidad espacial
CUESTIONES DE REPASO
1.1. Enumere y defina brevemente los cuatro elementos principales de un computador.
1.2. Defina las dos categorías principales de los registros del procesador.
1.3. En términos generales, ¿cuáles son las cuatro acciones distintas que puede especificar una
instrucción de máquina?
1.4. ¿Qué es una interrupción?
1.5. ¿Cómo se tratan múltiples interrupciones?
1.6. ¿Qué características distinguen a los diversos elementos de una jerarquía de memoria?
1.7. ¿Qué es una memoria cache?
1.8. Enumere y defina brevemente las tres técnicas para las operaciones de E/S.
1.9. ¿Cuál es la diferencia entre la proximidad espacial y la temporal?
1.10. En general, ¿cuáles son las estrategias para aprovechar la proximidad espacial y la temporal?
01-Capitulo 1
16/5/05
17:03
Página 39
Introducción a los computadores
39
PROBLEMAS
1.1. Suponga que la máquina hipotética de la Figura 1.3 tiene también dos instrucciones de E/S:
0011 = Carga el AC con un valor leído de un dispositivo de E/S
0111 = Almacena el AC en un dispositivo de E/S
En estos casos, la dirección de 12 bits identifica un determinado dispositivo externo. Muestre la ejecución del programa (utilizando el formato de la Figura 1.4) correspondiente al siguiente fragmento:
1.
2.
3.
4.
Carga el AC con un valor leído del dispositivo 5.
Suma al AC el contenido de la posición de memoria 940.
Almacena el AC en el dispositivo 6.
Asuma que el siguiente valor leído del dispositivo 5 es 3 y que la posición 940 contiene
el valor 2.
1.2. La ejecución del programa de la Figura 1.4 se describe en el texto utilizando seis pasos.
Extienda esta descripción para mostrar el uso del RDIM y del RDAM.
1.3. Considere un hipotético microprocesador de 32 bits que tiene instrucciones de 32 bits compuestas de dos campos: el primer byte contiene el código de operación y el resto un operando inmediato o la dirección de un operando.
a) ¿Cuál es la máxima capacidad de memoria directamente direccionable (en bytes)?
b) Estudie el impacto en la velocidad del sistema dependiendo de si el bus del microprocesador tiene:
1. un bus de direcciones local de 32 bits y un bus de datos local de 16 bits o
2. un bus de direcciones local de 16 bits y un bus de datos local de 16 bits.
c) ¿Cuántos bits se necesitan para el contador del programa y para el registro de instrucciones?
1.4. Considere un microprocesador hipotético que genera una dirección de 16 bits (por ejemplo,
asuma que el contador de programa y los registros de dirección tienen un ancho de 16 bits)
y que tiene un bus de datos de 16 bits.
a) ¿Cuál es el máximo espacio de direcciones de memoria al que el procesador puede acceder directamente si se conecta a una «memoria de 16 bits»?
b) ¿Cuál es el máximo espacio de direcciones de memoria al que el procesador puede acceder directamente si se conecta a una «memoria de 8 bits»?
c) ¿Qué características arquitectónicas permitirán a este microprocesador acceder a un
«espacio de E/S» separado?
d) Si una instrucción de entrada/salida puede especificar un número de puerto de E/S de 8
bits, ¿cuántos puertos de E/S de 8 bits puede manejar el microprocesador? ¿Y cuántos
de 16 bits? Razone la respuesta.
1.5. Considere un microprocesador de 32 bits, con un bus de datos externo de 16 bits, alimentado por un reloj de entrada de 8 MHz. Asuma que este microprocesador tiene un ciclo de
bus cuya duración mínima es igual a cuatro ciclos del reloj de entrada. ¿Cuál es la tasa de
transferencia de datos máxima en el bus que este microprocesador puede mantener medida
en bytes/s? Para incrementar su rendimiento, ¿sería mejor hacer que su bus de datos externo tenga 32 bits o doblar la frecuencia del reloj externo suministrada al microprocesador?
01-Capitulo 1
40
16/5/05
17:03
Página 40
Sistemas operativos. Aspectos internos y principios de diseño
Detalle cualquier otra suposición que se realice, razonando la misma. Sugerencia: determine el número de bytes que se pueden transferir por cada ciclo de bus.
1.6. Considere un computador que contiene un módulo de E/S que controla un sencillo teletipo
con impresora y teclado. La CPU contiene los siguientes registros, que están conectados
directamente con el bus del sistema:
RENT: Registro de entrada, 8 bits
RSAL: Registro de salida, 8 bits
INE: Indicador de entrada, 1 bit
INS: Indicador de salida, 1 bit
HAI: Habilitación de interrupción, 1 bit
El módulo de E/S controla la entrada de teclado del teletipo y la salida a la impresora. El
teletipo es capaz de codificar un símbolo alfanumérico en palabras de 8 bits y descodificar
una palabra de 8 bits en un símbolo alfanumérico. El indicador de entrada se activa cuando
se introduce una palabra de 8 bits en el registro de entrada del teletipo. El indicador de salida se activa cuando se imprime una palabra.
a) Describa cómo la CPU, utilizando los cuatro primeros registros enumerados en este
problema, puede realizar E/S con el teletipo.
b) Describa cómo se puede realizar más eficientemente la función empleando también
HAI.
1.7. En prácticamente todos los sistemas que incluyen módulos de DMA, se otorga mayor prioridad a los accesos del módulo de DMA a la memoria principal que a los accesos del procesador. ¿Por qué?
1.8. Un módulo de DMA está transfiriendo caracteres a la memoria principal desde un dispositivo externo transmitiendo a 9600 bits por segundo (bps). El procesador puede leer instrucciones a una velocidad de 1 millón de instrucciones por segundo. ¿En cuánto se ralentizará
el procesador debido a la actividad de DMA?
1.9. Un computador consta de una CPU y un dispositivo D de E/S conectado a la memoria
principal M mediante un bus compartido con una anchura de bus de datos de una palabra. La CPU puede ejecutar un máximo de 106 instrucciones por segundo. Una instrucción media requiere cinco ciclos de máquina, tres de los cuales utilizan el bus de
memoria. Una operación de lectura o escritura en memoria utiliza un ciclo de máquina. Supóngase que la CPU está ejecutando constantemente programas en segundo plano (background) que requieren el 95% de su tasa de ejecución de instrucciones pero
ninguna instrucción de E/S. Asuma que un ciclo de procesador es igual a un ciclo de
bus. Ahora suponga que se tienen que transferir bloques de datos muy grandes entre
M y D.
a) Si se utiliza E/S programada y cada transferencia de E/S de una palabra requiere que la
CPU ejecute dos instrucciones, estime la tasa máxima de transferencia de datos posible
de E/S, en palabras por segundo, a través de D.
b) Estime la misma tasa si se utiliza una transferencia mediante DMA
1.10. Considere el siguiente código:
for (i = 0; i < 20; i++)
for (j = 0; j < 10; j++)
a[i] = a[i] *j
01-Capitulo 1
16/5/05
17:03
Página 41
Introducción a los computadores
41
a) Proporcione un ejemplo de proximidad espacial en el código.
b) Proporcione un ejemplo de proximidad temporal en el código.
1.11. Generalice las ecuaciones (1.1) y (1.2) del Apéndice 1A para jerarquías de memoria de n
niveles.
1.12. Considere un sistema de memoria con los siguientes parámetros:
Tc = 100 ns
Cc = 0,01 céntimos/bit
Tm = 1.200 ns
Cm = 0,001 céntimos/bit
a) ¿Cuál es el coste de 1MByte de memoria principal?
b) ¿Cuál es el coste de 1Mbyte de memoria principal utilizando tecnología de memoria
cache?
c) Si el tiempo de acceso efectivo es un 10% mayor que el tiempo de acceso a la cache,
¿cuál es la tasa de aciertos A?
1.13. Un computador tiene una cache, una memoria principal y un disco usado para la memoria
virtual. Si la palabra accedida está en la cache, se requieren 20 ns para accederla. Si está en
la memoria principal pero no en la cache, se necesitan 60 ns para cargarla en la cache (esto
incluye el tiempo para comprobar inicialmente si está en la cache) y, a continuación, la referencia comienza de nuevo. Si la palabra no está en memoria principal, se requieren 12 ms
para buscar la palabra del disco, seguido de 60 ns para copiarla de la cache y luego se inicia nuevamente la referencia. La tasa de aciertos de la cache es 0,9 y la de la memoria principal es 0,6. ¿Cuál es el tiempo medio en ns requerido para acceder a una palabra en este
sistema?
1.4.
Suponga que el procesador utiliza una pila para gestionar las llamadas a procedimiento y
los retornos de los mismos. ¿Puede eliminarse el contador de programa utilizando la cima
de la pila como contador de programa?
APÉNDICE 1A CARACTERÍSTICAS DE RENDIMIENTO DE LAS MEMORIAS
DE DOS NIVELES
En este capítulo, se hace referencia a la cache que actúa como un buffer entre la memoria principal y
el procesador, creando una memoria interna de dos niveles. Esta arquitectura de dos niveles proporciona un rendimiento mejorado con respecto a una memoria de un nivel equiparable, explotando una
propiedad conocida como proximidad, que se analizará en este apéndice.
Tabla 1.2. Características de las memorias de dos niveles.
Cache de memoria
principal
Memoria virtual
(Paginación)
Cache de disco
Proporción típica entre
tiempos de acceso
5:1
106:1
106:1
Sistema de gestión
de memoria
Implementado por
un hardware especial
Combinación de
hardware y software
de sistema
Software de sistema
Tamaño típico de bloque
4 a 128 bytes
64 a 4096 bytes
64 a 4096 bytes
Acceso del procesador
al segundo nivel
Acceso directo
Acceso indirecto
Acceso indirecto
01-Capitulo 1
42
16/5/05
17:03
Página 42
Sistemas operativos. Aspectos internos y principios de diseño
El mecanismo de cache de memoria principal es parte de la arquitectura del computador, implementado en hardware y habitualmente invisible al sistema operativo. Por tanto, en este libro no se trata este mecanismo. Sin embargo, hay otras dos instancias de la técnica de memoria de dos niveles que
también explotan la propiedad de la proximidad y que son, al menos parcialmente, implementadas en
el sistema operativo: la memoria virtual y la cache de disco (Tabla 1.2). Estos dos temas se analizarán
en los Capítulos 8 y 11, respectivamente. En este apéndice, se revisarán algunas características de
rendimiento de la memoria de dos niveles que son comunes a las tres técnicas.
PROXIMIDAD
La base de la ganancia en rendimiento de la memoria de dos niveles reside en el principio de la proximidad, comentado en la Sección 1.5. Este principio establece que las referencias a memoria tienden a agruparse. En un largo periodo de tiempo, los grupos que se están usando van cambiando,
pero en un periodo corto, el procesador está primordialmente trabajando con grupos fijos de referencias a memoria.
La experiencia real muestra que el principio de la proximidad es válido. Para comprobarlo, considere la siguiente línea de razonamiento:
1. Excepto para las interrupciones de salto y llamada, que constituyen sólo una pequeña parte de
todas las instrucciones del programa, la ejecución del programa es secuencial. Por tanto, en la
mayoría de los casos, la próxima instrucción que se va a leer es la que sigue inmediatamente a
la última instrucción leída.
2. No es frecuente que se produzca una larga secuencia ininterrumpida de llamadas a procedimiento seguida por la correspondiente secuencia de retornos. Lo habitual es que un programa
permanezca confinado en una ventana de anidamiento de invocación de procedimientos bastante estrecha. Por tanto, durante un periodo corto de tiempo, las referencias a instrucciones
tienden a localizarse en unos pocos procedimientos.
3. La mayoría de las construcciones iterativas consta de un número relativamente pequeño de
instrucciones repetidas muchas veces. Mientras se ejecuta una iteración, el cálculo queda confinado, por tanto, en una pequeña parte contigua del programa.
4. En muchos programas, gran parte del cálculo implica procesar estructuras de datos, tales como
vectores o secuencias de registros. En muchos casos, las sucesivas referencias a estas estructuras de datos corresponderán con elementos de datos situados próximamente.
Esta línea de razonamiento se ha confirmado en muchos estudios. Con respecto al primer punto,
varios estudios han analizado el comportamiento de programas escritos en lenguajes de alto nivel. La
Tabla 1.3 incluye los resultados fundamentales, midiendo la aparición de varios tipos de sentencias
durante la ejecución, extraídos de los estudios que se detallan a continuación. El más antiguo estudio
del comportamiento de un lenguaje de programación, realizado por Knuth [KNUT71], examinaba
una colección de programas en FORTRAN usados como ejercicios para estudiantes. Tanenbaum
[TANE78] publicó medidas recogidas de unos 300 procedimientos utilizados en programas del sistema operativo y escritos en un lenguaje que da soporte a la programación estructurada (SAL). Patterson y Sequin [PATT82] analizaron un conjunto de medidas obtenidas por compiladores, programas
de composición de documentos, de diseño asistido por computador (Computer-Aided Design, CAD),
de ordenamiento y de comparación de ficheros. Se estudiaron los lenguajes de programación C y Pascal. Huck [HUCK83] analizó cuatro programas seleccionados para representar una mezcla de cálculos científicos de propósito general, incluyendo la transformada rápida de Fourier y la integración de
sistemas de ecuaciones diferenciales. Hay una coincidencia general en los resultados de esta mezcla
01-Capitulo 1
16/5/05
17:03
Página 43
Introducción a los computadores
43
de lenguajes y aplicaciones en lo que se refiere a que las instrucciones de salto y de llamada representan sólo una fracción de las sentencias ejecutadas durante la vida de un programa. Por tanto, estos estudios confirman la primera afirmación de la lista precedente.
Tabla 1.3.
Frecuencia dinámica relativa de las operaciones en lenguajes de alto nivel.
Estudio
Lenguaje
Tipo de carga
[HUCK83]
Pascal
Científica
[KNUT71]
FORTRAN
Estudiantes
[PATT82]
Pascal
C
Sistema
Sistema
[TANE78]
SAL
Sistema
Asignación
74
67
45
38
42
Bucle
4
3
5
3
4
Llamada
1
3
15
12
12
IF
20
11
29
43
36
GOTO
2
9
—
3
—
Otros
—
7
6
1
6
Con respecto a la segunda afirmación, los estudios presentados en [PATT85] proporcionan una
confirmación. Esto se ilustra en la Figura 1.20, que muestra el comportamiento de llamadas y retornos. Cada llamada se representa por una línea descendente que se desplaza hacia la derecha, y cada
retorno por una línea ascendente desplazándose a la derecha. En la figura, se define una ventana con
una profundidad igual a 5. Sólo una secuencia de llamadas y retornos con un movimiento neto de 6
en cualquier dirección causa que la ventana se mueva. Como puede observarse, el programa en ejecución puede permanecer dentro de una ventana estacionaria durante largos periodos de tiempo. Un estudio de los mismos autores sobre programas en C y Pascal mostró que una ventana de profundidad 8
sólo necesitaría desplazarse en menos del 1% de las llamadas o retornos [TAMI83].
El principio de proximidad de referencias continúa siendo validado en los estudios más recientes.
Por ejemplo, la Figura 1.21 muestra el resultado de un estudio de los patrones de acceso a las páginas
web de un determinado sitio.
Tiempo
(en números de llamadas/retornos)
t 33
Retorno
Llamada
v5
Nivel de
anidamiento
Figura 1.20.
Ejemplo de comportamiento de llamadas y retornos de un programa.
44
16/5/05
17:03
Página 44
Sistemas operativos. Aspectos internos y principios de diseño
3000
2500
Número de refrencias
01-Capitulo 1
2000
1500
1000
500
0
50
100
150
200
250
300
350
400
Número acumulativo de documentos
Figura 1.21.
Proximidad de referencias para páginas web [BAEN97].
La bibliografía sobre el tema hace una distinción entre la proximidad espacial y la temporal. La
proximidad espacial se refiere a la tendencia de una ejecución a involucrar posiciones de memoria
que están agrupadas. Esto refleja la tendencia de un procesador a acceder secuencialmente a las instrucciones. La proximidad espacial también refleja la tendencia de un programa a acceder de forma
secuencial a las posiciones de datos, como cuando se procesa una tabla de datos. La proximidad
temporal hace referencia a la tendencia de un procesador a acceder a posiciones de memoria que se
han utilizado recientemente. Por ejemplo, cuando se ejecuta un bucle, el procesador ejecuta el mismo
juego de instrucciones repetidamente.
Tradicionalmente, la proximidad temporal se explota manteniendo en la memoria cache los valores de las instrucciones y los datos usados recientemente aprovechando una jerarquía de cache. La
proximidad espacial se explota generalmente utilizando bloques de cache más grandes e incorporando mecanismos de lectura anticipada (se buscan elementos cuyo uso se prevé) en la lógica de control
de la cache. Recientemente, ha habido investigaciones considerables para la mejora de estas técnicas
con objeto de alcanzar un mayor rendimiento, pero las estrategias básicas siguen siendo las mismas.
MODO DE OPERACIÓN DE LA MEMORIA DE DOS NIVELES
La propiedad de la proximidad se puede explotar para la creación de una memoria de dos niveles. La
memoria de nivel superior (M1) es más pequeña, más rápida y más cara (por bit) que la memoria de
nivel inferior (M2). M1 se utiliza como un almacenamiento temporal para una parte del contenido de
M2, que es más grande. Cuando se realiza una referencia a memoria, se hace un intento de acceder al
elemento en M1. Si tiene éxito, se lleva a cabo un acceso rápido. En caso contrario, se copia un bloque de posiciones de memoria de M2 a M1 y, a continuación, el acceso tiene lugar en M1. Gracias a
la proximidad, una vez que se trae un bloque a M1, debería haber varios accesos a las posiciones en
ese bloque, dando como resultado un servicio global rápido.
Para expresar el tiempo medio de acceso a un elemento, no sólo se deberá considerar las velocidades de los dos niveles de memoria sino también la probabilidad de que una referencia dada puede
encontrarse en M1. Se tiene:
TS = A ¥ T1 + (1 – A) ¥ (T1 + T2)
= T1 + (1 – A) ¥ T2
(1.1)
01-Capitulo 1
16/5/05
17:03
Página 45
Introducción a los computadores
45
donde:
TS = tiempo medio de acceso (del sistema).
T1 = tiempo de acceso a M1 (por ejemplo, cache, cache de disco).
T2 = tiempo de acceso a M2 (por ejemplo, memoria principal, disco).
A = tasa de aciertos (tasa de referencias encontradas en M1).
La Figura 1.15 muestra el tiempo medio de acceso como una función de la tasa de aciertos. Como
puede observarse, para un porcentaje alto de aciertos, el tiempo medio de acceso total está mucho
más cerca al de M1 que al de M2.
RENDIMIENTO
A continuación, se examinan algunos de los parámetros relevantes para la valoración de un mecanismo de memoria de dos niveles. En primer lugar, se considera el coste:
CS =
C1D1 + C2D2
(1.2)
D1 + D2
donde:
CS = coste medio por bit de la memoria combinada de dos niveles
C1 = coste medio por bit de la memoria M1 de nivel superior
C2 = coste medio por bit de la memoria M2 de nivel inferior
D1 = tamaño de M1
D2 = tamaño de M2
Sería deseable que CS ª C2. Dado que C1 >> C2, se requiere que M1 << M2. La Figura 1.22 muestra la relación7.
Seguidamente, considere el tiempo de acceso. Para que una memoria de dos niveles proporcione
una mejora significativa de rendimiento, se necesita tener TS aproximadamente igual a T1 (TS ª T1).
Dado que T1 es mucho menor que T2 (T1 << T2), se necesita una tasa de aciertos próxima a 1.
Por consiguiente, se pretende que M1 sea pequeña para mantener el coste bajo y grande para mejorar la tasa de acierto y, por tanto, el rendimiento. ¿Hay un tamaño de M1 que satisface ambos requisitos hasta un punto razonable? Se puede responder a esta cuestión mediante una serie de preguntas
adicionales:
• ¿Qué valor de la tasa de aciertos se necesita para satisfacer el requisito de rendimiento planteado?
• ¿Qué tamaño de M1 asegurará la tasa de aciertos requerida?
• ¿Satisface este tamaño el requisito de coste?
7
Nótese que los dos ejes usan una escala logarítmica. Una revisión básica de las escalas logarítmicas se encuentra en el documento de repaso de matemáticas en el Computer Science Student Support Site en WilliamStallings.com/StudentSupport.html..
46
16/5/05
17:03
Página 46
Sistemas operativos. Aspectos internos y principios de diseño
1000
8
7
6
5
4
3
Coste combinado relativo (CS /C2)
01-Capitulo 1
(C1/C2) = 1000
2
100
8
7
6
5
4
3
(C1/C2) = 100
2
10
8
7
6
5
4
(C1/C2) = 10
3
2
1
5
6
7 8 9
Figura 1.22.
10
2
3
4
5
6
7 8 9
2
100
Tamaño relativo de los dos niveles (D2/D1)
3
4
5
6
7 8
1000
Relación del coste medio de la memoria en relación con su tamaño relativo
para una memoria de dos niveles.
Para responder a estas preguntas, considere la cantidad T1/TS, que se conoce como eficiencia de
acceso. Es una medida que refleja hasta qué punto el tiempo de acceso medio (TS) está cerca del tiempo de acceso de M1 (T1). A partir de la ecuación (1.1):
T1
TS
=
1
1 + (1 – A)
T2
(1.3)
T1
En la Figura 1.23 se dibuja T1/TS como una función de la tasa de aciertos A, con la cantidad T2/T1
como parámetro. Parece necesitarse una tasa de aciertos en el intervalo de 0,8 a 0,9 para satisfacer el
requisito de rendimiento.
En este momento se puede expresar más exactamente la pregunta sobre el tamaño relativo de la
memoria. ¿Es razonable una tasa de aciertos mayor o igual a 0,8 para D1 << D2? Esto dependerá de
varios factores, incluyendo las características del software que se está ejecutando y los detalles de diseño de la memoria de dos niveles. El determinante principal es, evidentemente, el grado de proximidad. La Figura 1.24 sugiere el efecto de la proximidad en la tasa de aciertos. Claramente, si M1 tiene
el mismo tamaño que M2, la tasa de aciertos será igual a 1,0: todos los elementos en M2 también estarán siempre almacenados en M1. A continuación, supóngase que no hay proximidad, es decir, las
referencias son completamente aleatorias. En ese caso, la tasa de aciertos será una función estrictamente lineal del tamaño relativo de la memoria. Por ejemplo, si M1 tiene la mitad de tamaño de M2,
en cualquier momento la mitad de los elementos de M2 están también en M1 y la tasa de aciertos será
de 0,5. Sin embargo, en la práctica, hay algún grado de proximidad en las referencias. En la figura se
indican los efectos de una proximidad moderada y de una acusada.
16/5/05
17:03
Página 47
47
Introducción a los computadores
1
r1
0.1
r 10
0.01
r 100
r 1000
0.001
0.0
0.2
0.4
0.6
0.8
1.0
Tasa de aciertos A
Figura 1.23.
Eficiencia de acceso en función de la tasa de aciertos (r = T2/T1).
1.0
Proximidad
acusada
0.8
Tasa de aciertos
Eficiencia de acceso T1/Ts
01-Capitulo 1
Proximidad
moderada
0.6
0.4
Sin proximidad
0.2
0.0
0.0
Figura 1.24.
0.2
0.4
0.6
0.8
Tamaño relativo de las memorias (D1/D2)
1.0
Tasa de aciertos en función del tamaño de la memoria.
01-Capitulo 1
48
16/5/05
17:03
Página 48
Sistemas operativos. Aspectos internos y principios de diseño
En consecuencia, si hay una proximidad acusada, es posible lograr valores elevados en la tasa de
aciertos incluso con un tamaño de memoria de nivel superior relativamente pequeño. Por ejemplo,
numerosos estudios muestran que tamaños de cache bastante pequeños producirán una tasa de aciertos por encima del 0,75, con independencia del tamaño de la memoria principal (por ejemplo,
[AGAR98], [PRZY88], [STRE83] y [SMIT82]). Una cache con un tamaño en el intervalo entre 1K y
128K palabras es generalmente adecuada, mientras que actualmente la memoria principal se presenta
normalmente en un intervalo que se extiende entre cientos de megabytes hasta más de un gigabyte.
Cuando se considera la memoria virtual y la cache de disco, se pueden citar otros estudios que confirman el mismo fenómeno, es decir, que una memoria M1 relativamente pequeña produce un valor elevado en la tasa de aciertos gracias a la proximidad.
Esto conduce a la última pregunta listada anteriormente: ¿satisface el tamaño relativo de las dos
memorias el requisito de coste? La respuesta es claramente sí. Si se necesita sólo una memoria de nivel superior relativamente pequeña para alcanzar un buen rendimiento, el coste medio por bit de los
dos niveles de memoria se aproximará al de la memoria de nivel inferior.
APÉNDICE 1B CONTROL DE PROCEDIMIENTOS
Una técnica habitual para controlar la ejecución de llamadas a procedimiento y los retornos de los
mismos es utilizar una pila. Este apéndice resume las propiedades básicas de las pilas y revisa su uso
para el control de procedimientos.
IMPLEMENTACIÓN DE LA PILA
Una pila es un conjunto ordenado de elementos, tal que en cada momento solamente se puede acceder a uno de ellos (el más recientemente añadido). El punto de acceso se denomina cima de la pila El
número de elementos de la pila, o longitud de la pila, es variable. Por esta razón, se conoce también a
una pila como una lista de apilamiento o lista donde el último que entra es el primero que sale (LastIn First-Out, LIFO).
La implementación de una pila requiere que haya un conjunto de posiciones dedicado a almacenar los elementos de la pila. En la Figura 1.25 se muestra una técnica habitual. Se reserva en memoria
principal (o memoria virtual) un bloque contiguo de posiciones. La mayoría de las veces el bloque
está parcialmente lleno con elementos de la pila y el resto está disponible para el crecimiento de la
pila. Se necesitan tres direcciones para un funcionamiento adecuado, que habitualmente se almacenan
en registros del procesador:
• Puntero de pila. Contiene la dirección de la cima de la pila. Si se añade un elemento (APILA)
o se elimina (EXTRAE), el puntero se decrementa o se incrementa para contener la dirección
de la nueva cima de la pila.
• Base de la pila. Contiene la dirección de la posición inferior en el bloque reservado. Se trata
de la primera posición que se utiliza cuando se añade un elemento a una pila vacía. Si se hace
un intento de extraer un elemento cuando la pila está vacía, se informa del error.
• Límite de la pila. Contiene la dirección del otro extremo, o cima, del bloque reservado. Si se
hace un intento para apilar un elemento cuando la pila está llena, se indica el error.
Tradicionalmente, y en la mayoría de las máquinas actuales, la base de la pila está en el extremo
con la dirección más alta del bloque de pila reservado, y el límite está en el extremo con la dirección
más baja. Por tanto, la pila crece desde las direcciones más altas a las más bajas.
01-Capitulo 1
16/5/05
17:03
Página 49
Introducción a los computadores
Registros
de la CPU
Memoria
principal
Registros
de la CPU
Límite
de la pila
Elemento
superior
de la pila
Puntero
de pila
Segundo
elemento
de la pila
Base de
la pila
Libre
Bloque
reservado
para la pila
En uso
49
Memoria
principal
Límite
de la pila
Libre
Puntero
de pila
En uso
Bloque
reservado
para la pila
Base de
la pila
(a) Toda la pila en memoria
Figura 1.25.
(b) Los dos elementos superiores en registros
Organización habitual de la pila.
LLAMADAS Y RETORNOS DE PROCEDIMIENTOS
Una técnica habitual para gestionar las llamadas y los retornos de los procedimientos es utilizar una
pila. Cuando el procesador ejecuta una llamada, se almacena (apila) la dirección de retorno en la pila.
Cuando se ejecuta un retorno, se utiliza la dirección de la cima de la pila y se elimina (extrae) esa dirección de la pila. La Figura 1.27 muestra el uso de la pila para los procedimientos anidados presentados en la Figura 1.26.
Es también necesario con frecuencia pasar parámetros en una llamada a procedimiento. Estos se
podrían pasar en los registros. Otra posibilidad es almacenar los parámetros en la memoria justo después de las instrucciones de llamada. En este caso, el retorno debe estar en la posición siguiente a los
parámetros. Ambas técnicas tienen sus inconvenientes. Si se utilizan los registros, el programa llamado y el que realiza la llamada deben escribirse de manera que se asegure que los registros se utilizan
apropiadamente. El almacenamiento de parámetros en memoria hace difícil intercambiar un número
variable de parámetros.
Una estrategia más flexible para el paso de parámetros es la pila. Cuando el procesador ejecuta
una llamada, no sólo apila la dirección de retorno, sino también los parámetros que se desean pasar al
procedimiento llamado. El procedimiento invocado puede acceder a los parámetros en la pila. Al retornar, los parámetros de retorno se pueden almacenar también en la pila, debajo de la dirección de
retorno. El conjunto completo de parámetros, incluyendo la dirección de retorno, que se almacena en
una invocación de procedimiento se denomina marco de pila.
En la Figura 1.28 se muestra un ejemplo. El ejemplo se refiere a un procedimiento P en el que se
declaran las variables locales x1 y x2, y el procedimiento Q, al cual puede llamar P y en el que se declaran las variables y1 y y2. El primer elemento almacenado en cada marco de pila es un puntero al
principio del marco previo. Esta técnica se necesita si el número o la longitud de los parámetros que
01-Capitulo 1
50
16/5/05
17:03
Página 50
Sistemas operativos. Aspectos internos y principios de diseño
Direcciones
4000
4100
4101
Memoria principal
Programa
principal
LLAMADA a Proc1
4500
4600
4601
LLAMADA a Proc2
4650
4651
LLAMADA a Proc2
Procedimiento
Proc1
RETORNO
4800
Procedimiento
Proc2
RETORNO
(a) Llamadas y retornos
Figura 1.26.
(b) Secuencia de ejecución
Procedimientos anidados.
4601
(a) Contenido
inicial
de la pila
Figura 1.27.
4651
4101
4101
4101
4101
4101
(b) Después de
LLAMADA
a Proc1
(c) LLAMADA
inicial a Proc2
(d) Después
de
RETORNO
(e) Después
de LLAMADA
a Proc2
(f) Después
de
RETORNO
(g) Después
de
RETORNO
Uso de la pila para implementar los procedimientos anidados de la Figura 1.26.
se van a apilar es variable. A continuación, se almacena el punto de retorno del procedimiento que corresponde a este marco de pila. Finalmente, se reserva espacio en la cima del marco de pila para las
variables locales. Estas variables locales se pueden utilizar para el paso de parámetros. Por ejemplo,
supóngase que cuando P llama a Q le pasa un valor como parámetro. Este valor se podría almacenar
en la variable y1. Por tanto, en un lenguaje de alto nivel, habría una instrucción en la rutina P similar
a la siguiente:
LLAMADA Q(y1)
Cuando se ejecuta esta llamada, se crea un nuevo marco de pila para Q (Figura 1.28b), que incluye un puntero al marco de pila para P, la dirección de retorno de P, y dos variables locales para Q, una
01-Capitulo 1
16/5/05
17:03
Página 51
Introducción a los computadores
y2
51
Cima del puntero
de pila
y1
Dirección de retorno
Q:
Cima del puntero
de pila
x2
x2
x1
Puntero al
marco previo
Dirección de retorno
Puntero
al marco
actual
(a) P está activo
Figura 1.28.
Puntero
al marco
actual
x1
Dirección de retorno
P:
Puntero al
marco previo
P:
Puntero al
marco previo
(b) P ha llamado a Q
Crecimiento del marco de pila utilizando los procedimientos de ejemplo P y Q.
de las cuales se inicia con el valor pasado como parámetro desde P. La otra variable local, y2, es simplemente una variable local utilizada por Q en sus cálculos. En el siguiente apartado se analiza la necesidad de incluir las variables locales en el marco de pila.
PROCEDIMIENTOS REENTRANTES
Un concepto útil, particularmente en un sistema que da soporte a múltiples usuarios al mismo tiempo,
es el de los procedimientos reentrantes. Un procedimiento reentrante es aquél en el que una única copia del código del programa se puede compartir por múltiples usuarios durante el mismo periodo de
tiempo. El carácter reentrante de un procedimiento tiene dos aspectos fundamentales: el código del
programa no puede modificarse a sí mismo y los datos locales de cada usuario deben almacenarse separadamente. Un procedimiento reentrante puede ser interrumpido e invocado por el programa que
causó la interrupción y, a pesar de ello, ejecutarse correctamente al retornar al procedimiento. En un
sistema compartido, el carácter reentrante permite un uso más eficiente de la memoria principal. Se
mantiene una copia del código del programa en memoria principal, pero más de una aplicación puede
llamar al procedimiento.
Por tanto, un procedimiento reentrante debe tener una parte permanente (las instrucciones que
constituyen el procedimiento) y una parte temporal (un puntero de retorno al programa que realizó la
llamada, así como espacio para las variables locales usadas por el programa). Cada instancia de la
ejecución, llamada activación, de un procedimiento ejecutará el código en la parte permanente pero
debe tener su propia copia de los parámetros y las variables locales. La parte temporal asociada con
una activación en particular se denomina registro de activación.
La manera más conveniente de dar soporte a los procedimientos reentrantes es mediante una pila.
Cuando se llama a un procedimiento reentrante, el registro de activación del procedimiento se puede
almacenar en la pila. Por tanto, el registro de activación se convierte en parte del marco de pila que se
crea en la llamada a procedimiento.
01-Capitulo 1
16/5/05
17:03
Página 52
02-Capitulo 2
12/5/05
16:18
Página 53
CAPÍTULO
2
Introducción a los
sistemas operativos
2.1.
Objetivos y funciones de los sistemas operativos
2.2.
La evolución de los sistemas operativos
2.3.
Principales logros
2.4.
Desarrollos que han llevado a los sistemas operativos modernos
2.5.
Descripción global de Microsoft Windows
2.6.
Sistemas UNIX tradicionales
2.7.
Sistemas UNIX modernos
2.8.
Linux
2.9.
Lecturas y sitios web recomendados
2.10. Términos clave, cuestiones de repaso y problemas
02-Capitulo 2
54
12/5/05
16:18
Página 54
Sistemas operativos. Aspectos internos y principios de diseño
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
Comenzamos nuestro estudio de los sistemas operativos con una breve historia. La historia es interesante y también permite ofrecer una visión general de los principios de los sistemas operativos.
La primera sección examina los objetivos y funciones de los sistemas operativos. A continuación, se
muestra cómo han evolucionado los sistemas operativos, desde los primitivos sistemas en lotes
hasta los sofisticados sistemas multitarea y multiusuario. El resto del capítulo describe la historia y
características generales de los dos sistemas operativos que se utilizan como ejemplos a lo largo del
libro. Todo el material de este capítulo se cubre en mayor profundidad en el resto de los capítulos.
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
2.1. OBJETIVOS Y FUNCIONES DE LOS SISTEMAS OPERATIVOS
U
n sistema operativo es un programa que controla la ejecución de aplicaciones y programas y
que actúa como interfaz entre las aplicaciones y el hardware del computador. Se puede considerar que un sistema operativo tiene los siguientes tres objetivos:
• Facilidad de uso. Un sistema operativo facilita el uso de un computador.
• Eficiencia. Un sistema operativo permite que los recursos de un sistema de computación se
puedan utilizar de una manera eficiente.
• Capacidad para evolucionar. Un sistema operativo se debe construir de tal forma que se
puedan desarrollar, probar e introducir nuevas funciones en el sistema sin interferir con su
servicio.
Se van a examinar en orden estos tres aspectos de un sistema operativo.
EL SISTEMA OPERATIVO COMO UNA INTERFAZ DE USUARIO/COMPUTADOR
El hardware y software utilizados para proporcionar aplicaciones a los usuarios se pueden ver de forma jerárquica o en capas, tal y como se muestra en la Figura 2.1. El usuario de dichas aplicaciones, es
decir, el usuario final, normalmente no se preocupa por los detalles del hardware del computador. Por
tanto, el usuario final ve un sistema de computación en términos de un conjunto de aplicaciones. Una
aplicación se puede expresar en un lenguaje de programación y normalmente es desarrollada por un
programador de aplicaciones. Si un programador tuviera que desarrollar una aplicación como un conjunto de instrucciones en código máquina que se encargaran de controlar completamente el hardware
del computador, se enfrentaría a una labor extremadamente compleja. Para facilitar esta tarea, se proporcionan un conjunto de programas de sistema. Algunos de estos programas se conocen como utilidades. Estos programas utilizan frecuentemente funciones que asisten al programador en las fases de
creación de programas, gestión de ficheros y control de los dispositivos de E/S. Un programador hará
uso de estas utilidades cuando desarrolle una aplicación, y las aplicaciones, invocarán a las utilidades
durante su ejecución para llevar a cabo ciertas funciones. El programa de sistema más importante es
el sistema operativo. El sistema operativo oculta los detalles del hardware al programador y le proporciona una interfaz apropiada para utilizar el sistema. Actúa como mediador, haciendo más fácil al
programador y a la aplicación el acceso y uso de dichas utilidades y servicios.
De forma resumida, el sistema operativo proporciona normalmente servicios en las siguientes
áreas:
• Desarrollo de programas. El sistema operativo proporciona una variedad de utilidades y servicios, tales como editores y depuradores, para asistir al programador en la creación de los
02-Capitulo 2
12/5/05
16:18
Página 55
Introducción a los sistemas operativos
55
Usuario
final
Programador
Programas de aplicación
Diseñador
del sistema
operativo
Utilidades
Sistema operativo
Hardware del computador
Figura 2.1.
Capas y vistas de un sistema de computación.
programas. Normalmente, estos servicios se ofrecen en la forma de utilidades que, aunque no
forman parte del núcleo del sistema operativo, se ofrecen con dicho sistema y se conocen
como herramientas de desarrollo de programas de aplicación.
• Ejecución de programas. Se necesita realizar una serie de pasos para ejecutar un programa.
Las instrucciones y los datos se deben cargar en memoria principal. Los dispositivos de E/S y
los ficheros se deben inicializar, y otros recursos deben prepararse. Los sistemas operativos realizan estas labores de planificación en nombre del usuario.
• Acceso a dispositivos de E/S. Cada dispositivo de E/S requiere su propio conjunto peculiar de
instrucciones o señales de control para cada operación. El sistema operativo proporciona una
interfaz uniforme que esconde esos detalles de forma que los programadores puedan acceder a
dichos dispositivos utilizando lecturas y escrituras sencillas.
• Acceso controlado a los ficheros. Para el acceso a los ficheros, el sistema operativo debe reflejar una comprensión detallada no sólo de la naturaleza del dispositivo de E/S (disco, cinta),
sino también de la estructura de los datos contenidos en los ficheros del sistema de almacenamiento. Adicionalmente, en el caso de un sistema con múltiples usuarios, el sistema operativo
puede proporcionar mecanismos de protección para controlar el acceso a los ficheros.
• Acceso al sistema. Para sistemas compartidos o públicos, el sistema operativo controla el acceso al sistema completo y a recursos del sistema específicos. La función de acceso debe proporcionar protección a los recursos y a los datos, evitando el uso no autorizado de los usuarios
y resolviendo conflictos en el caso de conflicto de recursos.
• Detección y respuesta a errores. Se pueden dar gran variedad de errores durante la ejecución
de un sistema de computación. Éstos incluyen errores de hardware internos y externos, tales
como un error de memoria, o un fallo en un dispositivo; y diferentes errores software, tales
como la división por cero, el intento de acceder a una posición de memoria prohibida o la incapacidad del sistema operativo para conceder la solicitud de una aplicación. En cada caso, el
02-Capitulo 2
56
12/5/05
16:18
Página 56
Sistemas operativos. Aspectos internos y principios de diseño
sistema operativo debe proporcionar una respuesta que elimine la condición de error, suponiendo el menor impacto en las aplicaciones que están en ejecución. La respuesta puede oscilar entre finalizar el programa que causó el error hasta reintentar la operación o simplemente
informar del error a la aplicación.
• Contabilidad. Un buen sistema operativo recogerá estadísticas de uso de los diferentes recursos y monitorizará parámetros de rendimiento tales como el tiempo de respuesta. En cualquier
sistema, esta información es útil para anticipar las necesidades de mejoras futuras y para optimizar el sistema a fin de mejorar su rendimiento. En un sistema multiusuario, esta información
se puede utilizar para facturar a los diferentes usuarios.
EL SISTEMA OPERATIVO COMO GESTOR DE RECURSOS
Un computador es un conjunto de recursos que se utilizan para el transporte, almacenamiento y procesamiento de los datos, así como para llevar a cabo el control de estas funciones. El sistema operativo se encarga de gestionar estos recursos.
¿Se puede decir que es el sistema operativo quien controla el transporte, almacenamiento y procesamiento de los datos? Desde un punto de vista, la respuesta es afirmativa: gestionando los recursos
del computador, el sistema operativo tiene el control de las funciones básicas del mismo. Pero este
control se realiza de una forma curiosa. Normalmente, se habla de un mecanismo de control como
algo externo al dispositivo controlado, o al menos como algo que constituye una parte separada o distinta de dicho dispositivo. (Por ejemplo, un sistema de calefacción de una residencia se controla a través de un termostato, que está separado de los aparatos de generación y distribución de calor.) Este
no es el caso del sistema operativo, que es un mecanismo de control inusual en dos aspectos:
• Las funciones del sistema operativo actúan de la misma forma que el resto del software; es decir, se trata de un programa o conjunto de programas ejecutados por el procesador.
• El sistema operativo frecuentemente cede el control y depende del procesador para volver a
retomarlo.
De hecho, el sistema operativo es un conjunto de programas. Como otros programas, proporciona
instrucciones para el procesador. La principal diferencia radica en el objetivo del programa. El sistema operativo dirige al procesador en el uso de los otros recursos del sistema y en la temporización de
la ejecución de otros programas. No obstante, para que el procesador pueda realizar esto, el sistema
operativo debe dejar paso a la ejecución de otros programas. Por tanto, el sistema operativo deja el
control para que el procesador pueda realizar trabajo «útil» y de nuevo retoma el control para permitir
al procesador que realice la siguiente pieza de trabajo. Los mecanismos que se utilizan para llevar a
cabo esto quedarán más claros a lo largo del capítulo.
La Figura 2.2 muestra los principales recursos gestionados por el sistema operativo. Una porción
del sistema operativo se encuentra en la memoria principal. Esto incluye el kernel, o núcleo, que
contiene las funciones del sistema operativo más frecuentemente utilizadas y, en cierto momento,
otras porciones del sistema operativo actualmente en uso. El resto de la memoria principal contiene
programas y datos de usuario. La asignación de este recurso (memoria principal) es controlada de forma conjunta por el sistema operativo y el hardware de gestión de memoria del procesador, como se
verá. El sistema operativo decide cuándo un programa en ejecución puede utilizar un dispositivo de
E/S y controla el acceso y uso de los ficheros. El procesador es también un recurso, y el sistema operativo debe determinar cuánto tiempo de procesador debe asignarse a la ejecución de un programa de
usuario particular. En el caso de un sistema multiprocesador, esta decisión debe ser tomada por todos
los procesadores.
02-Capitulo 2
12/5/05
16:18
Página 57
Introducción a los sistemas operativos
57
Sistema de computación
Dispositivos de E/S
Memoria
Sofware
del sistema
operativo
Controlador de E/S
Impresoras,
teclados,
cámaras digitales,
etc.
Controlador de E/S
Programas
y datos
Controlador de E/S
Procesador
Procesador
Almacenamiento
OS
Programas
Datos
Figura 2.2.
El sistema operativo como gestor de recursos.
FACILIDAD DE EVOLUCIÓN DE UN SISTEMA OPERATIVO
Un sistema operativo importante debe evolucionar en el tiempo por las siguientes razones:
• Actualizaciones de hardware más nuevos tipos de hardware. Por ejemplo, las primeras
versiones de los sistemas operativos UNIX e IBM OS/2 no empleaban un mecanismo de paginado porque ejecutaban en máquinas sin hardware de paginación. La paginación se presenta
brevemente, más adelante en este capítulo, y se discute detalladamente en el Capítulo 7. Versiones más recientes de estos sistemas operativos han cambiado esta faceta para explotar las
capacidades de paginación. Además, el uso de terminales gráficos y en modo página en lugar
de terminales de línea también afecta al diseño de los sistemas operativos. Por ejemplo, un terminal gráfico normalmente permite al usuario ver varias aplicaciones al mismo tiempo a través del uso de «ventanas» en la pantalla. Esto requiere una gestión más sofisticada por parte
del sistema operativo.
• Nuevos servicios. En respuesta a la demanda del usuario o en respuesta a las necesidades de
los gestores de sistema, el sistema operativo debe ofrecer nuevos servicios. Por ejemplo, si es
difícil mantener un buen rendimiento con las herramientas existentes, se pueden añadir al sistema operativo nuevas herramientas de medida y control. Como segundo ejemplo, la mayoría
de las aplicaciones requieren el uso de ventanas en la pantalla. Esta característica requiere actualizaciones importantes en el sistema operativo si éste no soporta ventanas.
• Resolución de fallos. Cualquier sistema operativo tiene fallos. Estos fallos se descubren con el
transcurso del tiempo y se resuelven. Por supuesto, esto implica la introducción de nuevos fallos.
02-Capitulo 2
58
12/5/05
16:18
Página 58
Sistemas operativos. Aspectos internos y principios de diseño
La necesidad de cambiar regularmente un sistema operativo introduce ciertos requisitos en su diseño. Un hecho obvio es que el sistema debe tener un diseño modular, con interfaces entre los módulos claramente definidas, y que debe estar bien documentado. Para programas grandes, tal como el típico sistema operativo contemporáneo, llevar a cabo una modularización sencilla no es adecuado
[DENN80a]. Se detallará este tema más adelante en el capítulo.
2.2. LA EVOLUCIÓN DE LOS SISTEMAS OPERATIVOS
Para comprender los requisitos claves de un sistema operativo y el significado de las principales características de un sistema operativo contemporáneo, es útil considerar la evolución de los sistemas
operativos a lo largo de los años.
PROCESAMIENTO SERIE
Con los primeros computadores, desde finales de los años 40 hasta mediados de los años 50, el programador interaccionaba directamente con el hardware del computador; no existía ningún sistema
operativo. Estas máquinas eran utilizadas desde una consola que contenía luces, interruptores, algún
dispositivo de entrada y una impresora. Los programas en código máquina se cargaban a través del
dispositivo de entrada (por ejemplo, un lector de tarjetas). Si un error provocaba la parada del programa, las luces indicaban la condición de error. El programador podía entonces examinar los registros
del procesador y la memoria principal para determinar la causa de error. Si el programa terminaba de
forma normal, la salida aparecía en la impresora.
Estos sistemas iniciales presentaban dos problemas principales:
• Planificación. La mayoría de las instalaciones utilizaban una plantilla impresa para reservar
tiempo de máquina. Típicamente, un usuario podía solicitar un bloque de tiempo en múltiplos
de media hora aproximadamente. Un usuario podía obtener una hora y terminar en 45 minutos; esto implicaba malgastar tiempo de procesamiento del computador. Por otro lado, el usuario podía tener problemas, si no finalizaba en el tiempo asignado y era forzado a terminar antes de resolver el problema.
• Tiempo de configuración. Un único programa, denominado trabajo, podía implicar la carga
en memoria del compilador y del programa en lenguaje de alto nivel (programa en código
fuente) y a continuación la carga y el enlace del programa objeto y las funciones comunes.
Cada uno de estos pasos podían suponer montar y desmontar cintas o configurar tarjetas. Si
ocurría un error, el desgraciado usuario normalmente tenía que volver al comienzo de la secuencia de configuración. Por tanto, se utilizaba una cantidad considerable de tiempo en configurar el programa que se iba a ejecutar.
Este modo de operación puede denominarse procesamiento serie, para reflejar el hecho de que los
usuarios acceden al computador en serie. A lo largo del tiempo, se han desarrollado varias herramientas de software de sistemas con el fin de realizar el procesamiento serie más eficiente. Estas herramientas incluyen bibliotecas de funciones comunes, enlazadores, cargadores, depuradores, y rutinas
de gestión de E/S disponibles como software común para todos los usuarios.
SISTEMAS EN LOTES SENCILLOS
Las primeras máquinas eran muy caras, y por tanto, era importante maximizar su utilización. El tiempo malgastado en la planificación y configuración de los trabajos era inaceptable.
02-Capitulo 2
12/5/05
16:18
Página 59
Introducción a los sistemas operativos
59
Para mejorar su utilización, se desarrolló el concepto de sistema operativo en lotes. Parece ser
que el primer sistema operativo en lotes (y el primer sistema operativo de cualquier clase) fue desarrollado a mediados de los años 50 por General Motors para el uso de un IBM 701 [WEIZ81]. El concepto fue subsecuentemente refinado e implementado en el IBM 704 por un número de clientes de
IBM. A principios de los años 60, un número de vendedores había desarrollado sistemas operativos
en lote para sus sistemas de computación. IBSYS, el sistema operativo de IBM para los computadores 7090/7094, es particularmente notable por su gran influencia en otros sistemas.
La idea central bajo el esquema de procesamiento en lotes sencillo es el uso de una pieza de software denomina monitor. Con este tipo de sistema operativo, el usuario no tiene que acceder directamente a la máquina. En su lugar, el usuario envía un trabajo a través de una tarjeta o cinta al operador
del computador, que crea un sistema por lotes con todos los trabajos enviados y coloca la secuencia
de trabajos en el dispositivo de entrada, para que lo utilice el monitor. Cuando un programa finaliza
su procesamiento, devuelve el control al monitor, punto en el cual dicho monitor comienza la carga
del siguiente programa.
Para comprender cómo funciona este esquema, se puede analizar desde dos puntos de vista: el del
monitor y el del procesador.
• Punto de vista del monitor. El monitor controla la secuencia de eventos. Para ello, una gran
parte del monitor debe estar siempre en memoria principal y disponible para la ejecución (Figura 2.3). Esta porción del monitor se denomina monitor residente. El resto del monitor está
formado por un conjunto de utilidades y funciones comunes que se cargan como subrutinas en
el programa de usuario, al comienzo de cualquier trabajo que las requiera. El monitor lee de
uno en uno los trabajos desde el dispositivo de entrada (normalmente un lector de tarjetas o
dispositivo de cinta magnética). Una vez leído el dispositivo, el trabajo actual se coloca en el
área de programa de usuario, y se le pasa el control. Cuando el trabajo se ha completado, devuelve el control al monitor, que inmediatamente lee el siguiente trabajo. Los resultados de
cada trabajo se envían a un dispositivo de salida, (por ejemplo, una impresora), para entregárselo al usuario.
Procesamiento de
interruptores
Manejadores de
dispositivos
Monitor
Secuenciamientos
de trabajo
Intérprete de
lenguaje de control
Límite
Área de
programa
de usuario
Figura 2.3.
Disposición de memoria de un monitor residente.
02-Capitulo 2
60
12/5/05
16:18
Página 60
Sistemas operativos. Aspectos internos y principios de diseño
• Punto de vista del procesador. En un cierto punto, el procesador ejecuta instrucciones de la
zona de memoria principal que contiene el monitor. Estas instrucciones provocan que se lea el
siguiente trabajo y se almacene en otra zona de memoria principal. Una vez que el trabajo se
ha leído, el procesador encontrará una instrucción de salto en el monitor que le indica al procesador que continúe la ejecución al inicio del programa de usuario. El procesador entonces
ejecutará las instrucciones del programa usuario hasta que encuentre una condición de finalización o de error. Cualquiera de estas condiciones hace que el procesador ejecute la siguiente
instrucción del programa monitor. Por tanto, la frase «se pasa el control al trabajo» simplemente significa que el procesador leerá y ejecutará instrucciones del programa de usuario, y la
frase «se devuelve el control al monitor» indica que el procesador leerá y ejecutará instrucciones del programa monitor.
El monitor realiza una función de planificación: en una cola se sitúa un lote de trabajos, y los trabajos se ejecutan lo más rápidamente posible, sin ninguna clase de tiempo ocioso entre medias. Además, el monitor mejora el tiempo de configuración de los trabajos. Con cada uno de los trabajos, se
incluye un conjunto de instrucciones en algún formato primitivo de lenguaje de control de trabajos
(Job Control Language, JCL). Se trata de un tipo especial de lenguaje de programación utilizado para
dotar de instrucciones al monitor. Un ejemplo sencillo consiste en un usuario enviando un programa
escrito en el lenguaje de programación FORTRAN más algunos datos que serán utilizados por el programa. Además del código en FORTRAN y las líneas de datos, el trabajo incluye instrucciones de
control del trabajo, que se representan mediante líneas que comienzan mediante el símbolo `$´. El
formato general del trabajo tiene el siguiente aspecto:
$JOB
$FTN
Instrucciones FORTRAN
$LOAD
$RU
N
Datos
$END
Para ejecutar este trabajo, el monitor lee la línea $FTN y carga el compilador apropiado de su sistema de almacenamiento (normalmente una cinta). El compilador traduce el programa de usuario en
código objeto, el cual se almacena en memoria en el sistema de almacenamiento. Si se almacena en
memoria, la operación se denomina «compilar, cargar, y ejecutar». En el caso de que se almacene en
una cinta, se necesita utilizar la instrucción $LOAD. El monitor lee esta instrucción y recupera el
control después de la operación de compilación. El monitor invoca al cargador, que carga el programa objeto en memoria (en el lugar del compilador) y le transfiere el control. De esta forma, se puede
compartir un gran segmento de memoria principal entre diferentes subsistemas, aunque sólo uno de
ellos se puede ejecutar en un momento determinado.
Durante la ejecución del programa de usuario, cualquier instrucción de entrada implica la lectura
de una línea de datos. La instrucción de entrada del programa de usuario supone la invocación de una
rutina de entrada, que forma parte del sistema operativo. La rutina de entrada comprueba que el pro-
02-Capitulo 2
12/5/05
16:18
Página 61
Introducción a los sistemas operativos
61
grama no lea accidentalmente una línea JCL. Si esto sucede, se genera un error y se transfiere el control al monitor. Al finalizar el trabajo de usuario, el monitor analizará todas las líneas de entrada hasta
que encuentra la siguiente instrucción JCL. De esta forma, el sistema queda protegido frente a un programa con excesivas o escasas líneas de datos.
El monitor, o sistema operativo en lotes, es simplemente un programa. Éste confía en la habilidad
del procesador para cargar instrucciones de diferentes porciones de la memoria principal que de forma alternativa le permiten tomar y abandonar el control. Otras características hardware son también
deseables:
• Protección de memoria. Durante la ejecución del programa de usuario, éste no debe alterar el
área de memoria que contiene el monitor. Si esto ocurriera, el hardware del procesador debe
detectar un error y transferir el control al monitor. El monitor entonces abortará el trabajo, imprimirá un mensaje de error y cargará el siguiente trabajo.
• Temporizador. Se utiliza un temporizador para evitar que un único trabajo monopolice el sistema. Se activa el temporizador al comienzo de cada trabajo. Si el temporizador expira, se
para el programa de usuario, y se devuelve el control al monitor.
• Instrucciones privilegiadas. Ciertas instrucciones a nivel de máquina se denominan privilegiadas y sólo las puede ejecutar el monitor. Si el procesador encuentra estas instrucciones
mientras ejecuta un programa de usuario, se produce un error provocando que el control se
transfiera al monitor. Entre las instrucciones privilegiadas se encuentran las instrucciones de
E/S, que permiten que el monitor tome control de los dispositivos de E/S. Esto evita, por
ejemplo, que un programa de usuario de forma accidental lea instrucciones de control de trabajos del siguiente trabajo. Si un programa de usuario desea realizar operaciones de E/S, debe
solicitar al monitor que realice las operaciones por él.
• Interrupciones. Los modelos de computadores iniciales no tenían esta capacidad. Esta característica proporciona al sistema operativo más flexibilidad para dejar y retomar el control desde los programas de usuario.
Ciertas consideraciones sobre la protección de memoria y las instrucciones privilegiadas llevan al
concepto de modos de operación. Un programa de usuario ejecuta en modo usuario, en el cual los
usuarios no pueden acceder a ciertas áreas de memoria y no puede ejecutar ciertas instrucciones. El
monitor ejecuta en modo sistema, o lo que se denomina modo núcleo, en el cual se pueden ejecutar
instrucciones privilegiadas y se puede acceder a áreas de memoria protegidas.
Por supuesto, se puede construir un sistema operativo sin estas características. Pero los fabricantes de computadores rápidamente se dieron cuenta de que los resultados no eran buenos, y de este
modo, se construyeron sistemas operativos en lotes primitivos con estas características hardware.
Con un sistema operativo en lotes, el tiempo de máquina alterna la ejecución de programas de
usuario y la ejecución del monitor. Esto implica dos sacrificios: el monitor utiliza parte de la memoria
principal y consume parte del tiempo de máquina. Ambas situaciones implican una sobrecarga. A pesar de esta sobrecarga, el sistema en lotes simple mejora la utilización del computador.
SISTEMAS EN LOTES MULTIPROGRAMADOS
El procesador se encuentra frecuentemente ocioso, incluso con el secuenciamiento de trabajos automático que proporciona un sistema operativo en lotes simple. El problema consiste en que los
dispositivos de E/S son lentos comparados con el procesador. La Figura 2.4 detalla un cálculo representativo de este hecho, que corresponde a un programa que procesa un fichero con registros y
02-Capitulo 2
62
12/5/05
16:18
Página 62
Sistemas operativos. Aspectos internos y principios de diseño
realiza de media 100 instrucciones máquina por registro. En este ejemplo, el computador malgasta
aproximadamente el 96% de su tiempo esperando a que los dispositivos de E/S terminen de transferir datos a y desde el fichero. La Figura 2.5a muestra esta situación, donde existe un único programa, lo que se denomina monoprogramación. El procesador ejecuta durante cierto tiempo hasta
que alcanza una instrucción de E/S. Entonces debe esperar que la instrucción de E/S concluya antes de continuar.
Leer un registro del fichero
15 ms
Ejecutar 100 instrucciones
1 ms
15 ms
Escribir un registro al fichero
31 ms
TOTAL
Porcentaje de utilización de la CPU =
1
= 0,032 = 3,2%
31
Figura 2.4.
Programa A
Ejemplo de utilización del sistema.
Ejecución
Ejecución
Espera
Espera
Tiempo
(a) Monoprogramación
Programa A
Ejecución
Ejecución
Espera
Espera
Programa B
Espera Ejecución
Espera
Ejecución
Espera
Combinado
Ejecución Ejecución
A
B
Espera
Ejecución Ejecución
A
B
Espera
Tiempo
(b) Multiprogramador con dos programas
Programa A
Espera
Ejecución
Programa B
Espera Ejecución
Programa C
Espera
Combinado
Espera
Ejecución
Ejecución Ejecución Ejecución
A
B
C
Espera
Ejecución
Espera
Ejecución
Ejecución
Espera
Ejecución Ejecución Ejecución
A
B
C
Espera
Espera
Espera
Tiempo
(c) Multiprogramador con tres programas
Figura 2.5.
Ejemplo de multiprogramación.
02-Capitulo 2
12/5/05
16:18
Página 63
Introducción a los sistemas operativos
63
Esta ineficiencia puede evitarse. Se sabe que existe suficiente memoria para contener al sistema operativo (monitor residente) y un programa de usuario. Supóngase que hay espacio para el
sistema operativo y dos programas de usuario. Cuando un trabajo necesita esperar por la E/S, se
puede asignar el procesador al otro trabajo, que probablemente no esté esperando por una operación de E/S (Figura 2.5b). Más aún, se puede expandir la memoria para que albergue tres, cuatro
o más programas y pueda haber multiplexación entre todos ellos (Figura 2.5c). Este enfoque se
conoce como multiprogramación o multitarea. Es el tema central de los sistemas operativos
modernos.
Para mostrar los beneficios de la multiprogramación, se describe un ejemplo sencillo. Sea un
computador con 250 Mbytes de memoria disponible (sin utilizar por el sistema operativo), un disco,
un terminal y una impresora. Se envían simultáneamente a ejecución tres programas TRABAJO1,
TRABAJO2 y TRABAJO3, con las características listadas en la Tabla 2.1. Se asumen requisitos mínimos de procesador para los trabajos TRABAJO1 y TRABAJO3, así como uso continuo de impresora y disco por parte del trabajo TRABAJO3. En un entorno por lotes sencillo, estos trabajos se ejecutarán en secuencia. Por tanto, el trabajo TRABAJO1 se completará en 5 minutos. El trabajo
TRABAJO2 esperará estos 5 minutos y a continuación se ejecutará, terminando 15 minutos después.
El trabajo TRABAJO3 esperará estos 20 minutos y se completará 30 minutos después de haberse enviado. La media de utilización de recursos, productividad y tiempos de respuestas se muestran en la
columna de monoprogramación de la Tabla 2.2. La utilización de cada dispositivo se ilustra en la Figura 2.6a. Es evidente que existe una infrautilización de todos los recursos cuando se compara respecto al periodo de 30 minutos requerido.
Tabla 2.1.
Atributos de ejecución de ejemplos de programas.
TRABAJO 1
TRABAJO 2
TRABAJO 3
Tipo de trabajo
Computación pesada
Gran cantidad de E/S
Gran cantidad de E/S
Duración
5 minutos
15 minutos
10 minutos
Memoria requerida
50 M
100 M
75 M
¿Necesita disco?
No
No
Sí
¿Necesita terminal?
No
Sí
No
¿Necesita impresora?
No
No
Sí
Tabla 2.2.
Efectos de la utilización de recursos sobre la multiprogramación.
Monoprogramación
Multiprogramación
Uso de procesador
20%
40%
Uso de memoria
33%
67%
Uso de disco
33%
67%
Uso de impresora
33%
67%
Tiempo transcurrido
30 minutos
15 minutos
Productividad
6 trabajos/hora
12 trabajos/hora
Tiempo de respuesta medio
18 minutos
10 minutos
02-Capitulo 2
64
12/5/05
16:18
Página 64
Sistemas operativos. Aspectos internos y principios de diseño
Ahora supóngase que los trabajos se ejecutan concurrentemente bajo un sistema operativo
multiprogramado. Debido a que hay poco conflicto entre los trabajos, todos pueden ejecutar casi
en el mínimo tiempo mientras coexisten con los otros en el computador (asumiendo que se asigne
a los trabajos TRABAJO2 y TRABAJO3 suficiente tiempo de procesador para mantener sus operaciones de entrada y salida activas). El trabajo TRABAJO1 todavía requerirá 5 minutos para
completarse, pero al final de este tiempo, TRABAJO2 habrá completado un tercio de su trabajo y
TRABAJO3 la mitad. Los tres trabajos habrán finalizado en 15 minutos. La mejora es evidente al
examinar la columna de multiprogramación de la Tabla 2.2, obtenido del histograma mostrado en
la Figura 2.6b.
Del mismo modo que un sistema en lotes simple, un sistema en lotes multiprogramado también
debe basarse en ciertas características hardware del computador. La característica adicional más notable que es útil para la multiprogramación es el hardware que soporta las interrupciones de E/S y
DMA (Direct Memory Access: acceso directo a memoria). Con la E/S gestionada a través de interrupciones o DMA, el procesador puede solicitar un mandato de E/S para un trabajo y continuar con la
ejecución de otro trabajo mientras el controlador del dispositivo gestiona dicha operación de E/S.
Cuando esta última operación finaliza, el procesador es interrumpido y se pasa el control a un programa de tratamiento de interrupciones del sistema operativo. Entonces, el sistema operativo pasará el
control a otro trabajo.
Los sistemas operativos multiprogramados son bastante sofisticados, comparados con los sistemas monoprogramados. Para tener varios trabajos listos para ejecutar, éstos deben guardarse en memoria principal, requiriendo alguna forma de gestión de memoria. Adicionalmente, si varios trabajos
están listos para su ejecución, el procesador debe decidir cuál de ellos ejecutar; esta decisión requiere
un algoritmo para planificación. Estos conceptos se discuten más adelante en este capítulo.
SISTEMAS DE TIEMPO COMPARTIDO
Con el uso de la multiprogramación, el procesamiento en lotes puede ser bastante eficiente. Sin embargo, para muchos trabajos, es deseable proporcionar un modo en el cual el usuario interaccione directamente con el computador. De hecho, para algunos trabajos, tal como el procesamiento de transacciones, un modo interactivo es esencial.
Hoy en día, los computadores personales dedicados o estaciones de trabajo pueden cumplir, y frecuentemente lo hacen, los requisitos que necesita una utilidad de computación interactiva. Esta opción no estuvo disponible hasta los años 60, cuando la mayoría de los computadores eran grandes y
costosos. En su lugar, se desarrolló el concepto de tiempo compartido.
Del mismo modo que la multiprogramación permite al procesador gestionar múltiples trabajos en
lotes en un determinado tiempo, la multiprogramación también se puede utilizar para gestionar múltiples trabajos interactivos. En este último caso, la técnica se denomina tiempo compartido, porque se
comparte el tiempo de procesador entre múltiples usuarios. En un sistema de tiempo compartido,
múltiples usuarios acceden simultáneamente al sistema a través de terminales, siendo el sistema operativo el encargado de entrelazar la ejecución de cada programa de usuario en pequeños intervalos de
tiempo o cuantos de computación. Por tanto, si hay n usuarios activos solicitando un servicio a la vez,
cada usuario sólo verá en media 1/n de la capacidad de computación efectiva, sin contar la sobrecarga
introducida por el sistema operativo. Sin embargo, dado el tiempo de reacción relativamente lento de
los humanos, el tiempo de respuesta de un sistema diseñado adecuadamente debería ser similar al de
un computador dedicado.
Ambos tipos de procesamiento, en lotes y tiempo compartido, utilizan multiprogramación. Las
diferencias más importantes se listan en la Tabla 2.3.
0
5
Historia
del trabajo TRABAJO1
Impresora
Terminal
Disco
Memoria
15
Minutos
20
30
Histogramas de utilización.
Tiempo
25
TRABAJO3
0
10
Minutos
15
Tiempo
(b) Multiprogramación
5
TRABAJO2
TRABAJO3
Historia
del trabajo TRABAJO1
Impresora
Terminal
Disco
Memoria
0%
0%
100%
0%
100%
0%
100%
0%
100%
Introducción a los sistemas operativos
Figura 2.6.
(a) Monoprogramación
10
TRABAJO2
0%
0%
100%
0%
100%
0%
100%
0%
100%
CPU
16:18
CPU
100%
12/5/05
100%
02-Capitulo 2
Página 65
65
02-Capitulo 2
66
12/5/05
16:18
Página 66
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 2.3.
Multiprogramación en lotes frente a tiempo compartido.
Multiprogramación en lotes
Tiempo compartido
Objetivo principal
Maximizar el uso del procesador
Minimizar el tiempo de respuesta
Fuente de directivas
al sistema operativo
Mandatos del lenguaje
de control de trabajos
proporcionados por el trabajo
Mandatos introducidos
al terminal
Uno de los primeros sistemas operativos de tiempo compartido desarrollados fue el sistema
CTSS (Compatible Time-Sharing System) [CORB62], desarrollado en el MIT por un grupo conocido
como Proyecto MAC (Machine-Aided Cognition, o Multiple-Access Computers). El sistema fue inicialmente desarrollado para el IBM 709 en 1961 y más tarde transferido al IBM 7094.
Comparado con sistemas posteriores, CTSS es primitivo. El sistema ejecutó en una máquina con
memoria principal con 32.000 palabras de 36 bits, con el monitor residente ocupando 5000 palabras.
Cuando el control se asignaba a un usuario interactivo, el programa de usuario y los datos se cargaban en las restantes 27.000 palabras de memoria principal. Para arrancar, un programa siempre se
cargaba al comienzo de la palabra 5000; esto simplificaba tanto el monitor como la gestión de memoria. Un reloj del sistema generaba una interrupción cada 0,2 segundos aproximadamente. En cada interrupción de reloj, el sistema operativo retomaba el control y podía asignar el procesador a otro
usuario. Por tanto, a intervalos regulares de tiempo, el usuario actual podría ser desalojado y otro
usuario puesto a ejecutar. Para preservar el estado del programa de usuario antiguo, los programas de
usuario y los datos se escriben en el disco antes de que se lean los nuevos programas de usuario y
nuevos datos. Posteriormente, el código y los datos del programa de usuario antiguo se restauran en
memoria principal cuando dicho programa vuelve a ser planificado.
Para minimizar el tráfico de disco, la memoria de usuario sólo es escrita a disco cuando el programa entrante la sobreescribe. Este principio queda ilustrado en la Figura 2.7. Sean cuatro usuarios
interactivos con los siguiente requisitos de memoria:
• TRABAJO1: 15.000
• TRABAJO2: 20.000
• TRABAJO3: 5000
• TRABAJO4: 10.000
Inicialmente, el monitor carga el trabajo TRABAJO1 y le transfiere control (a). Después, el monitor decide transferir el control al trabajo TRABAJO2. Debido a que el TRABAJO2 requiere más
memoria que el TRABAJO1, se debe escribir primero el TRABAJO1 en disco, y a continuación debe
cargarse el TRABAJO2 (b). A continuación, se debe cargar el TRABAJO3 para ejecutarse. Sin embargo, debido a que el TRABAJO3 es más pequeño que el TRABAJO2, una porción de este último
queda en memoria, reduciendo el tiempo de escritura de disco (c). Posteriormente, el monitor decide
transferir el control de nuevo al TRABAJO1. Una porción adicional de TRABAJO2 debe escribirse
en disco cuando se carga de nuevo el TRABAJO1 en memoria (d). Cuando se carga el TRABAJO4,
parte del trabajo TRABAJO1 y la porción de TRABAJO2 permanecen en memoria (e). En este punto, si cualquiera de estos trabajos (TRABAJO1 o TRABAJO2) son activados, sólo se requiere una
carga parcial. En este ejemplo, es el TRABAJO2 el que ejecuta de nuevo. Esto requiere que el TRABAJO4 y la porción residente de el TRABAJO1 se escriban en el disco y la parte que falta de el
TRABAJO2 se lea (f).
02-Capitulo 2
12/5/05
16:18
Página 67
Introducción a los sistemas operativos
0
5000
Monitor
0
Monitor
5000
0
5000
10000
TRABAJO 1
TRABAJO 2
32000
25000
25000
Libre
32000
0
Monitor
TRABAJO 1
20000
25000
(TRABAJO 2)
(b)
0
Monitor
5000
(c)
0
5000
Monitor
TRABAJO 4
15000
20000
25000
TRABAJO 2
(TRABAJO 1)
(TRABAJO 2)
25000
Libre
Libre
32000
Libre
32000
(a)
5000
Monitor
TRABAJO 3
(TRABAJO 2)
20000
Libre
67
32000
(d)
(e)
Figura 2.7.
Libre
32000
(f)
Operación del CTSS.
La técnica utilizada por CTSS es primitiva comparada con las técnicas de tiempo compartido actuales, pero funcionaba. Era extremadamente sencilla, lo que minimizaba el tamaño del monitor. Debido a que un trabajo siempre se cargaba en la misma dirección de memoria, no había necesidad de
utilizar técnicas de reubicación en tiempo de carga (que se discutirán más adelante). El hecho de sólo
escribir en disco cuando es necesario, minimiza la actividad del disco. Ejecutando sobre el 7094,
CTSS permitía un número máximo de 32 usuarios.
La compartición de tiempo y la multiprogramación implican nuevos problemas para el sistema
operativo. Si existen múltiples trabajos en memoria, éstos deben protegerse para evitar que interfieran
entre sí, por ejemplo, a través de la modificación de los datos de los mismos. Con múltiples usuarios
interactivos, el sistema de ficheros debe ser protegido, de forma que sólo usuarios autorizados tengan
acceso a un fichero particular. También debe gestionarse los conflictos entre los recursos, tal como
impresoras y dispositivos de almacenamiento masivo. Éstos y otros problemas, con sus posibles soluciones, se describirán a lo largo de este libro.
2.3. PRINCIPALES LOGROS
Los sistemas operativos se encuentran entre las piezas de software más complejas jamás desarrolladas. Esto refleja el reto de intentar resolver la dificultad de alcanzar determinados objetivos, algunas
veces conflictivos, de conveniencia, eficiencia y capacidad de evolución. [DENN80a] propone cinco
principales avances teóricos en el desarrollo de los sistemas operativos:
•
•
•
•
•
Procesos.
Gestión de memoria.
Protección y seguridad de la información.
Planificación y gestión de los recursos.
Estructura del sistema.
02-Capitulo 2
68
12/5/05
16:18
Página 68
Sistemas operativos. Aspectos internos y principios de diseño
Cada avance se caracteriza por principios, o abstracciones, que se han desarrollado para resolver
problemas prácticos. Tomadas de forma conjunta, estas cinco áreas incluyen la mayoría de los aspectos clave de diseño e implementación de los sistemas operativos modernos. La breve revisión de estas
cinco áreas en esta sección sirve como una visión global de gran parte del resto del libro.
PROCESOS
El concepto de proceso es fundamental en la estructura de los sistemas operativos. Este término fue
utilizado por primera vez por los diseñadores del sistema Multics en los años 60 [DALE68]. Es un
término un poco más general que el de trabajo. Se han dado muchas definiciones del término
proceso, incluyendo:
• Un programa en ejecución.
• Una instancia de un programa ejecutándose en un computador.
• La entidad que se puede asignar o ejecutar en un procesador.
• Una unidad de actividad caracterizada por un solo hilo secuencial de ejecución, un estado actual, y un conjunto de recursos del sistema asociados.
Este concepto se aclarará a lo largo del texto.
Tres líneas principales de desarrollo del sistema de computación crearon problemas de temporización y sincronización que contribuyeron al desarrollo del concepto de proceso: operación en lotes
multiprogramados, tiempo compartido, y sistemas de transacciones de tiempo real. Como ya se ha
visto, la multiprogramación se diseñó para permitir el uso simultáneo del procesador y los dispositivos de E/S, incluyendo los dispositivos de almacenamiento, para alcanzar la máxima eficiencia. El
mecanismo clave es éste: en respuesta a las señales que indican la finalización de las transacciones de
E/S, el procesador es planificado para los diferentes programas que residen en memoria principal.
Una segunda línea de desarrollo fue el tiempo compartido de propósito general. En este caso, el
objetivo clave de diseño es responder a las necesidades del usuario y, debido a razones económicas,
ser capaz de soportar muchos usuarios simultáneamente. Estos objetivos son compatibles debido al
tiempo de reacción relativamente lento del usuario. Por ejemplo, si un usuario típico necesita una media de 2 segundos de tiempo de procesamiento por minuto, entonces una cantidad de 30 usuarios
aproximadamente podría compartir el mismo sistema sin interferencias notables. Por supuesto, la sobrecarga del sistema debe tenerse en cuenta para realizar estos cálculos.
Otra línea importante de desarrollo han sido los sistemas de procesamiento de transacciones de
tiempo real. En este caso, un cierto número de usuarios realizan consultas o actualizaciones sobre una
base de datos. Un ejemplo es un sistema de reserva para una compañía aérea. La principal diferencia
entre el sistema de procesamiento de transacciones y el sistema de tiempo real es que el primero está
limitado a una o unas pocas aplicaciones, mientras que los usuarios de un sistema de tiempo real pueden estar comprometidos en el desarrollo de programas, la ejecución de trabajos, y el uso de varias
aplicaciones. En ambos casos, el tiempo de respuesta del sistema es impresionante.
La principal herramienta disponible para programadores de sistema para el desarrollo de la inicial
multiprogramación y los sistemas interactivos multiusuario fue la interrupción. Cualquier trabajo podía suspender su actividad por la ocurrencia de un evento definido, tal como la finalización de una
operación de E/S. El procesador guardaría alguna forma de contexto (por ejemplo, el contador de
programa y otros registros) y saltaría a una rutina de tratamiento de interrupciones, que determinaría
la naturaleza de la interrupción, procesaría la interrupción, y después continuaría el procesamiento de
usuario con el trabajo interrumpido o algún otro trabajo.
02-Capitulo 2
12/5/05
16:18
Página 69
Introducción a los sistemas operativos
69
El diseño del software del sistema para coordinar estas diversas actividades resultó ser notablemente difícil. Con la progresión simultánea de muchos trabajos, cada uno de los cuales suponía la
realización de numerosos pasos para su ejecución secuencial, era imposible analizar todas las posibles combinaciones de secuencias de eventos. Con la ausencia de algún método sistemático de coordinación y cooperación entre las actividades, los programadores acudían a métodos «ad hoc» basados
en la comprensión del entorno que el sistema operativo tenía que controlar. Estos esfuerzos eran vulnerables frente a errores de programación sutiles, cuyos efectos sólo podían observarse cuando ciertas
extrañas secuencias de acciones ocurrían. Estos errores eran difíciles de diagnosticar, porque necesitaban distinguirse de los errores software y hardware de las aplicaciones. Incluso cuando se detectaba
el error, era difícil determinar la causa, porque las condiciones precisas bajo las cuales el error aparecía, eran difíciles de reproducir. En términos generales, existen cuatro causas principales de dichos
errores [DEBB80a]:
• Inapropiada sincronización. Es frecuente el hecho de que una rutina se suspenda esperando
por algún evento en el sistema. Por ejemplo, un programa que inicia una lectura de E/S debe
esperar hasta que los datos estén disponibles en un buffer antes de proceder. En este caso, se
necesita una señal procedente de otra rutina. El diseño inapropiado del mecanismo de señalización puede provocar que las señales se pierdan o se reciban señales duplicadas.
• Violación de la exclusión mutua. Frecuentemente, más de un programa o usuario intentan
hacer uso de recursos compartidos simultáneamente. Por ejemplo, dos usuarios podrían intentar editar el mismo fichero a la vez. Si estos accesos no se controlan, podría ocurrir un error.
Debe existir algún tipo de mecanismo de exclusión mutua que permita que sólo una rutina en
un momento determinado actualice un fichero. Es difícil verificar que la implementación de la
exclusión mutua es correcta en todas las posibles secuencias de eventos.
• Operación no determinista de un programa. Los resultados de un programa particular normalmente dependen sólo de la entrada a dicho programa y no de las actividades de otro programa en un sistema compartido. Pero cuando los programas comparten memoria, y sus ejecuciones son entrelazadas por el procesador, podrían interferir entre ellos, sobreescribiendo
zonas de memoria comunes de una forma impredecible. Por tanto, el orden en el que diversos
programas se planifican puede afectar a la salida de cualquier programa particular.
• Interbloqueos. Es posible que dos o más programas se queden bloqueados esperándose entre
sí. Por ejemplo, dos programas podrían requerir dos dispositivos de E/S para llevar a cabo una
determinada operación (por ejemplo, una copia de un disco o una cinta). Uno de los programas ha tomado control de uno de los dispositivos y el otro programa tiene control del otro dispositivo. Cada uno de ellos está esperando a que el otro programa libere el recurso que no poseen. Dicho interbloqueo puede depender de la temporización de la asignación y liberación de
recursos.
Lo que se necesita para enfrentarse a estos problemas es una forma sistemática de monitorizar y
controlar la ejecución de varios programas en el procesador. El concepto de proceso proporciona los
fundamentos. Se puede considerar que un proceso está formado por los siguientes tres componentes:
• Un programa ejecutable.
• Los datos asociados que necesita el programa (variables, espacio de trabajo, buffers, etc.).
• El contexto de ejecución del programa.
Este último elemento es esencial. El contexto de ejecución, o estado del proceso, es el conjunto
de datos interno por el cual el sistema operativo es capaz de supervisar y controlar el proceso. Esta
información interna está separada del proceso, porque el sistema operativo tiene información a la que
02-Capitulo 2
70
12/5/05
16:18
Página 70
Sistemas operativos. Aspectos internos y principios de diseño
el proceso no puede acceder. El contexto incluye toda la información que el sistema operativo necesita para gestionar el proceso y que el procesador necesita para ejecutar el proceso apropiadamente. El
contexto incluye el contenido de diversos registros del procesador, tales como el contador de programa y los registros de datos. También incluye información de uso del sistema operativo, como la prioridad del proceso y si un proceso está esperando por la finalización de un evento de E/S particular.
La Figura 2.8 indica una forma en la cual los procesos pueden gestionarse. Dos procesos, A y B,
se encuentran en una porción de memoria principal. Es decir, se ha asignado un bloque de memoria a
cada proceso, que contiene el programa, datos e información de contexto. Se incluye a cada proceso
en una lista de procesos que construye y mantiene el sistema operativo. La lista de procesos contiene
una entrada por cada proceso, e incluye un puntero a la ubicación del bloque de memoria que contiene el proceso. La entrada podría también incluir parte o todo el contexto de ejecución del proceso. El
resto del contexto de ejecución es almacenado en otro lugar, tal vez junto al propio proceso (como
queda reflejado en la Figura 2.8) o frecuentemente en una región de memoria separada. El registro índice del proceso contiene el índice del proceso que el procesador está actualmente controlando en la
lista de procesos. El contador de programa apunta a la siguiente instrucción del proceso que se va a
ejecutar. Los registros base y límite definen la región de memoria ocupada por el proceso: el registro
base contiene la dirección inicial de la región de memoria y el registro límite el tamaño de la región
(en bytes o palabras). El contador de programa y todas las referencias de datos se interpretan de for-
Memoria
principal
Registros del
procesador
Índice de procesos
i
PC
i
Lista de
procesos
Base
Límite
j
b
h
Otros
registros
Contexto
Proceso
A
Datos
Programa
(codigo)
b
Contexto
Proceso
B
Datos
h
Programa
(codigo)
Figura 2.8.
Implementación de procesos típica.
02-Capitulo 2
12/5/05
16:18
Página 71
Introducción a los sistemas operativos
71
ma relativa al registro base y no deben exceder el valor almacenado en el registro límite. Esto previene la interferencia entre los procesos.
En la Figura 2.8, el registro índice del proceso indica que el proceso B está ejecutando. El proceso A estaba ejecutando previamente, pero fue interrumpido temporalmente. Los contenidos de todos
los registros en el momento de la interrupción de A fueron guardados en su contexto de ejecución.
Posteriormente, el sistema operativo puede cambiar el proceso en ejecución y continuar la ejecución
del contexto de A. Cuando se carga el contador de programa con un valor que apunta al área de programa de A, el proceso A continuará la ejecución automáticamente.
Por tanto, el proceso puede verse como una estructura de datos. Un proceso puede estar en ejecución o esperando ejecutarse. El estado completo del proceso en un instante dado se contiene en su
contexto. Esta estructura permite el desarrollo de técnicas potentes que aseguran la coordinación y la
cooperación entre los procesos. Se pueden diseñar e incorporar nuevas características en el sistema
operativo (por ejemplo, la prioridad), expandiendo el contexto para incluir cualquier información
nueva que se utilice para dar soporte a dicha característica. A lo largo del libro, veremos un gran número de ejemplos donde se utiliza esta estructura de proceso para resolver los problemas provocados
por la multiprogramación o la compartición de recursos.
GESTIÓN DE MEMORIA
Un entorno de computación que permita programación modular y el uso flexible de los datos puede
ayudar a resolver mejor las necesidades de los usuarios. Los gestores de sistema necesitan un control
eficiente y ordenado de la asignación de los recursos. Para satisfacer estos requisitos, el sistema operativo tiene cinco responsabilidades principales de gestión de almacenamiento:
• Aislamiento de procesos. El sistema operativo debe evitar que los procesos independientes
interfieran en la memoria de otro proceso, tanto datos como instrucciones.
• Asignación y gestión automática. Los programas deben tener una asignación dinámica de
memoria por demanda, en cualquier nivel de la jerarquía de memoria. La asignación debe ser
transparente al programador. Por tanto, el programador no debe preocuparse de aspectos relacionados con limitaciones de memoria, y el sistema operativo puede lograr incrementar la eficiencia, asignando memoria a los trabajos sólo cuando se necesiten.
• Soporte a la programación modular. Los programadores deben ser capaces de definir módulos de programación y crear, destruir, y alterar el tamaño de los módulos dinámicamente.
• Protección y control de acceso. La compartición de memoria, en cualquier nivel de la jerarquía de memoria, permite que un programa direccione un espacio de memoria de otro proceso.
Esto es deseable cuando se necesita la compartición por parte de determinadas aplicaciones.
Otras veces, esta característica amenaza la integridad de los programas e incluso del propio
sistema operativo. El sistema operativo debe permitir que varios usuarios puedan acceder de
distintas formas a porciones de memoria.
• Almacenamiento a largo plazo. Muchas aplicaciones requieren formas de almacenar la información durante largos periodos de tiempo, después de que el computador se haya apagado.
Normalmente, los sistemas operativos alcanzan estos requisitos a través del uso de la memoria
virtual y las utilidades de los sistemas operativos. El sistema operativo implementa un almacenamiento a largo plazo, con la información almacenada en objetos denominados ficheros. El fichero es un
concepto lógico, conveniente para el programador y es una unidad útil de control de acceso y protección para los sistemas operativos.
02-Capitulo 2
72
12/5/05
16:18
Página 72
Sistemas operativos. Aspectos internos y principios de diseño
La memoria virtual es una utilidad que permite a los programas direccionar la memoria desde
un punto de vista lógico, sin importar la cantidad de memoria principal física disponible. La memoria virtual fue concebida como un método para tener múltiples trabajos de usuario residiendo en
memoria principal de forma concurrente, de forma que no exista un intervalo de tiempo de espera
entre la ejecución de procesos sucesivos, es decir, mientras un proceso se escribe en almacenamiento secundario y se lee el proceso sucesor. Debido a que los procesos varían de tamaño, si el
procesador planifica un determinado número de procesos, es difícil almacenarlos compactamente
en memoria principal. Se introdujeron los sistemas de paginación, que permiten que los procesos
se compriman en un número determinado de bloques de tamaño fijo, denominados páginas. Un
programa referencia una palabra por medio de una dirección virtual, que consiste en un número
de página y un desplazamiento dentro de la página. Cada página de un proceso se puede localizar
en cualquier sitio de memoria principal. El sistema de paginación proporciona una proyección dinámica entre las direcciones virtuales utilizadas en el programa y una dirección real, o dirección
física, de memoria principal.
Con el hardware de proyección dinámica disponible, el siguiente paso era eliminar el requisito de
que todas las páginas de un proceso residan en memoria principal simultáneamente. Todas las páginas
de un proceso se mantienen en disco. Cuando un proceso está en ejecución, algunas de sus páginas se
encuentran en memoria principal. Si se referencia una página que no está en memoria principal, el
hardware de gestión de memoria lo detecta y permite que la página que falta se cargue. Dicho esquema se denomina área de memoria virtual y está representado en la Figura 2.9.
El hardware del procesador, junto con el sistema operativo, dota al usuario de un «procesador
virtual» que tiene acceso a la memoria virtual. Este almacen podría ser un espacio de almacenamiento lineal o una colección de segmentos, que son bloques de longitud variable de direcciones
contiguas. En ambos casos, las instrucciones del lenguaje de programación pueden referenciar al
programa y a las ubicaciones de los datos en el área de memoria virtual. El aislamiento de los procesos se puede lograr dando a cada proceso una única área de memoria virtual, que no se solape con
otras áreas. La compartición de memoria se puede lograr a través de porciones de dos espacios de
memoria virtual que se solapan. Los ficheros se mantienen en un almacenamiento a largo plazo. Los
ficheros o parte de los mismos se pueden copiar en la memoria virtual para que los programas los
manipulen.
La Figura 2.10 destaca los aspectos de direccionamiento de un esquema de memoria virtual. El
almacenamiento está compuesto por memoria principal directamente direccionable (a través de instrucciones máquina) y memoria auxiliar de baja velocidad a la que se accede de forma indirecta, cargando bloques de la misma en memoria principal. El hardware de traducción de direcciones (memory
management unit: unidad de gestión de memoria) se interpone entre el procesador y la memoria. Los
programas hacen referencia a direcciones virtuales, que son proyectadas sobre direcciones reales de
memoria principal. Si una referencia a una dirección virtual no se encuentra en memoria física, entonces una porción de los contenidos de memoria real son llevados a la memoria auxiliar y los contenidos de la memoria real que se están buscando, son llevados a memoria principal. Durante esta tarea, el proceso que generó la dirección se suspende. El diseñador del sistema operativo necesita
desarrollar un mecanismo de traducción de direcciones que genere poca sobrecarga y una política de
asignación de almacenamiento que minimice el tráfico entre los diferentes niveles de la jerarquía de
memoria.
PROTECCIÓN Y SEGURIDAD DE INFORMACIÓN
El crecimiento del uso de los sistemas de tiempo compartido y, más recientemente, las redes de computadores ha originado un incremento de la preocupación por la protección de la información. La naturaleza de las amenazas que conciernen a una organización variarán enormemente dependiendo de
02-Capitulo 2
12/5/05
16:18
Página 73
Introducción a los sistemas operativos
73
A.1
A.0
A.2
A.5
B.0
B.1
B.2
B.3
A.7
A.9
0
0
1
1
2
2
3
3
4
4
5
5
6
6
7
Programa de
usuario
B
8
A.8
9
10
Programa de
usuario
A
B.5
B.6
Memoria principal
La memoria principal está formada
por varios marcos de tamaño fijo,
cada uno de ellos igual al tamaño
de una página. Para que se ejecute
un programa, algunas o todas las
páginas se deben encontrar en
memoria principal.
Figura 2.9.
Disco
La memoria secundaria (disco) puede
contener muchas páginas de tamaño
fijo. Un programa de usuario está
formado por varias páginas. Las páginas
de todos los programas más el sistema
operativo se encuentran en disco, ya que
son ficheros.
Conceptos de memoria virtual.
las circunstancias. Sin embargo, hay algunas herramientas de propósito general que se pueden utilizar
en los computadores y sistemas operativos para soportar una gran variedad de mecanismos de protección y seguridad. En general, el principal problema es el control del acceso a los sistemas de computación y a la información almacenada en ellos.
La mayoría del trabajo en seguridad y protección relacionado con los sistemas operativos se puede agrupar de forma genérica en cuatro categorías:
• Disponibilidad. Relacionado con la protección del sistema frente a las interrupciones.
• Confidencialidad. Asegura que los usuarios no puedan leer los datos sobre los cuales no tienen autorización de acceso.
• Integridad de los datos. Protección de los datos frente a modificaciones no autorizadas.
• Autenticidad. Relacionado con la verificación apropiada de la identidad de los usuarios y la
validez de los mensajes o los datos.
02-Capitulo 2
74
12/5/05
16:18
Página 74
Sistemas operativos. Aspectos internos y principios de diseño
Procesador
Dirección
virtual
Unidad de
gestión
de memoria
Dirección
real
Memoria
principal
Dirección
de disco
Memoria
secundaria
Figura 2.10.
Direccionamiento de memoria virtual.
PLANIFICACIÓN Y GESTIÓN DE LOS RECURSOS
Una responsabilidad clave de los sistemas operativos es la gestión de varios recursos disponibles para
ellos (espacio de memoria principal, dispositivos de E/S, procesadores) y para planificar su uso por
parte de los distintos procesos activos. Cualquier asignación de recursos y política de planificación
debe tener en cuenta tres factores:
• Equitatividad. Normalmente, se desea que todos los procesos que compiten por un determinado recurso, se les conceda un acceso equitativo a dicho recurso. Esto es especialmente cierto
para trabajos de la misma categoría, es decir, trabajos con demandas similares.
• Respuesta diferencial. Por otro lado, el sistema operativo puede necesitar discriminar entre
diferentes clases de trabajos con diferentes requisitos de servicio. El sistema operativo debe
tomar las decisiones de asignación y planificación con el objetivo de satisfacer el conjunto total de requisitos. Además, debe tomar las decisiones de forma dinámica. Por ejemplo, si un
proceso está esperando por el uso de un dispositivo de E/S, el sistema operativo puede intentar
planificar este proceso para su ejecución tan pronto como sea posible a fin de liberar el dispositivo para posteriores demandas de otros procesos.
• Eficiencia. El sistema operativo debe intentar maximizar la productividad, minimizar el tiempo de respuesta, y, en caso de sistemas de tiempo compartido, acomodar tantos usuarios como
sea posible. Estos criterios entran en conflicto; encontrar un compromiso adecuado en una situación particular es un problema objeto de la investigación sobre sistemas operativos.
La planificación y la gestión de recursos son esencialmente problemas de investigación, y se pueden aplicar los resultados matemáticos de esta disciplina. Adicionalmente, medir la actividad del sistema es importante para ser capaz de monitorizar el rendimiento y realizar los ajustes correspondientes.
La Figura 2.11 sugiere los principales elementos del sistema operativo relacionados con la planificación de procesos y la asignación de recursos en un entorno de multiprogramación. El sistema operativo mantiene un número de colas, cada una de las cuales es simplemente una lista de procesos esperando por algunos recursos. La cola a corto plazo está compuesta por procesos que se encuentran
02-Capitulo 2
12/5/05
16:18
Página 75
Introducción a los sistemas operativos
75
en memoria principal (o al menos una porción mínima esencial de cada uno de ellos está en memoria
principal) y están listos para ejecutar, siempre que el procesador esté disponible. Cualquiera de estos
procesos podría usar el procesador a continuación. Es responsabilidad del planificador a corto plazo,
o dispatcher, elegir uno de ellos. Una estrategia común es asignar en orden a cada proceso de la cola
un intervalo de tiempo; esta técnica se conoce como round-robin o turno rotatorio. En efecto, la
técnica de turno rotatorio emplea una cola circular. Otra estrategia consiste en asignar niveles de prioridad a los distintos procesos, siendo el planificador el encargado de elegir los procesos en orden de
prioridad.
La cola a largo plazo es una lista de nuevos trabajos esperando a utilizar el procesador. El sistema
operativo añade trabajos al sistema transfiriendo un proceso desde la cola a largo plazo hasta la cola a
corto plazo. En este punto, se debe asignar una porción de memoria principal al proceso entrante. Por
tanto, el sistema operativo debe estar seguro de que no sobrecarga la memoria o el tiempo de procesador admitiendo demasiados procesos en el sistema. Hay una cola de E/S por cada dispositivo de E/S.
Más de un proceso puede solicitar el uso del mismo dispositivo de E/S. Todos los procesos que esperan utilizar dicho dispositivo, se encuentran alineados en la cola del dispositivo. De nuevo, el sistema
operativo debe determinar a qué proceso le asigna un dispositivo de E/S disponible.
Si ocurre una interrupción, el sistema operativo recibe el control del procesador a través de un
manejador de interrupciones. Un proceso puede invocar específicamente un servicio del sistema operativo, tal como un manejador de un dispositivo de E/S, mediante una llamada a sistema. En este
caso, un manejador de la llamada a sistema es el punto de entrada al sistema operativo. En cualquier
caso, una vez que se maneja la interrupción o la llamada a sistema, se invoca al planificador a corto
plazo para que seleccione un proceso para su ejecución.
Lo que se ha detallado hasta ahora es una descripción funcional; los detalles y el diseño modular
de esta porción del sistema operativo difiere en los diferentes sistemas. Gran parte del esfuerzo en la
investigación y desarrollo de los sistemas operativos ha sido dirigido a la creación de algoritmos de
planificación y estructuras de datos que proporcionen equitatividad, respuesta diferencial y eficiencia.
Sistema operativo
Llamada a
sistema desde
el proceso
Manejador de
llamadas al sistema
(código)
Interrupción
desde el
proceso
Interrupción
de E/S
Manejador de
interrupciones
(código)
Cola
a largo
plazo
Cola
a corto
plazo
Colas
E/S
Planificador
a corto plazo
(código)
Pasa control
al proceso
Figura 2.11.
Elementos clave de un sistema operativo para la multiprogramación.
02-Capitulo 2
76
12/5/05
16:18
Página 76
Sistemas operativos. Aspectos internos y principios de diseño
ESTRUCTURA DEL SISTEMA
A medida que se han añadido más características a los sistemas operativos y el hardware subyacente
se ha vuelto más potente y versátil, han crecido el tamaño y la complejidad de los sistemas operativos. CTSS, que fue puesto en marcha en el MIT en 1963, estaba compuesto aproximadamente por
32.000 palabras de almacenamiento de 36 bits. OS/360, presentado por IBM un año después, tenía
más de un millón de instrucciones de máquina. En 1975, el sistema Multics, desarrollado por MIT y
los laboratorios Bell, había superado los 20 millones de instrucciones. Es cierto que más recientemente, se han introducido algunos sistemas operativos más sencillos para sistemas más pequeños, pero
éstos inevitablemente se hacen más complejos a medida que el hardware subyacente y los requisitos
de usuario se incrementan. Por tanto, el sistema UNIX de hoy es muchísimo más complejo que el sistema casi de juguete puesto en marcha por unos pocos programadores de gran talento a comienzo de
los años 70, y el sencillo sistema MS-DOS supuso el comienzo de los ricos y complejos sistemas
OS/2 y Windows. Por ejemplo, Windows NT 4.0 contiene 16 millones de líneas de código y Windows 2000 duplica este número.
El tamaño de un sistema operativo con un conjunto completo de características, y la dificultad del
problema que afronta dicho sistema, ha llevado a esta disciplina a cuatro desafortunados, aunque demasiado comunes, problemas. En primer lugar, los sistemas operativos se entregan tarde de forma
crónica. Esto implica la creación de nuevos sistemas operativos y frecuentes actualizaciones a viejos
sistemas. En segundo lugar, los sistemas tienen fallos latentes que deben ser planteados y resueltos.
En tercer lugar, el rendimiento no es frecuentemente el esperado. En último lugar, se ha comprobado
que es imposible construir un sistema operativo complejo que no sea vulnerable a una gran cantidad
de ataques de seguridad, incluyendo virus, gusanos y accesos no autorizados.
Para gestionar la complejidad de los sistemas operativos y eliminar estos problemas, se ha puesto
mucho énfasis en la estructura software del sistema operativo a lo largo de los años. Ciertos puntos
parecen obvios. El software debe ser modular. Esto ayudará a organizar el proceso de desarrollo de
software y limitará el esfuerzo de diagnosticar y corregir errores. Los módulos deben tener interfaces
bien definidas, y estas interfaces deben ser tan sencillas como sea posible. De nuevo, esto facilita la
programación. También facilita la evolución del sistema. Con mínimas interfaces entre los módulos,
se puede modificar un módulo con un mínimo impacto en otros módulos.
Para sistemas operativos grandes, que ejecutan desde millones a decenas de millones de líneas
de código, no es suficiente la programación modular. De hecho, ha habido un incremento en el uso
de los conceptos de capas jerárquicas y abstracción de información. La estructura jerárquica de un
sistema operativo moderno separa sus funciones de acuerdo a las características de su escala de
tiempo y su nivel de abstracción. Se puede ver el sistema como una serie de niveles. Cada nivel realiza un subconjunto relacionado de funciones requeridas por el sistema operativo. Dicho nivel confía
en los niveles inmediatamente inferiores para realizar funciones más primitivas y ocultar los detalles
de esas funciones. Cada nivel proporciona servicios a la capa inmediatamente superior. Idealmente,
estos niveles deben definirse de tal forma que los cambios en un nivel no requieran cambios en otros
niveles. Por tanto, de esta forma se ha descompuesto un problema en un número de subproblemas
más manejables.
En general, las capas inferiores tratan con una escala de tiempo menor. Algunas partes del sistema operativo deben interaccionar directamente con el hardware del computador, donde los eventos
pueden tener una escala de tiempo tan ínfima como unas pocas mil millonésimas de segundo. En el
otro extremo del espectro, algunas partes del sistema operativo se comunican con el usuario, que invoca mandatos en una escala de tiempo mucho más larga, tal vez unos pocos segundos. El uso de un
conjunto de niveles se adapta adecuadamente a este entorno.
La forma en que estos principios se aplican varía enormemente entre los sistemas operativos contemporáneos. Sin embargo, es útil en este punto, para el propósito de mostrar los sistemas operativos,
02-Capitulo 2
12/5/05
16:18
Página 77
Introducción a los sistemas operativos
77
presentar un modelo de sistema operativo jerárquico. Uno propuesto en [BROW84] y [DENN84] es
útil, aunque no corresponde a ningún sistema operativo particular. El modelo se define en la Tabla 2.4
y está compuesto por los siguientes niveles:
• Nivel 1. Está formado por circuitos electrónicos, donde los objetos tratados son registros, celdas de memoria, y puertas lógicas. Las operaciones definidas en estos objetos son acciones,
como poner a cero un registro o leer una posición de memoria.
• Nivel 2. El conjunto de instrucciones del procesador. Las operaciones a este nivel son aquéllas
permitidas en el conjunto de instrucciones de lenguaje máquina, como adición, resta, carga o
almacenamiento.
• Nivel 3. Añade el concepto de procedimiento o subrutina, más las operaciones de llamada y
retorno (call/return).
• Nivel 4. Introduce las interrupciones, que permiten al procesador guardar el contexto actual e
invocar una rutina de tratamiento de interrupciones.
Tabla 2.4.
Jerarquía de diseño del sistema operativo.
Nivel
Nombre
Objetos
Ejemplos de operaciones
13
Intérprete de mandatos
Entorno de programación
de usuario
Sentencias en lenguaje
del intérprete de mandatos
12
Procesos de usuario
Procesos de usuario
Salir, matar, suspender,
continuar
11
Directorios
Directorios
Crear, destruir, insertar
entrada, eliminar entrada,
buscar, listar
10
Dispositivos
Dispositivos externos,
como impresoras, pantallas
y teclados
Abrir, cerrar, leer, escribir
9
Sistema de ficheros
Ficheros
Crear, destruir, abrir,
cerrar, leer, escribir
8
Comunicaciones
Tuberías
Crear, destruir, abrir,
cerrar, leer, escribir
7
Memoria virtual
Segmentos, páginas
Leer, escribir, cargar
6
Almacenamiento
secundario local
Bloques de datos,
canales de dispositivo
Leer, escribir, asignar,
liberar
5
Procesos primitivos
Procesos primitivos,
semáforos, lista de
procesos listos
Suspender, continuar,
esperar, señalizar
4
Interrupciones
Programas de gestión
de interrupciones
Invocar, enmascarar,
desenmascarar, reintentar
3
Procedimientos
Procedimientos, pila
de llamadas, registro
de activación
Marcar la pila, llamar,
retornar
2
Conjunto de instrucciones
Pila de evaluación,
intérprete de microprogramas,
datos escalares y vectoriales
Cargar, almacenar, sumar,
restar, saltar
1
Circuitos electrónicos
Registros, puertas,
buses, etc.
Poner a 0, transferir,
activar, complementar
El área sombreado en gris representa al hardware.
02-Capitulo 2
78
12/5/05
16:18
Página 78
Sistemas operativos. Aspectos internos y principios de diseño
Estos cuatros primeros niveles no son parte del sistema operativo, sino que constituyen el hardware del procesador. Sin embargo, algunos elementos del sistema operativo se empiezan a mostrar en
estos niveles, por ejemplo, las rutinas de tratamiento de interrupciones. Es el Nivel 5 el que corresponde con el sistema operativo propiamente dicho y en el que los conceptos asociados a la multiprogramación aparecen.
• Nivel 5. En este nivel se introduce la noción de un proceso como un programa en ejecución.
Los requisitos fundamentales de los sistemas operativos para dar soporte a múltiples procesos incluyen la habilidad de suspender y continuar los procesos. Esto requiere guardar los registros de hardware de forma que se pueda interrumpir la ejecución de un proceso e iniciar la
de otro. Adicionalmente, si los procesos necesitan cooperar, se necesitan algunos métodos de
sincronización. Una de las técnicas más sencillas, y un concepto importante en el diseño de
los sistemas operativos, es el semáforo, una técnica de señalización sencilla que se analiza en
el Capítulo 5.
• Nivel 6. Trata los dispositivos de almacenamiento secundario del computador. En este nivel,
se dan la funciones para posicionar las cabezas de lectura/escritura y la transferencia real de
bloques. El Nivel 6 delega al Nivel 5 la planificación de la operación y la notificación al proceso solicitante de la finalización de la misma. Niveles más altos se preocupan de la dirección
en el disco de los datos requeridos y proporcionan una petición del bloque de datos apropiado
a un controlador de dispositivo del Nivel 5.
• Nivel 7. Crea un espacio de direcciones lógicas para los procesos. Este nivel organiza el espacio de direcciones virtuales en bloques que pueden moverse entre memoria principal y memoria secundaria. Tres esquemas son de uso común: aquéllos que utilizan páginas de tamaño fijo,
aquéllos que utilizan segmentos de longitud variable y aquéllos que utilizan ambos. Cuando
un bloque de memoria necesario no se encuentra en memoria principal, la lógica de este nivel
requiere una transferencia desde el Nivel 6.
Hasta este punto, el sistema operativo trata con los recursos de un único procesador. Comenzando
con el Nivel 8, el sistema operativo trata con objetos externos, como dispositivos periféricos y posiblemente redes y computadores conectados a la red. Los objetos de estos niveles superiores son objetos lógicos con nombre, que pueden compartirse entre procesos del mismo computador o entre múltiples computadores.
• Nivel 8. Trata con la comunicación de información y mensajes entre los procesos. Mientras
que el Nivel 5 proporciona un mecanismo de señalización primitivo que permite la sincronización de procesos, este nivel trata con una compartición de información más rica. Una de las
herramientas más potentes para este propósito es la tubería o pipe, que es un canal lógico para
el flujo de datos entre los procesos. Una tubería se define por su salida de un proceso y su entrada en otro proceso. Se puede también utilizar para enlazar dispositivos externos o ficheros a
procesos. El concepto se discute en el Capítulo 6.
• Nivel 9. Da soporte al almacenamiento a largo plazo en ficheros con nombre. En este nivel,
los datos en el almacenamiento secundario se ven en términos de entidades abstractas y con
longitud variable. Esto contrasta con la visión orientada a hardware del almacenamiento secundario en términos de pistas, sectores y bloques de tamaño fijo en el Nivel 6.
• Nivel 10. Proporciona acceso a los dispositivos externos utilizando interfaces estándar.
• Nivel 11. Es el nivel responsable para mantener la asociación entre los identificadores externos e internos de los recursos y objetos del sistema. El identificador externo es un nombre que
puede utilizar una aplicación o usuario. El identificador interno es una dirección de otro identificador que puede utilizarse por parte de los niveles inferiores del sistema operativo para lo-
02-Capitulo 2
12/5/05
16:18
Página 79
Introducción a los sistemas operativos
79
calizar y controlar un objeto. Estas asociaciones se mantienen en un directorio. Las entradas
no sólo incluyen la asociación entre identificadores externos/internos, sino también características como los derechos de acceso.
• Nivel 12. Proporciona una utilidad completa para dar soporte a los procesos. Este nivel va más
allá del proporcionado por el Nivel 5. En el Nivel 5, sólo se mantienen los contenidos de los
registros del procesador asociados con un proceso, más la lógica de los procesos de planificación. En el Nivel 12, se da soporte a toda la información necesaria para la gestión ordenada de
los procesos. Esto incluye el espacio de direcciones virtuales de los procesos, una lista de objetos y procesos con la cual puede interactuar y las restricciones de esta interacción, parámetros pasados al proceso en la creación. También se incluye cualquier otra característica que pudiera utilizar el sistema operativo para controlar el proceso.
• Nivel 13. Proporciona una interfaz del sistema operativo al usuario. Se denomina shell (caparazón), porque separa al usuario de los detalles de los sistemas operativos y presenta el sistema
operativo simplemente como una colección de servicios. El shell acepta mandatos de usuario o
sentencias de control de trabajos, los interpreta y crea y controla los procesos que necesita
para su ejecución. Por ejemplo, a este nivel la interfaz podría implementarse de una manera
gráfica, proporcionando al usuario mandatos a través de una lista presentada como un menú y
mostrando los resultados utilizando una salida gráfica conectada a un dispositivo específico,
tal y como una pantalla.
Este modelo hipotético de un sistema operativo proporciona una estructura descriptiva útil y sirve
como una guía de implementación. El lector puede volver a estudiar esta estructura durante el desarrollo del libro, para situar cualquier aspecto particular de diseño bajo discusión en el contexto apropiado.
2.4. DESARROLLOS QUE HAN LLEVADO A LOS SISTEMAS OPERATIVOS MODERNOS
A lo largo de los años, ha habido una evolución gradual de la estructura y las capacidades de los sistemas operativos. Sin embargo, en los últimos años se han introducido un gran número de nuevos elementos de diseño tanto en sistemas operativos nuevos como en nuevas versiones de sistemas operativos existentes que han creado un cambio fundamental en la naturaleza de los sistemas operativos.
Estos sistemas operativos modernos responden a nuevos desarrollos en hardware, nuevas aplicaciones y nuevas amenazas de seguridad. Entre las causas de hardware principales se encuentran las máquinas multiprocesador, que han logrado incrementar la velocidad de la máquina en mayor medida,
los dispositivos de conexión de alta velocidad a la red, y el tamaño creciente y variedad de dispositivos de almacenamiento de memoria. En el campo de las aplicaciones, las aplicaciones multimedia,
Internet y el acceso a la Web, y la computación cliente/servidor han influido en el diseño del sistema
operativo. Respecto a la seguridad, el acceso a Internet de los computadores ha incrementado en gran
medida la amenaza potencial y ataques sofisticados, tales como virus, gusanos, y técnicas de hacking,
lo que ha supuesto un impacto profundo en el diseño de los sistemas operativos.
La velocidad de cambio en las demandas de los sistemas operativos requiere no sólo modificaciones o mejoras en arquitecturas existentes sino también nuevas formas de organizar el sistema operativo. Un amplio rango de diferentes técnicas y elementos de diseño se han probado tanto en sistemas
operativos experimentales como comerciales, pero la mayoría de este trabajo encaja en las siguientes
categorías:
• Arquitectura micronúcleo o microkernel.
• Multihilo.
• Multiprocesamiento simétrico.
02-Capitulo 2
80
12/5/05
16:18
Página 80
Sistemas operativos. Aspectos internos y principios de diseño
• Sistemas operativos distribuidos.
• Diseño orientado a objetos.
Hasta hace relativamente poco tiempo, la mayoría de los sistemas operativos estaban formados
por un gran núcleo monolítico. Estos grandes núcleos proporcionan la mayoría de las funcionalidades consideradas propias del sistema operativo, incluyendo la planificación, los sistemas de ficheros,
las redes, los controladores de dispositivos, la gestión de memoria y otras funciones. Normalmente,
un núcleo monolítico se implementa como un único proceso, con todos los elementos compartiendo
el mismo espacio de direcciones. Una arquitectura micronúcleo asigna sólo unas pocas funciones
esenciales al núcleo, incluyendo los espacios de almacenamiento, comunicación entre procesos (IPC),
y la planificación básica. Ciertos procesos proporcionan otros servicios del sistema operativo, algunas
veces denominados servidores, que ejecutan en modo usuario y son tratados como cualquier otra aplicación por el micronúcleo. Esta técnica desacopla el núcleo y el desarrollo del servidor. Los servidores pueden configurarse para aplicaciones específicas o para determinados requisitos del entorno. La
técnica micronúcleo simplifica la implementación, proporciona flexibilidad y se adapta perfectamente a un entorno distribuido. En esencia, un micronúcleo interactúa con procesos locales y remotos del
servidor de la misma forma, facilitando la construcción de los sistemas distribuidos.
Multitheading es una técnica en la cual un proceso, ejecutando una aplicación, se divide en una serie de hilos o threads que pueden ejecutar concurrentemente. Se pueden hacer las siguientes distinciones:
• Thread o hilo. Se trata de una unidad de trabajo. Incluye el contexto del procesador (que contiene el contador del programa y el puntero de pila) y su propia área de datos para una pila
(para posibilitar el salto a subrutinas). Un hilo se ejecuta secuencialmente y se puede interrumpir de forma que el procesador pueda dar paso a otro hilo.
• Proceso. Es una colección de uno o más hilos y sus recursos de sistema asociados (como la
memoria, conteniendo tanto código, como datos, ficheros abiertos y dispositivos). Esto corresponde al concepto de programa en ejecución. Dividiendo una sola aplicación en múltiples hilos, el programador tiene gran control sobre la modularidad de las aplicaciones y la temporización de los eventos relacionados con la aplicación.
La técnica multithreading es útil para las aplicaciones que llevan a cabo un número de tareas esencialmente independientes que no necesitan ser serializadas. Un ejemplo es un servidor de bases de datos
que escucha y procesa numerosas peticiones de cliente. Con múltiples hilos ejecutándose dentro del mismo proceso, intercambiar la ejecución entre los hilos supone menos sobrecarga del procesador que intercambiar la ejecución entre diferentes procesos pesados. Los hilos son también útiles para estructurar procesos que son parte del núcleo del sistema operativo, como se describe en los capítulos siguientes.
Hasta hace poco tiempo, los computadores personales y estaciones de trabajo virtualmente de un
único usuario contenían un único procesador de propósito general. A medida que la demanda de rendimiento se incrementa y el coste de los microprocesadores continúa cayendo, los fabricantes han introducido en el mercado computadores con múltiples procesadores. Para lograr mayor eficiencia y
fiabilidad, una técnica consiste en emplear multiprocesamiento simétrico (SMP: Symmetric MultiProcessing), un término que se refiere a la arquitectura hardware del computador y también al comportamiento del sistema operativo que explota dicha arquitectura. Se puede definir un multiprocesador simétrico como un sistema de computación aislado con las siguientes características:
1. Tiene múltiples procesadores.
2. Estos procesadores comparten las mismas utilidades de memoria principal y de E/S, interconectadas por un bus de comunicación u otro esquema de conexión interna.
3. Todos los procesadores pueden realizar las mismas funciones (de ahí el término simétrico).
02-Capitulo 2
12/5/05
16:18
Página 81
Introducción a los sistemas operativos
81
El sistema operativo de un SMP planifica procesos o hilos a través de todos los procesadores. SMP
tiene diversas ventajas potenciales sobre las arquitecturas monoprocesador, entre las que se incluyen:
• Rendimiento. Si el trabajo se puede organizar de tal forma que alguna porción del trabajo se
pueda realizar en paralelo, entonces un sistema con múltiples procesadores alcanzará mayor
rendimiento que uno con un solo procesador del mismo tipo. Esto se muestra en la Figura
2.12. Con la multiprogramación, sólo un proceso puede ejecutar a la vez; mientras tanto, el
resto de los procesos esperan por el procesador. Con multiproceso, más de un proceso puede
ejecutarse simultáneamente, cada uno de ellos en un procesador diferente.
• Disponibilidad. En un multiprocesador simétrico, debido a que todos los procesadores pueden
llevar a cabo las mismas funciones, el fallo de un solo procesador no para la máquina. Por el
contrario, el sistema puede continuar funcionando con un rendimiento reducido.
• Crecimiento incremental. Un usuario puede mejorar el rendimiento de un sistema añadiendo
un procesador adicional.
• Escalado. Los fabricantes pueden ofrecer un rango de productos con diferente precio y características basadas en el número de procesadores configurado en el sistema.
Es importante notar que estas características son beneficios potenciales, no garantizados. El sistema
operativo debe proporcionar herramientas y funciones para explotar el paralelismo en un sistema SMP.
Tiempo
Proceso 1
Proceso 2
Proceso 3
(a) Intercalado (multiprogramación, un procesador)
Proceso 1
Proceso 2
Proceso 3
(b) Intercalado y solapamiento (multiproceso; dos procesadores)
Bloqueado
Ejecutando
Figura 2.12.
Multiprogramación y multiproceso.
02-Capitulo 2
82
12/5/05
16:18
Página 82
Sistemas operativos. Aspectos internos y principios de diseño
La técnica multithreading y SMP son frecuentemente analizados juntos, pero son dos utilidades
independientes. Incluso en un nodo monoprocesador, la técnica de multihreading es útil para estructurar aplicaciones y procesos de núcleo. Una máquina SMP es útil incluso para procesos que no contienen hilos, porque varios procesos pueden ejecutar en paralelo. Sin embargo, ambas utilidades se
complementan y se pueden utilizar de forma conjunta efectivamente.
Una característica atractiva de un SMP es que la existencia de múltiples procesadores es transparente al usuario. El sistema operativo se encarga de planificar los hilos o procesos en procesadores individuales y de la sincronización entre los procesadores. Este libro discute la planificación y los mecanismos de sincronización utilizados para proporcionar una apariencia de único sistema al usuario.
Un problema diferente es proporcionar la apariencia de un solo sistema a un cluster de computadores
separado-un sistema multicomputador. En este caso, se trata de una colección de entidades (computadores) cada uno con sus propios módulos de memoria principal, de memoria secundaria y otros módulos de E/S.
Un sistema operativo distribuido proporciona la ilusión de un solo espacio de memoria principal y un solo espacio de memoria secundario, más otras utilidades de acceso unificadas, como un sistema de ficheros distribuido. Aunque los clusters se están volviendo cada día más populares, y hay
muchos productos para clusters en el mercado, el estado del arte de los sistemas distribuidos está retrasado con respecto a los monoprocesadores y sistemas operativos SMP. Se examinarán dichos sistemas en la Parte 6.
Otra innovación en el diseño de los sistemas operativos es el uso de tecnologías orientadas a objetos. El diseño orientado a objetos introduce una disciplina al proceso de añadir extensiones modulares a un pequeño núcleo. A nivel del sistema operativo, una estructura basada en objetos permite
a los programadores personalizar un sistema operativo sin eliminar la integridad del sistema. La
orientación a objetos también facilita el desarrollo de herramientas distribuidas y sistemas operativos distribuidos.
2.5. DESCRIPCIÓN GLOBAL DE MICROSOFT WINDOWS
En esta sección se va a realizar una descripción global de Microsoft Windows; se describirá UNIX en
la siguiente sección.
HISTORIA
La historia de Windows comienza con un sistema operativo muy diferente, desarrollado por Microsoft para el primer computador personal IBM y conocido como MS-DOS o PC-DOS. La versión inicial, DOS 1.0 apareció en 1981. Estaba compuesto por 4000 líneas de código fuente en ensamblador
y ejecutaba en 8 Kbytes de memoria, utilizando el microprocesador Intel 8086.
Cuando IBM desarrolló un computador personal basado en disco duro, el PC XT, Microsoft desarrolló DOS 2.0, que salió al mercado en 1983. Este sistema daba soporte al disco duro y proporcionaba
jerarquía de directorios. Hasta ese momento, un disco podía contener sólo un directorio con ficheros,
soportando un máximo de 64 ficheros. Mientras que esto era adecuado en la era de los disquetes, era
demasiado limitado para los discos duros, y la restricción de un solo directorio era demasiado burda.
Esta nueva versión permitía que los directorios contuvieran tantos subdirectorios como ficheros. La
nueva versión también contenía un conjunto de mandatos más rico dentro del sistema operativo, que
proporcionaban funciones que eran realizadas como programas externos con la versión 1. Entre las capacidades añadidas se encontraban algunas características de los sistemas UNIX, como la redirección
de E/S, que consiste en la capacidad para modificar la entrada o salida de una determinada aplicación,
y la impresión en segundo plano. La porción de memoria residente creció a 24 Kbytes.
02-Capitulo 2
12/5/05
16:18
Página 83
Introducción a los sistemas operativos
83
Cuando IBM anunció el PC AT en 1984, Microsoft introdujo DOS 3.0. El sistema AT contenía el
procesador Intel 80286, que proporcionaba características de direccionamiento extendido y protección
de memoria. Estas no eran utilizadas por DOS. Para que fuera compatible con versiones anteriores, el
sistema operativo simplemente utilizaba el 80286 como un «8086 rápido». El sistema operativo sí daba
soporte a un nuevo teclado y periféricos de disco duro. Incluso así, los requisitos de memoria se incrementaron a 36 Kbytes. Hubo varias actualizaciones notables de la versión 3.0. DOS 3.1, que apareció en
1984, daba soporte a la conexión a través de la red para PC. El tamaño de la porción residente no cambió; esto se logró incrementando la cantidad de sistema operativo que podía ser intercambiado (swapped). DOS 3.3, que apareció en 1987, daba soporte a la nueva línea de máquinas IBM, las PS/2. De nuevo, esta versión no se beneficiaba de las capacidades del procesador del PS/2, proporcionadas por el
80286 y los chips de 32 bits 80386. En este punto, la porción residente había alcanzado un mínimo de
46 Kbytes, incrementándose esta cantidad si se seleccionaban ciertas extensiones opcionales.
En este momento, DOS estaba utilizando el entorno muy por debajo de sus posibilidades. La introducción del 80486 y el chip Intel Pentium proporcionaban características que simplemente no podía explotar un sencillo sistema DOS. Mientras tanto, al comienzo de los años 80, Microsoft comenzó
a desarrollar una interfaz gráfica de usuario (GUI: Graphical User Interface) que sería interpuesta entre el usuario y el sistema operativo DOS. El objetivo de Microsoft era competir con Macintosh, cuyo
sistema operativo era insuperable por facilidad de uso. En 1990, Microsoft tenía una versión de la
GUI, conocida como Windows 3.0, que incorporaba algunas de las características más amigables de
Macintosh. Sin embargo, estaba todavía limitada por la necesidad de ejecutar encima de DOS.
Microsoft intentó el desarrollo de un sistema operativo de nueva generación con IBM para explotar la potencia de los nuevos microprocesadores, el cual incorporaría las características de facilidad
de uso de Windows, pero este proyecto fue finalmente abortado. Después de este intento fallido, Microsoft desarrolló un nuevo y propio sistema operativo desde cero, que denominó Windows NT. Windows NT explota las capacidades de los microprocesadores contemporáneos y proporciona multitarea
en un entorno mono o multiusuario.
La primera versión de Windows NT (3.1) apareció en 1993, con la misma interfaz gráfica que
Windows 3.1, otro sistema operativo de Microsoft (el sucesor de Windows 3.0). Sin embargo, NT 3.1
era un nuevo sistema operativo de 32 bits con la capacidad de dar soporte a las aplicaciones Windows, a las antiguas aplicaciones de DOS y a OS/2.
Después de varias versiones de NT 3.x, Microsoft produjo NT 4.0. NT 4.0 tiene esencialmente la
misma arquitectura interna que 3.x. El cambio externo más notable es que NT 4.0 proporciona la misma interfaz de usuario que Windows 95. El cambio arquitectónico más importante es que varios componentes gráficos que ejecutaban en modo usuario como parte del subsistema Win32 en 3.x se mueven al sistema ejecutivo de Windows NT, que ejecuta en modo núcleo. El beneficio de este cambio
consiste en la aceleración de estas funciones importantes. La desventaja potencial es que estas funciones gráficas ahora tienen acceso a servicios de bajo nivel del sistema, que pueden impactar en la fiabilidad del sistema operativo.
En 2000, Microsoft introdujo la siguiente principal actualización, que se materializó en el sistema
Windows 2000. De nuevo, el sistema ejecutivo subyacente y la arquitectura del núcleo son fundamentalmente los mismos que NT 4.0, pero se han añadido nuevas características. El énfasis en Windows 2000 es la adición de servicios y funciones que dan soporte al procesamiento distribuido. El
elemento central de las nuevas características de Windows 2000 es Active Directory, que es un servicio de directorios distribuido capaz de realizar una proyección entre nombres de objetos arbitrarios y
cualquier información sobre dichos objetos.
Un punto final general sobre Windows 2000 es la distinción entre Windows 2000 Server y el escritorio de Windows 2000. En esencia, el núcleo, la arquitectura ejecutiva y los servicios son los mismos,
pero Windows 2000 Server incluye algunos servicios requeridos para su uso como servidor de red.
02-Capitulo 2
84
12/5/05
16:18
Página 84
Sistemas operativos. Aspectos internos y principios de diseño
En 2001, apareció la última versión de escritorio de Windows, conocida como Windows XP. Se
ofrecieron versiones de XP tanto de PC de hogar como de estación de trabajo de negocio. También en
2001, apareció una versión de XP de 64 bits. En el año 2003, Microsoft presentó una nueva versión
de servidor, conocido como Windows Server 2003. Existen versiones disponibles de 32 y 64 bits. Las
versiones de 64 bits de XP y Server 2003 están diseñadas específicamente para el hardware del Intel
Itanium de 64 bits.
MULTITAREA MONOUSUARIO
Windows (desde Windows 2000 en adelante) es un ejemplo significativo de lo que significa la nueva
ola en los sistemas operativos de microcomputadores (otros ejemplos son OS/2 y MacOS). El desarrollo de Windows fue dirigido por la necesidad de explotar las capacidades de los microprocesadores
actuales de 32 bits, que rivalizan con los mainframes y minicomputadores de hace unos poco años en
velocidad, sofisticaciones de hardware y capacidad de memoria.
Una de las características más significativas de estos nuevos sistemas operativos es que, aunque
están todavía pensados para dar soporte a un único usuario interactivo, se trata de sistemas operativos
multitarea. Dos principales desarrollos han disparado la necesidad de multitarea en computadores
personales, estaciones de trabajo y servidores. En primer lugar, con el incremento de la velocidad y la
capacidad de memoria de los microprocesadores, junto al soporte para memoria virtual, las aplicaciones se han vuelto más complejas e interrelacionadas. Por ejemplo, un usuario podría desear utilizar
un procesador de texto, un programa de dibujo, y una hoja de cálculo simultáneamente para producir
un documento. Sin multitarea, si un usuario desea crear un dibujo y copiarlo en un documento, se requieren los siguientes pasos:
1. Abrir el programa de dibujo.
2. Crear el dibujo y guardarlo en un fichero o en el portapapeles.
3. Cerrar el programa de dibujo.
4. Abrir el procesador de texto.
5. Insertar el dibujo en el sitio adecuado.
Si se desea cualquier cambio, el usuario debe cerrar el procesador de texto, abrir el programa de
dibujo, editar la imagen gráfica, guardarlo, cerrar el programa de dibujo, abrir el procesador de texto
e insertar la imagen actualizada. Este proceso se vuelve tedioso muy rápidamente. A medida que los
servicios y capacidades disponibles para los usuarios se vuelven más potentes y variados, el entorno
monotarea se vuelve más burdo y menos amigable. En un entorno multitarea, el usuario abre cada
aplicación cuando la necesita, y la deja abierta. La información se puede mover entre las aplicaciones
fácilmente. Cada aplicación tiene una o más ventanas abiertas, y una interfaz gráfica con un dispositivo puntero tal como un ratón permite al usuario navegar fácilmente en este entorno.
Una segunda motivación para la multitarea es el aumento de la computación cliente/servidor. En
este paradigma, un computador personal o estación de trabajo (cliente) y un sistema host (servidor) se
utilizan de forma conjunta para llevar a cabo una aplicación particular. Los dos están enlazados, y a
cada uno se le asigna aquella parte del trabajo que se adapta a sus capacidades. El paradigma cliente/servidor se puede llevar a cabo en una red de área local formada por computadores personales y
servidores o por medio de un enlace entre un sistema usuario y un gran host, como por ejemplo un
mainframe. Una aplicación puede implicar a uno o más computadores personales y uno o más dispositivos de servidor. Para proporcionar la respuesta requerida, el sistema operativo necesita dar soporte
al hardware de comunicación en tiempo real, los protocolos de comunicación asociados y las arquitecturas de transferencia de datos, y a la vez dar soporte a la interacción de los usuarios.
02-Capitulo 2
12/5/05
16:18
Página 85
Introducción a los sistemas operativos
85
Las características que se describen a continuación se refieren a la versión Profesional de Windows. La versión Server (Servidor) es también multitarea pero debe permitir el uso de múltiples usuarios. Da soporte a múltiples conexiones locales de servidor y proporciona servicios compartidos que
utilizan múltiples usuarios en la red. Como un servidor Internet, Windows puede permitir miles de
conexiones web simultáneas.
ARQUITECTURA
La Figura 2.13 muestra la estructura global de Windows 2000; posteriores versiones de Windows tienen esencialmente la misma estructura a este nivel de detalle. Su estructura modular da a Windows
una considerable flexibilidad. Se ha diseñado para ejecutar en una variedad de plataformas hardware
y da soporte a aplicaciones escritas para una gran cantidad de sistemas operativos. En el momento de
escritura de este libro, Windows está implementado solamente en las plataformas hardware Intel Pentium/x86 e Itanium.
Como virtualmente en todos los sistemas operativos, Windows separa el software orientado a
aplicación del software del sistema operativo. La segunda parte, que incluye el sistema ejecutivo, el
Procesos de servicio
Procesos de
soporte al sistema
Gestor de control
de servicios
OS/2
SVChost.exe
Gestor de tareas
Winmgmt.exe
Lsass
Explorador
de Windows
Spooler
Winlogon
Gestor de
sesiones
Subsistemas
de servicio
Aplicaciones
POSIX
Aplicación
de usuario
Services.exe
DLL de subsistemas
Win32
Ntdll.dll
Hilos del
sistema
Modo usuario
Modo núcleo
Entregador de servicios del sistema
(Interfaces de llamada en modo núcleo)
Llamada a
procedimiento local
Gestor de configuración
(registro)
Procesos e
hilos
Memoria virtual
Monitor de refencia
de seguridad
Gestor de potencia
Gestor de
plug and play
Gestor de objetos
Controladores
de dispositivo
y sistema
de ficheros
Cache del sistema
de ficheros
Gestor de E/S
Win32 USER,
GDI
Controladores
de gráficos
Núcleo
Capa de abstracción de hardware (HAL)
Lsass = servidor de autenticación de seguridad local
POSIX = interfaz de sistema operativo portable
GDI = interfaz de dispositivo gráfico
DLL = bibliotecas de enlace dinámicas
Figura 2.13.
Áreas coloreadas indican Sistema Ejecutivo
Arquitectura de Windows 2000 [SOLO00].
02-Capitulo 2
86
12/5/05
16:18
Página 86
Sistemas operativos. Aspectos internos y principios de diseño
núcleo o kernel y la capa de abstracción del hardware, ejecuta en modo núcleo. El software que ejecuta en modo núcleo tiene acceso a los datos del sistema y al hardware. El resto del software, que ejecuta en modo usuario, tiene un acceso limitado a los datos del sistema.
ORGANIZACIÓN DEL SISTEMA OPERATIVO
Windows no tiene una arquitectura micronúcleo pura, sino lo que Microsoft denomina arquitectura
micronúcleo modificada. Como en el caso de las arquitecturas micronúcleo puras, Windows es muy
modular. Cada función del sistema se gestiona mediante un único componente del sistema operativo.
El resto del sistema operativo y todas las aplicaciones acceden a dicha función a través del componente responsable y utilizando una interfaz estándar. Sólo se puede acceder a los datos del sistema claves
mediante la función apropiada. En principio, se puede borrar, actualizar o reemplazar cualquier módulo sin reescribir el sistema completo o su interfaz de programa de aplicación (API). Sin embargo, a diferencia de un sistema micronúcleo puro, Windows se configura de forma que muchas de las funciones del sistema externas al micronúcleo ejecutan en modo núcleo. La razón reside en el rendimiento.
Los desarrolladores de Windows descubrieron que utilizando la técnica micronúcleo pura, muchas
funciones fuera del micronúcleo requerían varios intercambios entre procesos o hilos, cambios de
modo y el uso de buffers de memoria extra. Los componentes en modo núcleo son los siguientes:
• Sistema ejecutivo. Contiene los servicios básicos del sistema operativo, como la gestión de
memoria, la gestión de procesos e hilos, seguridad, E/S y comunicación entre procesos.
• Núcleo. Está formado por los componentes más fundamentales del sistema operativo. El núcleo gestiona la planificación de hilos, el intercambio de procesos, las excepciones, el manejo
de interrupciones y la sincronización de multiprocesadores. A diferencia del resto del sistema
ejecutivo y el nivel de usuario, el código del núcleo no se ejecuta en hilos. Por tanto, es la única parte del sistema operativo que no es expulsable o paginable.
• Capa de abstracción de hardware (HAL: Hardware Abstraction Layer). Realiza una proyección entre mandatos y respuestas hardware genéricos y aquéllos que son propios de una
plataforma específica. Aísla el sistema operativo de las diferencias de hardware específicas de
la plataforma. El HAL hace que el bus de sistema, el controlador de acceso a memoria directa
(DMA), el controlador de interrupciones, los temporizadores de sistema y los módulos de memoria de cada máquina parezcan los mismos al núcleo. También entrega el soporte necesario
para multiprocesamiento simétrico (SMP), explicado anteriormente.
• Controladores de dispositivo. Incluye tanto sistemas de ficheros como controladores de dispositivos hardware que traducen funciones de E/S de usuario en peticiones específicas a dispositivos hardware de E/S.
• Gestión de ventanas y sistemas gráficos. Implementa las funciones de la interfaz gráfica de
usuario (GUI), tales como la gestión de ventanas, los controles de la interfaz de usuario y el
dibujo.
El sistema ejecutivo de Windows incluye módulos para funciones del sistema específicas y proporciona un API para software en modo usuario. A continuación se describen cada uno de estos módulos del sistema ejecutivo:
• Gestor de E/S. Proporciona un entorno a través del cual las aplicaciones pueden acceder a los
dispositivos de E/S. El gestor de E/S es responsable de enviar la petición al controlador del
dispositivo apropiado para un procesamiento posterior. El gestor de E/S implementa todas las
API de E/S de Windows y provee seguridad y nombrado para dispositivos y sistemas de ficheros (utilizando el gestor de objetos). La E/S de Windows se discute en el Capítulo 11.
02-Capitulo 2
12/5/05
16:18
Página 87
Introducción a los sistemas operativos
87
• Gestor de cache. Mejora el rendimiento de la E/S basada en ficheros, provocando que los datos de disco referenciados recientemente residan en memoria principal para un acceso rápido,
y retardando las escrituras en disco a través del mantenimiento de las actualizaciones en memoria durante un periodo corto de tiempo antes de enviarlas a disco.
• Gestor de objetos. Crea, gestiona y borra los objetos del sistema ejecutivo de Windows y los
tipos de datos abstractos utilizados para representar recursos como procesos, hilos y objetos de
sincronización. Provee reglas uniformes para mantener el control, el nombrado y la configuración de seguridad de los objetos. El gestor de objetos también crea los manejadores de objetos,
que están formados por información de control de acceso y un puntero al objeto. Los objetos
de Windows se discuten posteriormente en esta sección.
• Gestor de plug and play. Determina qué controladores se necesitan para un determinado dispositivo y carga dichos controladores.
• Gestor de potencia. Coordina la gestión de potencia entre varios dispositivos y se puede configurar para reducir el consumo de potencia hibernando el procesador.
• Monitor de referencia de seguridad. Asegura la validación de acceso y las reglas de generación
de auditoría. El modelo orientado a objetos de Windows proporciona una visión de seguridad consistente y uniforme, especificando las entidades fundamentales que constituyen el sistema ejecutivo. Por tanto, Windows utiliza las mismas rutinas de validación de acceso y las comprobaciones
de auditoria para todos los objetos protegidos, incluyendo ficheros, procesos, espacios de direcciones y dispositivos de E/S. La seguridad de Windows se discute en el Capítulo 15.
• Gestor de memoria virtual. Proyecta direcciones virtuales del espacio de direcciones del
proceso a las páginas físicas de la memoria del computador. La gestión de memoria virtual de
Windows se describe en el Capítulo 8.
• Gestor de procesos e hilos. Crea y borra los objetos y traza el comportamiento de los objetos
proceso e hilo. La gestión de los procesos e hilos Windows se describe en el Capítulo 4.
• Gestor de configuración. Es responsable de implementar y gestionar el registro del sistema, que
es el repositorio para la configuración de varios parámetros a nivel de sistema global y por usuario.
• Utilidad de llamada a procedimiento local (LPC: Local Procedure Call). Fuerza una relación cliente/servidor entre las aplicaciones y los subsistemas ejecutivos dentro de un único sistema, en un modo similar a una utilidad de llamada a procedimiento remoto (RPC: Remote
Procedure Call) utilizada para procesamiento distribuido.
PROCESOS EN MODO USUARIO
Hay cuatro tipos básicos de procesos en modo usuario en Windows:
• Procesos de sistema especiales. Incluye servicios no proporcionados como parte del sistema
operativo Windows, como el proceso de inicio y el gestor de sesiones.
• Procesos de servicio. Otros servicios de Windows como por ejemplo, el registro de eventos.
• Subsistemas de entorno. Expone los servicios nativos de Windows a las aplicaciones de
usuario y por tanto, proporciona un entorno o personalidad de sistema operativo. Los subsistemas soportados son Win32, Posix y OS/2. Cada subsistema de entorno incluye bibliotecas de
enlace dinámico (Dynamic Link Libraries, DLL), que convierten las llamadas de la aplicación
de usuario a llamadas Windows.
• Aplicaciones de usuario. Pueden ser de cinco tipos: Win32, Posix, OS/2, Windows 3.1 o
MS-DOS.
02-Capitulo 2
88
12/5/05
16:18
Página 88
Sistemas operativos. Aspectos internos y principios de diseño
Windows está estructurado para soportar aplicaciones escritas para Windows 2000 y versiones
posteriores. Windows 98 y varios sistemas operativos Windows proporcionan este soporte utilizando
un solo sistema ejecutivo compacto a través de subsistemas de entorno protegidos. Los subsistemas
protegidos son aquellas partes de Windows que interactúan con el usuario final. Cada subsistema es
un proceso separado, y el sistema ejecutivo protege su espacio de direcciones del resto de subsistemas y aplicaciones. Un subsistema protegido proporciona una interfaz de usuario gráfica o de línea
de mandatos que define el aspecto del sistema operativo para un usuario. Adicionalmente, cada subsistema protegido proporciona el API para dicho entorno operativo particular.
Esto significa que las aplicaciones creadas para un entorno operativo particular podrían ejecutarse sin ningún cambio en Windows, porque la interfaz del sistema operativo que ven es la misma que
aquélla para la que se han escrito. De esta forma, por ejemplo, las aplicaciones basadas en OS/2 se
pueden ejecutar en el sistema operativo Windows sin ninguna modificación. Más aún, debido a que el
sistema Windows está diseñado para ser independiente de plataforma, mediante el uso de la capa de
abstracción de hardware (HAL), debería ser relativamente fácil portar tanto los subsistemas protegidos como las aplicaciones soportadas de una plataforma hardware a otra. En muchos casos, sólo se
requiere recompilar.
El subsistema más importante es Win32. Win32 es el API implementada tanto en Windows 2000
y versiones posteriores como en Windows 98. Algunas de las características de Win32 no están disponibles en Windows 98, pero las características implementadas en Windows 98 son idénticas a aquéllas de Windows 2000 y posteriores versiones.
MODELO CLIENTE/SERVIDOR
El sistema ejecutivo, los subsistemas protegidos y las aplicaciones se estructuran de acuerdo al modelo de computación cliente/servidor, que es un modelo común para la computación distribuida, que se
discute en la sexta parte. Esta misma arquitectura se puede adoptar para el uso interno de un solo sistema, como es el caso de Windows.
Cada subsistema de entorno y subsistema del servicio ejecutivo se implementa como uno o más
procesos. Cada proceso espera la solicitud de un cliente por uno de sus servicios (por ejemplo, servicios de memoria, servicios de creación de procesos o servicios de planificación de procesadores). Un
cliente, que puede ser un programa u otro módulo del sistema operativo, solicita un servicio a través
del envío de un mensaje. El mensaje se encamina a través del sistema ejecutivo al servidor apropiado.
El servidor lleva a cabo la operación requerida y devuelve los resultados o la información de estado
por medio de otro mensaje, que se encamina de vuelta al cliente mediante el servicio ejecutivo.
Las ventajas de la arquitectura cliente/servidor son las siguientes:
• Simplifica el sistema ejecutivo. Es posible construir diversos API sin conflictos o duplicaciones en el sistema ejecutivo. Se pueden añadir fácilmente nuevas interfaces.
• Mejora la fiabilidad. Cada módulo de los servicios ejecutivos se ejecuta como un proceso separado, con su propia partición de memoria, protegida de otros módulos. Además, los clientes
no pueden acceder directamente al hardware o modificar la zona de memoria en la cual se almacena el sistema ejecutivo. Un único servidor puede fallar sin provocar el fallo o corromper
el resto del sistema operativo.
• Proporciona a las aplicaciones maneras uniformes de comunicarse con el sistema ejecutivo a
través de los LPC sin restringir la flexibilidad. Las aplicaciones cliente esconden el proceso de
paso de mensajes a través de resguardos de funciones, que son contenedores no ejecutables almacenados en bibliotecas de enlace dinámicas (Dynamic Link Libraries, DLL). Cuando una
02-Capitulo 2
12/5/05
16:18
Página 89
Introducción a los sistemas operativos
89
aplicación realiza una llamada a la interfaz del subsistema de entorno, el resguardo de la aplicación cliente empaqueta los parámetros de la llamada y los envía como un mensaje a un subsistema servidor que implementa la llamada.
• Proporciona una base adecuada para la computación distribuida. Normalmente, la computación distribuida utiliza el modelo cliente/servidor, con llamadas a procedimientos remotos implementadas utilizando módulos distribuidos cliente y servidor y el intercambio de mensajes
entre clientes y servidores. Con Windows, un servidor local puede pasar un mensaje al servidor remoto para realizar su procesamiento en nombre de las aplicaciones locales cliente. Los
clientes no necesitan saber si una petición es atendida local o remotamente. De hecho, si una
petición se atiende de forma local o remota, puede cambiar dinámicamente, de acuerdo a las
condiciones de carga actuales y a los cambios dinámicos de configuración.
HILOS Y SMP
Dos características importantes de Windows son el soporte que da a los hilos y a SMP, ambas características presentadas en la Sección 2.4. [CUST93] enumera las siguientes características de Windows
que dan soporte a los hilos y a los SMP:
• Las rutinas del sistema operativo se pueden ejecutar en cualquier procesador disponible, y diferentes rutinas se pueden ejecutar simultáneamente en diferentes procesadores.
• Windows permite el uso de múltiple hilos de ejecución dentro de un único proceso. Múltiples
hilos dentro del mismo proceso se pueden ejecutar en diferentes procesadores simultáneamente.
• Los procesos de servidor pueden utilizar múltiples hilos para procesar peticiones de más de un
cliente simultáneamente.
• Windows proporciona mecanismos para compartir datos y recursos entre procesos y capacidades flexibles de comunicación entre procesos.
OBJETOS DE WINDOWS
Windows se apoya enormemente en los conceptos del diseño orientado a objetos. Este enfoque facilita la compartición de recursos y datos entre los procesos y la protección de recursos frente al acceso
no autorizado. Entre los conceptos clave del diseño orientado a objetos utilizados por Windows se encuentran los siguientes:
• Encapsulación. Un objeto está compuesto por uno o más elementos de información, denominados atributos y uno o más procedimientos que se podrían llevar a cabo sobre esos datos, denominados servicios. La única forma de acceder a los datos en un objeto es invocando uno de
los servicios del objeto. Por tanto, los datos de un objeto se pueden proteger fácilmente del uso
no autorizado o incorrecto (por ejemplo, intentando ejecutar una pieza de datos no ejecutable).
• Clases e instancias de objetos. Una clase de objeto es una plantilla que lista los atributos y
los servicios de un objeto y define ciertas características de los objetos. El sistema operativo
puede crear instancias específicas de una clase de objetos cuando lo necesite. Por ejemplo, hay
una única clase de objeto de proceso y un objeto de proceso por cada proceso actualmente activo. Este enfoque simplifica la creación y gestión de los objetos.
• Herencia. Esta característica no es soportada a nivel de usuario sino por alguna extensión
dentro del sistema ejecutivo. Por ejemplo, los objetos directorio son ejemplos de objeto contenedor. Una propiedad de un objeto contenedor es que los objetos que contiene pueden heredar
02-Capitulo 2
90
12/5/05
16:18
Página 90
Sistemas operativos. Aspectos internos y principios de diseño
propiedades del contenedor mismo. Como un ejemplo, supongamos que hay un directorio en
el sistema de ficheros que está comprimido. Entonces, cualquier fichero que se cree dentro del
contenedor directorio también estará comprimido.
• Polimorfismo. Internamente, Windows utiliza un conjunto común de funciones para manipular objetos de cualquier tipo; ésta es una característica de polimorfismo, tal y como se define
en el Apéndice B. Sin embargo, Windows no es completamente polimórfico, porque hay muchas API que son específicas para tipos de objetos específicos.
El lector que no esté familiarizado con los conceptos orientados a objetos debe revisar el Apéndice B, que se encuentra al final del libro.
No todas las entidades de Windows son objetos. Los objetos se utilizan en casos donde los datos
se usan en modo usuario y cuando el acceso a los datos es compartido o restringido. Entre las entidades representadas por los objetos se encuentran los ficheros, procesos, hilos, semáforos, temporizadores y ventanas. Windows crea y gestiona todos los tipos de objetos de una forma uniforme, a través
del gestor de objetos. El gestor de objetos es responsable de crear y destruir objetos en nombre de las
aplicaciones y de garantizar acceso a los servicios y datos de los objetos.
Cada objeto dentro del sistema ejecutivo, algunas veces denominado objeto del núcleo (para distinguirlo de los objetos a nivel de usuario, objetos que no son gestionados por el sistema ejecutivo),
existe como un bloque de memoria gestionado por el núcleo y que es accesible solamente por el núcleo. Algunos elementos de la estructura de datos (por ejemplo, el nombre del objeto, parámetros de
seguridad, contabilidad de uso) son comunes a todos los tipos de objetos, mientras que otros elementos son específicos de un tipo de objeto particular (por ejemplo, la prioridad del objeto hilo). Sólo el
núcleo puede acceder a estas estructuras de datos de los objetos del núcleo; es imposible que una
aplicación encuentre estas estructuras de datos y las lea o escriba directamente. En su lugar, las aplicaciones manipulan los objetos indirectamente a través del conjunto de funciones de manipulación de
objetos soportado por el sistema ejecutivo. Cuando se crea un objeto, la aplicación que solicita la
creación recibe un manejador del objeto. Esencialmente un manejador es un puntero al objeto referenciado. Cualquier hilo puede utilizar este manejador dentro del mismo proceso para invocar las
funciones Win32 que trabajan con objetos.
Los objetos pueden tener información de seguridad asociada con ellos, en la forma de un descriptor de seguridad (Security Descriptor, SD). Esta información de seguridad se puede utilizar para restringir el acceso al objeto. Por ejemplo, un proceso puede crear un objeto semáforo con el objetivo de
que sólo ciertos usuarios deben ser capaces de abrir y utilizar el semáforo. El SD de dicho objeto semáforo puede estar compuesto por la lista de aquellos usuarios que pueden (o no pueden) acceder al
objeto semáforo junto con el conjunto de accesos permitidos (lectura, escritura, cambio, etc.).
En Windows, los objetos pueden tener nombre o no. Cuando un proceso crea un objeto sin nombre, el gestor de objetos devuelve un manejador para dicho objeto, y el manejador es la única forma
de referirse a él. Los objetos con nombre son referenciados por otros procesos mediante dicho nombre. Por ejemplo, si un proceso A desea sincronizarse con el proceso B podría crear un objeto de tipo
evento con nombre y pasar el nombre del evento a B. El proceso B podría entonces abrir y utilizar el
objeto evento. Sin embargo, si A simplemente deseara utilizar el evento para sincronizar dos hilos
dentro del proceso, crearía un objeto evento sin nombre, porque no necesita que otros procesos puedan utilizar dicho evento.
Como ejemplo de los objetos gestionados por Windows, a continuación se listan las dos categorías de objetos que gestiona el núcleo:
• Objetos de control. Utilizados para controlar las operaciones del núcleo en áreas que no corresponden a la planificación y la sincronización. La Tabla 2.5 lista los objetos de control del núcleo.
02-Capitulo 2
12/5/05
16:18
Página 91
Introducción a los sistemas operativos
91
• Objetos dispatcher. Controla la activación y la sincronización de las operaciones del sistema.
Estos objetos se describen en el Capítulo 6.
Tabla 2.5.
Objetos de control del micronúcleo de Windows [MS96].
Llamada a procedimiento
asíncrono
Utilizado para romper la ejecución de un hilo específico y provocar que
se llame a un procedimiento en un modo de procesador especificado
Interrupción
Utilizado para conectar un origen de interrupción a una rutina de servicio de interrupciones por medio de una entrada en una tabla IDT (Interrupt Dispatch Table). Cada procesador tiene una tabla IDT que se utiliza
para entregar interrupciones que ocurren en dicho procesador.
Proceso
Representa el espacio de direcciones virtuales e información de control
necesaria para la ejecución de un conjunto de objetos hilo. Un proceso
contiene un puntero a un mapa de direcciones, una lista de hilos listos
para ejecutar que contiene objetos hilo, una lista de hilos que pertenecen al proceso, el tiempo total acumulado para todos los hilos que se
ejecuten dentro del proceso y una prioridad base.
Perfil
Utilizado para medir la distribución de tiempo de ejecución dentro de
un bloque de código. Se puede medir el perfil tanto de código de usuario como de sistema.
Windows no es un sistema operativo completamente orientado a objetos. No está implementado
en un lenguaje orientado a objetos. Las estructuras de datos que residen completamente dentro de un
componente del sistema ejecutivo no están representadas como objetos. Sin embargo, Windows
muestra la potencia de la tecnología orientada a objetos y representa la tendencia cada vez más significativa del uso de esta tecnología en el diseño de los sistemas operativos.
2.6. SISTEMAS UNIX TRADICIONALES
HISTORIA
La historia de UNIX es un relato narrado con frecuencia y no se repetirá con muchos detalles aquí.
Por el contrario, aquí se realizará un breve resumen.
UNIX se desarrolló inicialmente en los laboratorios Bell y se hizo operacional en un PDP-7
en 1970. Algunas de las personas relacionadas con los laboratorios Bell también habían participado en el trabajo de tiempo compartido desarrollado en el proyecto MAC del MIT. Este proyecto
se encargó primero del desarrollo de CTSS y después de Multics. Aunque es habitual decir que el
UNIX original fue una versión recortada de Multics, los desarrolladores de UNIX realmente dijeron estar más influenciados por CTSS [RITC78]. No obstante, UNIX incorporó muchas ideas de
Multics.
El trabajo de UNIX en los laboratorios Bell, y después en otras instituciones, produjo un conjunto de versiones de UNIX. El primer hito más notable fue portar el sistema UNIX de PDP-7 al
PDP-11. Ésta fue la primera pista de que UNIX sería un sistema operativo para todos los computadores. El siguiente hito importante fue la reescritura de UNIX en el lenguaje de programación C.
Ésta era una estrategia sin precedentes en ese tiempo. Se pensaba que algo tan complejo como un
sistema operativo, que debe tratar eventos críticos en el tiempo, debía escribirse exclusivamente en
lenguaje ensamblador. La implementación C demostró las ventajas de utilizar un lenguaje de alto nivel para la mayoría o todo el código del sistema. Hoy, prácticamente todas las implementaciones
UNIX están escritas en C.
02-Capitulo 2
92
12/5/05
16:18
Página 92
Sistemas operativos. Aspectos internos y principios de diseño
Estas primeras versiones de UNIX fueron populares dentro de los laboratorios Bell. En 1974, el
sistema UNIX se describió en una revista técnica por primera vez [RITC74]. Esto despertó un gran
interés por el sistema. Se proporcionaron licencias de UNIX tanto a instituciones comerciales como a
universidades. La primera versión completamente disponible fuera de los laboratorios Bell fue la Versión 6, en 1976. La siguiente versión, la Versión 7, aparecida en 1978, es la antecesora de los sistemas UNIX más modernos. El sistema más importante no vinculado con AT&T se desarrolló en la
Universidad de California en Berkeley, y se llamó UNIX BSD (Berkeley Software Distribution), ejecutándose primero en PDP y después en máquinas VAX. AT&T continuó desarrollando y refinando el
sistema. En 1982, los laboratorios Bell combinaron diversas variantes AT&T de UNIX en un único
sistema, denominado comercialmente como UNIX System III. Un gran número de características se
añadieron posteriormente al sistema operativo para producir UNIX System V.
DESCRIPCIÓN
La Figura 2.14 proporciona una descripción general de la arquitectura UNIX. El hardware subyacente
es gestionado por el software del sistema operativo. El sistema operativo se denomina frecuentemente
el núcleo del sistema, o simplemente núcleo, para destacar su aislamiento frente a los usuarios y a las
aplicaciones. Esta porción de UNIX es lo que se conocerá como UNIX en este libro. Sin embargo,
UNIX viene equipado con un conjunto de servicios de usuario e interfaces que se consideran parte
del sistema. Estos se pueden agrupar en el shell, otro software de interfaz, y los componentes del
compilador C (compilador, ensamblador, cargador). La capa externa está formada por las aplicaciones de usuario y la interfaz de usuario al compilador C.
La Figura 2.15 muestra una vista más cercana del núcleo. Los programas de usuario pueden invocar los servicios del sistema operativo directamente o a través de programas de biblioteca. La interfaz
de llamada a sistemas es la frontera con el usuario y permite que el software de alto nivel obtenga acceso a funciones específicas de núcleo. En el otro extremo, el sistema operativo contiene rutinas primitivas que interaccionan directamente con el hardware. Entre estas dos interfaces, el sistema se divi-
Comandos y
bibliotecas UNIX
Interfaz de
llamada a sistema
Núcleo
Hardware
Aplicaciones escritas
por el usuario
Figura 2.14.
Arquitectura general de UNIX.
02-Capitulo 2
12/5/05
16:18
Página 93
Introducción a los sistemas operativos
93
Programas de usuario
Trap
Bibliotecas
Nivel usuario
Nivel núcleo
Interfaz de llamadas a sistema
Comunicación
entre procesos
Subsistema de fichero
Subsistema
de control
de procesos
Gestión de
memoria
Cache de Buffer
Carácter
Planificador
Bloque
Controladores de dispositivos
Control de hardware
Nivel núcleo
Nivel hardware
Hardware
Figura 2.15.
Núcleo tradicional de UNIX [BACH86].
de en dos partes principales, una encargada del control de procesos y la otra encargada de la gestión
de ficheros y de la E/S. El subsistema de control de procesos se encarga de la gestión de memoria, la
planificación y ejecución de los procesos, así como de la sincronización y la comunicación entre los
procesos. El sistema de ficheros intercambia datos entre la memoria y los dispositivos externos tanto
como flujos de caracteres como bloques. Para lograr esto, se utilizan una gran variedad de controladores de dispositivos. Para las transferencias orientadas a bloques, se utiliza una técnica de cache de
discos: entre el espacio de direccionamiento del usuario y el dispositivo externo se interpone un buffer de sistema en memoria principal.
La descripción de esta subsección se refiere a lo que se han denominado sistemas UNIX tradicionales; [VAHA96] utiliza este término para referirse a System V Versión 3 (SVR3: System V Release
3), 4.3BSD y versiones anteriores. Las siguientes sentencias generales pueden afirmarse sobre un sistema UNIX tradicional. Se diseñaron para ejecutar sobre un único procesador y carecen de la capacidad para proteger sus estructuras de datos del acceso concurrente por parte de múltiples procesadores.
Su núcleo no es muy versátil, soportando un único tipo de sistema de ficheros, una única política de
planificación de procesos y un único formato de fichero ejecutable. El núcleo tradicional de UNIX no
está diseñado para ser extensible y tiene pocas utilidades para la reutilización de código. El resultado
es que, según se iban añadiendo nuevas características a varias versiones de UNIX, se tuvo que añadir mucho código, proporcionando un núcleo de gran tamaño y no modular.
02-Capitulo 2
94
12/5/05
16:18
Página 94
Sistemas operativos. Aspectos internos y principios de diseño
2.7. SISTEMAS UNIX MODERNOS
Cuando UNIX evolucionó, un gran número de diferentes implementaciones proliferó, cada una de las
cuales proporcionó algunas características útiles. Fue necesaria la producción de una nueva implementación que unificara muchas de las importantes innovaciones, añadiera otras características de diseño de los sistemas operativos modernos, y produjera una arquitectura más modular. La arquitectura
mostrada en la Figura 2.16 muestra los aspectos típicos de un núcleo UNIX moderno. Existe un pequeño núcleo de utilidades, escritas de forma modular, que proporciona funciones y servicios necesarios para procesos del sistema operativo. Cada uno de los círculos externos representa funciones y
una interfaz que podría implementarse de diferentes formas.
Ahora se verán algunos ejemplos de sistemas UNIX modernos.
SYSTEM V RELEASE 4 (SVR4)
SVR4, desarrollado conjuntamente por AT&T y Sun Microsistemas, combina características de
SVR3, 4.3BSD, Microsoft Xenix System y SunOS. Fue casi una reescritura completa del núcleo del
coff
a.out
elf
Intercambiador
de ejecución
NFS
Proyecciones de fichero
FFS
Proyecciones
de fichero
Entorno de
memoria
virtual
Interfaz
vnode/vfs
Proyecciones
anónimas
s5fs
RFS
Utilidades
comunes
Controlador
de disco
Intercambiador
de dispositivo
de bloques
Entorno de
planificación
Procesos
de sistema
Controlador de cinta
Flujos
Controlador
de red
Figura 2.16.
Controlador
de terminal
Núcleo UNIX moderno [VAHA96].
Procesos
de tiempo
compartido
02-Capitulo 2
12/5/05
16:18
Página 95
Introducción a los sistemas operativos
95
System V y produjo una implementación bien organizada, aunque compleja. Las nuevas características de esta versión incluyen soporte al procesamiento en tiempo real, clases de planificación de procesos, estructuras de datos dinámicamente asignadas, gestión de la memoria virtual, sistema de ficheros virtual y un núcleo expulsivo.
SVR4 mezcló los esfuerzos de los diseñadores comerciales y académicos y se desarrolló para
proporcionar una plataforma uniforme que permitiera el despegue comercial de UNIX. Logró su objetivo y es quizá una de las variantes más importantes de UNIX. Incorpora la mayoría de las características importantes desarrolladas en cualquier sistema UNIX y lo hace de una forma integrada y comercialmente viable. SVR4 ejecuta en un gran rango de máquinas, desde los microprocesadores de
32 bits hasta los supercomputadores. Muchos de los ejemplos UNIX de este libro son ejemplos de
SVR4.
SOLARIS 9
Solaris es una versión UNIX de Sun basada en SVR4. La última versión es la 9, y proporciona todas
las características de SVR4 más un conjunto de características avanzadas, como un núcleo multihilo,
completamente expulsivo, con soporte completo para SMP, y una interfaz orientada a objetos para los
sistemas de ficheros. Solaris es la implementación UNIX más utilizada y comercialmente más exitosa. Para algunas características de los sistemas operativos, se utiliza a Solaris como ejemplo en este
libro.
4.4BSD
Las series de UNIX BSD (Berkeley Software Distribution) han jugado un papel importante en el desarrollo de la teoría de diseño de los sistemas operativos. 4.xBSD se ha utilizado ampliamente en instalaciones académicas y ha servido como base de algunos productos comerciales UNIX. Es probable
que BSD es seguramente responsable de gran parte de la popularidad de UNIX y que la mayoría de
las mejoras de UNIX aparecieron en primer lugar en las versiones BSD.
4.4BSD fue la versión final de BSD que Berkeley produjo, disolviéndose posteriormente la organización encargada del diseño e implementación. Se trata de una actualización importante de
4.3BSD, que incluye un nuevo sistema de memoria virtual, cambios en la estructura del núcleo, y una
larga lista de otras mejoras.
La última versión del sistema operativo de Macintosh, Mac OS X, se basa en 4.4BSD.
2.8. LINUX
HISTORIA
Linux comenzó como una variante UNIX para la arquitectura del PC IBM (Intel 80386). Linus Torvalds, un estudiante finlandés de informática, escribió la versión inicial. Torvalds distribuyó por Internet una primera versión de Linux en 1991. Desde entonces, algunas personas, colaborando en Internet, han contribuido al desarrollo de Linux, todo bajo el control de Torvalds. Debido a que Linux
es libre y el código fuente está disponible, se convirtió pronto en una alternativa para otras estaciones
de trabajo UNIX, tal como las ofrecidas por Sun Microsystems e IBM. Hoy en día, Linux es un sistema UNIX completo que ejecuta en todas esas plataformas y algunas más, incluyendo Intel Pentium e
Itanium, y el PowerPC de Motorota/IBM.
02-Capitulo 2
96
12/5/05
16:18
Página 96
Sistemas operativos. Aspectos internos y principios de diseño
La clave del éxito de Linux ha sido la disponibilidad de los paquetes de software libre bajo los
auspicios de la Fundación de Software Libre (Free Software Foundation, FSF). Esta fundación se
centra en un software estable, independiente de plataforma, con alta calidad, y soportado por la comunidad de usuarios. El proyecto de GNU proporciona herramientas para desarrolladores de software y la licencia pública GNU (GPL: GNU Public License) es el sello de aprobación de FSF. Torvald utilizó herramientas GNU para el desarrollo del núcleo, que fue posteriormente distribuido
bajo la licencia GPL. Por tanto, las distribuciones Linux que aparecen hoy en día son los productos
del proyecto GNU de FSF, los esfuerzos individuales de Torvalds y muchos colaboradores a lo largo del mundo.
Además de su uso por muchos programadores individuales, Linux ha hecho ahora una penetración significativa en el mundo corporativo. Esto no es sólo debido al software libre, sino también a la
calidad del núcleo de Linux. Muchos programadores con talento han contribuido a la versión actual,
dando lugar a un producto técnicamente impresionante. Más aún, Linux es muy modular y fácilmente
configurable. Resulta óptimo para incrementar el rendimiento de una variedad de plataformas hardware. Además, con el código fuente disponible, los distribuidores pueden adaptar las aplicaciones y
facilidades para cumplir unos requisitos específicos. A lo largo de este libro, se proporcionarán detalles internos del núcleo de Linux.
ESTRUCTURA MODULAR
La mayoría de los núcleos Linux son monolíticos. Como se mencionó anteriormente en el capítulo,
un núcleo monolítico es aquél que incluye prácticamente toda la funcionalidad del sistema operativo
en un gran bloque de código que ejecuta como un único proceso con un único espacio de direccionamiento. Todos los componentes funcionales del núcleo tienen acceso a todas las estructuras internas
de datos y rutinas. Si los cambios se hacen sobre cualquier porción de un sistema operativo monolítico, todos los módulos y rutinas deben volverse a enlazar y reinstalar, y el sistema debe ser reiniciado
para que los cambios tengan efecto. Como resultado, cualquier modificación, como por ejemplo añadir un nuevo controlador de dispositivo o función del sistema de fichero, es difícil. Este problema es
especialmente agudo para Linux, cuyo desarrollo es global y ha sido realizado por un grupo de programadores independientes asociados de forma difusa.
Aunque Linux no utiliza una técnica de micronúcleo, logra muchas de las ventajas potenciales de
esta técnica por medio de su arquitectura modular particular. Linux está estructurado como una colección de módulos, algunos de los cuales pueden cargarse y descargarse automáticamente bajo demanda. Estos bloques relativamente independientes se denominan módulos cargables [GOYE99]. Esencialmente, un módulo es un fichero cuyo código puede enlazarse y desenlazarse con el núcleo en
tiempo real. Normalmente, un módulo implementa algunas funciones específicas, como un sistema
de ficheros, un controlador de dispositivo o algunas características de la capa superior del núcleo. Un
módulo no se ejecuta como su propio proceso o hilo, aunque puede crear los hilos del núcleo que necesite por varios propósitos. En su lugar, un módulo se ejecuta en modo núcleo en nombre del proceso actual.
Por tanto, aunque Linux se puede considerar monolítico, su estructura modular elimina algunas
de las dificultades para desarrollar y evolucionar el núcleo.
Los módulos cargables de Linux tienen dos características importantes:
• Enlace dinámico. Un módulo de núcleo puede cargarse y enlazarse al núcleo mientras el núcleo está en memoria y ejecutándose. Un módulo también puede desenlazarse y eliminarse de
la memoria en cualquier momento.
02-Capitulo 2
12/5/05
16:18
Página 97
Introducción a los sistemas operativos
97
• Módulos apilables. Los módulos se gestionan como una jerarquía. Los módulos individuales
sirven como bibliotecas cuando los módulos cliente los referencian desde la parte superior de
la jerarquía, y actúan como clientes cuando referencian a módulos de la parte inferior de la
jerarquía.
El enlace dinámico [FRAN97] facilita la configuración y reduce el uso de la memoria del núcleo.
En Linux, un programa de usuario o un usuario puede cargar y descargar explícitamente módulos del
núcleo utilizando los mandatos insmod y rmmod. El núcleo mismo detecta la necesidad de funciones
particulares y puede cargar y descargar módulos cuando lo necesite. Con módulos apilables, se pueden definir dependencias entre los módulos. Esto tiene dos ventajas:
1. El código común para un conjunto de módulos similares (por ejemplo, controladores para
hardware similar) se puede mover a un único módulo, reduciendo la replicación.
2. El núcleo puede asegurar que los módulos necesarios están presentes, impidiendo descargar
un módulo del cual otros módulos que ejecutan dependen y cargando algunos módulos adicionalmente requeridos cuando se carga un nuevo módulo.
La Figura 2.17 es un ejemplo que ilustra las estructuras utilizadas por Linux para gestionar módulos. La figura muestra la lista de los módulos del núcleo que existen después de que sólo dos módulos han sido cargados: FAT y VFAT. Cada módulo se define mediante dos tablas, la tabla de módulos y la tabla de símbolos. La tabla de módulos incluye los siguientes elementos:
• *next. Puntero al siguiente módulo. Todos los módulos se organizan en una lista enlazada. La
lista comienza con un pseudomódulo (no mostrado en la Figura 2.17).
• *name. Puntero al nombre del módulo.
• size. Tamaño del módulo en páginas de memoria
• usecount. Contador del uso del módulo. El contador se incrementa cuando una operación relacionada con las funciones del módulo comienza y se decrementa cuando la operación finaliza.
• flags. Opciones del módulo.
• nsyms. Número de símbolos exportados.
• ndeps. Número de módulos referenciados.
• *syms. Puntero a la tabla de símbolos de este módulo.
• *deps. Puntero a la lista de módulos referenciados por este módulo.
• *refs. Puntero a la lista de módulos que usa este módulo.
La tabla de símbolos define aquellos símbolos controlados por este módulo que se utilizan en
otros sitios.
La Figura 2.17 muestra que el módulo VFAT se carga después del módulo FAT y que el módulo
VFAT es dependiente del módulo FAT.
COMPONENTES DEL NÚCLEO
La Figura 2.18, tomada de [MOSB02] muestra los principales componentes del núcleo Linux tal y
como están implementados en una arquitectura IA-64 (por ejemplo, Intel Itanium). La figura muestra
02-Capitulo 2
98
12/5/05
16:18
Página 98
Sistemas operativos. Aspectos internos y principios de diseño
módulo
módulo
*next
*name
size
usecount
flags
nysms
ndeps
*syms
*deps
*refs
*name
size
usecount
flags
nysms
ndeps
*syms
*deps
*refs
*next
FAT
VFAT
tabla de símbolos
tabla de símbolos
value
*name
value
*name
value
*name
value
*name
value
*name
value
*name
Figura 2.17.
Lista ejemplo de módulos de núcleo de Linux.
varios procesos ejecutando encima del núcleo. Cada caja indica un proceso separado, mientras que
cada línea curvada con una cabeza de flecha representa un hilo de ejecución*.
El núcleo mismo está compuesto por una colección de componentes que interaccionan, usando
flechas para indicar las principales interacciones. También se muestra el hardware subyacente como
un conjunto de componentes utilizando flechas para indicar qué componentes del núcleo utilizan o
controlan qué componentes del hardware. Todos los componentes del núcleo, por supuesto, ejecutan
en la CPU, pero por simplicidad no se muestran estas relaciones.
Brevemente, los principales componentes del núcleo son los siguientes:
• Señales. El núcleo utiliza las señales para llamar a un proceso. Por ejemplo, las señales se utilizan para notificar ciertos fallos a un proceso como por ejemplo, la división por cero. La Tabla 2.6 da unos pocos ejemplos de señales.
• Llamadas al sistema. La llamada al sistema es la forma en la cual un proceso requiere un
servicio de núcleo específico. Hay varios cientos de llamadas al sistema, que pueden agruparse básicamente en seis categorías: sistema de ficheros, proceso, planificación, comunicación
entre procesos, socket (red) y misceláneos. La Tabla 2.7 define unos pocos ejemplos de cada
categoría.
En Linux, no hay distinción entre los conceptos de proceso e hilo. Sin embargo, múltiples hilos en Linux se pueden agrupar
de tal forma que, efectivamente, pueda existir un único proceso compuesto por múltiples hilos. Estos aspectos se discuten en el
Capítulo 4.
12/5/05
16:18
Página 99
99
Nivel usuario
Introducción a los sistemas operativos
Procesos
Procesos y
planificador
Sistemas
de ficheros
Protocolos
de red
Controladores de dispositivos
orientados a bloque
Controladores de
disposivos de red
Memoria
virtual
Controladores de dispositivos
orientados a carácter
Interrupciones
y fallos
Memoria
física
CPU
Memoria
de sistema
Núcleo
Llamadas
al sistema
Señales
Interrupciones
Terminal
Figura 2.18.
Disco
Controlador de
interfaz de red
Hardware
02-Capitulo 2
Componentes del núcleo de Linux.
• Procesos y planificador. Crea, gestiona y planifica procesos.
• Memoria virtual. Asigna y gestiona la memoria virtual para los procesos.
• Sistemas de ficheros. Proporciona un espacio de nombres global y jerárquico para los ficheros, directorios y otros objetos relacionados con los ficheros. Además, proporciona las funciones del sistema de ficheros.
• Protocolos de red. Da soporte a la interfaz Socket para los usuarios, utilizando la pila de protocolos TCP/IP.
• Controladores de dispositivo tipo carácter. Gestiona los dispositivos que requiere el núcleo
para enviar o recibir datos un byte cada vez, como los terminales, los módems y las impresoras.
• Controladores de dispositivo tipo bloque. Gestiona dispositivos que leen y escriben datos en
bloques, tal como varias formas de memoria secundaria (discos magnéticos, CDROM, etc.).
• Controladores de dispositivo de red. Gestiona las tarjetas de interfaz de red y los puertos
de comunicación que permiten las conexiones a la red, tal como los puentes y los encaminadores.
• Traps y fallos. Gestiona los traps y fallos generados por la CPU, como los fallos de memoria.
• Memoria física. Gestiona el conjunto de marcos de páginas de memoria real y asigna las páginas de memoria virtual.
• Interrupciones. Gestiona las interrupciones de los dispositivos periféricos.
02-Capitulo 2
100
12/5/05
16:18
Página 100
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 2.6.
Algunas señales de Linux.
SIGHUP
Desconexión de un terminal
SIGCONT
Continuar
SIGQUIT
Finalización por teclado
SIGTSTP
Parada por teclado
SIGTRAP
Traza
SIGTTOU
Escritura de terminal
SIGBUS
Error de bus
SIGXCPU
Límite de CPU excedido
SIGKILL
Señal para matar
SIGVTALRM
Reloj de alarma virtual
SIGSEGV
Violación de segmentación
SIGWINCH
Cambio de tamaño de una ventana
SIGPIPE
Tubería rota
SIGPWR
Fallo de potencia
SIGTERM
Terminación
SIGRTMIN
Primera señal de tiempo real
SIGCHLD
Cambio en el estado del hijo
SIGRTMAX
Última señal de tiempo real
Tabla 2.7.
Algunas llamadas al sistema de Linux.
Relacionadas con el sistema de ficheros
close
Cierra un descriptor de fichero.
link
Construye un nuevo nombre para un fichero.
open
Abre y posiblemente crea un fichero o dispositivo.
read
Lee un descriptor de fichero.
write
Escribe a través de un descriptor de fichero.
Relacionadas con los procesos
execve
Ejecuta un programa.
exit
Termina el proceso que lo invoca.
getpid
Obtiene la identificación del proceso.
setuid
Establece la identidad del usuario del proceso actual.
prtrace
Proporciona una forma por la cual un proceso padre puede observar y
controlar la ejecución de otro proceso, examinar y cambiar su imagen
de memoria y los registros.
Relacionadas con la planificación
sched_getparam
Establece los parámetros de planificación asociados con la política de
planificación para el proceso identificado por su pid.
sched_get_priority_max
Devuelve el valor máximo de prioridad que se puede utilizar con el algoritmo de planificación identificado por la política.
sched_setscheduler
Establece tanto la política de planificación (por ejemplo, FIFO) como los
parámetros asociados al pid del proceso.
sched_rr_get_interval
Escribe en la estructura timespec apuntada por el parámetro tp el cuanto de tiempo round robin para el proceso pid.
sched_yield
Un proceso puede abandonar el procesador voluntariamente sin necesidad de bloquearse a través de una llamada al sistema. El proceso entonces se moverá al final de la cola por su prioridad estática y un nuevo proceso se pondrá en ejecución.
02-Capitulo 2
12/5/05
16:18
Página 101
Introducción a los sistemas operativos
101
Relacionadas con la comunicación entre procesos (IPC)
msgrcv
Se asigna una estructura de buffer de mensajes para recibir un mensaje.
Entonces, la llamada al sistema lee un mensaje de la cola de mensajes
especificada por msqid en el buffer de mensajes nuevamente creado.
semctl
Lleva a cabo la operación de control especificada por cmd en el conjunto de semáforos semid.
semop
Lleva a cabo operaciones en determinados miembros del conjunto de
semáforos semid.
shmat
Adjunta el segmento de memoria compartido identificado por shmid al
segmento de datos del proceso que lo invoca.
shmctl
Permite al usuario recibir información sobre un segmento de memoria
compartido, establecer el propietario, grupo y permisos de un segmento de memoria compartido o destruir un segmento.
Relacionadas con los sockets (red)
bind
Asigna la dirección IP local y puerto para un socket. Devuelve 0 en caso
de éxito y -1 en caso de error.
connect
Establece una conexión entre el socket dado y el socket asociado remoto con sockaddr.
gethostname
Devuelve el nombre de máquina local.
send
Envia los bytes que tiene el buffer apuntado por *msg sobre el socket
dado.
setsockopt
Envia las opciones sobre un socket.
Misceláneos
create_module
Intenta crear una entrada del módulo cargable y reservar la memoria
de núcleo que será necesario para contener el módulo.
fsync
Copia todas las partes en memoria de un fichero a un disco y espera
hasta que el dispositivo informa que todas las partes están en almacenamiento estable.
query_module
Solicita información relacionada con los módulos cargables desde el
núcleo.
time
Devuelve el tiempo en segundos desde 1 de enero de 1970.
vhangup
Simula la suspensión del terminal actual. Esta llamada sirve para que
otros usuarios puedan tener un terminal «limpio» en tiempo de inicio.
2.9. LECTURAS Y SITIOS WEB RECOMENDADOS
Como en el área de arquitectura de computadores, existen muchos libros de sistemas operativos.
[SILB04], [NUTT04] y [TANE01] cubren los principios básicos usando diversos sistemas operativos
importantes como casos de estudio. [BRIN01] es una colección excelente de artículos que cubren los
principales avances del diseño de los sistemas operativos a lo largo de los años.
Un tratamiento excelente de los aspectos internos de UNIX, que proporciona un análisis comparativo de un gran número de variantes, es [VAHA96]. Para UNIX SVR4, [GOOD94] proporciona un
tratamiento definitivo, con amplios detalles técnicos. Para el sistema académicamente popular Berkeley UNIX 4.4BSD, [MCKU96] es altamente recomendado. [MAUR01] proporciona un buen trata-
02-Capitulo 2
102
12/5/05
16:18
Página 102
Sistemas operativos. Aspectos internos y principios de diseño
miento de los aspectos internos de Solaris. Dos buenos tratamientos de los aspectos internos de Linux
se recogen en [BOVE03] y [BAR00].
Aunque hay incontables libros sobre varias versiones de Windows, hay curiosamente poco material disponible sobre los aspectos internos de Windows. [SOLO00] proporciona un tratamiento excelente de los aspectos internos de Windows 2000 y gran parte de este material es válido para posteriores versiones. [BOSW03] cubre parte de los aspectos internos de Windows 2003.
BAR00 Bar, M. Linux Internals. New York, McGraw-Hill, 2000.
BOSW03 Boswell, W. Inside Windows Server 2003. Reading, MA:Addison-Wesley, 2003.
BOVE03 Bovet, D., y Cesati, M. Understanding the Linux Kernel. Sebastopol, CA: O’Reilly, 2003.
BRIN01 Brinch Hansen, P. Classic Operating Systems: From Batch Processing to Distributed Systems.
New York: Springer-Verlag, 2001.
GOOD94 Goodheart, B., y Cox, J. The Magic Garden Explained: The Internals of UNIX System V Release 4. Englewood Cliffs, NJ: Prentice Hall, 1994.
MAUR01 Mauro, J., y McDougall, R. Solaris Internals: Core Kernel Architecture. Palo Alto, CA: Sun Microsystems Press, 2001.
MCKU96 McKusick, M.; Bostic, K.; Karels, M.; y Quartermain, J. The Design and Implementation of the
4.4BSD UNIX Operating System. Reading, MA: Addison-Wesley, 1996.
NUTT04 Nutt,G. Operating System. Reading, MA: Addison-Wesley, 2004.
SILB04 Silberschatz, A.; Galvin, P.; y Gagne,G. Operating System Concepts with Java. Reading, MA:
Addison-Wesley, 2004.
SOLO00 Solomon, D. Inside Microsoft Windows 2000. Redmond, WA: Microsoft Press, 2000.
TANE01 Tanenbaum, A. Modern Operating Systems. Upper Saddle River, NJ: Prentice Hall, 2001.
VAHA96 Vahalia, U. UNIX Internals: The New Frontiers. Upper Saddle River, NJ: Prentice Hall, 1996.
SITIOS WEB RECOMENDADOS
• El centro de recursos del sistema operativo. Una colección útil de documentos y artículos
sobre un amplio rango de temas de sistemas operativos.
• Revisión de los sistemas operativos. Una extensa revisión de sistemas operativos comerciales, libres, de investigación y para aficionados.
• Comparación técnica de los sistemas operativos. Incluye una cantidad sustancial de información sobre una variedad de sistemas operativos.
• Grupo especial de interés ACM sobre los sistemas operativos. Información sobre publicaciones y conferencias SIGOPS.
• Comité Técnico IEEE sobre los sistemas operativos y los entornos de aplicación. Incluye
un boletín en línea y enlaces a otros sitios.
• La FAQ comp.os.research. FAQ amplio y válido que cubre aspectos de diseño de los sistemas operativos.
02-Capitulo 2
12/5/05
16:18
Página 103
Introducción a los sistemas operativos
103
• Universo Guru UNIX. Fuente excelente de información sobre UNIX.
• Proyecto de documentación Linux. El nombre describe el sitio.
2.10. TÉRMINOS CLAVE, CUESTIONES DE REPASO Y PROBLEMAS
TÉRMINOS CLAVE
Contexto de ejecución
Monitor
Procesamiento serie
Dirección física
Monitor residente
Proceso
Dirección real
Monoprogramación
Round-robin o turno rotatorio
Dirección virtual
Multihilo
Sistema batch o en lotes
Estado del proceso
Multiprocesamiento simétrico
Sistema batch o en lotes
multiprogramado
Gestión de memoria
Multiprogramación
Sistema de tiempo compartido
Hilo
Multitarea
Sistema operativo
Interrupción
Núcleo
Trabajo
Instrucción privilegiada
Núcleo monolítico
Tarea
Lenguaje de control de trabajos
Planificación
Tiempo compartido
Micronúcleo
Procesamiento batch o en lotes
CUESTIONES DE REPASO
2.1. ¿Cuáles son los tres objetivos de diseño de un sistema operativo?
2.2. ¿Qué es el núcleo de un sistema operativo?
2.3. ¿Qué es multiprogramación?
2.4. ¿Qué es un proceso?
2.5. ¿Cómo utiliza el sistema operativo el contexto de ejecución de un proceso?
2.6. Liste y explique brevemente cinco responsabilidades relacionadas con la gestión de almacenamiento de un sistema operativo típico.
2.7. Explique la distinción entre una dirección real y una dirección virtual.
2.8. Describa la técnica de planificación round-robin o turno rotatorio.
2.9. Explique la diferencia entre un núcleo monolítico y un micronúcleo.
2.10. ¿En qué consiste el uso de multihilos o multithreading?
PROBLEMAS
2.1. Supóngase que se tiene un computador multiprogramado en el cual cada trabajo tiene características idénticas. En un periodo de computación T, un trabajo gasta la mitad del tiempo en E/S y la otra mitad en actividad del procesador. Cada trabajo ejecuta un total de N
02-Capitulo 2
104
12/5/05
16:18
Página 104
Sistemas operativos. Aspectos internos y principios de diseño
periodos. Asúmase que se utiliza una planificación round-robin simple, y que las operaciones de E/S se pueden solapar con las operaciones del procesador. Defina las siguientes
cantidades:
• Tiempo de servicio = Tiempo real para completar un trabajo.
• Productividad = Número medio de trabajos completados en el periodo T.
• Utilización del procesador = Porcentaje de tiempo que el procesador está activo (no esperando).
Calcule estas cantidades para uno, dos y cuatro trabajos simultáneos, asumiendo que el periodo T se distribuye de las siguientes formas:
a) Primera mitad E/S, segunda mitad procesador.
b) Primero y cuarto cuartos E/S, segundo y tercer cuartos procesador.
2.2. Un programa limitado por la E/S es aquel que, si ejecuta solo, gasta más tiempo esperando
operaciones de E/S que utilizando el procesador. Un programa limitado por el procesador es
lo contrario. Supóngase un algoritmo de planificación a corto plazo que favorece a aquellos
programas que han utilizado poco tiempo de procesador en el pasado reciente. Explique por
qué este algoritmo favorece a los programas limitados por la E/S y no niega permanentemente el tiempo de procesador a los programas limitados por el procesador.
2.3. Contraste las políticas de planificación que se podrían utilizar cuando se intenta optimizar
un sistema de tiempo compartido y aquéllas que se utilizan para optimizar un sistema en lotes multiprogramado.
2.4. ¿Cuál es el propósito de las llamadas al sistema y cómo se relacionan las llamadas al sistema con el sistema operativo y el concepto de modo dual (modo núcleo y modo usuario)?
2.5. En el sistema operativo de IBM, OS/390, uno de los módulos principales en el núcleo es el
gestor de recursos del sistema (SRM: System Resource Manager). Este módulo es responsable de la asignación de recursos entre los espacios de direcciones (procesos). El SRM da a
OS/390 un grado de sofisticación único entre los sistemas operativos. Ningún otro sistema
operativo de mainframe, y ciertamente ningún otro tipo de sistema operativo, puede realizar
las funciones llevadas a cabo por SRM. El concepto de recurso incluye al procesador, memoria real y canales de E/S. SRM acumula estadísticas pertenecientes a la utilización del
procesador, canales y varias estructuras de datos clave. Su propósito es proporcionar un rendimiento óptimo basado en monitorización y análisis del rendimiento. La instalación proporciona varios objetivos de rendimiento y éstas sirven como guía al SRM, que modifica
dinámicamente la instalación y las características de rendimiento de los trabajos basándose
en la utilización del sistema. Adicionalmente, SRM proporciona informes que permiten al
operador entrenado refinar las configuraciones y el establecimiento de parámetros para mejorar el servicio de usuario.
Este problema es un ejemplo de actividad SRM. La memoria real se divide en bloques de igual
tamaño llamados marcos, de los cuales podría haber muchos miles. Cada marco puede contener un
bloque de memoria virtual, conocido como página. SRM recibe el control aproximadamente 20 veces
cada segundo, inspeccionando cada marco de página. Si la página no se ha referenciado o cambiado,
un contador se incrementa por 1. A lo largo del tiempo, SRM hace la media de estos números para
determinar el número de segundos medio que un marco de página en el sistema queda inalterable.
¿Cuál podría ser el propósito de esto y qué acción podría realizar SRM?
03-Capitulo 3
12/5/05
16:19
Página 105
PA RT E
II
PROCESOS
L
a tarea fundamental de cualquier sistema operativo moderno es la gestión de procesos. El sistema operativo debe reservar recursos para los procesos, permitir a los mismos compartir e intercambiar información, proteger los recursos de cada uno de ellos del resto, y permitir la sincronización entre procesos. Para conseguir alcanzar estos requisitos, el sistema operativo debe mantener
una estructura determinada para cada proceso que describa el estado y la propiedad de los recursos y
que permite al sistema operativo establecer el control sobre los procesos.
En un monoprocesador multiprogramado, la ejecución de varios procesos se puede intercalar en
el tiempo. En un multiprocesador, no sólo se intercala la ejecución de procesos, también es posible
que haya múltiples procesos que se ejecuten de forma simultánea. Tanto la ejecución intercalada
como de forma simultánea son tipos de concurrencia y llevan a que el sistema se enfrente a diferentes problemas, tanto en el ámbito de las aplicaciones de programador como en el de los sistemas
operativos.
En muchos sistemas operativos actuales, la problemática de la gestión de procesos se encuentra
ampliada por la introducción del concepto de hilo (thread). En un sistema multihilo, el concepto de
proceso mantiene los atributos de la propiedad de recursos, mientras que los aspectos de la ejecución
de múltiples flujos de instrucciones se encuentra relacionada con los hilos que ejecutan dentro de ese
proceso.
ÍNDICE PARA LA PARTE DOS
CAPÍTULO 3. DESCRIPCIÓN Y CONTROL DE PROCESOS
El objetivo de los sistemas operativos tradicionales es la gestión de procesos. Cada proceso se encuentra, en un instante dado, en uno de los diferentes estados de ejecución, que incluyen Listo, Ejecutando, y Bloqueado. El sistema operativo sigue la traza de estos estados de ejecución y gestiona el
movimiento de procesos entre los mismos. Con este fin el sistema operativo mantiene unas estructuras de datos complejas que describen cada proceso. El sistema operativo debe realizar las operaciones
de planificación y proporcionar servicios para la compartición entre procesos y la sincronización. El
Capítulo 3 repasa estas estructuras de datos y las técnicas utilizadas de forma habitual por los sistemas operativos para la gestión de procesos.
03-Capitulo 3
106
12/5/05
16:19
Página 106
Sistemas operativos. Aspectos internos y principios de diseño
CAPÍTULO 4. HILOS, SMP, Y MICRONÚCLEOS
El Capítulo 4 cubre tres áreas características de los sistemas operativos contemporáneos que representan unos avances sobre el diseño de los sistemas operativos tradicionales. En muchos sistemas
operativos, el concepto tradicional de proceso se ha dividido en dos partes: una de ellas que trata de
la propiedad de los recursos (proceso) y otra que trata de la ejecución del flujo de instrucciones (hilo
o thread). Un único proceso puede contener múltiples hilos. La organización multihilo proporciona
ventajas en la estructuración de las aplicaciones y en su rendimiento. El Capítulo 4 también examina
los multiprocesadores simétricos (SMP), que son sistemas que tienen múltiples procesadores, cada
uno de los cuales es capaz de ejecutar cualquier aplicación o el código de sistema. La organización
SMP mejora el rendimiento y la fiabilidad. SMP a menudo se usa en conjunto con la programación
multihilo, pero aún sin ella proporciona unas importantes mejoras a nivel de rendimiento. Para finalizar el Capítulo 4 examina el concepto de micronúcleo, que es un estilo de diseño de sistemas operativos que minimiza la cantidad de código de sistema que se ejecuta en modo núcleo. Las ventajas de
esta estrategia también se analizan.
CAPÍTULO 5. CONCURRENCIA. EXCLUSIÓN MUTUA Y SINCRONIZACIÓN
Dos temas centrales en los sistemas operativos modernos son las multiprogramación y el procesamiento distribuido. El concepto de concurrencia es fundamental para ambos, y fundamental también
para la tecnología de diseño de los sistemas operativos. El Capítulo 5 repasa dos aspectos del control
de la concurrencia: la exclusión mutua y la sincronización. La exclusión mutua se refiere a la posibilidad de que múltiples procesos (o hilos) compartan código, recursos, o datos de forma de que sólo
uno de ellos tenga acceso al objeto compartido en cada momento. La sincronización se encuentra relacionada con la exclusión mutua: es la posibilidad de que múltiples procesos coordinen sus actividades para intercambiar información. El Capítulo 5 proporciona un amplio tratamiento de los aspectos
relativos a la concurrencia, comenzando por un repaso de las consideraciones de diseño implicadas.
El capítulo proporciona una revisión del soporte hardware para la concurrencia presentando los mecanismos más importantes para darle soporte: semáforos, monitores, y paso de mensajes.
CAPÍTULO 6. CONCURRENCIA. INTERBLOQUEO E INANICIÓN
El Capítulo 6 muestra dos aspectos más de la concurrencia. Un interbloqueo es una situación en la
cual dos o más procesos están esperando a que otros miembros del conjunto completen una operación
para poder continuar, pero ninguno de los miembros es capaz de hacerlo. Los interbloqueos son un
fenómeno difícil de anticipar, y no hay soluciones generales fáciles para éstos. El Capítulo 6 muestra
las tres estrategias principales para manejar un interbloqueo: prevenirlo, evitarlo, y detectarlo. La inanición se refiere una situación en la cual un proceso se encuentra listo para ejecutar pero se le deniega
el acceso al procesador de forma continuada en deferencia a otros procesos. En su mayor parte, la
inanición se trata como una cuestión de planificación y por tanto la trataremos en la Parte Cuatro.
Aunque el Capítulo 6 se centra en los interbloqueos, la inanición se trata dentro del contexto de las
soluciones a los mismos necesarias para evitar el problema de la inanición.
03-Capitulo 3
12/5/05
16:19
Página 107
CAPÍTULO
3
Descripción y
control de procesos
3.1.
¿Qué es un proceso?
3.2.
Estados de los procesos
3.3.
Descripción de los procesos
3.4.
Control de procesos
3.5.
Gestión de procesos en UNIX SVR4
3.6.
Resumen
3.7.
Lecturas recomendadas
3.8.
Términos clave, cuestiones de repaso, y problemas
03-Capitulo 3
108
12/5/05
16:19
Página 108
Sistemas operativos. Aspectos internos y principios de diseño
El diseño de un sistema operativo debe reflejar ciertos requisitos generales. Todos los sistemas operativos multiprogramados, desde los sistemas operativos monousuario como Windows 98 hasta sistemas mainframes como IBM z/OS, que son capaces de dar soporte a miles de usuarios, se construyen
en torno al concepto de proceso. La mayoría de los requisitos que un sistema operativo debe cumplir
se pueden expresar con referencia a los procesos:
• El sistema operativo debe intercalar la ejecución de múltiples procesos, para maximizar la utilización del procesador mientras se proporciona un tiempo de respuesta razonable.
• El sistema operativo debe reservar recursos para los procesos conforme a una política específica (por ejemplo, ciertas funciones o aplicaciones son de mayor prioridad) mientras que al mismo tiempo evita interbloqueos1.
• Un sistema operativo puede requerir dar soporte a la comunicación entre procesos y la creación de procesos, mediante las cuales ayuda a la estructuración de las aplicaciones.
Se comienza el estudio detallado de los sistemas operativos examinando la forma en la que éstos
representan y controlan los procesos. Después de una introducción al concepto de proceso, el capítulo
presentará los estados de los procesos, que caracterizan el comportamiento de los mismos. Seguidamente, se presentarán las estructuras de datos que el sistema operativo usa para gestionar los procesos. Éstas incluyen las estructuras para representar el estado de cada proceso así como para registrar
características de los mismos que el sistema operativo necesita para alcanzar sus objetivos. Posteriormente, se verá cómo el sistema operativo utiliza estas estructuras para controlar la ejecución de los
procesos. Por último, se discute la gestión de procesos en UNIX SVR4. El Capítulo 4 proporciona
ejemplos más modernos de gestión de procesos, tales como Solaris, Windows, y Linux.
Nota: en este capítulo hay referencias puntuales a la memoria virtual. La mayoría de las veces podemos ignorar este concepto en relación con los procesos, pero en ciertos puntos de la discusión, las
consideraciones sobre memoria virtual se hacen pertinentes. La memoria virtual no se discutirá en detalle hasta el Capítulo 8; ya se ha proporcionado una somera visión general en el Capítulo 2.
3.1. ¿QUÉ ES UN PROCESO?
CONCEPTOS PREVIOS
Antes de definir el término proceso, es útil recapitular algunos de los conceptos ya presentados en los
Capítulos 1 y 2:
1. Una plataforma de computación consiste en una colección de recursos hardware, como procesador, memoria, módulos de E/S, relojes, unidades de disco y similares.
2. Las aplicaciones para computadores se desarrollan para realizar determinadas tareas. Suelen
aceptar entradas del mundo exterior, realizar algún procesamiento y generar salidas.
3. No es eficiente que las aplicaciones estén escritas directamente para una plataforma hardware
específica. Las principales razones son las siguientes:
1
Los interbloqueos se examinarán en el Capítulo 6. Como ejemplo sencillo, un interbloqueo ocurre si dos procesos necesitan
dos recursos iguales para continuar y cada uno de los procesos tiene la posesión de uno de los recursos. A menos que se realice alguna acción, cada proceso esperará indefinidamente por conseguir el otro recurso.
03-Capitulo 3
12/5/05
16:19
Página 109
Descripción y control de procesos
109
a) Numerosas aplicaciones pueden desarrollarse para la misma plataforma, de forma que tiene sentido desarrollar rutinas comunes para acceder a los recursos del computador.
b) El procesador por sí mismo proporciona únicamente soporte muy limitado para la multiprogramación. Es necesario disponer de software para gestionar la compartición del procesador así como otros recursos por parte de múltiples aplicaciones al mismo tiempo.
c) Cuando múltiples aplicaciones están activas al mismo tiempo es necesario proteger los datos, el uso de la E/S y los recursos propios de cada aplicación con respecto a las demás.
4. El sistema operativo se desarrolló para proporcionar una interfaz apropiada para las aplicaciones, rica en funcionalidades, segura y consistente. El sistema operativo es una capa de software entre las aplicaciones y el hardware del computador (Figura 2.1) que da soporte a aplicaciones y utilidades.
5. Se puede considerar que el sistema operativo proporciona una representación uniforme y abstracta de los recursos, que las aplicaciones pueden solicitar y acceder. Los recursos incluyen la
memoria principal, las interfaces de red, los sistemas de ficheros, etc. Una vez que el sistema
operativo ha creado estas abstracciones de los recursos para que las aplicaciones las usen,
debe también controlar su uso. Por ejemplo, un sistema operativo podría permitir compartición
y protección de recursos.
Ahora que se conocen los conceptos de aplicaciones, software de sistema y de recursos se está en
disposición de hablar sobre cómo un sistema operativo puede, de forma ordenada, gestionar la ejecución de aplicaciones de forma que:
• Los recursos estén disponibles para múltiples aplicaciones.
• El procesador físico se conmute entre múltiples aplicaciones, de forma que todas lleguen a
procesarse.
• El procesador y los dispositivos de E/S se puedan usar de forma eficiente.
El enfoque adoptado por todos los sistemas operativos modernos recae en un modelo bajo el cual
la ejecución de una aplicación se corresponde con la existencia de uno o más procesos.
PROCESOS Y BLOQUES DE CONTROL DE PROCESOS
Se debe recordar que en el Capítulo 2 se sugirieron diversas definiciones del término proceso, incluyendo:
• Un programa en ejecución.
• Una instancia de un programa ejecutado en un computador.
• La entidad que se puede asignar y ejecutar en un procesador.
• Una unidad de actividad que se caracteriza por la ejecución de una secuencia de instrucciones,
un estado actual, y un conjunto de recursos del sistema asociados.
También se puede pensar en un proceso como en una entidad que consiste en un número de elementos. Los dos elementos esenciales serían el código de programa (que puede compartirse con
otros procesos que estén ejecutando el mismo programa) y un conjunto de datos asociados a dicho
03-Capitulo 3
110
12/5/05
16:19
Página 110
Sistemas operativos. Aspectos internos y principios de diseño
código. Supongamos que el procesador comienza a ejecutar este código de programa, y que nos referiremos a esta entidad en ejecución como un proceso. En cualquier instante puntual del tiempo, mientras el proceso está en ejecución, este proceso se puede caracterizar por una serie de elementos, incluyendo los siguientes:
• Identificador. Un identificador único asociado a este proceso, para distinguirlo del resto de
procesos.
• Estado. Si el proceso está actualmente corriendo, está en el estado en ejecución.
• Prioridad: Nivel de prioridad relativo al resto de procesos.
• Contador de programa. La dirección de la siguiente instrucción del programa que se ejecutará.
• Punteros a memoria. Incluye los punteros al código de programa y los datos asociados a dicho proceso, además de cualquier bloque de memoria compartido con otros procesos.
• Datos de contexto. Estos son datos que están presenten en los registros del procesador cuando
el proceso está corriendo.
• Información de estado de E/S. Incluye las peticiones de E/S pendientes, dispositivos de E/S
(por ejemplo, una unidad de cinta) asignados a dicho proceso, una lista de los ficheros en uso
por el mismo, etc.
• Información de auditoría. Puede incluir la cantidad de tiempo de procesador y de tiempo de
reloj utilizados, así como los límites de tiempo, registros contables, etc.
La información de la lista anterior se almacena en una estructura de datos, que se suele llamar
bloque de control de proceso (process control block) (Figura 3.1), que el sistema operativo crea y
gestiona. El punto más significativo en relación al bloque de control de proceso, o BCP, es que contiene suficiente información de forma que es posible interrumpir el proceso cuando está corriendo y
posteriormente restaurar su estado de ejecución como si no hubiera habido interrupción alguna. El
BCP es la herramienta clave que permite al sistema operativo dar soporte a múltiples procesos y proporcionar multiprogramación. Cuando un proceso se interrumpe, los valores actuales del contador de
programa y los registros del procesador (datos de contexto) se guardan en los campos correspondientes del BCP y el estado del proceso se cambia a cualquier otro valor, como bloqueado o listo (descritos a continuación). El sistema operativo es libre ahora para poner otro proceso en estado de ejecución. El contador de programa y los datos de contexto se recuperan y cargan en los registros del
procesador y este proceso comienza a correr.
De esta forma, se puede decir que un proceso está compuesto del código de programa y los datos
asociados, además del bloque de control de proceso o BCP. Para un computador monoprocesador, en
un instante determinado, como máximo un único proceso puede estar corriendo y dicho proceso estará en el estado en ejecución.
3.2. ESTADOS DE LOS PROCESOS
Como se acaba de comentar, para que un programa se ejecute, se debe crear un proceso o tarea para
dicho programa. Desde el punto de vista del procesador, él ejecuta instrucciones de su repertorio de
instrucciones en una secuencia dictada por el cambio de los valores del registro contador de programa. A lo largo del tiempo, el contador de programa puede apuntar al código de diferentes programas
que son parte de diferentes procesos. Desde el punto de vista de un programa individual, su ejecución
implica una secuencia de instrucciones dentro de dicho programa.
03-Capitulo 3
12/5/05
16:19
Página 111
Descripción y control de procesos
111
Identificador
Estado
Prioridad
Contador de programa
Punteros de memoria
Datos de contexto
Información de
estado de E/S
Información de
auditoría
Figura 3.1.
Bloque de control de programa (BCP) simplificado.
Se puede caracterizar el comportamiento de un determinado proceso, listando la secuencia de instrucciones que se ejecutan para dicho proceso. A esta lista se la denomina traza del proceso. Se puede caracterizar el comportamiento de un procesador mostrando cómo las trazas de varios procesos se entrelazan.
Considere un ejemplo. La Figura 3.2 muestra el despliegue en memoria de tres procesos. Para
simplificar la exposición, se asume que dichos procesos no usan memoria virtual; por tanto, los tres
procesos están representados por programas que residen en memoria principal. De manera adicional,
existe un pequeño programa activador (dispatcher) que intercambia el procesador de un proceso a
otro. La Figura 3.3 muestra las trazas de cada uno de los procesos en los primeros instantes de ejecución. Se muestran las 12 primeras instrucciones ejecutadas por los procesos A y C. El proceso B ejecuta 4 instrucciones y se asume que la cuarta instrucción invoca una operación de E/S, a la cual el
proceso debe esperar.
Ahora vea estas trazas desde el punto de vista del procesador. La Figura 3.4 muestra las trazas entrelazadas resultante de los 52 primeros ciclos de ejecución (por conveniencia los ciclos de instrucciones han sido numerados). En este ejemplo, se asume que el sistema operativo sólo deja que un proceso
continúe durante seis ciclos de instrucción, después de los cuales se interrumpe; lo cual previene que
un solo proceso monopolice el uso del tiempo del procesador. Como muestra la Figura 3.4, las primeras seis instrucciones del proceso A se ejecutan seguidas de una alarma de temporización (time-out) y
de la ejecución de cierto código del activador, que ejecuta seis instrucciones antes de devolver el control al proceso B2. Después de que se ejecuten cuatro instrucciones, el proceso B solicita una acción de
2
El reducido número de instrucciones ejecutadas por los procesos y por el planificador es irreal; se ha utilizado para simplificar
el ejemplo y clarificar las explicaciones.
03-Capitulo 3
112
12/5/05
16:19
Página 112
Sistemas operativos. Aspectos internos y principios de diseño
Dirección
0
100
Contador de programa
Memoria principal
8000
Activador
5000
Proceso A
8000
Proceso B
12000
Proceso C
Figura 3.2.
Instantánea de un ejemplo de ejecución (Figura 3.4) en el ciclo de instrucción 13.
5000
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
(a) Traza del Proceso A
8000
8001
8002
8003
(b) Traza del Proceso B
12000
12001
12002
12003
12004
12005
12006
12007
12008
12009
12010
12011
(c) Traza del Proceso C
5000 = Dirección de comienzo del programa del Proceso A.
8000 = Dirección de comienzo del programa del Proceso B.
12000 = Dirección de comienzo del programa del Proceso C.
Figura 3.3.
Traza de los procesos de la Figura 3.2.
E/S, para la cual debe esperar. Por tanto, el procesador deja de ejecutar el proceso B y pasa a ejecutar
el proceso C, por medio del activador. Después de otra alarma de temporización, el procesador vuelve
al proceso A. Cuando este proceso llega a su temporización, el proceso B aún estará esperando que se
complete su operación de E/S, por lo que el activador pasa de nuevo al proceso C.
03-Capitulo 3
12/5/05
16:19
Página 113
Descripción y control de procesos
113
UN MODELO DE PROCESO DE DOS ESTADOS
La responsabilidad principal del sistema operativo es controlar la ejecución de los procesos; esto incluye determinar el patrón de entrelazado para la ejecución y asignar recursos a los procesos. El primer paso en el diseño de un sistema operativo para el control de procesos es describir el comportamiento que se desea que tengan los procesos.
Se puede construir el modelo más simple posible observando que, en un instante dado, un proceso está siendo ejecutando por el procesador o no. En este modelo, un proceso puede estar en dos estados: Ejecutando o No Ejecutando, como se muestra en la Figura 3.5a. Cuando el sistema operativo
crea un nuevo proceso, crea el bloque de control de proceso (BCP) para el nuevo proceso e inserta dicho proceso en el sistema en estado No Ejecutando. El proceso existe, es conocido por el sistema
operativo, y está esperando su oportunidad de ejecutar. De cuando en cuando, el proceso actualmente
en ejecución se interrumpirá y una parte del sistema operativo, el activador, seleccionará otro proceso
1
2
3
4
5
6
5000
5001
5002
5003
5004
5005
7
8
9
10
11
12
13
14
15
16
100
101
102
103
104
105
8000
8001
8002
8003
17
18
19
20
21
22
23
24
25
26
100
101
102
103
104
105
12000
12001
12002
12003
27
28
12004
12005
29
30
31
32
33
34
35
36
37
38
39
40
100
101
102
103
104
105
5006
5007
5008
5009
5010
5011
41
42
43
44
45
46
47
48
49
50
51
52
100
101
102
103
104
105
12006
12007
12008
12009
12010
12011
Temporización
Temporización
Temporización
Petición de E/S
Temporización
100 = Dirección de comienzo del programa activador.
Las zonas sombreadas indican la ejecución del proceso de activación;
la primera y la tercera columna cuentan ciclos de instrucciones;
la segunda y la cuarta columna las direcciones de las instrucciones que se ejecutan
Figura 3.4.
Traza combinada de los procesos de la Figura 3.2.
03-Capitulo 3
114
12/5/05
16:19
Página 114
Sistemas operativos. Aspectos internos y principios de diseño
a ejecutar. El proceso saliente pasará del estado Ejecutando a No Ejecutando y pasará a Ejecutando
un nuevo proceso.
De este modelo simple, ya se puede apreciar algo del diseño de los elementos del sistema operativo. Cada proceso debe representarse de tal manera que el sistema operativo pueda seguirle la pista.
Es decir, debe haber información correspondiente a cada proceso, incluyendo el estado actual y su localización en memoria; esto es el bloque de control de programa. Los procesos que no están ejecutando deben estar en una especie de cola, esperando su turno de ejecución. La Figura 3.5b sugiere esta
estructura. Existe una sola cola cuyas entradas son punteros al BCP de un proceso en particular. Alternativamente, la cola debe consistir en una lista enlazada de bloques de datos, en la cual cada bloque representa un proceso; exploraremos posteriormente esta última implementación.
Podemos describir el comportamiento del activador en términos de este diagrama de colas. Un
proceso que se interrumpe se transfiere a la cola de procesos en espera. Alternativamente, si el proceso ha finalizado o ha sido abortado, se descarta (sale del sistema). En cualquier caso, el activador selecciona un proceso de la cola para ejecutar.
CREACIÓN Y TERMINACIÓN DE PROCESOS
Antes de intentar refinar nuestro sencillo modelo de dos estados, resultará útil hablar de la creación y
terminación de los procesos; por último, y de forma independiente del modelo de comportamiento de
procesos que se use, la vida de un proceso está acotada entre su creación y su terminación.
Creación de un proceso. Cuando se va a añadir un nuevo proceso a aquellos que se están gestionando en un determinado momento, el sistema operativo construye las estructuras de datos que
se usan para manejar el proceso (como se describió en la Sección 3.3) y reserva el espacio de direcciones en memoria principal para el proceso. Estas acciones constituyen la creación de un nuevo
proceso.
Activación
Entrada
No
ejecutado
Salida
Ejecutado
Detención
(a) Diagrama de transiciones de estados
Cola
Entrada
Activación
Procesador
Salida
Detención
(b) Modelos de colas
Figura 3.5.
Modelo de proceso de dos estados.
03-Capitulo 3
12/5/05
16:19
Página 115
Descripción y control de procesos
115
Existen cuatro eventos comunes que llevan a la creación de un proceso, como se indica en la Tabla 3.1. En un entorno por lotes, un proceso se crea como respuesta a una solicitud de trabajo. En un
entorno interactivo, un proceso se crea cuando un nuevo usuario entra en el sistema. En ambos casos
el sistema operativo es responsable de la creación de nuevos procesos. Un sistema operativo puede, a
petición de una aplicación, crear procesos. Por ejemplo, si un usuario solicita que se imprima un fichero, el sistema operativo puede crear un proceso que gestione la impresión. El proceso solicitado
puede, de esta manera, operar independientemente del tiempo requerido para completar la tarea de
impresión.
TABLA 3.1.
Razones para la creación de un proceso.
Nuevo proceso de lotes
El sistema operativo dispone de un flujo de control de lotes de
trabajos, habitualmente una cinta un disco. Cuando el sistema
operativo está listo para procesar un nuevo trabajo, leerá la siguiente secuencia de mandatos de control de trabajos.
Sesión interactiva
Un usuario desde un terminal entra en el sistema.
Creado por el sistema
operativo para proporcionar
un servicio
El sistema operativo puede crear un proceso para realizar una función en representación de un programa de usuario, sin que el
usuario tenga que esperar (por ejemplo, un proceso para controlar la impresión).
Creado por un proceso existente
Por motivos de modularidad o para explotar el paralelismo, un
programa de usuario puede ordenar la creación de un número de
procesos.
Tradicionalmente, un sistema operativo creaba todos los procesos de una forma que era transparente para usuarios y programas. Esto aún es bastante común en muchos sistemas operativos contemporáneos. Sin embargo, puede ser muy útil permitir a un proceso la creación de otro. Por ejemplo, un
proceso de aplicación puede generar otro proceso para recibir los datos que la aplicación está generando y para organizar esos datos de una forma apropiada para su posterior análisis. El nuevo proceso
ejecuta en paralelo con el proceso original y se activa cuando los nuevos datos están disponibles. Esta
organización puede ser muy útil en la estructuración de la aplicación. Otro ejemplo, un proceso servidor (por ejemplo, un servidor de impresoras, servidor de ficheros) puede generar un proceso por cada
solicitud que esté manejando. Cuando un sistema operativo crea un proceso a petición explícita de
otro proceso, dicha acción se denomina creación del proceso.
Cuando un proceso lanza otro, al primero se le denomina proceso padre, y al proceso creado se
le denomina proceso hijo. Habitualmente, la relación entre procesos necesita comunicación y cooperación entre ellos. Alcanzar esta cooperación es una tarea complicada para un programador; este aspecto se verá en el Capítulo 5.
Terminación de procesos. La Tabla 3.2 resume las razones típicas para la terminación de un proceso. Todo sistema debe proporcionar los mecanismos mediante los cuales un proceso indica su finalización, o que ha completado su tarea. Un trabajo por lotes debe incluir una instrucción HALT o una
llamada a un servicio de sistema operativo especifica para su terminación. En el caso anterior, la instrucción HALT generará una interrupción para indicar al sistema operativo que dicho proceso ha finalizado. Para una aplicación interactiva, las acciones del usuario indicarán cuando el proceso ha terminado. Por ejemplo, en un sistema de tiempo compartido, el proceso de un usuario en particular puede
terminar cuando el usuario sale del sistema o apaga su terminal. En un ordenador personal o una estación de trabajo, el usuario puede salir de una aplicación (por ejemplo, un procesador de texto o una
03-Capitulo 3
116
12/5/05
16:19
Página 116
Sistemas operativos. Aspectos internos y principios de diseño
hoja de cálculo). Todas estas acciones tienen como resultado final la solicitud de un servicio al sistema operativo para terminar con el proceso solicitante.
Adicionalmente, un número de error o una condición de fallo puede llevar a la finalización de un
proceso. La Tabla 3.2 muestra una lista de las condiciones más habituales de finalización por esos
motivos3.
Tabla 3.2.
Razones para la terminación de un proceso.
Finalización normal
El proceso ejecuta una llamada al sistema operativo para indicar que
ha completado su ejecución
Límite de tiempo excedido
El proceso ha ejecutado más tiempo del especificado en un límite máximo. Existen varias posibilidades para medir dicho tiempo. Estas incluyen el tiempo total utilizado, el tiempo utilizado únicamente en ejecución, y, en el caso de procesos interactivos, la cantidad de tiempo
desde que el usuario realizó la última entrada.
Memoria no disponible
El proceso requiere más memoria de la que el sistema puede proporcionar.
Violaciones de frontera
El proceso trata de acceder a una posición de memoria a la cual no tiene acceso permitido.
Error de protección
El proceso trata de usar un recurso, por ejemplo un fichero, al que no
tiene permitido acceder, o trata de utilizarlo de una forma no apropia,
por ejemplo, escribiendo en un fichero de sólo lectura.
Error aritmético
El proceso trata de realizar una operación de cálculo no permitida, tal
como una división por 0, o trata de almacenar números mayores de los
que la representación hardware puede codificar.
Límite de tiempo
El proceso ha esperado más tiempo que el especificado en un valor
máximo para que se cumpla un determinado evento.
Fallo de E/S
Se ha producido un error durante una operación de entrada o salida,
por ejemplo la imposibilidad de encontrar un fichero, fallo en la lectura
o escritura después de un límite máximo de intentos (cuando, por
ejemplo, se encuentra un área defectuosa en una cinta), o una operación inválida (la lectura de una impresora en línea).
Instrucción no válida
El proceso intenta ejecutar una instrucción inexistente (habitualmente
el resultado de un salto a un área de datos y el intento de ejecutar dichos datos).
Instrucción privilegiada
El proceso intenta utilizar una instrucción reservada al sistema operativo.
Uso inapropiado de datos
Una porción de datos es de tipo erróneo o no se encuentra inicializada.
Intervención del operador
por el sistema operativo
Por alguna razón, el operador o el sistema operativo ha finalizado el
proceso (por ejemplo, se ha dado una condición de interbloqueo).
Terminación del proceso
padre
Cuando un proceso padre termina, el sistema operativo puede automáticamente finalizar todos los procesos hijos descendientes de dicho padre.
Solicitud del proceso padre
Un proceso padre habitualmente tiene autoridad para finalizar sus propios procesos descendientes.
3
Un sistema operativo compasivo podría en algunos casos permitir al usuario recuperarse de un fallo sin terminar el proceso.
Por ejemplo, si un usuario solicita acceder a un fichero y se deniega ese acceso, el sistema operativo podría simplemente informar al
usuario de ese hecho y permitir que el proceso continúe.
03-Capitulo 3
12/5/05
16:19
Página 117
Descripción y control de procesos
117
Por último, en ciertos sistemas operativos, un proceso puede terminarse por parte del proceso que
lo creó o cuando dicho proceso padre a su vez ha terminado.
MODELO DE PROCESO DE CINCO ESTADOS
Si todos los procesos estuviesen siempre preparados para ejecutar, la gestión de colas proporcionada
en la Figura 3.5b sería efectiva. La cola es una lista de tipo FIFO y el procesador opera siguiendo una
estrategia cíclica (round-robin o turno rotatorio) sobre todos los procesos disponibles (cada proceso
de la cola tiene cierta cantidad de tiempo, por turnos, para ejecutar y regresar de nuevo a la cola, a
menos que se bloquee). Sin embargo, hasta con el sencillo ejemplo que vimos antes, esta implementación es inadecuada: algunos procesos que están en el estado de No Ejecutando están listos para ejecutar, mientras que otros están bloqueados, esperando a que se complete una operación de E/S. Por
tanto, utilizando una única cola, el activador no puede seleccionar únicamente los procesos que lleven
más tiempo en la cola. En su lugar, debería recorrer la lista buscando los procesos que no estén bloqueados y que lleven en la cola más tiempo.
Una forma más natural para manejar esta situación es dividir el estado de No Ejecutando en dos
estados, Listo y Bloqueado. Esto se muestra la Figura 3.6. Para gestionarlo correctamente, se han
añadido dos estados adicionales que resultarán muy útiles. Estos cinco estados en el nuevo diagrama
son los siguientes:
• Ejecutando. El proceso está actualmente en ejecución. Para este capítulo asumimos que el
computador tiene un único procesador, de forma que sólo un proceso puede estar en este estado en un instante determinado.
• Listo. Un proceso que se prepara para ejecutar cuando tenga oportunidad.
• Bloqueado. Un proceso que no puede ejecutar hasta que se cumpla un evento determinado o
se complete una operación E/S.
• Nuevo. Un proceso que se acaba de crear y que aún no ha sido admitido en el grupo de procesos ejecutables por el sistema operativo. Típicamente, se trata de un nuevo proceso que no ha
sido cargado en memoria principal, aunque su bloque de control de proceso (BCP) si ha sido
creado.
• Saliente. Un proceso que ha sido liberado del grupo de procesos ejecutables por el sistema operativo, debido a que ha sido detenido o que ha sido abortado por alguna razón.
Activación
Nuevo
Admisión
Listo
Ejecutando
Salida
Temporización
Sucede
evento
Espera por
evento
Bloqueado
Figura 3.6.
Modelo de proceso de cinco estados.
Saliente
03-Capitulo 3
118
12/5/05
16:19
Página 118
Sistemas operativos. Aspectos internos y principios de diseño
Los estados Nuevo y Saliente son útiles para construir la gestión de procesos. El estado Nuevo se
corresponde con un proceso que acaba de ser definido. Por ejemplo, si un nuevo usuario intenta entrar
dentro de un sistema de tiempo compartido o cuando se solicita un nuevo trabajo a un sistema de proceso por lotes, el sistema operativo puede definir un nuevo proceso en dos etapas. Primero, el sistema operativo realiza todas las tareas internas que correspondan. Se asocia un identificador a dicho proceso. Se
reservan y construyen todas aquellas tablas que se necesiten para gestionar al proceso. En este punto, el
proceso se encuentra el estado Nuevo. Esto significa que el sistema operativo ha realizado todas las tareas necesarias para crear el proceso pero el proceso en sí, aún no se ha puesto en ejecución. Por ejemplo, un sistema operativo puede limitar el número de procesos que puede haber en el sistema por razones de rendimiento o limitaciones de memoria principal. Mientras un proceso está en el estado Nuevo,
la información relativa al proceso que se necesite por parte del sistema operativo se mantiene en tablas
de control de memoria principal. Sin embargo, el proceso en sí mismo no se encuentra en memoria principal. Esto es, el código de programa a ejecutar no se encuentra en memoria principal, y no se ha reservado ningún espacio para los datos asociados al programa. Cuando un proceso se encuentra en el estado
Nuevo, el programa permanece en almacenamiento secundario, normalmente en disco4.
De forma similar, un proceso sale del sistema en dos fases. Primero, el proceso termina cuando
alcanza su punto de finalización natural, cuando es abortado debido a un error no recuperable, o
cuando otro proceso con autoridad apropiada causa que el proceso se aborte. La terminación mueve
el proceso al estado Saliente. En este punto, el proceso no es elegible de nuevo para su ejecución. Las
tablas y otra información asociada con el trabajo se encuentran temporalmente preservadas por el sistema operativo, el cual proporciona tiempo para que programas auxiliares o de soporte extraigan la
información necesaria. Por ejemplo, un programa de auditoría puede requerir registrar el tiempo de
proceso y otros recursos utilizados por este proceso saliente con objeto de realizar una contabilidad
de los recursos del sistema. Un programa de utilidad puede requerir extraer información sobre el histórico de los procesos por temas relativos con el rendimiento o análisis de la utilización. Una vez que
estos programas han extraído la información necesaria, el sistema operativo no necesita mantener
ningún dato relativo al proceso y el proceso se borra del sistema.
La Figura 3.6 indica que tipos de eventos llevan a cada transición de estado para cada proceso;
las posibles transiciones son las siguientes:
• Null Æ Nuevo. Se crea un nuevo proceso para ejecutar un programa. Este evento ocurre por
cualquiera de las relaciones indicadas en la tabla 3.1.
• Nuevo Æ Listo. El sistema operativo mueve a un proceso del estado nuevo al estado listo
cuando éste se encuentre preparado para ejecutar un nuevo proceso. La mayoría de sistemas
fijan un límite basado en el número de procesos existentes o la cantidad de memoria virtual
que se podrá utilizar por parte de los procesos existentes. Este límite asegura que no haya demasiados procesos activos y que se degrade el rendimiento sistema.
• Listo Æ Ejecutando. Cuando llega el momento de seleccionar un nuevo proceso para ejecutar,
el sistema operativo selecciona uno los procesos que se encuentre en el estado Listo. Esta es una
tarea la lleva acabo el planificador. El planificador se estudiará más adelante en la Parte Cuatro.
• Ejecutando Æ Saliente. el proceso actual en ejecución se finaliza por parte del sistema operativo tanto si el proceso indica que ha completado su ejecución como si éste se aborta. Véase
tabla 3.2.
4
En las explicaciones de este párrafo, hemos ignorado el concepto de memoria virtual. En sistemas que soporten la memoria
virtual, cuando un proceso se mueve de Nuevo a Listo, su código de programa y sus datos se cargan en memoria virtual. La memoria
virtual se ha explicado brevemente en el Capítulo 2 y se examinará con más detalle en el Capítulo 8.
03-Capitulo 3
12/5/05
16:19
Página 119
Descripción y control de procesos
119
• Ejecutando Æ Listo. La razón más habitual para esta transición es que el proceso en ejecución haya alcanzado el máximo tiempo posible de ejecución de forma ininterrumpida;
prácticamente todos los sistemas operativos multiprogramados imponen este tipo de restricción de tiempo. Existen otras posibles causas alternativas para esta transición, que no están
incluidas en todos los sistemas operativos. Es de particular importancia el caso en el cual el
sistema operativo asigna diferentes niveles de prioridad a diferentes procesos. Supóngase,
por ejemplo, que el proceso A está ejecutando a un determinado nivel de prioridad, y el
proceso B, a un nivel de prioridad mayor, y que se encuentra bloqueado. Si el sistema operativo se da cuenta de que se produce un evento al cual el proceso B está esperando, moverá el proceso B al estado de Listo. Esto puede interrumpir al proceso A y poner en ejecución al proceso B. Decimos, en este caso, que el sistema operativo ha expulsado al proceso
A5. Adicionalmente, un proceso puede voluntariamente dejar de utilizar el procesador. Un
ejemplo son los procesos que realiza alguna función de auditoría o de mantenimiento de
forma periódica.
• Ejecutando Æ Bloqueado. Un proceso se pone en el estado Bloqueado si solicita algo por lo
cual debe esperar. Una solicitud al sistema operativo se realiza habitualmente por medio de
una llamada al sistema; esto es, una llamada del proceso en ejecución a un procedimiento que
es parte del código del sistema operativo. Por ejemplo, un proceso ha solicitado un servicio
que el sistema operativo no puede realizar en ese momento. Puede solicitar un recurso, como
por ejemplo un fichero o una sección compartida de memoria virtual, que no está inmediatamente disponible. Cuando un proceso quiere iniciar una acción, tal como una operación de
E/S, que debe completarse antes de que el proceso continúe. Cuando un proceso se comunica
con otro, un proceso puede bloquearse mientras está esperando a que otro proceso le proporcione datos o esperando un mensaje de ese otro proceso.
• Bloqueado Æ Listo. Un proceso en estado Bloqueado se mueve al estado Listo cuando sucede el evento por el cual estaba esperando.
• Listo Æ Saliente. Por claridad, esta transición no se muestra en el diagrama de estados. En
algunos sistemas, un padre puede terminar la ejecución de un proceso hijo en cualquier momento. También, si el padre termina, todos los procesos hijos asociados con dicho padre pueden finalizarse.
• Bloqueado Æ Saliente. Se aplican los comentarios indicados en el caso anterior.
Si regresamos a nuestro sencillo ejemplo, Figura 3.7, ahí se muestra la transición entre cada uno
de los estados de proceso. La figura 3.8a sugiere la forma de aplicar un esquema de dos colas: la colas de Listos y la cola de Bloqueados. Cada proceso admitido por el sistema, se coloca en la cola de
Listos. Cuando llega el momento de que el sistema operativo seleccione otro proceso a ejecutar, selecciona uno de la cola de Listos. En ausencia de un esquema de prioridad, esta cola puede ser una
lista de tipo FIFO (first-in-first-out). Cuando el proceso en ejecución termina de utilizar el procesador, o bien finaliza o bien se coloca en la cola de Listos o de Bloqueados, dependiendo de las circunstancias. Por último, cuando sucede un evento, cualquier proceso en la cola de Bloqueados que únicamente esté esperando a dicho evento, se mueve a la cola de Listos.
Esta última transición significa que, cuando sucede un evento, el sistema operativo debe recorrer
la cola entera de Bloqueados, buscando aquellos procesos que estén esperando por dicho evento. En
5
En general, el término expulsión (preemption) se define como la reclamación de un recurso por parte de un proceso antes de
que el proceso que lo poseía finalice su uso. En este caso, el recurso es el procesador. El proceso está ejecutando y puede continuar
su ejecución pero es expulsado por otro proceso que va a entrar a ejecutar.
03-Capitulo 3
120
12/5/05
16:19
Página 120
Sistemas operativos. Aspectos internos y principios de diseño
Proceso A
Proceso B
Proceso C
Activador
0
5
10
15
Ejecutando
Figura 3.7.
20
Listo
25
30
35
40
45
50
Bloqueado
Estado de los procesos de la traza de la Figura 3.4.
los sistemas operativos con muchos procesos, esto puede significar cientos o incluso miles de procesos en esta lista, por lo que sería mucho más eficiente tener una cola por cada evento. De esta forma,
cuando sucede un evento, la lista entera de procesos de la cola correspondiente se movería al estado
de Listo (Figura 3. 8b).
Un refinamiento final sería: si la activación de procesos está dictada por un esquema de prioridades, sería conveniente tener varias colas de procesos listos, una por cada nivel de prioridad. El sistema operativo podría determinar cual es el proceso listo de mayor prioridad simplemente seleccionando éstas en orden.
PROCESOS SUSPENDIDOS
La necesidad de intercambio o swapping. Los tres principales estados descritos (Listo, Ejecutando, Bloqueado) proporcionan una forma sistemática de modelizar el comportamiento de los procesos y diseñar la implementación del sistema operativo. Se han construido algunos sistemas operativos
utilizando únicamente estos tres estados.
Sin embargo, existe una buena justificación para añadir otros estados al modelo. Para ver este beneficio de nuevos estados, vamos a suponer un sistema que no utiliza memoria virtual. Cada proceso
que se ejecuta debe cargarse completamente en memoria principal. Por ejemplo, en la Figura 3.8b, todos los procesos en todas las colas deben residir en memoria principal.
Recuérdese que la razón de toda esta compleja maquinaria es que las operaciones de E/S son mucho más lentas que los procesos de cómputo y, por tanto, el procesador en un sistema monoprogramado estaría ocioso la mayor parte del tiempo. Pero los ajustes de la Figura 3.8b no resuelven completamente el problema. Es verdad que, en este caso, la memoria almacena múltiples procesos y el
procesador puede asignarse a otro proceso si el que lo usa se queda bloqueado. La diferencia de velocidad entre el procesador y la E/S es tal que sería muy habitual que todos los procesos en memoria se
encontrasen a esperas de dichas operaciones. Por tanto, incluso en un sistema multiprogramado, el
procesador puede estar ocioso la mayor parte del tiempo.
¿Qué se puede hacer? La memoria principal puede expandirse para acomodar más procesos. Hay
dos fallos en esta solución. Primero, existe un coste asociado a la memoria principal, que, desde un
03-Capitulo 3
12/5/05
16:19
Página 121
Descripción y control de procesos
121
Salida
Cola de Listos
Activación
Admisión
Procesador
Temporización
Cola de Bloqueados
Espera por evento
Ocurre
evento
(a) Cola simple de Bloqueados
Salida
Cola de Listos
Admisión
Activación
Procesador
Temporización
Cola del evento 1
Espera por evento 1
Ocurre
evento 1
Cola del evento 2
Espera por evento 2
Ocurre
evento 2
Cola del evento n
Espera por evento n
Ocurre
evento n
(b) Múltiples colas de Bloqueados
Figura 3.8.
Modelo de colas de la Figura 3.6.
coste reducido a nivel de bytes, comienza a incrementarse según nos acercamos a un almacenamiento
de gigabytes. Segundo, el apetito de los programas a nivel de memoria ha crecido tan rápido como ha
bajado el coste de las memorias. De forma que las grandes memorias actuales han llevado a ejecutar
procesos de gran tamaño, no más procesos.
Otra solución es el swapping (memoria de intercambio), que implica mover parte o todo el proceso
de memoria principal al disco. Cuando ninguno de los procesos en memoria principal se encuentra en estado Listo, el sistema operativo intercambia uno de los procesos bloqueados a disco, en la cola de Suspendidos. Esta es una lista de procesos existentes que han sido temporalmente expulsados de la memoria
principal, o suspendidos. El sistema operativo trae otro proceso de la cola de Suspendidos o responde a
una solicitud de un nuevo proceso. La ejecución continúa con los nuevos procesos que han llegado.
El swapping, sin embargo, es una operación de E/S, y por tanto existe el riesgo potencial de hacer
que el problema empeore. Pero debido a que la E/S en disco es habitualmente más rápida que la E/S
sobre otros sistemas (por ejemplo, comparado con cinta o impresora), el swapping habitualmente mejora el rendimiento del sistema.
Con el uso de swapping tal y como se ha escrito, debe añadirse un nuevo estado a nuestro modelo
de comportamiento de procesos (Figura 3.9a): el estado Suspendido. Cuando todos los procesos en
03-Capitulo 3
122
12/5/05
16:19
Página 122
Sistemas operativos. Aspectos internos y principios de diseño
memoria principal se encuentran en estado Bloqueado, el sistema operativo puede suspender un proceso poniéndolo en el estado Suspendido y transfiriéndolo a disco. El espacio que se libera en memoria principal puede usarse para traer a otro proceso.
Cuando el sistema operativo ha realizado la operación de swap (transferencia a disco de un
proceso), tiene dos opciones para seleccionar un nuevo proceso para traerlo a memoria principal:
puede admitir un nuevo proceso que se haya creado o puede traer un proceso que anteriormente se
encontrase en estado de Suspendido. Parece que sería preferible traer un proceso que anteriormente
estuviese suspendido, para proporcionar dicho servicio en lugar de incrementar la carga total del
sistema.
Pero, esta línea de razonamiento presenta una dificultad: todos los procesos que fueron suspendidos se encontraban previamente en el estado de Bloqueado en el momento de su suspensión. Claramente no sería bueno traer un proceso bloqueado de nuevo a memoria porque podría no encontrarse todavía listo para la ejecución. Se debe reconocer, sin embargo, que todos los procesos en
estado Suspendido estaban originalmente en estado Bloqueado, en espera de un evento en particular. Cuando el evento sucede, el proceso no está Bloqueado y está potencialmente disponible para
su ejecución.
De esta forma, necesitamos replantear este aspecto del diseño. Hay dos conceptos independientes
aquí: si un proceso está esperando a un evento (Bloqueado o no) y si un proceso está transferido de
memoria a disco (suspendido o no). Para describir estas combinaciones de 2 ¥ 2 necesitamos cuatro
estados:
• Listo. El proceso está en memoria principal disponible para su ejecución.
• Bloqueado. El proceso está en memoria principal y esperando un evento.
• Bloqueado/Suspendido. El proceso está en almacenamiento secundario y esperando un evento.
• Listo/Suspendido. El proceso está en almacenamiento secundario pero está disponible para su
ejecución tan pronto como sea cargado en memoria principal.
Antes de ir a ver un diagrama de transiciones de estado que incluya estos dos nuevos estados Suspendidos, se debe mencionar otro punto. La explicación ha asumido que no se utiliza memoria virtual
y que un proceso está completamente en memoria principal, o completamente fuera de la misma. Con
un esquema de memoria virtual, es posible ejecutar un proceso que está sólo parcialmente en memoria principal. Si se hace referencia a una dirección de proceso que no se encuentra en memoria principal, la porción correspondiente del proceso se trae a ella. El uso de la memoria principal podría parecer que elimina la necesidad del swapping explícito, porque cualquier dirección necesitada por un
proceso que la demande puede traerse o transferirse de disco a memoria por el propio hardware de
gestión de memoria del procesador. Sin embargo, como veremos en el Capítulo 8 el rendimiento de
los sistemas de memoria virtual pueden colapsarse si hay un número suficientemente grande de procesos activos, todos ellos están parcialmente en memoria principal. De esta forma, incluso con un sistema de memoria virtual, el sistema operativo necesita hacer swapping de los procesos de forma explícita y completa, de vez en cuando, con la intención de mejorar el rendimiento.
Veamos ahora, en la Figura 3.9b, el modelo de transición de estados que ha sido diseñado. (Las
líneas punteadas de la figura indican transiciones posibles pero no necesarias.) Las nuevas transiciones más importantes son las siguientes:
• Bloqueado Æ Bloqueado/Suspendido. Si no hay procesos listos, entonces al menos uno de
los procesos bloqueados se transfiere al disco para hacer espacio para otro proceso que no se
encuentra bloqueado. Esta transición puede realizarse incluso si hay procesos listos disponi-
12/5/05
16:19
Página 123
Descripción y control de procesos
Activación
Admisión
Nuevo
Listo
Ejecutando
Salida
123
Saliente
pe
ra
po
re
Es
Re
ac
Sucede
evento
tiv
ac
ió
n
ve
nt
o
Temporización
Suspensión
Suspendido
Bloqueado
(a) Con un único estado Suspendido
Nuevo
Suspe
nsión
n
Ad
sió
mi
mi
sió
n
Ad
Listo/
suspendido
Reactivación
Activación
Listo
Suspensión
Ejecutando
Salida
Saliente
Es
pe
ra
po
re
ve
nt
o
Temporización
Sucede
evento
Sucede
evento
03-Capitulo 3
Bloqueado/
suspendido
Reactivación
Bloqueado
Suspensión
(b) Con dos estados Suspendido
Figura 3.9.
Diagrama de transición de estados de procesos con estado suspendidos.
bles, si el sistema operativo determina que el proceso actualmente en ejecución o los procesos listos que desea ejecutar requieren más memoria principal para mantener un rendimiento
adecuado.
• Bloqueado/Suspendido Æ Listo/Suspendido. Un proceso en el estado Bloqueado/Suspendido se mueve al estado Listo/Suspendido cuando sucede un evento al que estaba esperando.
Nótese que esto requiere que la información de estado concerniente a un proceso suspendido
sea accesible para el sistema operativo.
• Listo/Suspendido Æ Listo. Cuando no hay más procesos listos en memoria principal, el sistema operativo necesitará traer uno para continuar la ejecución. Adicionalmente, puede darse
el caso de que un proceso en estado Listo/Suspendido tenga mayor prioridad que cualquiera de
los procesos en estado Listo. En este caso, el sistema operativo puede haberse diseñado para
determinar que es más importante traer un proceso de mayor prioridad que para minimizar el
efecto del swapping.
03-Capitulo 3
124
12/5/05
16:19
Página 124
Sistemas operativos. Aspectos internos y principios de diseño
• Listo Æ Listo/Suspendido. Normalmente, el sistema operativo preferirá suspender procesos
bloqueados que un proceso Listo, porque un proceso Listo se puede ejecutar en ese momento,
mientras que un proceso Bloqueado ocupa espacio de memoria y no se puede ejecutar. Sin embargo, puede ser necesario suspender un proceso Listo si con ello se consigue liberar un bloque suficientemente grande de memoria. También el sistema operativo puede decidir suspender un proceso Listo de baja prioridad antes que un proceso Bloqueado de alta prioridad, si se
cree que el proceso Bloqueado estará pronto listo.
Otras transiciones interesantes de considerar son las siguientes:
• Nuevo Æ Listo/Suspendido y Nuevo a Listo. Cuando se crea un proceso nuevo, puede añadirse a la cola de Listos o a la cola de Listos/Suspendidos. En cualquier caso, el sistema operativo puede crear un bloque de control de proceso (BCP) y reservar el espacio de direcciones del proceso. Puede ser preferible que el sistema operativo realice estas tareas internas
cuanto antes, de forma que pueda disponer de suficiente cantidad de procesos no bloqueados.
Sin embargo, con esta estrategia, puede ocurrir que no haya espacio suficiente en memoria
principal para el nuevo proceso; de ahí el uso de la transición (NuevoÆListo/Suspendido).
Por otro lado, deseamos hacer hincapié en que la filosofía de creación de procesos diferida,
haciéndolo cuanto más tarde, hace posible reducir la sobrecarga del sistema operativo y le
permite realizar las tareas de creación de procesos cuando el sistema está lleno de procesos
bloqueados.
• Bloqueado/Suspendido Æ Bloqueado. La incursión de esta transición puede parecer propia
de un diseño más bien pobre. Después de todo, si hay un proceso que no está listo para ejecutar y que no está en memoria principal, ¿qué sentido tiene traerlo? Pero considérese el siguiente escenario: un proceso termina, liberando alguna memoria principal. Hay un proceso en la
cola de Bloqueados/Suspendidos con mayor prioridad que todos los procesos en la cola de
Listos/Suspendidos y el sistema operativo tiene motivos para creer que el evento que lo bloquea va a ocurrir en breve. Bajo estas circunstancias, sería razonable traer el proceso Bloqueado a memoria por delante de los procesos Listos.
• Ejecutando Æ Listo/Suspendido. Normalmente, un proceso en ejecución se mueve a la
cola de Listos cuando su tiempo de uso del procesador finaliza. Sin embargo, si el sistema
operativo expulsa al proceso en ejecución porque un proceso de mayor prioridad en la cola
de Bloqueado/Suspendido acaba de desbloquearse, el sistema operativo puede mover al proceso en ejecución directamente a la cola de Listos/Suspendidos y liberar parte de la memoria principal.
• De cualquier estado Æ Saliente. Habitualmente, un proceso termina cuando está ejecutando,
bien porque ha completado su ejecución o debido a una condición de fallo. Sin embargo, en
algunos sistemas operativos un proceso puede terminarse por el proceso que lo creó o cuando
el proceso padre a su vez ha terminado. Si se permite esto, un proceso en cualquier estado
puede moverse al estado Saliente.
Otros usos para la suspensión de procesos. Hace poco, hemos definido el concepto de procesos suspendidos como el proceso que no se encuentra en memoria principal. Un proceso que no está
en memoria principal no está disponible de forma inmediata para su ejecución, independientemente
de si está a la espera o no de un evento.
Podemos analizar el concepto de procesos suspendidos, definiendo un proceso suspendido como
el que cumple las siguientes características:
1. El proceso no está inmediatamente disponible para su ejecución.
03-Capitulo 3
12/5/05
16:19
Página 125
Descripción y control de procesos
125
2. El proceso puede estar o no a la espera de un evento, si es así, la condición de bloqueo es independiente de la condición estar suspendido, y si sucede el evento que lo bloquea, eso no habilita al proceso para su ejecución inmediata.
3. El proceso fue puesto en estado suspendido por un agente: bien el proceso mismo, el proceso
padre o el sistema operativo, con el propósito de prevenir su ejecución.
4. El proceso no puede ser recuperado de este estado hasta que el agente explícitamente así lo
indique.
Tabla 3.3.
Razones para la suspensión de un proceso.
Swapping
El sistema operativo necesita liberar suficiente memoria principal para traer un proceso en estado Listo de ejecución.
Otras razones del sistema operativo
El sistema operativo puede suspender un proceso en segundo
plano o de utilidad o un proceso que se sospecha puede causar algún problema.
Solicitud interactiva del usuario
Un usuario puede desear suspender la ejecución de un programa con motivo de su depuración o porque está utilizando un
recurso.
Temporización
Un proceso puede ejecutarse periódicamente (por ejemplo, un
proceso monitor de estadísticas sobre el sistema) y puede suspenderse mientras espera el siguiente intervalo de ejecución.
Solicitud del proceso padre
Un proceso padre puede querer suspender la ejecución de un
descendiente para examinar o modificar dicho proceso suspendido, o para coordinar la actividad de varios procesos descendientes.
La Tabla 3.3 muestra una lista de las condiciones para la suspensión de un proceso. Una de las razones que hemos discutido anteriormente es proporcionar espacio de memoria para traer procesos en
estado Listos/Suspendidos o para incrementar el espacio de memoria disponible para los procesos
Listos. El sistema operativo puede tener otros motivos para suspender un proceso. Por ejemplo, se
puede utilizar un proceso de traza o de auditoría para monitorizar una actividad del sistema; el proceso puede recoger datos a nivel de utilización de varios recursos (procesador, memoria, canales) y la
tasa de progreso del uso de los procesos en el sistema. El sistema operativo, bajo el control del operador, puede activar y desactivar este proceso con determinada periodicidad. Si el sistema operativo detecta o sospecha que puede haber un problema, puede suspender procesos. Un ejemplo de esto es un
interbloqueo, que ya se discutirá en el Capítulo 6. Otro ejemplo, se detectan problemas en una línea
de comunicación, el operador solicita al sistema operativo que suspenda el proceso que utiliza dicha
línea hasta realizar algunas pruebas.
Otro tipo de razones están relacionadas con el uso interactivo del sistema. Por ejemplo, si los
usuarios sospechan de un programa con algún tipo de bug, se puede detener la ejecución de dicho
programa, examinando y modificando el programa y sus datos y reanudándolo de nuevo. O puede haber un proceso en segundo plano que recolecta trazas y estadísticas, el cual puede ser activado por
parte del usuario.
Algunas consideraciones de tiempo también pueden llevar a activar decisiones de swapping. Por
ejemplo, si un proceso se activa periódicamente pero está ocioso la mayor parte del tiempo puede ser
enviado a disco entre cada una de las ejecuciones que realiza. Un ejemplo es un programa que monitoriza la utilización del sistema y la actividad del usuario.
03-Capitulo 3
126
12/5/05
16:19
Página 126
Sistemas operativos. Aspectos internos y principios de diseño
Por último, un proceso padre puede suspender un proceso descendiente. Por ejemplo, el proceso
A lanza al proceso B para realizar una lectura sobre un fichero. Posteriormente, el proceso B encuentra un error en la lectura del fichero y se lo indica al proceso A. El proceso A suspende al proceso B e
investiga la causa.
En todos estos casos, la activación de un proceso Suspendido se solicita por medio del agente que
inicialmente puso al proceso en suspensión.
3.3. DESCRIPCIÓN DE PROCESOS
El sistema operativo controla los eventos dentro del computador, planifica y activa los procesos para
su ejecución por el procesador, reserva recursos para los mismos y responde a las solicitudes de servicios básicos de los procesos de usuario. Fundamentalmente, se piensa en el sistema operativo como
en la entidad que gestiona el uso de recursos del sistema por parte de los procesos.
Este concepto se ilustra en la Figura 3.10. En un entorno multiprogramado, hay numerosos
procesos (P1,... Pn) creados y residentes en memoria virtual. Cada proceso, durante el transcurso
de su ejecución, necesita acceder a ciertos recursos del sistema, incluido el procesador, los dispositivos de E/S y la memoria principal. En la figura, el proceso P1 está ejecutando; al menos parte del
proceso está en memoria principal, y controla dos dispositivos de E/S. El proceso P2 está también
totalmente en memoria principal pero está bloqueado esperando a disponer de un dispositivo de
E/S que está asignado a P1. El proceso Pn se encuentra transferido a disco (swapping) y está por
tanto suspendido.
Exploraremos los detalles de la gestión de estos recursos por parte del sistema operativo en los
próximos capítulos. Aquí nos interesa una cuestión más fundamental: ¿qué información necesita el
sistema operativo para controlar los procesos y gestionar los recursos de estos?
ESTRUCTURAS DE CONTROL DEL SISTEMA OPERATIVO
Si el sistema operativo se encarga de la gestión de procesos y recursos, debe disponer de información sobre el estado actual de cada proceso y cada recurso. El mecanismo universal para proporcionar esta información es el siguiente: el sistema operativo construye y mantiene tablas de
información sobre cada entidad que gestiona. Se indica una idea general del alcance de esta tarea
en la Figura 3.11, que muestra cuatro diferentes tipos de tablas mantenidas por el sistema operativo: memoria, E/S, ficheros y procesos. A pesar de que los detalles difieren de un sistema operativo a otro, fundamentalmente, todos los sistemas operativos mantienen información de estas cuatro categorías.
P1
P2
Pn
Memoria
virtual
Recursos del
ordenador
Procesador
Figura 3.10.
E/S
E/S
E/S
Memoria
principal
Procesos y recursos (reserva de recursos en una instantánea del sistema).
03-Capitulo 3
12/5/05
16:19
Página 127
Descripción y control de procesos
127
Las tablas de memoria se usan para mantener un registro tanto de la memoria principal (real)
como de la secundaria (virtual). Parte de la memoria principal está reservada para el uso del sistema
operativo; el resto está disponible para el uso de los procesos. Los procesos se mantienen en memoria
secundaria utilizando algún tipo de memoria virtual o técnicas de swapping. Las tablas de memoria
deben incluir la siguiente información:
• Las reservas de memoria principal por parte de los procesos.
• Las reservas de memoria secundaria por parte de los procesos.
• Todos los atributos de protección que restringe el uso de la memoria principal y virtual, de forma que los procesos puedan acceder a ciertas áreas de memoria compartida.
• La información necesaria para manejar la memoria virtual.
Veremos en detalle las estructuras de información para la gestión de memoria en la Parte Tres.
El sistema operativo debe utilizar las tablas de E/S para gestionar los dispositivos de E/S y los
canales del computador. Pero, en un instante determinado, un dispositivo E/S puede estar disponible o asignado a un proceso en particular. Si la operación de E/S se está realizando, el sistema operativo necesita conocer el estado de la operación y la dirección de memoria principal del área usada como fuente o destino de la transferencia de E/S. La gestión de E/S se examina en detalle en el
Capítulo 11.
El sistema operativo también puede mantener las tablas de ficheros. Estas tablas proporcionan
información sobre la existencia de ficheros, su posición en almacenamiento secundario, su estado ac-
Tablas de memoria
Proceso
1
Memoria
Dispositivos
Imagen del
proceso
Tablas de E/S
Ficheros
Procesos
Tablas de ficheros
Tabla primaria de procesos
Proceso 1
Proceso 2
Proceso 3
Imagen del
proceso
Proceso
n
Proceso n
Figura 3.11.
Estructura general de las tablas de control del sistema operativo.
03-Capitulo 3
128
12/5/05
16:19
Página 128
Sistemas operativos. Aspectos internos y principios de diseño
tual, y otros atributos. La mayoría de, o prácticamente toda, esta información se puede gestionar por
el sistema de ficheros, en cuyo caso el sistema operativo tiene poco o ningún conocimiento sobre los
ficheros. En otros sistemas operativos, la gran mayoría del detalle de la gestión de ficheros sí que
gestiona en el propio sistema operativo. Este aspecto se explorará en el Capítulo 12.
En último lugar, el sistema operativo debe mantener tablas de procesos para gestionar los procesos. La parte restante de esta sección se encuentra dedicada a examinar los requisitos que tienen las
tablas de procesos. Antes de proceder con esta explicación, vamos a realizar dos puntualizaciones importantes. Primero, a pesar de que la Figura 3.11 muestra cuatro tipos diferentes de tablas debe quedar claro que estas tablas se encuentran entrelazadas y referenciadas entre sí de alguna manera. Memoria, E/S, y ficheros se gestionan por parte de los procesos, de forma que debe haber algunas
referencias en estos recursos, directa o indirectamente, desde las tablas de procesos. Los ficheros indicados en las tablas de ficheros son accesibles mediante dispositivos E/S y estarán, en algún caso,
residentes en memoria virtual o principal. Las tablas son también accesibles para el sistema operativo, y además están controladas por la gestión de memoria.
Segundo, ¿cómo puede el sistema operativo crear las tablas iniciales? Ciertamente, el sistema
operativo debe tener alguna información sobre el entorno básico, tal como: cuánta memoria física
existe, cuáles son los dispositivos de E/S y cuáles son sus identificadores, por ejemplo. Esto es una
cuestión de configuración. Esto es, cuando el sistema operativo se inicializa, debe tener acceso a algunos datos de configuración que definen el entorno básico y estos datos se deben crear fuera del sistema operativo, con asistencia humana o por medio de algún software de autoconfiguración.
ESTRUCTURAS DE CONTROL DE PROCESO
Se considerará qué información debe conocer el sistema operativo si quiere manejar y controlar los procesos. Primero, debe conocer dónde están localizados los procesos, y segundo, debe conocer los atributos de los procesos que quiere gestionar (por ejemplo, identificador de proceso y estado del mismo).
Localización de los procesos. Antes de que podamos tratar la cuestión de dónde se encuentran
los procesos o cuáles son sus atributos, debemos tratar una cuestión más fundamental: ¿qué es la representación física de un proceso? Como mínimo, un proceso debe incluir un programa o un conjunto
de programas a ejecutar. Asociados con estos programas existen unas posiciones de memoria para los
datos de variables locales y globales y de cualquier constante definida. Así, un proceso debe consistir
en una cantidad suficiente de memoria para almacenar el programa y datos del mismo. Adicionalmente, la ejecución típica de un programa incluye una pila (véase Apéndice 1B) que se utiliza para
registrar las llamadas a procedimientos y los parámetros pasados entre dichos procedimientos. Por último, cada proceso está asociado a un número de atributos que son utilizados por el sistema operativo
para controlar el proceso. Normalmente, el conjunto de estos atributos se denomina bloque de control del proceso (BCP)6. Nos podemos referir al conjunto de programa, datos, pila, y atributos, como
la imagen del proceso (Tabla 3.4).
La posición de la imagen del proceso dependerá del esquema de gestión de memoria que se utilice. En el caso más simple, la imagen del proceso se mantiene como un bloque de memoria contiguo,
o continuo. Este bloque se mantiene en memoria secundaria, habitualmente disco. Para que el sistema
operativo pueda gestionar el proceso, al menos una pequeña porción de su imagen se debe mantener
6
Otros nombres habituales usados para esta estructura de datos son bloque de control de tarea, descriptor del proceso, y descriptor de tarea.
03-Capitulo 3
12/5/05
16:19
Página 129
Descripción y control de procesos
129
en memoria principal. Para ejecutar el proceso, la imagen del proceso completa se debe cargar en memoria principal o al menos en memoria virtual. Asimismo, el sistema operativo necesita conocer la
posición en disco de cada proceso y, para cada proceso que se encuentre en memoria principal, su posición en dicha memoria. Se vio una ligera modificación un poco más compleja de este esquema con
el sistema operativo CTSS, en el Capítulo 2. Con CTSS, cuando un proceso se transfiere a disco
(swapping) parte de la imagen del proceso permanece en memoria principal. De esta forma, el sistema operativo debe mantener en registro qué partes de la imagen del proceso se encuentran todavía en
memoria principal.
Tabla 3.4.
Elementos típicos de la imagen de un proceso.
Datos del usuario
La parte modificable del espacio de usuario. Puede incluir datos de
programa, área de pila de usuario, y programas que puedan ser
modificados.
Programa de usuario
El programa a ejecutar
Pila de sistema
Cada proceso tiene una o más pilas de sistema (LIFO) asociadas a él.
Una pila se utiliza para almacenar los parámetros y las direcciones
de retorno de los procedimientos y llamadas a sistema.
Bloque de control de proceso
Datos necesarios para que el sistema operativo pueda controlar los
procesos (véase tabla 3.5).
Los sistemas operativos modernos suponen la existencia de un hardware de paginación que permite el uso de la memoria física no contigua, para dar soporte a procesos parcialmente residentes en
la memoria principal. En esos casos, una parte de la imagen del proceso se puede encontrar en dicha
memoria principal, con las restantes en memoria secundaria7. De esta forma, las tablas mantenidas
por sistema operativo deben mostrar la localización de cada página de la imagen del proceso.
La Figura 3.11 muestra la estructura de la información de localización de la siguiente forma. Hay
una tabla primaria de procesos con una entrada por cada proceso. Cada entrada contiene, al menos,
un puntero a la imagen del proceso. Si la imagen del proceso contiene múltiples bloques, esta información se mantiene directamente en la tabla principal del proceso o bien está disponible por referencias cruzadas entre las entradas de las tablas de memoria. Por supuesto, esta es una representación genérica; un sistema operativo en particular puede tener su propia forma de organizar esta información
de localización.
Atributos de proceso. Cualquier sistema operativo multiprogramado de la actualidad requiere
una gran cantidad de información para manejar cada proceso. Como quedó explicado, esta información se puede considerar que reside en el bloque de control del proceso (BCP). Los diferentes
sistemas organizarán esta información de formas diferentes. Al final de este capítulo y en el próximo se muestran varios ejemplos. Por ahora, exploraremos simplemente el tipo de información que
el sistema operativo puede utilizar, sin considerar cualquier detalle de cómo esta información está
organizada.
7
Esta breve explicación pasa por encima algunos detalles. En particular, en un sistema que utilice memoria virtual, toda imagen de un proceso activo se encuentra siempre en memoria secundaria. Sólo una parte de la imagen se carga en memoria principal,
ésta se copia en lugar de moverse. De esta forma, la memoria secundaria mantiene una copia de todos los segmentos y/o páginas. Sin
embargo, si la parte de imagen del proceso en memoria principal se ve modificada, la copia en memoria secundaria estará desactualizada hasta que la parte residente en memoria principal se copie de nuevo al disco.
03-Capitulo 3
130
12/5/05
16:19
Página 130
Sistemas operativos. Aspectos internos y principios de diseño
La Tabla 3.5 muestra la lista de los tipos de categorías de información que el sistema operativo
requiere para cada proceso. Resulta sorprendente la cantidad de información necesaria para esta gestión. Cuando se vaya apreciando con mayor exactitud las responsabilidades del sistema operativo,
esta lista parecerá más razonable.
Podemos agrupar la información del bloque de control del proceso en tres categorías generales:
• Identificación del proceso
• Información de estado del procesador
• Información de control del proceso
Tabla 3.5.
Elementos típicos de un bloque de control del proceso (BCP).
Identificación del proceso
Identificadores
Identificadores numéricos que se pueden guardar dentro del bloque de control del proceso:
• identificadores del proceso
• identificador del proceso que creó a este proceso (proceso padre)
• identificador del usuario
Información de estado del procesador
Registros visibles por el usuario
Un registro visible por el usuario es aquel al que se puede hacer referencia por medio del lenguaje
máquina que ejecuta el procesador cuando está en modo usuario. Normalmente, existen de 8 a 32 de
estos registros, aunque determinadas implementaciones RISC tienen más de 100.
Registros de estado y control
Hay una gran variedad de registros del procesador que se utilizan para el control de operaciones. Estos incluyen:
• Contador de programa: contiene la dirección de la siguiente instrucción a ejecutar.
• Códigos de condición: resultan de la operación lógica o aritmética más reciente (por ejemplo, signo, cero, acarreo, igual, desbordamiento).
• Información de estado: incluyen los flags de interrupciones habilitadas/deshabilitadas, modo ejecución.
Puntero de pila
Cada proceso tiene una o más pilas de sistema (LIFO) asociadas a él. Una pila se utiliza para almacenar los parámetros y las direcciones de retorno de los procedimientos y llamadas a sistema. Un puntero de pila apunta a la parte más alta de la pila.
Información de control de proceso
Información de estado y de planificación
Esta es la información que el sistema operativo necesita para analizar las funciones de planificación.
Elementos típicos de esta información son:
03-Capitulo 3
12/5/05
16:19
Página 131
Descripción y control de procesos
131
• Estado del proceso: indica si está listo o no el proceso para ser planificado para su ejecución (por
ejemplo, Ejecutando, Listo, Esperando, Detenido).
• Prioridad: uno o más campos que se pueden utilizar para escribir la prioridad de planificación del
proceso. En algunos sistemas, se necesitan múltiples valores (por ejemplo, por-defecto, actual,
mayor-disponible).
• Información relativa a planificación: esto dependerá del algoritmo de planificación utilizado. Por
ejemplo, la cantidad de tiempo que el proceso estaba esperando y la cantidad de tiempo que el
proceso ha ejecutado la última vez que estuvo corriendo.
• Evento: identificar el evento al cual el proceso está esperando para continuar su ejecución.
Estructuración de datos
Un proceso puede estar enlazado con otro proceso en una cola, anillo o con cualquier otra estructura.
Por ejemplo, todos los procesos en estado de espera por un nivel de prioridad en particular pueden
estar enlazados en una cola. Un proceso puede mostrar una relación padre-hijo (creador-creado) con
otro proceso. El bloque de control del proceso puede contener punteros a otros procesos para dar soporte a estas estructuras.
Comunicación entre procesos
Se pueden asociar diferentes flags, señales, y mensajes relativos a la comunicación entre dos procesos independientes. Alguna o toda esta información se puede mantener en el bloque de control de
proceso (BCP).
Privilegios de proceso
Los procesos adquieren privilegios de acuerdo con la memoria a la que van a acceder y los tipos de
instrucciones que se pueden ejecutar. Adicionalmente, los privilegios se pueden utilizar para usar utilidades de sistema o servicios.
Gestión de memoria
Esta sección puede incluir punteros a tablas de segmentos y/o páginas que describen la memoria virtual asignada a este proceso.
Propia de recursos y utilización
Se deben indicar los recursos controlados por el proceso, por ejemplo, ficheros abiertos. También se
puede incluir un histórico de utilización del procesador o de otros recursos; esta información puede
ser necesaria para el planificador.
Con respecto al identificador de proceso, en prácticamente todos los sistemas operativos, a cada
proceso se le asocia un identificador numérico único, que puede ser simplemente un índice en la tabla
de procesos principal (Figura 3.11); de otra forma, debe existir una traducción que permita al sistema
operativo localizar las tablas apropiadas basándose en dicho identificador de proceso. Este identificador es útil para diferentes cuestiones. Muchas de las otras tablas controladas por el sistema operativo
incluyen información que referencia a otro proceso por medio del identificador de proceso. Por ejemplo, se puedan organizar las tablas de memoria para proporcionar un mapa de la memoria principal
que indique a qué proceso se tiene asignada cada región. Pueden aparecer referencias similares en las
tablas de E/S o de ficheros. Cuando un proceso se comunica con otro proceso, el identificador de proceso informa al sistema operativo del destino de la comunicación. Cuando los procesos pueden crear
otros procesos, los identificadores indican el proceso padre y los descendientes en cada caso.
Junto con estos identificadores de proceso, a un proceso se le puede asignar un identificador de
usuario que indica qué usuario es responsable del trabajo.
03-Capitulo 3
132
12/5/05
16:19
Página 132
Sistemas operativos. Aspectos internos y principios de diseño
La información de estado de proceso indica los contenidos de los registros del procesador.
Cuando un proceso está ejecutando, esta información está, por supuesto, en los registros. Cuando un
proceso se interrumpe, toda la información de los registros debe salvaguardarse de forma que se pueda restaurar cuando el proceso continúe con su ejecución. La naturaleza y el número de estos registros depende del diseño del procesador. Normalmente, el conjunto de registros incluye registros visibles por usuario, registros de control y de estado, y punteros de pila. Todos estos se describieron en el
Capítulo 1.
Se debe notar que, el diseño de todos los procesadores incluye un registro o conjuntos de registros, habitualmente conocidos como palabras de estado de programa (program status word, PSW)
que contiene información de estado. La PSW típicamente contiene códigos de condición así como
otra información de estado. Como ejemplo de una palabra de estado de un procesador se tiene lo que
los procesadores Pentium definen como registros EFLAFS (véase Figura 3.12 y Tabla 3.6). Esta estructura es utilizada por los sistemas operativos (incluyendo UNIX y Windows) que se ejecuten sobre
procesadores de tipo Pentium.
La tercera categoría principal de información del bloque de control de proceso se denomina, a
falta de otro nombre mejor, información de control de proceso. Esta información adicional la necesita el sistema operativo para controlar y coordinar varios procesos activos. La última parte de la Tabla 3.5 indica el ámbito de esta información. Según examinemos los detalles de funcionamiento del
sistema operativo en los sucesivos capítulos, la necesidad de algunos de los elementos de esta lista
parecerá más clara.
El contenido de esta figura no debería modificarse. La traducción de los registros de control de una
arquitectura en concreto no tiene mucho sentido, puesto que las siglas con las que se denotan están en
inglés. El significado de dichas siglas y la traducción del término se encuentran en la siguiente tabla.
La Figura 3.13 sugiere la estructura de las imágenes de los procesos en memoria virtual. Cada
imagen de proceso consiste en un bloque de control de proceso, una pila de usuario, un espacio de direcciones privadas del proceso, y cualquier otro espacio de direcciones que el proceso comparta con
otros procesos. En la figura, cada imagen de proceso aparece como un rango de direcciones contiguo,
pero en una implementación real, este no tiene por qué ser el caso; dependerá del esquema de gestión
de memoria y de la forma en la cual se encuentren organizadas las estructuras de control por parte del
sistema operativo.
31
21
I
D
16 15
V V
I I
P F
A V R
CM F
ID (Indicador de identificación)
VIP (Interrupción virtual pendiente)
VIF (Indicador de interrupción virtual)
AC (Comprobación de alineamiento)
VM (Modo virtual 8086)
RF (Indicación de reanudación)
NT (Indicador de tarea anidada)
IOPL (Nivel de privilegio de E/S)
OF (Indicador de desbordamiento)
Figura 3.12.
0
N IO O D I T S Z
T PL F F F F F F
A
F
P
F
C
F
DF (Indicador de dirección)
IF (Indicador de interrupciones habilitadas)
TF (Indicador de trap)
SF (Indicador de signo)
ZF (Indicador de cero)
AF Indicador auxiliar de acarreo)
PF (Indicador de paridad)
CF (Indicador de acarreo)
Registro EFLAGS de un Pentium II.
03-Capitulo 3
12/5/05
16:19
Página 133
Descripción y control de procesos
Tabla 3.6.
133
Bits del registro EFLAGS de un Pentium.
Bits de control
AC (Alignment check)
Fija si una palabra (o una doble-palabra) se encuentra direccionada en la frontera entre palabras (o dobles-palabras).
ID (Identification flag)
Si este bit puede ser puesto a 1 y a 0, este procesador soporta la
instrucción CPUID. Esta instrucción proporciona información sobre el vendedor, familia, y modelo.
RF (Resume flag)
Permite al programador desactivar las extensiones de depuración
de forma que la instrucción pueda volverse a ejecutar después de
una excepción de depuración sin causar inmediatamente después
otra excepción de depuración.
IOPL (I/O privilege level)
Cuando está activado, hace que el procesador genere una excepción para todos los accesos a dispositivo de E/S durante una operación en modo protegido.
DF (Direction flag)
Determina cuando una instrucción de procesamiento de cadenas
de caracteres incrementa o decrementa los semi-registros de 16
bits SE y DI (para operaciones de 16 bits) o los registros de 32 bits
ESE y EDI (para operaciones de 32 bits).
IF (Interruption enable flag)
Cuando está puesto a 1, el procesador reconocerá interrupciones
externas.
TF (Trap flag)
Cuando está puesto a 1, causa una interrupción después de la ejecución de cada instrucción. Su uso está dirigido a la depuración
de código.
Bits de modos de operación
NT (Nested task flag)
Indica que la tarea actual está anidada dentro de otra tarea en
modo de operación protegido.
VM (Virtual 8086 mode)
Permite al programador activar o desactivar el modo virtual 8086,
que determina si el procesador funciona como si fuese una máquina 8086.
VIP (Virtual interrupt pending)
Usado en el modo virtual 8086 para indicar que una o más interrupciones están a la espera de servicio.
VIF (Virtual interruption flag)
Usado en modo virtual 8086 en lugar de IF.
Códigos de condición
AF (Auxiliar carry flag)
Representa el acarreo o resto entre medios bytes de una operación aritmética o lógica de 8 bits usando el registro AL.
CF (Carry flag)
Indica el acarreo o el resto por medio del bit situado más a la izquierda después de una operación aritmética. También se modificaron por algunas operaciones de desplazamiento o rotación.
OF (Overflow flag)
Indica un desbordamiento (overflow) aritmético después de una
suma o resta.
PF (Parity flag)
Paridad del resultado de una operación aritmética o lógica. 1 indica paridad par; 0 indica paridad impar.
SF (Sign flag)
Indica el signo del resultado de una operación aritmética o lógica.
ZF (Zero flag)
Indica que el resultado de una operación aritmética o lógica es 0.
03-Capitulo 3
134
12/5/05
16:19
Página 134
Sistemas operativos. Aspectos internos y principios de diseño
Identificador del
proceso
Identificador del
proceso
Identificador del
proceso
Información de estado
del procesador
Información de estado
del procesador
Información de estado
del procesador
Información de
control del proceso
Información de
control del proceso
Información de
control del proceso
Pila de usuario
Pila de usuario
Pila de usuario
Espacio privado
de direcciones
de usuario
(programas y datos)
Espacio privado
de direcciones
de usuario
(programas y datos)
Espacio privado
de direcciones
de usuario
(programas y datos)
Espacio compartido
de direcciones
Espacio compartido
de direcciones
Espacio compartido
de direcciones
Proceso 1
Proceso 2
Proceso n
Figura 3.13.
Bloque de
control del
proceso
Procesos de usuario en memoria virtual.
Como indica la Tabla 3.5, un bloque de control de proceso puede contener información estructural, incluyendo punteros que permiten enlazar bloques de control de proceso entre sí. De esta forma,
las colas que se describieron en la sección anterior pueden implementarse como listas enlazadas de
bloques de control de proceso. Por ejemplo, la estructura de colas de la Figura 3.8a se puede implementar tal y como sugiere la Figura 3.14.
El papel del bloque de control de proceso. El bloque de control de proceso es la más importante de las estructuras de datos del sistema operativo. Cada bloque de control de proceso contiene toda
la información sobre un proceso que necesita el sistema operativo. Los bloques se leen y/o se modifican por la práctica totalidad de los módulos del sistema operativo, incluyendo aquellos relacionados
con la planificación, la reserva de recursos, el procesamiento entre regiones, y el análisis y monitorización del rendimiento. Se puede decir que el conjunto de los bloques de control de proceso definen
el estado del sistema operativo.
Esto muestra un aspecto importante en el diseño. Un gran número de rutinas dentro del sistema operativo necesitarán acceder a información de los bloques de control de proceso. Proporcionar acceso directo a estas tablas no es difícil. Cada proceso lleva asociado un único identificador,
que puede utilizarse para indexar dentro de una tabla de punteros a bloques de control de proceso.
La dificultad no reside en el acceso sino en la protección. Dos posibles problemas se pueden presentar son:
• Un fallo en una simple rutina, como un manejador de interrupción, puede dañar los bloques de
control de proceso, que puede destruir la capacidad del sistema para manejar los procesos
afectados.
• Un cambio en la estructura o en la semántica de los bloques de control de procesos puede
afectar a un gran número de módulos del sistema operativo.
03-Capitulo 3
12/5/05
16:19
Página 135
Descripción y control de procesos
135
Bloque de control
de proceso
Ejecutando
Listo
Bloqueado
Figura 3.14.
Estructuras de listas de procesos.
Estos problemas se pueden ser tratar obligando a que todas rutinas del sistema operativo pasen a
través de una rutina de manejador, cuyo único trabajo es proteger a los bloques de control de proceso, y
que es la única que realiza el arbitraje en las operaciones de lectura y escritura de dichos bloques. Los
factores a equilibrar en el uso de está rutina son, por un lado los aspectos de rendimiento, y por el otro,
el grado en el cual el resto del software del sistema puede verificarse para determinar su corrección.
3.4. CONTROL DE PROCESOS
MODOS DE EJECUCIÓN
Antes de continuar con nuestra explicación sobre cómo el sistema operativo gestiona los procesos, necesitamos distinguir entre los modos de ejecución del procesador, normalmente asociados con
el sistema operativo y los asociados con los programas de usuario. Muchos procesadores proporcionan al menos dos modos de ejecución. Ciertas instrucciones se pueden ejecutar en modos privilegiados únicamente. Éstas incluirían lectura y modificación de los registros de control, por ejemplo la palabra de estado de programa; instrucciones de E/S primitivas; e instrucciones relacionadas con la
gestión de memoria. Adicionalmente, ciertas regiones de memoria sólo se pueden acceder en los modos más privilegiados.
El modo menos privilegiado a menudo se denomina modo usuario, porque los programas de
usuario típicamente se ejecutan en este modo. El modo más privilegiado se denomina modo sistema,
modo control o modo núcleo. Este último término se refiere al núcleo del sistema operativo, que es
la parte del sistema operativo que engloba las funciones más importantes del sistema. La Tabla 3.7
lista las funciones que normalmente se encuentran dentro del núcleo del sistema operativo.
03-Capitulo 3
136
12/5/05
16:19
Página 136
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 3.7.
Funciones típicas de un núcleo de sistema operativo.
Gestión de procesos
• Creación y terminación de procesos
• Planificación y activación de procesos
• Intercambio de procesos
• Sincronización de procesos y soporte para comunicación entre procesos
• Gestión de los bloques de control de proceso
Gestión memoria
• Reserva de espacio direcciones para los procesos
•
Swapping
• Gestión de páginas y segmentos
Gestión E/S
• Gestión de buffers
• Reserva de canales de E/S y de dispositivos para los procesos
Funciones de soporte
• Gestión de interrupciones
• Auditoría
• Monitorización
El motivo por el cual se usan los otros modos es claro. Se necesita proteger al sistema operativo y
a las tablas clave del sistema, por ejemplo los bloques de control de proceso, de la interferencia con
programas de usuario. En modo núcleo, el software tiene control completo del procesador y de sus
instrucciones, registros, y memoria. Este nivel de control no es necesario y por seguridad no es recomendable para los programas de usuario.
Aparecen dos cuestiones: ¿cómo conoce el procesador en que modo está ejecutando y cómo este
modo puede modificarse? En lo referente la primera cuestión, existe típicamente un bit en la palabra
de estado de programa (PSW) que indica el modo de ejecución. Este bit se cambia como respuesta a
determinados eventos. Habitualmente, cuando un usuario realiza una llamada a un servicio del sistema operativo o cuando una interrupción dispara la ejecución de una rutina del sistema operativo,
este modo de ejecución se cambia a modo núcleo y; tras la finalización del servicio, el modo se fija
de nuevo a modo usuario. Por ejemplo, si consideramos el procesador Intel Itanium, que implementa la arquitectura IA-64 de 64 bits, el procesador tiene un registro de estado (psr) que incluye un
campo de los 2 bits llamado cpl (current privilege level). El Nivel 0 es en el nivel con más privilegios mientras que el Nivel 3 es el nivel con menos privilegios. La mayoría de los sistemas operativos, como Linux, utilizar el Nivel 0 para el núcleo y uno de los otros niveles como nivel de usuario.
Cuando llega una interrupción, el procesador limpia la mayor parte de los bits en psr, incluyendo el
campo cpl. Esto automáticamente pone en 0 el cpl. Al final de la rutina de manejo de interrupción,
la última instrucción que se ejecuta es irt (interrupt return). Esta instrucción hace que el procesador
restaure el psr del programa interrumpido, que restaura a su vez el nivel de privilegios de dicho programa. Una secuencia similar ocurre cuando la aplicación solicita una llamada al sistema. Para los
Itanium, una aplicación solicita la llamada sistema indicando un identificador de llamada de sistema
03-Capitulo 3
12/5/05
16:19
Página 137
Descripción y control de procesos
137
y los argumentos de dicha llamada en unas áreas definidas y posteriormente ejecutando una instrucción especial que se puede invocar en modo usuario y que tiene como objeto transferir el control al
núcleo.
CREACIÓN DE PROCESOS
En la Sección 3.1 hemos comentado los eventos que llevará creación de uno proceso. Una vez vistas
las estructuras de datos asociadas a un proceso, ahora posiciones está en posición de describir brevemente los pasos que llevan a la verdadera creación de un proceso.
Una vez que el sistema operativo decide, por cualquier motivo (Tabla 3.1), crear un proceso procederá de la siguiente manera:
1. Asignar un identificador de proceso único al proceso. En este instante, se añade una nueva
entrada a la tabla primaria de procesos, que contiene una entrada por proceso.
2. Reservar espacio para proceso. Esto incluye todos los elementos de la imagen del proceso.
Para ello, el sistema operativo debe conocer cuánta memoria se requiere para el espacio de direcciones privado (programas y datos) y para la pila de usuario. Estos valores se pueden asignar por defecto basándonos en el tipo de proceso, o pueden fijarse en base a la solicitud de
creación del trabajo remitido por el usuario. Si un proceso es creado por otro proceso, el proceso padre puede pasar los parámetros requeridos por el sistema operativo como parte de la
solicitud de la creación de proceso. Si existe una parte del espacio direcciones compartido por
este nuevo proceso, se fijan los enlaces apropiados. Por último, se debe reservar el espacio
para el bloque de control de proceso (BCP).
3. Inicialización del bloque de control de proceso. La parte de identificación de proceso del
BCP contiene el identificador del proceso así como otros posibles identificadores, tal como el
indicador del proceso padre. En la información de estado de proceso del BCP, habitualmente
se inicializa con la mayoría de entradas a 0, excepto el contador de programa (fijado en el punto entrada del programa) y los punteros de pila de sistema (fijados para definir los límites de la
pila del proceso). La parte de información de control de procesos se inicializa en base a los valores por omisión, considerando también los atributos que han sido solicitados para este proceso. Por ejemplo, el estado del proceso se puede inicializar normalmente a Listo o Listo/Suspendido. La prioridad se puede fijar, por defecto, a la prioridad más baja, a menos que una
solicitud explicita la eleve a una prioridad mayor. Inicialmente, el proceso no debe poseer ningún recurso (dispositivos de E/S, ficheros) a menos que exista una indicación explícita de ello
o que haya sido heredados del padre.
4. Establecer los enlaces apropiados. Por ejemplo, si el sistema operativo mantiene cada cola
del planificador como una lista enlazada, el nuevo proceso debe situarse en la cola de Listos o
en la cola de Listos/Suspendidos.
5. Creación o expansión de otras estructuras de datos. Por ejemplo, el sistema operativo puede mantener un registro de auditoría por cada proceso que se puede utilizar posteriormente a
efectos de facturación y/o de análisis de rendimiento del sistema.
CAMBIO DE PROCESO
A primera vista, la operación de cambio de proceso puede parecer sencilla. En algunos casos, un proceso en ejecución se interrumpe para que el sistema operativo asigne a otro proceso el estado de Ejecutando y de esta forma establecer el turno entre los procesos. Sin embargo, es necesario tener en
03-Capitulo 3
138
12/5/05
16:19
Página 138
Sistemas operativos. Aspectos internos y principios de diseño
cuenta algunas consideraciones de diseño. Primero, ¿qué evento dispara los cambios de proceso?
Otra consideración que se debe tener en cuenta es la distinción entre cambio de proceso (process
switching) y cambio de modo (mode switching). Por último, ¿qué debe hacer el sistema operativo con
las diferentes estructuras que gestiona para proporcionar un cambio de proceso?
Cuándo se realiza el cambio de proceso. Un cambio de proceso puede ocurrir en cualquier instante en el que el sistema operativo obtiene el control sobre el proceso actualmente en ejecución. La
Tabla 3.8 indica los posibles momentos en los que el sistema operativo puede tomar dicho control.
Primero, consideremos las interrupciones del sistema. Realmente, podemos distinguir, como
hacen muchos sistemas, dos tipos diferentes de interrupciones de sistema, unas simplemente denominadas interrupciones, y las otras denominadas traps. Las primeras se producen por causa de algún tipo de evento que es externo e independiente al proceso actualmente en ejecución, por ejemplo la finalización de la operación de E/S. Las otras están asociadas a una condición de error o
excepción generada dentro del proceso que está ejecutando, como un intento de acceso no permitido a un fichero. Dentro de una interrupción ordinaria, el control se transfiere inicialmente al manejador de interrupción, que realiza determinadas tareas internas y que posteriormente salta a una
rutina del sistema operativo, encargada de cada uno de los tipos de interrupciones en particular. Algunos ejemplos son:
• Interrupción de reloj. El sistema operativo determinar si el proceso en ejecución ha excedido
o no la unidad máxima de tiempo de ejecución, denominada rodaja de tiempo (time slice).
Esto es, una rodaja de tiempo es la máxima cantidad de tiempo que un proceso puede ejecutar
antes de ser interrumpido. En dicho caso, este proceso se puede pasar al estado de Listo y se
puede activar otro proceso.
• Interrupción de E/S. El sistema operativo determina qué acción de E/S ha ocurrido. Si la acción de E/S constituye un evento por el cual están esperando uno o más procesos, el sistema
operativo mueve todos los procesos correspondientes al estado de Listos (y los procesos en estado Bloqueado/Suspendido al estado Listo/Suspendido). El sistema operativo puede decidir si
reanuda la ejecución del proceso actualmente en estado Ejecutando o si lo expulsa para proceder con la ejecución de un proceso Listo de mayor prioridad.
Tabla 3.8.
Mecanismo
Mecanismos para la interrupción de la ejecución de un proceso.
Causa
Uso
Interrupción
Externa a la ejecución del proceso
actualmente en ejecución.
Reacción ante un evento externo
asíncrono.
Trap
Asociada a la ejecución
de la instrucción actual.
Manejo de una condición de error o
de excepción.
Llamada al sistema
Solicitud explícita.
Llamada a una función del sistema
operativo.
• Fallo de memoria. El procesador se encuentra con una referencia a una dirección de memoria
virtual, a una palabra que no se encuentra en memoria principal. El sistema operativo debe traer el bloque (página o segmento) que contiene la referencia desde memoria secundaria a memoria principal. Después de que se solicita la operación de E/S para traer el bloque a memoria,
el proceso que causó el fallo de memoria se pasa al estado de Bloqueado; el sistema operativo
realiza un cambio de proceso y pone a ejecutar a otro proceso. Después de que el bloque de
memoria solicitado se haya traído, el proceso pasará al estado Listo.
03-Capitulo 3
12/5/05
16:19
Página 139
Descripción y control de procesos
139
Con un trap, el sistema operativo conoce si una condición de error o de excepción es irreversible.
Si es así, el proceso en ejecución se pone en el estado Saliente y se hace un cambio de proceso. De no
ser así, el sistema operativo actuará dependiendo de la naturaleza del error y su propio diseño. Se
puede intentar un procedimiento de recuperación o simplemente la notificación al usuario, pudiendo
implicar tanto un cambio de proceso como la continuación de la ejecución del proceso actual.
Por último, el sistema operativo se puede activar por medio de una llamada al sistema procedente del programa en ejecución. Por ejemplo, si se está ejecutando un proceso y se ejecuta una operación que implica una llamada de E/S, como abrir un archivo. Esta llamada implica un salto a una rutina que es parte del código del sistema operativo. La realización de una llamada al sistema puede
implicar en algunos casos que el proceso que la realiza pase al estado de Bloqueado.
Cambio de modo. En el Capítulo 1, discutimos la inclusión de una fase de interrupción como parte
del ciclo de una instrucción. Recordando esto, en la fase de interrupción, el procesador comprueba
que no exista ninguna interrupción pendiente, indicada por la presencia de una señal de interrupción.
Si no hay interrupciones pendientes, el procesador pasa a la fase de búsqueda de instrucción, siguiendo con el programa del proceso actual. Si hay una interrupción pendiente, el proceso actúa de la siguiente manera:
1. Coloca el contador de programa en la dirección de comienzo de la rutina del programa manejador de la interrupción.
2. Cambia de modo usuario a modo núcleo de forma que el código de tratamiento de la interrupción pueda incluir instrucciones privilegiadas.
El procesador, acto seguido, pasa a la fase de búsqueda de instrucción y busca la primera instrucción del programa de manejo de interrupción, que dará servicio a la misma. En este punto, habitualmente, el contexto del proceso que se ha interrumpido se salvaguarda en el bloque de control de proceso del programa interrumpido.
Una pregunta que se puede plantear es, ¿qué constituye el contexto que se debe salvaguardar? La
respuesta es que se trata de toda la información que se puede ver alterada por la ejecución de la rutina
de interrupción y que se necesitará para la continuación del proceso que ha sido interrumpido. De
esta forma, se debe guardar la parte del bloque de control del proceso que hace referencia a la información de estado del procesador. Esto incluye el contador de programa, otros registros del procesador, y la información de la pila.
¿Se necesita hacer algo más? Eso depende de qué ocurra luego. El manejador de interrupción es
habitualmente un pequeño programa que realiza unas pocas tareas básicas relativas a la interrupción.
Por ejemplo, borra el flag o indicador que señala la presencia de interrupciones. Puede enviar una
confirmación a la entidad que lanzó dicha interrupción, como por ejemplo el módulo de E/S. Y puede
realizar algunas tareas internas variadas relativas a los efectos del evento que causó la interrupción.
Por ejemplo, si la interrupción se refiere a un evento de E/S, el manejador de interrupción comprobará la existencia o no de una condición de error. Si ha ocurrido un error, el manejador mandará una señal al proceso que solicitó dicha operación de E/S. Si la interrupción proviene del reloj, el manejador
la va a pasar el control al activador, el cual decidirá pasar a otro proceso debido a que la rodaja de
tiempo asignada a ese proceso ha expirado.
¿Qué pasa con el resto de información del bloque de control de proceso? Si a esta interrupción
le sigue un cambio de proceso a otro proceso, se necesita hacer algunas cosas más. Sin embargo, en
muchos sistemas operativos, la existencia de una interrupción no implica necesariamente un cambio
de proceso. Es posible, por tanto, que después de la ejecución de la rutina de interrupción, la ejecución se reanude con el mismo proceso. En esos casos sólo se necesita salvaguardar la información
del estado del procesador cuando se produce la interrupción y restablecerlo cuando se reanude la
03-Capitulo 3
140
12/5/05
16:19
Página 140
Sistemas operativos. Aspectos internos y principios de diseño
ejecución del proceso. Habitualmente, las operaciones de salvaguarda y recuperación se realizan por
hardware.
Cambio del estado del proceso. Esta claro, por tanto, que el cambio de modo es un concepto diferente del cambio de proceso8. Un cambio de modo puede ocurrir sin que se cambie el estado del
proceso actualmente en estado Ejecutando. En dicho caso, la salvaguarda del estado y su posterior
restauración comportan sólo una ligera sobrecarga. Sin embargo, si el proceso actualmente en estado
Ejecutando, se va a mover a cualquier otro estado (Listo, Bloqueado, etc.), entonces el sistema operativo debe realizar cambios sustanciales en su entorno. Los pasos que se realizan para un cambio de
proceso completo son:
1. Salvar el estado del procesador, incluyendo el contador de programa y otros registros.
2. Actualizar el bloque de control del proceso que está actualmente en el estado Ejecutando.
Esto incluye cambiar el estado del proceso a uno de los otros estados (Listo, Bloqueado, Listo/Suspendido, o Saliente). También se tienen que actualizar otros campos importantes, incluyendo la razón por la cual el proceso ha dejado el estado de Ejecutando y demás información
de auditoría.
3. Mover el bloque de control de proceso a la cola apropiada (Listo, Bloqueado en el evento i,
Listo/Suspendido).
4. Selección de un nuevo proceso a ejecutar; esta cuestión se analiza con más detalle en la Parte
Cuatro.
5. Actualizar el bloque de control del proceso elegido. Esto incluye pasarlo al estado Ejecutando.
6. Actualizar las estructuras de datos de gestión de memoria. Esto se puede necesitar, dependiendo de cómo se haga la traducción de direcciones; estos aspectos se cubrirán en la Parte
Tres.
7. Restaurar el estado del procesador al que tenía en el momento en el que el proceso seleccionado salió del estado Ejecutando por última vez, leyendo los valores anteriores de contador de
programa y registros.
Por tanto, el cambio de proceso, que implica un cambio en el estado, requiere un mayor esfuerzo
que un cambio de modo.
EJECUCIÓN DEL SISTEMA OPERATIVO
En el Capítulo 2, señalamos dos aspectos intrínsecos del sistema operativo:
• El sistema operativo funciona de la misma forma que cualquier software, en el sentido que un
sistema operativo es un conjunto de programas ejecutados por un procesador.
• El sistema operativo, con frecuencia, cede el control y depende del procesador para recuperar
dicho control.
8
El término cambio de contexto (context switch) se usa en literatura sobre sistemas operativos y en libros de texto. Desafortunadamente, aunque la mayoría de estas referencias utilizan este término para lo que en este libro se ha denominado cambio de proceso, otras fuentes lo usan para referirse al cambio de modo o incluso al cambio de thread (definido en el siguiente capítulo). Para evitar ambigüedades, este término no se ha utilizado en este libro.
03-Capitulo 3
12/5/05
16:19
Página 141
Descripción y control de procesos
141
Si el sistema operativo es simplemente una colección de programas y si son ejecutados por el
procesador, tal y como cualquier otro programa, ¿es el sistema operativo un proceso del sistema? y si
es así ¿cómo se controla? Estas interesantes cuestiones han inspirado unas cuentas direcciones diferentes de diseño. La Figura 3.15 ilustra el rango de diferentes visiones que se encuentran en varios
sistemas operativos contemporáneos.
Núcleo sin procesos. Una visión tradicional, que es común a muchos sistemas operativos antiguos, es la ejecución del sistema operativo fuera de todo proceso (Figura 3.15a). Con esta visión,
cuando el proceso en ejecución se interrumpe o invoca una llamada al sistema, el contexto se
guarda y el control pasa al núcleo. El sistema operativo tiene su propia región de memoria y su
propia pila de sistema para controlar la llamada a procedimientos y sus retornos. El sistema operativo puede realizar todas las funciones que necesite y restaurar el contexto del proceso interrumpido, que hace que se retome la ejecución del proceso de usuario afectado. De forma alternativa,
el sistema operativo puede realizar la salvaguarda del contexto y la activación de otro proceso diferente. Si esto ocurre o no depende de la causa de la interrupción y de las circunstancias puntuales en el momento.
En cualquier caso, el punto clave en este caso es que el concepto de proceso se aplica aquí únicamente a los programas de usuario. El código del sistema operativo se ejecuta como una entidad independiente que requiere un modo privilegiado de ejecución.
Ejecución dentro de los procesos de usuario. Una alternativa que es común en los sistemas
operativos de máquinas pequeñas (PC, estaciones de trabajo) es ejecutar virtualmente todo el software de sistema operativo en el contexto de un proceso de usuario. Esta visión es tal que el sistema operativo se percibe como un conjunto de rutinas que el usuario invoca para realizar diferentes funciones, ejecutadas dentro del entorno del proceso de usuario. Esto se muestra en la Figura 3.15b. En un
punto determinado, el sistema operativo maneja n imágenes de procesos. Cada imagen incluye no
P1
P2
Pn
Núcleo
(a) Núcleo independiente
P1
P2
Pn
Funciones
del SO
Funciones
del SO
Funciones
del SO
Funciones de conmutación entre procesos
(b) Funciones del SO se ejecutan dentro del proceso de usuario
P1
P2
Pn
OS1
OSk
Funciones de conmutación entre procesos
(c) Funciones del SO se ejecutan como procesos independiente
Figura 3.15.
Relación entre el sistema operativo y los procesos de usuario.
03-Capitulo 3
142
12/5/05
16:19
Página 142
Sistemas operativos. Aspectos internos y principios de diseño
sólo las regiones mostradas en la Figura 3.13, sino que además incluye áreas de programa, datos y
pila para los programas del núcleo.
La Figura 3.16 sugiere la estructura de imagen de proceso típica para esta estrategia. Se usa una
pila de núcleo separada para manejar llamadas/retornos cuando el proceso está en modo núcleo. El
código de sistema operativo y sus datos están en el espacio de direcciones compartidas y se comparten entre todos los procesos.
Cuando ocurre una interrupción, trap o llamada al sistema, el procesador se pone en modo núcleo y el control se pasa al sistema operativo. Para este fin, el contexto se salva y se cambia de
modo a una rutina del sistema operativo. Sin embargo, la ejecución continúa dentro del proceso de
usuario actual. De esta forma, no se realiza un cambio de proceso, sino un cambio de modo dentro
del mismo proceso.
Si el sistema operativo, después de haber realizado su trabajo, determina que el proceso actual
debe continuar con su ejecución, entonces el cambio de modo continúa con el programa interrumpido, dentro del mismo proceso. Ésta es una de las principales ventajas de esta alternativa: se ha interrumpido un programa de usuario para utilizar alguna rutina del sistema operativo, y luego continúa, y todo esto se ha hecho sin incurrir en un doble cambio de proceso. Sin embargo, si se
determina que se debe realizar un cambio de proceso en lugar de continuar con el proceso anterior,
entonces el control pasa a la rutina de cambio de proceso. Esta rutina puede o no ejecutarse dentro
del proceso actual, dependiendo del diseño del sistema. En algún momento, no obstante, el proceso
actual se debe poner en un estado de no ejecución, designando a otro porceso como el siguiente a
Identificador del
proceso
Información de
estado del procesador
Bloque de
control del
proceso
Información de
control del proceso
Pila de usuario
Espacio privado de
direcciones de usuario
(programas y datos)
Pila del núcleo
Espacio compartido
de direcciones
Figura 3.16.
Imagen de proceso: el sistema operativo ejecuta dentro del espacio de usuario.
03-Capitulo 3
12/5/05
16:19
Página 143
Descripción y control de procesos
143
ejecutar. Durante esta fase, es más conveniente ver la ejecución como fuera de cualquiera de los
procesos.
De alguna forma, esta visión de un sistema operativo es muy reseñable. De una forma más sencilla, en determinados instantes de tiempo, un proceso salva su estado, elige otro proceso a ejecutar entre aquellos que están listos, y delega el control a dicho proceso. La razón por la cual este esquema no
se convierte en una situación arbitraria y caótica es que durante los instantes críticos el código que se
está ejecutando es código de compartido de sistema operativo, no código de usuario. Debido al concepto de modo usuario y modo núcleo, el usuario no puede interferir con las rutinas del sistema operativo, incluso cuando se están ejecutando dentro del entorno del proceso del usuario. Esto nos recuerda que existe una clara distinción entre el concepto de proceso y programa y que la relación entre
los dos no es uno a uno. Dentro de un proceso, pueden ejecutarse tanto un programas de usuario
como programas del sistema operativo. Los programas de sistema operativo que se ejecutan en varios
procesos de usuario son idénticos.
Sistemas operativos basados en procesos. Otra alternativa, mostrada en la Figura 3.15c, es
implementar un sistema operativo como una colección de procesos de sistema. Como en las otras opciones, el software que es parte del núcleo se ejecuta en modo núcleo. En este caso, las principales
funciones del núcleo se organizan como procesos independientes. De nuevo, debe haber una pequeña
cantidad de código para intercambio de procesos que se ejecuta fuera de todos los procesos.
Esta visión tiene diferentes ventajas. Impone una disciplina de diseño de programas que refuerza
el uso de sistemas operativos modulares con mínimas y claras interfaces entre los módulos. Adicionalmente, otras funciones del sistema operativo que no sean críticas están convenientemente separadas como otros procesos. Por ejemplo, hemos mencionado anteriormente un programa de monitorización que recoge niveles de utilización de diferentes recursos (procesador, memoria, canales) y el ratio
de progreso de los procesos en el sistema. Debido a que este programa no realiza ningún servicio a
ningún otro programa activo, sólo se puede invocar por el sistema operativo. Como proceso, esta función puede ejecutarse a un nivel de prioridad determinado, intercalándose con otros procesos bajo el
control del activador. Por último, la implementación del sistema operativo como un grupo de procesos en entornos de multiprocesadores y multicomputadores, en los cuales determinados servicios del
sistema operativo se pueden enviar a procesadores dedicados, incrementando el rendimiento.
3.5. UNIX SVR4 PROCESS MANAGEMENT
UNIX System V hace uso de una gestión de procesos simple pero muy potente que es fácilmente visible a nivel de usuario. UNIX sigue el modelo mostrado en la Figura 3.15b, en la cual la gran mayoría
del sistema operativo se ejecuta dentro del entorno del proceso. De esta forma se necesitan dos modos, usuario y núcleo. UNIX utiliza también dos categorías de procesos, procesos de sistema y procesos de usuario. Los procesos del sistema ejecutan en modo núcleo y ejecuta código del sistema operativo para realizar tareas administrativas o funciones internas, como reserva de memoria o swapping
de procesos. Los procesos de usuario operan en modo usuario para ejecutar programas y utilidades y
en modo núcleo para ejecutar instrucciones que pertenecen al núcleo. Un proceso de usuario entra en
modo núcleo por medio de la solicitud de la llamada al sistema, cuando se genera una de excepción
(fallo) o cuando ocurre una interrupción.
ESTADOS DE LOS PROCESOS
En los sistemas operativos UNIX se utilizan un total de nueve estados de proceso; estos se encuentran
recogidos en la Tabla 3.9 y el diagrama de transiciones se muestra en la Figura 3.17 (basada en figura
03-Capitulo 3
144
12/5/05
16:19
Página 144
Sistemas operativos. Aspectos internos y principios de diseño
en [BACH86]). Esta figura es similar a la Figura 3.9b, con dos estados de procesos dormidos, propios
de UNIX, correspondientes a dos estados de bloqueo. Las diferencias se pueden resumir en:
• UNIX utiliza dos estados Ejecutando que indican si el proceso está ejecutando en modo usuario o en modo núcleo.
• Se debe realizar la distinción entre dos estados: (Listo para Ejecutar, en Memoria) y (Expulsado). Estos son esencialmente el mismo estado, como indica la línea punteada que los une. La
distinción se realiza para hacer énfasis en la forma mediante la cual se llega al estado de Expulsado. Cuando un proceso ejecuta en modo núcleo (como resultado de una llamada al sistema, interrupción de reloj o interrupción de E/S), requerirá un tiempo para que el sistema operativo complete su trabajo y esté listo para devolver el control al proceso de usuario. En este
punto, el núcleo decide expulsar al proceso actual en favor de uno de los procesos listos de
mayor prioridad. En este caso, el proceso actual se mueve al estado Expulsado. Sin embargo,
por cuestiones de activación, estos procesos en el estado Expulsado y todos aquellos en los estados Listo para Ejecutar en Memoria, forman una única cola.
La expulsión sólo puede ocurrir cuando un proceso está a punto de moverse de modo núcleo a
modo usuario. Mientras los procesos ejecutan al modo núcleo, no pueden ser expulsados. Esto hace
que los sistemas UNIX no sean apropiados para procesamiento en tiempo real. Se proporcionará una
explicación de los requisitos para procesamiento de tiempo-real en el Capítulo 10.
En UNIX existen dos procesos con un interés particular. El proceso 0 es un proceso especial que
se crea cuando el sistema arranca; en realidad, es una estructura de datos predefinida que se carga en
cuanto el ordenador arranca. Es el proceso swapper. Adicionalmente, el proceso 0 lanza al proceso 1,
que se denomina proceso init; todos los procesos del sistema tienen al proceso 1 como antecesor.
Cuando un nuevo usuario interactivo quiere entrar en el sistema, es el proceso 1 el que crea un proceso de usuario para él. Posteriormente, el proceso del usuario puede crear varios procesos hijos que
conforman una estructura de árbol de procesos, de esta forma cualquier aplicación en particular puede consistir en un número de procesos relacionados entre sí.
Tabla 3.9.
Estados de procesos en UNIX.
Ejecutando Usuario
Ejecutando en modo usuario.
Ejecutando Núcleo
Ejecutando en modo núcleo.
Listo para Ejecutar, en Memoria
Listo para ejecutar tan pronto como el núcleo lo planifique.
Dormido en Memoria
No puede ejecutar hasta que ocurra un evento; proceso en
memoria principal (estado de bloqueo).
Listo para Ejecutar, en Swap
El proceso está listo para preguntar, pero el swapper debe
cargar el proceso en memoria principal antes de que el núcleo pueda planificarlo para su ejecución.
Durmiendo, en Swap
El proceso está esperando un evento y ha sido expulsado al
almacenamiento secundario (estado de bloqueo).
Expulsado
El proceso ha regresado de modo núcleo a modo usuario,
pero el núcleo lo ha expulsado y ha realizado la activación
de otro proceso.
Creado
El proceso ha sido creado recientemente y aún no está listo
para ejecutar.
Zombie
El proceso ya no existe, pero deja un registro para que lo recoja su proceso padre.
03-Capitulo 3
12/5/05
16:19
Página 145
Descripción y control de procesos
145
Fork
Creado
Expulsado
Suficiente
memoria
Retorno a
usuario
Ejecutando
en usuario
No hay suficiente memoria
(sólo sistemas con swap)
Expulsión
Retorno
Llamada al sistema,
interrupción
Replanificación
del proceso
Expulsión a swap
Recuperación
desde swap
Listo para
ejecutar en
swap
Ejecutando
en núcleo
Dormido
Interrupción,
retorno de
interrupción
Despertar
Despertar
Salida
Zombie
Figura 3.17.
Listo para
ejecutar en
memoria
Dormido
en
memoria
Expulsión a swap
Dormido
en swap
Diagrama de transiciones entre estados de procesos UNIX.
DESCRIPCIÓN DE PROCESOS
Un proceso UNIX es un conjunto de estructuras de datos, más bien complejas, que proporcionan al
sistema operativo toda la información necesaria para manejar y activar los procesos. La Tabla 3.10 recoge los elementos de la imagen de proceso, que están organizados en tres partes: contexto a nivel de
usuario, contexto de registros, y contexto a nivel de sistema.
El contexto a nivel de usuario contiene los elementos básicos de un programa de usuario
que se pueden generar directamente desde un fichero objeto compilado. Un programa de usuario
se divide en áreas de texto y datos; el área texto es de sólo-lectura y se crea con la intención de
contener las instrucciones del programa. Cuando el proceso está en ejecución, el procesador utiliza el área de pila de usuario para gestionar las llamadas a procedimientos y sus retornos, así
como los parámetros pasados. El área de memoria compartida es un área de datos que se comparte con otros procesos. Sólo existe una única copia física del área de memoria compartida,
pero, por medio de la utilización de la memoria virtual, se presenta a cada uno de los procesos
que comparten esta región de memoria común dentro de su espacio de dirección. Cuando un proceso está ejecutando, la información de estado de procesador se almacena en el área de contexto
de registros.
El contexto a nivel de sistema contiene la información restante que necesita el sistema operativo para manejar el proceso. Consiste en una parte estática, de tamaño fijo y que permanece
como parte del proceso a lo largo de todo su tiempo de vida, y una parte dinámica, que varía de
tamaño a lo largo de la vida del proceso. La entrada de la tabla de procesos es un elemento de la
parte estática y contiene información de control del proceso que es accesible por parte del núcleo
03-Capitulo 3
146
12/5/05
16:19
Página 146
Sistemas operativos. Aspectos internos y principios de diseño
en todo momento; además, en un sistema de memoria virtual, todas las entradas en la tabla de
procesos se mantienen en memoria principal. La Tabla 3.11 muestra los contenidos de la entrada
de la tabla de procesos. El área de usuario, o área U, contiene información adicional de proceso
que necesita el núcleo cuando está ejecutando en el contexto de este proceso; también se utiliza
cuando el proceso se pagina desde/hacia memoria (swapping). La Tabla 3.12 muestra los contenidos de dicha tabla.
Tabla 3.10.
Imagen de un proceso UNIX.
Contexto a nivel de usuario
Texto
Instrucciones máquina ejecutables del programa.
Datos
Datos accesibles por parte del programa asociado a dicho
proceso.
Pila de usuario
Contiene los argumentos, las variables locales, y los punteros a
funciones ejecutadas en modo usuario.
Memoria compartida
Memoria compartida con otros procesos, usada para la comunicación entre procesos.
Contexto de registros
Contador de programa
Dirección de la siguiente instrucción a ejecutar; puede tratarse del espacio de memoria del núcleo o de usuario de dicho
proceso.
Registro de estado
del procesador
Contiene el estado del hardware del procesador en el momento
de la expulsión; los contenidos y el formato dependen específicamente del propio hardware.
Puntero de pila
Apunta a la cima de la pila de núcleo o usuario, dependiendo
del modo de operación en el momento de la expulsión.
Registros de propósito general
Depende del hardware.
Contexto nivel de sistema
Entrada en la tabla de procesos
Define el estado del proceso; esta información siempre está accesible por parte de sistema operativo.
Área U (de usuario)
Información de control del proceso que sólo se necesita acceder
en el contexto del propio proceso.
Tabla de regiones por proceso
Define la traducción entre las direcciones virtuales y físicas;
también contiene información sobre los permisos que indican el
tipo de acceso permitido por parte del proceso; sólo-lectura, lectura-escritura, o lectura-ejecución.
Pila del núcleo
Contiene el marco de pila de los procedimientos del núcleo
cuando el proceso ejecuta en modo núcleo.
03-Capitulo 3
12/5/05
16:19
Página 147
Descripción y control de procesos
Tabla 3.11.
147
Entrada en la tabla de procesos UNIX.
Estado del proceso
Estado actual del proceso.
Punteros
Al área U y al área de memoria del proceso (texto, datos, pila).
Tamaño de proceso
Permite al sistema operativo conocer cuánto espacio está reservado
para este proceso.
Identificadores de usuario
El ID de usuario real identifica el usuario que es responsable de la
ejecución del proceso. El ID de usuario efectivo se puede utilizar
para que el proceso gane, de forma temporal, los privilegios asociados a un programa en particular; mientras ese programa se ejecuta como parte del proceso, el proceso opera con el identificador
de usuario efectivo.
Identificadores de proceso
Identificador de este proceso; identificador del proceso padre. Estos
identificadores se fijan cuando el proceso entra en el estado Creado
después de la llamaba al sistema fork.
Descriptor de evento
Válido cuando el proceso está en un estado dormido; cuando el
evento ocurre, el proceso se mueve al estado Listo para Ejecutar.
Prioridad
Utilizado en la planificación del proceso.
Señal
Enumera las señales enviadas a este proceso pero que no han sido
aún manejadas.
Temporizadores
Incluye el tiempo de ejecución del proceso, la utilización de recursos de núcleo, y el temporizador fijado por el usuario para enviar la
señal de alarma al proceso.
P_link
Puntero al siguiente enlace en la cola de Listos (válido si proceso
está Listo para Ejecutar).
Estado de memoria
Indica si la imagen del proceso se encuentra en memoria principal
o secundaria. Si está en memoria, este campo indica si puede ser
expulsado a swap o si está temporalmente fijado en memoria
principal.
La distinción entre la entrada de la tabla de procesos y el área U refleja el hecho de que el
núcleo de UNIX siempre ejecuta en el contexto de un proceso. La mayor parte del tiempo, el núcleo está realizando tareas relacionadas con dicho proceso. Sin embargo, otra parte del tiempo,
como cuando el núcleo está realizando tareas de planificación preparatorias para la activación de
otro proceso, se necesita la información sobre la totalidad de procesos del sistema. La información en la tabla de procesos es accesible cuando el proceso específico no es el que actualmente
está en ejecución.
La tercera parte estática de contexto a nivel de sistema es una tabla de regiones por proceso, que
se utiliza para el sistema de gestión de memoria. Por último, la pila del núcleo es una parte dinámica
de contexto a nivel de sistema. Esta pila se usa cuando el proceso está ejecutando en modo núcleo y
contiene la información que debe salvaguardarse y restaurarse en las llamadas a procedimientos o
cuando ocurre una interrupción.
03-Capitulo 3
148
12/5/05
16:19
Página 148
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 3.12. Área U de UNIX.
Puntero a la tabla de proceso
Indica la entrada correspondiente a esta área U.
Identificador de usuario
Identificador de usuario real y efectivo. Utilizado para determinar los privilegios.
Temporizadores
Registro del tiempo que el proceso (y sus descendientes)
han utilizado para ejecutar en modo usuario y modo núcleo.
Vector de manejadores de señales
Para cada tipo de señal definida en el sistema, se indica
cómo el proceso va a reaccionar a la hora de recibirla (salir,
ignorar, ejecutar una función específica definida por el
usuario).
Terminal de control
Indica el terminal de acceso (login) para este proceso, si
existe.
Campo de error
Recoge los errores encontrados durante una llamada al
sistema.
Valor de retorno
Contiene los resultados de una llamada al sistema.
Parámetros de E/S
Indica la cantidad de datos transferidos, la dirección fuente
(o destino) del vector de datos en el espacio de usuario, y los
desplazamientos en fichero para la E/S.
Parámetros en fichero
Directorio actual y directorio raíz, dentro del sistema de ficheros, asociado al entorno de este proceso.
Tabla de descriptores de fichero
de usuario
Recoge los ficheros del proceso abierto.
Campos límite
Restringe el tamaño del proceso y el tamaño máximo de fichero que puede crear.
Campos de los modos de permiso
Máscara de los modos de protección para la creación de ficheros por parte de este proceso.
CONTROL DE PROCESOS
La creación de procesos en UNIX se realiza por medio de la llamada al sistema fork(). Cuando con
un proceso solicita una llamada fork, el sistema operativo realiza las siguientes funciones [BACH86]:
1. Solicita la entrada en la tabla de procesos para el nuevo proceso.
2. Asigna un identificador de proceso único al proceso hijo.
3. Hace una copia de la imagen del proceso padre, con excepción de las regiones de memoria
compartidas.
4. Incrementa el contador de cualquier fichero en posesión del padre, para reflejar el proceso adicional que ahora también posee dichos ficheros.
5. Asigna al proceso hijo el estado Listo para Ejecutar.
6. Devuelve el identificador del proceso hijo al proceso padre, y un valor 0 al proceso hijo.
Todo este trabajo se realiza en modo núcleo, dentro del proceso padre. Cuando el núcleo ha completado estas funciones puede realizar cualquiera de las siguientes acciones, como parte de la rutina
del activador:
03-Capitulo 3
12/5/05
16:19
Página 149
Descripción y control de procesos
149
1. Continuar con el proceso padre. El control vuelve a modo usuario en el punto en el que se realizó la llamada fork por parte del padre.
2. Transferir el control al proceso hijo. El proceso hijo comienza ejecutar en el mismo punto del
código del padre, es decir en el punto de retorno de la llamada fork.
3. Transferir el control a otro proceso. Ambos procesos, padre e hijo, permanecen en el estado
Listos para Ejecutar.
Puede resultar quizá un poco difícil visualizar este modo de creación de procesos debido a que
ambos procesos, padre e hijo, están ejecutando el mismo segmento de código. La diferencia reside en
que: cuando se retorna de la función fork, el parámetro de retorno se comprueba. Si el valor es 0, entonces este es el proceso hijo, y se puede realizar una bifurcación en la ejecución del programa para
continuar con la ejecución de programa hijo. Si el valor no es 0, éste es el proceso padre, y puede
continuar con la línea principal ejecución.
3.6. RESUMEN
El concepto fundamental dentro de los sistemas operativos modernos es el concepto de proceso. La
función principal de un sistema operativo es crear, gestionar y finalizar los procesos. Cuando un proceso está activo, el sistema operativo debe ver cómo reservar tiempo para su ejecución por parte del
procesador, coordinar sus actividades, gestionar las demandas que planteen conflictos, y reservar recursos del sistema para estos procesos.
Para realizar estas funciones de gestión de procesos, el sistema operativo mantiene una descripción de cada proceso o imagen de proceso, que incluye el espacio de direcciones dentro del cual el
proceso está ejecutando, y el bloque de control de proceso. Este último contiene toda la información
que el sistema operativo necesita para gestionar el proceso, incluyendo el estado actual, la reserva de
recursos, la prioridad y otros datos de relevancia.
Durante su tiempo de vida, un proceso se mueve a lo largo de diferentes estados. Los más importantes de estos estados son Listo, Ejecutando, y Bloqueado. Un proceso Listo es un proceso
que no está actualmente en ejecución pero que está listo para ser ejecutado tan pronto el sistema
operativo lo active. Un proceso que está Ejecutando es aquel que está actualmente en ejecución
por el procesador. En los sistemas multiprocesador, podrá haber varios procesos en este estado.
Un proceso Bloqueado está esperando a que se complete un determinado evento, como una operación de E/S.
Un proceso en ejecución se interrumpe bien por una interrupción, que es un evento que ocurre fuera de proceso y que es recogido por el procesador, o por la ejecución de una llamada al
sistema. En cualquier caso, el procesador realiza un cambio de modo, que es la transferencia
de control a unas rutinas del sistema operativo. El sistema operativo, después de haber realizado el trabajo necesario, puede continuar con el proceso interrumpido o puede cambiar a otros
procesos.
3.7. LECTURAS RECOMENDADAS
Todos los libros de texto listados de la Sección 2.9 cubren el material de este capítulo. Se pueden encontrar en [GOOD94] y [GRAY97] unas buenas descripciones de la gestión de procesos en UNIX.
[NEHM75] es una descripción interesante de los estados de proceso del sistema operativo y las funciones necesarias para la activación de procesos.
03-Capitulo 3
150
12/5/05
16:19
Página 150
Sistemas operativos. Aspectos internos y principios de diseño
GOOD94 Goodheart, B., y Cox, J. The magic Garden Explained: The Internals of UNIX System V Release 4. Englewood Cliffs, NJ: Prentice Hall, 1994.
GRAY97 Gray, J. Interprocess Communication in Unix: The Nooks and Crannies. Upper Saddler River, NJ: Prentice Hall, 1997.
NEHM75 Nehmer, J. «Dispatcher Primitives for the Construction of Operating System Kernels.» Acta
Informática, vol 5, 1975.
3.8. TÉRMINOS CLAVE, CUESTIONES DE REPASO, Y PROBLEMAS
TÉRMINOS CLAVE
bloque de control de proceso
imagen de proceso
proceso hijo
cambio de modo
interrupción
proceso padre
cambio de proceso
modo núcleo
round-robin
estado bloqueado
modo privilegiado
swapping
estado ejecutando
modo sistema
tarea
estado listo
modo usuario
traza
trap
estado saliente
nuevo estado
estado suspendido
palabra de estado de programa
expulsión
proceso
CUESTIONES DE REPASO
3.1.
¿Qué es una traza de instrucciones?
3.2.
¿Cuáles son los eventos comunes que llevan a la creación de un proceso?
3.3.
Para el modelo de procesamiento de la Figura 3.6, defina brevemente cada estado.
3.4.
¿Qué significa la expulsión de un proceso?
3.5.
¿Que es el swapping y cuál es su objetivo?
3.6.
¿Por qué la Figura 3.9 tiene dos estados bloqueados?
3.7.
Indique cuatro características de un proceso suspendido.
3.8.
¿Para qué tipo de entidades el sistema operativo mantiene tablas de información por motivos de gestión?
3.9.
Indique tres categorías generales de información que hay en el bloque de control de proceso.
3.10. ¿Por qué se necesitan dos modos (usuario y núcleo)?
3.11. ¿Cuáles son los pasos que realiza el sistema operativo para la creación de un proceso?
3.12. ¿Cuál es la diferencia entre interrupción y trap?
3.13. Dé tres ejemplos de interrupción.
3.14. ¿Cuál es la diferencia entre cambio de modo y cambio de proceso?
03-Capitulo 3
12/5/05
16:19
Página 151
Descripción y control de procesos
151
PROBLEMAS
3.1.
Nombre cinco actividades principales del sistema operativo respecto a la gestión de procesos, y de forma breve describa por qué cada una es necesaria.
3.2.
En [PINK89], se definen para los procesos los siguientes estados: ejecuta (ejecutando), activo (listo), bloqueado, y suspendido. Un proceso está bloqueado si está esperando el permiso para acceder a un recurso, y está bloqueado cuando está esperando a que se realice
una operación sobre un recurso que ya ha adquirido. En muchos sistemas operativos, estos
dos estados están agrupados en el estado de bloqueado, y el estado suspendido tiene el sentido usado en este capítulo. Compare las ventajas de ambas definiciones.
3.3.
Para el modelo de siete estados de la Figura 3.9b, dibuje un diagrama de colas similar al de
la Figura 3.8b.
3.4.
Considerando el diagrama de transiciones de la Figura 3.9b. Suponga que le toca al sistema operativo activar un proceso y que hay procesos en el estado Listo y Listo/Suspendido, y que al menos uno de los procesos en estado Listo/Suspendido tiene mayor prioridad
que todos los procesos Listos. Existen dos políticas extremas (1) siempre activar un proceso en estado Listo, para minimizar el efecto del swapping y (2) siempre dar preferencia
a los procesos con mayor prioridad, incluso cuando eso implique hacer swapping, y dicho
swapping no fuese necesario. Sugiera una política que encuentre un punto medio entre
prioridad y rendimiento.
3.5.
La Tabla 3.13 muestra los estados de proceso del sistema operativo VAX/VMS.
a) ¿Puede proporcionar una justificación para la existencia de tantos estados distintos de
espera?
b) ¿Por qué los siguientes estados no tienen una versión residente y en swap: espera por
fallo de página, espera por colisión de página, espera a un evento común, espera a liberación de página, y espera por recurso?
c) Dibuje un diagrama de transiciones de estado que indique la acción o suceso que causa
dicha transición.
3.6.
El sistema operativo VAX/VMS utiliza cuatro modos de acceso al procesador para facilitar
la protección y compartición de recursos del sistema entre los procesos. El modo de acceso
determina:
• Privilegios de ejecución de instrucciones. ¿Qué instrucciones puede ejecutar el procesador?
• Privilegios de acceso a memoria. ¿Qué posiciones de memoria pueden acceder las instrucciones actuales?
Los cuatros modos de acceso son:
• Núcleo. Ejecuta el núcleo del sistema operativo VMS, que incluye gestión de memoria,
manejo de interrupciones, y operaciones de E/S.
• Ejecutivo. Ejecuta muchas de las llamadas al sistema, incluyendo las rutinas de gestión
de ficheros y registros (disco o cinta).
• Supervisor. Ejecuta otros servicios del sistema operativo, tales como las respuestas a
los mandatos del usuario.
• Usuario. Ejecuta los programas de usuario, además de las utilidades como compiladores, editores, enlazadores, y depuradores.
03-Capitulo 3
152
12/5/05
16:19
Página 152
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 3.13.
VAX/VMS Estados del Proceso
Estado del Proceso
Condición del Proceso
Actualmente en ejecución
Proceso en ejecución.
Computable (residente)
Listo y residente en memoria principal.
Computable (en swap)
Listo, pero expulsado de memoria principal.
Espera por fallo de página
El proceso ha hecho referencia a una página que no está en
memoria principal y debe esperar a que dicha página se lea.
Espera por colisión de página
El proceso ha hecho referencia a una página compartida que ha
sido la causa de una espera por fallo de página en otro proceso, o una página privada que está en fase de ser leída en memoria o escrita a disco.
Espera a un evento común
Esperando a un flag de evento compartido (flags de evento son
mecanismos de comunicación entre procesos por señales de
un solo bit).
Espera por liberación de página
Esperando a que se libere una nueva página en memoria principal, que será asignada a un conjunto de páginas asignadas a
dicho proceso (el conjunto de trabajo del proceso).
Espera hibernada (residente)
El proceso se ha puesto él mismo en estado de espera.
Espera hibernada (en swap)
Proceso hibernado que ha sido expulsado de memoria principal.
Espera a un evento local (residente)
El proceso está en memoria principal y a la espera de un flag
de evento local (habitualmente a la espera de la finalización de
una operación de E/S).
Espera a un evento local (en swap)
Proceso en espera de un evento local que ha sido expulsado de
memoria principal.
Espera suspendida (residente)
El proceso se ha puesto en estado de espera por otro proceso.
Espera suspendida (en swap)
Proceso en espera suspendida que ha sido expulsado de memoria principal.
Espera de recurso
El proceso está esperando a disponer de un recurso de tipo general del sistema.
Un proceso que ejecuta en el modo con menos privilegios a menudo necesita llamar a un
procedimiento que se ejecuta en un nivel de mayor privilegio; por ejemplo, un programa de
usuario requiere una llamada al sistema. Esta llamada se realiza por medio de una instrucción de cambio de modo (CHM), que causa una interrupción que transfiere el control a una
rutina en el nuevo modo de acceso. El retorno se realiza por medio de la instrucción REI
(retorno de una excepción o interrupción).
a) Un gran número de sistemas operativos sólo tienen dos modos, núcleo y usuario. ¿Cuáles son las ventajas y los inconvenientes de tener cuatro modos en lugar de dos?
b) ¿Puede incluir un caso en el que haya más de cuatro modos?
3.7.
El esquema de VMS comentado en el problema anterior se denomina habitualmente estructura de protección en anillo, tal y como se ilustra en la Figura 3.18. En realidad, el modelo
sencillo núcleo/usuario, tal y como se describe en la Sección 3.3, es una estructura de dos
anillos. [SILB04] aborda el problema con el siguiente enfoque.
La principal desventaja del modelo de estructura en anillo (jerárquico) es que no nos permite forzar el principio de necesidad-de-conocimiento. En particular, si un objeto debe ser
12/5/05
16:19
Página 153
Descripción y control de procesos
153
accesible en el dominio Dj pero no accesible en el dominio Di, entonces debemos tener j<i.
Pero esto implica que todo segmento accesible en Di también es accesible en Dj.
a) Explique claramente cuál es el problema indicado en esta última cita.
b) Sugiera una forma mediante la cual un sistema operativo con estructura en anillo pueda
resolver este problema.
3.8.
La Figura 3.7b sugiere que un proceso sólo se puede encontrar en una cola de espera por
un evento en el mismo instante.
a) ¿Es posible que un proceso esté esperando por más de un evento a la vez? Proporcione
un ejemplo.
b) En dicho caso, ¿cómo modificaría la estructura de colas de la figura para dar soporte a
esta nueva funcionalidad?
3.9.
En gran número de los primeros ordenadores, una interrupción hacía que automáticamente
los registros del procesador se guardasen en unas posiciones determinadas asociadas con
esa señal de interrupción en particular, ¿bajo qué circunstancias esta es una buena técnica?
Explique, por qué en general no es así.
3.10. En la Sección 3.4, se indica que UNIX no es apropiado para aplicaciones de tiempo real
porque un proceso ejecutando en modo núcleo no puede ser expulsado. Elabore más el razonamiento.
I
CH
x
M
RE
03-Capitulo 3
Núcleo
Ejecutivo
Supervisor
Usuario
Figura 3.18.
Modos de acceso de VAX/VMS.
03-Capitulo 3
154
12/5/05
16:19
Página 154
Sistemas operativos. Aspectos internos y principios de diseño
PROYECTO DE PROGRAMACIÓN UNO. DESARROLLO DE UN INTÉRPRETE
DE MANDATOS
El shell o intérprete de mandatos es la interfaz de usuario fundamental de los sistemas operativos.
El primer proyecto es escribir un intérprete de mandatos sencillo – myshell – con las siguientes propiedades:
1. El intérprete de mandatos debe dar soporte a los siguientes mandatos internos:
i. cd <directorio> – cambia el directorio actual a <directorio>. Si el argumento <directorio>
no aparece, devuelve el directorio actual. Si el directorio no existe se debe proporcionar un
mensaje de error apropiado. Este mandato debe modificar también la variable de entorno
PWD.
ii. clr – limpia la pantalla.
iii. dir <directory> – lista el contenido de <directorio>.
iv. environ – muestra todas las variables de entorno.
v. echo <comentario> – muestra <comentario> en la pantalla seguido de una nueva línea (espacios múltiples o tabuladores se reducen a un especio sencillo).
vi. help – muestra el manual de usuario usando el filtro more.
vii. pause – detiene la ejecución del intérprete de mandatos hasta que se pulse ‘Intro’.
viii. quit – sale del intérprete de mandatos.
ix. El entorno del intérprete de mandatos debe contener shell=<ruta>/myshell donde
<ruta>/myshell es la ruta completa al ejecutable del intérprete de mandatos (no una ruta fijada al directorio inicial, sino la ruta real desde dónde se ha ejecutado).
2. Todo el resto de entradas por teclado se interpretan como la invocación de un programa, que deben realizarse por medio de un fork y la ejecución de dicho programa. Todo ello como un proceso hijo del intérprete de mandatos. Los programa deben ejecutarse en un entorno que incluya la
entrada: parent=<ruta>/myshell donde <ruta>/myshell es la ruta descrita en el apartado 1.ix
anterior.
3. El intérprete de mandatos debe ser capaz de leer su entrada de mandatos de un fichero. Por ejemplo, si se invoca al intérprete de mandatos con la línea:
myshell fichero-lotes
Donde fichero-lotes se supone que contiene las líneas de mandatos para el intérprete. Cuando se
llegue al final del fichero, el intérprete de mandatos debe terminar. Obviamente, si el intérprete se
invoca sin argumentos de entrada, solicitará los mandatos al usuario vía consola.
4. El intérprete de mandatos debe soportar redirección de E/S, sobre stdin y/o stdout. Por ejemplo, la
línea de mandatos:
nombreprograma arg1 arg2 < entrada > salida
ejecutará el programa nombreprograma con los argumentos arg1 y arg2, el flujo de entra stdin se
alimentará del fichero entrada y el flujo de salida stdout se volcará en el fichero salida.
La redirección de stdout debe de ser posible también para los mandatos internos: dir, environ,
echo, y help.
Para la redirección de salida, si el carácter de redirección es > se creará el fichero salida si no existe y si existe se truncará su contenido. Si el indicador de redirección es >> se creará el fichero salida si no existe y si existe se añadirá la salida al final de su contenido.
5. El intérprete de mandatos debe soportar la ejecución de mandatos en segundo plano (background). Un signo & al final de la línea de mandatos indica que el intérprete debe devolver un
prompt al usuario, inmediatamente después de haber lanzado el programa.
6. El prompt debe indicar la ruta del directorio actual.
03-Capitulo 3
12/5/05
16:19
Página 155
Descripción y control de procesos
155
Nota: se puede asumir que todos los argumentos en la línea de mandatos (incluyendo los símbolos
de redirección >, <, y >>; y background &) estarán separados de los otros argumentos en la línea de
mandatos por espacios en blanco – uno o más espacios y/o tabuladores (obsérvese la línea de mandatos en 4).
Requisitos del proyecto
1. Diseñe un intérprete de mandatos sencillo que satisfaga los criterios antes mencionados e impleméntelo en la plataforma UNIX seleccionada.
2. Escriba un manual sencillo que describa cómo usar el intérprete. El manual debe contener suficiente información para que un usuario principiante en UNIX pueda usarlo. Por ejemplo, se debe
explicar los conceptos de redirección de E/S, de entorno de programa, y de ejecución en segundo
plano (background). El manual DEBE llamarse readme y debe ser un documento de texto plano
que pueda leerse con un editor de texto estándar.
Como ejemplo del tipo de profundidad que se pide, deberá inspeccionar los manuales en línea de
csh y tcsh. (man csh, man tcsh). Estos intérpretes tienen mucha más funcionalidad que el que se
pide, de forma que el manual que se requiere no deberá ser tan largo. No debe incluir bajo ningún
concepto consideraciones de implementación, ficheros fuente o código. Esto se incluirá en otros ficheros del proyecto. Este manual debe ser un Manual de Usuario no un Manual de Desarrollo.
3. El código fuente DEBE estar extensamente comentado y apropiadamente estructurado, permitiendo a sus colegas comprenderlo y darle mantenimiento al código. ¡El código comentado con propiedad y bien alineado es mucho más fácil de interpretar e interesa que la persona que pueda evaluar su código pueda entenderlo con facilidad sin necesidad de hacer gimnasia mental!
4. Los detalles sobre el envío del proyecto se proporcionarán con antelación a la fecha límite.
5. El envío del proyecto debe contener sólo ficheros fuente, incluyendo ficheros de cabecera, makefile (en letras minúsculas, por favor) el fichero readme (en letras minúsculas, por favor). No se debe
incluir ningún fichero ejecutable. El evaluador recompilará automáticamente su intérprete de mandatos a partir del código fuente. Si el código fuente no compila, no será calificado.
6. El makefile (en letras minúsculas, por favor) DEBE generar un fichero binario llamado myshell (en
letras minúsculas, por favor). Un ejemplo de makefile sería:
# Pepe Potamo, s1234567 – Proyecto 1 de SO
# CompLab1/01 tutor: Chema Peña
myshell: myshell.c utility.c myshell.h
gcc –Wall myshell.c utility.c –o myshell
El programa myshell se generará simplemente tecleando make en la línea de mandatos.
Nota: la cuarta línea del makefile de ejemplo DEBE comenzar por un tabulador.
7. En el ejemplo mostrado arriba los ficheros incluidos en el directorio de envío eran:
makefile
myshell.c
utility.c
myshell.h
readme
Envío
Es necesario un makefile. Todos los ficheros incluidos en su envío se copiarán al mismo directorio,
por tanto no incluya rutas en su makefile. El makefile debe incluir todas las dependencias para compilar el programa. Si se incluye una biblioteca, su makefile debe construir dicha biblioteca.
03-Capitulo 3
156
12/5/05
16:19
Página 156
Sistemas operativos. Aspectos internos y principios de diseño
Para dejar esto claro: no construya a mano ningún binario o fichero objeto. Todo lo que se requerirá serán sus ficheros fuente, un makefile, y el fichero readme. Verifique su proyecto, copiando
dichos ficheros a un directorio vacío y compilándolo completamente por medio del mandato make.
Usaremos un script que copia sus ficheros a un directorio de prueba, borre el fichero myshell previo,
así como todos los ficheros *.a, y/o los *.o, y ejecute un make, copie una serie de ficheros de pruebas al directorio, y compruebe su intérprete de mandatos por medio de una serie de ficheros por lotes
pasados por la entrada estándar (stdin) y por línea de mandatos. Si esta batería de pruebas falla debido a nombres erróneos, diferencias entre mayúsculas y minúsculas, versiones erróneas de código
que fallen al compilar o falta de ficheros, etc., la secuencia de evaluación se detendrá. En este caso, la
calificación obtenida será la de las pruebas pasadas completamente, la puntuación sobre el código
fuente y el manual.
Documentación solicitada
En primer lugar, su código fuente se comprobará y evaluará así como el manual readme. Los comentarios son completamente necesarios en su código fuente. El manual de usuario se podrá presentar
en el formato que se desee (siempre que se pueda visualizar por medio de un editor de texto estándar). Además, el manual debe contener suficiente información para que un usuario principiante de
UNIX pueda usar el intérprete. Por ejemplo, se debe explicar los conceptos de redirección de E/S, de
entorno de programa, y de ejecución en segundo plano (background). El manual DEBE llamarse readme (todo en minúsculas, por favor, SIN extensión .txt)
04-Capitulo 4
12/5/05
16:20
Página 157
CAPÍTULO
4
Hilos, SMP
y micronúcleos
4.1.
Procesos e hilos
4.2.
Multiprocesamiento simétrico
4.3.
Micronúcleos
4.4.
Gestión de hilos y SMP en Windows
4.5.
Gestión de hilos y SMP en Solaris
4.6.
Gestión de procesos e hilos en Linux
4.7.
Resumen
4.8.
Lecturas recomendadas
4.9
Términos clave, cuestiones de repaso y problemas
04-Capitulo 4
158
12/5/05
16:20
Página 158
Sistemas operativos. Aspectos internos y principios de diseño
Este capítulo analiza algunos conceptos avanzados relativos a la gestión de procesos que se pueden encontrar en los sistemas operativos modernos. En primer lugar, se muestra cómo el concepto de
proceso es más complejo y sutil de lo que se ha visto hasta este momento y, de hecho, contiene dos
conceptos diferentes y potencialmente independientes: uno relativo a la propiedad de recursos y otro
relativo a la ejecución. En muchos sistemas operativos esta distinción ha llevado al desarrollo de estructuras conocidas como hilos (threads). Después de analizar los hilos se pasa a ver el multiprocesamiento simétrico (Symmetric Multiprocessing, SMP). Con SMP el sistema operativo debe ser capaz
de planificar simultáneamente diferentes procesos en múltiples procesadores. Por último, se introduce
el concepto de micronúcleo, una forma útil de estructurar el sistema operativo para dar soporte al manejo de procesos y sus restantes tareas.
4.1. PROCESOS E HILOS
Hasta este momento se ha presentado el concepto de un proceso como poseedor de dos características:
• Propiedad de recursos. Un proceso incluye un espacio de direcciones virtuales para el manejo de la imagen del proceso; como ya se explicó en el Capítulo 3 la imagen de un proceso es la
colección de programa, datos, pila y atributos definidos en el bloque de control del proceso.
De vez en cuando a un proceso se le puede asignar control o propiedad de recursos tales como
la memoria principal, canales E/S, dispositivos E/S y archivos. El sistema operativo realiza la
función de protección para evitar interferencias no deseadas entre procesos en relación con los
recursos.
• Planificación/ejecución. La ejecución de un proceso sigue una ruta de ejecución (traza) a través de uno o más programas. Esta ejecución puede estar intercalada con ese u otros procesos.
De esta manera, un proceso tiene un estado de ejecución (Ejecutando, Listo, etc.) y una prioridad de activación y ésta es la entidad que se planifica y activa por el sistema operativo.
En la mayor parte de los sistemas operativos tradicionales, estas dos características son, realmente, la esencia de un proceso. Sin embargo, debe quedar muy claro que estas dos características son independientes y podrían ser tratadas como tales por el sistema operativo. Así se hace en diversos sistemas operativos, sobre todo en los desarrollados recientemente. Para distinguir estas dos
características, la unidad que se activa se suele denominar hilo (thread), o proceso ligero, mientras
que la unidad de propiedad de recursos se suele denominar proceso o tarea1.
MULTIHILO
Multihilo se refiere a la capacidad de un sistema operativo de dar soporte a múltiples hilos de ejecución en un solo proceso. El enfoque tradicional de un solo hilo de ejecución por proceso, en el que no
se identifica con el concepto de hilo, se conoce como estrategia monohilo. Las dos configuraciones
que se muestran en la parte izquierda de la Figura 4.1 son estrategia monohilo. Un ejemplo de siste-
1
Ni siquiera se puede mantener este grado de consistencia. En los sistemas operativos para mainframe de IBM, los conceptos
de espacio de direcciones y tarea, respectivamente, más o menos se corresponden a los conceptos de proceso e hilo que se describen
en esta sección. Además, en la literatura, el término proceso ligero se utiliza para (1) equivalente al término hilo, (2) un tipo particular de hilo conocido como hilo de nivel de núcleo, o (3) en el caso de Solaris, una entidad que asocia hilos de nivel de usuario con hilos de nivel de núcleo.
04-Capitulo 4
12/5/05
16:20
Página 159
Hilos, SMP y micronúcleos
Un proceso
Un hilo
Un proceso
Múltiples hilos
Múltiples procesos
Un hilo por proceso
Múltiples procesos
Múltiples hilos por proceso
159
= Traza de instrucción
Figura 4.1.
Hilos y procesos [ANDE97].
ma operativo que soporta un único proceso de usuario y un único hilo es el MS-DOS. Otros sistemas
operativos, como algunas variedades de UNIX, soportan múltiples procesos de usuario, pero sólo un
hilo por proceso. La parte derecha de la Figura 4.1 representa las estrategias multihilo. El entorno de
ejecución de Java es un ejemplo de sistema con un único proceso y múltiples hilos. Lo interesante en
esta sección es el uso de múltiples procesos, cada uno de los cuales soporta múltiples hilos. Este enfoque es el de Windows, Solaris, Mach, y OS/2 entre otros. En esta sección se ofrece una descripción
general del mecanismo multihilo; más adelante en este capítulo se discuten los detalles de los enfoques de Windows, Solaris y Linux.
En un entorno multihilo, un proceso se define como la unidad de asignación de recursos y una
unidad de protección. Se asocian con procesos los siguientes:
• Un espacio de direcciones virtuales que soporta la imagen del proceso.
• Acceso protegido a procesadores, otros procesos (para comunicación entre procesos), archivos
y recursos de E/S (dispositivos y canales).
Dentro de un proceso puede haber uno o más hilos, cada uno con:
• Un estado de ejecución por hilo (Ejecutando, Listo, etc.).
• Un contexto de hilo que se almacena cuando no está en ejecución; una forma de ver a un hilo
es como un contador de programa independiente dentro de un proceso.
• Una pila de ejecución.
• Por cada hilo, espacio de almacenamiento para variables locales.
04-Capitulo 4
160
12/5/05
16:20
Página 160
Sistemas operativos. Aspectos internos y principios de diseño
• Acceso a la memoria y recursos de su proceso, compartido con todos los hilos de su mismo
proceso.
La Figura 4.2 muestra la diferencia entre hilos y procesos desde el punto de vista de gestión de
procesos. En un modelo de proceso monohilo (es decir, no existe el concepto de hilo), la representación de un proceso incluye su bloque de control de proceso y el espacio de direcciones de usuario,
además de las pilas de usuario y núcleo para gestionar el comportamiento de las llamadas/retornos en
la ejecución de los procesos. Mientras el proceso está ejecutando, los registros del procesador se controlan por ese proceso y, cuando el proceso no se está ejecutando, se almacena el contenido de estos
registros. En un entorno multihilo, sigue habiendo un único bloque de control del proceso y un espacio de direcciones de usuario asociado al proceso, pero ahora hay varias pilas separadas para cada
hilo, así como un bloque de control para cada hilo que contiene los valores de los registros, la prioridad, y otra información relativa al estado del hilo.
De esta forma, todos los hilos de un proceso comparten el estado y los recursos de ese proceso, residen en el mismo espacio de direcciones y tienen acceso a los mismos datos. Cuando un hilo cambia determinados datos en memoria, otros hilos ven los resultados cuando acceden a estos datos. Si un hilo abre
un archivo con permisos de lectura, los demás hilos del mismo proceso pueden también leer ese archivo.
Los mayores beneficios de los hilos provienen de las consecuencias del rendimiento:
1. Lleva mucho menos tiempo crear un nuevo hilo en un proceso existente que crear un proceso
totalmente nuevo. Los estudios realizados por los que desarrollaron el sistema operativo Mach
muestran que la creación de un hilo es diez veces más rápida que la creación de un proceso en
UNIX [TEVA87].
2. Lleva menos tiempo finalizar un hilo que un proceso.
3. Lleva menos tiempo cambiar entre dos hilos dentro del mismo proceso.
4. Los hilos mejoran la eficiencia de la comunicación entre diferentes programas que están ejecutando. En la mayor parte de los sistemas operativos, la comunicación entre procesos inde-
Modelo de proceso con
un único hilo
Bloque de
control del
proceso
Pila de
usuario
Espacio de
direcciones
de usuario
Pila de
núcleo
Figura 4.2.
Modelo de proceso
multihilo
Hilo
Hilo
Hilo
Bloque de
control de
hilo
Bloque de
control de
hilo
Bloque de
control de
hilo
Bloque de
control del
proceso
Pila de
usuario
Pila de
usuario
Pila de
usuario
Espacio de
direcciones
de usuario
Pila de
núcleo
Pila de
núcleo
Pila de
núcleo
Modelos de proceso con un único hilo y multihilo.
04-Capitulo 4
12/5/05
16:20
Página 161
Hilos, SMP y micronúcleos
161
pendientes requiere la intervención del núcleo para proporcionar protección y los mecanismos necesarios de comunicación. Sin embargo, ya que los hilos dentro de un mismo proceso
comparten memoria y archivos, se pueden comunicar entre ellos sin necesidad de invocar al
núcleo.
De esta forma, si se desea implementar una aplicación o función como un conjunto de unidades
de ejecución relacionadas, es mucho más eficiente hacerlo con un conjunto de hilos que con un conjunto de procesos independientes.
Un ejemplo de una aplicación que podría hacer uso de hilos es un servidor de archivos. Cada vez
que llega una nueva petición de archivo, el programa de gestión de archivos puede ejecutar un nuevo
hilo. Ya que un servidor manejará muchas peticiones, se crearán y finalizarán muchos hilos en un corto periodo de tiempo. Si el servidor ejecuta en una máquina multiprocesador, pueden estar ejecutando
simultáneamente múltiples hilos del mismo proceso en diferentes procesadores. Además, ya que los
procesos o los hilos en un servidor de archivos deben compartir archivos de datos y, por tanto, coordinar sus acciones, es más rápido usar hilos y memoria compartida que usar procesos y paso de mensajes para esta coordinación.
A veces los hilos son también útiles en un solo procesador ya que ayudan a simplificar la estructura de programas que realizan varias funciones diferentes.
[LETW88] ofrece cuatro ejemplos de uso de hilos en un sistema de multiprocesamiento de un
solo usuario:
• Trabajo en primer plano y en segundo plano. Por ejemplo, en un programa de hoja de cálculo, un hilo podría mostrar menús y leer la entrada de usuario, mientras otro hilo ejecuta los
mandatos de usuario y actualiza la hoja de cálculo. Esta forma de trabajo a menudo incrementa la velocidad que se percibe de la aplicación, permitiendo al programa solicitar el siguiente
mandato antes de que el mandato anterior esté completado.
• Procesamiento asíncrono. Los elementos asíncronos de un programa se pueden implementar como hilos. Por ejemplo, se puede diseñar un procesador de textos con protección
contra un fallo de corriente que escriba el buffer de su memoria RAM a disco una vez por
minuto. Se puede crear un hilo cuyo único trabajo sea crear una copia de seguridad periódicamente y que se planifique directamente a través del sistema operativo; no se necesita
código adicional en el programa principal que proporcione control de tiempo o que coordine la entrada/salida.
• Velocidad de ejecución. Un proceso multihilo puede computar una serie de datos mientras
que lee los siguientes de un dispositivo. En un sistema multiprocesador pueden estar ejecutando simultáneamente múltiples hilos de un mismo proceso. De esta forma, aunque un hilo
pueda estar bloqueado por una operación de E/S mientras lee datos, otro hilo puede estar
ejecutando.
• Estructura modular de programas. Los programas que realizan diversas tareas o que tienen
varias fuentes y destinos de entrada y salida, se pueden diseñar e implementar más fácilmente
usando hilos.
En un sistema operativo que soporte hilos, la planificación y la activación se realizan a nivel de
hilo; de aquí que la mayor parte de la información de estado relativa a la ejecución se mantenga en
estructuras de datos a nivel de hilo. Existen, sin embargo, diversas acciones que afectan a todos los
hilos de un proceso y que el sistema operativo debe gestionar a nivel de proceso. Suspender un proceso implica expulsar el espacio de direcciones de un proceso de memoria principal para dejar hueco a
otro espacio de direcciones de otro proceso. Ya que todos los hilos de un proceso comparten el mismo
04-Capitulo 4
162
12/5/05
16:20
Página 162
Sistemas operativos. Aspectos internos y principios de diseño
espacio de direcciones, todos los hilos se suspenden al mismo tiempo. De forma similar, la finalización de un proceso finaliza todos los hilos de ese proceso.
FUNCIONALIDADES DE LOS HILOS
Los hilos, al igual que los procesos, tienen estados de ejecución y se pueden sincronizar entre ellos. A
continuación se analizan estos dos aspectos de las funcionalidades de los hilos.
Estados de los hilos. Igual que con los procesos, los principales estados de los hilos son: Ejecutando, Listo y Bloqueado. Generalmente, no tiene sentido aplicar estados de suspensión a un hilo, ya
que dichos estados son conceptos de nivel de proceso. En particular, si se expulsa un proceso, todos
sus hilos se deben expulsar porque comparten el espacio de direcciones del proceso.
Hay cuatro operaciones básicas relacionadas con los hilos que están asociadas con un cambio de
estado del hilo [ANDE97]:
• Creación. Cuando se crea un nuevo proceso, también se crea un hilo de dicho proceso. Posteriormente, un hilo del proceso puede crear otro hilo dentro del mismo proceso, proporcionando un puntero a las instrucciones y los argumentos para el nuevo hilo. Al nuevo hilo se le
proporciona su propio registro de contexto y espacio de pila y se coloca en la cola de Listos.
• Bloqueo. Cuando un hilo necesita esperar por un evento se bloquea, almacenando los registros
de usuario, contador de programa y punteros de pila. El procesador puede pasar a ejecutar otro
hilo en estado Listo, dentro del mismo proceso o en otro diferente.
• Desbloqueo. Cuando sucede el evento por el que el hilo está bloqueado, el hilo se pasa a la
cola de Listos.
• Finalización. Cuando se completa un hilo, se liberan su registro de contexto y pilas.
Un aspecto importante es si el bloqueo de un hilo implica el bloqueo del proceso completo. En
otras palabras, si se bloquea un hilo de un proceso, ¿esto impide la ejecución de otro hilo del mismo
proceso incluso si el otro hilo está en estado de Listo? Sin lugar a dudas, se pierde algo de la potencia
y flexibilidad de los hilos si el hilo bloqueado bloquea al proceso entero.
Volveremos a este tema a continuación cuando veamos los hilos a nivel de usuario y a nivel de núcleo, pero por el momento consideraremos los beneficios de rendimiento de los hilos que no bloquean
al proceso completo. La Figura 4.3 (basada en una de [KLEI96]) muestra un programa que realiza dos
llamadas a procedimiento remoto (RPC)2 a dos máquinas diferentes para poder combinar los resultados. En un programa de un solo hilo, los resultados se obtienen en secuencia, por lo que el programa
tiene que esperar a la respuesta de cada servidor por turnos. Reescribir el programa para utilizar un
hilo diferente para cada RPC, mejora sustancialmente la velocidad. Observar que si el programa ejecuta en un uniprocesador, las peticiones se deben generar en secuencia y los resultados se deben procesar
en secuencia; sin embargo, el programa espera concurrentemente las dos respuestas.
En un uniprocesador, la multiprogramación permite el intercalado de múltiples hilos con múltiples procesos. En el ejemplo de la Figura 4.4, se intercalan tres hilos de dos procesos en un procesa-
2
RPC es una técnica por la que dos programas, que pueden ejecutar en diferentes máquinas, interactúan utilizando la sintaxis
y la semántica de las llamadas a procedimiento. Tanto el programa llamante como el llamado se comportan como si el otro programa estuviera ejecutando en la misma máquina. Los RPC se suelen utilizar en las aplicaciones cliente/servidor y se analizan en el
Capítulo 13.
04-Capitulo 4
12/5/05
16:20
Página 163
Hilos, SMP y micronúcleos
163
Tiempo
Petición
RPC
Petición
RPC
Proceso 1
Servidor
Servidor
(a) RPC utilizando un único hilo
Servidor
Petición
RPC
Hilo A (Proceso 1)
Hilo B (Proceso 1)
Petición
RPC
Servidor
(b) RPC utilizando un hilo por servidor (en un uniprocesador)
Bloqueado, esperando respuesta RPC
Bloqueado, esperando el procesador, que está en uso por Hilo B
Ejecutando
Figura 4.3.
Llamadas a Procedimiento Remoto (RPC) utilizando hilos.
Tiempo
Petición Petición
E/S
completa
Finalizada la
rodaja de tiempo
Hilo A (Proceso 1)
Hilo B (Proceso 1)
Hilo C (Proceso 2)
Finalizada la
rodaja de tiempo
Proceso
creado
Bloqueado
Figura 4.4.
Listo
Ejecutando
Ejemplo multihilo en un uniprocesador.
dor. La ejecución pasa de un hilo a otro cuando se bloquea el hilo actualmente en ejecución o su porción de tiempo se agota3.
3
En este ejemplo, el hilo C comienza a ejecutar después de que el hilo A finaliza su rodaja de tiempo, aunque el hilo B esté
también listo para ejecutar. La elección entre B y C es una decisión de planificación, un tema que se aborda en la Parte Cuatro.
04-Capitulo 4
164
12/5/05
16:20
Página 164
Sistemas operativos. Aspectos internos y principios de diseño
Sincronización de hilos. Todos los hilos de un proceso comparten el mismo espacio de direcciones y otros recursos, como por ejemplo, los archivos abiertos. Cualquier alteración de un recurso por
cualquiera de los hilos, afecta al entorno del resto de los hilos del mismo proceso. Por tanto, es necesario sincronizar las actividades de los hilos para que no interfieran entre ellos o corrompan estructuras de datos. Por ejemplo, si dos hilos de modo simultáneo intentan añadir un elemento a una lista doblemente enlazada, se podría perder un elemento o la lista podría acabar malformada.
Los asuntos que surgen y las técnicas que se utilizan en la sincronización de los hilos son, en general, los mismos que en la sincronización de procesos. Estos aspectos y técnicas se tratan en los
Capítulos 5 y 6.
EJEMPLO—ADOBE PAGEMAKER
Un ejemplo del uso de los hilos es la aplicación Adobe PageMaker cuando ejecuta en un sistema
compartido. PageMaker es una herramienta de escritura, diseño y producción para entornos de escritorio. La estructura de hilos de PageMaker utilizada en OS/2, que se muestran la Figura 4.5
[KRON90], se eligió para optimizar la respuesta de la aplicación (se pueden encontrar estructuras de
hilos similares en otros sistemas operativos). Siempre hay tres hilos activos: un hilo para manejar
eventos, un hilo para repintar la pantalla y un hilo de servicio.
Generalmente, OS/2 es menos sensible en la gestión de ventanas si cualquier mensaje de entrada
requiere demasiado proceso. En OS/2 se recomienda que ningún mensaje requiera más de 0,1 segundos de tiempo de procesamiento. Por ejemplo, llamar a una subrutina para imprimir una página
mientras se procesa un mandato de impresión podría impedir al sistema el envío de más mensajes a
Hi
ser lo de
vic
io
Ini
cia
Im
liz
aci
ón
po
rta
r
jo
aut
de om
tex átic
o
Im to
pre
sió
n
Flu
HiEv
lo en
mat-h
neajn
Hi
addoli
lo
rndg t
de
e here
rep
veand
int
tos
ado
de
pan
tal
la
Figura 4.5.
Estructura de hilos para Adobe PageMaker.
04-Capitulo 4
12/5/05
16:20
Página 165
Hilos, SMP y micronúcleos
165
otras aplicaciones, degradando el rendimiento. Para cumplir este criterio, las operaciones de usuario
que requieren mucho tiempo —imprimir, importar datos, y descargar texto— son realizadas por un
hilo de servicio. En su mayor parte, la inicialización del programa también se lleva a cabo por el
hilo de servicio, y utiliza el tiempo inactivo mientras el usuario invoca el diálogo de creación de un
nuevo documento o abre un documento existente. Un hilo independiente espera nuevos mensajes de
evento.
La sincronización del hilo de servicio y del hilo que gestiona los eventos es complicada porque
un usuario puede continuar escribiendo o moviendo el ratón (lo cual activa el hilo de manejo de eventos), mientras el hilo de servicio está todavía ocupado. Si sucede este conflicto, PageMaker filtra estos mensajes y sólo acepta algunos básicos, tales como el redimensionamiento de la ventana.
El hilo de servicio manda un mensaje al hilo de manejo de eventos para indicar que ha finalizado
su tarea. Hasta que esto ocurre, se restringe la actividad de usuario en PageMaker. El programa indica
esta situación deshabilitando elementos de los menús y mostrando un cursor «ocupado». El usuario
tiene la libertad de cambiar a otra aplicación; cuando el cursor se mueve a la nueva ventana, cambiará
al cursor apropiado para esa aplicación.
La función de repintado de pantalla se maneja por un hilo diferente. Se realiza así por dos
razones:
1. PageMaker no limita el número de objetos que aparecen en una página; de esta forma, procesar una petición de repintado puede exceder fácilmente la recomendación de 0,1 segundos.
2. Utilizar un hilo independiente permite al usuario cancelar el repintado. En este caso, cuando el
usuario reescala una página, el repintado se puede realizar de forma inmediata. El programa es
menos sensible si completa una visualización antigua antes de comenzar con una visualización
en la nueva escala.
También es posible el desplazamiento dinámico línea a línea —repintar la pantalla a medida que
el usuario mueve el indicador de desplazamiento—. El hilo de manejo de eventos monitoriza la barra
de desplazamiento y repinta las reglas de los márgenes (haciéndolo de forma rápida para dar al usuario la posición actual de forma inmediata). Mientras tanto, el hilo de repintado de pantalla está constantemente intentando repintar la página.
La implementación del repintado dinámico sin el uso de múltiples hilos genera una gran sobrecarga en la aplicación. Múltiples hilos permiten separar actividades concurrentes de forma más natural en el código.
HILOS DE NIVEL DE USUARIO Y DE NIVEL DE NÚCLEO
Existen dos amplias categorías de implementación de hilos: hilos de nivel de usuario (user-level threads, ULT) e hilos de nivel de núcleo (kernel-level threads, KLT)4. Los últimos son también conocidos
en la literatura como hilos soportados por el núcleo (kernel-supported threads) o procesos ligeros
(lightweight processes).
Hilos de nivel de usuario. En un entorno ULT puro, la aplicación gestiona todo el trabajo de los
hilos y el núcleo no es consciente de la existencia de los mismos. La Figura 4.6a muestra el enfoque
4
Los acrónimos ULT y KLT son exclusivos de este libro y se introducen por concisión.
04-Capitulo 4
166
12/5/05
16:20
Página 166
Sistemas operativos. Aspectos internos y principios de diseño
Biblioteca
de hilos
Espacio de
usuario
Espacio de
usuario
Espacio
de núcleo
Espacio
de núcleo
Biblioteca
de hilos
Espacio de
usuario
Espacio
de núcleo
P
P
P
(a) Nivel de usuario puro
Hilo de nivel de usuario
(b) Nivel de núcleo puro
Hilo de nivel de núcleo
Figura 4.6.
P
P
(c) Combinado d
Proceso
Hilos de nivel de usuario y de nivel de núcleo.
ULT. Cualquier aplicación puede programarse para ser multihilo a través del uso de una biblioteca de
hilos, que es un paquete de rutinas para la gestión de ULT. La biblioteca de hilos contiene código para
la creación y destrucción de hilos, para paso de mensajes y datos entre los hilos, para planificar la ejecución de los hilos, y para guardar y restaurar el contexto de los hilos.
Por defecto, una aplicación comienza con un solo hilo y ejecutando en ese hilo. Esta aplicación y
su hilo se localizan en un solo proceso gestionado por el núcleo. En cualquier momento que la aplicación esté ejecutando (el proceso está en estado Ejecutando), la aplicación puede crear un nuevo hilo a
ejecutar dentro del mismo proceso. La creación se realiza llamando a la utilidad de creación en la biblioteca de hilos. Se pasa el control a esta utilidad a través de una llamada a procedimiento. La biblioteca de hilos crea una estructura de datos para el nuevo hilo y luego pasa el control a uno de los hilos
de ese proceso que esté en estado listo, utilizando algún algoritmo de planificación. Cuando se pasa el
control a la biblioteca, se almacena el contexto del hilo actual, y cuando se pasa el control de la biblioteca al hilo, se recupera el contexto de ese hilo. El contexto tiene esencialmente el contenido de
los registros del usuario, el contador que programa, y los punteros de pila.
Toda la actividad descrita en el párrafo anterior tiene lugar en el espacio de usuario y dentro de
un solo proceso. El núcleo no es consciente de esta actividad. El núcleo continúa planificando el
proceso como una unidad y asigna al proceso un único estado (Listo, Ejecutando, Bloqueado, etc.).
Los siguientes ejemplos deberían aclarar la relación entre planificación de hilos y planificación de
procesos. Suponer que el proceso B está ejecutando en su hilo 2; en la Figura 4.7a se muestran los
estados del proceso y de dos ULT que son parte del proceso. Cada una de las siguientes es una posible situación:
1. La aplicación ejecutando en el hilo 2 hace una llamada al sistema que bloquea a B. Por ejemplo, se realiza una llamada de E/S. Esto hace que se pase el control al núcleo. El núcleo llama
a la acción de E/S, sitúa al proceso B en estado de Bloqueado, y cambia a otro proceso. Mientras tanto, de acuerdo a la estructura de datos conservada en la biblioteca de los hilos, el hilo 2
del proceso B está todavía en estado Ejecutando. Es importante darse cuenta de que el hilo 2
no está ejecutando realmente en el sentido de estar corriendo en el procesador; pero se percibe
como estado Ejecutando en la biblioteca de los hilos. Los diagramas de estado correspondientes se muestran en la Figura 4.7b.
Listo
Bloqueado
Bloqueado
Bloqueado
Ejecutando
Proceso B
Listo
Hilo 2
Ejecutando
Bloqueado
Ejecutando
(d)
(b)
Listo
Listo
Listo
Listo
Bloqueado
Ejecutando
Bloqueado
Hilo 2
Ejecutando
Proceso B
Listo
Ejecutando
Bloqueado
Hilo 2
Ejecutando
Bloqueado
Ejecutando
Bloqueado
Hilo 1
Listo
Proceso B
Ejecutando
Bloqueado
Hilo 1
Ejemplos de relaciones entre los estados de los hilos de nivel de usuario y los estados de proceso.
Listo
Listo
Ejecutando
Proceso B
Ejecutando
Bloqueado
Figura 4.7.
Listo
Hilo 1
Bloqueado
Ejecutando
Hilo 2
16:20
(c)
Listo
Hilo 1
12/5/05
(a)
04-Capitulo 4
Página 167
Hilos, SMP y micronúcleos
167
04-Capitulo 4
168
12/5/05
16:20
Página 168
Sistemas operativos. Aspectos internos y principios de diseño
2. Una interrupción de reloj pasa el control al núcleo y decide que el proceso actual en ejecución
(B) ha finalizado su porción de tiempo. El núcleo pasa al proceso B a estado de Listo y cambia
a otro proceso. Mientras tanto, de acuerdo a la estructura de datos conservada en la biblioteca
de los hilos, el hilo 2 del proceso B está todavía en estado de Ejecución. Los diagramas de estado correspondientes se muestran en la Figura 4.7c.
3. El hilo 2 llega a un punto donde necesita una acción del hilo 1 o del proceso B. El hilo 2 entra
en estado de Bloqueado y el hilo 1 pasa de Listo a Ejecutando. El proceso en sí continúa en estado Ejecutando. Los diagramas de estado correspondientes se muestran en la Figura 4.7d.
En los casos 1 y 2 (Figuras 4.7b y 4.7c), cuando el núcleo devuelve el control al proceso B, la
ejecución se reanuda en el hilo 2. También hay que advertir que un proceso puede interrumpirse, bien
por finalizar su porción de tiempo o bien por ser expulsado por un proceso de mayor prioridad, mientras está ejecutando código de la biblioteca de los hilos. De esta forma, un proceso puede estar en medio de una transición de un hilo a otro hilo cuando se interrumpe. Cuando se reanuda el proceso, la
ejecución continúa con la biblioteca de los hilos, que completa el cambio de hilo y pasa el control al
nuevo hilo del proceso.
El uso de ULT en lugar de KLT, presenta las siguientes ventajas:
1. El cambio de hilo no requiere privilegios de modo núcleo porque todas las estructuras de datos
de gestión de hilos están en el espacio de direcciones de usuario de un solo proceso. Por consiguiente, el proceso no cambia a modo núcleo para realizar la gestión de hilos. Esto ahorra la
sobrecarga de dos cambios de modo (usuario a núcleo; núcleo a usuario).
2. La planificación puede especificarse por parte de la aplicación. Una aplicación se puede beneficiar de un simple algoritmo de planificación cíclico, mientras que otra se podría beneficiar
de un algoritmo de planificación basado en prioridades. El algoritmo de planificación se puede
hacer a medida sin tocar el planificador del sistema operativo.
3. Los ULT pueden ejecutar en cualquier sistema operativo. No se necesita ningún cambio en el
nuevo núcleo para dar soporte a los ULT. La biblioteca de los hilos es un conjunto de utilidades a nivel de aplicación que comparten todas las aplicaciones.
Hay dos desventajas de los ULT en comparación con los KLT:
1. En un sistema operativo típico muchas llamadas al sistema son bloqueantes. Como resultado,
cuando un ULT realiza una llamada al sistema, no sólo se bloquea ese hilo, sino que se bloquean todos los hilos del proceso.
2. En una estrategia pura ULT, una aplicación multihilo no puede sacar ventaja del multiproceso.
El núcleo asigna el proceso a un solo procesador al mismo tiempo. Por consiguiente, en un determinado momento sólo puede ejecutar un hilo del proceso. En efecto, tenemos multiprogramación a nivel de aplicación con un solo proceso. Aunque esta multiprogramación puede dar
lugar a una mejora significativa de la velocidad de la aplicación, hay aplicaciones que se podrían beneficiar de la habilidad de ejecutar porciones de código de forma concurrente.
Hay formas de afrontar estos dos problemas. Por ejemplo, ambos problemas pueden superarse escribiendo una aplicación de múltiples procesos en lugar de múltiples hilos. Pero este enfoque elimina
la principal ventaja de los hilos: cada cambio es un cambio de proceso en lugar de un cambio de hilo,
lo que genera una gran sobrecarga.
Otra forma de solucionar el problema de hilos que se bloquean es una técnica denominada jacketing (revestimiento). El objetivo de esta técnica es convertir una llamada al sistema bloqueante en una
04-Capitulo 4
12/5/05
16:20
Página 169
Hilos, SMP y micronúcleos
169
llamada al sistema no bloqueante. Por ejemplo, en lugar de llamar directamente a una rutina del sistema de E/S, un hilo puede llamar a una rutina jacket de E/S a nivel de aplicación. Con esta rutina jacket, el código verifica si el dispositivo de E/S está ocupado. Si lo está, el hilo entra en estado Bloqueado y pasa el control (a través de la biblioteca de hilos) a otro hilo. Cuando este hilo recupera de
nuevo el control, chequea de nuevo el dispositivo de E/S.
Hilos a nivel de núcleo. En un entorno KLT puro, el núcleo gestiona todo el trabajo de gestión de
hilos. No hay código de gestión de hilos en la aplicación, solamente una interfaz de programación
de aplicación (API) para acceder a las utilidades de hilos del núcleo. Windows es un ejemplo de este
enfoque.
La Figura 4.6b representa el entorno KLT puro. Cualquier aplicación puede programarse para ser
multihilo. Todos los hilos de una aplicación se mantienen en un solo proceso. El núcleo mantiene información de contexto del proceso como una entidad y de los hilos individuales del proceso. La planificación realizada por el núcleo se realiza a nivel de hilo. Este enfoque resuelve los dos principales
inconvenientes del enfoque ULT. Primero, el núcleo puede planificar simultáneamente múltiples hilos
de un solo proceso en múltiples procesadores. Segundo, si se bloquea un hilo de un proceso, el núcleo puede planificar otro hilo del mismo proceso. Otra ventaja del enfoque KLT es que las rutinas
del núcleo pueden ser en sí mismas multihilo.
La principal desventaja del enfoque KLT en comparación con el enfoque ULT es que la transferencia de control de un hilo a otro del mismo proceso requiere un cambio de modo al núcleo. Para
mostrar estas diferencias, la Tabla 4.1 muestra los resultados de las medidas tomadas en un uniprocesador VAX ejecutando un sistema operativo tipo UNIX. Las dos medidas son las siguientes: Crear un
Proceso Nulo, el tiempo para crear, planificar, ejecutar, y completar un proceso/hilo que llama al procedimiento nulo (es decir, la sobrecarga de crear un proceso/hilo); y Señalizar-Esperar, el tiempo que
le lleva a un proceso/hilo señalizar a un proceso/hilo que está esperando y a continuación esperar una
condición (es decir, la sobrecarga de sincronizar dos procesos/hilos).
Tabla 4.1.
Latencia de las Operaciones en Hilos y Procesos (mS) [ANDE92].
Operación
Hilos a nivel de usuario
Hilos a nivel de núcleo
Procesos
Crear Proceso Nulo
34
948
11.300
Señalizar-Esperar
37
441
1.840
Como se puede apreciar puede haber más de un orden de diferencia entre ULT y KLT y de forma
similar entre KLT y procesos.
De esta forma, mientras que hay una ganancia significativa entre el uso de multihilos KLT en comparación con procesos de un solo hilo, hay una ganancia significativa adicional por el uso de ULT. Sin
embargo, depende de la naturaleza de la aplicación involucrada que nos podamos beneficiar o no de la
ganancia adicional. Si la mayor parte de los cambios de hilo en una aplicación requieren acceso al
modo núcleo, el esquema basado en ULT no sería tan superior al esquema basado en KLT.
Enfoques combinados. Algunos sistemas operativos proporcionan utilidades combinadas
ULT/KLT (Figura 4.6c). Solaris es el principal ejemplo de esto. En un sistema combinado, la creación
de hilos se realiza por completo en el espacio de usuario, como la mayor parte de la planificación y
sincronización de hilos dentro de una aplicación. Los múltiples ULT de una aplicación se asocian en
un número (menor o igual) de KLT. El programador debe ajustar el número de KLT para una máquina
y aplicación en particular para lograr los mejores resultados posibles.
04-Capitulo 4
170
12/5/05
16:20
Página 170
Sistemas operativos. Aspectos internos y principios de diseño
En los enfoques combinados, múltiples hilos de la misma aplicación pueden ejecutar en paralelo
en múltiples procesadores, y una llamada al sistema bloqueante no necesita bloquear el proceso completo. Si el sistema está bien diseñado, este enfoque debería combinar las ventajas de los enfoques
puros ULT y KLT, minimizando las desventajas.
OTRAS CONFIGURACIONES
Como hemos comentado, los conceptos de asignación de recursos y unidades de activación han sido
tradicionalmente relacionados con el concepto de proceso; esto es, como una relación 1:1 entre hilos
y procesos. Recientemente, ha habido mucho interés en proporcionar múltiples hilos dentro de un
solo proceso, lo que es una relación muchos-a-uno. Sin embargo, como muestra la Tabla 4.2, las otras
dos combinaciones han sido también investigadas, y se denominan relación muchos-a-muchos y relación uno-a-muchos.
Tabla 4.2.
Hilos:Procesos
Relación Entre Hilos y Procesos.
Descripción
Sistemas de Ejemplo
1:1
Cada hilo de ejecución es un
único proceso con su propio
espacio de direcciones
y recursos.
Implementaciones UNIX
tradicionales.
M:1
Un proceso define un
espacio de direcciones y
pertenencia dinámica
de recursos. Se pueden crear
y ejecutar múltiples hilos
en ese proceso.
Windows NT, Solaris, Linux,
OS/2, OS/390, MACH.
1:M
Un hilo puede migrar de un
entorno de proceso a otro.
Esto permite a los hilos
moverse fácilmente entre
distintos sistemas.
Ra (Clouds), Emerald.
M:N
Combina atributos
de M:1 y casos 1:M.
TRIX.
Relación muchos-a-muchos. La idea de tener una relación muchos-a-muchos entre hilos y procesos ha sido explorada en el sistema operativo experimental TRIX [SIEB83,WARD80]. En TRIX,
existen los conceptos de dominio de hilo. Un dominio es una entidad estática, que consiste en un
espacio de direcciones y «puertos» a través de los cuales se pueden enviar y recibir mensajes. Un
hilo es una ruta de ejecución, con una pila de ejecución, estado del procesador e información de
planificación.
Al igual que en el enfoque multihilo visto hasta el momento, múltiples hilos podrían ejecutar en
un solo dominio, proporcionando las ventajas discutidas anteriormente. Sin embargo, también es posible realizar la actividad de usuario o ejecutar aplicaciones en múltiples dominios. En este caso hay
un hilo que se puede mover entre dominios.
El uso de un solo hilo en múltiples dominios está motivado por el deseo de proporcionar herramientas de estructuración al programador. Por ejemplo, considere un programa que hace uso de un
04-Capitulo 4
12/5/05
16:20
Página 171
Hilos, SMP y micronúcleos
171
subprograma de E/S. En un entorno multiprogramado que permite procesos creados por los usuarios,
el programa principal podría generar un nuevo proceso para el manejo de la E/S y continuar ejecutando. Sin embargo, si el futuro progreso del programa principal depende del funcionamiento de la E/S,
entonces el programa principal tendrá que esperar a la finalización del otro programa de E/S. Hay varias formas de implementar esta aplicación:
1. El programa completo puede implementarse como un solo proceso. Esta solución es razonable
y directa. Sin embargo, existen desventajas relativas a la gestión de memoria. El proceso completo podría requerir una gran cantidad de memoria para ejecutar de forma eficiente, mientras
que el subprograma de E/S requiere relativamente poco espacio de direccionamiento para almacenar la E/S y para manejar su poca cantidad de código fuente. Ya que el programa de E/S
ejecuta en el espacio de direcciones del programa mayor, puede suceder que el proceso completo deba permanecer en la memoria principal durante el funcionamiento de la E/S o que la
operación de E/S esté sujeta a intercambio. Este efecto sobre la gestión de memoria también
existiría si el programa principal y el subprograma hubieran sido implementados como dos hilos en el mismo espacio de direcciones.
2. El programa principal y el subprograma de E/S podrían implementarse como dos procesos independientes. Este enfoque presenta la sobrecarga de la creación del proceso subordinado. Si
la actividad de E/S es frecuente, se deberá dejar vivo al proceso subordinado, con el consiguiente consumo de recursos, o se deberá crear y destruir frecuentemente el subprograma, con
la consiguiente pérdida de eficiencia.
3. Tratar al programa principal y al subprograma de E/S como una sola actividad que se puede
implementar en un solo hilo. Sin embargo, se podría crear un espacio de direcciones (dominio)
para el programa principal y otro para el subprograma de E/S. De esta forma, el hilo se podría
mover entre los dos espacios de direcciones a lo largo de la ejecución. El sistema operativo
puede gestionar los dos espacios de direcciones de forma independiente, y no se genera una
sobrecarga de creación de procesos. Adicionalmente, el espacio de direcciones utilizado por el
subprograma de E/S también podría ser compartido por otros programas de E/S.
Las experiencias de los desarrolladores de TRIX indican que la tercera opción tiene mérito y que
podría ser la más eficiente para algunas aplicaciones.
Relación uno-a-muchos. En el campo de los sistemas operativos distribuidos (diseñados para
controlar sistemas de computadores distribuidos), ha habido interés en el concepto de un hilo como
una entidad que se puede mover entre espacios de direcciones5. Un ejemplo importante de esta investigación es el sistema operativo Clouds, y especialmente su núcleo, conocido como Ra [DASG92].
Otro ejemplo es el sistema Emerald [STEE95].
Desde el punto de vista del usuario, un hilo en Clouds es una unidad de actividad. Un proceso es
un espacio de direcciones virtual con un bloque de control de proceso asociado. Una vez creado, un
hilo comienza ejecutando en un proceso a través de la invocación de un punto de entrada de un programa en ese proceso. Los hilos se pueden mover de un espacio de direcciones a otro, incluso fuera
de los límites de la máquina (es decir, moverse de un computador a otro). Según se mueve el hilo,
debe llevarse determinada información con él, tal como el controlador de terminal, los parámetros
globales y las guías de planificación (por ejemplo, prioridad).
5
El movimiento de procesos o hilos entre espacios de direcciones, o migración de hilos entre máquinas diferentes, se ha convertido en un tema de interés en los últimos años. Este tema se analiza en el Capítulo 14.
04-Capitulo 4
172
12/5/05
16:20
Página 172
Sistemas operativos. Aspectos internos y principios de diseño
El enfoque de Clouds proporciona una forma eficiente de aislar a los usuarios y programadores
de los detalles del entorno distribuido. La actividad del usuario puede ser representada como un solo
hilo, y el movimiento de ese hilo entre máquinas puede ser gestionado por el sistema operativo gracias a información relativa al sistema, tal como la necesidad de acceder a un recurso remoto o el equilibrado de carga.
4.2. MULTIPROCESAMIENTO SIMÉTRICO
Tradicionalmente, el computador ha sido visto como una máquina secuencial. La mayor parte de los
lenguajes de programación requieren que el programador especifique algoritmos como una secuencia
de instrucciones. Un procesador ejecuta programas a través de la ejecución de instrucciones máquina
en secuencia y de una en una. Cada instrucción se ejecuta como una secuencia de operaciones (ir a
buscar la instrucción, ir a buscar los operandos, realizar la operación, almacenar resultados).
Esta visión del computador nunca ha sido totalmente cierta. A nivel de micro-operación, se generan múltiples señales de control al mismo tiempo. El pipeline de instrucciones, al menos en lo relativo a la búsqueda y ejecución de operaciones, ha estado presente durante mucho tiempo. Éstos son dos
ejemplos de realización de funciones en paralelo.
A medida que ha evolucionado la tecnología de los computadores y el coste del hardware ha descendido, los diseñadores han visto cada vez más oportunidades para el paralelismo, normalmente
para mejorar el rendimiento y, en algunos casos, para mejorar la fiabilidad. En este libro, examinamos los dos enfoques más populares para proporcionar paralelismo a través de la réplica de procesadores: multiprocesamiento simétricos (SMP) y clusters. Los SMP se abordan en esta sección; los
clusters se examinan en la Parte Seis.
ARQUITECTURA SMP
Es útil ver donde encaja la arquitectura SMP dentro de las categorías de procesamiento paralelo. La
forma más común de categorizar estos sistemas es la taxonomía de sistemas de procesamiento paralelo introducida por Flynn [FLYN72]. Flynn propone las siguientes categorías de sistemas de
computadores:
• Única instrucción, único flujo de datos – Single instruction single data (SISD) stream. Un
solo procesador ejecuta una única instrucción que opera sobre datos almacenados en una sola
memoria.
• Única instrucción, múltiples flujos de datos – Single instruction multiple data (SIMD)
stream. Una única instrucción de máquina controla la ejecución simultánea de un número
de elementos de proceso. Cada elemento de proceso tiene una memoria de datos asociada,
de forma que cada instrucción se ejecuta en un conjunto de datos diferente a través de los
diferentes procesadores. Los procesadores vectoriales y matriciales entran dentro de esta
categoría.
• Múltiples instrucciones, único flujo de datos – Multiple instruction single data (MISD)
stream. Se transmite una secuencia de datos a un conjunto de procesadores, cada uno de
los cuales ejecuta una secuencia de instrucciones diferente. Esta estructura nunca se ha implementado.
• Múltiples instrucciones, múltiples flujos de datos – Multiple instruction multiple data
(MIMD) stream. Un conjunto de procesadores ejecuta simultáneamente diferentes secuencias
de instrucciones en diferentes conjuntos de datos.
04-Capitulo 4
12/5/05
16:20
Página 173
Hilos, SMP y micronúcleos
173
Con la organización MIMD, los procesadores son de propósito general, porque deben ser capaces
de procesar todas las instrucciones necesarias para realizar las transformaciones de datos apropiadas.
MIMD se puede subdividir por la forma en que se comunican los procesadores (Figura 4.8). Si cada
procesador tiene una memoria dedicada, cada elemento de proceso es en sí un computador. La comunicación entre los computadores se puede realizar a través de rutas prefijadas o bien a través de redes.
Este sistema es conocido como un cluster, o multicomputador. Si los procesadores comparten una
memoria común, entonces cada procesador accede a los programas y datos almacenados en la memoria compartida, y los procesadores se comunican entre sí a través de dicha memoria; este sistema se
conoce como multiprocesador de memoria compartida.
Una clasificación general de los multiprocesadores de memoria compartida se basa en la forma
de asignar procesos a los procesadores. Los dos enfoques fundamentales son maestro/esclavo y simétrico. Con la arquitectura maestro/esclavo, el núcleo del sistema operativo siempre ejecuta en un determinado procesador. El resto de los procesadores sólo podrán ejecutar programas de usuario y, a lo
mejor, utilidades del sistema operativo. El maestro es responsable de la planificación de procesos e
hilos. Una vez que un proceso/hilo está activado, si el esclavo necesita servicios (por ejemplo, una
llamada de E/S), debe enviar una petición al maestro y esperar a que se realice el servicio. Este enfoque es bastante sencillo y requiere pocas mejoras respecto a un sistema operativo multiprogramado
uniprocesador. La resolución de conflictos se simplifica porque un procesador tiene el control de toda
la memoria y recursos de E/S. Las desventajas de este enfoque son las siguientes:
• Un fallo en el maestro echa abajo todo el sistema.
• El maestro puede convertirse en un cuello de botella desde el punto de vista del rendimiento,
ya que es el único responsable de hacer toda la planificación y gestión de procesos.
En un multiprocesador simétrico (Symmetric Multiprocessor, SMP), el núcleo puede ejecutar
en cualquier procesador, y normalmente cada procesador realiza su propia planificación del conjunto
disponible de procesos e hilos. El núcleo puede construirse como múltiples procesos o múltiples hilos, permitiéndose la ejecución de partes del núcleo en paralelo. El enfoque SMP complica al sistema
operativo, ya que debe asegurar que dos procesadores no seleccionan un mismo proceso y que no se
pierde ningún proceso de la cola. Se deben emplear técnicas para resolver y sincronizar el uso de los
recursos.
Procesadores paralelos
SIMD
(única instrucción
múltiples flujos de datos)
MIMD
(múltiples instrucciones
múltiples flujos de datos)
Memoria compartida
(fuertemente acoplados)
Maestro/esclavo
Figura 4.8.
Multiprocesadores
simétricos
(SMP)
Memoria distribuida
(débilmente acoplados)
Clusters
Arquitectura de procesadores paralelos.
04-Capitulo 4
174
12/5/05
16:20
Página 174
Sistemas operativos. Aspectos internos y principios de diseño
El diseño de SMP y clusters es complejo, e involucra temas relativos a la organización física, estructuras de interconexión, comunicación entre procesadores, diseño del sistema operativo y técnicas
de aplicaciones software. Nuestra preocupación aquí, y más adelante cuando hablemos de los clústeres (Capítulo 13), se centra en los aspectos de diseño del sistema operativo, aunque en ambos casos
veremos brevemente temas de organización.
ORGANIZACIÓN SMP
La Figura 4.9 muestra la organización general de un SMP. Existen múltiples procesadores, cada uno
de los cuales contiene su propia unidad de control, unidad aritmético-lógica y registros. Cada procesador tiene acceso a una memoria principal compartida y dispositivos de E/S a través de algún mecanismo de interconexión; el bus compartido es común a todos los procesadores. Los procesadores se
pueden comunicar entre sí a través de la memoria (mensajes e información de estado dejados en espacios de memoria compartidos). Los procesadores han de poder intercambiarse señales directamente.
A menudo la memoria está organizada de tal manera que se pueden realizar múltiples accesos simultáneos a bloques separados.
En máquinas modernas, los procesadores suelen tener al menos un nivel de memoria cache, que
es privada para el procesador. El uso de esta cache introduce nuevas consideraciones de diseño. Debido a que la cache local contiene la imagen de una porción de memoria principal, si se altera una palabra en una cache, se podría invalidar una palabra en el resto de las caches. Para prevenir esto, el resto
de los procesadores deben ser alertados de que se ha llevado a cabo una actualización. Este problema
se conoce como el problema de coherencia de caches y se suele solucionar con técnicas hardware
más que con el sistema operativo6.
CONSIDERACIONES DE DISEÑO DE SISTEMAS OPERATIVOS MULTIPROCESADOR
Un sistema operativo SMP gestiona los procesadores y otros recursos del computador, de manera que
el usuario puede ver al sistema de la misma forma que si fuera un sistema uniprocesador multiprogramado. Un usuario puede desarrollar aplicaciones que utilicen múltiples procesos o múltiples hilos
dentro de procesos sin preocuparse de si estará disponible un único procesador o múltiples procesadores. De esta forma, un sistema operativo multiprocesador debe proporcionar toda la funcionalidad
de un sistema multiprogramado, además de características adicionales para adecuarse a múltiples procesadores. Las principales claves de diseño incluyen las siguientes características:
• Procesos o hilos simultáneos concurrentes. Las rutinas del núcleo necesitan ser reentrantes
para permitir que varios procesadores ejecuten el mismo código del núcleo simultáneamente.
Debido a que múltiples procesadores pueden ejecutar la misma o diferentes partes del código
del núcleo, las tablas y la gestión de las estructuras del núcleo deben ser gestionas apropiadamente para impedir interbloqueos u operaciones inválidas.
• Planificación. La planificación se puede realizar por cualquier procesador, por lo que se deben evitar los conflictos. Si se utiliza multihilo a nivel de núcleo, existe la posibilidad de planificar múltiples hilos del mismo proceso simultáneamente en múltiples procesadores. En el
Capítulo 10 se examina la planificación multiprocesador.
6
En [STAL03] se proporciona una descripción de esquemas de coherencia de cache basados en el hardware.
04-Capitulo 4
12/5/05
16:20
Página 175
Hilos, SMP y micronúcleos
Procesador
Procesador
L1 cache
Procesador
L1 cache
L2 cache
175
L1 cache
L2 cache
L2 cache
Bus del sistema
Memoria
principal
Subsistema
de E/S
Adaptador
de E/S
Adaptador
de E/S
Adaptador
de E/S
Figura 4.9.
Organización de los multiprocesadores simétricos.
• Sincronización. Con múltiples procesos activos, que pueden acceder a espacios de direcciones compartidas o recursos compartidos de E/S, se debe tener cuidado en proporcionar una
sincronización eficaz. La sincronización es un servicio que fuerza la exclusión mutua y el orden de los eventos. Un mecanismo común de sincronización que se utiliza en los sistemas operativos multiprocesador son los cerrojos, descritos en el Capítulo 5.
• Gestión de memoria. La gestión de memoria en un multiprocesador debe tratar con todos
los aspectos encontrados en las máquinas uniprocesador, que se verán en la Parte Tres. Además, el sistema operativo necesita explotar el paralelismo hardware existente, como las memorias multipuerto, para lograr el mejor rendimiento. Los mecanismos de paginación de los
diferentes procesadores deben estar coordinados para asegurar la consistencia cuando varios
procesadores comparten una página o segmento y para decidir sobre el reemplazo de una
página.
• Fiabilidad y tolerancia a fallos. El sistema operativo no se debe degradar en caso de fallo de
un procesador. El planificador y otras partes del sistema operativo deben darse cuenta de la
pérdida de un procesador y reestructurar las tablas de gestión apropiadamente.
Debido a que los aspectos de diseño de un sistema operativo multiprocesador suelen ser extensiones a soluciones de problemas de diseño de uniprocesadores multiprogramados, no los trataremos por
separado. En su lugar, los aspectos específicos a los temas multiprocesador se tratarán en su contexto
apropiado a lo largo del libro.
04-Capitulo 4
176
12/5/05
16:20
Página 176
Sistemas operativos. Aspectos internos y principios de diseño
4.3. MICRONÚCLEOS
Un concepto que últimamente está recibiendo mucha atención es el de micronúcleo. Un micronúcleo
es la pequeña parte central de un sistema operativo que proporciona las bases para extensiones modulares. Sin embargo, el término es algo confuso, y hay varias cuestiones relacionadas con los micronúcleos con respuestas distintas por parte de diferentes equipos de diseño de sistemas operativos. Estas
cuestiones incluyen, cómo de pequeño debe ser un núcleo para denominarse micronúcleo, cómo diseñar manejadores de dispositivos para obtener el mejor rendimiento a la vez que se abstraen sus funciones del hardware, si ejecutar operaciones que no pertenecen al núcleo dentro de éste o en el espacio de usuario, y si mantener el código de subsistemas existentes (por ejemplo, una versión de UNIX)
o empezar de cero.
El enfoque de micronúcleo se popularizó por su uso en el sistema operativo Mach. En teoría este
enfoque proporciona un alto grado de flexibilidad y modularidad. Determinados productos ya tienen
implementaciones micronúcleo, y este enfoque general de diseño se verá en la mayor parte de los
computadores personales, estaciones de trabajo, y sistemas operativos servidor que se desarrollen en
un futuro cercano.
ARQUITECTURA MICRONÚCLEO
Los primeros sistemas operativos desarrollados a mediados y finales de los años 50 fueron diseñados
sin preocuparse por su arquitectura. Nadie tenía la experiencia necesaria en construcción de sistemas
software realmente grandes, y los problemas causados por la dependencia mutua e interacción no se
tenían en cuenta. En estos sistemas operativos monolíticos, de prácticamente cualquier procedimiento se podía llamar a cualquier otro. Esta falta de estructura se hizo insostenible a medida que los
sistemas operativos crecieron hasta proporciones desmesuradas. Por ejemplo, la primera versión de
OS/360 contenía más de un millón de líneas del código; Multics, desarrollado más tarde, creció hasta
20 millones de líneas del código [DENN84]. Como vimos en la Sección 2.3, se necesitaron técnicas
de programación modular para manejar esta escala de desarrollo software. Específicamente, se desarrollaron los sistemas operativos por capas7 (Figura 4.10a), en los cuales las funciones se organizan
jerárquicamente y sólo hay interacción entre las capas adyacentes. Con el enfoque por capas, la mayor parte o todas las capas ejecutan en modo núcleo.
Los problemas permanecen incluso en el enfoque por capas. Cada capa posee demasiada funcionalidad y grandes cambios en una capa pueden tener numerosos efectos, muchos difíciles de
seguir, en el código de las capas adyacentes (encima o debajo). Como resultado es difícil implementar versiones a medida del sistema operativo básico con algunas funciones añadidas o eliminadas. Además, es difícil construir la seguridad porque hay muchas interacciones entre capas
adyacentes.
La filosofía existente en el micronúcleo es que solamente las funciones absolutamente esenciales del sistema operativo estén en el núcleo. Los servicios y aplicaciones menos esenciales se construyen sobre el micronúcleo y se ejecutan en modo usuario. Aunque la filosofía de qué hay dentro y
qué hay fuera del micronúcleo varía de un diseño a otro, la característica general es que muchos servicios que tradicionalmente habían formado parte del sistema operativo ahora son subsistemas ex-
7
Como de costumbre, la terminología en esta área no se aplica de forma consistente en la literatura. El término sistema operativo monolítico se utiliza frecuentemente para referirse a los dos tipos de sistemas operativos que hemos denominado como monolíticos y por capas.
04-Capitulo 4
12/5/05
16:20
Página 177
Hilos, SMP y micronúcleos
Gestión de E/S y de dispositivos
Memoria virtual
Modo
núcleo
Modo
usuario
Servidor de procesos
Comunicación entre procesos
Proceso cliente
Sistema de ficheros
Servidor de ficheros
Usuarios
Manejador de dispositivos
Modo
usuario
177
Memoria virtual
Gestión de primitivas de procesos
Modo
núcleo
Micronúcleo
HARDWARE
HARDWARE
(a) Núcleo por capas
(b) Micro núcleo
Figura 4.10.
Arquitectura del núcleo.
ternos que interactúan con el núcleo y entre ellos mismos; algunos ejemplos son: manejadores de
dispositivos, servidores de archivos, gestores de memoria virtual, sistemas de ventana y servicios de
seguridad.
La arquitectura del micronúcleo reemplaza la tradicional estructura vertical y estratificada en capas por una horizontal (Figura 4.10b). Los componentes del sistema operativo externos al micronúcleo se implementan como servidores de procesos; interactúan entre ellos dos a dos, normalmente por
paso de mensajes a través del micronúcleo. De esta forma, el micronúcleo funciona como un intercambiador de mensajes: válida mensajes, los pasa entre los componentes, y concede el acceso al
hardware. El micronúcleo también realiza una función de protección; previene el paso de mensajes a
no ser que el intercambio esté permitido.
Por ejemplo, si una aplicación quiere abrir un archivo, manda un mensaje al servidor del sistema
de archivos. Si quiere crear un proceso o hilo, manda un mensaje al servidor de procesos. Cada uno
de los servidores puede mandar mensajes al resto de los servidores y puede invocar funciones primitivas del micronúcleo. Es decir, es un arquitectura cliente/servidor dentro de un solo computador.
BENEFICIOS DE UNA ORGANIZACIÓN MICRONÚCLEO
En la literatura se pueden encontrar una serie de ventajas del uso de los micronúcleos (por ejemplo,
[FINK97], [LIED96a], [WAYN94a]. Estas incluyen:
• Interfaces uniformes
• Extensibilidad
• Flexibilidad
• Portabilidad
• Fiabilidad
• Soporte de sistemas distribuidos
• Soporte de sistemas operativos orientados a objetos (OOOS)
04-Capitulo 4
178
12/5/05
16:20
Página 178
Sistemas operativos. Aspectos internos y principios de diseño
El micronúcleo impone una interfaz uniforme en las peticiones realizadas por un proceso. Los
procesos no necesitan diferenciar entre servicios a nivel de núcleo y a nivel de usuario, porque todos
los servicios se proporcionan a través de paso de mensajes.
De forma inevitable, un sistema operativo tendrá que adquirir nuevas características que no están
en su diseño actual, a medida que se desarrollen nuevos dispositivos hardware y nuevas técnicas software. La arquitectura micronúcleo facilita la extensibilidad, permitiendo agregar nuevos servicios,
así como la realización de múltiples servicios en la misma área funcional. Por ejemplo, puede haber
múltiples organizaciones de archivos para disquetes; cada organización se puede implementar como
un proceso a nivel de usuario más que tener múltiples servicios de archivos disponibles en el núcleo.
De esta forma, los usuarios pueden elegir, de una variedad de servicios, el que mejor se adapte a sus
necesidades. Con la arquitectura micronúcleo, cuando se añade una nueva característica, sólo el servidor relacionado necesita modificarse o añadirse. El impacto de un servidor nuevo o modificado se
restringe a un subconjunto del sistema. Además, las modificaciones no requieren la construcción de
un nuevo núcleo.
Relacionado con la extensibilidad de una arquitectura micronúcleo está su flexibilidad. No sólo
se pueden añadir nuevas características al sistema operativo, además las características existentes se
pueden eliminar para realizar una implementación más pequeña y más eficiente. Un sistema operativo micronúcleo no es necesariamente un sistema pequeño. De hecho, su propia estructura le permite
añadir un amplio rango de características. Pero no todo el mundo necesita, por ejemplo, un alto nivel
de seguridad o la necesidad de realizar computación distribuida. Si las características sustanciales (en
términos de requisitos de memoria) se hacen opcionales, el producto será atractivo para mayor número de usuarios.
El monopolio que casi tiene Intel en muchos segmentos del mercado de la computación es improbable que dure indefinidamente. De esta forma, la portabilidad se convierte en una característica interesante en los sistemas operativos. En la arquitectura micronúcleo, todo o gran parte del código específico del procesador está en el micronúcleo. Por tanto, los cambios necesarios para transferir el
sistema a un nuevo procesador son menores y tienden a estar unidos en grupos lógicos.
Mayor sea el tamaño de un producto software, mayor es la dificultad de asegurar su fiabilidad.
Aunque el diseño modular ayuda a mejorar la fiabilidad, con una arquitectura micronúcleo se
pueden lograr mayores ganancias. Un micronúcleo pequeño se puede verificar de forma rigurosa.
El que sólo utilice un pequeño número de interfaces de programación de aplicaciones (API) hace
más sencillo producir un código de calidad para los servicios del sistema operativo fuera del núcleo. El programador del sistema tiene un número limitado de API que dominar y métodos limitados de interacción y, por consiguiente, es más difícil afectar negativamente a otros componentes
del sistema.
El micronúcleo nos lleva por sí mismo al soporte de sistemas distribuidos, incluyendo clusters
controlados por sistemas operativos distribuidos. Cuando se envía un mensaje desde un cliente hasta
un proceso servidor, el mensaje debe incluir un identificador del servicio pedido. Si se configura un
sistema distribuido (por ejemplo, un cluster) de tal forma que todos los procesos y servicios tengan
identificadores únicos, entonces habrá una sola imagen del sistema a nivel de micronúcleo. Un proceso puede enviar un mensaje sin saber en qué máquina reside el servicio pedido. Volveremos a este
punto en nuestra discusión de los sistemas distribuidos en la Parte Seis.
Una arquitectura micronúcleo funciona bien en el contexto de un sistema operativo orientado a
objetos. Un enfoque orientado a objetos puede servir para diseñar el micronúcleo y para desarrollar
extensiones modulares para el sistema operativo. Como resultado, varios diseños de micronúcleos
van en la dirección de orientación a objetos [WAYN94b]. Un enfoque prometedor para casar la arquitectura micronúcleo con los principios de OOOS es el uso de componentes [MESS96]. Los componentes son objetos con interfaces claramente definidas que pueden ser interconectadas para la realiza-
04-Capitulo 4
12/5/05
16:20
Página 179
Hilos, SMP y micronúcleos
179
ción de software a través de bloques de construcción. Toda la interacción entre componentes utiliza la
interfaz del componente. Otros sistemas, tales como Windows, no se basan exclusivamente o por
completo en métodos orientados a objetos pero han incorporado los principios de la orientación a objetos en el diseño del micronúcleo.
RENDIMIENTO DEL MICRONÚCLEO
Una potencial desventaja que se cita a menudo de los micronúcleos es la del rendimiento. Lleva más
tiempo construir y enviar un mensaje a través del micronúcleo, y aceptar y decodificar la respuesta,
que hacer una simple llamada a un servicio. Sin embargo, también son importantes otros factores, de
forma que es difícil generalizar sobre la desventaja del rendimiento, si es que la hay.
Hay mucho que depende del tamaño y de la funcionalidad del micronúcleo. [LIED96a] resume
un número de estudios que revelan una pérdida sustancial del rendimiento en los que pueden ser
denominados micronúcleos de primera generación. Estas pérdidas continúan a pesar de los esfuerzos realizados para optimizar el código del micronúcleo. Una respuesta a este problema fue hacer
mayores los micronúcleos, volviendo a introducir servicios críticos y manejadores en el sistema
operativo. Los primeros ejemplos de este enfoque son Mach y Chorus. Incrementando de forma selectiva la funcionalidad del micronúcleo se reduce el número de cambios de modo usuario-núcleo
y el número de cambios de espacio de direcciones de proceso. Sin embargo, esta solución reduce
los problemas de rendimiento sacrificando la fortaleza del diseño del micronúcleo: mínimas interfaces, flexibilidad, etc.
Otro enfoque consiste en hacer el micronúcleo, no más grande, sino más pequeño. [LIED96b]
argumenta que, apropiadamente diseñado, un micronúcleo muy pequeño elimina las pérdidas de
rendimiento y mejora la flexibilidad y fiabilidad. Para dar una idea de estos tamaños, el típico micronúcleo de primera generación tenía 300 Kbytes de código y 140 interfaces de llamadas al sistema. Un ejemplo de un micronúcleo pequeño de segunda generación es el L4 [HART97, LIED95],
que consiste en 12 Kbytes de código y 7 llamadas al sistema. Las experimentaciones realizadas en
estos sistemas indican que pueden funcionar tan bien o mejor que sistemas operativos por capas
como por ejemplo, UNIX.
DISEÑO DEL MICRONÚCLEO
Debido a que los diferentes micronúcleos presentan una gran variedad de tamaños y funcionalidades,
no se pueden facilitar reglas concernientes a qué funcionalidades deben darse por parte del micronúcleo y qué estructura debe implementarse. En esta sección, presentamos un conjunto mínimo de funciones y servicios del micronúcleo, para dar una perspectiva del diseño de los mismos.
El micronúcleo debe incluir aquellas funciones que dependen directamente del hardware y aquellas funciones necesarias para mantener a los servidores y aplicaciones operando en modo usuario.
Estas funciones entran dentro de las categorías generales de gestión de memoria a bajo nivel, intercomunicación de procesos (IPC), y E/S y manejo de interrupciones.
Gestión de memoria a bajo nivel. El micronúcleo tiene que controlar el concepto hardware de
espacio de direcciones para hacer posible la implementación de protección a nivel de proceso. Con tal
de que el micronúcleo se responsabilice de la asignación de cada página virtual a un marco físico, la
parte principal de gestión de memoria, incluyendo la protección del espacio de memoria entre procesos, el algoritmo de reemplazo de páginas y otra lógica de paginación, pueden implementarse fuera
del núcleo. Por ejemplo, un módulo de memoria virtual fuera del micronúcleo decide cuándo traer
04-Capitulo 4
180
12/5/05
16:20
Página 180
Sistemas operativos. Aspectos internos y principios de diseño
una página a memoria y qué página presente en memoria debe reemplazarse; el micronúcleo proyecta
estas referencias de página en direcciones físicas de memoria principal.
El paginador externo de Mach [YOUN87] introdujo el concepto de que la paginación y la gestión
de la memoria virtual se pueden realizar de forma externa al núcleo. La Figura 4.11 muestra el funcionamiento del paginador externo. Cuando un hilo de la aplicación hace referencia a una página que
no está en memoria principal, hay un fallo de página y la ejecución pasa al núcleo. El núcleo entonces
manda un mensaje al proceso paginador indicando la página que ha sido referenciada. El paginador
puede decidir cargar esa página y localizar un marco de página para este propósito. El paginador y el
núcleo deben interactuar para realizar las operaciones lógicas del paginador en memoria física. Una
vez que la página está disponible, el paginador manda un mensaje a la aplicación a través del micronúcleo.
Esta técnica permite a un proceso fuera del núcleo proyectar archivos y bases de datos en espacios de direcciones de usuario sin llamar al núcleo. Las políticas de compartición de memoria específicas de la aplicación se pueden implementar fuera del núcleo.
[LIED95] recomienda un conjunto de tres operaciones de micronúcleo que pueden dar soporte a
la paginación externa y a la gestión de memoria virtual:
• Conceder (Grant). El propietario de un espacio de direcciones (un proceso) puede conceder
alguna de sus páginas a otro proceso. El núcleo borra estas páginas del espacio de memoria
del otorgante y se las asigna al proceso especificado.
• Proyectar (Map). Un proceso puede proyectar cualquiera de sus páginas en el espacio de direcciones de otro proceso, de forma que ambos procesos tienen acceso a las páginas. Esto genera memoria compartida entre dos procesos. El núcleo mantiene la asignación de estas páginas al propietario inicial, pero proporciona una asociación que permite el acceso de otros
procesos.
• Limpiar (Flush). Un proceso puede reclamar cualquier página que fue concedida o asociada a
otro proceso.
Para comenzar, el núcleo asigna toda la memoria física disponible como recursos a un proceso
base del sistema. A medida que se crean los nuevos procesos, se pueden conceder o asociar algunas
páginas del espacio de direcciones original a los nuevos procesos. Este esquema podría dar soporte a
múltiples esquemas de memoria virtual simultáneamente.
Comunicación entre procesos (Interprocess Communication). La forma básica de comunicación entre dos procesos o hilos en un sistema operativo con micronúcleo son los mensajes. Un
Aplicación
Fallo
de
página
Paginador
Reanudar
Llamada a
función
espaciodirecciones
Micronúcleo
Figura 4.11.
Procesamiento del fallo de página.
04-Capitulo 4
12/5/05
16:20
Página 181
Hilos, SMP y micronúcleos
181
mensaje incluye una cabecera que identifica a los procesos remitente y receptor y un cuerpo que contiene directamente los datos, un puntero a un bloque de datos, o alguna información de control del
proceso. Normalmente podemos pensar que las IPC se fundamentan en puertos asociados a cada proceso. Un puerto es, en esencia, una cola de mensajes destinada a un proceso particular; un proceso
puede tener múltiples puertos. Asociada a cada puerto existe una lista que indica qué procesos se pueden comunicar con éste. Las identidades y funcionalidades de cada puerto se mantienen en el núcleo.
Un proceso puede concederse nuevas funcionalidades mandando un mensaje al núcleo con las nuevas
funcionalidades del puerto.
Llegado a este punto sería conveniente realizar un comentario sobre el paso de mensajes. El
paso de mensajes entre dos procesos que no tengan solapado el espacio de direcciones implica realizar una copia de memoria a memoria, y de esta forma está limitado por la velocidad de la memoria y no se incrementa con la velocidad del procesador. De esta forma, la investigación actual de
los sistemas operativos muestra interés en IPC basado en hilos y esquemas de memoria compartida
como la re-proyección de páginas —page remapping— (una sola página compartida por múltiples
procesos).
Gestión de E/S e interrupciones. Con una arquitectura micronúcleo es posible manejar las interrupciones hardware como mensajes e incluir los puertos de E/S en los espacios de direcciones. El
micronúcleo puede reconocer las interrupciones pero no las puede manejar. Más bien, genera un mensaje para el proceso a nivel de usuario que está actualmente asociado con esa interrupción. De esta
forma, cuando se habilita una interrupción, se asigna un proceso de nivel de usuario a esa interrupción y el núcleo mantiene las asociaciones. La transformación de las interrupciones en mensajes las
debe realizar el micronúcleo, pero el micronúcleo no está relacionado con el manejo de interrupciones específico de los dispositivos.
[LIED96a] sugiere ver el hardware como un conjunto de hilos que tienen identificadores de
hilo único y que mandan mensajes (únicamente con el identificador de hilo) a hilos asociados en
el espacio de usuario. El hilo receptor determina si el mensaje proviene de una interrupción y
determina la interrupción específica. La estructura general de este código a nivel de usuario es la
siguiente:
hilo del dispositivo:
do
waitFor(msg, remitente);
if (remitente == mi_interrupcion_hardware)
{
leer/escribir puertos E/S;
reanudar interrupción hardware;
}
else ...
while(true);
4.4. GESTIÓN DE HILOS Y SMP EN WINDOWS
El diseño de un proceso Windows está limitado por la necesidad de proporcionar soporte a diversos
entornos de sistemas operativos. Los procesos soportados por diferentes entornos de sistemas operativos se diferencian en varias cosas, incluyendo las siguientes:
04-Capitulo 4
182
12/5/05
16:20
Página 182
Sistemas operativos. Aspectos internos y principios de diseño
• La denominación de los procesos.
• Si se proporcionan hilos con los procesos.
• Cómo se representa a los procesos.
• Cómo se protege a los recursos de los procesos.
• Qué mecanismos se utilizan para la comunicación y sincronización entre procesos.
• Cómo se relacionan los procesos entre sí.
Como consecuencia, las estructuras de los procesos y los servicios proporcionados por el núcleo
de Windows son relativamente sencillos y de propósito general, permitiendo a cada subsistema del
sistema operativo que emule una estructura y funcionalidad particular del proceso. Algunas características importantes de los procesos Windows son las siguientes:
• Los procesos Windows están implementados como objetos.
• Un proceso ejecutable puede contener uno o más hilos.
• Tanto el objeto proceso como el objeto hilo, tienen funcionalidades de sincronización preconstruidas.
La Figura 4.12, basada en una de [SOLO00], muestra la forma en la que un proceso se asocia a
los recursos que controla o utiliza. A cada proceso se le asigna un testigo (token) de seguridad de acceso, denominada la ficha principal del proceso. Cuando un usuario inicia una sesión, Windows crea
una ficha de acceso que incluye el ID de seguridad para el usuario. Cada proceso que se crea o ejecuta en representación de este usuario, tiene una copia de este testigo de acceso. Windows lo utiliza
para comprobar si el usuario puede acceder a objetos de seguridad, o puede realizar funciones restringidas en el sistema o en un objeto de seguridad. El testigo de acceso controla si un proceso puede modificar sus propios atributos. En este caso, el proceso no tiene un manejador abierto hacia su testigo
de acceso. Si el proceso intenta abrir este manejador, el sistema de seguridad determinará si está permitido y por tanto si el proceso puede modificar sus propios atributos.
También relacionado con el proceso, hay una serie de bloques que definen el espacio de direcciones virtuales actualmente asignado al proceso. El proceso no puede modificar directamente estas estructuras, ya que dependen del gestor de memoria virtual, que proporciona servicios de asignación de
memoria a los procesos.
Finalmente, el proceso incluye una tabla de objetos, que trata los otros objetos conocidos por el
proceso. Hay un manejador para cada hilo que está contenido en este objeto. La Figura 4.12 muestra
un único hilo. El proceso tiene acceso a un objeto archivo y a un objeto segmento, que define un segmento de memoria compartido.
OBJETO PROCESO Y OBJETO HILO
La estructura orientada a objetos de Windows facilita el desarrollo de un proceso de propósito general. Windows hace uso de dos tipos de objetos relacionados con los procesos: procesos e hilos. Un
proceso es una entidad que corresponde a un trabajo de usuario o una aplicación que posee recursos
como la memoria y archivos abiertos. Un hilo es una unidad de trabajo que se puede activar, que ejecuta secuencialmente y que es interrumpible, de forma que el procesador puede cambiar a otro hilo.
Cada proceso Windows se representa por un objeto. En la Figura 4.23a se puede ver la estructura
de dicho objeto. Un proceso se define por una serie de atributos y encapsula una serie de acciones, o
servicios, que puede realizar. Un proceso realizará un servicio cuando reciba el mensaje apropiado; la
04-Capitulo 4
12/5/05
16:20
Página 183
Hilos, SMP y micronúcleos
183
Ficha de
acceso
Descriptores de direcciones virtuales
Objeto de
proceso
Tabla de
manejadores
Objetos
disponibles
Hilo
Manejador 1
Manejador 2
Archivo y
Manejador 3
Sección
Figura 4.12.
x
z
Un proceso Windows y sus recursos.
única forma de invocar a este servicio es a través de mensajes a un objeto proceso que proporciona
ese servicio. Cuando Windows crea un nuevo proceso, utiliza el objeto definido para el proceso Windows como plantilla para generar la nueva instancia del proceso. En el momento de la creación se
asignan los valores de los atributos. La Tabla 4.3 da una breve definición de cada atributo de objeto
contenido en el objeto proceso.
Un proceso Windows debe contener por lo menos un hilo que ejecutar. Ese hilo puede a su vez
crear otros hilos. En un sistema multiprocesador, múltiples hilos de un mismo proceso pueden ejecutar en paralelo. La Figura 4.13b representa la estructura de objeto hilo, y la Tabla 4.4 define los
atributos del objeto hilo. Es importante darse cuenta de que alguno de los atributos de los hilos se
asemeja a los de los procesos. En estos casos, el valor del atributo del hilo se deriva del valor del
atributo del proceso. Por ejemplo, la afinidad de procesador asociada al hilo (thread processor affinity) es el conjunto de procesadores, en un sistema multiprocesador, que pueden ejecutar este
hilo. Este conjunto es igual o un subconjunto de la afinidad de procesador asociada al proceso.
Adviértase que uno de los atributos del proceso hilo es el contexto. Esta información permite que
el hilo se suspenda y reanude. Más aún, es posible cambiar el comportamiento de un hilo, alterando
su contenido cuando el hilo está suspendido.
MULTIHILO
Windows soporta la concurrencia entre procesos ya que hilos de diferentes procesos pueden ejecutar en
paralelo. Además, múltiples hilos del mismo proceso pueden estar asignados a distintos procesadores y
pueden ejecutar de modo concurrente. Un proceso multihilo puede lograr la concurrencia sin la sobrecarga del uso de múltiples procesos. Los hilos del mismo proceso pueden intercambiar información a
través de su espacio de direcciones común y tienen acceso a los recursos compartidos del proceso. Los
hilos de diferentes procesos pueden intercambiar información a través del uso de memoria compartida.
04-Capitulo 4
184
12/5/05
16:20
Página 184
Sistemas operativos. Aspectos internos y principios de diseño
Proceso
Tipo de objeto
Hilo
Tipo de objeto
Atributos
del cuerpo
del objeto
ID proceso
Descriptor de seguridad
Prioridad base
Afinidad de procesador por defecto
Límite de cuota
Tiempo de ejecución
Contadores E/S
Contadores de operaciones de MV
Puertos de excepciones y de depurado
Estado de salida
Servicios
Crear proceso
Abrir proceso
Solicitar información del proceso
Establecer información del proceso
Proceso actual
Terminar proceso
(a) Objeto proceso
Atributos
del cuerpo
del objeto
Servicios
ID hilo
Contexto del hilo
Prioridad dinámica
Prioridad base
Afinidad de procesador de hilo
Tiempo de ejecución del hilo
Estado de alerta
Contador de suspensiones
Testigo de personificación
Puerto de finalización
Estado de salida del hilo
Crear hilo
Abrir hilo
Solicitar información del hilo
Establecer información del hilo
Hilo actual
Terminar hilo
Obtener contexto
Establecer contexto
Suspender
Reanudar
Alertar hilo
Chequear estado de alerta
Registrar puerto finalización
(b) Objeto hilo
Figura 4.13.
Tabla 4.3.
Objetos de Windows proceso e hilo.
Atributos del objeto proceso de Windows.
ID proceso
Un valor único que identifica al proceso en el sistema operativo.
Descriptor de seguridad
Describe al creador del objeto, quién puede acceder o utilizar el objeto y a quién se le deniega acceso al objeto.
Prioridad base
Prioridad de ejecución base para los hilos del proceso.
Afinidad de procesador
por defecto
Conjunto de procesadores por defecto, en los que pueden ejecutar los
hilos del proceso.
Límite de cuota
Máxima cantidad de memoria del sistema paginada y no paginada,
espacio del archivo de páginas y tiempo de procesador que pueden
utilizar los procesos de un usuario.
Tiempo de ejecución
La cantidad total de tiempo que han ejecutado todos los hilos de un
proceso.
Contadores de E/S
Variables que almacenan el número y tipo de operaciones de E/S que
han realizado los hilos de un proceso.
Contadores de operaciones
de MV
Variables que almacenan el número y tipo de operaciones de
memoria virtual que han realizado los hilos de un proceso.
Puertos de excepciones
y de depurado
Canales de comunicación entre procesos a los que el gestor de procesos manda un mensaje cuando uno de los hilos del proceso causa
una excepción.
Estado de salida
La razón de la terminación del proceso.
04-Capitulo 4
12/5/05
16:20
Página 185
Hilos, SMP y micronúcleos
Tabla 4.4.
185
Atributos del objeto hilo de Windows.
ID hilo
Valor único que identifica a un hilo cuando llama a un servidor.
Contexto del hilo
El conjunto de los valores de los registros y otra información volátil
que define el estado de ejecución de un hilo.
Prioridad dinámica
La prioridad de ejecución de un hilo en un determinado momento.
Prioridad base
El límite inferior de la prioridad dinámica de un hilo.
Afinidad de procesador
asociada al hilo
El conjunto de procesadores en el que puede ejecutar un hilo, que es
un subconjunto del valor definido en el objeto proceso.
Tiempo de ejecución del hilo
La cantidad de tiempo acumulado que ha ejecutado un hilo en modo
usuario y modo núcleo.
Estado de alerta
Un flag que indica si el hilo debe ejecutar una llamada a procedimiento asíncrona.
Contador de suspensión
El número de veces que ha sido suspendida la ejecución de un hilo
sin ser reanudado.
Testigo de personificación
Una señal de acceso temporal que permite al hilo realizar operaciones
en lugar de otro proceso (utilizado por los subsistemas).
Puerto de finalización
Canal de comunicación entre procesos al que el gestor de procesos
manda un mensaje cuando termina el hilo (utilizado por los subsistemas).
Estado de salida del hilo
La razón de la terminación del hilo.
Un proceso multihilo orientado a objetos es una forma efectiva de implementar una aplicación
servidora. Por ejemplo, un proceso servidor podría atender a varios clientes. Cada petición de cliente
desencadena la creación de un nuevo hilo del servidor.
ESTADO DE LOS HILOS
Un hilo de Windows se encuentra en uno de estos seis estados (Figura 4.14):
• Listo (ready). Puede planificarse para ejecución. El activador del micronúcleo conoce todos
los hilos listos y los planifica en orden de prioridad.
• Substituto (standby). Un hilo substituto se ha seleccionado para ejecutar en siguiente lugar en
un determinado procesador. Si la prioridad del hilo substituto es suficientemente alta, el hilo
actualmente en ejecución en ese procesador podría ser expulsado en su favor. De otra forma,
el hilo substituto espera hasta que el hilo en ejecución se bloquea o finaliza su porción de
tiempo.
• Ejecutando (running). Una vez que el micronúcleo realiza un intercambio de hilo o proceso, el hilo susbtituto pasa al estado de ejecución y ejecuta hasta que es expulsado, finaliza
su porción de tiempo, se bloquea o termina. En los dos primeros casos vuelve a la cola de
listos.
• Esperando (waiting). Un hilo pasa a estado esperando cuando (1) se bloquea en un evento
(por ejemplo, E/S), (2) espera voluntariamente por temas de sincronización, o (3) un subsistema manda al hilo a estado de suspendido. Cuando se satisface la condición de espera, el hilo
pasa al estado Listo si todos sus recursos están disponibles.
04-Capitulo 4
186
12/5/05
16:20
Página 186
Sistemas operativos. Aspectos internos y principios de diseño
• Transición (transition). Un hilo entra en este estado después de esperar si está listo para ejecutar pero los recursos no están disponibles. Por ejemplo, la pila del hilo puede no estar en
memoria. Cuando los recursos están disponibles, el hilo pasa al estado Listo.
• Terminado (terminated). Un hilo se puede finalizar por sí mismo, por otro hilo o cuando su
proceso padre finaliza. Cuando se completan las tareas internas, el hilo se borra del sistema, o
puede retenerse por el ejecutivo8 para futuras reinicializaciones.
SOPORTE PARA SUBSISTEMAS DE SISTEMAS OPERATIVOS
Los servicios de procesos e hilos de propósito general, deben dar soporte a las estructuras de procesos
e hilos de varios SO cliente. Cada subsistema de SO es responsable de sacar provecho de los procesos
e hilos de Windows para su propio sistema operativo. Esta área de gestión de procesos/hilos es complicada, y nosotros sólo damos una pequeña visión general.
La creación de un proceso comienza con la petición de una aplicación de un nuevo proceso. La
aplicación manda una solicitud de creación de proceso a su correspondiente subsistema, que pasa la
solicitud al ejecutivo de Windows. El ejecutivo crea un objeto proceso y devuelve al subsistema el
manejador de dicho objeto. Cuando Windows crea un proceso, no crea automáticamente un hilo. En
el caso de Win32 y OS/2, siempre se crea un nuevo proceso con un hilo. Por consiguiente, para estos
sistemas operativos, el subsistema llama de nuevo al gestor de procesos de Windows para crear un
hilo para el nuevo proceso, recibiendo un manejador de hilo como respuesta. A continuación se devuelven a la aplicación la información del hilo y del proceso. En el caso de Windows 16-bit y POSIX,
Ejecutable
Sustituto
Escogido para
ejecutar
Cambiar
Expulsado
Listo
Recurso
disponible
Transición
Desbloquear/ reanudar
Recurso disponible
Desbloquear
Recurso no disponible
Ejecutando
Bloquear/
suspender
Esperando
Finalizar
Finalizado
No Ejecutable
Figura 4.14.
Estados de un hilo de Windows.
8
El ejecutivo de Windows se describe en el Capítulo 2. Contiene los servicios base del sistema operativo, como gestión de memoria, gestión de procesos e hilos, seguridad, E/S y comunicación entre procesos.
04-Capitulo 4
12/5/05
16:20
Página 187
Hilos, SMP y micronúcleos
187
no se soportan los hilos. Por tanto, para estos sistemas operativos, el subsistema obtiene un hilo para
el nuevo proceso de Windows, para que el proceso pueda activarse, pero devuelve sólo la información del proceso a la aplicación. El hecho de que el proceso de la aplicación esté implementado como
un hilo, no es visible para la aplicación.
Cuando se crea un nuevo proceso en Win32 o OS/2, el nuevo proceso hereda muchos de sus atributos del proceso que le ha creado. Sin embargo, en el entorno Windows, este procedimiento de creación se realiza indirectamente. El proceso cliente de una aplicación manda su solicitud de creación de
proceso al subsistema del SO; un proceso del subsistema a su vez manda una solicitud de proceso al
ejecutivo de Windows. Ya que el efecto deseado es que el nuevo proceso herede las características del
proceso cliente, y no del proceso servidor, Windows permite al subsistema especificar el padre del
nuevo proceso. El nuevo proceso hereda el testigo de acceso, límite de cuota, prioridad base y afinidad a procesador por defecto de su padre.
SOPORTE PARA MULTIPROCESAMIENTO SIMÉTRICO
Windows soporta una configuración hardware SMP. Los hilos de cualquier proceso, incluyendo los
del ejecutivo, pueden ejecutar en cualquier procesador. En ausencia de restricciones de afinidad, explicadas en el siguiente párrafo, el micronúcleo asigna un hilo listo al siguiente procesador disponible. Esto asegura que ningún procesador está ocioso o está ejecutando un hilo de menor prioridad
cuando un hilo de mayor prioridad está listo. Múltiples hilos de un proceso pueden ejecutar a la vez
en múltiples procesadores.
Por defecto, el micronúcleo utiliza la política afinidad débil (soft affinity) para asignar procesadores a los hilos: el planificador intenta asignar un proceso listo al mismo procesador que lo ejecutó
la última vez. Esto ayuda a reutilizar datos que estén todavía en la memoria cache del procesador de
la ejecución previa del hilo. Para una aplicación es posible restringir la ejecución de sus hilos a determinados procesadores afinidad fuerte (hard affinity).
4.5. GESTIÓN DE HILOS Y SMP EN SOLARIS
Solaris implementa un soporte de hilo multinivel poco habitual, diseñado para proporcionar considerable flexibilidad para sacar provecho de los recursos del procesador.
ARQUITECTURA MULTIHILO
Solaris utiliza cuatro conceptos relacionados con los hilos:
• Procesos. Es un proceso normal UNIX e incluye el espacio de direcciones del usuario, la pila
y el bloque de control del proceso.
• Hilos de nivel de usuario. Implementados a través de una biblioteca de hilos en el espacio de
direcciones de un proceso, son invisibles al sistema operativo. Los hilos de nivel de usuario
(user-level threads, ULT)9 son la interfaz para las aplicaciones paralelas.
9
De nuevo, el acrónimo ULT es exclusivo de este libro y no se encuentra en la literatura de Solaris.
04-Capitulo 4
188
12/5/05
16:20
Página 188
Sistemas operativos. Aspectos internos y principios de diseño
• Procesos ligeros. Un proceso ligero (lightweight process, LWP) puede ser visto como una
asociación entre ULT e hilos de núcleo. Cada LWP soporta uno o más ULT y se asocia con un
hilo de núcleo. Los LWP se planifican de forma independiente por el núcleo y pueden ejecutar
en paralelo en múltiples procesadores.
• Hilos de núcleo. Son entidades fundamentales que se pueden planificar para ejecutar en cualquier procesador del sistema.
La Figura 4.15 muestra la relación entre estas cuatro entidades. Nótese que hay siempre un hilo
de núcleo por cada LWP. Un LWP es visible dentro de un proceso de la aplicación. De esta forma, las
estructuras de datos LWP existen dentro del espacio de direcciones del proceso respectivo. Al mismo
tiempo, cada LWP está vinculado a un único hilo de núcleo activable, y la estructura de datos para ese
hilo de núcleo se mantiene dentro del espacio de direcciones del núcleo.
En nuestro ejemplo, el proceso 1 consiste en un único ULT vinculado con un único LWP. De esta
forma, hay un único hilo de ejecución, correspondiente a un proceso UNIX tradicional. Cuando no se
requiere concurrencia en un proceso, una aplicación utiliza esta estructura de proceso. El proceso 2 se
corresponde con una estrategia ULT pura. Todos los ULT están soportados por un único hilo de núcleo, y por tanto sólo se puede ejecutar un ULT al mismo tiempo. Esta estructura es útil para una aplicación que se puede programar de forma que exprese la concurrencia, pero que no es necesario ejecutar en paralelo con múltiples hilos. El proceso 3 muestra múltiples hilos multiplexados en un menor
número de LWP. En general, Solaris permite a las aplicaciones multiplexar ULT en un número menor
o igual de LWP. Esto permite a la aplicación especificar el grado de paralelismo a nivel de núcleo que
tendrá este proceso. El proceso 4 tiene sus hilos permanentemente vinculados a LWP de uno en uno.
Esta estructura hace el paralelismo a nivel de núcleo totalmente visible a la aplicación. Es útil si los
Proceso 1
Proceso 2
Proceso 3
Proceso 4
Proceso 5
Usuario
L
L
L
L
L
L
L
L
Biblioteca
de hilos
L
Núcleo
P
Hardware
Hilo de nivel de usuario
Hilo de núcleo
Figura 4.15.
P
L
P
Proceso ligero
P
P
P
Procesador
Ejemplo de arquitectura multihilo de Solaris.
04-Capitulo 4
12/5/05
16:20
Página 189
Hilos, SMP y micronúcleos
189
hilos van a suspenderse frecuentemente al quedar bloqueados. El proceso 5 muestra tanto la asociación de múltiples ULT en múltiples LWP como el enlace de un ULT con un LWP. Además, hay un
LWP asociado a un procesador particular.
Lo que no se muestra en la figura, es la presencia de hilos de núcleo que no están asociados con
LWP. El núcleo crea, ejecuta y destruye estos hilos de núcleo para ejecutar funciones específicas del
sistema. El uso de hilos de núcleo en lugar de procesos de núcleo para implementar funciones del sistema reduce la sobrecarga de los intercambios dentro del núcleo (de cambios de procesos a cambios
de hilos).
MOTIVACIÓN
La combinación de los hilos a nivel de usuario y a nivel de núcleo, le da la oportunidad al programador de la aplicación de explotar la concurrencia de la forma más efectiva y apropiada para su
aplicación.
Algunos programas tienen un paralelismo lógico del que se puede sacar provecho para simplificar y estructurar el código, pero que no necesitan paralelismo hardware. Por ejemplo, una aplicación
que emplee múltiples ventanas, de las cuales sólo una está activa en un determinado momento, podría beneficiarse de la implementación como un conjunto de ULT en un único LWP. La ventaja de
restringir estas aplicaciones a ULT es la eficiencia. Los ULT se pueden crear, destruir, bloquear, activar, etc., sin involucrar al núcleo. Si el núcleo conociera cada ULT, tendría que asignar estructuras de
datos de núcleo para cada uno de ellos y realizar el intercambio de hilos. Como hemos visto (Tabla
4.1), el intercambio de hilos de nivel de núcleo es más costoso que el intercambio de hilos de nivel
de usuario.
Si una aplicación tiene hilos que se pueden bloquear, por ejemplo cuando realiza E/S, resultará
interesante tener múltiples LWP para soportar un número igual o mayor de ULT. Ni la aplicación ni la
biblioteca de hilos necesitan hacer contorsiones para permitir ejecutar a otros hilos del mismo proceso. En su lugar, si un hilo de un proceso se bloquea, otros hilos del mismo proceso pueden ejecutar en
los restantes LWP.
Asociar ULT y LWP uno a uno, es efectivo para algunas aplicaciones. Por ejemplo, una computación paralela de matrices podría dividir las filas de sus matrices en diferentes hilos. Si hay exactamente un ULT por LWP, no se requiere ningún intercambio de hilos en el proceso de computación.
La mezcla de hilos que están permanentemente asociados con LWP e hilos no asociados (múltiples hilos compartiendo múltiples LWP) es apropiada en algunas aplicaciones. Por ejemplo, una aplicación de tiempo real podría querer que algunos hilos tuvieran una gran prioridad en el sistema y planificación en tiempo real, mientras que otros hilos realizan funciones secundarias y pueden compartir
un pequeño conjunto de LWP.
ESTRUCTURA DE LOS PROCESOS
La Figura 4.16 compara, en términos generales, la estructura de un proceso de un sistema UNIX tradicional con la de Solaris. En una implementación UNIX típica, la estructura del proceso incluye el
ID del proceso; el ID del usuario; una tabla de tratamiento de señales, que usa el núcleo para decidir
qué hacer cuando llega una señal a un proceso; descriptores de archivos, que describen el estado de
los archivos en uso por el proceso; un mapa de memoria, que define el espacio de direcciones de este
proceso; y una estructura del estado del procesador, que incluye la pila del núcleo para este proceso.
Solaris contiene esta estructura básica pero reemplaza el bloque de estado del procesador con una lista de estructuras que contienen un bloque de datos por cada LWP.
04-Capitulo 4
190
12/5/05
16:20
Página 190
Sistemas operativos. Aspectos internos y principios de diseño
Estructura de un proceso en UNIX
Estructura de un proceso en Solaris
ID proceso
ID proceso
ID usuario
ID usuario
Tabla de activación de señales
Tabla de activación de señales
Mapa de memoria
Mapa de memoria
Prioridad
Máscara de señales
Registros
PILA
Descriptores de archivos
Descriptores de archivos
Estado del procesador
Figura 4.16.
LWP 2
LWP 1
ID LWP
Prioridad
Máscara de señales
Registros
ID LWP
Prioridad
Máscara de señales
Registros
PILA
PILA
Estructura de procesos en UNIX tradicional y Solaris.
La estructura de datos de LWP incluye los siguientes elementos:
• Un identificador LWP.
• La prioridad de este LWP y, por tanto, del hilo del núcleo que le da soporte.
• Una máscara de señales que dice al núcleo qué señales se aceptarán.
• Valores almacenados de los registros a nivel de usuario (cuando el LWP no está ejecutando).
• La pila del núcleo para este LWP. Que incluye argumentos de llamadas al sistema, resultados y
códigos de error para cada nivel de llamada.
• Uso de recursos y datos de perfiles.
• Puntero al correspondiente hilo del núcleo.
• Puntero a la estructura del proceso.
EJECUCIÓN DE HILOS
La Figura 4.17 muestra una visión simplificada de los estados de ejecución de ULT y LWP. La ejecución de los hilos a nivel de usuario se gestiona por la biblioteca de los hilos. Consideremos primero
04-Capitulo 4
12/5/05
16:20
Página 191
Hilos, SMP y micronúcleos
191
los hilos no vinculados, es decir, los hilos que comparten una serie de LWP. Un hilo no vinculado
puede estar en uno de los siguientes estados: ejecutable, activo, durmiendo o detenido. Un ULT en un
estado activo se asigna a un LWP y ejecuta mientras que el hilo de núcleo subyacente ejecuta. Hay
una serie de eventos que pueden causar que el ULT deje el estado activo. Consideremos un ULT activo denominado T1. Pueden ocurrir los siguientes sucesos:
• Sincronización. T1 invoca una de las primitivas concurrentes discutidas en el Capítulo 5 para
coordinar su actividad con otros hilos y para forzar exclusión mutua. T1 se pasa a estado durmiendo. Cuando se cumple la condición de sincronización, T1 se pasa a estado ejecutable.
• Suspensión. Cualquier hilo (incluyendo T1) puede causar que se suspenda T1 y que se pase a
estado detenido. T1 permanece en este estado hasta que otro hilo manda una petición de continuación, que lo pasa a estado ejecutable.
• Expulsión. Un hilo activo (T1 o cualquier otro hilo) hace algo que causa que otro hilo (T2) de
mayor prioridad pase a ejecutable. Si T1 es el hilo con la menor prioridad, se expulsa y se pasa
a estado ejecutable, y T2 se asigna al LWP disponible.
• Ceder paso. Si T1 ejecuta el mandato de biblioteca thr_yield(), el planificador de hilos
de la biblioteca mirará si hay otro proceso ejecutable (T2) de la misma prioridad. Si existe,
T1 se pasa a estado ejecutable y T2 se asigna al LWP liberado. Si no existe, T1 continúa
ejecutando.
En todos los casos precedentes, cuando T1 sale del estado activo, la biblioteca de hilos selecciona otro hilo no vinculado en estado ejecutable y lo ejecuta en el LWP que está nuevamente
disponible.
La Figura 4.17 también muestra el diagrama de estados de un LWP. Podemos ver este diagrama
de estados como una descripción detallada del estado activo del ULT, ya que un hilo no vinculado
sólo tiene un LWP asignado cuando está en estado Activo. El diagrama de estados de LWP se explica
por sí mismo. Un hilo activo sólo está ejecutando cuando su LWP está en el estado Ejecutando. Cuando un hilo activo ejecuta una llamada al sistema bloqueante, el LWP pasa a estado Bloqueado. Sin
embargo, el ULT permanece vinculado a ese LWP y, hasta donde concierne a la biblioteca de hilos, el
ULT permanece activo.
Con los hilos vinculados, la relación entre ULT y LWP es ligeramente diferente. Por ejemplo, si
un ULT vinculado pasa al estado Durmiendo mientras espera un evento de sincronización, su LWP
también debe parar de ejecutar. Esto se logra manteniendo el bloqueo de LWP en una variable de sincronización a nivel de núcleo.
INTERRUPCIONES COMO HILOS
La mayor parte de los sistemas operativos contienen dos formas de actividad concurrente: procesos e
interrupciones. Los procesos (o hilos) cooperan entre sí y gestionan el uso de estructuras de datos
compartidas a través de diversas primitivas que fuerzan la exclusión mutua (sólo un proceso al mismo tiempo puede ejecutar determinado código o acceder a determinados datos) y que sincronizan su
ejecución. Las interrupciones se sincronizan impidiendo su manejo por un periodo de tiempo. Solaris
unifica estos dos conceptos en un solo modelo. Para hacer esto, las interrupciones se convierten a hilos de núcleo.
La motivación para convertir las interrupciones en hilos, es reducir la sobrecarga. El proceso de
tratamiento de las interrupciones, a menudo maneja datos compartidos con el resto del núcleo. Por
consiguiente, mientras que se ejecuta una rutina de núcleo que accede a esos datos, las interrupciones
16:20
Página 192
Sistemas operativos. Aspectos internos y principios de diseño
Parar
Ejecutable
Hilos de nivel de usuario
Despertar
Continuar
Activar
192
12/5/05
Parar
Parado
Durmiendo
Expulsar
04-Capitulo 4
Parar
Dormir
Activo
Rodaja de
tiempo o
expulsado
Ejecutando
Parar
Despertar
Activar
Bloqueando
sistema
llamada
Ejecutable
Parado
Continuar
Despertar
Bloqueado
Parar
Procesos ligeros
Figura 4.17.
Estados de los hilos de nivel de usuarios y LWP en Solaris.
se deben bloquear, incluso aunque la mayor parte de las interrupciones no vayan a afectar a esos datos. Normalmente, la forma de hacer esto es que la rutina suba el nivel de prioridad de interrupciones,
para bloquear las interrupciones, y baje el nivel de prioridad cuando el acceso está completado. Estas
operaciones llevan tiempo. El problema es mayor en un sistema multiprocesador. El núcleo debe proteger más objetos y podría necesitar bloquear las interrupciones en todos los procesadores.
La solución de Solaris se puede resumir de la siguiente manera:
1. Solaris emplea un conjunto de hilos de núcleo para manejar las interrupciones. Como cualquier hilo del núcleo, un hilo de interrupción tiene su propio identificador, prioridad, contexto
y pila.
2. El núcleo controla el acceso a las estructuras de datos y sincroniza los hilos de interrupción
utilizando primitivas de exclusión mutua, del tipo discutido en el Capítulo 5. Es decir, se utilizan las técnicas habituales de sincronización de hilos en el manejo de interrupciones.
04-Capitulo 4
12/5/05
16:20
Página 193
Hilos, SMP y micronúcleos
193
3. Se asigna mayor prioridad a los hilos de interrupción que a cualquier otro tipo de hilo de
núcleo.
Cuando sucede una interrupción, se envía a un determinado procesador y el hilo que estuviera
ejecutando en ese procesador es inmovilizado (pinned). Un hilo inmovilizado no se puede mover a
otro procesador y su contexto se preserva; simplemente se suspende hasta que se procesa la interrupción. Entonces, el procesador comienza a ejecutar un hilo de interrupción. Hay un conjunto de hilos
de interrupción desactivados disponibles, por lo que no es necesario crear un nuevo hilo. A continuación ejecuta el hilo de interrupción para manejar la interrupción. Si la rutina de interrupción necesita
acceso a una estructura de memoria que ya está bloqueada por otro hilo en ejecución, el hilo de interrupción deberá esperar para acceder a dicha estructura. Un hilo de interrupción sólo puede ser expulsado por otro hilo de interrupción de mayor prioridad.
La experimentación con las interrupciones de Solaris indican que este enfoque proporciona un
rendimiento superior a las estrategias de manejo de interrupciones tradicionales [KLEI95].
4.6. GESTIÓN DE PROCESOS E HILOS EN LINUX
TAREAS LINUX
Un proceso, o tarea, en Linux se representa por una estructura de datos task_struct, que contiene
información de diversas categorías:
• Estado. El estado de ejecución del proceso (ejecutando, listo, suspendido, detenido, zombie).
Pasaremos a describirlo posteriormente.
• Información de planificación. Información necesitada por Linux para planificar procesos. Un
proceso puede ser normal o de tiempo real y tener una prioridad. Los procesos de tiempo real
se planifican antes que los procesos normales y, dentro de cada categoría, se pueden usar prioridades relativas. Hay un contador que lleva la cuenta de la cantidad de tiempo que un proceso
ha estado ejecutando.
• Identificadores. Cada proceso tiene un identificador único de proceso y también tiene identificadores de usuario y grupo. Un identificador de usuario se utiliza para asignar privilegios de
acceso a recursos a un grupo de procesos.
• Comunicación entre procesos. Linux soporta el mecanismo IPC encontrado en UNIX SVR4,
descrito en el Capítulo 6.
• Enlaces. Cada proceso incluye un enlace a sus padres, enlaces a sus hermanos (procesos con
el mismo padre), y enlaces a todos sus hijos.
• Tiempos y temporizadores. Incluye el tiempo de creación del proceso y la cantidad de tiempo de procesador consumido por el proceso hasta el momento. Un proceso también puede tener asociado uno o más temporizadores. Un proceso define un temporizador a través de una
llamada al sistema; como resultado se manda una señal al proceso cuando finaliza el temporizador. Un temporizador puede ser de un solo uso o periódico.
• Sistema de archivos. Incluye punteros a cualquier archivo abierto por este proceso, así como
punteros a los directorios actual y raíz para este proceso.
• Espacio de direcciones. Define el espacio de direcciones virtual asignado a este proceso.
04-Capitulo 4
194
12/5/05
16:20
Página 194
Sistemas operativos. Aspectos internos y principios de diseño
Parado
Señal
Creación
Señal
Estado
ejecutando
Listo
Planificando
Finalización
Ejecutando
Zombie
Evento
Señal o
evento
Ininterrumpible
Interrumpible
Figura 4.18.
Modelo de procesos e hilos en Linux.
• Contexto específico del procesador. La información de los registros y de la pila que constituyen el contexto de este proceso.
• Ejecutando. Este valor de estado se corresponde con dos estados. Un proceso Ejecutando
puede estar ejecutando o está listo para ejecutar.
• Interrumpible. Es un estado bloqueado, en el que el proceso está esperando por un evento, tal
como la finalización de una operación de E/S, la disponibilidad de un recurso o una señal de
otro proceso.
• Ininterrumpible. Éste es otro estado bloqueado. La diferencia entre este estado y el estado Interrumpible es que en el estado Ininterrumpible un proceso está esperando directamente sobre
un estado del hardware y por tanto no manejará ninguna señal.
• Detenido. El proceso ha sido parado y sólo puede ser reanudado por la acción positiva de otro
proceso. Por ejemplo, un proceso que está siendo depurado se puede poner en estado Parado.
• Zombie. El proceso se ha terminado pero, por alguna razón, todavía debe tener su estructura
de tarea en la tabla de procesos.
HILOS LINUX
Los sistemas UNIX tradicionales soportan un solo hilo de ejecución por proceso, mientras que los
sistemas UNIX modernos suelen proporcionar soporte para múltiples hilos de nivel de núcleo por
proceso. Como con los sistemas UNIX tradicionales, las versiones antiguas del núcleo de Linux no
ofrecían soporte multihilo. En su lugar, las aplicaciones debían escribirse con un conjunto de fun-
04-Capitulo 4
12/5/05
16:20
Página 195
Hilos, SMP y micronúcleos
195
ciones de biblioteca de nivel de usuario. La más popular de estas bibliotecas se conoce como biblioteca pthread (POSIX thread), en donde se asociaban todos los hilos en un único proceso a nivel
de núcleo. Hemos visto que las versiones modernas de UNIX ofrecen hilos a nivel de núcleo. Linux proporciona una solución única en la que no diferencia entre hilos y procesos. Utilizando un
mecanismo similar al de los procesos ligeros de Solaris, los hilos de nivel de usuario se asocian
con procesos de nivel de núcleo. Múltiples hilos de nivel de usuario que constituyen un único proceso de nivel de usuario, se asocian con procesos Linux a nivel de núcleo y comparten el mismo ID
de grupo. Esto permite a estos procesos compartir recursos tales como archivos y memorias y evitar la necesidad de un cambio de contexto cuando el planificador cambia entre procesos del mismo
grupo.
En Linux se crea un nuevo proceso copiando los atributos del proceso actual. Un nuevo proceso
se puede clonar de forma que comparte sus recursos, tales como archivos, manejadores de señales y
memoria virtual. Cuando los dos procesos comparten la misma memoria virtual, funcionan como hilos de un solo proceso. Sin embargo, no se define ningún tipo de estructura de datos independiente
para un hilo. En lugar del mandato normal fork(), los procesos se crean en Linux usando el mandato clone(). Este mandato incluye un conjunto de flags como argumentos, definidos en la Tabla 4.5.
La llamada al sistema tradicional fork() se implementa en Linux con la llamada al sistema clone() sin ningún flag.
Cuando el núcleo de Linux realiza un cambio de un proceso a otro, verifica si la dirección del directorio de páginas del proceso actual es la misma que en el proceso a ser planificado. Si lo es, están
compartiendo el mismo espacio de direcciones, por lo que el cambio de contexto consiste básicamente en saltar de una posición del código a otra.
Aunque los procesos clonados que son parte del mismo grupo de procesos, pueden compartir el
mismo espacio de memoria, no pueden compartir la misma pila de usuario. Por tanto, la llamada
clone() crea espacios de pila separados para cada proceso.
Tabla 4.5.
Flags de la llamada clone() de Linux.
CLONE_CLEARID
Borrar el ID de tarea.
CLONE_DETACHED
El padre no quiere el envío de la señal SIGCHLD en su finalización.
CLONE_FILES
Compartir la tabla que identifica los archivos abiertos.
CLONE_FS
Compartir la tabla que identifica al directorio raíz y al directorio actual de
trabajo, así como el valor de la máscara de bits utilizada para enmascarar
los permisos iniciales de un nuevo archivo.
CLONE_IDLETASK
Establecer el PID a cero, que se refiere a la tarea idle. La tarea idle se utiliza
cuando todas las tareas disponibles están bloqueadas esperando por recursos.
CLONE_NEWNS
Crear un nuevo espacio de nombres para el hijo.
CLONE_PARENT
El llamante y la nueva tarea comparten el mismo proceso padre.
CLONE_PTRACE
Si el proceso padre está siendo trazado, el proceso hijo también lo hará.
CLONE_SETTID
Escribir el TID en el espacio de usuario.
CLONE_SETTLS
Crear un nuevo TLS para el hijo.
CLONE_SIGHAND
Compartir la tabla que identifica los manejadores de señales.
04-Capitulo 4
196
12/5/05
16:20
Página 196
Sistemas operativos. Aspectos internos y principios de diseño
CLONE_SYSVSEM
Compartir la semántica SEM_UNDO de System V.
CLONE_THREAD
Insertar este proceso en el mismo grupo de hilos del padre. Si este flag
está activado, fuerza de forma implícita a CLONE_PARENT.
CLONE_VFORK
Si está activado, el padre no se planifica para ejecución hasta que el hijo
invoque la llamada al sistema execve().
CLONE_VM
Compartir el espacio de direcciones (descriptor de memoria y todas las tablas de páginas).
4.7. RESUMEN
Algunos sistemas operativos distinguen los conceptos de proceso e hilo, el primero relacionado
con la propiedad de recursos y el segundo relacionado con la ejecución de programas. Este enfoque podría llevar a mejorar la eficiencia y la conveniencia del código. En un sistema multihilo, se
pueden definir múltiples hilos concurrentes en un solo proceso. Esto se podría hacer utilizando
tanto hilos de nivel de usuario como hilos de nivel de núcleo. El sistema operativo desconce la
existencia de los hilos de nivel de usuario y se crean y gestionan por medio de una biblioteca de
hilos que ejecuta en el espacio de usuario de un proceso. Los hilos de nivel de usuario son muy
eficientes por que no se requiere ningún cambio de contexto para cambiar de uno a otro hilo. Sin
embargo, sólo puede estar ejecutando al mismo tiempo un único hilo de nivel de usuario, y si un
hilo se bloquea, el proceso entero hará lo mismo. Los hilos a nivel de núcleo son hilos de un proceso que se mantienen en el núcleo. Como son reconocidos por el núcleo, múltiples hilos del mismo proceso pueden ejecutar en paralelo en un multiprocesador y el bloqueo de un hilo no bloquea
al proceso completo. Sin embargo, se requiere un cambio de contexto para cambiar de un hilo a
otro.
El multiprocesamiento simétrico es un método de organizar un sistema multiprocesador de tal
forma que cualquier proceso (o hilo) puede ejecutar en cualquier procesador; esto incluye al código
del núcleo y a los procesos. Las arquitecturas SMP generan nuevos conceptos de diseño en los sistemas operativos y proporcionan mayor rendimiento que un sistema uniprocesador bajo las mismas
condiciones.
En los últimos años, ha habido mucho interés en el diseño de los sistemas operativos basados en
micronúcleo. En su forma pura, un sistema operativo micronúcleo consiste en un micronúcleo muy
pequeño que ejecuta en modo núcleo y que sólo contiene las funciones más esenciales y críticas del
sistema operativo. El resto de funciones del sistema operativo se implementa para ejecutar en modo
usuario y para utilizar el micronúcleo para servicios críticos. El diseño de tipo micronúcleo lleva a
implementaciones flexibles y altamente modulares. Sin embargo, todavía persisten algunas preguntas
sobre el rendimiento de estas arquitecturas.
4.8. LECTURAS RECOMENDADAS
[LEWI96] y [KLEI96] proporcionan una buena visión general del concepto de los hilos y una discusión sobre las estrategias de programación; el primero se centra más en conceptos y el último se centra más en programación, pero ambos proporcionan una buena cobertura de ambos temas. [PHAM96]
trata los hilos de Windows NT en profundidad. [ROBB04] tiene una buena cobertura sobre el concepto de los hilos en UNIX.
04-Capitulo 4
12/5/05
16:20
Página 197
Hilos, SMP y micronúcleos
197
[MUKH96] proporciona una buena discusión sobre los aspectos de diseño de los sistemas operativos para SMP. [CHAP97] contiene cinco artículos sobre las direcciones del diseño actual de los sistemas operativos multiprocesador. [LIED95] y [LIED96] contienen discusiones interesantes de los
principios del diseño de micronúcleos; el último se centra en aspectos del rendimiento.
CHAP97 Chapin, S., y Maccabe, A., eds. «Multiprocessor Operating Systems: Harnessing the Power.»
Special issue of IEEE Concurrency, Abril-Junio 1997.
KLEI96 Kleiman, S.; Shah, D.; y Smallders, B. Programming with Threads. Upper Saddle River, NJ:
Prentice Hall, 1996.
LEWI96 Lewis, B., y Berg, D. Threads Primer. Upper Saddle River, NJ: Prentice Hall, 1996.
LIED95 Liedtke, J. «On m-Kernel Construction.» Proceedings of the Fifteenth ACM Symposium on
Operating Systems Principles, Diciembre 1995.
LIED96 Liedtke, J. «Toward Real Microkernels.» Communications of the ACM, Septiembre 1996.
MUKH96 Mukherjee, B., y Karsten, S. «Operating Systems for Parallel Machines.» in Parallel Computers: Theory y Practice. Edited by T. Casavant, P. Tvrkik, y F. Plasil. Los Alamitos, CA: IEEE Computer Society Press, 1996.
PHAM96 Pham, T., y Garg, P. Multithreaded Programming with Windows NT. Upper Saddle River, NJ:
Prentice Hall, 1996.
ROBB04 Robbins, K., y Robbins, S. UNIX Systems Programming: Communication, Concurrency, y Threads. Upper Saddle River, NJ: Prentice Hall, 2004.
4.9. TÉRMINOS CLAVE, CUESTIONES DE REPASO Y PROBLEMAS
TÉRMINOS CLAVE
hilo
micronúcleo
procesos ligeros
hilos de nivel de núcleo (KLT)
multihilo
puerto
hilo de nivel de usuario (ULT)
multiprocesador simétrico (SMP) sistema operativo monolítico
mensaje
proceso
tarea
PREGUNTAS DE REVISIÓN
4.1.
La Tabla 3.5 enumera los elementos típicos que se encuentran en un bloque de control de
proceso para un sistema operativo monohilo. De éstos, ¿cuáles deben pertenecer a un bloque de control de hilo y cuáles deben pertenecer a un bloque de control de proceso para un
sistema multihilo?
4.2.
Enumere las razones por las que un cambio de contexto entre hilos puede ser más barato
que un cambio de contexto entre procesos.
4.3.
¿Cuáles son las dos características diferentes y potencialmente independientes en el concepto de proceso?
4.4.
Dé cuatro ejemplos generales del uso de hilos en un sistema multiprocesador monousuario.
4.5.
¿Qué recursos son compartidos normalmente por todos los hilos de un proceso?
4.6.
Enumere tres ventajas de los ULT sobre los KLT.
04-Capitulo 4
198
12/5/05
16:20
Página 198
Sistemas operativos. Aspectos internos y principios de diseño
4.7.
Enumere dos desventajas de los ULT en comparación con los KLT.
4.8.
Defina jacketing (revestimiento).
4.9.
Defina brevemente las diversas arquitecturas que se nombran en la Figura 4.8.
4.10. Enumere los aspectos principales de diseño de un sistema operativo SMP.
4.11. Dé ejemplos de servicios y funciones que se encuentran en un sistema operativo monolítico típico que podrían ser subsistemas externos en un sistema operativo micronúcleo.
4.12. Enumere y explique brevemente siete ventajas potenciales de un diseño micronúcleo en
comparación con un diseño monolítico.
4.13. Explique la desventaja potencial de rendimiento de un sistema operativo micronúcleo.
4.14. Enumere cuatro funciones que le gustaría encontrar incluso en un sistema operativo micronúcleo mínimo.
4.15. ¿Cuál es la forma básica de comunicación entre procesos o hilos en un sistema operativo
micronúcleo?
PROBLEMAS
4.1.
Se dijo que las dos ventajas de utilizar múltiples hilos dentro de un proceso son que (1) se
necesita menos trabajo en la creación de un nuevo hilo dentro de un proceso existente que
en la creación de un nuevo proceso, y (2) se simplifica la comunicación entre hilos del
mismo proceso. ¿Es cierto también que el cambio de contexto entre dos hilos del mismo
proceso conlleva menos trabajo que el cambio de contexto entre dos hilos de diferentes
procesos?
4.2.
En la discusión de ULT frente a KLT, se señaló que una desventaja de los ULT es que
cuando ejecutan una llamada al sistema, no sólo se bloquea ese hilo, sino todos los hilos
del proceso. ¿Por qué sucede esto?
4.3.
En OS/2, lo que normalmente abarca el concepto de proceso en otros sistemas operativos, se divide en tres tipos de entidad: sesión, procesos e hilos. Una sesión es una colección de uno o más hilos asociados con el interfaz de usuario (teclado, pantalla y ratón).
La sesión representa una aplicación de usuario interactiva, tal como un procesador de
textos o una hoja de cálculo. Este concepto permite al usuario abrir más de una aplicación, cada una de ellas con una o más ventanas en pantalla. El sistema operativo debe saber qué ventana, y por tanto qué sesión, está activa, de forma que las entradas del teclado
y del ratón se manden a la sesión apropiada. En todo momento, hay una sesión en primer
plano, y el resto de las sesiones está en segundo plano. Todas las entradas del teclado y
del ratón se redirigen a uno de los procesos de la sesión en primer plano, según lo indique la aplicación. Cuando una sesión está en primer plano, un proceso que realiza salida
gráfica, manda la señal directamente al buffer del hardware gráfico y, por tanto, a la pantalla. Cuando se pasa la sesión a segundo plano, se almacena el buffer del hardware gráfico en un buffer lógico de vídeo para esa sesión. Mientras que una sesión está en segundo plano, si cualquiera de los hilos de cualquiera de los procesos de esa sesión ejecuta y
produce alguna salida por pantalla, esa salida se redirige al buffer lógico de vídeo. Cuando la sesión vuelve a primer plano, se actualiza la pantalla para reflejar el contenido del
buffer lógico de vídeo.
Hay una forma de reducir el número de conceptos relacionados con los procesos en OS/2
de tres a dos: eliminar las sesiones y asociar el interfaz de usuario (teclado, pantalla y ra-
04-Capitulo 4
12/5/05
16:20
Página 199
Hilos, SMP y micronúcleos
199
tón) con procesos. De esta forma, sólo hay un proceso en primer plano. Para mayor estructuración, los procesos pueden estar formados por hilos.
a) ¿Qué beneficios se pierden en este enfoque?
b) Si utiliza estas modificaciones, ¿dónde asigna recursos (memoria, archivos, etc.): a nivel de proceso o de hilo?
4.4.
Considérese un entorno en el que hay una asociación uno a uno entre hilos de nivel de
usuario e hilos de nivel de núcleo que permite a uno o más hilos de un proceso ejecutar llamadas al sistema bloqueantes mientras otros hilos continúan ejecutando. Explique por qué
este modelo puede hacer que ejecuten más rápido los programas multihilo que sus correspondientes versiones monohilo en una máquina monoprocesador.
4.5.
Si un proceso termina y hay todavía ejecutando hilos de ese proceso, ¿continuarán ejecutando?
4.6.
El sistema operativo OS/390 para mainframe se estructura sobre los conceptos de espacio
de direcciones y tarea. Hablando de forma aproximada, un espacio de direcciones único
corresponde a una única aplicación y corresponde más o menos a un proceso en otros sistemas operativos. Dentro de un espacio de direcciones, varias tareas se pueden generar y
ejecutar de forma concurrente; esto corresponde, más o menos, al concepto de multihilo.
Hay dos estructuras de datos claves para gestionar esta estructura de tareas. Un bloque de
control del espacio de direcciones (ASCB) contiene información sobre un espacio de direcciones, y es requerido por el OS/390 esté o no ejecutando ese espacio de direcciones.
La información del ASBC incluye la prioridad de la planificación, memoria real y virtual
asignada a ese espacio de direcciones, el número de tareas listas en ese espacio de direcciones, y si cada una de ellas está en la zona de intercambio. Un bloque de control de tareas (TCB) representa a un programa de usuario en ejecución. Contiene información necesaria para gestionar una tarea dentro de un espacio de direcciones e incluye información del
estado del procesador, punteros a programas que son parte de esta tarea y el estado de ejecución de la tarea. Los ASCB son estructuras globales mantenidas en la memoria del sistema, mientras que los TCB son estructuras locales que se mantienen en su espacio de direcciones. ¿Cuál es la ventaja de dividir la información de control en porciones globales y
locales?
4.7.
Un multiprocesador con ocho procesadores tiene 20 unidades de cinta. Hay un gran número de trabajos enviados al sistema, cada uno de los cuales requiere un máximo de cuatro cintas para completar su ejecución. Suponga que cada trabajo comienza ejecutando
con sólo 3 unidades de cinta durante largo tiempo, antes de requerir la cuarta cinta durante
un periodo breve cerca de su finalización. Suponga también un número infinito de estos
trabajos.
a) Suponga que el planificador del sistema operativo no comenzará un trabajo a menos
que haya cuatro cintas disponibles. Cuando comienza un trabajo, se asignan cuatro cintas de forma inmediata y no se liberan hasta que el trabajo finaliza. ¿Cuál es el número
máximo de trabajos que pueden estar en progreso en un determinado momento? ¿Cuál
es el número máximo y mínimo de unidades de cinta que pueden estar paradas como resultado de esta política?
b) Sugiera una política alternativa para mejorar la utilización de las unidades de cinta y al
mismo tiempo evitar un interbloqueo en el sistema. ¿Cuál es el número máximo de trabajos que pueden estar en progreso en un determinado momento? ¿Cuáles son los límites del número de unidades de cinta paradas?
04-Capitulo 4
200
12/5/05
16:20
Página 200
Sistemas operativos. Aspectos internos y principios de diseño
4.8.
En la descripción de los estados de un ULT en Solaris, se dijo que un ULT podría ceder el
paso a otro ULT de la misma prioridad. ¿No sería posible que hubiera un hilo ejecutable de
mayor prioridad y que, por tanto, la función de cesión diera como resultado la cesión a un
hilo de la misma o de mayor prioridad?
05-Capitulo 5
12/5/05
16:21
Página 201
CAPÍTULO
5
Concurrencia. Exclusión
mutua y sincronización
5.1.
Principios de la concurrencia
5.2.
Exclusión mutua: soporte hardware
5.3.
Semáforos
5.4.
Monitores
5.5.
Paso de mensajes
5.6.
El problema de los Lectores/Escritores
5.7.
Resumen
5.8.
Lecturas recomendadas
5.9.
Términos clave, cuestiones de repaso y problemas
05-Capitulo 5
202
12/5/05
16:21
Página 202
Sistemas operativos. Aspectos internos y principios de diseño
Los temas centrales del diseño de sistemas operativos están todos relacionados con la gestión de
procesos e hilos:
• Multiprogramación. Gestión de múltiples procesos dentro de un sistema monoprocesador.
• Multiprocesamiento. Gestión de múltiples procesos dentro de un multiprocesador.
• Procesamiento distribuido. Gestión de múltiples procesos que ejecutan sobre múltiples sistemas de cómputo distribuidos.
La concurrencia es fundamental en todas estas áreas y en el diseño del sistema operativo. La concurrencia abarca varios aspectos, entre los cuales están la comunicación entre procesos y la compartición de, o competencia por, recursos, la sincronización de actividades de múltiples procesos y la reserva de tiempo de procesador para los procesos. Debemos entender que todos estos asuntos no sólo
suceden en el entorno del multiprocesamiento y el procesamiento distribuido, sino también en sistemas monoprocesador multiprogramados.
La concurrencia aparece en tres contextos diferentes:
• Múltiples aplicaciones. La multiprogramación fue ideada para permitir compartir dinámicamente el tiempo de procesamiento entre varias aplicaciones activas.
• Aplicaciones estructuradas. Como extensión de los principios del diseño modular y de la
programación estructurada, algunas aplicaciones pueden ser programadas eficazmente como
un conjunto de procesos concurrentes.
• Estructura del sistema operativo. Las mismas ventajas constructivas son aplicables a la programación de sistemas y, de hecho, los sistemas operativos son a menudo implementados en sí
mismos como un conjunto de procesos o hilos.
Dada la importancia de este tema, cuatro de los capítulos de este libro tratan aspectos relacionados con la concurrencia. Este capítulo y el siguiente tratan de la concurrencia en sistemas multiprogramados y con multiproceso. Los Capítulos 13 y 14 examinan aspectos de la concurrencia en relación con el procesamiento distribuido. Aunque el resto de este libro cubre cierto número de otros
temas importantes del diseño del sistema operativo, en nuestra opinión la concurrencia juega un papel
principal frente a todos estos otros temas.
Este capítulo comienza con una introducción al concepto de concurrencia y a las implicaciones
de la ejecución de múltiples procesos concurrentes1. Se descubre que el requisito básico para conseguir ofrecer procesos concurrentes es la capacidad de hacer imperar la exclusión mutua; esto es, la
capacidad de impedir a cualquier proceso realizar una acción mientras se le haya permitido a otro.
Después, se examinan algunos mecanismos hardware que pueden permitir conseguir exclusión mutua. Entonces, se verán soluciones que no requieren espera activa y que pueden ser tanto ofrecidas
por el sistema operativo como impuestas por el compilador del lenguaje. Se examinan tres propuestas: semáforos, monitores y paso de mensajes.
Se utilizan dos problemas clásicos de concurrencia para ilustrar los conceptos y comparar las propuestas presentadas en este capítulo. El problema productor/consumidor se introduce pronto para utilizarse luego como ejemplo. El capítulo se cierra con el problema de los lectores/escritores.
1
Por simplicidad, generalmente nos referiremos a la ejecución concurrente de procesos. De hecho, como se ha visto en el capítulo anterior, en algunos sistemas la unidad fundamental de concurrencia es el hilo en vez del proceso.
05-Capitulo 5
12/5/05
16:21
Página 203
Concurrencia. Exclusión mutua y sincronización
Tabla 5.1.
203
Algunos términos clave relacionados con la concurrencia.
sección crítica
(critical section)
Sección de código dentro de un proceso que requiere acceso a recursos
compartidos y que no puede ser ejecutada mientras otro proceso esté en
una sección de código correspondiente.
interbloqueo
(deadlock)
Situación en la cual dos o más procesos son incapaces de actuar porque
cada uno está esperando que alguno de los otros haga algo.
círculo vicioso
(livelock)
Situación en la cual dos o más procesos cambian continuamente su estado
en respuesta a cambios en los otros procesos, sin realizar ningún trabajo
útil.
exclusión mutua
(mutual exclusion)
Requisito de que cuando un proceso esté en una sección crítica que accede a recursos compartidos, ningún otro proceso pueda estar en una sección crítica que acceda a ninguno de esos recursos compartidos.
condición de carrera
(race condition)
Situación en la cual múltiples hilos o procesos leen y escriben un dato
compartido y el resultado final depende de la coordinación relativa de sus
ejecuciones.
inanición
(starvation)
Situación en la cual un proceso preparado para avanzar es soslayado indefinidamente por el planificador; aunque es capaz de avanzar, nunca se le
escoge.
La exposición sobre la concurrencia continúa en el Capítulo 6 y la discusión de los mecanismos
de concurrencia de nuestros sistemas de ejemplo se pospone hasta el final del capítulo.
La Tabla 5.1 muestra algunos términos clave relacionados con la concurrencia.
5.1. PRINCIPIOS DE LA CONCURRENCIA
En un sistema multiprogramado de procesador único, los procesos se entrelazan en el tiempo para
ofrecer la apariencia de ejecución simultánea (Figura 2.12a). Aunque no se consigue procesamiento
paralelo real, e ir cambiando de un proceso a otro supone cierta sobrecarga, la ejecución entrelazada
proporciona importantes beneficios en la eficiencia del procesamiento y en la estructuración de los
programas. En un sistema de múltiples procesadores no sólo es posible entrelazar la ejecución de
múltiples procesos sino también solaparlas (Figura 2.12b).
En primera instancia, puede parecer que el entrelazado y el solapamiento representan modos de
ejecución fundamentalmente diferentes y que presentan diferentes problemas. De hecho, ambas técnicas pueden verse como ejemplos de procesamiento concurrente y ambas presentan los mismos
problemas. En el caso de un monoprocesador, los problemas surgen de una característica básica de
los sistemas multiprogramados: no puede predecirse la velocidad relativa de ejecución de los procesos. Ésta depende de la actividad de los otros procesos, de la forma en que el sistema operativo maneja las interrupciones y de las políticas de planificación del sistema operativo. Se plantean las siguientes dificultades:
1. La compartición de recursos globales está cargada de peligros. Por ejemplo, si dos procesos
utilizan ambos la misma variable global y ambos realizan lecturas y escrituras sobre esa variable, entonces el orden en que se ejecuten las lecturas y escrituras es crítico. En la siguiente
subsección se muestra un ejemplo de este problema.
2. Para el sistema operativo es complicado gestionar la asignación de recursos de manera óptima. Por ejemplo, el proceso A puede solicitar el uso de un canal concreto de E/S, y serle concedido el control, y luego ser suspendido justo antes de utilizar ese canal. Puede no ser desea-
05-Capitulo 5
204
12/5/05
16:21
Página 204
Sistemas operativos. Aspectos internos y principios de diseño
ble que el sistema operativo simplemente bloquee el canal e impida su utilización por otros
procesos; de hecho esto puede conducir a una condición de interbloqueo, tal como se describe en el Capítulo 6.
3. Llega a ser muy complicado localizar errores de programación porque los resultados son típicamente no deterministas y no reproducibles (por ejemplo, véase en [LEBL87, CARR89,
SHEN02] discusiones sobre este asunto).
Todas las dificultades precedentes se presentan también en un sistema multiprocesador, porque
aquí tampoco es predecible la velocidad relativa de ejecución de los procesos. Un sistema multiprocesador debe bregar también con problemas derivados de la ejecución simultánea de múltiples procesos. Sin embargo, fundamentalmente los problemas son los mismos que aquéllos de un sistema monoprocesador. Esto quedará claro a medida que la exposición avance.
UN EJEMPLO SENCILLO
Considere el siguiente procedimiento:
void eco ()
{
cent = getchar();
csal = cent;
putchar(csal);
}
Este procedimiento muestra los elementos esenciales de un programa que proporcionará un procedimiento de eco de un carácter; la entrada se obtiene del teclado, una tecla cada vez. Cada carácter
introducido se almacena en la variable cent. Luego se transfiere a la variable csal y se envía a la
pantalla. Cualquier programa puede llamar repetidamente a este procedimiento para aceptar la entrada del usuario y mostrarla por su pantalla.
Considere ahora que se tiene un sistema multiprogramado de un único procesador y para un único usuario. El usuario puede saltar de una aplicación a otra, y cada aplicación utiliza el mismo teclado para la entrada y la misma pantalla para la salida. Dado que cada aplicación necesita usar el procedimiento eco, tiene sentido que éste sea un procedimiento compartido que esté cargado en una
porción de memoria global para todas las aplicaciones. De este modo, sólo se utiliza una única copia
del procedimiento eco, economizándose espacio.
La compartición de memoria principal entre procesos es útil para permitir una interacción eficiente y próxima entre los procesos. No obstante, esta interacción puede acarrear problemas. Considere la siguiente secuencia:
1. El proceso P1 invoca el procedimiento eco y es interrumpido inmediatamente después de que
getchar devuelva su valor y sea almacenado en cent. En este punto, el carácter introducido
más recientemente, x, está almacenado en cent.
2. El proceso P2 se activa e invoca al procedimiento eco, que ejecuta hasta concluir, habiendo
leído y mostrado en pantalla un único carácter, y.
3. Se retoma el proceso P1. En este instante, el valor x ha sido sobrescrito en cent y por tanto se
ha perdido. En su lugar, cent contiene y, que es transferido a csal y mostrado.
05-Capitulo 5
12/5/05
16:21
Página 205
Concurrencia. Exclusión mutua y sincronización
205
Así, el primer carácter se pierde y el segundo se muestra dos veces. La esencia de este problema
es la variable compartida global, cent. Múltiples procesos tienen acceso a esta variable. Si un proceso actualiza la variable global y justo entonces es interrumpido, otro proceso puede alterar la variable antes de que el primer proceso pueda utilizar su valor. Suponga ahora, que decidimos que sólo un
proceso al tiempo pueda estar en dicho procedimiento. Entonces la secuencia anterior resultaría como
sigue:
1. El proceso P1 invoca el procedimiento eco y es interrumpido inmediatamente después de que
concluya la función de entrada. En este punto, el carácter introducido más recientemente, x,
está almacenado en cent.
2. El proceso P2 se activa e invoca al procedimiento eco. Sin embargo, dado que P1 está todavía dentro del procedimiento eco, aunque actualmente suspendido, a P2 se le impide entrar
en el procedimiento. Por tanto, P2 se suspende esperando la disponibilidad del procedimiento
eco.
3. En algún momento posterior, el proceso P1 se retoma y completa la ejecución de eco. Se
muestra el carácter correcto, x.
4. Cuando P1 sale de eco, esto elimina el bloqueo de P2. Cuando P2 sea más tarde retomado,
invocará satisfactoriamente el procedimiento eco.
La lección a aprender de este ejemplo es que es necesario proteger las variables globales compartidas (así como otros recursos globales compartidos) y que la única manera de hacerlo es controlar el
código que accede a la variable. Si imponemos la disciplina de que sólo un proceso al tiempo pueda
entrar en eco y que una vez en eco el procedimiento debe ejecutar hasta completarse antes de estar
disponible para otro proceso, entonces el tipo de error que se acaba de describir no ocurrirá. Cómo se
puede imponer esa disciplina es uno de los temas capitales de este capítulo.
Este problema fue enunciado con la suposición de que se trataba de un sistema operativo multiprogramado para un monoprocesador. El ejemplo demuestra que los problemas de la concurrencia suceden incluso cuando hay un único procesador. En un sistema multiprocesador, aparecen los mismos
problemas de recursos compartidos protegidos, y funcionan las mismas soluciones. Primero, supóngase que no hay mecanismo para controlar los accesos a la variable global compartida:
1. Los procesos P1 y P2 están ambos ejecutando, cada cual en un procesador distinto. Ambos
procesos invocan el procedimiento eco.
2. Ocurren los siguientes eventos; los eventos en la misma línea suceden en paralelo.
Proceso P1
Proceso P2
•
cent = getchar();
•
csal = cent;
putchar(csal);
•
•
•
•
cent = getchar();
csal = cent;
•
putchar(csal);
•
El resultado es que el carácter introducido a P1 se pierde antes de ser mostrado, y el carácter
introducido a P2 es mostrado por ambos P1 y P2. De nuevo, añádase la capacidad de cumplir la
05-Capitulo 5
206
12/5/05
16:21
Página 206
Sistemas operativos. Aspectos internos y principios de diseño
disciplina de que sólo un proceso al tiempo pueda estar en eco. Entonces, sucede la siguiente
secuencia:
1. Los procesos P1 y P2 están ambos ejecutando, cada cual en un procesador distinto. Ambos
procesos invocan al procedimiento eco.
2. Mientras P1 está dentro del procedimiento eco, P2 invoca a eco. Dado que P1 está todavía
dentro del procedimiento eco (ya esté P1 suspendido o ejecutando), a P2 se le bloqueará la
entrada al procedimiento. Por tanto, P2 se suspende en espera de la disponibilidad del procedimiento eco.
3. En algún momento posterior, el proceso P1 completa la ejecución de eco, sale del procedimiento y continúa ejecutando. Inmediatamente después de que P1 salga de eco, se retoma P2
que comienza la ejecución de eco.
En el caso de un sistema monoprocesador, el motivo por el que se tiene un problema es que una
interrupción puede parar la ejecución de instrucciones en cualquier punto de un proceso. En el caso
de un sistema multiprocesador, se tiene el mismo motivo y, además, puede suceder porque dos procesos pueden estar ejecutando simultáneamente y ambos intentando acceder a la misma variable global.
Sin embargo, la solución a ambos tipos de problema es la misma: controlar los accesos a los recursos
compartidos.
CONDICIÓN DE CARRERA
Una condición de carrera sucede cuando múltiples procesos o hilos leen y escriben datos de manera
que el resultado final depende del orden de ejecución de las instrucciones en los múltiples procesos.
Consideremos dos casos sencillos.
Como primer ejemplo, suponga que dos procesos, P1 y P2, comparten la variable global a.
En algún punto de su ejecución, P1 actualiza a al valor 1 y, en el mismo punto en su ejecución,
P2 actualiza a al valor 2. Así, las dos tareas compiten en una carrera por escribir la variable a.
En este ejemplo el «perdedor» de la carrera (el proceso que actualiza el último) determina el valor de a.
Para nuestro segundo ejemplo, considere dos procesos, P3 y P4, que comparten las variables globales b y c, con valores iniciales b = 1 y c = 2. En algún punto de su ejecución, P3 ejecuta la asignación b = b + c y, en algún punto de su ejecución, P4 ejecuta la asignación c = b + c. Note que
los dos procesos actualizan diferentes variables. Sin embargo, los valores finales de las dos variables
dependen del orden en que los dos procesos ejecuten estas dos asignaciones. Si P3 ejecuta su sentencia de asignación primero, entonces los valores finales serán b = 3 y c = 5. Si P4 ejecuta su sentencia
de asignación primero, entonces los valores finales serán b = 4 y c = 3.
El Apéndice A trata sobre las condiciones de carrera, utilizando los semáforos como ejemplo.
PREOCUPACIONES DEL SISTEMA OPERATIVO
¿Qué aspectos de diseño y gestión surgen por la existencia de la concurrencia? Pueden enumerarse
las siguientes necesidades:
1. El sistema operativo debe ser capaz de seguir la pista de varios procesos. Esto se consigue con
el uso de bloques de control de proceso y fue descrito en el Capítulo 4.
05-Capitulo 5
12/5/05
16:21
Página 207
Concurrencia. Exclusión mutua y sincronización
207
2. El sistema operativo debe ubicar y desubicar varios recursos para cada proceso activo. Estos
recursos incluyen:
• Tiempo de procesador. Esta es la misión de la planificación, tratada en la Parte Cuatro.
• Memoria. La mayoría de los sistemas operativos usan un esquema de memoria virtual. El
tema es abordado en la Parte Tres.
• Ficheros. Tratados en el Capítulo 12.
• Dispositivos de E/S. Tratados en el Capítulo 11.
3. El sistema operativo debe proteger los datos y recursos físicos de cada proceso frente a interferencias involuntarias de otros procesos. Esto involucra técnicas que relacionan memoria, ficheros y dispositivos de E/S. En el Capítulo 15 se encuentra tratado en general el tema de la
protección.
4. El funcionamiento de un proceso y el resultado que produzca, debe ser independiente de la velocidad a la que suceda su ejecución en relación con la velocidad de otros procesos concurrentes. Este es el tema de este capítulo.
Para entender cómo puede abordarse la cuestión de la independencia de la velocidad, necesitamos ver las formas en que los procesos pueden interaccionar.
INTERACCIÓN DE PROCESOS
Podemos clasificar las formas en que los procesos interaccionan en base al grado en que perciben la
existencia de cada uno de los otros. La Tabla 5.2 enumera tres posibles grados de percepción más las
consecuencias de cada uno:
• Procesos que no se perciben entre sí. Son procesos independientes que no se pretende que
trabajen juntos. El mejor ejemplo de esta situación es la multiprogramación de múltiples procesos independientes. Estos bien pueden ser trabajos por lotes o bien sesiones interactivas o
una mezcla. Aunque los procesos no estén trabajando juntos, el sistema operativo necesita preocuparse de la competencia por recursos. Por ejemplo, dos aplicaciones independientes pueden querer ambas acceder al mismo disco, fichero o impresora. El sistema operativo debe regular estos accesos.
• Procesos que se perciben indirectamente entre sí. Son procesos que no están necesariamente al tanto de la presencia de los demás mediante sus respectivos ID de proceso, pero que comparten accesos a algún objeto, como un buffer de E/S. Tales procesos exhiben cooperación en
la compartición del objeto común.
• Procesos que se perciben directamente entre sí. Son procesos capaces de comunicarse entre
sí vía el ID del proceso y que son diseñados para trabajar conjuntamente en cierta actividad.
De nuevo, tales procesos exhiben cooperación.
Las condiciones no serán siempre tan claras como se sugiere en la Tabla 5.2. Mejor dicho, algunos procesos pueden exhibir aspectos tanto de competición como de cooperación. No obstante, es
constructivo examinar cada uno de los tres casos de la lista precedente y determinar sus implicaciones para el sistema operativo.
05-Capitulo 5
208
12/5/05
16:21
Página 208
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 5.2.
Grado de percepción
Relación
Interacción de procesos.
Influencia que un
proceso tiene sobre
el otro
Potenciales
problemas
de control
Procesos que no se
perciben entre sí
Competencia
• Los resultados de un
proceso son
independientes de la
acción de los otros
La temporización del
proceso puede verse
afectada
• Exclusión mutua
• Interbloqueo
(recurso renovable)
• Inanición
Procesos que se
perciben
indirectamente
entre sí
(por ejemplo,
objeto compartido)
Cooperación por
compartición
• Los resultados de un
proceso pueden
depender de la
información obtenida
de otros
• La temporización del
proceso puede verse
afectada
• Exclusión mutua
• Interbloqueo
(recurso renovable)
• Inanición
• Coherencia de datos
Procesos que se
perciben directamente
entre sí (tienen
primitivas de
comunicación a su
disposición)
Cooperación por
comunicación
• Los resultados de un
proceso pueden
depender de la
información obtenida
de otros
• La temporización del
proceso puede verse
afectada
• Interbloqueo
(recurso consumible)
• Inanición
Competencia entre procesos por recursos. Los procesos concurrentes entran en conflicto entre ellos cuando compiten por el uso del mismo recurso. En su forma pura, puede describirse la situación como sigue. Dos o más procesos necesitan acceso a un recurso durante el curso de su ejecución.
Ningún proceso se percata de la existencia de los otros procesos y ninguno debe verse afectado por la
ejecución de los otros procesos. Esto conlleva que cada proceso debe dejar inalterado el estado de
cada recurso que utilice. Ejemplos de recursos son los dispositivos de E/S, la memoria, el tiempo de
procesador y el reloj.
No hay intercambio de información entre los procesos en competencia. No obstante, la ejecución
de un proceso puede afectar al comportamiento de los procesos en competencia. En concreto, si dos
procesos desean ambos acceder al mismo recurso único, entonces, el sistema operativo reservará el
recurso para uno de ellos, y el otro tendrá que esperar. Por tanto, el proceso al que se le deniega el acceso será ralentizado. En un caso extremo, el proceso bloqueado puede no conseguir nunca el recurso
y por tanto no terminar nunca satisfactoriamente.
En el caso de procesos en competencia, deben afrontarse tres problemas de control. Primero está
la necesidad de exclusión mutua. Supóngase que dos o más procesos requieren acceso a un recurso
único no compartible, como una impresora. Durante el curso de la ejecución, cada proceso estará
enviando mandatos al dispositivo de E/S, recibiendo información de estado, enviando datos o recibiendo datos. Nos referiremos a tal recurso como un recurso crítico, y a la porción del programa
que lo utiliza como la sección crítica del programa. Es importante que sólo se permita un programa
al tiempo en su sección crítica. No podemos simplemente delegar en el sistema operativo para que
entienda y aplique esta restricción porque los detalles de los requisitos pueden no ser obvios. En el
caso de una impresora, por ejemplo, queremos que cualquier proceso individual tenga el control de
05-Capitulo 5
12/5/05
16:21
Página 209
Concurrencia. Exclusión mutua y sincronización
209
la impresora mientras imprime el fichero completo. De otro modo, las líneas de los procesos en
competencia se intercalarían.
La aplicación de la exclusión mutua crea dos problemas de control adicionales. Uno es el del interbloqueo. Por ejemplo, considere dos procesos, P1 y P2, y dos recursos, R1 y R2. Suponga que
cada proceso necesita acceder a ambos recursos para realizar parte de su función. Entonces es posible
encontrarse la siguiente situación: el sistema operativo asigna R1 a P2, y R2 a P1. Cada proceso está
esperando por uno de los dos recursos. Ninguno liberará el recurso que ya posee hasta haber conseguido el otro recurso y realizado la función que requiere ambos recursos. Los dos procesos están interbloqueados.
Un último problema de control es la inanición. Suponga que tres procesos (P1, P2, P3) requieren
todos accesos periódicos al recurso R. Considere la situación en la cual P1 está en posesión del recurso y P2 y P3 están ambos retenidos, esperando por ese recurso. Cuando P1 termine su sección crítica,
debería permitírsele acceso a R a P2 o P3. Asúmase que el sistema operativo le concede acceso a P3 y
que P1 solicita acceso otra vez antes de completar su sección crítica. Si el sistema operativo le concede acceso a P1 después de que P3 haya terminado, y posteriormente concede alternativamente acceso
a P1 y a P3, entonces a P2 puede denegársele indefinidamente el acceso al recurso, aunque no suceda
un interbloqueo.
El control de la competencia involucra inevitablemente al sistema operativo ya que es quien ubica los recursos. Además, los procesos necesitarán ser capaces por sí mismos de expresar de alguna
manera el requisito de exclusión mutua, como bloqueando el recurso antes de usarlo. Cualquier solución involucrará algún apoyo del sistema operativo, como proporcionar un servicio de bloqueo. La
Figura 5.1 ilustra el mecanismo de exclusión mutua en términos abstractos. Hay n procesos para ser
ejecutados concurrentemente. Cada proceso incluye (1) una sección crítica que opera sobre algún recurso Ra, y (2) código adicional que precede y sucede a la sección crítica y que no involucra acceso a
Ra. Dado que todos los procesos acceden al mismo recurso Ra, se desea que sólo un proceso esté en
su sección crítica al mismo tiempo. Para aplicar exclusión mutua se proporcionan dos funciones: entrarcritica y salircritica. Cada función toma como un argumento el nombre del recurso sujeto de la competencia. A cualquier proceso que intente entrar en su sección crítica mientras otro proceso está en su sección crítica, por el mismo recurso, se le hace esperar.
/* PROCESO 1 */
void P1
{
while (true)
{
/* código anterior */;
entrarcritica (Ra);
/* sección crítica */;
salircritica (Ra);
/* código posterior */;
}
}
/* PROCESO 2 */
void P2
{
while (true)
{
/* código anterior */;
entrarcritica (Ra);
/* sección crítica */;
salircritica (Ra);
/* código posterior */;
}
}
Figura 5.1.
• • •
/* PROCESO n */
void Pn
{
while (true)
{
/* código anterior */;
entrarcritica (Ra);
/* sección crítica */;
salircritica (Ra);
/* código posterior */;
}
}
Ilustración de la exclusión mutua.
05-Capitulo 5
210
12/5/05
16:21
Página 210
Sistemas operativos. Aspectos internos y principios de diseño
Falta examinar mecanismos específicos para proporcionar las funciones entrarcritica y salircritica. Por el momento, postergamos este asunto mientras consideramos los otros casos de
interacción de procesos.
Cooperación entre procesos vía compartición. El caso de cooperación vía compartición cubre
procesos que interaccionan con otros procesos sin tener conocimiento explícito de ellos. Por ejemplo,
múltiples procesos pueden tener acceso a variables compartidas o a ficheros o bases de datos compartidas. Los procesos pueden usar y actualizar los datos compartidos sin referenciar otros procesos pero
saben que otros procesos pueden tener acceso a los mismos datos. Así, los procesos deben cooperar
para asegurar que los datos que comparten son manipulados adecuadamente. Los mecanismos de
control deben asegurar la integridad de los datos compartidos.
Dado que los datos están contenidos en recursos (dispositivos, memoria), los problemas de control de exclusión mutua, interbloqueo e inanición están presentes de nuevo. La única diferencia es
que los datos individuales pueden ser accedidos de dos maneras diferentes, lectura y escritura, y sólo
las operaciones de escritura deben ser mutuamente exclusivas.
Sin embargo, por encima de estos problemas, surge un nuevo requisito: el de la coherencia de datos. Como ejemplo sencillo, considérese una aplicación de contabilidad en la que pueden ser actualizados varios datos individuales. Supóngase que dos datos individuales a y b han de ser mantenidos en
la relación a = b. Esto es, cualquier programa que actualice un valor, debe también actualizar el otro
para mantener la relación. Considérense ahora los siguientes dos procesos:
P1:
a = a + 1;
b = b + 1;
P2:
b = 2 * b;
a = 2 * a;
Si el estado es inicialmente consistente, cada proceso tomado por separado dejará los datos compartidos en un estado consistente. Ahora considere la siguiente ejecución concurrente en la cual los
dos procesos respetan la exclusión mutua sobre cada dato individual (a y b).
a = a + 1;
b = 2 * b;
b = b + 1;
a = 2 * a;
Al final de la ejecución de esta secuencia, la condición a = b ya no se mantiene. Por ejemplo, si
se comienza con a = b = 1, al final de la ejecución de esta secuencia, tendremos a = 4 y b = 3. El
problema puede ser evitado declarando en cada proceso la secuencia completa como una sección
crítica.
Así se ve que el concepto de sección crítica es importante en el caso de cooperación por compartición. Las mismas funciones abstractas entrarcritica y salircritica tratadas anteriormente
(Figura 5.1) pueden usarse aquí. En este caso, el argumento de las funciones podría ser una variable,
un fichero o cualquier otro objeto compartido. Es más, si las secciones críticas se utilizan para conse-
05-Capitulo 5
12/5/05
16:21
Página 211
Concurrencia. Exclusión mutua y sincronización
211
guir integridad de datos, entonces puede no haber recurso o variable concreta que pueda ser identificada como argumento. En este caso, puede pensarse en el argumento como un identificador compartido entre los procesos concurrentes que identifica las secciones críticas que deben ser mutuamente
exclusivas.
Cooperación entre procesos vía comunicación. En los dos primeros casos que se han tratado,
cada proceso tiene su propio entorno aislado que no incluye a los otros procesos. Las interacciones
entre procesos son indirectas. En ambos casos hay cierta compartición. En el caso de la competencia,
hay recursos compartidos sin ser conscientes de los otros procesos. En el segundo caso, hay compartición de valores y aunque cada proceso no es explícitamente consciente de los demás procesos, es
consciente de la necesidad de mantener integridad de datos. Cuando los procesos cooperan vía comunicación, en cambio, los diversos procesos involucrados participan en un esfuerzo común que los vincula a todos ellos. La comunicación proporciona una manera de sincronizar o coordinar actividades
varias.
Típicamente, la comunicación se fundamenta en mensajes de algún tipo. Las primitivas de envío
y recepción de mensajes deben ser proporcionadas como parte del lenguaje de programación o por el
núcleo del sistema operativo.
Dado que en el acto de pasar mensajes los procesos no comparten nada, la exclusión mutua no
es un requisito de control en este tipo de cooperación. Sin embargo, los problemas de interbloqueo e
inanición están presentes. Como ejemplo de interbloqueo, dos procesos pueden estar bloqueados,
cada uno esperando por una comunicación del otro. Como ejemplo de inanición, considérense tres
procesos, P1, P2 y P3, que muestran el siguiente comportamiento. P1 está intentando repetidamente
comunicar con P2 o con P3, y P2 y P3 están ambos intentando comunicar con P1. Podría suceder
una secuencia en la cual P1 y P2 intercambiasen información repetidamente, mientras P3 está bloqueado esperando comunicación de P1. No hay interbloqueo porque P1 permanece activo, pero P3
pasa hambre.
REQUISITOS PARA LA EXCLUSIÓN MUTUA
Cualquier mecanismo o técnica que vaya a proporcionar exclusión mutua debería cumplimentar los
siguientes requisitos:
1. La exclusión mutua debe hacerse cumplir: sólo se permite un proceso al tiempo dentro de su
sección crítica, de entre todos los procesos que tienen secciones críticas para el mismo recurso
u objeto compartido.
2. Un proceso que se pare en su sección no crítica debe hacerlo sin interferir con otros procesos.
3. No debe ser posible que un proceso que solicite acceso a una sección crítica sea postergado indefinidamente: ni interbloqueo ni inanición.
4. Cuando ningún proceso esté en una sección crítica, a cualquier proceso que solicite entrar en
su sección crítica debe permitírsele entrar sin demora.
5. No se hacen suposiciones sobre las velocidades relativas de los procesos ni sobre el número de
procesadores.
6. Un proceso permanece dentro de su sección crítica sólo por un tiempo finito.
Hay varias maneras de satisfacer los requisitos para la exclusión mutua. Una manera es delegar
la responsabilidad en los procesos que desean ejecutar concurrentemente. Esos procesos, ya sean
05-Capitulo 5
212
12/5/05
16:21
Página 212
Sistemas operativos. Aspectos internos y principios de diseño
programas del sistema o programas de aplicación, estarían obligados a coordinarse entre sí para
cumplir la exclusión mutua, sin apoyo del lenguaje de programación ni del sistema operativo. Podemos referirnos a esto como soluciones software. Aunque este enfoque es propenso a una alta sobrecarga de procesamiento y a errores, sin duda es útil examinar estas propuestas para obtener una mejor comprensión de la complejidad de la programación concurrente. Este tema se cubre en el
Apéndice A. Un segundo enfoque es proporcionar cierto nivel de soporte dentro del sistema operativo o del lenguaje de programación. Tres de los más importantes de estos enfoques se examinan en
las Secciones 5.3 a 5.5.
5.2. EXCLUSIÓN MUTUA: SOPORTE HARDWARE
Se han desarrollado cierto número de algoritmos software para conseguir exclusión mutua, de los
cuales el más conocido es el algoritmo de Dekker. La solución software es fácil que tenga una alta sobrecarga de procesamiento y es significativo el riesgo de errores lógicos. No obstante, el estudio de
estos algoritmos ilustra muchos de los conceptos básicos y de los potenciales problemas del desarrollo de programas concurrentes. Para el lector interesado, el Apéndice A incluye un análisis de las soluciones software. En esta sección se consideran varias interesantes soluciones hardware a la exclusión mutua.
DESHABILITAR INTERRUPCIONES
En una máquina monoprocesador, los procesos concurrentes no pueden solaparse, sólo pueden entrelazarse. Es más, un proceso continuará ejecutando hasta que invoque un servicio del sistema operativo o hasta que sea interrumpido. Por tanto, para garantizar la exclusión mutua, basta con impedir que
un proceso sea interrumpido. Esta técnica puede proporcionarse en forma de primitivas definidas por
el núcleo del sistema para deshabilitar y habilitar las interrupciones. Un proceso puede cumplir la exclusión mutua del siguiente modo (compárese con la Figura 5.1):
while (true)
{
/* deshabilitar interrupciones */;
/* sección crítica */;
/* habilitar interrupciones */;
/* resto */;
}
Dado que la sección crítica no puede ser interrumpida, se garantiza la exclusión mutua. El precio
de esta solución, no obstante, es alto. La eficiencia de ejecución podría degradarse notablemente porque se limita la capacidad del procesador de entrelazar programas. Un segundo problema es que esta
solución no funcionará sobre una arquitectura multiprocesador. Cuando el sistema de cómputo incluye más de un procesador, es posible (y típico) que se estén ejecutando al tiempo más de un proceso.
En este caso, deshabilitar interrupciones no garantiza exclusión mutua.
INSTRUCCIONES MÁQUINA ESPECIALES
En una configuración multiprocesador, varios procesadores comparten acceso a una memoria principal común. En este caso no hay una relación maestro/esclavo; en cambio los procesadores se compor-
05-Capitulo 5
12/5/05
16:21
Página 213
Concurrencia. Exclusión mutua y sincronización
213
tan independientemente en una relación de igualdad. No hay mecanismo de interrupción entre procesadores en el que pueda basarse la exclusión mutua.
A un nivel hardware, como se mencionó, el acceso a una posición de memoria excluye cualquier
otro acceso a la misma posición. Con este fundamento, los diseñadores de procesadores han propuesto varias instrucciones máquina que llevan a cabo dos acciones atómicamente2, como leer y escribir o
leer y comprobar, sobre una única posición de memoria con un único ciclo de búsqueda de instrucción. Durante la ejecución de la instrucción, el acceso a la posición de memoria se le bloquea a toda
otra instrucción que referencie esa posición. Típicamente, estas acciones se realizan en un único ciclo
de instrucción.
En esta sección, se consideran dos de las instrucciones implementadas más comúnmente. Otras
están descritas en [RAYN86] y [STON93].
Instrucción Test and Set. La instrucción test and set (comprueba y establece) puede definirse
como sigue:
boolean testset (int i)
{
if (i == 0)
{
i = 1;
return true;
}
else
{
return false;
}
}
La instrucción comprueba el valor de su argumento i. Si el valor es 0, entonces la instrucción reemplaza el valor por 1 y devuelve cierto. En caso contrario, el valor no se cambia y devuelve falso.
La función testset completa se realiza atómicamente; esto es, no está sujeta a interrupción.
La Figura 5.2a muestra un protocolo de exclusión mutua basado en el uso de esta instrucción. La
construcción paralelos (P1, P2, …, Pn) significa lo siguiente: suspender la ejecución del programa
principal; iniciar la ejecución concurrente de los procedimientos P1, P2, …, Pn; cuando todos los P1,
P2, …, Pn hayan terminado, retomar al programa principal. Una variable compartida cerrojo se inicializa a 0. El único proceso que puede entrar en su sección crítica es aquél que encuentra la variable
cerrojo igual a 0. Todos los otros procesos que intenten entrar en su sección crítica caen en un
modo de espera activa. El término espera activa (busy waiting), o espera cíclica (spin waiting) se refiere a una técnica en la cual un proceso no puede hacer nada hasta obtener permiso para entrar en su
sección crítica, pero continúa ejecutando una instrucción o conjunto de instrucciones que comprueban la variable apropiada para conseguir entrar. Cuando un proceso abandona su sección crítica, restablece cerrojo a 0; en este punto, a uno y sólo a uno de los procesos en espera se le concederá ac-
2
El término atómico significa que la instrucción se realiza en un único paso y no puede ser interrumpida.
05-Capitulo 5
214
12/5/05
16:21
Página 214
Sistemas operativos. Aspectos internos y principios de diseño
/* programa exclusión mutua */
const int n = /* número de procesos */;
int cerrojo;
void P(int i)
{
while (true)
{
while (!testset (cerrojo))
/* no hacer nada */;
/* sección crítica */;
cerrojo = 0;
/* resto */
}
}
void main()
{
cerrojo = 0;
paralelos (P(1), P(2), . . . ,P(n));
}
/* programa exclusión mutua */
const int n = /* número de procesos */;
int cerrojo;
void P(int i)
{
int llavei = 1;
while (true)
{
do exchange (llavei, cerrojo)
while (llavei != 0);
/* sección crítica */;
exchange (llavei, cerrojo);
/* resto */
}
}
void main()
{
cerrojo = 0;
paralelos (P(1), P(2), . . ., P(n));
}
(a) Instrucción test and set
Figura 5.2.
(b) Instrucción exchange
Soporte hardware para la exclusión mutua.
ceso a su sección crítica. La elección del proceso depende de cuál de los procesos es el siguiente que
ejecuta la instrucción testset.
Instrucción Exchange. La instrucción exchange (intercambio) puede definirse como sigue:
void exchange (int registro, int memoria)
{
int temp;
temp = memoria;
memoria = registro;
registro = temp;
}
La instrucción intercambia los contenidos de un registro con los de una posición de memoria.
Tanto la arquitectura Intel IA-32 (Pentium) como la IA-64 (Itanium) contienen una instrucción
XCHG.
La Figura 5.2b muestra un protocolo de exclusión mutua basado en el uso de una instrucción exchange. Una variable compartida cerrojo se inicializa a 0. Cada proceso utiliza una variable local
llavei que se inicializa a 1. El único proceso que puede entrar en su sección crítica es aquél que en-
05-Capitulo 5
12/5/05
16:21
Página 215
Concurrencia. Exclusión mutua y sincronización
215
cuentra cerrojo igual a 0, y al cambiar cerrojo a 1 se excluye a todos los otros procesos de la sección crítica. Cuando el proceso abandona su sección crítica, se restaura cerrojo al valor 0, permitiéndose que otro proceso gane acceso a su sección crítica.
Nótese que la siguiente expresión siempre se cumple dado el modo en que las variables son inicializadas y dada la naturaleza del algoritmo exchange:
cerrojo + Â llavei = n
i
Si cerrojo = 0, entonces ningún proceso está en su sección crítica. Si cerrojo = 1, entonces
exactamente un proceso está en su sección crítica, aquél cuya variable llavei es igual a 0.
Propiedades de la solución instrucción máquina. El uso de una instrucción máquina especial
para conseguir exclusión mutua tiene ciertas ventajas:
• Es aplicable a cualquier número de procesos sobre un procesador único o multiprocesador de
memoria principal compartida.
• Es simple y, por tanto, fácil de verificar.
• Puede ser utilizado para dar soporte a múltiples secciones críticas: cada sección crítica puede
ser definida por su propia variable.
Hay algunas desventajas serias:
• Se emplea espera activa. Así, mientras un proceso está esperando para acceder a una sección
crítica, continúa consumiendo tiempo de procesador.
• Es posible la inanición. Cuando un proceso abandona su sección crítica y hay más de un proceso esperando, la selección del proceso en espera es arbitraria. Así, a algún proceso podría
denegársele indefinidamente el acceso.
• Es posible el interbloqueo. Considérese el siguiente escenario en un sistema de procesador único. El proceso P1 ejecuta la instrucción especial (por ejemplo, testset, exchange) y
entra en su sección crítica. Entonces P1 es interrumpido para darle el procesador a P2, que
tiene más alta prioridad. Si P2 intenta ahora utilizar el mismo recurso que P1, se le denegará el acceso, dado el mecanismo de exclusión mutua. Así caerá en un bucle de espera activa. Sin embargo, P1 nunca será escogido para ejecutar por ser de menor prioridad que otro
proceso listo, P2.
Dados los inconvenientes de ambas soluciones software y hardware que se acaban de esbozar, es
necesario buscar otros mecanismos.
5.3. SEMÁFOROS
Pasamos ahora a mecanismos del sistema operativo y del lenguaje de programación que se utilizan
para proporcionar concurrencia. Comenzando, en esta sección, con los semáforos. Las siguientes dos
secciones tratarán de monitores y de paso de mensajes.
El primer avance fundamental en el tratamiento de los problemas de programación concurrente
ocurre en 1965 con el tratado de Dijkstra [DIJK65]. Dijkstra estaba involucrado en el diseño de un
sistema operativo como una colección de procesos secuenciales cooperantes y con el desarrollo de
05-Capitulo 5
216
12/5/05
16:21
Página 216
Sistemas operativos. Aspectos internos y principios de diseño
mecanismos eficientes y fiables para dar soporte a la cooperación. Estos mecanismos podrían ser usados fácilmente por los procesos de usuario si el procesador y el sistema operativo colaborasen en hacerlos disponibles.
El principio fundamental es éste: dos o más procesos pueden cooperar por medio de simples señales, tales que un proceso pueda ser obligado a parar en un lugar específico hasta que haya recibido
una señal específica. Cualquier requisito complejo de coordinación puede ser satisfecho con la estructura de señales apropiada. Para la señalización, se utilizan unas variables especiales llamadas semáforos. Para transmitir una señal vía el semáforo s, el proceso ejecutará la primitiva semSignal(s).
Para recibir una señal vía el semáforo s, el proceso ejecutará la primitiva semWait(s); si la correspondiente señal no se ha transmitido todavía, el proceso se suspenderá hasta que la transmisión tenga
lugar3.
Para conseguir el efecto deseado, el semáforo puede ser visto como una variable que contiene un
valor entero sobre el cual sólo están definidas tres operaciones:
1. Un semáforo puede ser inicializado a un valor no negativo.
2. La operación semWait decrementa el valor del semáforo. Si el valor pasa a ser negativo, entonces el proceso que está ejecutando semWait se bloquea. En otro caso, el proceso continúa
su ejecución.
3. La operación semSignal incrementa el valor del semáforo. Si el valor es menor o igual que
cero, entonces se desbloquea uno de los procesos bloqueados en la operación semWait.
Aparte de estas tres operaciones no hay manera de inspeccionar o manipular un semáforo.
La Figura 5.3 sugiere una definición más formal de las primitivas del semáforo. Las primitivas
semWait y semSignal se asumen atómicas. Una versión más restringida, conocida como semáforo
binario o mutex, se define en la Figura 5.4. Un semáforo binario sólo puede tomar los valores 0 y 1
y se puede definir por las siguientes tres operaciones:
1. Un semáforo binario puede ser inicializado a 0 o 1.
2. La operación semWaitB comprueba el valor del semáforo. Si el valor es cero, entonces el proceso que está ejecutando semWaitB se bloquea. Si el valor es uno, entonces se cambia el valor
a cero y el proceso continúa su ejecución.
3. La operación semSignalB comprueba si hay algún proceso bloqueado en el semáforo. Si lo
hay, entonces se desbloquea uno de los procesos bloqueados en la operación semWaitB. Si
no hay procesos bloqueados, entonces el valor del semáforo se pone a uno.
En principio debería ser más fácil implementar un semáforo binario, y puede demostrarse que tiene la misma potencia expresiva que un semáforo general (véase el Problema 5.9). Para contrastar los
dos tipos de semáforos, el semáforo no-binario es a menudo referido como semáforo con contador o
semáforo general.
Para ambos, semáforos con contador y semáforos binarios, se utiliza una cola para mantener los
procesos esperando por el semáforo. Surge la cuestión sobre el orden en que los procesos deben ser
3
En el artículo original de Dijkstra y en mucha de la literatura, se utiliza la letra P para semWait y la letra V para semSignal;
estas son las iniciales de las palabras holandesas prueba (probaren) e incremento (verhogen). En alguna literatura se utilizan los términos wait y signal. Este libro utiliza semWait y semSignal por claridad y para evitar confusión con las operaciones similares
wait y signal de los monitores, tratadas posteriormente.
05-Capitulo 5
12/5/05
16:21
Página 217
Concurrencia. Exclusión mutua y sincronización
struct semaphore {
int cuenta;
queueType cola;
}
void semWait(semaphore s)
{
s.cuenta—;
if (s.cuenta < 0)
{
poner este proceso en s.cola;
bloquear este proceso;
}
}
void semSignal(semaphore s)
{
s.cuenta++;
if (s.cuenta <= 0)
{
extraer un proceso P de s.cola;
poner el proceso P en la lista de listos;
}
}
Figura 5.3.
Una definición de las primitivas del semáforo.
struct binary_semaphore {
enum {cero, uno} valor;
queueType cola;
};
void semWaitB(binary_semaphore s)
{
if (s.valor == 1)
s.valor = 0;
else
{
poner este proceso en s.cola;
bloquear este proceso;
}
}
void semSignalB(binary_semaphore s)
{
if (esta_vacia(s.cola))
s.valor = 1;
else
{
extraer un proceso P de s.cola;
poner el proceso P en la lista de listos;
}
}
Figura 5.4.
Una definición de las primitivas del semáforo binario.
217
05-Capitulo 5
218
12/5/05
16:21
Página 218
Sistemas operativos. Aspectos internos y principios de diseño
extraídos de tal cola. La política más favorable es FIFO (primero-en-entrar-primero-en-salir): el proceso que lleve más tiempo bloqueado es el primero en ser extraído de la cola; un semáforo cuya definición incluye esta política se denomina semáforo fuerte. Un semáforo que no especifica el orden en
que los procesos son extraídos de la cola es un semáforo débil. La Figura 5.5, basada en otra de
[DENN84], es un ejemplo de la operación de un semáforo fuerte. Aquí los procesos A, B y C dependen de un resultado del proceso D. Inicialmente (1), A está ejecutando; B, C y D están listos; y el
contador del semáforo es 1, indicando que uno de los resultados de D está disponible. Cuando A realiza una instrucción semWait sobre el semáforo s, el semáforo se decrementa a 0 y A puede continuar
ejecutando; posteriormente se adjunta a la lista de listos. Entonces B ejecuta (2), finalmente realiza
una instrucción semWait y es bloqueado, permitiendo que D ejecute (3). Cuando D completa un nuevo resultado, realiza una instrucción semSignal, que permite a B moverse a la lista de listos (4). D
se adjunta a la lista de listos y C comienza a ejecutar (5) pero se bloquea cuando realiza una instrucción semWait. De manera similar, A y B ejecutan y se bloquean en el semáforo, permitiendo a D retomar la ejecución (6). Cuando D tiene un resultado realiza un semSignal, que transfiere a C a la
lista de listos. Posteriores ciclos de D liberarán A y B del estado Bloqueado.
Para el algoritmo de exclusión mutua tratado en la siguiente subsección e ilustrado en la Figura
5.6, los semáforos fuertes garantizan estar libres de inanición mientras que los semáforos débiles no.
Se asumirán semáforos fuertes dado que son más convenientes y porque ésta es la forma típica del semáforo proporcionado por los sistemas operativos.
EXCLUSIÓN MUTUA
La Figura 5.6 muestra una solución directa al problema de la exclusión mutua usando un semáforo s
(compárese con la Figura 5.1). Considere n procesos, identificados como P(i), los cuales necesitan todos acceder al mismo recurso. Cada proceso tiene una sección crítica que accede al recurso. En cada
proceso se ejecuta un semWait(s) justo antes de entrar en su sección crítica. Si el valor de s pasa a ser
negativo, el proceso se bloquea. Si el valor es 1, entonces se decrementa a 0 y el proceso entra en su
sección crítica inmediatamente; dado que s ya no es positivo, ningún otro proceso será capaz de entrar en su sección crítica.
El semáforo se inicializa a 1. Así, el primer proceso que ejecute un semWait será capaz de entrar
en su sección crítica inmediatamente, poniendo el valor de s a 0. Cualquier otro proceso que intente
entrar en su sección crítica la encontrará ocupada y se bloqueará, poniendo el valor de s a -1. Cualquier número de procesos puede intentar entrar, de forma que cada intento insatisfactorio conllevará
otro decremento del valor de s. Cuando el proceso que inicialmente entró en su sección crítica salga
de ella, s se incrementa y uno de los procesos bloqueados (si hay alguno) se extrae de la lista de procesos bloqueados asociada con el semáforo y se pone en estado Listo. Cuando sea planificado por el
sistema operativo, podrá entrar en la sección crítica.
La Figura 5.7, basada en otra de [BACO03], muestra una posible secuencia de tres procesos
usando la disciplina de exclusión mutua de la Figura 5.6. En este ejemplo, tres procesos (A, B, C) acceden a un recurso compartido protegido por el semáforo s. El proceso A ejecuta semWait(s), y
dado que el semáforo tiene el valor 1 en el momento de la operación semWait, A puede entrar inmediatamente en su sección crítica y el semáforo toma el valor 0. Mientras A está en su sección crítica,
ambos B y C realizan una operación semWait y son bloqueados, pendientes de la disponibilidad del
semáforo. Cuando A salga de su sección crítica y realice semSignal(s), B, que fue el primer proceso en la cola, podrá entonces entrar en su sección crítica.
El programa de la Figura 5.6 puede servir igualmente si el requisito es que se permita más de un
proceso en su sección crítica a la vez. Este requisito se cumple simplemente inicializando el semáforo al
valor especificado. Así, en cualquier momento, el valor de s.cuenta puede ser interpretado como sigue:
05-Capitulo 5
12/5/05
16:21
Página 219
Concurrencia. Exclusión mutua y sincronización
Procesador
1
A
s1
Cola de bloqueados
Semáforo
C D B
Cola de listos
Procesador
2
B
s0
Cola de bloqueados
Semáforo
A C D
Cola de listos
Procesador
3
D
B
Cola de bloqueados
s 1
Semáforo
A C
Cola de listos
Procesador
4
D
s0
Cola de bloqueados
Semáforo
B A C
Cola de listos
Procesador
5
C
s0
Cola de bloqueados
Semáforo
D B A
Cola de listos
Procesador
6
D
B A C
Cola de bloqueados
s 3
Semáforo
Cola de listos
Procesador
7
D
B A
Cola de bloqueados
Figura 5.5.
s 2
Semáforo
C
Cola de listos
Ejemplo de mecanismo semáforo.
219
05-Capitulo 5
220
12/5/05
16:21
Página 220
Sistemas operativos. Aspectos internos y principios de diseño
/* programa exclusión mutua */
const int n = /* número de procesos */;
semafore s = 1;
void P(int i)
{
while (true)
{
semWait(s);
/* sección crítica */;
semSignal(s);
/* resto */
}
}
void main()
{
paralelos (P(1), P(2), . . . ,P(n));
}
Figura 5.6.
Cola del
semáforo s
Exclusión mutua usando semáforos.
Valor del
semáfor s
A
B
C
1
Ejecución
normal
semWait(s)
0
semWait(s)
B
1
C B
2
Región
crítica
Bloqueado en
el semáforo s
semWait(s)
semSignal(s)
C
1
semSignal(s)
0
semSignal(s)
1
Figura 5.7.
Nota: la ejecución
normal sucede en
paralelo pero las
regiones críticas se
serializan
Procesos accediendo a datos compartidos protegidos por un semáforo.
05-Capitulo 5
12/5/05
16:21
Página 221
Concurrencia. Exclusión mutua y sincronización
221
• s.cuenta >= 0: s.cuenta es el número de procesos que pueden ejecutar semWait(s) sin suspensión (si no se ejecuta semSignal(s) entre medias). Tal situación permitirá a los semáforos admitir sincronización así como exclusión mutua.
• s.cuenta < 0: la magnitud de s.cuenta es el número de procesos suspendidos en s.cola.
EL PROBLEMA PRODUCTOR/CONSUMIDOR
Examinemos ahora uno de los problemas más comunes afrontados en programación concurrente: el
problema productor/consumidor. El enunciado general es éste: hay uno o más procesos generando algún tipo de datos (registros, caracteres) y poniéndolos en un buffer. Hay un único consumidor que
está extrayendo datos de dicho buffer de uno en uno. El sistema está obligado a impedir la superposición de las operaciones sobre los datos. Esto es, sólo un agente (productor o consumidor) puede acceder al buffer en un momento dado. Analizaremos varias soluciones a este problema para ilustrar tanto
la potencia como las dificultades de los semáforos.
Para empezar, asúmase que el buffer es infinito y consiste en un vector de datos. En términos abstractos, las funciones productor y consumidor se definen como sigue:
productor:
while (true)
{
/* producir dato v */;
b[entra] = v;
entra++;
}
consumidor:
while (true)
{
while (entra <= sale)
/* no hacer nada */;
w = b[sale];
sale++;
/* consumir dato w */
}
La Figura 5.8 ilustra la estructura del buffer b. El productor puede generar datos y almacenarlos
en el vector a su propio ritmo. Cada vez, se incrementa un índice (entra) sobre el vector. El consumidor procede de manera similar pero debe asegurarse de que no intenta leer de una entrada vacía del
vector. Por tanto, el consumidor se asegura de que el productor ha avanzado más allá que él (entra >
sale) antes de seguir.
Intentemos implementar este sistema utilizando semáforos binarios. La Figura 5.9 es un primer
intento. En vez de tratar con los índices entra y sale, simplemente guardamos constancia del número
0
1
2
3
4
b[1]
b[2]
b[3]
b[4]
b[5]
sale
entra
Nota: el área sombreada indica la porción del buffer que está ocupada
Figura 5.8.
Buffer infinito para el problema productor/consumidor.
05-Capitulo 5
222
12/5/05
16:21
Página 222
Sistemas operativos. Aspectos internos y principios de diseño
de datos en el buffer usando la variable entera n (= entra – sale). El semáforo s se utiliza para cumplir
la exclusión mutua; el semáforo retardo se usa para forzar al consumidor a esperar (semWait) si el
buffer está vacío.
/* programa productor consumidor */
int n;
binary_semaphore s = 1;
binary_semaphore retardo = 0;
void productor()
{
while (true)
{
producir();
semWaitB(s);
anyadir();
n++;
if (n==1)
semSignalB(retardo);
semSignalB(s);
}
}
void consumidor()
{
semWaitB(retardo);
while (true)
{
semWaitB(s);
extraer();
n—;
semSignalB(s);
consumir();
if (n==0)
semWaitB(retardo);
}
}
void main()
{
n = 0;
paralelos (productor, consumidor);
}
Figura 5.9.
Una solución incorrecta al problema productor/consumidor con buffer infinito
usando semáforos binarios.
05-Capitulo 5
12/5/05
16:21
Página 223
Concurrencia. Exclusión mutua y sincronización
223
Esta solución parece bastante directa. El productor es libre de añadir al buffer en cualquier
momento. Realiza semWaitB(s) antes de añadir y semSignalB(s) tras haberlo hecho, para impedir que el consumidor, o cualquier otro productor, acceda al buffer durante la operación de añadir el dato. También, mientras está en su sección crítica, el productor incrementa el valor de n. Si
n = 1, entonces el buffer estaba vacío justo antes de este añadido, por lo que el productor realiza
un semSignalB(retardo) para alertar al consumidor de este hecho. El consumidor comienza
esperando a que se produzca el primer dato, usando semWaitB(retardo). Luego toma el dato y
decrementa n en su sección crítica. Si el productor es capaz de permanecer por delante del consumidor (una situación usual), entonces el consumidor raramente se bloqueará en el semáforo retardo porque n será normalmente positivo. Por tanto, productor y consumidor avanzarán ambos
sin problemas.
Sin embargo, hay un defecto en este programa. Cuando el consumidor ha agotado los datos del
buffer, necesita restablecer el semáforo retardo para obligarse a esperar hasta que el productor haya
introducido más datos en el buffer. Este el propósito de la sentencia: if (n == 0) semWaitB(retardo). Considere el escenario esbozado en la Tabla 5.3. En la línea 14, el consumidor no ejecuta la
operación semWaitB. El consumidor, efectivamente, ha vaciado el buffer y ha puesto n a 0 (línea 8),
pero el productor ha incrementado n antes de que el consumidor pueda comprobar su valor en la línea
14. El resultado es un semSignalB que no casa con un semWaitB anterior. El valor de -1 en n en la
línea 20 significa que el consumidor ha consumido del buffer un dato inexistente. No cabe simplemente mover la sentencia condicional dentro de la sección crítica del consumidor, porque esto podría
dar lugar a un interbloqueo (por ejemplo, tras la línea 8 de la tabla).
Una solución para este problema es introducir una variable auxiliar que pueda establecerse dentro
de la sección crítica del consumidor para su uso posterior. Esto se muestra en la Figura 5.10. El análisis cuidadoso de la lógica del código debería convencer de que el interbloqueo ya no puede suceder.
Si se utilizan semáforos generales (también denominados semáforos con contador) puede obtenerse una solución algo más clara, como se muestra en la Figura 5.11. La variable n es ahora un semáforo. Su valor sigue siendo en número de datos en el buffer. Supóngase ahora que en la transcripción de este programa, se comete un error y se intercambian las operaciones semSignal(s) y
semSignal(n). Esto requeriría que la operación semSignal(n) se realizase en la sección crítica
del productor sin interrupción por parte del consumidor o de otro productor. ¿Afectaría esto al programa? No, porque el consumidor debe, en cualquier caso, esperar por ambos semáforos antes de
seguir.
Supóngase ahora que las operaciones semWait(n) y semWait(s) se intercambian accidentalmente. Esto produce un serio error, de hecho fatal. Si el consumidor entrase alguna vez en su sección
crítica cuando el buffer está vacío (n.cuenta = 0), entonces ningún productor podrá nunca añadir al
buffer y el sistema estará interbloqueado. Éste es un buen ejemplo de la sutileza de los semáforos y
de la dificultad de producir diseños correctos.
Finalmente, añadamos una restricción nueva y realista al problema productor/consumidor: a
saber, que el buffer es finito. El buffer se trata como un buffer circular (Figura 5.12), y los valores que lo indexan deben ser expresados módulo el tamaño del buffer. Se mantiene la siguiente
relación:
Bloquearse cuando:
Desbloquear cuando:
Productor: al insertar con el buffer lleno
Consumidor: dato insertado
Consumidor: al extraer con el buffer vacío
Productor: dato extraído
05-Capitulo 5
224
12/5/05
16:21
Página 224
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 5.3.
Posible Escenario para el Programa de la Figura 5.9.
Productor
Consumidor
1
s
n
Retardo
1
0
0
2
semWaitB(s)
0
0
0
3
n++
0
1
0
4
if (n==1)
semSignalB(retardo)
0
1
1
5
semSignalB(s)
1
1
1
6
semWaitB(retardo)
1
1
0
7
semWaitB(s)
0
1
0
8
n--
0
0
0
9
semSignalB(s)
1
0
0
10
semWaitB(s)
0
0
0
11
n++
0
1
0
12
if (n==1)
semSignalB(retardo)
0
1
1
13
semSignalB(s)
1
1
1
if (n==0)
semWaitB(retardo)
1
1
1
15
semWaitB(s)
0
1
1
16
n--
0
0
1
17
semSignalB(s)
1
0
1
18
if (n==0)
semWaitB(retardo)
1
0
0
19
semWaitB(s)
0
0
0
20
n--
0
-1
0
21
semSignalB(s)
1
-1
0
14
Las áreas en blanco representan la sección crítica controlada por semáforos.
Las funciones productor y consumidor pueden expresarse como sigue (las variables entra y
sale están inicializadas a 0):
productor:
while (true)
{
/* producir dato v */;
while ((entra + 1) % n == sale)
/* no hacer nada */;
b[entra] = v;
entra = (entra + 1) % n;
}
consumidor:
while (true)
{
while (entra == sale)
/* no hacer nada */;
w = b[sale];
sale = (sale + 1) % n;
/* consumir dato w */
}
05-Capitulo 5
12/5/05
16:21
Página 225
Concurrencia. Exclusión mutua y sincronización
/* programa productor consumidor */
int n;
binary_semaphore s = 1;
binary_semaphore retardo = 0;
void productor()
{
while (true)
{
producir();
semWaitB(s);
anyadir();
n++;
if (n==1) semSignalB(retardo);
semSignalB(s);
}
}
void consumidor()
{
int m; /* una variable local */
semWaitB(retardo);
while (true)
{
semWaitB(s);
extraer();
n—;
m = n;
semSignalB(s);
consumir();
if (m==0) semWaitB(retardo);
}
}
void main()
{
n = 0;
paralelos (productor, consumidor);
}
Figura 5.10.
Una solución correcta al problema productor/consumidor con buffer
infinito usando semáforos binarios.
225
05-Capitulo 5
226
12/5/05
16:21
Página 226
Sistemas operativos. Aspectos internos y principios de diseño
/* programa productor consumidor */
semaphore n = 0;
semaphore s = 1;
void productor()
{
while (true)
{
producir();
semWait(s);
anyadir();
semSignal(s);
semSignal(n);
}
}
void consumidor()
{
while (true)
{
semWait(n);
semWait(s);
extraer();
semSignal(s);
consumir();
}
}
void main()
{
paralelos (productor, consumidor);
}
Figura 5.11.
Una solución al problema productor/consumidor con buffer infinito usando semáforos.
La Figura 5.13 muestra una solución usando semáforos generales. El semáforo e se ha añadido
para llevar la cuenta del número de espacios vacíos.
Otro instructivo ejemplo del uso de los semáforos es el problema de la barbería, descrito en el
Apéndice A. El Apéndice A también incluye ejemplos adicionales del problema de las condiciones de
carrera cuando se usan semáforos.
IMPLEMENTACIÓN DE SEMÁFOROS
Como se mencionó anteriormente, es imperativo que las funciones semWait y semSignal sean
implementadas como primitivas atómicas. Una manera obvia es implementarlas en hardware o
firmware. A falta de esto, se han propuesto variedad de esquemas. La esencia del problema es la
05-Capitulo 5
12/5/05
16:21
Página 227
Concurrencia. Exclusión mutua y sincronización
b[1]
b[2]
b[3]
b[4]
b[5]
227
b[n]
entra
sale
(a)
b[1]
b[2]
b[3]
b[4]
b[5]
entra
b[n]
sale
(b)
Figura 5.12.
Buffer finito circular para el problema productor/consumidor.
/* programa productor consumidor */
semaphore s = 1;
semaphore n = 0;
semaphore e = /* tamaño del buffer */;
void productor()
{
while (true)
{
producir();
semWait(e);
semWait(s);
anyadir();
semSignal(s);
semSignal(n);
}
}
void consumidor()
{
while (true)
{
semWait(n);
semWait(s);
extraer();
semSignal(s);
semSignal(e);
consumir();
}
}
void main()
{
paralelos (productor, consumidor);
}
Figura 5.13.
Una solución al problema productor/consumidor con buffer acotado usando semáforos.
05-Capitulo 5
228
12/5/05
16:21
Página 228
Sistemas operativos. Aspectos internos y principios de diseño
de la exclusión mutua: sólo un proceso al tiempo puede manipular un semáforo bien con la operación semWait o bien con semSignal. Así, cualquiera de los esquemas software, tales como
el algoritmo de Dekker o el de Peterson (Apéndice A), pueden usarse, si bien esto supondría
una substancial sobrecarga de procesamiento. Otra alternativa es utilizar uno de los esquemas
soportados por hardware para la exclusión mutua. Por ejemplo, la Figura 5.14a muestra la utilización de la instrucción test and set. En esta implementación, el semáforo es nuevamente una
estructura como en la Figura 5.3, pero ahora incluye un nuevo componente entero, s.ocupado.
Lo cierto es que involucra cierta forma de espera activa. Sin embargo, las operaciones semWait
y semSignal son relativamente cortas, luego la cantidad de espera activa implicada no debería
ser relevante.
En un sistema de procesador único, es posible inhibir interrupciones durante las operaciones
semWait y semSignal, tal y como se sugiere en la Figura 5.14b. Una vez más, la relativamente
corta duración de estas operaciones significa que esta solución es razonable.
semWait(s)
semWait(s)
{
{
while (!testset(s.ocupado))
inhibir interrupciones;
/* no hacer nada */;
s.cuenta––;
s.cuenta––;
if (s.cuenta < 0)
if (s.cuenta < 0)
{
{
poner este proceso en s.cola;
poner este proceso en s.cola;
bloquear este proceso
bloquear este proceso
(además poner s.ocupado a 0);
y habilitar interrupciones;
}
}
else
else
habilitar interrupciones;
s.ocupado = 0;
}
}
semSignal(s)
semSignal(s)
{
{
inhibir interrupciones;
while (!testset(s.ocupado))
s.cuenta++;
/* no hacer nada */;
if (s.cuenta <= 0)
s.cuenta++;
{
if (s.cuenta <= 0)
extraer un proceso P de s.cola;
{
poner el proceso P en la lista de listos;
extraer un proceso P de s.cola;
}
poner el proceso P en la lista de listos;
}
habilitar interrupciones;
}
s.ocupado = 0;
}
(a) Instrucción Testset
Figura 5.14.
(b) Interrupciones
Dos posibles implementaciones de semáforos.
05-Capitulo 5
12/5/05
16:21
Página 229
Concurrencia. Exclusión mutua y sincronización
229
5.4. MONITORES
Los semáforos proporcionan una herramienta potente y flexible para conseguir la exclusión mutua y
para la coordinación de procesos. Sin embargo, como la Figura 5.9 sugiere, puede ser difícil producir
un programa correcto utilizando semáforos. La dificultad es que las operaciones semWait y semSignal pueden estar dispersas a través de un programa y no resulta fácil ver el efecto global de estas
operaciones sobre los semáforos a los que afectan.
El monitor es una construcción del lenguaje de programación que proporciona una funcionalidad
equivalente a la de los semáforos pero es más fácil de controlar. El concepto se definió formalmente
por primera vez en [HOAR74]. La construcción monitor ha sido implementada en cierto número de
lenguajes de programación, incluyendo Pascal Concurrente, Pascal-Plus, Modula-2, Modula-3 y Java.
También ha sido implementada como una biblioteca de programa. Esto permite a los programadores
poner cerrojos monitor sobre cualquier objeto. En concreto, para algo como una lista encadenada, puede quererse tener un único cerrojo para todas las listas, por cada lista o por cada elemento de cada lista.
Comencemos viendo la versión de Hoare para luego examinar otra más refinada.
MONITOR CON SEÑAL
Un monitor es un módulo software consistente en uno o más procedimientos, una secuencia de inicialización y datos locales. Las principales características de un monitor son las siguientes:
1. Las variables locales de datos son sólo accesibles por los procedimientos del monitor y no por
ningún procedimiento externo.
2. Un proceso entra en el monitor invocando uno de sus procedimientos.
3. Sólo un proceso puede estar ejecutando dentro del monitor al tiempo; cualquier otro proceso
que haya invocado al monitor se bloquea, en espera de que el monitor quede disponible.
Las dos primeras características guardan semejanza con las de los objetos en el software orientado a objetos. De hecho, en un sistema operativo o lenguaje de programación orientado a objetos puede implementarse inmediatamente un monitor como un objeto con características especiales.
Al cumplir la disciplina de sólo un proceso al mismo tiempo, el monitor es capaz de proporcionar
exclusión mutua fácilmente. Las variables de datos en el monitor sólo pueden ser accedidas por un
proceso a la vez. Así, una estructura de datos compartida puede ser protegida colocándola dentro de
un monitor. Si los datos en el monitor representan cierto recurso, entonces el monitor proporciona la
función de exclusión mutua en el acceso al recurso.
Para ser útil para la programación concurrente, el monitor debe incluir herramientas de sincronización. Por ejemplo, suponga un proceso que invoca a un monitor y mientras está en él, deba bloquearse hasta que se satisfaga cierta condición. Se precisa una funcionalidad mediante la cual el proceso
no sólo se bloquee, sino que libere el monitor para que algún otro proceso pueda entrar en él. Más
tarde, cuando la condición se haya satisfecho y el monitor esté disponible nuevamente, el proceso
debe poder ser retomado y permitida su entrada en el monitor en el mismo punto en que se suspendió.
Un monitor soporta la sincronización mediante el uso de variables condición que están contenidas dentro del monitor y son accesibles sólo desde el monitor. Las variables condición son un tipo de
datos especial en los monitores que se manipula mediante dos funciones:
• cwait(c): Suspende la ejecución del proceso llamante en la condición c. El monitor queda
disponible para ser usado por otro proceso.
05-Capitulo 5
230
12/5/05
16:21
Página 230
Sistemas operativos. Aspectos internos y principios de diseño
• csignal(c): Retoma la ejecución de algún proceso bloqueado por un cwait en la misma
condición. Si hay varios procesos, elige uno de ellos; si no hay ninguno, no hace nada.
Nótese que las operaciones wait y signal de los monitores son diferentes de las de los semáforos.
Si un proceso en un monitor señala y no hay ningún proceso esperando en la variable condición, la
señal se pierde.
La Figura 5.15 ilustra la estructura de un monitor. Aunque un proceso puede entrar en el monitor
invocando cualquiera de sus procedimientos, puede entenderse que el monitor tiene un único punto
de entrada que es el protegido, de ahí que sólo un proceso pueda estar en el monitor a la vez. Otros
procesos que intenten entrar en el monitor se unirán a una cola de procesos bloqueados esperando por
la disponibilidad del monitor. Una vez que un proceso está en el monitor, puede temporalmente bloquearse a sí mismo en la condición x realizando un cwait(x); en tal caso, el proceso será añadido a
una cola de procesos esperando a reentrar en el monitor cuando cambie la condición y retomar la ejecución en el punto del programa que sigue a la llamada cwait(x).
Cola de
entrada de
procesos
Área de espera del monitor
Entrada
MONITOR
Condición c1
Datos locales
cwait(c1)
Variables condición
Procedimiento 1
Condición cn
cwait(cn)
Procedimiento k
Cola urgente
csignal
Código de inicialización
Salida
Figura 5.15.
Estructura de un monitor.
05-Capitulo 5
12/5/05
16:21
Página 231
Concurrencia. Exclusión mutua y sincronización
231
Si un proceso que está ejecutando en el monitor detecta un cambio en la variable condición x,
realiza un csignal(x), que avisa del cambio a la correspondiente cola de la condición.
Como un ejemplo del uso de un monitor, retornemos al problema productor/consumidor con buffer acotado. La Figura 5.16 muestra una solución utilizando monitores. El módulo monitor, bufferacotado, controla el buffer utilizado para almacenar y extraer caracteres. El monitor incluye dos variables condición (declaradas con la construcción cond): nolleno es cierta cuando hay espacio para
añadir al menos un carácter al buffer y novacio es cierta cuando hay al menos un carácter en el buffer.
Un productor puede añadir caracteres al buffer sólo por medio del procedimiento anyadir dentro del monitor; el productor no tiene acceso directo a buffer. El procedimiento comprueba primero la
condición nolleno para determinar si hay espacio disponible en el buffer. Si no, el proceso que ejecuta el monitor se bloquea en esa condición. Algún otro proceso (productor o consumidor) puede entrar
ahora en el monitor. Más tarde, cuando el buffer ya no esté lleno, el proceso bloqueado podrá ser extraído de la cola, reactivado y retomará su labor. Tras colocar el carácter en el buffer, el proceso señala la condición novacio. Una descripción similar puede realizarse de la función del consumidor.
Este ejemplo marca la división de responsabilidad en los monitores en comparación con los semáforos. En el caso de los monitores, la construcción del monitor impone en sí misma la exclusión
mutua: no es posible que ambos, productor y consumidor, accedan simultáneamente al buffer. Sin
embargo, el programador debe disponer las primitivas cwait y csignal apropiadas en el código del
monitor para impedir que se depositen datos cuando el buffer está lleno o que se extraigan cuando
está vacío. En el caso de los semáforos, tanto la exclusión mutua como la sincronización son responsabilidad del programador.
Nótese que en la Figura 5.16 un proceso sale del monitor inmediatamente después de ejecutar la
función csignal. Si csignal no sucede al final del procedimiento, entonces, en la propuesta de
Hoare, el proceso que emite la señal se bloquea para que el monitor pase a estar disponible, y se sitúa
en una cola hasta que el monitor sea liberado. En este punto, una posibilidad sería colocar el proceso
bloqueado en la cola de entrada, de manera que tendría que competir por el acceso con otros procesos
que no han entrado todavía en el monitor. No obstante, dado que un proceso bloqueado en una función csignal ha realizado ya parcialmente su tarea en el monitor, tiene sentido dar preferencia a este
proceso sobre los procesos que entraron recientemente, disponiéndolo en una cola urgente separada
(Figura 5.15). Uno de los lenguajes que utiliza monitores, Pascal Concurrente, exige que csignal
aparezca solamente como última operación ejecutada por un procedimiento monitor.
Si no hay procesos esperando en la condición x, entonces la ejecución de csignal(x) no tiene
efecto.
Como con los semáforos, es posible cometer errores en la función de sincronización de los monitores. Por ejemplo, si se omite alguna de las funciones csignal en el monitor bufferacotado, entonces los procesos que entren en la cola de la correspondiente condición estarán permanentemente
colgados. La ventaja que los monitores tienen sobre los semáforos es que todas las funciones de sincronización están confinadas en el monitor. Por tanto, es más fácil comprobar que la sincronización
se ha realizado correctamente y detectar los errores. Es más, una vez un monitor se ha programado
correctamente, el acceso al recurso protegido será correcto para todo acceso desde cualquier proceso.
En cambio, con los semáforos, el acceso al recurso será correcto sólo si todos los procesos que acceden al recurso han sido programados correctamente.
MODELO ALTERNATIVO DE MONITORES CON NOTIFICACIÓN Y DIFUSIÓN
La definición de monitor de Hoare [HOAR74] requiere que si hay al menos un proceso en una cola
de una condición, un proceso de dicha cola ejecuta inmediatamente cuando otro proceso realice un
05-Capitulo 5
232
12/5/05
16:21
Página 232
Sistemas operativos. Aspectos internos y principios de diseño
/* programa productor consumidor */
monitor bufferacotado;
char buffer[N];
int dentro, fuera;
int cuenta;
cond nolleno, novacio;
void anyadir (char x)
{
if (cuenta == N)
cwait(nolleno);
buffer[dentro] = x;
dentro = (dentro + 1) % N;
cuenta++;
csignal(novacio);
}
void extraer (char x)
{
if (cuenta == 0)
cwait(novacio);
x = buffer[fuera];
fuera = (fuera + 1) % N;
cuenta—;
csignal(nolleno);
}
{
dentro = 0; fuera = 0; cuenta = 0;
}
/* espacio para N datos */
/* punteros al buffer */
/* número de datos en el buffer */
/* variables condición para sincronizar */
/* buffer lleno, evitar desbordar */
/* un dato más en el buffer */
/* retoma algún consumidor en espera */
/* buffer vacío, evitar consumo */
/* un dato menos en el buffer */
/* retoma algún productor en espera */
/* cuerpo del monitor */
/* buffer inicialmente vacío */
void productor()
{
char x;
while (true)
{
producir(x);
anyadir(x);
}
}
void consumidor()
{
char x;
while (true)
{
extraer(x);
consumir(x);
}
}
void main()
{
paralelos (productor, consumidor);
}
Figura 5.16.
Una solución al problema productor/consumidor con buffer acotado usando un monitor.
05-Capitulo 5
12/5/05
16:21
Página 233
Concurrencia. Exclusión mutua y sincronización
233
csignal sobre dicha condición. Así, el proceso que realiza el csignal debe bien salir inmediata-
mente del monitor o bien bloquearse dentro del monitor.
Hay dos desventajas en esta solución:
1. Si el proceso que realiza el csignal no ha terminado con el monitor, entonces se necesitarán
dos cambios de proceso adicionales: uno para bloquear este proceso y otro para retomarlo
cuando el monitor quede disponible.
2. La planificación de procesos asociada con una señal debe ser perfectamente fiable. Cuando se
realiza un csignal, un proceso de la cola de la correspondiente condición debe ser activado
inmediatamente y el planificador debe asegurar que ningún otro proceso entra en el monitor
antes de la activación. De otro modo, la condición bajo la cual el proceso fue activado podría
cambiar. Por ejemplo, en la Figura 5.16, cuando se realiza un csignal(novacio), debe ser
activado un proceso de la cola novacio antes de que un nuevo consumidor entre en el monitor. Otro ejemplo: un proceso productor puede añadir un carácter a un buffer vacío y entonces
fallar justo antes de la señalización; los procesos de la cola novacio estarían permanentemente colgados.
Lampson y Redell desarrollaron una definición diferente de monitor para el lenguaje Mesa
[LAMP80]. Su solución resuelve los problemas que se acaban de enunciar y aporta varias extensiones útiles. La estructura del monitor de Mesa se utiliza también en el lenguaje de programación de
sistemas Modula-3 [NELS91]. En Mesa, la primitiva csignal se sustituye por la cnotify, con la
siguiente interpretación: cuando un proceso ejecutando un monitor ejecuta cnotify(x), provoca
que la cola de la condición x sea notificada, pero el proceso que señaló continúa ejecutando. El resultado de la notificación es que el proceso en cabeza de la cola de la condición será retomado en un
momento futuro conveniente, cuando el monitor esté disponible. Sin embargo, como no hay garantía
de que algún otro proceso entre en el monitor antes que el proceso notificado, el proceso notificado
deberá volver a comprobar la condición. Por ejemplo, los procedimientos en el monitor bufferacotado tendrían ahora el código de la Figura 5.17.
void anyadir (char x)
{
while (cuenta == N)
cwait(nolleno);
buffer[dentro] = x;
dentro = (dentro + 1) % N;
cuenta++;
cnofify(novacio);
}
void extraer (char x)
{
while (cuenta == 0)
cwait(novacio);
x = buffer[fuera];
fuera = (fuera + 1) % N;
cuenta—;
cnotify(nolleno);
}
Figura 5.17.
/* buffer lleno, evitar desbordar */
/* un dato más en el buffer */
/* notifica a algún consumidor en espera */
/* buffer vacío, evitar consumo */
/* un dato menos en el buffer */
/* notifica a algún productor en espera */
Monitor de buffer acotado con el monitor Mesa.
05-Capitulo 5
234
12/5/05
16:21
Página 234
Sistemas operativos. Aspectos internos y principios de diseño
Las sentencias if se remplazan por bucles while. Así, este convenio requiere al menos una evaluación extra de la variable condición. A cambio, sin embargo, no hay cambios de proceso extra ni tampoco hay restricciones sobre cuando, tras el cnotify, debe ejecutar el proceso notificado.
Una mejora útil que puede asociarse con la primitiva cnotify es un temporizador asociado con
cada primitiva de condición. Un proceso que haya estado esperando el máximo del intervalo de tiempo indicado, será situado en estado Listo con independencia de que la condición haya sido notificada.
Cuando sea activado, el proceso comprobará la condición y continuará si la condición se satisfizo. La
temporización evita la inanición indefinida de un proceso en el caso de que algún otro proceso falle
antes de señalar la condición.
Con la norma de que un proceso se notifica en vez de que se reactiva por la fuerza, es posible
añadir la primitiva cbroadcast al repertorio. La difusión (broadcast) provoca que todos los procesos esperando en una condición pasen a estado Listo. Esto es conveniente en situaciones donde un
proceso no sabe cuántos otros procesos deben ser reactivados. Por ejemplo, en el programa productor/consumidor, suponga que ambas funciones anyadir y extraer puedan aplicarse a bloques de
caracteres de longitud variable. En este caso, si un productor añade un bloque de caracteres al buffer,
no necesita saber cuántos caracteres está dispuesto a consumir cada consumidor en espera. Simplemente emite un cbroadcast y todos los procesos en espera serán avisados para que lo intenten de
nuevo.
En suma, puede usarse un cbroadcast cuando un proceso tenga dificultad en conocer de manera precisa cuántos otros procesos debe reactivar. Un buen ejemplo es un gestor de memoria. El gestor
tiene j bytes libres; un proceso libera k bytes adicionales, pero no se sabe si algún proceso en espera
puede seguir con un total de k + j bytes. Por tanto utiliza la difusión y todos los procesos verifican por
sí mismos si hay suficiente memoria libre.
Una ventaja de los monitores de Lampson/Redell sobre los monitores de Hoare es que la solución de Lampson/Redell es menos propensa a error. En la solución de Lampson/Redell, dado que, al
usarse la construcción while, cada procedimiento comprueba la variable condición después de ser
señalado, un proceso puede señalar o difundir incorrectamente sin causar un error en el programa
señalado. El programa señalado comprobará la variable relevante y si la condición no se cumple,
volverá a esperar.
Otra ventaja del monitor Lampson/Redell es que se presta a un enfoque más modular de la construcción de programas. Por ejemplo, considere la implementación de la reserva de un buffer de E/S.
Hay dos niveles de condiciones que deben ser satisfechas por los procesos secuenciales cooperantes:
1. Estructuras de datos concordantes. Así, el monitor cumple la exclusión mutua y completa una
operación de entrada o salida antes de permitir otra operación sobre el buffer.
2. Nivel 1, más suficiente memoria para que este proceso pueda completar su solicitud de reserva.
En el monitor de Hoare, cada señal transporta la condición de nivel 1 pero también lleva un mensaje implícito, «He liberado suficientes bytes para que tu llamada de solicitud de reserva pueda ahora
funcionar». Así, la señal lleva implícita la condición de nivel 2. Si el programador cambia más tarde
la definición de la condición de nivel 2, será necesario reprogramar todos los procesos que señalizan.
Si el programador cambia las suposiciones realizadas por cualquier proceso en espera concreto (esto
es, esperar por un invariante ligeramente diferente al nivel 2), podría ser necesario reprogramar todos
los procesos que señalizan. Esto no es modular y es propenso a causar errores de sincronización (por
ejemplo, despertar por error) cuando se modifica el código. El programador ha de acordarse de modificar todos los procedimientos del monitor cada vez que se realiza un pequeño cambio en la condición de nivel 2. Con un monitor Lampson/Redell, una difusión asegura la condición de nivel 1 e insinúa que puede que se cumpla la de nivel 2; cada proceso debe comprobar la condición de nivel 2 por
05-Capitulo 5
12/5/05
16:21
Página 235
Concurrencia. Exclusión mutua y sincronización
235
sí mismo. Si se realiza un cambio en la condición de nivel 2, bien en quién espera bien en quién señala, no hay posibilidad de un despertar erróneo porque cada procedimiento verifica su propia condición de nivel 2. Por tanto, la condición de nivel 2 puede quedar oculta dentro de cada procedimiento.
Con el monitor de Hoare, la condición de nivel 2 debe trasladarse desde el código del proceso en espera hasta el código de cada uno de los procesos que señalizan, lo cual viola la abstracción de datos y
los principios de la modularidad interprocedural.
5.5. PASO DE MENSAJES
Cuando los procesos interaccionan entre sí, deben satisfacerse dos requisitos fundamentales: sincronización y comunicación. Los procesos necesitan ser sincronizados para conseguir exclusión mutua; los
procesos cooperantes pueden necesitar intercambiar información. Un enfoque que proporciona ambas
funciones es el paso de mensajes. El paso de mensajes tiene la ventaja añadida de que se presta a ser
implementado tanto en sistemas distribuidos como en multiprocesadores de memoria compartida y
sistemas monoprocesador.
Los sistemas de paso de mensajes se presentan en varias modalidades. En esta sección, presentamos una introducción general que trata las características típicas encontradas en tales sistemas. La funcionalidad real del paso de mensajes se proporciona normalmente en forma de un par de primitivas:
send(destino, mensaje)
receive(origen, mensaje)
Este es el conjunto mínimo de operaciones necesarias para que los procesos puedan entablar
paso de mensajes. El proceso envía información en forma de un mensaje a otro proceso designado
por destino. El proceso recibe información ejecutando la primitiva receive, indicando la fuente y el
mensaje.
Tabla 5.4. Características de diseño en sistemas de mensajes
para comunicación y sincronización interprocesador.
Sincronización
Send
Bloqueante
No bloqueante
Receive
Bloqueante
No bloqueante
Comprobación de llegada
Direccionamiento
Directo
Send
Receive
Explícito
Implícito
Indirecto
Estático
Dinámico
Propiedad
Formato
Contenido
Longitud
Fija
Variable
Disciplina de cola
FIFO
Prioridad
05-Capitulo 5
236
12/5/05
16:21
Página 236
Sistemas operativos. Aspectos internos y principios de diseño
En la Tabla 5.4 se muestran cierto número de decisiones de diseño relativas a los sistemas de
paso de mensaje que van a ser examinadas en el resto de esta sección.
SINCRONIZACIÓN
La comunicación de un mensaje entre dos procesos implica cierto nivel de sincronización entre los dos:
el receptor no puede recibir un mensaje hasta que no lo haya enviado otro proceso. En suma, tenemos
que especificar qué le sucede a un proceso después de haber realizado una primitiva send o receive.
Considérese primero la primitiva send. Cuando una primitiva send se ejecuta en un proceso,
hay dos posibilidades: o el proceso que envía se bloquea hasta que el mensaje se recibe o no se bloquea. De igual modo, cuando un proceso realiza la primitiva receive, hay dos posibilidades:
1. Si el mensaje fue enviado previamente, el mensaje será recibido y la ejecución continúa.
2. Si no hay mensajes esperando, entonces: (a) el proceso se bloquea hasta que el mensaje llega o
(b) el proceso continúa ejecutando, abandonando el intento de recepción.
Así, ambos emisor y receptor pueden ser bloqueantes o no bloqueantes. Tres son las combinaciones típicas, si bien un sistema en concreto puede normalmente implementar sólo una o dos de las
combinaciones:
• Envío bloqueante, recepción bloqueante. Ambos emisor y receptor se bloquean hasta que el
mensaje se entrega; a esto también se le conoce normalmente como rendezvous.
• Envío no bloqueante, recepción bloqueante. Aunque el emisor puede continuar, el receptor
se bloqueará hasta que el mensaje solicitado llegue. Esta es probablemente la combinación
más útil.
• Envío no bloqueante, recepción no bloqueante. Ninguna de las partes tiene que esperar.
Para muchas tareas de programación concurrente es más natural el send no bloqueante. Por
ejemplo, si se utiliza para realizar una operación de salida, como imprimir, permite que el proceso solicitante emita la petición en forma de un mensaje y luego continúe. Un peligro potencial del send no
bloqueante es que un error puede provocar una situación en la cual los procesos generan mensajes repetidamente. Dado que no hay bloqueo que castigue al proceso, los mensajes podrían consumir recursos del sistema, incluyendo tiempo de procesador y espacio de almacenamiento, en detrimento de
otros procesos y del sistema operativo. También, el envío no bloqueante pone sobre el programador la
carga de determinar si un mensaje ha sido recibido: los procesos deben emplear mensajes de respuesta para reconocer la recepción de un mensaje.
Para la primitiva receive, la versión bloqueante parece ser la más natural para muchas tareas
de programación concurrente. Generalmente, un proceso que quiere un mensaje necesita esperar la
información antes de continuar. No obstante, si un mensaje se pierde, lo cual puede suceder en un sistema distribuido, o si un proceso falla antes de enviar un mensaje que se espera, el proceso receptor
puede quedar bloqueado indefinidamente. Este problema puede resolverse utilizando el receive no
bloqueante. Sin embargo, el peligro de este enfoque es que si un mensaje se envía después de que un
proceso haya realizado el correspondiente receive, el mensaje puede perderse. Otras posibles soluciones son permitir que el proceso receptor compruebe si hay un mensaje en espera antes de realizar
el receive y permitirle al proceso especificar más de un origen en la primitiva receive. La segunda solución es útil si un proceso espera mensajes de más de un posible origen y puede continuar si
llega cualquiera de esos mensajes.
05-Capitulo 5
12/5/05
16:21
Página 237
Concurrencia. Exclusión mutua y sincronización
237
DIRECCIONAMIENTO
Claramente, es necesario tener una manera de especificar en la primitiva de envío qué procesos deben
recibir el mensaje. De igual modo, la mayor parte de las implementaciones permiten al proceso receptor indicar el origen del mensaje a recibir.
Los diferentes esquemas para especificar procesos en las primitivas send y receive caben dentro de dos categorías: direccionamiento directo y direccionamiento indirecto. Con el direccionamiento directo, la primitiva send incluye un identificador específico del proceso destinatario. La primitiva receive puede ser manipulada de dos maneras. Una posibilidad es que el proceso deba designar
explícitamente un proceso emisor. Así, el proceso debe conocer con anticipación de qué proceso espera el mensaje. Esto suele ser lo más eficaz para procesos concurrentes cooperantes. En otros casos,
sin embargo, es imposible especificar con anticipación el proceso de origen. Un ejemplo es un proceso servidor de impresión, que deberá aceptar un mensaje de solicitud de impresión de cualquier otro
proceso. Para tales aplicaciones, una solución más efectiva es el uso de direccionamiento implícito.
En este caso, el parámetro origen de la primitiva receive toma un valor devuelto por la operación
de recepción cuando se completa.
El otro esquema general es el direccionamiento indirecto. En este caso, los mensajes no se envían directamente por un emisor a un receptor sino que son enviados a una estructura de datos compartida que consiste en colas que pueden contener mensajes temporalmente. Tales colas se conocen generalmente como buzones (mailboxes). Así, para que dos procesos se comuniquen, un proceso envía
un mensaje al buzón apropiado y otro proceso toma el mensaje del buzón.
Una virtud del uso del direccionamiento indirecto es que, desacoplando emisor y receptor, se permite una mayor flexibilidad en el uso de mensajes. La relación entre emisores y receptores puede ser
uno-a-uno, muchos-a-uno, uno-a-muchos o muchos-a-muchos (Figura 5.18). Una relación uno-a-uno
permite establecer un enlace de comunicaciones privadas entre dos procesos. Esto aísla su interacción
de interferencias erróneas de otros procesos. Una relación muchos-a-uno es útil para interacciones
cliente/servidor; un proceso proporciona servicio a otros muchos procesos. En este caso, el buzón se
conoce normalmente como puerto. Una relación uno-a-muchos permite un emisor y múltiples receptores; esto es útil para aplicaciones donde un mensaje, o cierta información, debe ser difundido a un
conjunto de procesos. Una relación muchos-a-muchos permite a múltiples procesos servidores proporcionar servicio concurrente a múltiples clientes.
La asociación de procesos a buzones puede ser estática o dinámica. Normalmente los puertos se
asocian estáticamente con un proceso en particular; esto es, el puerto se crea y asigna a un proceso
permanentemente. De igual modo, normalmente una relación uno-a-uno se define estática y permanentemente. Cuando hay varios emisores, la asociación de un emisor a un buzón puede ocurrir dinámicamente. Para este propósito pueden utilizarse primitivas como connect y disconnect.
Un aspecto relacionado tiene que ver con la propiedad del buzón. En el caso de un puerto, típicamente es creado por (y propiedad de) el proceso receptor. Así, cuando se destruye el proceso, el puerto también. Para el caso general de buzón, el sistema operativo puede ofrecer un servicio para crearlos. Tales buzones pueden verse bien como propiedad del proceso que lo crea, en cuyo caso se
destruye junto con el proceso, o bien propiedad del sistema operativo, en cuyo caso se precisa un
mandato explícito para destruir el buzón.
FORMATO DE MENSAJE
El formato del mensaje depende de los objetivos de la facilidad de mensajería y de cuándo tal facilidad ejecuta en un computador único o en un sistema distribuido. En algunos sistemas operativos, los
05-Capitulo 5
238
12/5/05
16:21
Página 238
Sistemas operativos. Aspectos internos y principios de diseño
S1
S1
R1
Puerto
Puerto
R1
Sn
(a) Uno a uno
(b) Muchos a uno
R1
S1
S1
R1
Buzón
Buzón
R
n
(c) Uno a muchos
Figura 5.18.
Sn
Rn
(d) Muchos a muchos
Comunicación indirecta de procesos.
diseñadores han preferido mensajes cortos de longitud fija para minimizar la sobrecarga de procesamiento y almacenamiento. Si se va a transferir una gran cantidad de datos, los datos pueden estar dispuestos en un archivo y el mensaje puede simplemente indicar el archivo. Una solución más sencilla
es permitir mensajes de longitud variable.
La Figura 5.19 muestra un formato típico de mensaje para un sistema operativo que proporciona mensajes de longitud variable. El mensaje está dividido en dos partes: una cabecera, que
contiene información acerca del mensaje, y un cuerpo, que contiene el contenido real del mensaje. La cabecera puede contener una identificación del origen y del destinatario previsto
del mensaje, un campo de longitud y un campo de tipo para discriminar entre varios tipos
de mensajes. Puede haber también información adicional de control, como un campo puntero,
para así poder crear una lista encadenada de mensajes; un número de secuencia, para llevar la
cuenta del número y orden de los mensajes intercambiados entre origen y destino; y un campo
de prioridad.
DISCIPLINA DE COLA
La disciplina de cola más simple es FIFO, pero puede no ser suficiente si algunos mensajes son más
urgentes que otros. Una alternativa es permitir especificar la prioridad del mensaje, en base al tipo de
mensaje o por indicación del emisor. Otra alternativa es permitir al receptor inspeccionar la cola de
mensajes y seleccionar qué mensaje quiere recibir el siguiente.
05-Capitulo 5
12/5/05
16:21
Página 239
Concurrencia. Exclusión mutua y sincronización
239
Tipo de mensaje
ID de destino
Cabecera
ID de origen
Longitud del mensaje
Información de control
Cuerpo
Figura 5.19.
Contenido de mensajes
Formato general de mensaje.
/* programa exclusión mutua */
const int n = /* número de procesos */;
void P(int i)
{
message carta;
while (true)
{
receive (buzon, carta);
/* sección crítica */;
send (buzon, carta);
/* resto */;
}
}
void main()
{
create_mailbox (buzon);
send (buzon, null);
paralelos (P(1), P(2), . . ., P(n));
}
Figura 5.20.
Exclusión mutua usando mensajes.
EXCLUSIÓN MUTUA
La Figura 5.20 muestra un modo de usar el paso de mensajes para conseguir exclusión mutua (compárense las Figuras 5.1, 5.2 y 5.6). Se asume el uso de la primitiva receive bloqueante y de la primitiva send no bloqueante. Un conjunto de procesos concurrentes comparten un buzón que pueden
usar todos los procesos para enviar y recibir. El buzón se inicializa conteniendo un único mensaje de
contenido nulo. El proceso que desea entrar en su sección crítica primero intentar recibir un mensaje.
Si el buzón está vacío, el proceso se bloquea. Cuando el proceso ha conseguido el mensaje, realiza su
05-Capitulo 5
240
12/5/05
16:21
Página 240
Sistemas operativos. Aspectos internos y principios de diseño
sección crítica y luego devuelve el mensaje al buzón. Así, el mensaje se comporta como un testigo
que va pasando de un proceso a otro.
La solución precedente asume que si más de un proceso realiza la operación de recepción concurrentemente, entonces:
• Si hay un mensaje, se le entregará sólo a uno de los procesos y los otros se bloquearán, o
• Si la cola de mensajes está vacía, todos los procesos se bloquearán; cuando haya un mensaje
disponible sólo uno de los procesos se activará y tomará el mensaje.
Estos supuestos son prácticamente ciertos en todas las facilidades de paso de mensajes.
Como ejemplo de uso del paso de mensajes, la Figura 5.21 presenta una solución al problema
productor/consumidor con buffer acotado. Utilizando la exclusión mutua básica que proporciona el
/* programa productor consumidor */
const int
capacidad = /* capacidad de almacenamiento */;
null = /* mensaje vacío */;
int i;
void productor()
{ message pmsg;
while (true)
{
receive (puedeproducir, pmsg);
producir();
receive (puedeconsumir, pmsg);
}
}
void consumidor()
{ message cmsg;
while (true)
{
receive (puedeconsumir, cmsg);
consumir();
receive (puedeproducir, cmsg);
}
}
void main()
{
create_mailbox(puedeproducir);
create_mailbox(puedeconsumir);
for (int i = 1; i <= capacidad; i++)
send(puedeproducir, null);
paralelos (productor, consumidor);
}
Figura 5.21.
Una solución al problema productor/consumidor con buffer acotado usando mensajes.
05-Capitulo 5
12/5/05
16:21
Página 241
Concurrencia. Exclusión mutua y sincronización
241
paso de mensajes, el problema se podría haber resuelto con un algoritmo similar al de la Figura 5.13.
En cambio, el programa de la Figura 5.21 aprovecha la ventaja del paso de mensajes para poder
transferir datos además de señales. Se utilizan dos buzones. A medida que el productor genera datos,
se envían como mensajes al buzón puedeconsumir. Tan sólo con que haya un mensaje en el buzón, el
consumidor puede consumirlo. Por tanto puedeconsumir sirve de buffer; los datos en el buffer se organizan en una cola de mensajes. El «tamaño» del buffer viene determinado por la variable global capacidad. Inicialmente, el buzón puedeproducir se rellena con un número de mensajes nulos igual a la
capacidad del buffer. El número de mensajes en puedeproducir se reduce con cada producción y crece con cada consumo.
Esta solución es bastante flexible. Puede haber múltiples productores y consumidores, mientras
tengan acceso a ambos buzones. El sistema puede incluso ser distribuido, con todos los procesos productores y el buzón puedeproducir a un lado y todos los procesos productores y el buzón puedeconsumir en el otro.
5.6. EL PROBLEMA DE LOS LECTORES/ESCRITORES
Para el diseño de mecanismos de sincronización y concurrencia, es útil ser capaz de relacionar el
problema en concreto con problemas conocidos y ser capaz de probar cualquier solución según
su capacidad para resolver estos problemas conocidos. En la literatura, algunos problemas han
tomado importancia y aparecen frecuentemente, bien porque son ejemplos de problemas de diseño comunes o bien por su valor educativo. Uno de estos problemas es el productor/consumidor,
que ya ha sido explorado. En esta sección, veremos otro problema clásico: el problema lectores/escritores.
El problema lectores/escritores se define como sigue: Hay un área de datos compartida entre un
número de procesos. El área de datos puede ser un fichero, un bloque de memoria principal o incluso
un banco de registros del procesador. Hay un número de procesos que sólo leen del área de datos
(lectores) y otro número que sólo escriben en el área de datos (escritores). Las siguientes condiciones
deben satisfacerse.
1. Cualquier número de lectores pueden leer del fichero simultáneamente.
2. Sólo un escritor al tiempo puede escribir en el fichero.
3. Si un escritor está escribiendo en el fichero ningún lector puede leerlo.
Antes de continuar, distingamos este problema de otros dos: el problema general de exclusión mutua y el problema productor/consumidor. En el problema lectores/escritores los lectores
no escriben en el área de datos ni los escritores leen del área de datos. Un caso más general, que
incluye este caso, es permitir a cualquier proceso leer o escribir en el área de datos. En tal caso,
podemos declarar cualquier parte del proceso que accede al área de datos como una sección crítica e imponer la solución general de la exclusión mutua. La razón para preocuparnos por el caso
más restrictivo es que es posible una solución más eficiente para este caso y que la solución menos eficiente al problema general es inaceptablemente lenta. Por ejemplo, supóngase que el área
compartida es un catálogo de biblioteca. Los usuarios ordinarios de la biblioteca leen el catálogo
para localizar un libro. Uno o más bibliotecarios deben poder actualizar el catálogo. En la solución general, cada acceso al catálogo sería tratado como una sección crítica y los usuarios se verían forzados a leer el catálogo de uno en uno. Esto claramente impondría retardos intolerables.
Al mismo tiempo, es importante impedir a los escritores interferirse entre sí y también es necesario impedir la lectura mientras la escritura está en curso para impedir que se acceda a información inconsistente.
05-Capitulo 5
242
12/5/05
16:21
Página 242
Sistemas operativos. Aspectos internos y principios de diseño
¿Puede considerarse el problema productor/consumidor simplemente un caso especial del problema lectores/escritores con un único escritor (el productor) y un único lector (el consumidor)? La respuesta es no. El productor no es simplemente un escritor. Él debe leer las posiciones sobre la cola
para determinar dónde escribir el siguiente dato y debe determinar si el buffer está lleno. De igual
modo, el consumidor no es sólo un lector, porque debe ajustar los punteros de la cola para mostrar
que ha eliminado una unidad del buffer.
Examinemos ahora dos soluciones al problema.
LOS LECTORES TIENEN PRIORIDAD
La Figura 5.22 es una solución utilizando semáforos, que muestra una instancia de cada, un lector y
un escritor. El proceso escritor es sencillo. El semáforo sescr se utiliza para cumplir la exclusión
mutua. Mientras un escritor esté accediendo al área de datos compartidos, ningún otro escritor y ningún lector podrán acceder. El proceso lector también utiliza sescr para cumplir la exclusión mutua.
No obstante, para permitir múltiples lectores, necesitamos que, cuando no hay lectores leyendo, el
primer lector que intenta leer debe esperar en sescr. Cuando ya haya al menos un lector leyendo, los
siguientes lectores no necesitan esperar antes de entrar. La variable global cuentalect se utiliza
para llevar la cuenta del número de lectores, y el semáforo x se usa para asegurar que cuentalect
se actualiza adecuadamente.
LOS ESCRITORES TIENEN PRIORIDAD
En la sección previa, los lectores tienen prioridad. Cuando un único lector ha comenzado a acceder al
área de datos, es posible que los lectores retengan el control del área de datos mientras quede un lector realizando la lectura. Por tanto, los escritores están sujetos a inanición.
La Figura 5.23 muestra una solución que garantiza que no se le permitirá a ningún lector el acceso al área una vez que al menos un escritor haya declarado su intención de escribir. Para los escritores, los siguientes semáforos y variables se añaden a las ya definidas:
• Un semáforo slect que inhibe a los lectores mientras haya un único escritor deseando acceder
al área de datos.
• Una variable en cuentaescr que controla el cambio de slect.
• Un semáforo y que controla la actualización de cuentaescr.
Para los lectores, se necesita un semáforo adicional. No se debe permitir que ocurra una gran cola
en slect; de otro modo los escritores no serán capaces de saltar la cola. Por tanto, sólo se le permite a
un lector encolarse en slect, cualquier lector adicional se encolará en el semáforo z, inmediatamente
antes de esperar en slect. La Tabla 5.5 resume las posibilidades.
En la Figura 5.24 se muestra una solución alternativa, que da prioridad a los escritores y está implementada con el paso de mensajes. En este caso, hay un proceso controlador que tiene el acceso al
área de datos compartidos. Otros procesos que desean acceder al área de datos envían un mensaje de
solicitud al controlador, que concede el acceso respondiendo con un mensaje «OK», e indican que el
acceso se ha completado con un mensaje «terminado».
El controlador está equipado con tres buzones, uno por cada uno de los tipos de mensaje que puede recibir.
05-Capitulo 5
12/5/05
16:21
Página 243
Concurrencia. Exclusión mutua y sincronización
243
/* programa lectores y escritores */
int cuentalect;
semaphore x = 1, sescr = 1;
void lector()
{
while (true)
{
semWait (x);
cuentalect++;
if (cuentalect == 1)
semWait (sescr);
semSignal (x);
LEERDATO();
semWait (x);
cuentalect—;
if (cuentalect == 0)
semSignal (sescr);
semSignal (x);
}
}
void escritor()
{
while (true)
{
semWait (sescr);
ESCRIBIRDATO();
semSignal (sescr);
}
}
void main()
{
cuentalect = 0;
paralelos (lector, escritor);
}
Figura 5.22.
Una solución al problema lectores/escritores usando
semáforos: los lectores tienen prioridad.
El proceso controlador, para dar prioridad a los escritores, sirve antes los mensajes que solicitan
escribir que los mensajes que solicitan leer. Además debe cumplirse la exclusión mutua. Para hacer
esto se usa la variable cuenta, que se inicializa en algún número mayor que el máximo número posible de lectores. En este ejemplo, se usa el valor 100. La acción del controlador puede resumirse
como sigue:
• Si cuenta > 0, entonces no hay escritor esperando y puede haber o no lectores activos. Servir
primero todos los mensajes «terminado» para limpiar los lectores activos.
• Si cuenta = 0, entonces la única solicitud pendiente es una solicitud de escritura. Permitir continuar al escritor y esperar el mensaje «terminado».
05-Capitulo 5
244
12/5/05
16:21
Página 244
Sistemas operativos. Aspectos internos y principios de diseño
/* programa lectores y escritores */
int cuentalect, cuentaescr;
semaphore x = 1, y = 1, z = 1, sescr = 1, slect = 1;
void lector()
{
while (true)
{
semWait (z);
semWait (slect);
semWait (x);
cuentalect++;
if (cuentalect == 1)
semWait (sescr);
semSignal (x);
semSignal (slect);
semSignal (z);
LEERDATO();
semWait (x);
cuentalect—;
if (cuentalect == 0)
semSignal (sescr);
semSignal (x);
}
}
void escritor ()
{
while (true)
{
semWait (y);
cuentaescr++;
if (cuentaescr == 1)
semWait (slect);
semSignal (y);
semWait (sescr);
ESCRIBIRDATO();
semSignal (sescr);
semWait (y);
cuentaescr—;
if (cuentaescr == 0)
semSignal (slect);
semSignal (y);
}
}
void main()
{
cuentalect = cuentaescr = 0;
paralelos (lector, escritor);
}
Figura 5.23.
Una solución al problema lectores/escritores usando
semáforos: los escritores tienen prioridad.
05-Capitulo 5
12/5/05
16:21
Página 245
Concurrencia. Exclusión mutua y sincronización
Tabla 5.5.
245
Estado de las colas de proceso para el programa de la Figura 5.23.
Sólo lectores en el sistema
• sescr establecido
• no hay colas
Sólo escritores en el sistema
• sescr y slect establecidos
• los escritores se encolan en sescr
Ambos, lectores y escritores, con lectura primero
•
•
•
•
•
sescr establecido por lector
slect establecido por escritor
todos los escritores se encolan en sescr
un lector se encola en slect
los otros lectores se encolan en z
Ambos, lectores y escritores, con escritura primero
•
•
•
•
•
sescr establecido por escritor
slect establecido por escritor
los escritores se encolan en sescr
un lector se encola en slect
los otros lectores se encolan en z
• Si cuenta < 0, entonces un escritor ha hecho una solicitud y se le está haciendo esperar
mientras se limpian todos los lectores activos. Por tanto, sólo deben recibirse mensajes
«terminado».
5.7. RESUMEN
Los temas centrales de los sistemas operativos modernos son multiprogramación, multiprocesamiento
y procesamiento distribuido. La concurrencia es fundamental en estos temas y fundamental en la tecnología de diseño de sistemas operativos. Cuando múltiples procesos están ejecutando concurrentemente, bien realmente, en el caso de un sistema multiprocesador, o bien virtualmente, en el caso de
un sistema multiprogramado de procesador único, surgen cuestiones sobre la resolución de conflictos
y la cooperación.
Los procesos concurrentes pueden interactuar de varias maneras. Los procesos que no se percatan
unos de otros pueden, sin embargo, competir por recursos, como tiempo de procesador o accesos a
dispositivos de entrada/salida. Los procesos pueden percatarse indirectamente unos de otros cuando
compartan acceso a un objeto común, como un bloque de memoria principal o un fichero. Finalmente, los procesos pueden ser directamente conscientes unos de otros y cooperar intercambiando información. Los aspectos clave que surgen en estas interacciones son exclusión mutua e interbloqueo.
La exclusión mutua es una condición en la cual hay un conjunto de procesos concurrentes, entre
los cuales sólo uno es capaz de acceder a un recurso dado y realizar una función dada en un momento
determinado. Las técnicas de exclusión mutua pueden usarse para resolver conflictos, como competencia por recursos o para sincronizar procesos y que así puedan cooperar. Un ejemplo de esto último
es el modelo productor/consumidor, en el cual un proceso pone datos en un buffer y uno o más procesos extraen datos de ese buffer.
Una forma de conseguir la exclusión mutua involucra el uso de instrucciones máquina de propósito específico. Esta solución reduce la sobrecarga pero todavía es ineficiente porque utiliza espera
activa.
05-Capitulo 5
246
12/5/05
16:21
Página 246
Sistemas operativos. Aspectos internos y principios de diseño
void lector(int i)
void controlador()
{
{
message msj;
while (true)
while (true)
{
{
if (cuenta > 0)
msj = i;
{
send (quiereleer, msj);
if (!vacio (terminado))
receive (buzon[i], msj);
{
LEERDATO ();
receive (terminado, msj);
msj = i;
cuenta++;
send (terminado, msj);
}
}
else if (!vacio (quiereescribir))
}
{
void escritor(int j)
receive (quiereescribir, msj);
{
escritor_id = msj.id;
message msj;
cuenta = cuenta – 100;
while(true)
}
{
else if (!vacio (quiereleer))
msj = j;
{
send (quiereescribir, msj);
receive (quiereleer, msj);
receive (buzon[j], msj);
cuenta—;
ESCRIBIRDATO ();
send (msj.id, «OK»);
msj = j;
}
send (terminado, msj);
}
}
if (cuenta == 0)
}
{
send (escritor_id, «OK»);
receive (terminado, msj);
cuenta = 100;
}
while (cuenta < 0)
{
receive (terminado, msj);
cuenta++;
}
}
}
Figura 5.24.
Una solución al problema lectores/escritores usando el paso de mensajes.
05-Capitulo 5
12/5/05
16:21
Página 247
Concurrencia. Exclusión mutua y sincronización
247
Otro enfoque para conseguir exclusión mutua es proporcionar esos servicios en el sistema operativo. Dos de las técnicas más comunes son los semáforos y las facilidades de mensajes. Los semáforos se utilizan para la señalización entre procesos y se pueden usar fácilmente para conseguir aplicar
la disciplina de exclusión mutua. Los mensajes son útiles para hacer cumplir la exclusión mutua y
también proporcionan un mecanismo eficiente de comunicación entre procesos.
5.8. LECTURAS RECOMENDADAS
[BEN82] proporciona una explicación muy clara e incluso entretenida de la concurrencia, la exclusión mutua, los semáforos y otros temas relacionados. Un tratamiento más formal, que incluye los
sistemas distribuidos se puede encontrar en [BEN90]. [AXFO88] es otro tratado legible y útil; también contiene varios problemas con soluciones desarrolladas. [RAYN86] es una exhaustiva y lúcida
colección de algoritmos para la exclusión mutua, cubriendo software (por ejemplo, Dekker) y soluciones hardware, así como semáforos y mensajes. [HOAR85] es un clásico fácil de leer que presenta
un enfoque formal a la definición de procesos secuenciales y concurrencia. [LAMP86] es un extenso
tratado formal sobre la exclusión mutua. [RUDO90] es una útil ayuda para entender la concurrencia.
[BACO03] es un tratado bien organizado sobre la concurrencia. [BIRR89] proporciona una buena introducción práctica a la programación concurrente. [BUHR95] es una exhaustiva investigación sobre
los monitores. [KANG98] es un instructivo análisis de 12 políticas de planificación para el problema
lectores/escritores.
AXFO88 Axford, T. Concurrent Programming: Fundamental Techniques for Real-Time and Parallel Software Design. New York: Wiley, 1988.
BACO03 Bacon, J., y Harris, T. Operating Systems: Concurrent and Distributed Software Design. Reading, MA: Addison-Wesley, 1998.
BEN82 Ben-Ari, M. Principles of Concurrent Programming. Englewood Cliffs, NJ: Prentice Hall, 1982.
BEN90 Ben-Ari, M.Principles of Concurrent and Distributed Programming. Englewood Cliffs, NJ: Prentice Hall, 1990.
BIRR89 Birrell, A. An Introduction to Programming with Threads. SRC Research Report 35, Compaq
Systems Research Center, Palo Alto, CA, Enero 1989. Disponible en http://www.research.compaq.com/SRC.
BUHR95 Buhr, P., y Fortier, M. «Monitor Classification.» ACM Computing Surveys, Marzo 1995.
HOAR85 Hoare, C. Communicating Sequential Processes. Englewood Cliffs, NJ: Prentice Hall, 1985.
KANG98 Kang, S., y Lee, J. «Analysis and Solution of Non-Preemptive Policies for Scheduling Readers
and Writers.» Operating Systems Review, Julio 1998.
LAMP86 Lamport, L. «The Mutual Exclusion Problem.» Journal of the ACM, Abril 1986.
RAYN86 Raynal, M. Algorithms for Mutual Exclusion. Cambridge, MA: MIT Press, 1986.
RUDO90 Rudolph, B. «Self-Assessment Procedure XXI: Concurrency.» Communications of the ACM,
Mayo 1990.
05-Capitulo 5
248
12/5/05
16:21
Página 248
Sistemas operativos. Aspectos internos y principios de diseño
5.9. TÉRMINOS CLAVE, CUESTIONES DE REPASO Y PROBLEMAS
TÉRMINOS CLAVE
bloqueante
interbloqueo
sección crítica
concurrencia
monitor
semáforo
condición de carrera
mutex
semáforo binario
corrutina
no bloqueante
semáforo con contador
espera activa
paso de mensajes
semáforo débil
exclusión mutua
proceso concurrente
semáforo fuerte
inanición
recurso crítico
semáforo general
CUESTIONES DE REPASO
5.1.
Enumere cuatro aspectos de diseño para los cuales el concepto de concurrencia es relevante.
5.2.
¿En qué tres contextos aparece la concurrencia?
5.3.
¿Cuál es el requisito básico para la ejecución de procesos concurrentes?
5.4.
Enumere tres grados de percepción entre procesos y defina brevemente cada uno.
5.5
¿Cuál es la diferencia entre procesos en competencia y procesos cooperantes?
5.6.
Enumere los tres problemas de control asociados con los procesos en competencia y defina
brevemente cada uno.
5.7.
Enumere las condiciones necesarias para la exclusión mutua.
5.8.
¿Qué operaciones pueden ser realizadas sobre un semáforo?
5.9.
¿Cuál es la diferencia entre semáforos binarios y semáforos generales?
5.10. ¿Cuál es la diferencia entre semáforos fuertes y semáforos débiles?
5.11. ¿Qué es un monitor?
5.12. ¿Cuál es la diferencia entre bloqueante y no bloqueante con respecto a los mensajes?
5.13. ¿Qué condiciones están asociadas generalmente con el problema lectores/escritores?
PROBLEMAS
5.1.
Los procesos y los hilos proporcionan una herramienta potente de estructuración para la
implementación de programas que serían mucho más complejos como simples programas
secuenciales. La corrutina es una construcción concurrente que es instructivo examinar. El
propósito de este problema es introducir las corrutinas y compararlas con los procesos.
Considere este sencillo problema de [CONW63].
Lea tarjetas de 80 columnas e imprímalas en líneas de 125 caracteres, con los siguientes
cambios. Inserte un blanco extra después de la imagen de cada tarjeta y reemplace cada pareja de asteriscos adyacentes (**) que aparezca por el carácter ≠.
05-Capitulo 5
12/5/05
16:21
Página 249
Concurrencia. Exclusión mutua y sincronización
249
a) Desarrolle una solución a este problema como un programa secuencial ordinario. Verá
que este programa es difícil de escribir. Las interacciones entre los diversos elementos
del programa son dispares debido a la conversión de longitud de 80 a 125; es más, la
longitud de la imagen de la tarjeta, después de la conversión, variará dependiendo del
número de dobles asteriscos que aparezcan. Una forma de mejorar la claridad y minimizar los posibles errores es escribir la aplicación como tres procedimientos separados. El
primer procedimiento lee la imagen de las tarjetas, añade a cada imagen un blanco y escribe el conjunto de caracteres en un fichero temporal. Después de leer todas las tarjetas
el segundo procedimiento lee el fichero temporal, hace la sustitución de caracteres y escribe sobre un segundo fichero temporal. El tercer procedimiento lee el contenido del
segundo fichero temporal y lo imprime en líneas de 125 caracteres cada una.
b) La solución secuencial no es atractiva por la sobrecarga de E/S y los ficheros temporales. Conway propuso una nueva forma de estructura de programa, la corrutina, que permite escribir la aplicación como tres programas conectados por buffers de un carácter
(Figura 5.25). En un procedimiento tradicional, hay una relación maestro/esclavo entre
el procedimiento llamante y el procedimiento llamado. El procedimiento llamante puede ejecutar una llamada desde cualquier punto en el procedimiento; el procedimiento
llamado comienza en su punto de entrada y retorna al procedimiento llamante en el
punto de la llamada. La corrutina exhibe una relación más simétrica. A medida que se
realiza cada llamada, la ejecución parte del último punto activo del procedimiento llamado. Dado que en ningún sentido el procedimiento llamante es superior al llamado, no
hay retornos. En cambio, una corrutina puede pasar el control a cualquier otra corrutina
con la sentencia resume (retomar). La primera vez que se invoca a una corrutina, se
«retoma» en su punto de entrada. Posteriormente, la corrutina se reactiva en el punto de
su propia última sentencia resume. Nótese que sólo una corrutina del programa puede
estar en ejecución en un momento dado y que los puntos de transición están definidos
explícitamente en el código, por tanto este no es un ejemplo de procesamiento concurrente. Explique la operación del programa de la Figura 5.25.
c) El programa no contempla la condición de terminación. Asuma que la rutina de E/S
LEERTARJETA devuelve el valor cierto si ha puesto una imagen de 80 caracteres en
entrada; y que devuelve falso en cualquier otro caso. Modifique el programa para incluir esta contingencia. Nótese que la última línea impresa puede, por tanto, contener
menos de 125 caracteres.
d) Reescriba la solución como un conjunto de tres procesos usando semáforos.
5.2.
Considere un programa concurrente con dos procesos, p y q, definidos como sigue. A, B,
C, D y E son sentencias atómicas (indivisibles) arbitrarias. Asuma que el programa principal (que no se muestra) realiza un paralelos de los dos procesos.
void p()
void q()
{
{
A;
D;
B;
E;
C;
}
}
Muestre todos los posibles entrelazados de la ejecución de los dos procesos precedentes
(muéstrelo dando «trazas» de ejecución en términos de sentencias atómicas).
05-Capitulo 5
250
12/5/05
16:21
Página 250
Sistemas operativos. Aspectos internos y principios de diseño
char ca, cp;
char entrada[80];
char salida[125];
void leer()
{
while (true)
{
LEERTARJETA (entrada);
for (int i=0; i < 80; i++)
{
ca = entrada [i];
RESUME filtrar;
}
ca = ‘ ‘;
RESUME filtrar;
}
}
void imprimir()
{
while (true)
{
for (int j = 0; j < 125; j++)
{
salida [j] = cp;
RESUME filtrar;
}
IMPRIMIRLINEA (salida);
}
}
Figura 5.25.
5.3.
void filtrar()
{
while (true)
{
if (ca != ‘*’)
{
cp = ca;
RESUME imprimir;
}
else
{
RESUME leer;
if (ca == ‘*’)
{
cp = ‘≠’;
RESUME imprimir;
}
else
{
cp = ‘*’;
RESUME imprimir;
cp = ca;
RESUME imprimir;
}
}
RESUME leer;
}
}
Una aplicación de las corrutinas.
Considere el siguiente programa:
const int n = 50;
int cuenta;
void total()
{
int i;
for (i = 1; i <= n; i++)
{
cuenta ++;
}
}
05-Capitulo 5
12/5/05
16:21
Página 251
Concurrencia. Exclusión mutua y sincronización
251
void main()
{
cuenta = 0;
paralelos (total (), total ());
write (cuenta);
}
a) Determine los límites inferior y superior correctos del valor final de la variable compartida cuenta producida por este programa concurrente. Asuma que los procesos pueden
ejecutar a cualquier velocidad relativa y que el valor sólo puede ser incrementado después de haber sido cargado en un registro con una instrucción máquina separada.
b) Suponga que se permite la ejecución en paralelo de un número arbitrario de estos procesos bajo los supuestos del Apartado a. ¿Qué efecto tendrá esta modificación en el rango de valores finales de cuenta?
5.4.
¿Es siempre menos eficiente (en términos de uso de tiempo de procesador) la espera activa
que la espera bloqueante? Explíquelo.
5.5.
Considere el siguiente programa:
boolean bloqueado[2];
int turno;
void P (int id)
{
while (true)
{
bloqueado[id] = true;
while (turno != id)
{
while (bloqueado[1-id])
/* no hacer nada */;
turno = id;
}
/* sección crítica */
bloqueado[id] = false;
/* resto */
}
}
void main()
{
bloqueado[0] = false;
bloqueado[1] = false;
turno = 0;
paralelos (P(0), P(1));
}
05-Capitulo 5
252
12/5/05
16:21
Página 252
Sistemas operativos. Aspectos internos y principios de diseño
Esta solución al problema de la exclusión mutua para dos procesos se propone en
[HYMA66]. Encuentre un contraejemplo que demuestre que esta solución es incorrecta. Es
interesante notar que este código engañó incluso al Communications of the ACM.
5.6.
Otra solución software a la exclusión mutua es el algoritmo de la panadería de Lamport
[LAMP74], llamado así porque está basado en la práctica de las panaderías y otras tiendas
donde cada cliente recibe un tique numerado cuando llega, que le permite ser servido por
turno. El algoritmo es como sigue:
boolean eligiendo[n];
int numero[n];
while (true)
{
eligiendo[i] = true;
numero[i] = 1 + maximo(numero[], n);
eligiendo[i] = false;
for (int j = 0; j < n; j++)
{
while (eligiendo[j])
continue;
while ((numero[j] != 0) && (numero[j],j) < (numero[i],i))
continue;
}
/* sección crítica */;
numero[i] = 0;
/* resto */;
}
Los vectores eligiendo y numero son inicializados a falso y 0 respectivamente. El elemento
i-ésimo de cada vector puede ser leído y escrito por el proceso i pero sólo puede ser leído
por otros procesos. La notación (a,b) < (c,d) se define como
(a < c) OR (a = c AND b < d)
a) Describa el algoritmo con palabras.
b) Muestre que este algoritmo evita el interbloqueo.
c) Muestre que cumple la exclusión mutua.
5.7.
Cuando se utiliza una instrucción máquina especial para proporcionar exclusión mutua a la
manera de la Figura 5.2, no hay control sobre cuánto tiempo deberá esperar un proceso antes de conseguir acceso a su región crítica. Idee un algoritmo que utilice la instrucción testset para garantizar que cualquier proceso esperando para entrar en su sección crítica lo hará
como mucho en n - 1 turnos, donde n es el número de procesos que puede solicitar acceso
a la región crítica y un «turno» es un evento consistente en un proceso que sale de su sección crítica y otro proceso al cual se le concede acceso.
5.8.
Considere la siguiente definición de semáforos:
void semWait(s)
{
05-Capitulo 5
12/5/05
16:21
Página 253
Concurrencia. Exclusión mutua y sincronización
253
if (s.cuenta > 0)
{
s.cuenta—;
}
else
{
poner este proceso en s.cola;
bloquear este proceso;
}
}
void semSignal (s)
{
if (hay al menos un proceso bloqueado en el semáforo s)
{
extraer un proceso P de s.cola;
poner el proceso P en la lista de listos;
}
else
s.cuenta++;
}
Compare este conjunto de definiciones con las de la Figura 5.3. Nótese una diferencia: con
la definición precedente, un semáforo nunca puede tomar un valor negativo. ¿Hay alguna
diferencia entre estos dos conjuntos de definiciones cuando se utilizan en programas? Esto
es, ¿podría sustituirse un conjunto por el otro sin alterar el significado del programa?
5.9.
Debe ser posible implementar semáforos generales usando semáforos binarios. Podemos
usar las operaciones semWaitB y semSignalB y dos semáforos binarios pausa y mutex.
Considere lo siguiente:
void semWait(semaphore s)
{
semWaitB(mutex);
s—;
if (s < 0)
{
semSignalB(mutex);
semWaitB(pausa);
}
else
SemsignalB(mutex);
}
void semSignal(semaphore s);
{
semWaitB(mutex);
05-Capitulo 5
254
12/5/05
16:21
Página 254
Sistemas operativos. Aspectos internos y principios de diseño
s++;
if (s <= 0)
semSignalB(pausa);
semSignalB(mutex);
}
Inicialmente, s se pone al valor deseado del semáforo. Cada operación semWait decrementa s y cada operación semSignal incrementa s. El semáforo binario mutex, que se
inicializa a 1, asegura que hay exclusión mutua en la actualización de s. El semáforo binario pausa, que se inicializa a 0, se usa para bloquear procesos.
Hay un defecto en el programa precedente. Demuestre el defecto y proponga un cambio
que lo subsane. Pista: suponga dos procesos que llaman a semWait(s) cuando s es inicialmente 0 y justo cuando el primero ha realizado semSignalB(mutex) pero aún no ha
realizado semWaitB(pausa), la segunda llamada a semWait(s) avanza hasta el mismo
punto. Todo lo que usted tiene que hacer es mover una única línea del programa.
5.10. En 1978, Dijkstra propuso la conjetura de que no hay solución al problema de la exclusión
mutua que evite la inanición, aplicable a un número desconocido pero finito de procesos,
usando un número finito de semáforos débiles. En 1979, J.M. Morris refutó esta conjetura
al publicar un algoritmo que utiliza tres semáforos débiles. El comportamiento del algoritmo puede ser descrito como sigue: si uno o más procesos están esperando en una operación
semWait(S) y otro proceso está ejecutando semSignal(S), el valor del semáforo S no
se modifica y uno de los procesos en espera se desbloquea independientemente de semWait(S). Aparte de los tres semáforos, el algoritmo utiliza dos variables enteras no negativas como contadores del número de procesos dentro de ciertas secciones del algoritmo.
Así, los semáforos A y B se inicializan a 1, mientras que el semáforo M y los contadores
NA y NM se inicializan a 0. El semáforo de exclusión mutua B protege los accesos a la variable compartida NA. Un proceso que intente entrar en su sección crítica debe cruzar dos
barreras representadas por los semáforos A y M. Los contadores NA y NM, respectivamente, contienen el número de procesos listos para cruzar la barrera A y aquéllos que ya han
cruzado la barrera A pero todavía no la barrera M. En la segunda parte del protocolo, los
NM procesos bloqueados en M entrarán en sus secciones críticas de uno en uno, usando
una técnica en cascada similar a la utilizada en la primera parte. Defina el algoritmo que
sea conforme con esta descripción.
5.11. El siguiente problema se planteó una vez en un examen:
Parque Jurásico consiste en un museo y un parque para hacer rutas safari. Hay m pasajeros y n coches monoplaza. Los pasajeros deambulan por el museo durante un rato y
luego hacen cola para dar un paseo en coche por el safari. Cuando hay un coche disponible se monta en él un pasajero y pasea por el parque una cantidad de tiempo aleatoria.
Si los n vehículos están todos de paseo por el parque con un pasajero abordo, entonces
el pasajero que quiere un coche se espera; si un coche está listo para cargar pero no hay
pasajeros esperando, entonces el coche se espera. Use semáforos para sincronizar los m
procesos pasajero con los n procesos coche.
El siguiente esqueleto de código fue encontrado garabateado en un papel en el suelo de la
sala de examen. Evalúe su corrección. Ignore la sintaxis y las variables no declaradas. Recuerde que P y V se corresponden con semWait y semSignal.
resource Parque_Jurasico()
sem coche_libre := 0, coche_tomado := 0, coche_ocupado := 0, pasajero_pendiente := 0
05-Capitulo 5
12/5/05
16:21
Página 255
Concurrencia. Exclusión mutua y sincronización
255
process pasajero(i := 1 to num_pasajeros)
do true -> dormir(int(random(1000*tiempo_visita)))
P(coche_libre); V(coche_tomado); P(coche_ocupado)
P(pasajero_pendiente)
od
end pasajero
process coche(j := 1 to num_coches)
do true -> V(coche_libre); P(coche_tomado); V(coche_ocupado)
dormir(int(random(1000*tiempo_paseo)))
V(pasajero_pendiente)
od
end coche
end Parque_Jurasico
5.12. En el comentario sobre la Figura 5.9 y la Tabla 5.3, se indicó que «no cabe simplemente
mover la sentencia condicional dentro de la sección crítica (controlada por s) del consumidor, porque esto podría dar lugar a un interbloqueo». Demuestre esto con una tabla similar
a la Tabla 5.3.
5.13. Considere la solución al problema productor/consumidor de buffer infinito definido en la
Figura 5.10. Suponga que tenemos un caso (usual) en el que el productor y el consumidor
están ejecutando aproximadamente a la misma velocidad. El escenario podría ser
a) Productor: añadir; semSignal; producir; ...; añadir; semSignal; producir; ...
Consumidor: consumir; ...; extraer; semWait; consumir; ...; extraer; semWait; ...
El productor siempre consigue añadir un nuevo elemento al buffer y señalar mientras el
consumidor consume el elemento previo. El productor siempre está añadiendo a un buffer vacío y el consumidor siempre está extrayendo el único dato del buffer. Aunque el
consumidor nunca se bloquea en el semáforo se están realizando un gran número de llamadas al mecanismo semáforo, creando una considerable sobrecarga.
Construya un nuevo programa que sea más eficiente bajo esas circunstancias. Pista:
Permita a n tomar el valor -1, que significará no sólo que el buffer está vacío sino que el
consumidor ha detectado este hecho y se va a bloquear hasta que el productor proporcione datos nuevos. La solución no necesita el uso de la variable local m que se encuentra en la Figura 5.10.
5.14. Considere la Figura 5.13. ¿Cambiaría el significado del programa si se intercambiase lo siguiente?
a)
b)
c)
d)
semWait(e); semWait(s)
semSignal(s); semSignal(n)
semWait(n); semWait(s)
semSignal(s); semSignal(e)
5.15. Nótese que en la exposición del problema productor/consumidor con buffer finito (Figura
5.12), nuestra definición permite como mucho n - 1 entradas en el buffer.
a) ¿Por qué es esto?
b) Modifique el algoritmo para corregir esta deficiencia.
05-Capitulo 5
256
12/5/05
16:21
Página 256
Sistemas operativos. Aspectos internos y principios de diseño
5.16. Este problema demuestra el uso de semáforos para coordinar tres tipos de procesos4. Santa
Claus duerme en su tienda en el Polo Norte y sólo puede ser despertado porque (1) los nueve renos han vuelto todos de sus vacaciones en el Pacífico Sur, o (2) algunos de los elfos
tienen dificultades fabricando los juguetes; para que Santa pueda dormir, los elfos sólo
pueden despertarle cuando tres de ellos tengan problemas. Cuando tres elfos están solucionando sus problemas cualquier otro elfo que desee visitar a Santa Claus debe esperar a que
esos elfos vuelvan. Si Santa Claus se despierta y encuentra a tres elfos esperando en la
puerta de su tienda, junto con el último reno que acaba de volver del trópico, Santa Claus
tiene decidido que los elfos pueden esperar hasta después de Navidad, porque es más importante tener listo su trineo. (Se asume que los renos no desean abandonar los trópicos y
que por tanto están allí hasta el último momento posible). El último reno en volver debe
buscar a Santa Claus mientras los otros esperan en un establo calentito antes de ser enganchados al trineo. Resuelva este problema usando semáforos.
5.17. Muestre que el paso de mensajes y los semáforos tienen una funcionalidad equivalente:
a) Implemente el paso de mensajes usando semáforos. Pista: haga uso de un área de almacenamiento compartida para mantener los buzones, consistiendo cada uno en un vector
con capacidad para un determinado número de mensajes.
b) Implemente un semáforo usando el paso de mensajes. Pista: introduzca un proceso de
sincronización separado.
4
Mi gratitud a John Trono del St. Michael’s College en Vermont por proporcionar este problema.
06-Capitulo 6
16/5/05
17:04
Página 257
CAPÍTULO
6
Concurrencia.
Interbloqueo e inanición
6.1.
Fundamentos del interbloqueo
6.2.
Prevención del interbloqueo
6.3.
Predicción del interbloqueo
6.4.
Detección del interbloqueo
6.5.
Una estrategia integrada de tratamiento del interbloqueo
6.6.
El problema de los filósofos comensales
6.7.
Mecanismos de concurrencia de UNIX
6.8.
Mecanismos de concurrencia del núcleo de Linux
6.9.
Funciones de sincronización de hilos de Solaris
6.10. Mecanismos de concurrencia de Windows
6.11. Resumen
6.12. Lecturas recomendadas
6.13. Términos clave, cuestiones de repaso y problemas
06-Capitulo 6
258
16/5/05
17:04
Página 258
Sistemas operativos. Aspectos internos y principios de diseño
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
Este capítulo continúa el estudio de la concurrencia examinando dos problemas que dificultan todas
las iniciativas para proporcionar procesamiento concurrente: el interbloqueo y la inanición. El capítulo comienza con un estudio de los principios fundamentales de los interbloqueos y los problemas
relacionados con la inanición. A continuación, se examinarán las tres estrategias básicas para tratar
con el interbloqueo: la prevención, la detección y la predicción. Acto seguido, se revisará uno de los
problemas clásicos utilizados para ilustrar tanto la sincronización como el interbloqueo: el problema
de los filósofos comensales.
Como en el Capítulo 5, el estudio de este capítulo se limita a considerar la concurrencia y el interbloqueo sobre un único sistema. Las estrategias para tratar con los problemas del interbloqueo distribuido se abordarán en el Capítulo 14.
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
6.1. FUNDAMENTOS DEL INTERBLOQUEO
S
e puede definir el interbloqueo como el bloqueo permanente de un conjunto de procesos que o
bien compiten por recursos del sistema o se comunican entre sí. Un conjunto de procesos está
interbloqueado cuando cada proceso del conjunto está bloqueado esperando un evento (normalmente la liberación de algún recurso requerido) que sólo puede generar otro proceso bloqueado del
conjunto. El interbloqueo es permanente porque no puede producirse ninguno de los eventos. A diferencia de otros problemas que aparecen en la gestión de procesos concurrentes, no hay una solución
eficiente para el caso general.
Todos los interbloqueos involucran necesidades conflictivas que afectan a los recursos de dos o
más procesos. Un ejemplo habitual es el interbloqueo del tráfico. La Figura 6.1a muestra una situación en la que cuatro coches han llegado aproximadamente al mismo tiempo a una intersección donde
confluyen cuatro caminos. Los cuatro cuadrantes de la intersección son los recursos que hay que controlar. En particular, si los cuatro coches desean cruzar la intersección, los requisitos de recursos son
los siguientes:
• El coche 1, que viaja hacia el norte, necesita los cuadrantes a y b.
• El coche 2 necesita los cuadrantes b y c.
• El coche 3 necesita los cuadrantes c y d.
• El coche 4 necesita los cuadrantes d y a.
La norma de circulación habitual es que un coche en un cruce de cuatro caminos debería dar preferencia a otro coche que está justo a su derecha. Esta regla funciona si hay sólo dos o tres coches en
la intersección. Por ejemplo, si sólo llegan a la intersección los coches que vienen del norte y del oeste, el del norte esperará y el del oeste proseguirá. Sin embargo, si todos los coches llegan aproximadamente al mismo tiempo, cada uno se abstendrá de cruzar la intersección, produciéndose un interbloqueo. Si los cuatro coches olvidan las normas y entran (cuidadosamente) en la intersección, cada
uno posee un recurso (un cuadrante) pero no pueden continuar porque el segundo recurso requerido
ya se lo ha apoderado otro coche. De nuevo, se ha producido un interbloqueo. Nótese también que
debido a que cada coche tiene justo detrás otro coche, no es posible dar marcha atrás para eliminar el
interbloqueo.
A continuación, se examina un diagrama de interbloqueo involucrando procesos y recursos del
computador. La Figura 6.2 (basada en una incluida en [BACO03]), denominada diagrama de progreso conjunto, muestra el progreso de dos procesos compitiendo por dos recursos. Cada proceso ne-
06-Capitulo 6
16/5/05
17:04
Página 259
Concurrencia. Interbloqueo e inanición
3
c
b
4 d
a
2
1
(b) Interbloqueo
(a) Posible interbloqueo
Figura 6.1.
Ilustración del interbloqueo.
Progreso
de Q
2
1
Libera
A
Se
requiere A
PyQ
quieren A
Libera
B
Solicita A
3
Se
requiere B
Interbloqueo
inevitable
PyQ
quieren B
5
Solicita B
4
6
Solicita A Solicita B
Libera A
Libera B
Progreso
de P
Tanto P como Q quieren el recurso A
Tanto P como Q quieren el recurso B
Se
requiere A
Se requiere B
Región de interbloqueo inevitable
= posible trayectoria de progreso de P y Q
Tramo horizontal de la trayectoria que indica que P está ejecutando y Q esperando.
Tramo vertical de la trayectoria que indica que Q está ejecutando y P esperando
Figura 6.2.
Ejemplo de interbloqueo.
259
06-Capitulo 6
260
16/5/05
17:04
Página 260
Sistemas operativos. Aspectos internos y principios de diseño
cesita el uso exclusivo de ambos recursos durante un cierto periodo de tiempo. Suponga que hay dos
procesos, P y Q, que tienen la siguiente estructura general:
Proceso P
...
Solicita A
...
Solicita B
...
Libera A
...
Libera B
...
Proceso Q
...
Solicita B
...
Solicita A
...
Libera B
...
Libera A
...
En la Figura 6.2, el eje x representa el progreso en la ejecución de P, mientras que el eje y representa el de Q. El progreso conjunto de los dos procesos se representa por tanto por una trayectoria
que avanza desde el origen en dirección nordeste. En el caso de un sistema uniprocesador, sólo puede
ejecutar un proceso cada vez, y la trayectoria consiste en segmentos horizontales y verticales alternados, tal que un segmento horizontal representa un periodo en el que P ejecuta y Q espera, mientras
que un segmento vertical representa un periodo en el que Q ejecuta y P espera. La figura muestra áreas en las que tanto P como Q requieren el recurso A (líneas ascendentes); áreas en las que ambos procesos requieren el recurso B (líneas descendentes); y áreas en las que ambos requieren ambos recursos. Debido a que se asume que cada proceso requiere el control exclusivo de un recurso, todas éstas
son regiones prohibidas; es decir, es imposible que cualquier trayectoria que represente el progreso de
la ejecución conjunta de P y Q entre en una de estas regiones.
La figura muestra seis diferentes trayectorias de ejecución, que se pueden resumir de la siguiente
manera:
1. Q adquiere B y, a continuación, A, y, más tarde, libera B y A. Cuando P continúe su ejecución,
será capaz de adquirir ambos recursos.
2. Q adquiere B y, a continuación, A. P ejecuta y se bloquea al solicitar A. Q libera B y A. Cuando P continúe su ejecución, será capaz de adquirir ambos recursos.
3. Q adquiere B y, a continuación, P adquiere A. El interbloqueo es inevitable, puesto que cuando
la ejecución continúe, Q se bloqueará a la espera de A y P a la de B.
4. P adquiere A y, a continuación, Q adquiere B. El interbloqueo es inevitable, puesto que cuando
la ejecución continúe, Q se bloqueará a la espera de A y P a la de B.
5. P adquiere A y, a continuación, B. Q ejecuta y se bloquea al solicitar B. P libera A y B. Cuando
Q continúe su ejecución, será capaz de adquirir ambos recursos.
6. P adquiere A y, a continuación, B, y, más tarde, libera A y B. Cuando Q continúe su ejecución,
será capaz de adquirir ambos recursos.
El área sombreada en gris en la Figura 6.2, que puede denominarse región fatal, está relacionada
con el comentario realizado sobre las trayectorias 3 y 4. Si una trayectoria de ejecución entra en esta
región fatal, el interbloqueo es inevitable. Nótese que la existencia de una región fatal depende de la
lógica de los dos procesos. Sin embargo, el interbloqueo es sólo inevitable si el progreso conjunto de
los dos procesos crea una trayectoria que entra en la región fatal.
06-Capitulo 6
16/5/05
17:04
Página 261
Concurrencia. Interbloqueo e inanición
261
La aparición de un interbloqueo depende tanto de la dinámica de la ejecución como de los detalles de la aplicación. Por ejemplo, supóngase que P no necesitase ambos recursos al mismo tiempo de
manera que los dos procesos tuvieran la siguiente estructura:
Proceso P
...
Solicita A
...
Libera A
...
Solicita B
...
Libera B
...
Proceso Q
...
Solicita B
...
Solicita A
...
Libera B
...
Libera A
...
La situación se refleja en la Figura 6.3. Si analiza la figura, el lector se convencerá de que
con independencia de la temporización relativa de los dos procesos, no puede ocurrir un interbloqueo.
Progreso
de Q
1
2
3
Libera
A
4
Se
requiere A
Libera
B
PyQ
quieren A
PyQ
quieren B
Solicita A
Se
requiere B
5
Solicita B
6
Solicita A
Libera A Solicita B Libera B
Se requiere A
Progreso
de P
Se requiere B
tanto P como Q quieren el recurso A
tanto P como Q quieren el recurso B
Figura 6.3.
posible trayectoria de progreso de P y Q
Tramo horizontal de la trayectoria que indica que P está ejecutando y Q esperando.
Tramo vertical de la trayectoria que indica que Q está ejecutando y P esperando
Ejemplo donde no hay interbloqueo [BACO03].
06-Capitulo 6
262
16/5/05
17:04
Página 262
Sistemas operativos. Aspectos internos y principios de diseño
Proceso P
Paso
Proceso Q
Acción
Paso
Acción
p0
Solicita (D)
q0
Solicita (C)
p1
Bloquea (D)
q1
Bloquea (C)
p2
Solicita (C)
q2
Solicita (D)
p3
Bloquea (C)
q3
Bloquea (D)
p4
Realiza función
q4
Realiza función
p5
Desbloquea (D)
q5
Desbloquea (C)
p6
Desbloquea (C)
q6
Desbloquea (D)
Figura 6.4.
Ejemplo de dos procesos compitiendo por recursos reutilizables.
Como se ha mostrado, se puede utilizar el diagrama de progreso conjunto para registrar la historia de la ejecución de dos procesos que comparten recursos. En los casos donde más de dos procesos
pueden competir por el mismo recurso, se requeriría un diagrama con más dimensiones. En cualquier caso, los principios concernientes con las regiones fatales y los interbloqueos permanecerían
igual.
RECURSOS REUTILIZABLES
Pueden distinguirse dos categorías de recursos: reutilizables y consumibles. Un recurso reutilizable es
aquél que sólo lo puede utilizar de forma segura un proceso en cada momento y que no se destruye
después de su uso. Los procesos obtienen unidades del recurso que más tarde liberarán para que puedan volver a usarlas otros procesos. Algunos ejemplos de recursos reutilizables incluyen procesadores, canales de E/S, memoria principal y secundaria, dispositivos, y estructuras de datos como ficheros, bases de datos y semáforos.
Como un ejemplo de recursos reutilizables involucrados en un interbloqueo, considere dos
procesos que compiten por el acceso exclusivo a un fichero de disco D y a una unidad de cinta C.
En la Figura 6.4 se muestran las operaciones realizadas por los programas implicados. El interbloqueo se produce si cada proceso mantiene un recurso y solicita el otro. Por ejemplo, ocurrirá un
interbloqueo si el sistema de multiprogramación intercala la ejecución de los procesos de la siguiente manera:
p0 p1 q0 q1 p2 q2
Puede parecer que se trata de un error de programación más que de un problema del diseñador
del sistema operativo. Sin embargo, ya se ha observado previamente que el diseño de programas concurrentes es complejo. Estos interbloqueos se pueden producir, estando su causa frecuentemente empotrada en la compleja lógica del programa, haciendo difícil su detección. Una estrategia para tratar
con este interbloqueo es imponer restricciones en el diseño del sistema con respecto al orden en que
se pueden solicitar los recursos.
Otro ejemplo de interbloqueo con un recurso reutilizable está relacionado con las peticiones de
reserva de memoria principal. Supóngase que el espacio disponible para reservar es de 200 Kbytes y
que se produce la secuencia siguiente de peticiones:
06-Capitulo 6
16/5/05
17:04
Página 263
Concurrencia. Interbloqueo e inanición
P1
P2
...
...
Solicita 80 Kbytes;
Solicita 70 Kbytes;
...
...
Solicita 60 Kbytes;
Solicita 80 Kbytes;
263
El interbloqueo sucede si ambos procesos progresan hasta su segunda petición. Si no se conoce
anticipadamente la cantidad de memoria que va a solicitarse, es difícil tratar con este tipo de interbloqueos mediante restricciones en el diseño del sistema. La mejor manera de tratar con este problema
es, en realidad, eliminar la posibilidad de que se produzca mediante la utilización de memoria virtual,
que se estudiará en el Capítulo 8.
RECURSOS CONSUMIBLES
Un recurso consumible es aquél que puede crearse (producirse) y destruirse (consumirse). Normalmente, no hay límite en el número de recursos consumibles de un determinado tipo. Un proceso productor desbloqueado puede crear un número ilimitado de estos recursos. Cuando un proceso consumidor adquiere un recurso, el recurso deja de existir. Algunos ejemplos de recursos consumibles son
las interrupciones, las señales, los mensajes y la información en buffers de E/S.
Como un ejemplo de interbloqueo que involucra recursos consumibles, considere el siguiente par
de procesos de tal forma que cada proceso intenta recibir un mensaje del otro y, a continuación, le envía un mensaje:
P1
P2
...
...
Recibe (P2);
Recibe (P1);
...
...
Envía (P2, M1);
Envía (P1, M2);
Se produce un interbloqueo si la función de recepción (Recibe) es bloqueante (es decir, el proceso receptor se bloquea hasta que se recibe el mensaje). Nuevamente, la causa del interbloqueo es un
error de diseño. Estos errores pueden ser bastante sutiles y difíciles de detectar. Además, puede darse una rara combinación de eventos que cause el interbloqueo; así, un programa podría estar usándose durante un periodo considerable de tiempo, incluso años, antes de que realmente ocurra el interbloqueo.
No hay una única estrategia efectiva que pueda tratar todos los tipos de interbloqueo. La Tabla 6.1 resume los elementos fundamentales de las estrategias más importantes que se han desarrollado: prevención, predicción y detección. Se estudiará cada una de ellas, después de que se
presenten los grafos de asignación de recursos y, a continuación, las condiciones para el interbloqueo.
Asegura que existe al
menos un camino seguro
Se invoca periódicamente
para comprobar si hay
interbloqueo
Muy liberal; los recursos
solicitados se conceden
en caso de que sea
posible
Detección
• Nunca retrasa la
iniciación del proceso
• Facilita la gestión en
línea
• Pérdidas inherentes
por expropiación
• El SO debe conocer los
futuros requisitos de
recursos de los
procesos
• Los procesos se
pueden bloquear
durante largos
periodos
• Impide solicitudes
graduales de recursos
• Expropia con más
frecuencia de lo
necesario
• Ineficiente
• Retrasa la iniciación del
proceso
• Los procesos deben
conocer sus futuros
requisitos de recursos
Principales desventajas
17:04
• No es necesaria la
expropiación
• Es posible asegurarlo
mediante
comprobaciones en
tiempo de compilación
• No necesita cálculos en
tiempo de ejecución ya
que el problema se
resuelve en el diseño
del sistema
Ordenamiento de recursos
A medio camino entre la
detección y la prevención
• Conveniente cuando se
aplica a recursos cuyo
estado se puede
guardar y restaurar
fácilmente
Expropiación
Predicción
• Adecuada para
procesos que realizan
una sola ráfaga de
actividad
• No es necesaria la
expropiación
Principales ventajas
Solicitud simultánea de
todos los recursos
Esquemas alternativos
Conservadora; infrautiliza
recursos
Política de reserva
de recursos
16/5/05
Prevención
Estrategia
Resumen de las estrategias de detección, prevención y predicción de interbloqueos en sistemas operativos [ISLO80].
264
Tabla 6.1.
06-Capitulo 6
Página 264
Sistemas operativos. Aspectos internos y principios de diseño
06-Capitulo 6
16/5/05
17:04
Página 265
Concurrencia. Interbloqueo e inanición
265
GRAFOS DE ASIGNACIÓN DE RECURSOS
Una herramienta útil para la caracterización de la asignación de recursos a los procesos es el grafo de
asignación de recursos, introducido por Holt [HOLT72]. El grafo de asignación de recursos es un
grafo dirigido que representa el estado del sistema en lo que se refiere a los recursos y los procesos,
de tal forma que cada proceso y cada recurso se representa por un nodo. Una arista del grafo dirigida
desde un proceso a un recurso indica que el proceso ha solicitado el recurso pero no se le ha concedido todavía (Figura 6.5a). En el interior de un nodo de recurso, se muestra un punto por cada instancia
de ese recurso. Un ejemplo de tipo de recurso que puede tener múltiples instancias es un conjunto de
dispositivos de E/S controlados por un módulo de gestión de recursos del sistema operativo. Una
arista del grafo dirigida desde un punto de un nodo de un recurso reutilizable hacia un proceso indica
que se le ha concedido una petición (Figura 6.5b), es decir, al proceso se le ha asignado una unidad
del recurso. Una arista del grafo dirigida desde un punto de un nodo de un recurso consumible hacia
un proceso indica que el proceso es el productor de ese recurso.
La Figura 6.5c muestra un ejemplo de interbloqueo. Hay sólo una unidad de cada recurso Ra y
Rb. El proceso P1 mantiene Rb y solicita Ra, mientras que P2 mantiene Ra pero pide Rb. La Figura
6.5d tiene la misma topología que la Figura 6.5c, pero no hay interbloqueo porque están disponibles
múltiples unidades de cada recurso.
El grafo de asignación de recursos de la Figura 6.6 corresponde a la situación de interbloqueo de
la Figura 6.1b. Nótese que en este caso no se trata de una situación sencilla en la cual hay dos proce-
Solicita
P1
Ra
Asignado a
P1
(a) Recurso solicitado
(b) Recurso asignado
Ra
Ra
As
ign
ta
ci
oli
S
P2
ign
a
cit
li
So
ad
oa
ign
ad
S
oa
P1
As
ita
c
oli
ad
As
Ra
P1
P2
As
ign
a
cit
li
So
ad
oa
Rb
Rb
(c) Espera circular
(d) Sin interbloqueo
Figura 6.5.
oa
Ejemplos de grafos de asignación de recursos.
06-Capitulo 6
266
16/5/05
17:04
Página 266
Sistemas operativos. Aspectos internos y principios de diseño
Figura 6.6.
P1
P2
P3
P4
Ra
Rb
Rc
Rd
Grafo de asignación de recursos correspondiente a la Figura 6.1b.
sos de tal forma que cada uno tiene el recurso que el otro necesita. Más bien, en este caso, hay una
cadena circular de procesos y recursos que tiene como resultado un interbloqueo.
LAS CONDICIONES PARA EL INTERBLOQUEO
Deben presentarse tres condiciones de gestión para que sea posible un interbloqueo:
1. Exclusión mutua. Sólo un proceso puede utilizar un recurso en cada momento. Ningún proceso puede acceder a una unidad de un recurso que se ha asignado a otro proceso.
2. Retención y espera. Un proceso puede mantener los recursos asignados mientras espera la
asignación de otros recursos.
3. Sin expropiación. No se puede forzar la expropiación de un recurso a un proceso que lo
posee.
Por diversos motivos, estas condiciones son realmente deseables. Por ejemplo, se necesita la exclusión mutua para asegurar la coherencia de los resultados y la integridad de una base de datos. Del
mismo modo, la expropiación no se debería hacer de una forma arbitraria. Por ejemplo, cuando están
involucrados recursos de datos, la expropiación debe implementarse mediante un mecanismo de recuperación mediante retroceso, que restaura un proceso y sus recursos a un estado previo adecuado desde el cual el proceso puede finalmente repetir sus acciones.
Si se cumplen estas tres condiciones se puede producir un interbloqueo, pero aunque se cumplan puede que no lo haya. Para que realmente se produzca el interbloqueo, se requiere una cuarta
condición:
4. Espera circular. Existe una lista cerrada de procesos, de tal manera que cada proceso posee al
menos un recurso necesitado por el siguiente proceso de la lista (como ejemplo, véase las Figuras 6.5c y 6.6).
Las tres primeras condiciones son necesarias pero no suficientes para que exista un interbloqueo.
La cuarta condición es, realmente, una consecuencia potencial de las tres primeras. Es decir, si se
cumplen las tres primeras condiciones, se puede producir una secuencia de eventos que conduzca a
una espera circular irresoluble. La espera circular irresoluble es de hecho la definición del interbloqueo. La espera circular enumerada como cuarta condición es irresoluble debido a que se cumplen las
06-Capitulo 6
16/5/05
17:04
Página 267
Concurrencia. Interbloqueo e inanición
267
tres primeras condiciones. Por tanto, las cuatro condiciones de forma conjunta constituyen condiciones necesarias y suficientes para el interbloqueo1.
Para clarificar esta discusión, es útil volver al concepto de diagrama de progreso conjunto, como
el que se mostró en la Figura 6.2. Recuerde que se definió una región fatal como una en la que, una
vez que los procesos han entrado en la región, estos se verán involucrados en un interbloqueo. Una
región fatal sólo existe si se cumplen conjuntamente las tres primeras condiciones anteriormente expuestas. Si no se satisface una o más de estas condiciones, no hay una región fatal y no puede ocurrir
un interbloqueo. Por tanto, se trata de condiciones necesarias para el interbloqueo. Para que se produzca el interbloqueo, no debe haber solamente una región fatal, sino también una secuencia de peticiones de recursos que conduzca a la región fatal. Si se cumple la condición de espera circular, se ha
entrado, de hecho, en la región fatal. Por tanto, las cuatro condiciones expuestas anteriormente son
suficientes para el interbloqueo. Resumiendo:
Posibilidad de interbloqueo
Existencia de interbloqueo
1. Exclusión mutua
1. Exclusión mutua
2. Sin expropiación
2. Sin expropiación
3. Retención y espera
3. Retención y espera
4. Espera circular
Existen tres estrategias para el tratamiento del interbloqueo. En primer lugar, se puede prevenir
el interbloqueo adoptando una política que elimine una de las condiciones (las 4 condiciones enumeradas previamente). En segundo lugar, se puede predecir el interbloqueo tomando las apropiadas
decisiones dinámicas basadas en el estado actual de asignación de recursos. En tercer lugar, se puede intentar detectar la presencia del interbloqueo (se cumplen las 4 condiciones) y realizar las acciones pertinentes para recuperarse del mismo. A continuación, se estudiarán sucesivamente estas
estrategias.
6.2. PREVENCIÓN DEL INTERBLOQUEO
La estrategia de prevención del interbloqueo consiste, de forma simplificada, en diseñar un sistema de manera que se excluya la posibilidad del interbloqueo. Se pueden clasificar los métodos de
prevención del interbloqueo en dos categorías. Un método indirecto de prevención del interbloqueo es impedir la aparición de una de las tres condiciones necesarias listadas previamente (las
tres primeras). Un método directo de prevención del interbloqueo impide que se produzca una espera circular (cuarta condición). A continuación, se examinan las técnicas relacionadas con las
cuatro condiciones.
1
Prácticamente todos los libros de texto simplemente enumeran estas cuatro condiciones como las condiciones necesarias para
el interbloqueo, pero esa presentación obscurece algunos de los aspectos más sutiles. La cuarta condición, la espera circular, es fundamentalmente diferente de las otras tres condiciones. Las tres primeras condiciones son decisiones de diseño, mientras que la cuarta
es una circunstancia que podría ocurrir dependiendo de la secuencia de peticiones y liberaciones realizada por los procesos involucrados. La asociación entre la espera circular y las tres condiciones necesarias conduce a una inadecuada distinción entre prevención y
predicción. Véase [SHUB90] y [SHUB03] para una discusión sobre el tema.
06-Capitulo 6
268
16/5/05
17:04
Página 268
Sistemas operativos. Aspectos internos y principios de diseño
EXCLUSIÓN MUTUA
En general, la primera de las cuatro condiciones no puede eliminarse. Si el acceso a un recurso requiere exclusión mutua, el sistema operativo debe proporcionarlo. Algunos recursos, como los ficheros, pueden permitir múltiples accesos de lectura pero acceso exclusivo sólo para las escrituras. Incluso en este caso, puede ocurrir un interbloqueo si más de un proceso requiere permiso de
escritura.
RETENCIÓN Y ESPERA
La condición de retención y espera puede eliminarse estableciendo que un proceso debe solicitar
al mismo tiempo todos sus recursos requeridos, bloqueándolo hasta que se le puedan conceder
simultáneamente todas las peticiones. Esta estrategia es insuficiente en dos maneras. En primer
lugar, un proceso puede quedarse esperando mucho tiempo hasta que todas sus solicitudes de recursos puedan satisfacerse, cuando, de hecho, podría haber continuado con solamente algunos de
los recursos. En segundo lugar, los recursos asignados a un proceso pueden permanecer inutilizados durante un periodo de tiempo considerable, durante el cual se impide su uso a otros procesos. Otro problema es que un proceso puede no conocer por anticipado todos los recursos que
requerirá.
Hay también un problema práctico creado por el uso de una programación modular o una estructura multihilo en una aplicación. La aplicación necesitaría ser consciente de todos los recursos que se
solicitarán en todos los niveles o en todos los módulos para hacer una solicitud simultánea.
SIN EXPROPIACIÓN
Esta condición se puede impedir de varias maneras. En primer lugar, si a un proceso que mantiene
varios recursos se le deniega una petición posterior, ese proceso deberá liberar sus recursos originales
y, si es necesario, los solicitará de nuevo junto con el recurso adicional. Alternativamente, si un proceso solicita un recurso que otro proceso mantiene actualmente, el sistema operativo puede expropiar
al segundo proceso y obligarle a liberar sus recursos. Este último esquema impediría el interbloqueo
sólo si no hay dos procesos que posean la misma prioridad.
Esta estrategia es sólo práctica cuando se aplica a recursos cuyo estado se puede salvar y restaurar más tarde, como es el caso de un procesador.
ESPERA CIRCULAR
La condición de espera circular se puede impedir definiendo un orden lineal entre los distintos tipos
de recursos. Si a un proceso le han asignado recursos de tipo R, posteriormente puede pedir sólo
aquellos recursos cuyo tipo tenga un orden posterior al de R.
Para comprobar que esta estrategia funciona correctamente, se puede asociar un índice a cada
tipo de recurso, de manera que el recurso Ri precede al Rj en la ordenación si i < j. A continuación, supóngase que dos procesos, A y B, están involucrados en un interbloqueo debido a que A ha adquirido
Ri y solicitado Rj, y B ha adquirido Rj y solicitado Ri. Esta condición es imposible puesto que implica
que i < j y j < i.
Como ocurría en el caso de la retención y espera, la prevención de la espera circular puede ser
ineficiente, ralentizando los procesos y denegando innecesariamente el acceso a un recurso.
06-Capitulo 6
16/5/05
17:04
Página 269
Concurrencia. Interbloqueo e inanición
269
6.3. PREDICCIÓN DEL INTERBLOQUEO
Una estrategia para resolver el problema del interbloqueo que difiere sutilmente de la prevención del
interbloqueo es la predicción del interbloqueo2. En la prevención del interbloqueo, se restringen las
solicitudes de recurso para impedir al menos una de las cuatro condiciones de interbloqueo. Esto se
realiza o bien indirectamente, impidiendo una de las tres condiciones de gestión necesarias (exclusión
mutua, retención y espera, y sin expropiación), o directamente, evitando la espera circular. Esto conlleva un uso ineficiente de los recursos y una ejecución ineficiente de los procesos. La predicción del
interbloqueo, por otro lado, permite las tres condiciones necesarias pero toma decisiones razonables
para asegurarse de que nunca se alcanza el punto del interbloqueo. De esta manera, la predicción permite más concurrencia que la prevención. Con la predicción del interbloqueo, se decide dinámicamente si la petición actual de reserva de un recurso, si se concede, podrá potencialmente causar un interbloqueo. La predicción del interbloqueo, por tanto, requiere el conocimiento de las futuras
solicitudes de recursos del proceso.
En esta sección se describen dos técnicas para predecir el interbloqueo:
• No iniciar un proceso si sus demandas podrían llevar al interbloqueo.
• No conceder una petición adicional de un recurso por parte de un proceso si esta asignación
podría provocar un interbloqueo.
DENEGACIÓN DE LA INICIACIÓN DEL PROCESO
Considere un sistema con n procesos y m tipos diferentes de recursos. En el mismo se definen los siguientes vectores y matrices:
Recursos = R = (R1, R2, ..., Rm)
cantidad total de cada recurso en el sistema
Disponibles = D = (D1, D2, ..., Dm)
cantidad total de cada recurso no asignada a ningún
proceso
Necesidad = N =
N11 N12 ... N1m
N21 N22 ... N2m
. . . .
. . . .
. . . .
Nn1 Nn2 ... Nnm
Nij = necesidades del proceso i con respecto
al recurso j
Asignación = A =
A11 A12 ... A1m
A21 A22 ... A2m
. . . .
. . . .
. . . .
An1 An2 ... Anm
Aij = asignación actual al proceso i con respecto
al recurso j
2
El término predicción es un poco confuso. De hecho, se podrían considerar las estrategias estudiadas en esta sección como
ejemplos de prevención del interbloqueo debido a que impiden efectivamente la aparición del interbloqueo.
06-Capitulo 6
270
16/5/05
17:04
Página 270
Sistemas operativos. Aspectos internos y principios de diseño
La matriz de Necesidad proporciona los requisitos máximos de cada proceso con respecto a cada
recurso, estando una fila dedicada a cada proceso. Esta información debe declararse con antelación
por el proceso para que la predicción del interbloqueo funcione. De modo similar, la matriz de Asignación proporciona la asignación actual a cada proceso. Se cumplen las siguientes relaciones:
n
1. Rj = Dj +Â Aij, para todo j
Todos los recursos están disponibles o asignados.
2. Nij £ Rj, para todo i, j
Ningún proceso puede necesitar más de la cantidad total
de recursos existentes en el sistema.
3. Aij £ Nij, para todo i, j
Ningún proceso tiene asignados más recursos de cualquier tipo que sus necesidades originales de ese recurso.
i=1
Con estas cantidades definidas, se puede establecer una política de predicción del interbloqueo
que rechace iniciar un nuevo proceso si sus requisitos de recursos pudiesen conducir al interbloqueo.
Se inicia un nuevo proceso Pn + 1 sólo si:
n
Rj ≥ N(n+1)j +Â Nij
para todo j
i=1
Es decir, sólo puede iniciarse un proceso si se pueden satisfacer las necesidades máximas de todos los procesos actuales más las del nuevo proceso. Esta estrategia está lejos de ser óptima, debido a
que asume el peor caso: todos los procesos solicitarán sus necesidades máximas simultáneamente.
DENEGACIÓN DE ASIGNACIÓN DE RECURSOS
La estrategia de la denegación de asignación de recursos, denominada algoritmo del banquero3, se
propuso por primera vez en [DIJK65]. Se comenzará definiendo los conceptos de estado y de estado
seguro. Considere un sistema con un número fijo de procesos y de recursos. En un determinado momento un proceso puede tener cero o más recursos asignados. El estado del sistema refleja la asignación actual de recursos a procesos. Por tanto, el estado consiste en los dos vectores, Recursos y Disponibles, y las dos matrices, Necesidad y Asignación, definidas anteriormente. Un estado seguro es
aquél en el que hay al menos una secuencia de asignación de recursos a los procesos que no implica
un interbloqueo (es decir, todos los procesos pueden ejecutarse al completo). Un estado inseguro es,
evidentemente, un estado que no es seguro.
El siguiente ejemplo muestra estos conceptos. La Figura 6.7a muestra el estado de un sistema
que consta de cuatro procesos y tres recursos. La cantidad total de recursos R1, R2, y R3 son de 9,
3 y 6 unidades, respectivamente. En el estado actual se han realizado asignaciones a los cuatro procesos, dejando disponibles 1 unidad de R2 y 1 unidad de R3. La pregunta es: ¿es un estado seguro?
Para responder a esta cuestión, se plantea una pregunta intermedia: ¿alguno de los cuatro procesos
3
Dijkstra usó este nombre debido a la analogía de este problema con uno que se da en la banca, donde los clientes que desean
un préstamo de dinero se corresponden con los procesos y el dinero para prestar se corresponde con los recursos. Enunciado como un
problema bancario, el problema sería como se especifica a continuación. El banco tiene una reserva limitada de dinero para prestar y
una lista de clientes, cada uno con una línea de crédito. En un momento dado, un cliente puede optar por solicitar un préstamo de una
cantidad que corresponda con una parte de su línea de crédito, y no hay garantía de que haga un reembolso hasta después de haber
obtenido la cantidad máxima del préstamo. El banquero puede rechazar un préstamo a un cliente si hay riesgo de que el banco se
quede sin fondos suficientes para satisfacer solicitudes de préstamo posteriores que permitirán que los clientes realicen finalmente un
reembolso.
06-Capitulo 6
16/5/05
17:04
Página 271
Concurrencia. Interbloqueo e inanición
271
puede ejecutarse por completo con los recursos disponibles? Es decir, ¿puede satisfacerse con los
recursos disponibles la diferencia entre los requisitos máximos y la asignación actual de algún proceso?
En términos de las matrices y vectores presentados previamente, la condición que debe cumplirse
para el proceso i es la siguiente:
Nij - Aij £ Dj, para todo j
Claramente, esto no es posible en el caso de P1, que tiene sólo una unidad de R1 y requiere 2
unidades adicionales, otras 2 de R2, así como 2 unidades de R3. Sin embargo, asignando una unidad de R3 al proceso P2, éste logra tener asignados sus recursos máximos requeridos y puede ejecutarse por completo. Asuma que esto se lleva a cabo. Cuando se complete P2, podrá retornar sus
recursos al conjunto de recursos disponibles. El estado resultante se muestra en la Figura 6.7b. En
ese momento, se repetiría la pregunta de si cualquiera de los procesos restantes puede completarse.
En este caso, todos los procesos restantes podrían completarse. Supóngase, que se elige P1, asignándole los recursos requeridos, se completa P1, y devuelve todos sus recursos al conjunto de disponibles. El estado resultante se muestra en la Figura 6.7c. A continuación, se puede completar P3,
alcanzándose el estado de la Figura 6.7d. Finalmente, se puede completar P4. En ese instante, todos los procesos se han ejecutado por completo. Por tanto, el estado definido en la Figura 6.7a es
seguro.
Estos conceptos sugieren la siguiente estrategia de predicción del interbloqueo, que asegura que
el sistema de procesos y recursos está siempre en un estado seguro. Cuando un proceso solicite un
conjunto de recursos, supóngase que se concede la petición, actualice el estado del sistema en consecuencia, y determine si el resultado es un estado seguro. En caso afirmativo, se concede la petición.
En caso contrario, se bloquea el proceso hasta que sea seguro conceder la petición.
Considérese el estado definido en la Figura 6.8a. Supóngase que P2 solicita una unidad adicional
de R1 y otra de R3. Si se asume que se concede la petición, el estado resultante es el de la Figura
6.7a, que ya se ha comprobado previamente que es un estado seguro. Por tanto, es seguro conceder la
petición. Retornando al estado de la Figura 6.8a y suponiendo que P1 solicita una unidad de R1 y otra
de R3; si se asume que se concede la petición, el estado resultante es el representado en la Figura
6.8b. ¿Es un estado seguro? La respuesta es que no, debido a que cada proceso necesitará al menos
una unidad adicional de R1, y no hay ninguna disponible. Por tanto, basándose en la predicción del
interbloqueo, la solicitud de P1 se denegaría y P1 se debería bloquear.
Es importante resaltar que la Figura 6.8b no es un estado de interbloqueo. Sólo indica la posibilidad del interbloqueo. Es posible, por ejemplo, que si P1 ejecutara a partir de este estado, liberaría
posteriormente una unidad de R1 y una de R3 antes de volver a necesitar estos recursos de nuevo. Si
esto sucediera, el sistema retornaría a un estado seguro. Por tanto, la estrategia de predicción de interbloqueo no predice el interbloqueo con certeza; sólo anticipa la posibilidad del interbloqueo y asegura que no haya tal posibilidad.
La Figura 6.9 da una versión abstracta de la lógica de predicción del interbloqueo. El algoritmo
principal se muestra en la parte (b). La estructura de datos estado define el estado del sistema,
mientras que peticion[*] es un vector que define los recursos solicitados por el proceso i. En
primer lugar, se hace una prueba para asegurarse de que la petición no excede las necesidades originales del proceso. Si la petición es válida, el siguiente paso es determinar si es posible satisfacerla (es decir, hay suficientes recursos disponibles). Si no es posible, se suspende la ejecución del
proceso. En caso de que lo sea, el paso final es determinar si es seguro satisfacer la petición. Para
hacer esto, se asignan provisionalmente los recursos al proceso i para formar el nuevo_estado.
A continuación, se realiza una comprobación de si el estado es seguro utilizando el algoritmo de la
Figura 6.9c.
06-Capitulo 6
272
16/5/05
17:04
Página 272
Sistemas operativos. Aspectos internos y principios de diseño
R1
R2
R3
P1
3
2
2
6
1
3
P2
P3
3
1
4
P4
4
2
2
Matriz de necesidad N
R1
R2
R3
P1
1
0
0
6
1
2
P2
P3
2
1
1
P4
0
0
2
Matriz de asignación A
P1
P2
P3
P4
R1
2
0
1
4
R2
R3
2
2
0
1
0
3
2
0
C–A
R1
R2
R3
R1
R2
R3
9
3
6
0
1
1
Vector de disponibles D
Vector de recursos R
(a) Estado inicial
R1
R2
R3
P1
3
2
2
0
0
0
P2
P3
3
1
4
P4
4
2
2
Matriz de necesidad N
R1
R2
R3
P1
1
0
0
0
0
0
P2
P3
2
1
1
P4
0
0
2
Matriz de asignación A
P1
P2
P3
P4
R1
2
0
1
4
R2
R3
2
2
0
0
0
3
2
0
C–A
R1
R2
R3
R1
R2
R3
9
3
6
6
2
3
Vector de disponibles D
Vector de recursos R
(b) P2 ejecuta hasta completarse
R1
R2
R3
P1
0
0
0
P2
0
0
0
P3
3
1
4
P4
4
2
2
Matriz de necesidad N
R1
R2
R3
P1
0
0
0
P2
0
0
0
P3
2
1
1
P4
0
0
2
Matriz de asignación A
P1
P2
P3
P4
R1
0
0
1
4
R2
R3
0
0
0
0
0
3
2
0
C–A
R1
R2
R3
R1
R2
R3
9
3
6
7
2
3
Vector de disponibles D
Vector de recursos R
(c) P1 ejecuta hasta completarse
R1
R2
R3
P1
0
0
0
P2
0
0
0
P3
0
0
0
P4
4
2
2
Matriz de necesidad N
R1
R2
R3
P1
0
0
0
P2
0
0
0
P3
0
0
0
P4
0
0
2
Matriz de asignación A
P1
P2
P3
P4
R1
0
0
0
4
R1
R2
R3
R1
R2
R3
9
3
6
9
3
4
Vector de recursos R
Vector de disponibles D
(d) P3 ejecuta hasta completarse
Figura 6.7.
Determinación de un estado seguro.
R2
R3
0
0
0
0
0
0
2
0
C–A
06-Capitulo 6
16/5/05
17:04
Página 273
Concurrencia. Interbloqueo e inanición
R1
R2
R3
P1
3
2
2
P2
6
1
3
P3
3
1
4
P4
4
2
2
Matriz de necesidad N
R1
R2
R3
P1
1
0
0
P2
5
1
1
P3
2
1
1
P4
0
0
2
Matriz de asignación A
P1
P2
P3
P4
R1
2
1
1
4
273
R2
R3
2
2
0
2
0
3
2
0
C–A
R1
R2
R3
R1
R2
R3
9
3
6
1
1
2
Vector de disponibles D
Vector de recursos R
(a) Estado inicial
R1
R2
R3
P1
3
2
2
6
1
3
P2
P3
3
1
4
P4
4
2
2
Matriz de necesidad N
R1
R2
R3
P1
2
0
1
5
1
1
P2
P3
2
1
1
P4
0
0
2
Matriz de asignación A
P1
P2
P3
P4
R1
1
1
1
4
R2
R3
2
1
0
2
0
3
2
0
C–A
R1
R2
R3
R1
R2
R3
9
3
6
0
1
1
Vector de disponibles D
Vector de recursos R
(b) P1 solicita una unidad de R1 y de R3
Figura 6.8.
Determinación de un estado inseguro.
La predicción del interbloqueo tiene la ventaja de que no es necesario expropiar a los procesos ni
retroceder su ejecución, como ocurre con la detección del interbloqueo, y es menos restrictivo que la
prevención del interbloqueo. Sin embargo, tiene varias restricciones de uso:
• Deben establecerse por anticipado los requisitos máximos de recursos de cada proceso.
• Los procesos involucrados deben ser independientes, es decir, el orden en el que se ejecutan
no debe estar restringido por ningún requisito de sincronización.
• Debe haber un número fijo de recursos que asignar.
• Ningún proceso puede terminar mientras mantenga recursos.
6.4. DETECCIÓN DEL INTERBLOQUEO
Las estrategias de prevención de interbloqueo son muy conservadoras: resuelven el problema del interbloqueo limitando el acceso a los recursos e imponiendo restricciones a los procesos. En el extremo contrario, la estrategia de detección del interbloqueo no limita el acceso a los recursos ni restringe
las acciones de los procesos. Con la detección del interbloqueo, los recursos pedidos se conceden a
los procesos siempre que sea posible. Periódicamente, el sistema operativo realiza un algoritmo que
le permite detectar la condición de espera circular descrita anteriormente en la condición (4) e ilustrada en la Figura 6.6.
06-Capitulo 6
274
16/5/05
17:04
Página 274
Sistemas operativos. Aspectos internos y principios de diseño
struct estado
{
int recursos[m];
int disponibles[m];
int necesidad[n][m];
int asignacion[n][m];
}
(a) estructuras de datos globales
if (asignacion [i,*] + peticion [*] > necesidad [i,*])
< error >;
else if (peticion [*] > disponibles [*])
< suspender al proceso >;
else
{
< definir nuevo_estado como:
asignacion [i,*] = asignacion [i,*] + peticion [*];
disponibles [*] = disponibles [*] - peticion [*] >;
}
if (seguro(nuevo_estado))
< llevar a cabo la asignación >;
else
{
< restaurar el estado original >;
< suspender al proceso >;
}
/* petición total > necesidad */
/* simular asignación */
(b) algoritmo de asignación de recursos
boolean seguro (estado E)
{
int disponibles_actual[m];
proceso resto[<número de procesos>];
disponibles_actual = disponibles;
resto = {todos los procesos};
posible = verdadero;
while (posible)
{
<encontrar un proceso Pk en resto tal que
necesidad [k,*] - asignacion [k,*] <= disponibles_actual;>
if (encontrado)
/* simular ejecución de Pk */
{
disponibles_actual = disponibles_actual + asignacion [k,*];
resto = resto – {Pk};
}
else
posible = falso;
}
return (resto == null);
}
(c) algoritmo para comprobar si el estado es seguro (algoritmo del banquero)
Figura 6.9.
Lógica para la predicción del interbloqueo.
06-Capitulo 6
16/5/05
17:04
Página 275
Concurrencia. Interbloqueo e inanición
275
ALGORITMO DE DETECCIÓN DEL INTERBLOQUEO
La comprobación de si hay interbloqueo se puede hacer con tanta frecuencia como una vez por cada
petición de recurso o, con menos frecuencia, dependiendo de la probabilidad de que ocurra un interbloqueo. Realizar la comprobación por cada petición de recurso tiene dos ventajas: conlleva una detección temprana y el algoritmo es relativamente sencillo debido a que está basado en cambios graduales del estado del sistema. Por otro lado, estas comprobaciones frecuentes consumen un
considerable tiempo del procesador.
Un algoritmo usual para la detección del interbloqueo es el que se describe en [COFF71]. En el
mismo se utilizan la matriz de Asignación y el vector de Disponibles descritos en la sección previa.
Además, se define una matriz de solicitud S tal que Sij representa la cantidad de recursos de tipo j solicitados por el proceso i. El algoritmo actúa marcando los procesos que no están en un interbloqueo. Inicialmente, todos los procesos están sin marcar. A continuación, se llevan a cabo los siguientes pasos:
1. Se marca cada proceso que tenga una fila de la matriz de Asignación completamente a cero.
2. Se inicia un vector temporal T asignándole el vector Disponibles.
3. Se busca un índice i tal que el proceso i no esté marcado actualmente y la fila i-ésima de S sea
menor o igual que T. Es decir, Sik £ Tk, para 1 £ k £ m. Si no se encuentra ninguna fila, el algoritmo termina.
4. Si se encuentra una fila que lo cumpla, se marca el proceso i y se suma la fila correspondiente
de la matriz de asignación a T. Es decir, se ejecuta Tk = Tk + Aik, para 1 £ k £ m. A continuación, se vuelve al tercer paso.
Existe un interbloqueo si y sólo si hay procesos sin marcar al final del algoritmo. Cada proceso
sin marcar está en un interbloqueo. La estrategia de este algoritmo es encontrar un proceso cuyas peticiones de recursos puedan satisfacerse con los recursos disponibles, y, a continuación, asumir que se
conceden estos recursos y el proceso se ejecuta hasta terminar y libera todos sus recursos. El algoritmo, a continuación, busca satisfacer las peticiones de otro proceso. Nótese que este algoritmo no garantiza la prevención del interbloqueo; esto dependerá del orden en que se concedan las peticiones futuras. Su labor es determinar si existe actualmente un interbloqueo.
Se puede utilizar la Figura 6.10 para mostrar el algoritmo de detección del interbloqueo. El algoritmo actúa de la siguiente manera:
1. Marca P4, porque no tiene recursos asignados.
2. Fija T = (0 0 0 0 1).
3. La petición del proceso P3 es menor o igual que T, así que se marca P3 y ejecuta T = T + (0 0
0 1 0) = (0 0 0 1 1).
4. Ningún otro proceso sin marcar tiene una fila de S que sea menor o igual a T. Por tanto, el algoritmo termina.
El algoritmo concluye sin marcar P1 ni P2, indicando que estos procesos están en un interbloqueo.
RECUPERACIÓN
Una vez que se ha detectado el interbloqueo, se necesita alguna estrategia para recuperarlo. Las siguientes estrategias, listadas en orden de sofisticación creciente, son posibles:
06-Capitulo 6
276
16/5/05
17:04
Página 276
Sistemas operativos. Aspectos internos y principios de diseño
R1
R2
R3
R4
R5
P1
0
1
0
0
1
P2
0
0
1
0
P3
0
0
0
P4
1
0
1
R1
R2
R3
R4
R5
R1
R2
R3
R4
R5
P1
1
0
1
1
0
2
1
1
2
1
1
P2
1
1
0
0
0
0
1
P3
0
0
0
1
0
0
1
P4
0
0
0
0
0
Matriz de asignación A
Matriz de solicitud S
Vector de recursos
R1
R2
R3
R4
R5
0
0
0
0
1
Vector de disponibles
Figura 6.10.
Ejemplo de detección del interbloqueo.
1. Abortar todos los procesos involucrados en el interbloqueo. Esta es, se crea o no, una de las
más usuales, si no la más, solución adoptada en los sistemas operativos.
2. Retroceder cada proceso en interbloqueo a algún punto de control (checkpoint) previamente
definido, y rearrancar todos los procesos. Esto requiere que se implementen en el sistema mecanismos de retroceso y rearranque. El riesgo de esta técnica es que puede repetirse el interbloqueo original. Sin embargo, el indeterminismo del procesamiento concurrente puede asegurar que probablemente esto no suceda.
3. Abortar sucesivamente los procesos en el interbloqueo hasta que éste deje de existir. El orden
en que seleccionan los procesos para abortarlos debería estar basado en algunos criterios que
impliquen un coste mínimo. Después de cada aborto, se debe invocar de nuevo el algoritmo de
detección para comprobar si todavía existe el interbloqueo.
4. Expropiar sucesivamente los recursos hasta que el interbloqueo deje de existir. Como en
el tercer punto, se debería utilizar una selección basada en el coste, y se requiere una nueva invocación del algoritmo de detección después de cada expropiación. Un proceso al
que se le ha expropiado un recurso debe retroceder a un punto anterior a la adquisición de
ese recurso.
Para los Puntos (3) y (4), el criterio de selección podría ser uno de los siguientes. Se elige el proceso con:
• la menor cantidad de tiempo de procesador consumida hasta ahora
• la menor cantidad de salida producida hasta ahora
• el mayor tiempo restante estimado
• el menor número total de recursos asignados hasta ahora
• la menor prioridad
Algunas de estas cantidades son más fáciles de medir que otras. El tiempo restante estimado es
particularmente de difícil aplicación. Además, con excepción del criterio basado en la prioridad, no
hay ninguna otra indicación del «coste» para el usuario, en contraposición con el coste para el sistema como un todo.
06-Capitulo 6
16/5/05
17:04
Página 277
Concurrencia. Interbloqueo e inanición
277
6.5. UNA ESTRATEGIA INTEGRADA DE TRATAMIENTO DEL INTERBLOQUEO
Como sugiere la Tabla 6.1, hay ventajas y desventajas en todas las estrategias para el tratamiento del
interbloqueo. En vez de intentar diseñar una solución en el sistema operativo que utilice una sola de
estas estrategias, podría ser más eficiente usar estrategias diferentes en distintas situaciones.
[HOWA73] sugiere la siguiente técnica:
• Agrupar los recursos en diversas clases de recursos diferentes.
• Utilizar la estrategia del orden lineal definida previamente para prevenir la espera circular impidiendo los interbloqueos entre clases de recursos.
• Dentro de una clase de recursos, usar el algoritmo que sea más apropiado para esa clase.
Como un ejemplo de esta técnica, considere las siguientes clases de recursos:
• Espacio de intercambio. Bloques de memoria en almacenamiento secundario utilizados al
expulsar los procesos.
• Recursos del proceso. Dispositivos asignables, como dispositivos de cinta y ficheros.
• Memoria principal. Asignable a los procesos en páginas o segmentos.
• Recursos internos. Como canales de E/S.
El orden de la lista anterior representa el orden en el que se asignan los recursos. Es un orden razonable, considerando la secuencia de pasos que puede seguir un proceso durante su tiempo de vida.
Dentro de cada clase, se podrían utilizar las siguientes estrategias:
• Espacio de intercambio. La prevención de los interbloqueos, obligando a que se asignen al
mismo tiempo todos los recursos necesitados que vayan a usarse, como en la estrategia de prevención de la retención y espera. Esta estrategia es razonable si se conocen los requisitos máximos de almacenamiento, lo cual frecuentemente es cierto. La predicción también es una posibilidad factible.
• Recursos del proceso. La predicción será usualmente efectiva para esta categoría, porque es
razonable esperar que los procesos declaren anticipadamente los recursos de esta clase que
requerirán. La prevención mediante ordenamiento de recursos es también posible para esta
clase.
• Memoria principal. La prevención por expropiación parece la estrategia más apropiada para
la memoria principal. Cuando se expropia a un proceso, simplemente es expulsado a memoria
secundaria, liberando el espacio para resolver el interbloqueo.
• Recursos internos. Puede utilizarse la prevención mediante el ordenamiento de recursos.
6.6. EL PROBLEMA DE LOS FILÓSOFOS COMENSALES
En esta sección se trata el problema de los filósofos comensales, presentado por Dijkstra [DIJK71].
Cinco filósofos viven en una casa, donde hay una mesa preparada para ellos. Básicamente, la vida
de cada filósofo consiste en pensar y comer, y después de años de haber estado pensando, todos los
filósofos están de acuerdo en que la única comida que contribuye a su fuerza mental son los espaguetis. Debido a su falta de habilidad manual, cada filósofo necesita dos tenedores para comer los
espaguetis.
06-Capitulo 6
278
16/5/05
17:04
Página 278
Sistemas operativos. Aspectos internos y principios de diseño
La disposición para la comida es simple (Figura 6.11): una mesa redonda en la que está colocado
un gran cuenco para servir espaguetis, cinco platos, uno para cada filósofo, y cinco tenedores. Un filósofo que quiere comer se dirige a su sitio asignado en la mesa y, utilizando los dos tenedores situados a cada lado del plato, toma y come algunos espaguetis. El problema: diseñar un ritual (algoritmo)
que permita a los filósofos comer. El algoritmo debe satisfacer la exclusión mutua (no puede haber
dos filósofos que puedan utilizar el mismo tenedor a la vez) evitando el interbloqueo y la inanición
(en este caso, el término tiene un sentido literal, además de algorítmico).
Este problema puede que no parezca importante o relevante en sí mismo. Sin embargo, muestra
los problemas básicos del interbloqueo y la inanición. Además, intenta desarrollar soluciones que revelan muchas de las dificultades de la programación concurrente (como ejemplo, véase [GING90]).
Asimismo, el problema de los filósofos comensales puede considerarse como representativo de los
problemas que tratan la coordinación de recursos compartidos, que puede ocurrir cuando una aplicación incluye hilos concurrentes en su ejecución. Por consiguiente, este problema es un caso de prueba
estándar para evaluar las estrategias de sincronización.
SOLUCIÓN UTILIZANDO SEMÁFOROS
La Figura 6.12 muestra una solución utilizando semáforos. Cada filósofo toma primero el tenedor de
la izquierda y después el de la derecha. Una vez que el filósofo ha terminado de comer, vuelve a colocar los dos tenedores en la mesa. Esta solución, desgraciadamente, conduce al interbloqueo: Si todos
los filósofos están hambrientos al mismo tiempo, todos ellos se sentarán, asirán el tenedor de la izquierda y tenderán la mano para tomar el otro tenedor, que no estará allí. En esta indecorosa posición,
los filósofos pasarán hambre.
P2
P1
P3
P4
Figura 6.11.
P0
Disposición para los filósofos comensales.
06-Capitulo 6
16/5/05
17:04
Página 279
Concurrencia. Interbloqueo e inanición
279
Para superar el riesgo de interbloqueo, se podrían comprar cinco tenedores adicionales (una solución más higiénica) o enseñar a los filósofos a comer espaguetis con un solo tenedor. Como alternativa, se podría incorporar un asistente que sólo permitiera que haya cuatro filósofos al mismo tiempo
en el comedor. Con un máximo de cuatro filósofos sentados, al menos un filósofo tendrá acceso a los
dos tenedores. La Figura 6.13 muestra esta solución, utilizando de nuevo semáforos. Esta solución
está libre de interbloqueos e inanición.
SOLUCIÓN UTILIZANDO UN MONITOR
La Figura 6.14 muestra una solución al problema de los filósofos comensales utilizando un monitor.
Se define un vector de cinco variables de condición, una variable de condición por cada tenedor. Estas variables de condición se utilizan para permitir que un filósofo espere hasta que esté disponible un
tenedor. Además, hay un vector de tipo booleano que registra la disponibilidad de cada tenedor (verdadero significa que el tenedor está disponible). El monitor consta de dos procedimientos. El procedimiento obtiene_tenedores lo utiliza un filósofo para obtener sus tenedores, el situado a su izquierda y a su derecha. Si ambos tenedores están disponibles, el proceso que corresponde con el
filósofo se encola en la variable de condición correspondiente. Esto permite que otro proceso filósofo
entre en el monitor. Se utiliza el procedimiento libera_tenedores para hacer que queden disponibles los dos tenedores. Nótese que la estructura de esta solución es similar a la solución del semáforo
propuesta en la Figura 6.12. En ambos casos, un filósofo toma primero el tenedor de la izquierda y
después el de la derecha. A diferencia de la solución del semáforo, esta solución del monitor no sufre
interbloqueos, porque sólo puede haber un proceso en cada momento en el monitor. Por ejemplo, se
/* programa filosofos_comensales */
semaforo tenedor [5] = {1};
int i;
void filosofo (int i)
{
while (verdadero)
{
piensa ();
wait (tenedor[i]);
wait (tenedor[(i+1) mod 5]);
come ();
signal (tenedor[(i+1) mod 5]);
signal (tenedor[i]);
}
}
void main ()
{
paralelos (filosofo (0), filosofo (1), filosofo (2),
filosofo (3), filosofo (4));
}
Figura 6.12.
Una primera solución al problema de los filósofos comensales.
06-Capitulo 6
280
16/5/05
17:04
Página 280
Sistemas operativos. Aspectos internos y principios de diseño
/* programa filosofos_comensales */
semaforo tenedor [5] = {1};
semaforo comedor = {4};
int i;
void filosofo (int i)
{
while (verdadero)
{
piensa ();
wait (comedor);
wait (tenedor[i]);
wait (tenedor[(i+1) mod 5]);
come ();
signal (tenedor[(i+1) mod 5]);
signal (tenedor[i]);
signal (comedor);
}
}
void main ()
{
paralelos (filosofo (0), filosofo (1), filosofo (2),
filosofo (3), filosofo (4));
}
Figura 6.13.
Una segunda solución al problema de los filósofos comensales.
garantiza que el primer proceso filósofo que entre en el monitor pueda asir el tenedor de la derecha
después de que tome el de la izquierda pero antes de que el siguiente filósofo a la derecha tenga una
oportunidad de asir el tenedor de su izquierda, que es el que está a la derecha de este filósofo.
6.7. MECANISMOS DE CONCURRENCIA DE UNIX
UNIX proporciona diversos mecanismos de comunicación y sincronización entre procesos. En esta
sección, se revisarán los más importantes:
• Tuberías (pipes).
• Mensajes.
• Memoria compartida.
• Semáforos.
• Señales.
Las tuberías, los mensajes y la memoria compartida pueden utilizarse para comunicar datos entre
procesos, mientras que los semáforos y las señales se utilizan para disparar acciones en otros procesos.
06-Capitulo 6
16/5/05
17:04
Página 281
Concurrencia. Interbloqueo e inanición
monitor controlador_de_comensales;
cond TenedorListo[5];
/* variable de condición para sincronizar */
boolean tenedor [5] = {verdadero};
/* disponibilidad de cada tenedor */
void obtiene_tenedores (int id_pr)
/* id_pr es el número de ident. del filósofo */
{
int izquierdo = id_pr;
int derecho = (id_pr++) % 5;
/* concede el tenedor izquierdo */
if (!tenedor(izquierdo))
cwait(TenedorListo[izquierdo]);
/* encola en variable de condición */
tenedor(izquierdo) = falso;
/* concede el tenedor derecho */
if (!tenedor(derecho))
cwait(TenedorListo[derecho]);
/* encola en variable de condición */
tenedor(derecho) = falso;
}
void libera_tenedores (int id_pr)
{
int izquierdo = id_pr;
int derecho = (id_pr++) % 5;
/* libera el tenedor izquierdo */
if (empty(TenedorListo[izquierdo]))
/* nadie espera por este tenedor */
tenedor(izquierdo) = verdadero;
else /* despierta a un proceso que espera por este tenedor */
csignal(TenedorListo[izquierdo]);
/*libera el tenedor derecho*/
if (empty(TenedorListo[derecho]))
/* nadie espera por este tenedor */
tenedor(derecho) = verdadero;
else
/* despierta a un proceso que espera por este tenedor */
csignal(TenedorListo[derecho]);
}
void filosofo[k=0 hasta 4]
{
while (verdadero)
{
<piensa>;
obtiene_tenedores(k);
<come espaguetis>;
libera_tenedores(k);
}
}
Figura 6.14.
/* los cinco clientes filósofos */
/* cliente solicita dos tenedores vía el monitor */
/* cliente libera tenedores vía el monitor */
Una solución al problema de los filósofos comensales usando un monitor.
281
06-Capitulo 6
282
16/5/05
17:04
Página 282
Sistemas operativos. Aspectos internos y principios de diseño
TUBERÍAS
Una de las contribuciones más significativas de UNIX en el desarrollo de los sistemas operativos es
la tubería. Inspirado por el concepto de corrutina [RITC84], una tubería es un buffer circular que
permite que dos procesos se comuniquen siguiendo el modelo productor-consumidor. Por tanto, se
trata de una cola de tipo el primero en entrar es el primero en salir, en la que escribe un proceso y
lee otro.
Cuando se crea una tubería, se le establece un tamaño fijo en bytes. Cuando un proceso intenta
escribir en la tubería, la petición de escritura se ejecuta inmediatamente si hay suficiente espacio; en
caso contrario, el proceso se bloquea. De manera similar, un proceso que lee se bloquea si intenta leer
más bytes de los que están actualmente en la tubería; en caso contrario, la petición de lectura se ejecuta inmediatamente. El sistema operativo asegura la exclusión mutua: es decir, en cada momento
sólo puede acceder a una tubería un único proceso.
Hay dos tipos de tuberías: con nombre y sin nombre. Sólo los procesos relacionados pueden compartir tuberías sin nombre, mientras que, los procesos pueden compartir tuberías con nombre tanto si
están relacionados como si no.
MENSAJES
Un mensaje es un conjunto de bytes con un tipo asociado. UNIX proporciona las llamadas al sistema
msgsnd y msgrcv para que los procesos puedan realizar la transferencia de mensajes. Asociada con
cada proceso existe una cola de mensajes, que funciona como un buzón.
El emisor del mensaje especifica el tipo de mensaje en cada mensaje que envía, de manera que
este tipo puede utilizarse como un criterio de selección por el receptor. El receptor puede recuperar
los mensajes tanto en orden de llegada o por su tipo. Un proceso se bloqueará cuando intente enviar
un mensaje a una cola llena. Un proceso también se bloqueará cuando intente leer un mensaje de una
cola vacía. Si un proceso intenta leer un mensaje de un cierto tipo y no es posible debido a que no
está presente ningún mensaje de este tipo, el proceso no se bloquea.
MEMORIA COMPARTIDA
La forma más rápida de comunicación entre procesos proporcionada en UNIX es la memoria compartida. Se trata de un bloque de memoria virtual compartido por múltiples procesos. Los procesos leen
y escriben en la memoria compartida utilizando las mismas instrucciones de máquina que se utilizan
para leer y escribir otras partes de su espacio de memoria virtual. El permiso para un determinado
proceso puede ser de sólo lectura o de lectura y escritura, estableciéndose de forma individual para
cada proceso. Las restricciones de exclusión mutua no son parte de este mecanismo de memoria compartida sino que las deben proporcionar los procesos que utilizan la memoria compartida.
SEMÁFOROS
Las llamadas al sistema de semáforos en UNIX System V son una generalización de las funciones
semWait y semSignal definidas en el Capítulo 5; se pueden realizar varias operaciones simultáneamente y las operaciones de incremento y decremento pueden corresponder con valores mayores que
1. El núcleo realiza todas las operaciones solicitadas atómicamente; ningún otro proceso puede acceder al semáforo hasta que se hayan completado todas las operaciones.
06-Capitulo 6
16/5/05
17:04
Página 283
Concurrencia. Interbloqueo e inanición
283
Un semáforo consta de los siguientes elementos:
• El valor actual del semáforo.
• El identificador del último proceso que operó con el semáforo.
• El número de procesos en espera de que el valor del semáforo sea mayor que su valor actual.
• El número de procesos en espera de que el valor del semáforo sea cero.
• Asociado con el semáforo están las colas de los procesos bloqueados en ese semáforo.
Los semáforos se crean realmente como conjuntos, constando cada conjunto de semáforos de uno
o más semáforos. Hay una llamada al sistema semct1 que permite que todos los valores de los semáforos del conjunto se fijen al mismo tiempo. Además, hay una llamada al sistema semop que toma
como argumento una lista de operaciones de semáforo, cada una definida sobre uno de los semáforos
de un conjunto. Cuando se realiza esta llamada, el núcleo lleva a cabo sucesivamente las operaciones
indicadas. Por cada operación, la función real se especifica mediante el valor sem_op, existiendo las
siguientes posibilidades:
• Si sem_op es positivo, el núcleo incrementa el valor del semáforo y despierta a todos los procesos en espera de que el valor del semáforo se incremente.
• Si sem_op es 0, el núcleo comprueba el valor del semáforo. Si el valor del semáforo es igual a
0, el núcleo continúa con las otras operaciones de la lista. En caso contrario, el núcleo incrementa el número de procesos en espera de que ese semáforo tenga el valor 0 y suspende al
proceso para que espere por el evento de que el valor del semáforo se haga igual a 0.
• Si sem_op es negativo y su valor absoluto es menor o igual al valor sel semáforo, el núcleo
añade sem_op (un número negativo) al valor del semáforo. Si el resultado es 0, el núcleo despierta a todos los procesos en espera de que el semáforo tome ese valor.
• Si sem_op es negativo y su valor absoluto es mayor que el valor del semáforo, el núcleo suspende al proceso en espera del evento de que el valor del semáforo se incremente.
Esta generalización de los semáforos proporciona una considerable flexibilidad para realizar la
sincronización y coordinación de procesos.
SEÑALES
Una señal es un mecanismo software que informa a un proceso de la existencia de eventos asíncronos. Una señal es similar a las interrupciones hardware pero no emplea prioridades. Es decir, todas
las señales se tratan por igual; las señales que ocurren al mismo tiempo se le presentan al proceso una
detrás de otra, sin ningún orden en particular.
Los procesos se pueden enviar señales entre sí, o puede ser el núcleo quien envíe señales internamente. Para entregar una señal, se actualiza un campo en la tabla de procesos correspondiente al proceso al que se le está enviando la señal. Dado que por cada señal se mantiene un único bit, las señales
de un determinado tipo no pueden encolarse. Una señal sólo se procesa después de que un proceso se
despierte para ejecutar o cuando el proceso está retornando de una llamada al sistema. Un proceso
puede responder a una señal realizando alguna acción por defecto (por ejemplo, su terminación), ejecutando una función de manejo de la señal o ignorando la señal.
La Tabla 6.2 enumera las señales definidas por UNIX SVR4.
06-Capitulo 6
284
16/5/05
17:04
Página 284
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 6.2.
Valor
Señales UNIX.
Nombre
Descripción
01
SIGHUP
Desconexión; enviada al proceso cuando el núcleo asume que el
usuario de ese proceso no está haciendo trabajo útil
02
SIGINT
Interrupción
03
SIGQUIT
Abandonar; enviada por el usuario para provocar la parada del
proceso y la generación de un volcado de su memoria (core dump)
04
SIGILL
Instrucción ilegal
05
SIGTRAP
Trap de traza; activa la ejecución de código para realizar un
seguimiento de la ejecución del proceso
06
SIGIOT
Instrucción IOT
07
SIGEMT
Instrucción EMT
08
SIGFPE
Excepción de coma flotante
09
SIGKILL
Matar; terminar el proceso
10
SIGBUS
Error de bus
11
SIGSEGV
Violación de segmento; el proceso intenta acceder a una posición
fuera de su espacio de direcciones
12
SIGSYS
Argumento erróneo en una llamada al sistema
13
SIGPIPE
Escritura sobre una tubería que no tiene lectores asociados
14
SIGALRM
Alarma; emitida cuando un proceso desea recibir una señal
cuando transcurra un determinado periodo de tiempo
15
SIGTERM
Terminación por software
16
SIGUSR1
Señal 1 definida por el usuario
17
SIGUSR2
Señal 2 definida por el usuario
18
SIGCHLD
Muerte de un proceso hijo
19
SIGPWR
Interrupción en el suministro de energía
6.8. MECANISMOS DE CONCURRENCIA DEL NÚCLEO DE LINUX
Linux incluye todos los mecanismos de concurrencia presentes en otros sistemas UNIX, como SVR4,
incluyendo tuberías, mensajes, memoria compartida y señales. Además, Linux 2.6 incluye un abundante conjunto de mecanismos de concurrencia especialmente destinados para su uso cuando se está
ejecutando un hilo en modo núcleo. Es decir, se trata de mecanismos utilizados dentro del núcleo
para proporcionar concurrencia en la ejecución del código de núcleo. Esta sección estudiará los mecanismos de concurrencia del núcleo Linux.
OPERACIONES ATÓMICAS
Linux proporciona un conjunto de funciones que garantiza que las operaciones sobre una variable
sean atómicas. Estas operaciones pueden utilizarse para evitar condiciones de carrera sencillas. Una
operación atómica ejecuta sin interrupción y sin interferencia. En un sistema uniprocesador, un hilo
que realiza una operación atómica no puede verse interrumpido una vez que ha comenzado la opera-
06-Capitulo 6
16/5/05
17:04
Página 285
Concurrencia. Interbloqueo e inanición
285
ción hasta que ésta termina. Además, en un sistema multiprocesador, se establece un cerrojo sobre la
variable con la que se está operando para impedir el acceso de otros hilos hasta que se complete esta
operación.
En Linux se definen dos tipos de operaciones atómicas: operaciones con enteros, que operan con
una variable de tipo entero, y operaciones con mapas de bits, que operan con un bit de un mapa de
bits (Tabla 6.3). Estas operaciones deben implementarse en cualquier arquitectura sobre la que se pretenda que ejecute Linux. Para algunas arquitecturas, hay instrucciones en lenguaje ensamblador que
corresponden directamente con estas operaciones atómicas. En otras arquitecturas, se utiliza una operación que establece un cerrojo en el bus de memoria para garantizar que la operación sea atómica.
Tabla 6.3.
Operaciones atómicas en Linux.
Operaciones atómicas con enteros
ATOMIC_INT (int i)
En una declaración: inicia un atomic_t con el
valor i
int atomic_read(atomic_t *v)
Lee el valor entero de v
void atomic_set(atomic_t *v, int i)
Asigna el valor entero i a v
void atomic_add(int i, atomic_t *v)
Suma i a v
void atomic_sub(int i, atomic_t *v)
Resta i de v
void atomic_inc(atomic_t *v)
Suma 1 a v
void atomic_dec(atomic_t *v)
Resta 1 de v
int atomic_sub_and_test(int i, atomic_t *v)
Resta i de v; devuelve 1 si el resultado es cero; y
0 en caso contrario
int atomic_dec_and_test(atomic_t *v)
Resta 1 de v; devuelve 1 si el resultado es cero; y
0 en caso contrario
Operaciones atómicas con mapas de bits
void set_bit(int n, void *dir)
Pone a 1 el bit n del mapa de bits apuntado por
dir
void clear_bit(int n, void *dir)
Pone a 0 el bit n del mapa de bits apuntado por
dir
void change_bit(int n, void *dir)
Invierte el valor del bit n del mapa de bits
apuntado por dir
int test_and_set_bit(int n, void *dir)
Pone a 1 el bit n del mapa de bits apuntado por
dir; devuelve su valor previo
int test_and_clear_bit(int n, void *dir)
Pone a 0 el bit n del mapa de bits apuntado por
dir; devuelve su valor previo
int test_and_change_bit(int n, void *dir)
Invierte el valor del bit n del mapa de bits
apuntado por dir; devuelve su valor previo
int test_bit(int n, void *dir)
Devuelve el valor del bit n del mapa de bits
apuntado por dir
Para las operaciones atómicas con enteros, se utiliza un tipo de datos especial, atomic_t.
Las operaciones atómicas con enteros pueden utilizar solamente este tipo de datos, no permitiéndose ninguna otra operación sobre el mismo. [LOVE04] enumera las siguientes ventajas de estas
restricciones:
06-Capitulo 6
286
16/5/05
17:04
Página 286
Sistemas operativos. Aspectos internos y principios de diseño
1. Las operaciones atómicas nunca se utilizan con variables que podrían en algunas circunstancias estar desprotegidas de condiciones de carrera.
2. Las variables de este tipo de datos están protegidas del uso inapropiado mediante operaciones
no atómicas.
3. El compilador no puede optimizar erróneamente el acceso al valor (por ejemplo, utilizando un
alias en vez de la dirección de memoria correcta).
4. Este tipo de datos sirve para esconder las diferencias específicas de cada arquitectura en su
implementación.
Un uso habitual de los tipos de datos enteros atómicos es la implementación de contadores.
Las operaciones atómicas con mapas de bits operan sobre una secuencia de bits almacenada en
una posición de memoria indicada por una variable de tipo puntero. Así, no hay un equivalente al tipo
de datos atomic_t requerido por las operaciones atómicas con enteros.
Las operaciones atómicas son el mecanismo más simple de sincronización del núcleo. A partir de
éstas, se pueden construir mecanismos de cerrojo más complejos.
CERROJOS CÍCLICOS
La técnica más frecuentemente utilizada para proteger una sección crítica en Linux es el cerrojo
cíclico (spinlock). En un determinado momento, sólo un único hilo puede adquirir un cerrojo cíclico. Cualquier otro hilo que intente adquirir el mismo cerrojo seguirá intentándolo (de forma cíclica) hasta que pueda adquirirlo. Esencialmente, un cerrojo cíclico se construye usando una posición en memoria que contiene un valor entero que cada hilo comprueba antes de entrar en su
sección crítica. Si el valor es 0, el hilo le asigna un valor igual a 1 y entra en su sección crítica. Si
el valor no es igual a cero, el hilo comprueba continuamente el valor hasta que sea cero. El cerrojo cíclico es fácil de implementar pero tiene la desventaja de que los hilos que se han quedado
fuera de la sección crítica continúan ejecutando en un modo con espera activa. Por tanto, los cerrojos cíclicos son más efectivos en situaciones donde el tiempo en espera hasta adquirir el cerrojo se prevé que va a ser muy breve, de un orden de magnitud inferior al correspondiente a dos
cambios de contexto.
La forma de uso básica de un cerrojo cíclico es la siguiente:
spin_lock(&cerrojo)
/* sección crítica */
spin_unlock(&cerrojo)
CERROJOS CÍCLICOS BÁSICOS
El cerrojo cíclico básico (en contraposición al cerrojo cíclico de escritura y lectura explicado posteriormente) se presenta en cuatro modalidades (Tabla 6.4):
• Sencillo. Si el código de la sección crítica no se ejecuta desde manejadores de interrupciones o
si las interrupciones están inhabilitadas durante la ejecución de la sección crítica, se puede utilizar un cerrojo cíclico simple. No afecta al estado de las interrupciones en el procesador en el
que se ejecuta.
06-Capitulo 6
16/5/05
17:04
Página 287
Concurrencia. Interbloqueo e inanición
287
• _irq. Si las interrupciones están siempre inhabilitadas, se debería utilizar este tipo de cerrojo
cíclico.
• _irqsave. Si no se conoce si las interrupciones estarán habilitadas o inhabilitadas en el momento de la ejecución, se debería utilizar esta versión. Cuando se adquiere un cerrojo, se salva
el estado actual de las interrupciones del procesador local, que se restaurará cuando se libere el
cerrojo.
• _bh. Cuando ocurre una interrupción, el manejador de interrupciones correspondiente realiza
la cantidad mínima de trabajo necesaria. Un fragmento de código, llamado mitad inferior (bottom half), realiza el resto del trabajo asociado a la interrupción, permitiendo que la interrupción actual se habilite lo antes posible. El cerrojo cíclico _bh se usa para inhabilitar, y después
habilitar, la ejecución de rutinas de tipo mitad inferior para evitar conflictos con la sección crítica protegida.
Tabla 6.4.
Cerrojos cíclicos en Linux.
void spin_lock(spinlock_t *cerrojo)
Adquiere el cerrojo especificado, comprobando
cíclicamente el valor hasta que esté disponible
void spin_lock_irq(spinlock_t *cerrojo)
Igual que spin_lock, pero prohibiendo las
interrupciones en el procesador local
void spin_lock_irqsave(spinlock_t *cerrojo,
unsigned long indicadores)
Igual que spin_lock_irq, pero guardando el estado
actual de las interrupciones en indicadores
void spin_lock_bh(spinlock_t *cerrojo)
Igual que spin_lock, pero prohibiendo la ejecución
de mitades inferiores
void spin_unlock(spinlock_t *cerrojo)
Libera el cerrojo especificado
void spin_unlock_irq(spinlock_t *cerrojo)
Libera el cerrojo especificado y habilita las
interrupciones locales
void spin_unlock_irqrestore(spinlock_t
*cerrojo, unsigned long indicadores)
Libera el cerrojo especificado y restaura el estado de
las interrupciones locales
void spin_unlock_bh(spinlock_t *cerrojo)
Libera el cerrojo especificado y habilita la ejecución de
mitades inferiores
void spin_lock_init(spinlock_t *cerrojo)
Inicia el cerrojo especificado
int spin_trylock(spinlock_t *cerrojo)
Intenta adquirir el cerrojo especificado, devolviendo un
valor distinto de cero si lo posee otro proceso y cero en
caso contrario
int spin_is_locked(spinlock_t *cerrojo)
Devuelve un valor distinto de cero si el cerrojo lo posee
otro proceso y cero en caso contrario
El cerrojo cíclico simple se utiliza si el programador sabe que los datos protegidos no van a accederse desde un manejador de interrupción o una rutina de tipo mitad inferior. En caso contrario, se
usa el tipo de cerrojo cíclico apropiado.
Los cerrojos cíclicos se implementan de una manera diferente en un sistema uniprocesador frente
a una máquina multiprocesadora. En un uniprocesador, se tendrán en cuenta las siguientes consideraciones. Si se inhabilita la opción de expulsión en el núcleo, de manera que un hilo en modo núcleo no
puede verse interrumpido, se eliminan los cerrojos cíclicos en tiempo de compilación, ya que no son
necesarios. Si se activa la expulsión en el núcleo, lo que permite las interrupciones, también se eliminan los cerrojos cíclicos (es decir, no se realiza ninguna comprobación de una posición de memoria
06-Capitulo 6
288
16/5/05
17:04
Página 288
Sistemas operativos. Aspectos internos y principios de diseño
asociada a un cerrojo cíclico), sino que se implementa simplemente como un código que habilita/deshabilita las interrupciones. En un sistema multiprocesador, el cerrojo cíclico se compila como código
que realiza realmente la comprobación de la posición de memoria asociada al cerrojo cíclico. La utilización del mecanismo del cerrojo cíclico en el programa permite que sea independiente de si se ejecuta en un sistema uniprocesador o multiprocesador.
CERROJO CÍCLICO DE LECTURA-ESCRITURA
El cerrojo cíclico de lectura-escritura es un mecanismo que permite un mayor grado de concurrencia
dentro del núcleo que el cerrojo cíclico básico. El cerrojo cíclico de lectura-escritura permite que
múltiples hilos tengan acceso simultáneo a la misma estructura de datos si sólo pretenden un acceso
de lectura, pero da acceso exclusivo al cerrojo cíclico si un hilo desea actualizar la estructura de datos. Cada cerrojo cíclico de lectura-escritura consta de un contador de lectores de 24 bits y un indicador de desbloqueo, que tienen la siguiente interpretación:
Contador
Indicador
Interpretación
0
1
El cerrojo cíclico está disponible para su uso
0
0
El cerrojo cíclico se ha adquirido para la escritura de 1 hilo
n (n > 0)
0
El cerrojo cíclico se ha adquirido para la lectura de n hilos
n (n > 0)
1
Inválido
Como ocurre con el cerrojo cíclico básico, el cerrojo cíclico de lectura-escritura tiene versiones
simples, de tipo _irq y de tipo _irqsave.
Nótese que el cerrojo cíclico de lectura-escritura favorece a los lectores frente a los escritores. Si
los lectores mantienen el cerrojo cíclico, mientras que haya al menos un lector, un escritor no puede
obtener el cerrojo cíclico. Además, se pueden incorporar nuevos lectores al cerrojo cíclico incluso
aunque haya un escritor esperando.
SEMÁFOROS
En el nivel de usuario, Linux proporciona una interfaz de semáforos que corresponde a la de UNIX
SVR4. Internamente, proporciona una implementación de semáforos para su propio uso. Es decir, el
código que forma parte del núcleo puede invocar a los semáforos del núcleo, pero estos no pueden
accederse directamente desde un programa de usuario mediante llamadas al sistema. Se implementan como funciones dentro del núcleo y son así más eficientes que los semáforos visibles para el
usuario.
Linux proporciona tres tipos de mecanismos de semáforos en el núcleo: semáforos binarios, semáforos con contador y semáforos de lectura-escritura.
SEMÁFOROS BINARIOS Y CON CONTADOR
Los semáforos binarios y con contador definidos en Linux 2.6 (Tabla 6.5) tienen la misma funcionalidad que la descrita para ambos tipos de semáforos en el Capítulo 5. Se utilizan los nombres de fun-
06-Capitulo 6
16/5/05
17:04
Página 289
Concurrencia. Interbloqueo e inanición
289
ción down y up para las funciones a las que se denominó semWait y semSignal en el Capítulo 5,
respectivamente.
Un semáforo con contador se inicia utilizando la función sema_init, que da al semáforo un
nombre y le asigna un valor inicial. Los semáforos binarios, llamados MUTEX en Linux, se inician
mediante las funciones init_MUTEX e init_MUTEX_LOCKED, que inician el semáforo a 1 o 0, respectivamente.
Linux proporciona tres versiones de la operación down (semWait).
1. La función down corresponde a la operación tradicional semWait. Es decir, el hilo comprueba el valor del semáforo y se bloquea si no está disponible. El hilo se despertará cuando se
produzca la correspondiente operación up en este semáforo. Obsérvese que se usa el mismo
nombre de función tanto para semáforos con contador como para binarios.
2. La función down_interruptible permite que el hilo reciba y responda a una señal del núcleo mientras está bloqueado en la operación down. Si una señal despierta al hilo, la función
down_interruptible incrementa el valor del contador del semáforo y devuelve un código
de error conocido en Linux como -EINTR. Este valor le indica al hilo que la función del semáforo se ha abortado. En efecto, el hilo se ha visto forzado a «abandonar» el semáforo. Esta
característica es útil para manejadores de dispositivos y otros servicios en los que es conveniente abortar una operación del semáforo.
3. La función down_trylock permite intentar adquirir un semáforo sin ser bloqueado. Si el semáforo está disponible, se adquiere. En caso contrario, esta función devuelve un valor distinto
de cero sin bloquear el hilo.
SEMÁFOROS DE LECTURA-ESCRITURA
El semáforo de lectura-escritura clasifica a los usuarios en lectores y escritores, permitiendo múltiples
lectores concurrentes (sin escritores) pero sólo un único escritor (sin lectores). En realidad, el semáforo funciona como un semáforo con contador para los lectores pero como un semáforo binario (MUTEX) para los escritores. La Tabla 6.5 muestra las operaciones básicas de los semáforos de lecturaescritura. Los semáforos de lectura-escritura utilizan un bloqueo ininterrumpible, por lo que sólo hay
una versión de cada una de las operaciones down.
BARRERAS
En algunas arquitecturas, los compiladores o el hardware del procesador pueden cambiar el orden de
los accesos a memoria del código fuente para optimizar el rendimiento. Estos cambios de orden se
llevan a cabo para optimizar el uso del pipeline de instrucciones del procesador. Los algoritmos que
los realizan contienen comprobaciones que aseguran que no se violan las dependencias de datos. Por
ejemplo, en el siguiente código:
a = 1;
b = 1;
se puede modificar el orden de manera que la posición de memoria b se actualice antes de que lo
haga la posición de memoria a. Sin embargo, en el siguiente código:
a = 1;
b = a;
06-Capitulo 6
290
16/5/05
17:04
Página 290
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 6.5.
Semáforos en Linux.
Semáforos tradicionales
void sema_init(struct semaphore *sem, int cont)
Inicia el semáforo creado dinámicamente
con el valor cont
void init_MUTEX(struct semaphore *sem)
Inicia el semáforo creado dinámicamente
con el valor 1 (inicialmente abierto)
void init_MUTEX_LOCKED(struct semaphore *sem) Inicia el semáforo creado dinámicamente
con el valor 0 (inicialmente cerrado)
void down(struct semaphore *sem)
Intenta adquirir el semáforo
especificado, entrando en un bloqueo
ininterrumpible si el semáforo no está
disponible
int down_interruptible(struct semaphore *sem)
Intenta adquirir el semáforo
especificado, entrando en un bloqueo
interrumpible si el semáforo no está
disponible; devuelve el valor –EINTR si
se recibe una señal
int down_trylock(struct semaphore *sem)
Intenta adquirir el semáforo
especificado, y devuelve un valor
distinto de cero si el semáforo no está
disponible
void up(struct semaphore *sem)
Libera el semáforo especificado
Semáforos de lectura-escritura
void init_rwsem(struct rw_semaphore, *sem_le)
Inicia el semáforo creado dinámicamente
con el valor 1
void down_read(struct rw_semaphore, *sem_le)
Operación down de los lectores
void up_read(struct rw_semaphore, *sem_le)
Operación up de los lectores
void down_write(struct rw_semaphore, *sem_le)
Operación down de los escritores
void up_write(struct rw_semaphore, *sem_le)
Operación up de los escritores
no se puede cambiar el orden de los accesos a memoria. Sin embargo, hay ocasiones en las que es importante que las lecturas y las escrituras se ejecuten en el orden especificado debido al uso que se
hace de la información desde otro hilo o desde un dispositivo hardware.
Para fijar el orden en el que se ejecuta cada instrucción, Linux proporciona el mecanismo
de la barrera de memoria. La Tabla 6.6 enumera las funciones más importantes que se definen
para este mecanismo. La operación rmb() asegura que no se produce ninguna lectura después
de la barrera definida por el lugar que ocupa rmb() en el código. Igualmente, la operación
wmb() asegura que no se produce ninguna escritura después de la barrera definida por el lugar
que ocupa wmb() en el código. La operación mb() proporciona una barrera tanto de lectura
como de escritura.
06-Capitulo 6
16/5/05
17:04
Página 291
Concurrencia. Interbloqueo e inanición
Tabla 6.6.
291
Operaciones de barrera de memoria en Linux.
rmb()
Impide que se cambie el orden de las lecturas evitando que se realicen después de la
barrera
wmb()
Impide que se cambie el orden de las escrituras evitando que se realicen después de
la barrera
mb()
Impide que se cambie el orden de las lecturas y escrituras evitando que se realicen
después de la barrera
barrier()
Impide al compilador cambiar el orden de las lecturas y escrituras evitando que se
realicen después de la barrera
smp_rmb()
En SMP proporciona un rmb() y en UP proporciona un barrier()
smp_wmb()
En SMP proporciona un wmb() y en UP proporciona un barrier()
smp_mb()
En SMP proporciona un mb() y en UP proporciona un barrier()
SMP = multiprocesador simétrico
UP = uniprocesador
Hay dos aspectos importantes sobre las operaciones de barrera que conviene resaltar:
1. Las barreras están relacionadas con instrucciones de máquina, concretamente, con las instrucciones de carga y almacenamiento. Por tanto, la instrucción de lenguaje de alto nivel
a = b implica tanto una carga (lectura) de la posición b y un almacenamiento (escritura) en
la posición a.
2. Las operaciones rmb, wmb y mb dictan el comportamiento tanto del compilador como del procesador. En el caso del compilador, la operación de barrera dicta que el compilador no cambie
el orden de las instrucciones durante el proceso de compilación. En el caso del procesador, la
operación de barrera dicta que cualquier instrucción pendiente de ejecución que esté incluida
en el pipeline antes de la barrera debe completarse antes de ejecutar cualquier instrucción que
se encuentre después de la barrera.
La operación barrier() es una operación más ligera que la operación mb(), puesto que sólo
controla el comportamiento del compilador. Esto sería útil si se conoce que el procesador no realizará cambios de orden indeseables. Por ejemplo, los procesadores x86 no modifican el orden de las
escrituras.
Las operaciones smp_rmb, smp_wmb y smp_mb proporcionan una optimización para el código
que puede compilarse en un uniprocesador (UP) o en un multiprocesador simétrico (SMP). Estas instrucciones se definen como barreras de memoria usuales en el caso de un SMP, pero si se trata de un
UP, se interpretan como barreras sólo para el compilador. Las operaciones smp_ son útiles en situaciones en las que las dependencias de datos de interés sólo surgirán en un contexto SMP.
6.9. FUNCIONES DE SINCRONIZACIÓN DE HILOS DE SOLARIS
Además de los mecanismos de concurrencia de UNIX SVR4, Solaris proporciona cuatro funciones de
sincronización de hilos:
• Cerrojos de exclusión mutua (mutex)
• Semáforos
06-Capitulo 6
292
16/5/05
17:04
Página 292
Sistemas operativos. Aspectos internos y principios de diseño
• Cerrojos de múltiples lectores y un único escritor (lectores/escritor)
• Variables de condición
Solaris implementa estas funciones dentro del núcleo para los hilos del núcleo, pero también se
proporcionan en la biblioteca de hilos para los hilos de nivel de usuario. La Figura 6.15 muestra las
estructuras de datos asociadas a estas funciones. Las funciones de iniciación de estos mecanismos rellenan algunos de los campos de datos. Una vez que se crea un objeto sincronización, básicamente,
sólo se pueden realizar dos operaciones: entrar (adquirir un cerrojo) y liberar (desbloquearlo). No hay
mecanismos en el núcleo o en la biblioteca de hilos que aseguren la exclusión mutua o impidan el interbloqueo. Si un hilo intenta acceder a un fragmento de datos o código que debería estar protegido
pero no utiliza la función de sincronización apropiada, a pesar de ello, ese acceso se produce. Si un
hilo establece un cerrojo sobre un objeto y después no lo desbloquea, el núcleo no toma ninguna acción correctiva.
Todas las primitivas de sincronización requieren la existencia de una instrucción hardware que
permita que un objeto sea consultado y modificado en una operación atómica, como se vio en la
Sección 5.3.
CERROJO DE EXCLUSIÓN MUTUA
Un mutex se utiliza para asegurarse que sólo un hilo en cada momento puede acceder al recurso protegido por el mutex. El hilo que obtiene el mutex debe ser el mismo que lo libera. Un hilo intenta ad-
Tipo (1 octeto)
cerrojo de escritura (1 octeto)
propietario (3 octetos)
en espera (2 octetos)
cerrojo (1 octeto)
en espera (2 octetos)
unión (4 octetos)
información específica del tipo
(4 octetos)
(posiblemente un ident. de turnstile
tipo de relleno del cerrojo,
o puntero a estadística)
(puntero a estadísticas o número
de peticiones de escritura
hilo propietario (4 octetos)
(a) Cerrojo MUTEX
(c) Cerrojo de lectura/escritura
Tipo (1 octeto)
cerrojo de escritura (1 octeto)
en espera (2 octetos)
en espera (2 octetos)
(d) Variable de condición
contador (4 octetos)
(b) Semáforo
Figura 6.15.
Estructura de datos de sincronización de Solaris.
06-Capitulo 6
16/5/05
17:04
Página 293
Concurrencia. Interbloqueo e inanición
293
quirir un cerrojo mutex ejecutando la función mutex_enter. Si mutex_enter no puede establecer
el cerrojo (porque ya lo ha hecho otro hilo), la acción bloqueante depende de la información específica de tipo almacenada en el objeto mutex. La política bloqueante por defecto es la de un cerrojo cíclico: un hilo bloqueado comprueba el estado del cerrojo mientras ejecuta un bucle de espera activa. De
forma opcional, hay un mecanismo de espera bloqueante basado en interrupciones. En este último
caso, el mutex incluye un identificador de turnstile que identifica una cola de los hilos bloqueados en
este cerrojo.
Las operaciones en un cerrojo mutex son las siguientes:
mutex_enter()
Adquiere el cerrojo, bloqueando potencialmente si ya lo posee otro hilo
mutex_exit()
Libera el cerrojo, desbloqueando potencialmente a un hilo en espera
mutex_tryenter() Adquiere un cerrojo si no lo posee otro hilo
La primitiva mutex_tryenter() proporciona una manera no bloqueante de realizar una función de exclusión mutua. Esto permite al programador utilizar una estrategia de espera activa para hilos del nivel de usuario evitando bloquear todo el proceso cuando un hilo se bloquea.
SEMÁFOROS
Solaris proporciona los clásicos semáforos con contador, ofreciendo las siguientes funciones:
sema_p()
Decrementa el semáforo, bloqueando potencialmente el hilo
sema_v()
Incrementa el semáforo, desbloqueando potencialmente un hilo en espera
sema_tryp()
Decrementa el semáforo si no se requiere un bloqueo
Una vez más, la función sema_tryp()permite una espera activa.
CERROJO DE LECTURA/ESCRITURA
El cerrojo de lectura/escritura permite que múltiples hilos tengan acceso simultáneo de lectura a objetos protegidos por un cerrojo. También permite que en cada momento un único hilo acceda al objeto
para modificarlo mientras se excluye el acceso de todos los lectores. Cuando se adquiere el cerrojo
para realizar una escritura, éste toma el estado de cerrojo de escritura: todos los hilos que intentan acceder para leer o escribir deben esperar. Si uno o más lectores han adquirido el cerrojo, su
estado es de cerrojo de lectura. Las funciones son las siguientes:
rw_enter()
Intenta adquirir un cerrojo como lector o escritor
rw_exit()
Libera un cerrojo como lector o escritor
rw_tryenter()
Decrementa el semáforo si no se requiere un bloqueo
rw_downgrade()
Un hilo que ha adquirido un cerrojo de escritura lo convierte en uno de
lectura. Cualquier escritor en espera permanece esperando hasta que este
hilo libere el cerrojo. Si no hay escritores en espera, la función despierta
a los lectores pendientes, si los hay
rw_tryupgrade()
Intenta convertir un cerrojo de lectura en uno de escritura
06-Capitulo 6
294
16/5/05
17:04
Página 294
Sistemas operativos. Aspectos internos y principios de diseño
VARIABLES DE CONDICIÓN
Una variable de condición se utiliza para esperar hasta que se cumpla una determinada condición.
Este mecanismo debe utilizarse en conjunción con un cerrojo mutex. Con ello, se implementa un monitor del tipo mostrado en la Figura 6.14. Las primitivas son las siguientes:
cv_wait()
Bloquea hasta que se activa la condición
cv_signal()
Despierta uno de los hilos bloqueados en cv_wait()
cv_broadcast()
Despierta todos los hilos bloqueados en cv_wait()
La función cv_wait() libera el mutex asociado antes de bloquearse y lo vuelve a adquirir antes de retornar. Dado que al intentar volverlo a adquirir puede bloquearse por otros hilos que compiten por el mutex, la condición que causó la espera debe volverse a comprobar. El uso típico es el
siguiente:
mutex_enter(&m);
• •
while (condicion) {
cv_wait(&vc, &m);
}
• •
mutex_exit(&m);
Esto permite que la condición sea una expresión compleja, puesto que está protegida por el
mutex.
6.10. MECANISMOS DE CONCURRENCIA DE WINDOWS
Windows XP y 2003 proporcionan sincronización entre hilos como parte de su arquitectura de
objetos. Los dos métodos de sincronización más importantes son los objetos de sincronización y
los de sección crítica. Los objetos de sincronización utilizan funciones de espera. Por tanto, en
primer lugar, se describirán las funciones de espera y, a continuación, se examinarán los dos tipos de objetos.
FUNCIONES DE ESPERA
Las funciones de espera permiten que un hilo bloquee su propia ejecución. Las funciones de espera
no retornan hasta que se cumplen los criterios especificados. El tipo de función de espera determina
el conjunto de criterios utilizado. Cuando se llama a una función de espera, ésta comprueba si se satisface el criterio de espera. En caso negativo, el hilo que realizó la llamada transita al estado de espera, no usando tiempo de procesador mientras no se cumplan los criterios de la misma.
El tipo más sencillo de función de espera es aquél que espera por un solo objeto. La función
WaitForSingleObject requiere un manejador que corresponda con un objeto de sincronización.
La función retorna cuando se produce una de las siguientes circunstancias:
06-Capitulo 6
16/5/05
17:04
Página 295
Concurrencia. Interbloqueo e inanición
295
• El objeto especificado está en el estado de señalado.
• Ha transcurrido el plazo máximo de espera. Dicho plazo máximo puede fijarse en INFINITE
para especificar que la espera será ilimitada.
OBJETOS DE SINCRONIZACIÓN
El mecanismo utilizado por el ejecutivo de Windows para implementar las funciones de sincronización se basa en la familia de objetos de sincronización, que se muestran con breves descripciones en
la Tabla 6.7.
Tabla 6.7.
Tipo de objeto
Objetos de sincronización de Windows.
Definición
Pasa al estado
de señalado cuando
Efecto sobre los hilos
en espera
Evento
Un aviso de que
ha ocurrido un evento
del sistema
Un hilo genera un
evento
Desbloquea a todos
Mutex
Un mecanismo que
proporciona exclusión
mutua; equivalente a
un semáforo binario
El hilo propietario u
otro hilo libera el mutex
Se desbloquea un hilo
Semáforo
Un contador que regula
el número de hilos
que pueden usar
un recurso
El contador del
semáforo llega a cero
Desbloquea a todos
Temporizador con
espera
Un contador que
registra el paso
del tiempo
Se cumple el tiempo
especificado o expira
el intervalo de tiempo
Desbloquea a todos
Notificación de cambio
en fichero
Una notificación de
cambios en el sistema
de ficheros
Ocurre un cambio en el
sistema de ficheros que
encaja con los criterios
de filtro de ese objeto
Se desbloquea un hilo
Entrada de consola
Una ventana de texto en
la pantalla (por ejemplo,
usada para manejar
E/S de pantalla en una
aplicación MS-DOS)
Hay entrada disponible
para procesar
Se desbloquea un hilo
Trabajo
Una instancia de un
fichero abierto o un
dispositivo de E/S
Se completa una
operación de E/S
Desbloquea a todos
Notificación sobre el
recurso de memoria
Una notificación de un
cambio en el recurso
de memoria
Se produce el tipo de
cambio especificado en
la memoria física
Desbloquea a todos
Proceso
Una invocación de un
programa, incluyendo el
espacio de direcciones y
los recursos requeridos
para ejecutar el programa
El último hilo termina
Desbloquea a todos
Hilo
Una entidad ejecutable
dentro de un proceso
El hilo termina
Desbloquea a todos
Nota: las filas que no están coloreadas corresponden a objetos que sólo existen para sincronización.
06-Capitulo 6
296
16/5/05
17:04
Página 296
Sistemas operativos. Aspectos internos y principios de diseño
Los primeros cuatro tipos de objetos de la tabla están diseñados específicamente para dar soporte
a la sincronización. Los tipos de objetos restantes tienen otros usos adicionales pero también pueden
utilizarse para la sincronización.
Cada instancia de un objeto de sincronización puede estar en el estado de señalado o de no señalado. Un hilo se puede bloquear en un objeto si está en el estado de no señalado, desbloqueándose
cuando el objeto transite al estado de señalado. El mecanismo es sencillo: un hilo realiza una petición
de espera al ejecutivo de Windows, utilizando el manejador del objeto de sincronización. Cuando el
objeto transita al estado de señalado, el ejecutivo de Windows desbloquea todos los objetos de tipo
hilo que están esperando en ese objeto de sincronización.
El objeto evento es útil para enviar una señal a un hilo para indicarle que ha ocurrido un determinado evento. Por ejemplo, en la entrada o salida asíncrona, el sistema establece un objeto evento específico de manera que dicho objeto transitará al estado de señalado cuando se haya completado la
operación asíncrona. El objeto mutex se usa para garantizar el acceso mutuamente exclusivo a un recurso, permitiendo que, en cada momento, sólo un hilo pueda conseguir el acceso al mismo. Este tipo
de objeto funciona, por tanto, como un semáforo binario. Cuando el objeto mutex pasa al estado de
señalado, sólo se desbloquea uno de los hilos que estaba esperando por el mutex. Los mutex se pueden utilizar para sincronizar hilos que se ejecutan en procesos diferentes. Como los mutex, los objetos semáforo pueden compartir los hilos pertenecientes a distintos procesos. El semáforo de Windows es un semáforo con contador. Básicamente, el objeto temporizador con espera avisa cuando
ha transcurrido un cierto tiempo o en intervalos regulares.
OBJETOS DE SECCIÓN CRÍTICA
Los objetos de sección crítica proporcionan un mecanismo de sincronización similar al proporcionado por los objetos mutex, excepto que los objetos de sección crítica sólo los pueden utilizar hilos del
mismo proceso. Los objetos mutex, eventos y semáforos se pueden utilizar también en una aplicación
que tenga un único proceso, pero los objetos de sección crítica proporcionan un mecanismo de sincronización para exclusión mutua ligeramente más rápido y más eficiente.
El proceso es el responsable de asignar la memoria utilizada por una sección crítica. Normalmente, esto se hace simplemente declarando una variable de tipo CRITICAL_SECTION. Antes de que los
hilos del proceso puedan utilizarla, la sección crítica se inicia utilizando las funciones InitializeCriticalSection o InitializeCriticalSectionAndSpinCount.
Un hilo usa las funciones EnterCriticalSection o TryEnterCriticalSection para solicitar la posesión de una sección crítica, utilizando la función LeaveCriticalSection para liberar
la posesión de la misma. Si el objeto de sección crítica lo posee actualmente otro hilo, EnterCriticalSection espera indefinidamente hasta poder obtener su posesión. En contraste, cuando se utiliza
un objeto mutex para lograr exclusión mutua, las funciones de espera aceptan que se especifique un
plazo de tiempo de espera máximo. La función TryEnterCriticalSection intenta entrar en una
sección crítica sin bloquear el hilo que realizó la llamada.
6.11. RESUMEN
El interbloqueo es el bloqueo de un conjunto de procesos que o bien compiten por recursos del sistema o se comunican entre sí. El bloqueo es permanente a menos que el sistema operativo tome acciones correctivas, como abortar uno o más procesos, o forzar que la ejecución de uno o más procesos
retroceda. El interbloqueo puede involucrar a recursos reutilizables o consumibles. Un recurso reutilizable es aquél que no se destruye cuando se usa, como un canal de E/S o una región de memoria. Un
06-Capitulo 6
16/5/05
17:04
Página 297
Concurrencia. Interbloqueo e inanición
297
recurso consumible es aquél que se destruye cuando lo adquiere un proceso; algunos ejemplos son los
mensajes y la información almacenada en buffers de E/S.
Hay tres estrategias generales para tratar con los interbloqueos: prevención, detección y predicción. La prevención del interbloqueo garantiza que no se produce el interbloqueo asegurándose de que
no se cumple una de las condiciones necesarias para el interbloqueo. La detección del interbloqueo es
necesaria si el sistema operativo está siempre dispuesto a conceder peticiones de recursos; periódicamente, el sistema operativo debe comprobar si hay interbloqueo y tomar las acciones pertinentes para
romperlo. La predicción del interbloqueo implica el análisis de cada nueva petición de un recurso para
determinar si podría conducir a un interbloqueo, concediéndola sólo si no es posible el interbloqueo.
6.12. LECTURAS RECOMENDADAS
El artículo clásico sobre interbloqueos, [HOLT72], sigue siendo una lectura valiosa, como lo es también [COFF71]. Otro estudio interesante es [ISLO80]. [CORB96] es un tratado general sobre la detección de interbloqueos. [DIMI98] es un ameno estudio general de los interbloqueos. Dos artículos
recientes de Levine [LEVI03a, LEVI03b] clarifican algunos conceptos utilizados en el estudio del interbloqueo. [SHUB03] es un estudio útil del interbloqueo.
Los mecanismos de concurrencia de UNIX SVR4, Linux y Solaris 2 están detalladamente estudiados en [GRAY97], [LOVE04] y [MAUR01], respectivamente.
COFF71 Coffman, E.; Elphick, M.; y Shoshani, A. «System Deadlocks.» Computing Surveys, Junio 1971.
CORB96 Corbett, J. «Evaluating Deadlock Detection Methods for Concurrent Software». IEEE Transactions on Software Engineering, Marzo 1996.
DIMI98 Dimitoglou, G. «Deadlocks and Methods for Their Detection, Prevention, and Recovery in Modern Operating Systems.» Operating Systems Review, Julio 1998.
GRAY97 Gray, J. Interprocess Communications in UNIX: The Nooks and Crannies. Upper Saddle River, NJ: Prentice Hall, 1997.
HOLT72 Holt, R. «Some Deadlock Properties of Computer Systems.» Computing Surveys, Septiembre
1972.
ISLO80 Isloor, S., y Marsland, T. «The Deadlock Problem: An Overview.» Computer, Septiembre 1980.
LEVI03a Levine, G. «Defining Deadlock. (Operating Systems Review), Enero 2003.
LEVI03b Levine, G. «Defining Deadlock with Fungible Resources.» Operating Systems Review, Julio 2003.
LOVE04 Love, R. Linux Kernel Development. Indianapolis, IN: Sams Publishing, 2004.
MAUR01 Mauro, J., McDougall, R. Solaris Internals. Upper Saddle River, NJ: Prentice Hall PTR, 2001.
SHUB03 Shub, C. «A Unified Treatment of Deadlock.» Journal of Computing in Small Colleges, Octubre 2003. Disponible en la biblioteca digital de ACM.
6.13. TÉRMINOS CLAVE, CUESTIONES DE REPASO Y PROBLEMAS
TÉRMINOS CLAVE
algoritmo del banquero
barrera de memoria
cerrojo cíclico
detección del interbloqueo
diagrama de progreso conjunto
espera circular
exclusión mutua
expropiación
grafo de asignación de recursos
inanición
interbloqueo
mensaje
predicción del interbloqueo
prevención del interbloqueo
recurso consumible
recurso reutilizable
retención y espera
tubería
06-Capitulo 6
298
16/5/05
17:04
Página 298
Sistemas operativos. Aspectos internos y principios de diseño
CUESTIONES DE REPASO
6.1.
Cite ejemplos de recursos reutilizables y consumibles.
6.2.
¿Cuáles son las tres condiciones que deben cumplirse para que sea posible un interbloqueo?
6.3.
¿Cuáles son las cuatro condiciones que producen un interbloqueo?
6.4.
¿Cómo se puede prever la condición de retención y espera?
6.5.
Enumere dos maneras cómo se puede prever la condición de sin expropiación.
6.6.
¿Cómo se puede prever la condición de espera circular?
6.7.
¿Cuál es la diferencia entre predicción, detección y prevención del interbloqueo?
PROBLEMAS
6.1.
Muestre las cuatro condiciones del interbloqueo aplicadas a la Figura 6.1a.
6.2.
Para la Figura 6.3, proporcione una descripción narrativa de cada una de las seis trayectorias representadas, similar a la descripción de la Figura 6.2 realizada en la Sección 6.1.
6.3.
Se afirmó que no puede ocurrir un interbloqueo en la situación reflejada en la Figura 6.3.
Justifique esa afirmación.
6.4.
Considere la siguiente instantánea del sistema. Suponga que no hay peticiones de recursos
pendientes de satisfacerse.
disponibles
r1
r2
r3
r4
2
1
0
0
asignación actual
necesidades máximas
proceso
r1
r2
r3
r4
r1
r2
r3
r4
p1
0
0
1
2
0
0
1
2
p2
2
0
0
0
2
7
5
0
p3
0
0
3
4
6
6
5
6
p4
2
3
5
4
4
3
5
6
p5
0
3
3
2
0
6
5
2
necesidades pendientes
r1
r2
r3
r4
a) Calcule cuánto más podría pedir todavía cada proceso y escríbalo en las columnas etiquetadas como «necesidades pendientes».
b) ¿Está el sistema actualmente en un estado seguro o inseguro? ¿Por qué?
c) ¿Está este sistema actualmente en un interbloqueo? ¿Por qué o por qué no?
d) ¿Qué procesos, en caso de que haya alguno, están o pueden llegar a estar en interbloqueo?
06-Capitulo 6
16/5/05
17:04
Página 299
Concurrencia. Interbloqueo e inanición
299
e) Si llega una solicitud de p3 de (0, 1, 0, 0), ¿puede concederse esa solicitud inmediatamente de forma segura? ¿En qué estado (interbloqueo, seguro o inseguro) quedaría el
sistema justo después de la concesión de esa petición completa? ¿Qué procesos, en
caso de que haya alguno, estarían en interbloqueo si se concede inmediatamente esa
petición completa?
6.5.
Aplique el algoritmo de detección del interbloqueo de la Sección 6.4 a los siguientes datos
y muestre el resultado.
Disponibles = (2 1 0 0)
2 0 0 1
0 0 1 0
Solicitud = 1 0 1 0
Asignación =
2 1 0 0
6.6.
2 0 0 1
0 1 2 0
Un sistema de spooling (Figura 6.16) consta de un proceso de entrada E, un proceso usuario P, y un proceso de salida S, conectados por dos buffers residentes en un disco. Los procesos intercambian datos en bloques de igual tamaño. Estos bloques se almacenan en el
disco utilizando un límite flotante entre los buffers de entrada y de salida, dependiendo de
la velocidad de los procesos. Las funciones de comunicación utilizadas aseguran que se
cumple la siguiente restricción con respecto a los recursos:
e + s £ máx
donde
máx = máximo número de bloques en disco
e = número de bloques de entrada en disco
s = número de bloques de salida en disco
Con respecto a los procesos, se conocen los siguientes aspectos:
1. Mientras que el entorno proporcione datos, el proceso E, en un momento dado, los escribirá en el disco (siempre que haya espacio disponible en el mismo).
2. Mientras que haya entrada disponible en el disco, el proceso P, en un momento dado, la
consumirá y escribirá en el disco una cantidad finita de datos de salida por cada bloque
leído (siempre que haya espacio disponible en el mismo).
3. Mientras que haya datos de salida disponibles en el disco, el proceso S, en un momento
dado, los consumirá. Demuestre que en este sistema puede producirse un interbloqueo.
6.7.
Sugiera una restricción adicional con respecto a los recursos que impida el interbloqueo del
Problema 6.6 pero que siga permitiendo que el límite entre los buffers de entrada y de salida pueda variar de acuerdo a las necesidades presentes de los procesos.
E
Almacenamiento
intermedio
de entrada
Figura 6.16.
P
Almacenamiento
intermedio
de salida
Un sistema de spooling.
S
06-Capitulo 6
300
16/5/05
17:04
Página 300
Sistemas operativos. Aspectos internos y principios de diseño
6.8.
En el sistema operativo THE [DIJK68], un tambor (precursor del disco para almacenamiento secundario) se divide en buffers de entrada, áreas de procesamiento y buffers de salida, con límites flotantes, dependiendo de la velocidad de los procesos involucrados. El
estado actual del tambor se puede caracterizar por los siguientes parámetros:
máx
= máximo número de páginas en el tambor
e
= número de páginas de entrada en el tambor
p
= número de páginas de procesamiento en el tambor
s
= número de páginas de salida en el tambor
ress
= número de páginas reservadas para la salida
resp
= número de páginas reservadas para el procesamiento
Formule las restricciones necesarias con respecto a los recursos de manera que se garantice
que no se excede la capacidad del tambor y que se reserva un número mínimo de páginas
permanentemente para la salida y el procesamiento.
6.9.
En el sistema operativo THE, una página puede realizar las siguientes transiciones:
1. vacío Æ buffer de entrada (producción de entrada)
2. buffer de entrada Æ área de procesamiento (consumo de entrada)
3. área de procesamiento Æ buffer de salida (producción de salida)
4. buffer de salida Æ vacío (consumo de salida)
5. vacío Æ área de procesamiento (llamada a procedimiento)
6. área de procesamiento Æ vacío (retorno de procedimiento)
a) Defina el efecto de estas transiciones en términos de las cantidades e, s y p.
b) ¿Puede alguna de ellas llevar a un interbloqueo si se mantienen las suposiciones hechas
en el Problema 6.6 con respecto a los procesos de entrada, a los de usuario y a los de
salida?
6.10. Considere un sistema con un total de 150 unidades de memoria, asignadas a tres procesos
como se muestra a continuación
Proceso
Máx
Asignadas
1
70
45
2
60
40
3
60
15
Aplique el algoritmo del banquero para determinar si sería seguro conceder cada una de las
siguientes peticiones. En caso afirmativo, indique una secuencia de terminaciones que se
puede garantizar como factible. En caso negativo, muestre la reducción de la tabla de asignación resultante.
a) Aparece un cuarto proceso, con una necesidad de memoria máxima de 60 y una necesidad inicial de 25 unidades.
b) Aparece un cuarto proceso, con una necesidad de memoria máxima de 60 y una necesidad inicial de 35 unidades.
06-Capitulo 6
16/5/05
17:04
Página 301
Concurrencia. Interbloqueo e inanición
301
6.11. Evalúe el grado de utilidad del algoritmo del banquero en un sistema operativo.
6.12. Se implementa un algoritmo con una estructura en serie (pipeline) de manera que un flujo
de elementos de datos de tipo T producido por un proceso P0 pasa a través de una secuencia de procesos P1, P2, ..., Pn-1, que operan sobre los elementos en ese orden.
a) Defina un buffer general de mensajes que contenga todos los elementos de datos parcialmente consumidos y escriba un algoritmo para el proceso Pi (0 £ i £ n -1), con la
siguiente estructura:
repetir indefinidamente
recibe desde el predecesor;
consume elemento;
envía a sucesor;
por siempre
Suponga que P0 recibe los elementos enviados por Pn-1. El algoritmo debería permitir
que los procesos trabajen directamente con los mensajes guardados en el buffer de manera que no sea necesario hacer copias.
b) Muestre que los procesos no pueden quedarse en un interbloqueo con respecto al uso
del buffer común.
6.13. a) Tres procesos comparten cuatro unidades de un recurso que pueden reservarse y liberarse sólo una en cada momento. La necesidad máxima de cada proceso es de dos unidades. Demuestre que no puede ocurrir un interbloqueo.
b) N procesos comparten M unidades de un recurso que pueden reservarse y liberarse sólo
una en cada momento. La necesidad máxima de cada proceso no excede de M, y la
suma de todas las necesidades máximas es menor que M + N. Demuestre que no puede
ocurrir un interbloqueo.
6.14. Considere un sistema formado por cuatro procesos y un solo recurso. El estado actual de
las matrices de asignación y necesidad es el siguiente:
3
N=
2
1
A=
1
9
3
7
2
¿Cuál es el número mínimo de unidades del recurso que necesitan estar disponibles para
que este estado sea seguro?
6.15. Considere los siguientes modos de manejo del interbloqueo: (1) algoritmo del banquero,
(2) detección del interbloqueo y aborto del hilo, liberando todos los recursos, (3) reserva de
todos los recursos por anticipado, (4) reinicio del hilo y liberación de todos los recursos si
el hilo necesita esperar, (5) ordenamiento de recursos, y (6) detección de interbloqueo y retroceso en la ejecución del hilo.
a) Un criterio utilizado en la evaluación de diferentes estrategias de tratamiento del interbloqueo es analizar cuál permite mayor concurrencia. En otras palabras, ¿qué estrategia permite ejecutar sin esperas a la mayoría de los hilos cuando no hay interbloqueo?
Asigne un orden de 1 a 6 a cada una de las estrategias de tratamiento del interbloqueo,
tal que 1 permite el mayor grado de concurrencia. Explique su ordenación.
06-Capitulo 6
302
16/5/05
17:04
Página 302
Sistemas operativos. Aspectos internos y principios de diseño
b) Otro criterio es la eficiencia; en otras palabras, ¿qué estrategia sobrecarga menos al
procesador? Ordene las estrategias desde 1 a 6, siendo 1 la más eficiente, asumiendo
que el interbloqueo es un evento que raramente se produce. Explique su ordenación.
¿Cambia su respuesta si el interbloqueo fuera frecuente?
6.16. Comente la siguiente solución al problema de los filósofos comensales. Un filósofo hambriento toma primero el tenedor de su izquierda; si también está disponible el de la derecha, lo tomará y empezará a comer; en caso contrario, devolverá el tenedor de la izquierda
y repetirá el ciclo.
6.17. Supóngase que hay dos tipos de filósofos. Uno de ellos siempre toma primero el tenedor de
la izquierda (un «zurdo»), y el otro tipo siempre toma primero el de su derecha (un «diestro»). El comportamiento del zurdo se define en la Figura 6.12, mientras que el del diestro
es el siguiente:
inicio
repetir indefinidamente
piensa;
wait (tenedor[(i+1) mod 5]);
wait (tenedor[i]);
come;
signal (tenedor[i]);
signal (tenedor[(i+1) mod 5]);
porsiempre
fin
Demuestre lo siguiente:
a) Cualquier combinación de zurdos y diestros con al menos uno de cada evita el interbloqueo.
b) Cualquier combinación de zurdos y diestros con al menos uno de cada impide la inanición.
6.18. La Figura 6.17 muestra otra solución al problema de los filósofos comensales usando monitores. Compare esta solución con la presentada en la Figura 6.14 y escriba sus conclusiones.
6.19. En la Tabla 6.3, algunas de las operaciones atómicas de Linux no involucran dos accesos a
una variable, como atomic_read(atomic_t *v). Una simple operación de lectura es
obviamente atómica en cualquier arquitectura. Por tanto, ¿por qué se añade esta operación
al repertorio de operaciones atómicas?
6.20. Considere el siguiente fragmento de código en un sistema Linux:
read_lock (&cerrojo_le);
write_lock (&cerrojo_le);
Donde cerrojo_le es un cerrojo de lectura_escritura. ¿Cuál es el efecto de este código?
6.21. Las dos variables a y b tienen valores iniciales de 1 y 2 respectivamente. Dado el siguiente
código que se ejecuta en un sistema Linux:
06-Capitulo 6
16/5/05
17:04
Página 303
Concurrencia. Interbloqueo e inanición
303
monitor controlador_ de_comensales;
enum estados {pensando, hambriento, comiendo} estado[5];
cond requiereTenedor [5];
/* variable de condición */
void obtiene_tenedores (int id_pr)
/* id_pr es el número de ident. del filósofo */
{
estado[id_pr] = hambriento;
/* anuncia que tiene hambre */
if ((estado[(id_pr+1) % 5] == comiendo)
|| (estado[(id_pr-1) % 5] == comiendo))
cwait(requiereTenedor [id_pr]);
/* espera si algún vecino come */
estado[id_pr] = comiendo;
/* continúa si ningún vecino come */
}
void libera_tenedores (int id_pr)
{
estado[id_pr] = pensando;
/* le da una oportunidad de comer al siguiente vecino (número mayor) */
if ((estado[(id_pr+1) % 5] == hambriento)
&& (estado[(id_pr+2) % 5] != comiendo))
csignal(requiereTenedor [id_pr+1]);
/* le da una oportunidad de comer al anterior vecino (número menor) */
else if ((estado[(id_pr-1) % 5] == hambriento)
&& (estado[(id_pr-2) % 5] != comiendo))
csignal(requiereTenedor [id_pr-1]);
}
void filosofo[k=0 hasta 4]
{
while (verdadero)
{
<piensa>;
obtiene_tenedores(k);
<come espaguetis>;
libera_tenedores();
}
}
Figura 6.17.
/* los cinco clientes filósofos */
/* cliente solicita dos tenedores vía el monitor */
/* cliente libera tenedores vía el monitor */
Otra solución al problema de los filósofos comensales usando un monitor.
Hilo 1
Hilo 2
a = 3 ;
—
mb () ;
—
b = 4 ;
c = b ;
—
rmb () ;
—
d = a ;
¿Qué errores puede evitar la utilización de las barreras de memoria?
06-Capitulo 6
16/5/05
17:04
Página 304
07-Capitulo 7
16/5/05
17:04
Página 305
PA RT E
III
MEMORIA
U
no de los aspectos más complejos del diseño de los sistemas operativos es la gestión de la memoria. Aunque el coste de la memoria ha caído dramáticamente, y como resultado, el tamaño
de la memoria principal en las máquinas modernas ha crecido, alcanzando rangos en torno a
los gigabytes, nunca hay suficientemente memoria principal para contener todos los programas y estructuras de datos necesarias para los procesos activos y el sistema operativo. Análogamente, una tarea central del sistema operativo es gestionar la memoria, lo que implica traer y llevar bloques de datos de memoria secundaria. Sin embargo, la E/S es una operación lenta, y la velocidad relativa al
tiempo de ciclo de una instrucción del procesador aumenta más y más con cada año que pasa. Para
mantener ocupado al procesador o procesadores y, por tanto, mantener la eficiencia, el sistema operativo debe gestionar de forma inteligente la transferencia entre memoria principal y secundaria para
minimizar el efecto de la E/S en el rendimiento.
ÍNDICE PARA LA PARTE TRES
CAPÍTULO 7. GESTIÓN DE LA MEMORIA
El Capítulo 7 proporciona una descripción básica de los mecanismos fundamentales utilizados en la
gestión de la memoria. Primero, se resumen los requisitos básicos de cualquier esquema de gestión de
la memoria. A continuación se introduce el uso de particionamiento de la memoria. Esta técnica no es
muy utilizada, excepto en casos especiales, tales como la gestión de la memoria del núcleo. Sin embargo, una revisión del particionamiento de la memoria ilumina muchos de los aspectos de diseño relacionados con la gestión de la memoria. El resto del capítulo trata sobre dos técnicas que forman los
elementos básicos de prácticamente todos los sistemas de gestión de la memoria: la paginación y la
segmentación.
CAPÍTULO 8. MEMORIA VIRTUAL
La memoria virtual, basada en el uso de paginación o la combinación de paginación y segmentación,
es una técnica casi universal para la gestión de la memoria en las máquinas contemporáneas. La memoria virtual es un esquema transparente a los procesos, que permite que cada proceso se comporte
07-Capitulo 7
306
16/5/05
17:04
Página 306
Sistemas operativos. Aspectos internos y principios de diseño
como si tuviera memoria ilimitada a su disposición. Para lograr esto, el sistema operativo crea por
cada proceso un espacio de direcciones virtual, o memoria virtual, en disco. Parte de la memoria virtual se trae a memoria principal real cuando se necesita. De esta forma, muchos procesos pueden
compartir una cantidad relativamente pequeña de memoria principal. Para que la memoria virtual trabaje de forma efectiva, se necesita que los mecanismos de hardware lleven a cabo funciones de paginación y segmentación básicas, tales como traducción de direcciones virtuales a reales. El Capítulo 8
comienza con una descripción de estos mecanismos hardware. El resto del capítulo se dedica a aspectos de diseño del sistema operativo relacionados con la memoria virtual.
07-Capitulo 7
16/5/05
17:04
Página 307
CAPÍTULO
7
Gestión de la memoria
7.1.
Requisitos de la gestión de la memoria
7.2.
Particionamiento de la memoria
7.3.
Paginación
7.4.
Segmentación
7.5.
Resumen
7.6.
Lecturas recomendadas
7.7.
Términos clave, cuestiones de revisión y problemas
Apéndice 7A Carga y enlace
07-Capitulo 7
308
16/5/05
17:04
Página 308
Sistemas operativos. Aspectos internos y principios de diseño
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
En un sistema monoprogramado, la memoria se divide en dos partes: una parte para el sistema
operativo (monitor residente, núcleo) y una parte para el programa actualmente en ejecución. En un
sistema multiprogramado, la parte de «usuario» de la memoria se debe subdividir posteriormente
para acomodar múltiples procesos. El sistema operativo es el encargado de la tarea de subdivisión y
a esta tarea se le denomina gestión de la memoria.
Una gestión de la memoria efectiva es vital en un sistema multiprogramado. Si sólo unos pocos
procesos se encuentran en memoria, entonces durante una gran parte del tiempo todos los procesos esperarían por operaciones de E/S y el procesador estaría ocioso. Por tanto, es necesario asignar la memoria para asegurar una cantidad de procesos listos que consuman el tiempo de procesador disponible.
Comenzaremos este capítulo con una descripción de los requisitos que la gestión de la memoria
pretende satisfacer. A continuación, se mostrará la tecnología de la gestión de la memoria, analizando una variedad de esquemas simples que se han utilizado. El capítulo se enfoca en el requisito de
que un programa se debe cargar en memoria principal para ejecutarse. Esta discusión introduce algunos de los principios fundamentales de la gestión de la memoria.
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
7.1. REQUISITOS DE LA GESTIÓN DE LA MEMORIA
M
ientras se analizan varios mecanismos y políticas asociados con la gestión de la memoria,
es útil mantener en mente los requisitos que la gestión de la memoria debe satisfacer.
[LIST93] sugiere cinco requisitos:
• Reubicación.
• Protección.
• Compartición.
• Organización lógica.
• Organización física.
REUBICACIÓN
En un sistema multiprogramado, la memoria principal disponible se comparte generalmente entre varios procesos. Normalmente, no es posible que el programador sepa anticipadamente qué programas
residirán en memoria principal en tiempo de ejecución de su programa. Adicionalmente, sería bueno
poder intercambiar procesos en la memoria principal para maximizar la utilización del procesador,
proporcionando un gran número de procesos para la ejecución. Una vez que un programa se ha llevado al disco, sería bastante limitante tener que colocarlo en la misma región de memoria principal donde se hallaba anteriormente, cuando éste se trae de nuevo a la memoria. Por el contrario, podría ser
necesario reubicar el proceso a un área de memoria diferente.
Por tanto, no se puede conocer de forma anticipada dónde se va a colocar un programa y se debe
permitir que los programas se puedan mover en la memoria principal, debido al intercambio o swap.
Estos hechos ponen de manifiesto algunos aspectos técnicos relacionados con el direccionamiento,
como se muestra en la Figura 7.1. La figura representa la imagen de un proceso. Por razones de simplicidad, se asumirá que la imagen de un proceso ocupa una región contigua de la memoria principal. Claramente, el sistema operativo necesitará conocer la ubicación de la información de control
07-Capitulo 7
16/5/05
17:04
Página 309
Gestión de la memoria
Información
de control
de proceso
Punto de
entrada al
programa
309
Bloque de control de proceso
Programa
Valores de
direcciones
crecientes
Instrucciones
de salto
Referencia
de datos
Datos
Extremo
actual de
la pila
Figura 7.1.
Pila
Requisitos de direccionamiento para un proceso.
del proceso y de la pila de ejecución, así como el punto de entrada que utilizará el proceso para iniciar la ejecución. Debido a que el sistema operativo se encarga de gestionar la memoria y es responsable de traer el proceso a la memoria principal, estas direcciones son fáciles de adquirir. Adicionalmente, sin embargo, el procesador debe tratar con referencias de memoria dentro del propio
programa. Las instrucciones de salto contienen una dirección para referenciar la instrucción que se
va a ejecutar a continuación. Las instrucciones de referencia de los datos contienen la dirección del
byte o palabra de datos referenciados. De alguna forma, el hardware del procesador y el software del
sistema operativo deben poder traducir las referencias de memoria encontradas en el código del programa en direcciones de memoria físicas, que reflejan la ubicación actual del programa en la memoria principal.
PROTECCIÓN
Cada proceso debe protegerse contra interferencias no deseadas por parte de otros procesos, sean accidentales o intencionadas. Por tanto, los programas de otros procesos no deben ser capaces de referenciar sin permiso posiciones de memoria de un proceso, tanto en modo lectura como escritura. Por
un lado, lograr los requisitos de la reubicación incrementa la dificultad de satisfacer los requisitos de
protección. Más aún, la mayoría de los lenguajes de programación permite el cálculo dinámico de direcciones en tiempo de ejecución (por ejemplo, calculando un índice de posición en un vector o un
puntero a una estructura de datos). Por tanto, todas las referencias de memoria generadas por un proceso deben comprobarse en tiempo de ejecución para poder asegurar que se refieren sólo al espacio
de memoria asignado a dicho proceso. Afortunadamente, se verá que los mecanismos que dan soporte
a la reasignación también dan soporte al requisito de protección.
Normalmente, un proceso de usuario no puede acceder a cualquier porción del sistema operativo,
ni al código ni a los datos. De nuevo, un programa de un proceso no puede saltar a una instrucción de
otro proceso. Sin un trato especial, un programa de un proceso no puede acceder al área de datos de
otro proceso. El procesador debe ser capaz de abortar tales instrucciones en el punto de ejecución.
07-Capitulo 7
310
16/5/05
17:04
Página 310
Sistemas operativos. Aspectos internos y principios de diseño
Obsérvese que los requisitos de protección de memoria deben ser satisfechos por el procesador
(hardware) en lugar del sistema operativo (software). Esto es debido a que el sistema operativo no
puede anticipar todas las referencias de memoria que un programa hará. Incluso si tal anticipación
fuera posible, llevaría demasiado tiempo calcularlo para cada programa a fin de comprobar la violación de referencias de la memoria. Por tanto, sólo es posible evaluar la permisibilidad de una referencia (acceso a datos o salto) en tiempo de ejecución de la instrucción que realiza dicha referencia. Para
llevar a cabo esto, el hardware del procesador debe tener esta capacidad.
COMPARTICIÓN
Cualquier mecanismo de protección debe tener la flexibilidad de permitir a varios procesos acceder a
la misma porción de memoria principal. Por ejemplo, si varios programas están ejecutando el mismo
programa, es ventajoso permitir que cada proceso pueda acceder a la misma copia del programa en
lugar de tener su propia copia separada. Procesos que estén cooperando en la misma tarea podrían necesitar compartir el acceso a la misma estructura de datos. Por tanto, el sistema de gestión de la memoria debe permitir el acceso controlado a áreas de memoria compartidas sin comprometer la protección esencial. De nuevo, se verá que los mecanismos utilizados para dar soporte a la reubicación
soportan también capacidades para la compartición.
ORGANIZACIÓN LÓGICA
Casi invariablemente, la memoria principal de un computador se organiza como un espacio de almacenamiento lineal o unidimensional, compuesto por una secuencia de bytes o palabras. A nivel físico,
la memoria secundaria está organizada de forma similar. Mientras que esta organización es similar al
hardware real de la máquina, no se corresponde a la forma en la cual los programas se construyen
normalmente. La mayoría de los programas se organizan en módulos, algunos de los cuales no se
pueden modificar (sólo lectura, sólo ejecución) y algunos de los cuales contienen datos que se pueden
modificar. Si el sistema operativo y el hardware del computador pueden tratar de forma efectiva los
programas de usuarios y los datos en la forma de módulos de algún tipo, entonces se pueden lograr
varias ventajas:
1. Los módulos se pueden escribir y compilar independientemente, con todas las referencias de
un módulo desde otro resueltas por el sistema en tiempo de ejecución.
2. Con una sobrecarga adicional modesta, se puede proporcionar diferentes grados de protección
a los módulos (sólo lectura, sólo ejecución).
3. Es posible introducir mecanismos por los cuales los módulos se pueden compartir entre los
procesos. La ventaja de proporcionar compartición a nivel de módulo es que se corresponde
con la forma en la que el usuario ve el problema, y por tanto es fácil para éste especificar la
compartición deseada.
La herramienta que más adecuadamente satisface estos requisitos es la segmentación, que es una
de las técnicas de gestión de la memoria exploradas en este capítulo.
ORGANIZACIÓN FÍSICA
Como se discute en la Sección 1.5, la memoria del computador se organiza en al menos dos niveles,
conocidos como memoria principal y memoria secundaria. La memoria principal proporciona acceso
07-Capitulo 7
16/5/05
17:04
Página 311
Gestión de la memoria
311
rápido a un coste relativamente alto. Adicionalmente, la memoria principal es volátil; es decir, no
proporciona almacenamiento permanente. La memoria secundaria es más lenta y más barata que la
memoria principal y normalmente no es volátil. Por tanto, la memoria secundaria de larga capacidad
puede proporcionar almacenamiento para programas y datos a largo plazo, mientras que una memoria
principal más pequeña contiene programas y datos actualmente en uso.
En este esquema de dos niveles, la organización del flujo de información entre la memoria principal y secundaria supone una de las preocupaciones principales del sistema. La responsabilidad para
este flujo podría asignarse a cada programador en particular, pero no es practicable o deseable por
dos motivos:
1. La memoria principal disponible para un programa más sus datos podría ser insuficiente.
En este caso, el programador debería utilizar una técnica conocida como superposición
(overlaying), en la cual los programas y los datos se organizan de tal forma que se puede
asignar la misma región de memoria a varios módulos, con un programa principal responsable para intercambiar los módulos entre disco y memoria según las necesidades. Incluso
con la ayuda de herramientas de compilación, la programación con overlays malgasta tiempo del programador.
2. En un entorno multiprogramado, el programador no conoce en tiempo de codificación cuánto
espacio estará disponible o dónde se localizará dicho espacio.
Por tanto, está claro que la tarea de mover la información entre los dos niveles de la memoria debería ser una responsabilidad del sistema. Esta tarea es la esencia de la gestión de la memoria.
7.2. PARTICIONAMIENTO DE LA MEMORIA
La operación principal de la gestión de la memoria es traer los procesos a la memoria principal para
que el procesador los pueda ejecutar. En casi todos los sistemas multiprogramados modernos, esto
implica el uso de un esquema sofisticado denominado memoria virtual. Por su parte, la memoria virtual se basa en una o ambas de las siguientes técnicas básicas: segmentación y paginación. Antes de
fijarse en estas técnicas de memoria virtual, se debe preparar el camino, analizando técnicas más sencillas que no utilizan memoria virtual (Tabla 7.1). Una de estas técnicas, el particionamiento, se ha
utilizado en algunas variantes de ciertos sistemas operativos ahora obsoletos. Las otras dos técnicas,
paginación sencilla y segmentación sencilla, no son utilizadas de forma aislada. Sin embargo, quedará más clara la discusión de la memoria virtual si se analizan primero estas dos técnicas sin tener en
cuenta consideraciones de memoria virtual.
PARTICIONAMIENTO FIJO
En la mayoría de los esquemas para gestión de la memoria, se puede asumir que el sistema operativo
ocupa alguna porción fija de la memoria principal y que el resto de la memoria principal está disponible para múltiples procesos. El esquema más simple para gestionar la memoria disponible es repartirla en regiones con límites fijos.
Tamaños de partición
La Figura 7.2 muestra ejemplos de dos alternativas para el particionamiento fijo. Una posibilidad
consiste en hacer uso de particiones del mismo tamaño. En este caso, cualquier proceso cuyo tamaño
07-Capitulo 7
312
16/5/05
17:04
Página 312
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 7.1.
Técnica
Técnicas de gestión de memoria.
Descripción
Virtudes
Defectos
Particionamiento fijo
La memoria principal se
divide en particiones
estáticas en tiempo
de generación del
sistema. Un proceso
se puede cargar en una
partición con igual o
superior tamaño.
Sencilla de implementar; Uso ineficiente de la
poca sobrecarga para
memoria, debido a la
el sistema operativo.
fragmentación interna;
debe fijarse el número
máximo de procesos
activos.
Particionamiento
dinámico
Las particiones se crean
de forma dinámica,
de tal forma que cada
proceso se carga en
una partición del
mismo tamaño que
el proceso.
No existe fragmentación
interna; uso más
eficiente de memoria
principal.
Uso ineficiente del
procesador, debido
a la necesidad de
compactación para
evitar la fragmentación
externa.
Paginación sencilla
La memoria principal
se divide en marcos
del mismo tamaño.
Cada proceso se divide
en páginas del mismo
tamaño que los marcos.
Un proceso se carga a
través de la carga de
todas sus páginas en
marcos disponibles,
no necesariamente
contiguos.
No existe fragmentación
externa.
Una pequeña cantidad
de fragmentación
interna.
Segmentación sencilla
Cada proceso se divide
en segmentos. Un
proceso se carga
cargando todos sus
segmentos en
particiones dinámicas,
no necesariamente
contiguas.
No existe fragmentación
interna; mejora la
utilización de la
memoria y reduce la
sobrecargada respecto
al particionamiento
dinámico.
Fragmentación externa.
Paginación con
memoria virtual
Exactamente igual que
la paginación sencilla,
excepto que no es
necesario cargar todas
las páginas de un
proceso. Las páginas
no residentes se traen
bajo demanda de forma
automática.
No existe fragmentación
externa; mayor grado
de multiprogramación;
gran espacio de
direcciones virtuales.
Sobrecarga por la
gestión compleja de
la memoria.
Segmentación con
memoria virtual
Exactamente igual que
la segmentación,
excepto que no es
necesario cargar todos
los segmentos de un
proceso. Los segmentos
no residentes se traen
bajo demanda de forma
automática.
No existe fragmentación
interna; mayor grado
de multiprogramación;
gran espacio de
direcciones virtuales;
soporte a protección y
compartición.
Sobrecarga por la
gestión compleja de
la memoria.
07-Capitulo 7
16/5/05
17:04
Página 313
Gestión de la memoria
Sistema operativo
8M
313
Sistema operativo
8M
2M
8M
4M
6M
8M
8M
8M
8M
8M
8M
12M
8M
16M
8M
(a) Participaciones de igual tamaño
Figura 7.2.
(b) Participaciones de distinto tamaño
Ejemplo de particionamiento fijo de una memoria de 64 Mbytes.
es menor o igual que el tamaño de partición puede cargarse en cualquier partición disponible. Si todas las particiones están llenas y no hay ningún proceso en estado Listo o Ejecutando, el sistema operativo puede mandar a swap a un proceso de cualquiera de las particiones y cargar otro proceso, de
forma que el procesador tenga trabajo que realizar.
Existen dos dificultades con el uso de las particiones fijas del mismo tamaño:
• Un programa podría ser demasiado grande para caber en una partición. En este caso, el programador debe diseñar el programa con el uso de overlays, de forma que sólo se necesite
una porción del programa en memoria principal en un momento determinado. Cuando se necesita un módulo que no está presente, el programa de usuario debe cargar dicho módulo en
la partición del programa, superponiéndolo (overlaying) a cualquier programa o datos que
haya allí.
• La utilización de la memoria principal es extremadamente ineficiente. Cualquier programa, sin
importar lo pequeño que sea, ocupa una partición entera. En el ejemplo, podría haber un programa cuya longitud es menor que 2 Mbytes; ocuparía una partición de 8 Mbytes cuando se
lleva a la memoria. Este fenómeno, en el cual hay espacio interno malgastado debido al hecho
07-Capitulo 7
314
16/5/05
17:04
Página 314
Sistemas operativos. Aspectos internos y principios de diseño
de que el bloque de datos cargado es menor que la partición, se conoce con el nombre de fragmentación interna.
Ambos problemas se pueden mejorar, aunque no resolver, utilizando particiones de tamaño diferente (Figura 7.2b). En este ejemplo, los programas de 16 Mbytes se pueden acomodar sin overlays.
Las particiones más pequeñas de 8 Mbytes permiten que los programas más pequeños se puedan acomodar sin menor fragmentación interna.
Algoritmo de ubicación
Con particiones del mismo tamaño, la ubicación de los procesos en memoria es trivial. En cuanto
haya una partición disponible, un proceso se carga en dicha partición. Debido a que todas las particiones son del mismo tamaño, no importa qué partición se utiliza. Si todas las particiones se encuentran
ocupadas por procesos que no están listos para ejecutar, entonces uno de dichos procesos debe llevarse a disco para dejar espacio para un nuevo proceso. Cuál de los procesos se lleva a disco es una decisión de planificación; este tema se describe en la Parte Cuatro.
Con particiones de diferente tamaño, hay dos formas posibles de asignar los procesos a las particiones. La forma más sencilla consiste en asignar cada proceso a la partición más pequeña dentro de
la cual cabe1. En este caso, se necesita una cola de planificación para cada partición, que mantenga
procesos en disco destinados a dicha partición (Figura 7.3a). La ventaja de esta técnica es que los
procesos siempre se asignan de tal forma que se minimiza la memoria malgastada dentro de una partición (fragmentación interna).
Aunque esta técnica parece óptima desde el punto de vista de una partición individual, no es óptima desde el punto de vista del sistema completo. En la Figura 7.2b, por ejemplo, se considera un caso
en el que no haya procesos con un tamaño entre 12 y 16M en un determinado instante de tiempo. En
este caso, la partición de 16M quedará sin utilizarse, incluso aunque se puede asignar dicha partición
a algunos procesos más pequeños. Por tanto, una técnica óptima sería emplear una única cola para todos los procesos (Figura 7.3b). En el momento de cargar un proceso en la memoria principal, se selecciona la partición más pequeña disponible que puede albergar dicho proceso. Si todas las particiones están ocupadas, se debe llevar a cabo una decisión para enviar a swap a algún proceso. Tiene
preferencia a la hora de ser expulsado a disco el proceso que ocupe la partición más pequeña que pueda albergar al proceso entrante. Es también posible considerar otros factores, como la prioridad o una
preferencia por expulsar a disco procesos bloqueados frente a procesos listos.
El uso de particiones de distinto tamaño proporciona un grado de flexibilidad frente a las particiones fijas. Adicionalmente, se puede decir que los esquemas de particiones fijas son relativamente
sencillos y requieren un soporte mínimo por parte del sistema operativo y una sobrecarga de procesamiento mínimo. Sin embargo, tiene una serie de desventajas:
• El número de particiones especificadas en tiempo de generación del sistema limita el número
de proceso activos (no suspendidos) del sistema.
• Debido a que los tamaños de las particiones son preestablecidos en tiempo de generación del
sistema, los trabajos pequeños no utilizan el espacio de las particiones eficientemente. En un
entorno donde el requisito de almacenamiento principal de todos los trabajos se conoce de an-
1
Se asume que se conoce el tamaño máximo de memoria que un proceso requerirá. No siempre es el caso. Si no se sabe lo que
un proceso puede ocupar, la única alternativa es un esquema de overlays o el uso de memoria virtual.
07-Capitulo 7
16/5/05
17:04
Página 315
Gestión de la memoria
Sistema
operativo
315
Sistema
operativo
Nuevos
procesos
Nuevos
procesos
(a) Una cola de procesos por participación
Figura 7.3.
(b) Un única cola
Asignación de memoria para particionamiento fijo.
temano, esta técnica puede ser razonable, pero en la mayoría de los casos, se trata de una técnica ineficiente.
El uso de particionamiento fijo es casi desconocido hoy en día. Un ejemplo de un sistema operativo exitoso que sí utilizó esta técnica fue un sistema operativo de los primeros mainframes de IBM,
el sistema operativo OS/MFT (Multiprogramming with a Fixed Number of Tasks; Multiprogramado
con un número fijo de tareas).
PARTICIONAMIENTO DINÁMICO
Para vencer algunas de las dificultades con particionamiento fijo, se desarrolló una técnica conocida
como particionamiento dinámico. De nuevo, esta técnica se ha sustituido por técnicas de gestión de
memoria más sofisticadas. Un sistema operativo importante que utilizó esta técnica fue el sistema
operativo de mainframes de IBM, el sistema operativo OS/MVT (Multiprogramming with a Variable
Number of Tasks; Multiprogramado con un número variable de tareas).
Con particionamiento dinámico, las particiones son de longitud y número variable. Cuando se lleva un proceso a la memoria principal, se le asigna exactamente tanta memoria como requiera y no
más. Un ejemplo, que utiliza 64 Mbytes de memoria principal, se muestra en la Figura 7.4. Inicialmente, la memoria principal está vacía, excepto por el sistema operativo (a). Los primeros tres procesos se cargan justo donde el sistema operativo finaliza y ocupando el espacio justo para cada proceso
(b, c, d). Esto deja un «hueco» al final de la memoria que es demasiado pequeño para un cuarto proceso. En algún momento, ninguno de los procesos que se encuentran en memoria está disponible. El
sistema operativo lleva el proceso 2 al disco (e), que deja suficiente espacio para cargar un nuevo
proceso, el proceso 4 (f). Debido a que el proceso 4 es más pequeño que el proceso 2, se crea otro pequeño hueco. Posteriormente, se alcanza un punto en el cual ninguno de los procesos de la memoria
principal está listo, pero el proceso 2, en estado Listo-Suspendido, está disponible. Porque no hay es-
07-Capitulo 7
316
16/5/05
17:04
Página 316
Sistemas operativos. Aspectos internos y principios de diseño
Sistema
operativo
8M
Sistema
operativo
Proceso 1
Sistema
operativo
20M
56M
Sistema
operativo
Proceso 1
20M
Proceso 1
20M
Proceso 2
14M
Proceso 2
14M
Proceso 3
18M
36M
22M
4M
(a)
(b)
(c)
(d)
Sistema
operativo
Sistema
operativo
Sistema
operativo
Sistema
operativo
Proceso 1
20M
Proceso 1
20M
20M
Proceso 2
14M
6M
14M
Proceso 4
8M
Proceso 4
6M
Proceso 3
18M
Proceso 3
(f)
Figura 7.4.
6M
Proceso 3
4M
4M
(e)
18M
8M
Proceso 4
18M
6M
Proceso 3
4M
(g)
8M
18M
4M
(h)
El efecto del particionamiento dinámico.
pacio suficiente en la memoria para el proceso 2, el sistema operativo lleva a disco el proceso 1 (g) y
lleva a la memoria el proceso 2 (h).
Como muestra este ejemplo, el método comienza correctamente, pero finalmente lleva a una situación en la cual existen muchos huecos pequeños en la memoria. A medida que pasa el tiempo, la
memoria se fragmenta cada vez más y la utilización de la memoria se decrementa. Este fenómeno se
conoce como fragmentación externa, indicando que la memoria que es externa a todas las particiones se fragmenta de forma incremental, por contraposición a lo que ocurre con la fragmentación interna, descrita anteriormente.
Una técnica para eliminar la fragmentación externa es la compactación: de vez en cuando, el sistema operativo desplaza los procesos en memoria, de forma que se encuentren contiguos y de este
modo toda la memoria libre se encontrará unida en un bloque. Por ejemplo, en la Figura 7.4R, la
compactación permite obtener un bloque de memoria libre de longitud 16M. Esto sería suficiente
para cargar un proceso adicional. La desventaja de la compactación es el hecho de que se trata de un
procedimiento que consume tiempo y malgasta tiempo de procesador. Obsérvese que la compactación requiere la capacidad de reubicación dinámica. Es decir, debe ser posible mover un programa
desde una región a otra en la memoria principal sin invalidar las referencias de la memoria de cada
programa (véase Apéndice 7A).
07-Capitulo 7
16/5/05
17:04
Página 317
Gestión de la memoria
317
Algoritmo de ubicación
Debido a que la compactación de memoria consume una gran cantidad de tiempo, el diseñador del
sistema operativo debe ser inteligente a la hora de decidir cómo asignar la memoria a los procesos
(cómo eliminar los huecos). A la hora de cargar o intercambiar un proceso a la memoria principal, y
siempre que haya más de un bloque de memoria libre de suficiente tamaño, el sistema operativo debe
decidir qué bloque libre asignar.
Tres algoritmos de colocación que pueden considerarse son mejor-ajuste (best-fit), primer-ajuste(first-fit) y siguiente-ajuste (next-fit). Todos, por supuesto, están limitados a escoger entre los bloques libres de la memoria principal que son iguales o más grandes que el proceso que va a llevarse a
la memoria. Mejor-ajuste escoge el bloque más cercano en tamaño a la petición. Primer-ajuste comienza a analizar la memoria desde el principio y escoge el primer bloque disponible que sea suficientemente grande. Siguiente-ajuste comienza a analizar la memoria desde la última colocación y
elige el siguiente bloque disponible que sea suficientemente grande.
La Figura 7.5a muestra un ejemplo de configuración de memoria después de un número de colocaciones e intercambios a disco. El último bloque que se utilizó fue un bloque de 22 Mbytes del cual
se crea una partición de 14 Mbytes. La Figura 7.5b muestra la diferencia entre los algoritmos de mejor-, primer- y siguiente- ajuste a la hora de satisfacer una petición de asignación de 16 Mbytes. Me-
8M
8M
12M
Primer-ajuste
12M
22M
6M
Mejor-ajuste
Último
bloque
asignado
18M
2M
8M
8M
6M
6M
Bloque asignado
Bloque libre
Nueva posible asignación
14M
14M
Próximo-ajuste
36M
20M
(a) Antes
Figura 7.5.
(b) Después
Ejemplo de configuración de la memoria antes y después de la asignación
de un bloque de 16 Mbytes.
07-Capitulo 7
318
16/5/05
17:04
Página 318
Sistemas operativos. Aspectos internos y principios de diseño
jor-ajuste busca la lista completa de bloques disponibles y hace uso del bloque de 18 Mbytes, dejando
un fragmento de 2 Mbytes. Primer-ajuste provoca un fragmento de 6 Mbytes, y siguiente-ajuste provoca un fragmento de 20 Mbytes.
Cuál de estas técnicas es mejor depende de la secuencia exacta de intercambio de procesos y del tamaño de dichos procesos. Sin embargo, se pueden hacer algunos comentarios (véase también
[BREN89], [SHOR75] y [BAYS77]). El algoritmo primer-ajuste no es sólo el más sencillo, sino que
normalmente es también el mejor y más rápido. El algoritmo siguiente-ajuste tiende a producir resultados ligeramente peores que el primer-ajuste. El algoritmo siguiente-ajuste llevará más frecuentemente a
una asignación de un bloque libre al final de la memoria. El resultado es que el bloque más grande de
memoria libre, que normalmente aparece al final del espacio de la memoria, se divide rápidamente en
pequeños fragmentos. Por tanto, en el caso del algoritmo siguiente-ajuste se puede requerir más frecuentemente la compactación. Por otro lado, el algoritmo primer-ajuste puede dejar el final del espacio
de almacenamiento con pequeñas particiones libres que necesitan buscarse en cada paso del primerajuste siguiente. El algoritmo mejor-ajuste, a pesar de su nombre, su comportamiento normalmente es el
peor. Debido a que el algoritmo busca el bloque más pequeño que satisfaga la petición, garantiza que el
fragmento que quede sea lo más pequeño posible. Aunque cada petición de memoria siempre malgasta
la cantidad más pequeña de la memoria, el resultado es que la memoria principal se queda rápidamente
con bloques demasiado pequeños para satisfacer las peticiones de asignación de la memoria. Por tanto,
la compactación de la memoria se debe realizar más frecuentemente que con el resto de los algoritmos.
Algoritmo de reemplazamiento
En un sistema multiprogramado que utiliza particionamiento dinámico, puede haber un momento en
el que todos los procesos de la memoria principal estén en estado bloqueado y no haya suficiente memoria para un proceso adicional, incluso después de llevar a cabo una compactación. Para evitar malgastar tiempo de procesador esperando a que un proceso se desbloquee, el sistema operativo intercambiará alguno de los procesos entre la memoria principal y disco para hacer sitio a un nuevo
proceso o para un proceso que se encuentre en estado Listo-Suspendido. Por tanto, el sistema operativo debe escoger qué proceso reemplazar. Debido a que el tema de los algoritmos de reemplazo se
contemplará en detalle respecto a varios esquemas de la memoria virtual, se pospone esta discusión
hasta entonces.
SISTEMA BUDDY
Ambos esquemas de particionamiento, fijo y dinámico, tienen desventajas. Un esquema de particionamiento fijo limita el número de procesos activos y puede utilizar el espacio ineficientemente si
existe un mal ajuste entre los tamaños de partición disponibles y los tamaños de los procesos. Un esquema de particionamiento dinámico es más complejo de mantener e incluye la sobrecarga de la
compactación. Un compromiso interesante es el sistema buddy ([KNUT97], [PETE77]).
En un sistema buddy, los bloques de memoria disponibles son de tamaño 2k, L £ K £ U, donde
2L = bloque de tamaño más pequeño asignado
2U = bloque de tamaño mayor asignado; normalmente 2U es el tamaño de la memoria completa
disponible
Para comenzar, el espacio completo disponible se trata como un único bloque de tamaño 2U. Si se
realiza una petición de tamaño s, tal que 2U-1< s £ 2U, se asigna el bloque entero. En otro caso, el blo-
07-Capitulo 7
16/5/05
17:04
Página 319
Gestión de la memoria
319
que se divide en dos bloques buddy iguales de tamaño 2U-1. Si 2U-2< s £ 2U-1, entonces se asigna la petición a uno de los otros dos bloques. En otro caso, uno de ellos se divide por la mitad de nuevo. Este
proceso continúa hasta que el bloque más pequeño mayor o igual que s se genera y se asigna a la petición. En cualquier momento, el sistema buddy mantiene una lista de huecos (bloques sin asignar) de
cada tamaño 2i. Un hueco puede eliminarse de la lista (i+1) dividiéndolo por la mitad para crear dos
bloques de tamaño 2i en la lista i. Siempre que un par de bloques de la lista i no se encuentren asignados, son eliminados de dicha lista y unidos en un único bloque de la lista (i+1). Si se lleva a cabo una
petición de asignación de tamaño k tal que 2i-1< k £ 2i, se utiliza el siguiente algoritmo recursivo (de
[LIST93]) para encontrar un hueco de tamaño 2i:
void obtener_hueco (int i)
{
if (i==(U+1))
<fallo>;
if (<lista_i vacía>)
{
obtener_hueco(i+1);
<dividir hueco en dos buddies>;
<colocar buddies en lista_i>;
}
<tomar primer hueco de la lista_i>;
}
La Figura 7.6 muestra un ejemplo que utiliza un bloque inicial de 1 Mbyte. La primera petición,
A, es de 100 Kbytes, de tal forma que se necesita un bloque de 128K. El bloque inicial se divide en
dos de 512K. El primero de éstos se divide en dos bloques de 256K, y el primero de éstos se divide
en dos de 128K, uno de los cuales se asigna a A. La siguiente petición, B, solicita un bloque de 256K.
Dicho bloque está disponible y es asignado. El proceso continúa, provocando divisiones y fusiones
de bloques cuando se requiere. Obsérvese que cuando se libera E, se unen dos bloques de 128K en un
bloque de 256K, que es inmediatamente unido con su bloque compañero (buddy).
La Figura 7.7 muestra una representación en forma de árbol binario de la asignación de bloques
inmediatamente después de la petición «Liberar B». Los nodos hoja representan el particionamiento
de la memoria actual. Si dos bloques son nodos hoja, entonces al menos uno de ellos debe estar asignado; en otro caso, se unen en un bloque mayor.
El sistema buddy es un compromiso razonable para eliminar las desventajas de ambos esquemas
de particionamiento, fijo y variable, pero en los sistemas operativos contemporáneos, la memoria virtual basada en paginación y segmentación es superior. Sin embargo, el sistema buddy se ha utilizado
en sistemas paralelos como una forma eficiente de asignar y liberar programas paralelos (por ejemplo, véase [JOHN92]). Una forma modificada del sistema buddy se utiliza en la asignación de memoria del núcleo UNIX (descrito en el Capítulo 8).
REUBICACIÓN
Antes de considerar formas de tratar los problemas del particionamiento, se va a aclarar un aspecto
relacionado con la colocación de los procesos en la memoria. Cuando se utiliza el esquema de particionamiento fijo de la Figura 7.3a, se espera que un proceso siempre se asigne a la misma partición.
07-Capitulo 7
320
16/5/05
17:04
Página 320
Sistemas operativos. Aspectos internos y principios de diseño
1 bloque de 1 Mbyte
1M
Solicitar 100K A 128K
128K
256K
512K
Solicitar 240K A 128K
128K
B 256K
512K
512K
Solicitar 64K A 128K
C 64K
64K
B 256K
Solicitar 256K A 128K
C 64K
64K
B 256K
D 256K
256K
Liberar B A 128K
C 64K
64K
256K
D 256K
256K
C 64K
64K
256K
D 256K
256K
C 64K
64K
256K
D 256K
256K
256K
D 256K
256K
D 256K
256K
Liberar A
128K
Solicitar 75K E 128K
Liberar C E 128K
128K
512K
Liberar E
Liberar D
1M
Figura 7.6.
Ejemplo de sistema Buddy.
1M
512K
256K
128K
64K
A 128K
C 64K
64K
Figura 7.7.
256K
D 256K
256K
Representación en forma de árbol del sistema Buddy.
Es decir, sea cual sea la partición seleccionada cuando se carga un nuevo proceso, ésta será la utilizada para el intercambio del proceso entre la memoria y el área de swap en disco. En este caso, se utiliza un cargador sencillo, tal y como se describe en el Apéndice 7A: cuando el proceso se carga por
07-Capitulo 7
16/5/05
17:04
Página 321
Gestión de la memoria
321
primera vez, todas las referencias de la memoria relativas del código se reemplazan por direcciones
de la memoria principal absolutas, determinadas por la dirección base del proceso cargado.
En el caso de particiones de igual tamaño (Figura 7.2), y en el caso de una única cola de procesos para particiones de distinto tamaño (Figura 7.3b), un proceso puede ocupar diferentes particiones durante el transcurso de su ciclo de vida. Cuando la imagen de un proceso se crea por primera
vez, se carga en la misma partición de memoria principal. Más adelante, el proceso podría llevarse
a disco; cuando se trae a la memoria principal posteriormente, podría asignarse a una partición distinta de la última vez. Lo mismo ocurre en el caso del particionamiento dinámico. Obsérvese en las
Figuras 7.4c y h que el proceso 2 ocupa dos regiones diferentes de memoria en las dos ocasiones
que se trae a la memoria. Más aún, cuando se utiliza la compactación, los procesos se desplazan
mientras están en la memoria principal. Por tanto, las ubicaciones (de las instrucciones y los datos)
referenciadas por un proceso no son fijas. Cambiarán cada vez que un proceso se intercambia o se
desplaza. Para resolver este problema, se realiza una distinción entre varios tipos de direcciones.
Una dirección lógica es una referencia a una ubicación de memoria independiente de la asignación
actual de datos a la memoria; se debe llevar a cabo una traducción a una dirección física antes de
que se alcance el acceso a la memoria. Una dirección relativa es un ejemplo particular de dirección
lógica, en el que la dirección se expresa como una ubicación relativa a algún punto conocido, normalmente un valor en un registro del procesador. Una dirección física, o dirección absoluta, es una
ubicación real de la memoria principal.
Los programas que emplean direcciones relativas de memoria se cargan utilizando carga dinámica en tiempo de ejecución (véase Apéndice 7A, donde se recoge una discusión). Normalmente, todas
las referencias de memoria de los procesos cargados son relativas al origen del programa. Por tanto,
se necesita un mecanismo hardware para traducir las direcciones relativas a direcciones físicas de la
memoria principal, en tiempo de ejecución de la instrucción que contiene dicha referencia.
La Figura 7.8 muestra la forma en la que se realiza normalmente esta traducción de direcciones.
Cuando un proceso se asigna al estado Ejecutando, un registro especial del procesador, algunas veces
llamado registro base, carga la dirección inicial del programa en la memoria principal. Existe también
un registro «valla», que indica el final de la ubicación del programa; estos valores se establecen cuando el programa se carga en la memoria o cuando la imagen del proceso se lleva a la memoria. A lo
largo de la ejecución del proceso, se encuentran direcciones relativas. Éstas incluyen los contenidos
del registro de las instrucciones, las direcciones de instrucciones que ocurren en los saltos e instrucciones call, y direcciones de datos existentes en instrucciones de carga y almacenamiento. El procesador manipula cada dirección relativa, a través de dos pasos. Primero, el valor del registro base se
suma a la dirección relativa para producir una dirección absoluta. Segundo, la dirección resultante se
compara con el valor del registro «valla». Si la dirección se encuentra dentro de los límites, entonces
se puede llevar a cabo la ejecución de la instrucción. En otro caso, se genera una interrupción, que
debe manejar el sistema operativo de algún modo.
El esquema de la Figura 7.8 permite que se traigan a memoria los programas y que se lleven a
disco, a lo largo de la ejecución. También proporciona una medida de protección: cada imagen del
proceso está aislada mediante los contenidos de los registros base y valla. Además, evita accesos no
autorizados por parte de otros procesos.
7.3. PAGINACIÓN
Tanto las particiones de tamaño fijo como variable son ineficientes en el uso de la memoria; los primeros provocan fragmentación interna, los últimos fragmentación externa. Supóngase, sin embargo,
que la memoria principal se divide en porciones de tamaño fijo relativamente pequeños, y que cada
proceso también se divide en porciones pequeñas del mismo tamaño fijo. A dichas porciones del pro-
07-Capitulo 7
322
16/5/05
17:04
Página 322
Sistemas operativos. Aspectos internos y principios de diseño
Dirección relativa
Bloque de control de proceso
Registro base
Sumador
Programa
Dirección
absoluta
Registro valla
Comparador
Datos
Interrupción al
sistema operativo
Pila
Imagen del proceso en
la memoria principal
Figura 7.8.
Soporte hardware para la reubicación.
ceso, conocidas como páginas, se les asigna porciones disponibles de memoria, conocidas como
marcos, o marcos de páginas. Esta sección muestra que el espacio de memoria malgastado por cada
proceso debido a la fragmentación interna corresponde sólo a una fracción de la última página de un
proceso. No existe fragmentación externa.
La Figura 7.9 ilustra el uso de páginas y marcos. En un momento dado, algunos de los marcos
de la memoria están en uso y algunos están libres. El sistema operativo mantiene una lista de marcos libres. El proceso A, almacenado en disco, está formado por cuatro páginas. En el momento
de cargar este proceso, el sistema operativo encuentra cuatro marcos libres y carga las cuatro páginas del proceso A en dichos marcos (Figura 7.9b). El proceso B, formado por tres páginas, y el
proceso C, formado por cuatro páginas, se cargan a continuación. En ese momento, el proceso B
se suspende y deja la memoria principal. Posteriormente, todos los procesos de la memoria principal se bloquean, y el sistema operativo necesita traer un nuevo proceso, el proceso D, que está
formado por cinco páginas.
Ahora supóngase, como en este ejemplo, que no hay suficientes marcos contiguos sin utilizar
para ubicar un proceso. ¿Esto evitaría que el sistema operativo cargara el proceso D? La respuesta
es no, porque una vez más se puede utilizar el concepto de dirección lógica. Un registro base sencillo de direcciones no basta en esta ocasión. En su lugar, el sistema operativo mantiene una tabla de
páginas por cada proceso. La tabla de páginas muestra la ubicación del marco por cada página del
proceso. Dentro del programa, cada dirección lógica está formada por un número de página y un
desplazamiento dentro de la página. Es importante recordar que en el caso de una partición simple,
una dirección lógica es la ubicación de una palabra relativa al comienzo del programa; el procesador
la traduce en una dirección física. Con paginación, la traducción de direcciones lógicas a físicas las
realiza también el hardware del procesador. Ahora el procesador debe conocer cómo acceder a la ta-
07-Capitulo 7
16/5/05
17:04
Página 323
Gestión de la memoria
Marco
número
Memoria principal
Memoria principal
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(a) Quince marcos
disponibles
(b) Cargar proceso A
Memoria principal
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
A.0
A.1
A.2
A.3
B.0
B.1
B.2
C.0
C.1
C.2
C.3
(d) Cargar proceso C
Figura 7.9.
A.0
A.1
A.2
A.3
Memoria principal
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
A.0
A.1
A.2
A.3
C.0
C.1
C.2
C.3
(e) Intercambiar B
A.0
A.1
A.2
A.3
B.0
B.1
B.2
(c) Cargar proceso B
Memoria principal
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
323
Memoria principal
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
A.0
A.1
A.2
A.3
D.0
D.1
D.2
C.0
C.1
C.2
C.3
D.3
D.4
(f) Cargar proceso D
Asignación de páginas de proceso a marcos libres.
bla de páginas del proceso actual. Presentado como una dirección lógica (número de página, desplazamiento), el procesador utiliza la tabla de páginas para producir una dirección física (número de
marco, desplazamiento).
Continuando con nuestro ejemplo, las cinco páginas del proceso D se cargan en los marcos
4, 5, 6, 11 y 12. La Figura 7.10 muestra las diferentes tablas de páginas en este momento. Una
tabla de páginas contiene una entrada por cada página del proceso, de forma que la tabla se indexe fácilmente por el número de página (iniciando en la página 0). Cada entrada de la tabla de páginas contiene el número del marco en la memoria principal, si existe, que contiene la página correspondiente, Adicionalmente, el sistema operativo mantiene una única lista de marcos libres de
todos los marcos de la memoria que se encuentran actualmente no ocupados y disponibles para
las páginas.
07-Capitulo 7
324
16/5/05
17:04
Página 324
Sistemas operativos. Aspectos internos y principios de diseño
0
1
2
3
0
1
2
3
Tabla de
páginas del
proceso A
Figura 7.10.
0
1
2
—
—
—
Tabla de
páginas del
proceso B
0
1
2
3
7
8
9
10
Tabla de
páginas del
proceso C
0
1
2
3
4
4
5
6
11
12
13
14
Lista de
marcos libre
Tabla de
páginas del
proceso D
Estructuras de datos para el ejemplo de la Figura 7.9 en el instante (f).
Por tanto vemos que la paginación simple, tal y como se describe aquí, es similar al particionamiento fijo. Las diferencias son que, con la paginación, las particiones son bastante pequeñas; un programa podría ocupar más de una partición; y dichas particiones no necesitan ser contiguas.
Para hacer este esquema de paginación conveniente, el tamaño de página y por tanto el tamaño
del marco debe ser una potencia de 2. Con el uso de un tamaño de página potencia de 2, es fácil demostrar que la dirección relativa, que se define con referencia al origen del programa, y la dirección
lógica, expresada como un número de página y un desplazamiento, es lo mismo. Se muestra un ejemplo en la Figura 7.11. En este ejemplo, se utilizan direcciones de 16 bits, y el tamaño de la página es
1K = 1024 bytes. La dirección relativa 1502, en formato binario, es 0000010111011110. Con una página de tamaño 1K, se necesita un campo de desplazamiento de 10 bits, dejando 6 bits para el número
de página. Por tanto, un programa puede estar compuesto por un máximo de 26=64 páginas de 1K
byte cada una. Como muestra la Figura 7.11, la dirección relativa 1502 corresponde a un desplazamiento de 478 (0111011110) en la página 1 (000001), que forma el mismo número de 16 bits,
0000010111011110.
Las consecuencias de utilizar un tamaño de página que es una potencia de 2 son dobles. Primero,
el esquema de direccionamiento lógico es transparente al programador, al ensamblador y al montador.
Cada dirección lógica (número de página, desplazamiento) de un programa es idéntica a su dirección
relativa. Segundo, es relativamente sencillo implementar una función que ejecute el hardware para
llevar a cabo dinámicamente la traducción de direcciones en tiempo de ejecución. Considérese una
dirección de n+m bits, donde los n bits de la izquierda corresponden al número de página y los m bits
de la derecha corresponden al desplazamiento. En el ejemplo (Figura 7.11b), n = 6 y m = 10. Se necesita llevar a cabo los siguientes pasos para la traducción de direcciones:
• Extraer el número de página como los n bits de la izquierda de la dirección lógica.
• Utilizar el número de página como un índice a tabla de páginas del proceso para encontrar el
número de marco, k.
• La dirección física inicial del marco es k ¥ 2m, y la dirección física del byte referenciado es dicho número más el desplazamiento. Esta dirección física no necesita calcularse; es fácilmente
construida concatenando el número de marco al desplazamiento.
En el ejemplo, se parte de la dirección lógica 0000010111011110, que corresponde a la página
número 1, desplazamiento 478. Supóngase que esta página reside en el marco de memoria principal 6
= número binario 000110. Por tanto, la dirección física corresponde al marco número 6, desplazamiento 478 = 0001100111011110 (Figura 7.12a).
Resumiendo, con la paginación simple, la memoria principal se divide en muchos marcos pequeños de igual tamaño. Cada proceso se divide en páginas de igual tamaño; los procesos más pequeños
requieren menos páginas, los procesos mayores requieren más. Cuando un proceso se trae a la memo-
07-Capitulo 7
16/5/05
17:04
Página 325
Gestión de la memoria
Dirección lógica =
Segmento# = 1, desplazamiento = 752
0001001011110000
Segmento 0
750 bytes
Dirección lógica =
Página# = 1, Desplazamiento = 478
0000010111011110
(a) Particionado
752
Segmento 1
1950 bytes
478
Fragmentación
interna
Página 1
Página 2
Proceso de usuario
(2700 bytes)
Página 0
Dirección relativa = 1502
0000010111011110
325
(c) Segmentación
(b) Paginación
(tamaño página = 1K)
Figura 7.11.
Direcciones lógicas.
ria, todas sus páginas se cargan en los marcos disponibles, y se establece una tabla de páginas. Esta
técnica resuelve muchos de los problemas inherentes en el particionamiento.
7.4. SEGMENTACIÓN
Un programa de usuario se puede subdividir utilizando segmentación, en la cual el programa y sus
datos asociados se dividen en un número de segmentos. No se requiere que todos los programas sean
de la misma longitud, aunque hay una longitud máxima de segmento. Como en el caso de la paginación, una dirección lógica utilizando segmentación está compuesta por dos partes, en este caso un número de segmento y un desplazamiento.
Debido al uso de segmentos de distinto tamaño, la segmentación es similar al particionamiento
dinámico. En la ausencia de un esquema de overlays o el uso de la memoria virtual, se necesitaría que
todos los segmentos de un programa se cargaran en la memoria para su ejecución. La diferencia,
comparada con el particionamiento dinámico, es que con la segmentación un programa podría ocupar
más de una partición, y estas particiones no necesitan ser contiguas. La segmentación elimina la fragmentación interna pero, al igual que el particionamiento dinámico, sufre de fragmentación externa.
Sin embargo, debido a que el proceso se divide en varias piezas más pequeñas, la fragmentación externa debería ser menor.
Mientras que la paginación es invisible al programador, la segmentación es normalmente visible
y se proporciona como una utilidad para organizar programas y datos. Normalmente, el programador
o compilador asignará programas y datos a diferentes segmentos. Para los propósitos de la programación modular, los programas o datos se pueden dividir posteriormente en múltiples segmentos. El in-
07-Capitulo 7
326
16/5/05
17:04
Página 326
Sistemas operativos. Aspectos internos y principios de diseño
Dirección lógica de 16 bits
Página de 6 bits # Desplazamiento de 10 bits
0000010111011110
0 000101
1 000110
2 011001
Tabla de páginas
de proceso
0001100111011110
Dirección física de 16 bits
(a) Paginación
Dirección lógica de 16 bits
Segmento de 4 bits # Desplazamiento de 12 bits
0001001011110000
Longitud
Base
0 001011101110 0000010000000000
1 0111100111100010000000100000
Tabla de segmentos de proceso
+
0010001100010000
Dirección física de 16 bits
(b) Segmentación
Figura 7.12.
Ejemplos de traducción de direcciones lógicas a físicas.
conveniente principal de este servicio es que el programador debe ser consciente de la limitación de
tamaño de segmento máximo.
Otra consecuencia de utilizar segmentos de distinto tamaño es que no hay una relación simple entre direcciones lógicas y direcciones físicas. De forma análoga a la paginación, un esquema de segmentación sencillo haría uso de una tabla de segmentos por cada proceso y una lista de bloques libre
de memoria principal. Cada entrada de la tabla de segmentos tendría que proporcionar la dirección
inicial de la memoria principal del correspondiente segmento. La entrada también debería proporcionar la longitud del segmento, para asegurar que no se utilizan direcciones no válidas. Cuando un proceso entra en el estado Ejecutando, la dirección de su tabla de segmentos se carga en un registro especial utilizado por el hardware de gestión de la memoria.
Considérese una dirección de n+m bits, donde los n bits de la izquierda corresponden al número
de segmento y los m bits de la derecha corresponden al desplazamiento. En el ejemplo (Figura 7.11c),
n = 4 y m = 12. Por tanto, el tamaño de segmento máximo es 212 = 4096. Se necesita llevar a cabo los
siguientes pasos para la traducción de direcciones:
• Extraer el número de segmento como los n bits de la izquierda de la dirección lógica.
• Utilizar el número de segmento como un índice a la tabla de segmentos del proceso para encontrar la dirección física inicial del segmento.
07-Capitulo 7
16/5/05
17:04
Página 327
Gestión de la memoria
327
• Comparar el desplazamiento, expresado como los m bits de la derecha, y la longitud del segmento. Si el desplazamiento es mayor o igual que la longitud, la dirección no es válida.
• La dirección física deseada es la suma de la dirección física inicial y el desplazamiento.
En el ejemplo, se parte de la dirección lógica 0001001011110000, que corresponde al segmento
número 1, desplazamiento 752. Supóngase que este segmento reside en memoria principal, comenzando en la dirección física inicial 0010000000100000. Por tanto, la dirección física es
0010000000100000 + 001011110000 = 0010001100010000 (Figura 7.12b).
Resumiendo, con la segmentación simple, un proceso se divide en un conjunto de segmentos que
no tienen que ser del mismo tamaño. Cuando un proceso se trae a memoria, todos sus segmentos se
cargan en regiones de memoria disponibles, y se crea la tabla de segmentos.
7.5. RESUMEN
Una de las tareas más importantes y complejas de un sistema operativo es la gestión de memoria. La
gestión de memoria implica tratar la memoria principal como un recurso que debe asignarse y compartirse entre varios procesos activos. Para utilizar el procesador y las utilidades de E/S eficientemente, es deseable mantener tantos procesos en memoria principal como sea posible. Además, es deseable también liberar a los programadores de tener en cuenta las restricciones de tamaño en el
desarrollo de los programas.
Las herramientas básicas de gestión de memoria son la paginación y la segmentación. Con la paginación, cada proceso se divide en un conjunto de páginas de tamaño fijo y de un tamaño relativamente pequeño. La segmentación permite el uso de piezas de tamaño variable. Es también posible
combinar la segmentación y la paginación en un único esquema de gestión de memoria.
7.6. LECTURAS RECOMENDADAS
Los libros de sistema operativos recomendados en la Sección 2.9 proporcionan cobertura para la gestión de memoria.
Debido a que el sistema de particionamiento se ha suplantado por técnicas de memoria virtual, la
mayoría de los libros sólo cubren superficialmente el tema. Uno de los tratamientos más completos e
interesantes se encuentra en [MILE92]. Una discusión más profunda de las estrategias de particionamiento se encuentra en [KNUT97].
Los temas de enlace y carga se cubren en muchos libros de desarrollo de programas, arquitectura
de computadores y sistemas operativos. Un tratamiento particularmente detallado es [BECK90].
[CLAR98] también contiene una buena discusión. Una discusión práctica en detalle de este tema, con
numerosos ejemplos de sistemas operativos, es [LEVI99].
BECK90 Beck, L. System Software. Reading, MA: Addison-Wesley, 1990.
CLAR98 Clarke, D., and Merusi, D. System Software Programming: The Way Things Work. Upper Saddle River, NJ: Prentice Hall, 1998.
KNUT97 Knuth, D. The Art of Computer Programming, Volume 1: Fundamental Algorithms. Reading,
MA: Addison-Wesley, 1997.
LEVI99 Levine, J. Linkers and Loaders. New York: Elsevier Science and Technology, 1999.
MILE92 Milenkovic,M. Operating Systems: Concepts and Design. New York: McGraw-Hill, 1992.
07-Capitulo 7
328
16/5/05
17:04
Página 328
Sistemas operativos. Aspectos internos y principios de diseño
7.7. TÉRMINOS CLAVE, CUESTIONES DE REVISIÓN Y PROBLEMAS
TÉRMINOS CLAVE
Carga
Enlace dinámico
Particionamiento
Carga absoluta
Enlazado
Particionamiento dinámico
Carga en tiempo real dinámica
Fragmentación externa
Particionamiento fijo
Carga reubicable
Fragmentación interna
Protección
Compactación
Gestión de memoria
Reubicación
Compartición
Marca
Segmentación
Dirección física
Organización lógica
Sistema XXXX
Dirección lógica
Organización física
Tabla de páginas
Dirección relativa
Página
Editor de enlaces
Paginación
CUESTIONES DE REVISIÓN
7.1.
¿Qué requisitos se intenta satisfacer en gestión de la memoria?
7.2.
¿Por qué es deseable la capacidad para reubicar procesos?
7.3.
¿Por qué no es posible forzar la protección de la memoria en tiempo de compilación?
7.4.
¿Qué razones existen para permitir que dos o más procesos accedan a una misma región de
la memoria?
7.5.
En un esquema de particionamiento fijo, ¿cuáles son las ventajas de utilizar particiones de
distinto tamaño?
7.6.
¿Cuál es la diferencia entre fragmentación interna y externa?
7.7.
¿Cuáles son las distinciones entre direcciones lógicas, relativas y físicas?
7.8.
¿Cuál es la diferencia entre una página y un marco?
7.9.
¿Cuál es la diferencia entre una página y un segmento?
PROBLEMAS
7.1.
En la Sección 2.3, se listaron cinco objetivos de la gestión de la memoria y en la Sección
7.1 cinco requisitos. Discutir si cada lista incluye los aspectos tratados en la otra lista.
7.2.
Considérese un esquema de particionamiento fijo con particiones de igual tamaño de 216
bytes y una memoria principal total de tamaño 224 bytes. Por cada proceso residente, se
mantiene una tabla de procesos que incluye un puntero a una partición. ¿Cuántos bits necesita el puntero?
7.3.
Considérese un esquema de particionamiento dinámico. Demostrar que, en media, la memoria contiene la mitad de huecos que de segmentos.
7.4.
Para implementar los diferentes algoritmos de colocación discutidos para el particionamiento dinámico (Sección 7.2), se debe guardar una lista de los bloques libres de memoria.
16/5/05
17:04
Página 329
Gestión de la memoria
329
Para cada uno de los tres métodos discutidos (mejor ajuste (best-fit), primer ajuste (first-fit)
y próximo ajuste (next-fit)), ¿cuál es la longitud media de la búsqueda?
40M
60M
60M
40M
20M
Si se utiliza un esquema de particionamiento dinámico y en un determinado momento la
configuración de memoria es la siguiente:
10M
7.6.
20M
Otro algoritmo de colocación para el particionamiento dinámico es el de peor ajuste
(worst-fit). En este caso, se utiliza el mayor bloque de memoria libre para un proceso. Discutir las ventajas e inconvenientes de este método comparado con el primer, próximo y mejor ajuste. ¿Cuál es la longitud media de la búsqueda para el peor ajuste?
20M
7.5.
20M
07-Capitulo 7
30M
40M
40M
Las áreas sombreadas son bloques asignados; las áreas blancas son bloques libres. Las siguientes tres peticiones de memoria son de 40M, 20M y 10M. Indíquese la dirección inicial para cada uno de los tres bloques utilizando los siguientes algoritmos de colocación:
a) Primer ajuste
b) Mejor ajuste
c) Siguiente ajuste. Asúmase que el bloque añadido más recientemente se encuentra al comienzo de la memoria.
d) Peor ajuste
7.7.
Un bloque de memoria de 1 Mbyte se asigna utilizando el sistema buddy:
a) Mostrar los resultados de la siguiente secuencia en una figura similar a la Figura 7.6:
Petición 70; Petición 35; Petición 80; Respuesta A; Petición 60; Respuesta B; Respuesta D; Respuesta C.
b) Mostrar la representación de árbol binario que sigue a Respuesta B.
7.8.
Considérese un sistema buddy en el que un determinado bloque en la asignación actual tiene la dirección 011011110000.
a) Si el bloque es de tamaño 4, ¿cuál es la dirección binaria de su bloque compañero o
buddy?
b) Si el bloque es de tamaño 16, ¿cuál es la dirección binaria de su bloque compañero o
buddy?
7.9.
Sea buddyk(x) = dirección del bloque de tamaño 2k, cuya dirección es x. Escribir una expresión general para el bloque compañero buddy de buddyk(x).
7.10. La secuencia Fibonacci se define como sigue:
Fo=0, F1=1, Fn+2= Fn+1 + Fn, n ≥ 0
a) ¿Podría utilizarse esta secuencia para establecer un sistema buddy?
b) ¿Cuál sería la ventaja de este sistema respecto al sistema buddy binario descrito en este
capítulo?
07-Capitulo 7
330
16/5/05
17:04
Página 330
Sistemas operativos. Aspectos internos y principios de diseño
7.11. Durante el curso de ejecución de un programa, el procesador incrementará en una palabra
los contenidos del registro de instrucciones (contador de programa) después de que se cargue cada instrucción, pero alterará los contenidos de dicho registro si encuentra un salto o
instrucción de llamada que provoque la ejecución de otra parte del programa. Ahora considérese la Figura 7.8. Hay dos alternativas respecto a las direcciones de la instrucción:
• Mantener una dirección relativa en el registro de instrucciones y hacer la traducción de
direcciones dinámica utilizando el registro de instrucciones como entrada. Cuando se
encuentra un salto o una llamada, la dirección relativa generada por dicho salto o llamada se carga en el registro de instrucciones.
• Mantener una dirección absoluta en el registro de instrucciones. Cuando se encuentra
un salto o una llamada, se emplea la traducción de direcciones dinámica, almacenando
los resultados en el registro de instrucciones.
¿Qué opción es preferible?
7.12. Considérese un sistema de paginación sencillo con los siguientes parámetros: 232 bytes de
memoria física; tamaño de página de 210 bytes; 216 páginas de espacio de direccionamiento
lógico.
a) ¿Cuántos bits hay en una dirección lógica?
b) ¿Cuántos bytes hay en un marco?
c) ¿Cuántos bits en la dirección física especifica el marco?
d) ¿Cuántas entradas hay en la tabla de páginas?
e) ¿Cuántos bits hay en cada entrada de la tabla de páginas? Asúmase que cada entrada de
la tabla de páginas incluye un bit válido/inválido.
7.13. Una dirección virtual a en un sistema de paginación es equivalente a un par (p,w), en el
cual p es un número de pagina y w es un número de bytes dentro de la página. Sea z el número de bytes de una página. Encontrar ecuaciones algebraicas que muestren p y w como
funciones de z y a.
7.14. Considérese un sistema de segmentación sencillo que tiene la siguiente tabla de segmentos:
Dirección inicial
Longitud (bytes)
660
248
1752
422
222
198
996
604
Para cada una de las siguientes direcciones lógicas, determina la dirección física o indica si
se produce un fallo de segmento:
a) 0,198
b) 2,156
c) 1,530
d) 3,444
e) 0,222
07-Capitulo 7
16/5/05
17:04
Página 331
Gestión de la memoria
331
7.15. Considérese una memoria en la cual se colocan segmentos contiguos S1, S2, …, Sn en su orden de creación, desde un extremo del dispositivo al otro, como se sugiere en la siguiente
figura:
S1
S2
Sn
Hueco
Cuando se crea el segmento contiguos Sn+1,se coloca inmediatamente después de Sn, incluso si algunos de los segmentos S1, S2, …, Sn ya se hubieran borrado. Cuando el límite entre
segmentos (en uso o borrados) y el hueco alcanzan el otro extremo de memoria, los segmentos en uso se compactan.
a) Mostrar que la fracción de tiempo F utilizada para la compactación cumple la siguiente
inigualdad:
F≥
1- f
t
, donde k =
-1
1 + kf
2s
donde
s = longitud media de un segmento, en palabras
t = tiempo de vida medio de un segmento, en referencias a memoria
f = fracción de la memoria que no se utiliza bajo condiciones de equilibrio
Sugerencia: Encontrar la velocidad media a la que los límites cruzan la memoria y
b) Encontrar F para f=0,2, t=1000 y s=50.
APÉNDICE 7A CARGA Y ENLACE
El primer paso en la creación de un proceso activo es cargar un programa en memoria principal y crear una imagen del proceso (Figura 7.13). Figura 7.14 muestra un escenario típico para la mayoría de
los sistemas. La aplicación está formada por varios módulos compilados o ensamblados en formato
de código objeto. Éstos son enlazados para resolver todas las referencias entre los módulos. Al mismo
tiempo, se resuelven las referencias a rutinas de biblioteca. Las rutinas de biblioteca pueden incorporarse al programa o hacerle referencia como código compartido que el sistema operativo proporciona
en tiempo de ejecución. En este apéndice, se resumen las características clave de los enlazadores y
cargadores. Por motivos de claridad en la presentación, se comienza con una descripción de la tarea
de carga cuando sólo se tiene un módulo de programa; en este caso no se requiere enlace.
CARGA
En la Figura 7.14, el cargador coloca el módulo de carga en la memoria principal, comenzando en la
ubicación x. En la carga del programa, se debe satisfacer el requisito de direccionamiento mostrado
en la Figura 7.1. En general, se pueden seguir tres técnicas diferentes:
• Carga absoluta
• Carga reubicable
• Carga dinámica en tiempo real
07-Capitulo 7
332
16/5/05
17:04
Página 332
Sistemas operativos. Aspectos internos y principios de diseño
Bloque de control de proceso
Programa
Programa
Datos
Datos
Código objeto
Pila
Imagen de proceso en
la memoria principal
Figura 7.13.
La función de carga.
Biblioteca
x
Módulo 1
Módulo 2
Montador
Módulo
de carga
Cargador
Módulo n
Memoria principal
Figura 7.14.
Un escenario de carga.
07-Capitulo 7
16/5/05
17:04
Página 333
Gestión de la memoria
333
Carga absoluta
Un cargador absoluto requiere que un módulo de carga dado debe cargarse siempre en la misma ubicación de la memoria principal. Por tanto, en el módulo de carga presentado al cargador, todas las referencias a direcciones deben ser direcciones de memoria principal específicas o absolutas. Por ejemplo, si en la Figura 7.14 x es la ubicación 1024, entonces la primera palabra de un módulo de carga
destinado para dicha región de memoria, tiene la dirección 1024.
La asignación de valores de direcciones específicas a referencias de programa dentro de un programa lo puede hacer el programador o se hacen en tiempo de compilación o ensamblado (Tabla
7.2a). La primera opción tiene varias desventajas. Primero, cada programador debe conocer la estrategia de asignación para colocar los módulos en memoria principal. Segundo, si se hace cualquier
modificación al programa que implique inserciones o borrados en el cuerpo del módulo, entonces todas las direcciones deben alterarse. Por tanto, es preferible permitir que las referencias de memoria
dentro de los programas se expresen simbólicamente, y entonces resolver dichas referencias simbólicas en tiempo de compilación o ensamblado. Esto queda reflejado en la Figura 7.15. Cada referencia
a una instrucción o elemento de datos se representa inicialmente como un símbolo. A la hora de preparar el módulo para su entrada a un cargador absoluto, el ensamblador o compilador convertirá todas
estas referencias a direcciones específicas (en este ejemplo, el módulo se carga en la dirección inicial
de 1024), tal como se muestra en la Figura 7.15b.
Tabla 7.2.
Asociación de direcciones.
(a) Cargador
Tiempo de asociación
Función
Tiempo de programación
El programador especifica directamente en el propio programa todas las
direcciones físicas reales.
Tiempo de compilación
o ensamblado
El programa contiene referencias a direcciones simbólicas y el compilador o ensamblador las convierte a direcciones físicas reales.
Tiempo de carga
El compilador o ensamblador produce direcciones relativas. El cargador
las traduce a direcciones absolutas cuando se carga el programa.
Tiempo de ejecución
El programa cargador retiene direcciones relativas. El hardware del procesador las convierte dinámicamente a direcciones absolutas.
(b) Montador
Tiempo de montaje
Función
Tiempo de programación
No se permiten referencias a programas o datos externos. El programador debe colocar en el programa el código fuente de todos los subprogramas que invoque.
Tiempo de compilación
o ensamblado
El ensamblador debe traer el código fuente de cada subrutina que se referencia y ensamblarlo como una unidad.
Creación de módulo
de carga
Todos los módulos objeto se han ensamblado utilizando direcciones relativas. Estos módulos se enlazan juntos y todas las referencias se restablecen en relación al origen del módulo de carga final.
Tiempo de carga
Las referencias externas no se resuelven hasta que el módulo de carga se
carga en memoria principal. En ese momento, los módulos con enlace dinámico referenciados se adjuntan al módulo de carga y el paquete completo se carga en memoria principal o virtual.
Tiempo de ejecución
Las referencias externas no se resuelven hasta que el procesador ejecuta
la llamada externa. En ese momento, el proceso se interrumpe y el módulo deseado se enlaza al programa que lo invoca.
07-Capitulo 7
334
16/5/05
17:04
Página 334
Sistemas operativos. Aspectos internos y principios de diseño
Direcciones
simbólicas
PROGRAMA
Direcciones
absolutas
1024
PROGRAMA
JUMP X
Direcciones
relativas
0 PROGRAMA
JUMP 1424
X
1424
LOAD Y
DATOS
Y
400
LOAD 2224
LOAD 1200
DATOS
DATOS
2224
(a) Módulo objeto
Figura 7.15.
JUMP 400
(b) Módulo de carga absoluto
1200
(c) Módulo de carga relativo
Módulos de carga absolutos y reubicables.
Carga reubicable
La desventaja de enlazar referencias de memoria a direcciones específicas antes de la carga es que el
módulo de carga resultante sólo se puede colocar en una región específica de memoria principal. Sin
embargo, cuando muchos programas comparten memoria principal, podría no ser deseable decidir al
principio en qué región de la memoria se debe cargar un módulo particular. Es mejor tomar esta decisión en tiempo de carga. Por tanto, necesitamos un módulo de carga que pueda ubicarse en cualquier
lugar de la memoria principal.
Para satisfacer este nuevo requisito, el ensamblador o compilador no produce direcciones de memoria reales (direcciones absolutas), sino direcciones relativas a algún punto conocido, tal como el
inicio del programa. Esta técnica se muestra en la Figura 7.15c. El comienzo del módulo de carga se
asigna a la dirección relativa 0, y el resto de las referencias de memoria dentro del módulo se expresan relativas al comienzo del módulo.
Con todas las referencias de la memoria expresadas en formato relativo, colocar el módulo en el
lugar adecuado se convierte en una tarea simple para el cargador. Si el módulo se carga al comienzo
de la ubicación x, entonces el cargador debe simplemente añadir x a cada referencia de la memoria
cuando carga el módulo en memoria. Para asistir en esta tarea, el módulo cargado debe incluir información que dice el cargador donde están las referencias de memoria y cómo se van a interpretar (normalmente relativo al origen del programa, pero también es posible relativo a algún otro punto del
programa, tal como la ubicación actual). El compilador o ensamblador prepara este conjunto de información, lo que se denomina normalmente diccionario de reubicación.
Carga dinámica en tiempo real
Los cargadores reubicables son comunes y proporcionan beneficios obvios si se comparan con los
cargadores absolutos. Sin embargo, en un entorno de multiprogramación, incluso en uno que no de-
07-Capitulo 7
16/5/05
17:04
Página 335
Gestión de la memoria
335
penda de la memoria virtual, el esquema de carga reubicable no es adecuado. A lo largo del libro, nos
hemos referido a la necesidad de traer y quitar imágenes de procesos de la memoria principal a fin de
maximizar la utilización del procesador. Para maximizar la utilización de la memoria principal, sería
importante poder intercambiar las imágenes de los procesos en diferentes localizaciones en diferentes
momentos. Por tanto, un programa, una vez cargado, puede intercambiarse a disco y a memoria en diferentes ubicaciones. Esto sería imposible si las referencias de la memoria se limitan a direcciones
absolutas en tiempo de carga inicial.
La alternativa es posponer el cálculo de una dirección absoluta hasta que se necesite realmente en
tiempo de ejecución. Para este propósito, el módulo de carga se carga en la memoria principal con todas las referencias de la memoria en formato relativo (Figura 7.15c). Hasta que una instrucción no se
ejecuta realmente, no se calcula la dirección absoluta. Para asegurar que esta función no degrada el
rendimiento, la realiza el hardware de procesador especial en lugar de llevarse a cabo por software. El
hardware se describe en la Sección 7.2.
El cálculo dinámico de direcciones proporciona una flexibilidad total. Un programa se carga en
cualquier región de la memoria principal. A continuación, la ejecución del programa se puede interrumpir y el programa se puede intercambiar entre disco y memoria, para posteriormente intercambiarse en una localización diferente.
ENLACE
La función de un montador es tomar como entrada una colección de módulos objeto y producir un
módulo de carga, formado por un conjunto integrado de programa y módulos de datos, que se pasará
al cargador. En cada módulo objeto, podría haber referencias a direcciones de otros módulos. Cada
una de estas referencias sólo se puede expresar simbólicamente en un módulo objeto no enlazado. El
montador crea un único módulo de carga que se une de forma contigua a todos los módulos objeto.
Cada referencia entre módulos debe cambiarse: una dirección simbólica debe convertirse en una referencia a una ubicación dentro del módulo de carga. Por ejemplo, el módulo A en la Figura 7.16a contiene una invocación a un procedimiento del módulo B. Cuando estos módulos se combinan en el
módulo de carga, esta referencia simbólica al módulo B se cambia por una referencia específica a la
localización del punto de entrada de B dentro del módulo de carga.
Editor de enlace
La naturaleza de este enlace de direcciones dependerá del tipo de módulo de carga que se cree y
cuando se lleve a cabo el proceso de enlace (Tabla 7.2b). Si se desea un módulo de carga reubicable,
como suele ser lo habitual, el enlace se hace normalmente de la siguiente forma. Cada módulo objeto
compilado o ensamblado se crea con referencias relativas al comienzo del módulo objeto. Todos estos
módulos se colocan juntos en un único módulo de carga reubicable con todas las referencias relativas
al origen del módulo de carga. Este módulo se puede utilizar como entrada para la carga reubicable o
carga dinámica en tiempo de ejecución.
Un montador que produce un módulo de carga reubicable se denomina frecuentemente editor de
enlace. La Figura 7.16 ilustra la función del editor de enlace.
Montador dinámico
Al igual que con la carga, también es posible posponer algunas funciones relativas al enlace. El término enlace dinámico se utiliza para denominar la práctica de posponer el enlace de algunos módulos
07-Capitulo 7
336
16/5/05
17:04
Página 336
Sistemas operativos. Aspectos internos y principios de diseño
Direcciones
relativas
0
Módulo A
Módulo A
Referencia
externa al
módulo B
CALL B;
JSR "L"
Longitud L
L 1 Return
L
Módulo B
Return
Módulo B
JSR "L M"
CALL C;
Longitud M
L M 1 Return
LM
Módulo C
Return
Módulo C
Longitud N
L M N 1 Return
(b) Módulo de carga
Return
(a) Módulos objeto
Figura 7.16.
La función de montaje.
externos hasta después de que el módulo de carga se cree. Por tanto, el módulo de carga contiene referencias sin resolver a otros programas. Estas referencias se pueden resolver o bien en tiempo de
carga o bien en tiempo de ejecución.
Para el enlace dinámico en tiempo de carga, se deben seguir los siguientes pasos. El módulo de
carga (módulo de aplicación) debe llevarse a memoria para cargarlo. Cualquier referencia a un módulo externo (módulo destino) provoca que el cargador encuentre al módulo destino, lo cargue y altere
la referencia a una dirección relativa a la memoria desde el comienzo del módulo de aplicación. Hay
varias ventajas de esta técnica frente al enlace estático:
• Se facilita incorporar versiones modificadas o actualizadas del módulo destino, el cual puede
ser una utilidad del sistema operativo o algunas otras rutinas de propósito general. Con el enlace estático, un cambio a ese módulo requeriría el reenlace del módulo de aplicación completo.
No sólo es ineficiente, sino que puede ser imposible en algunas circunstancias. Por ejemplo,
en el campo de los ordenadores personales, la mayoría del software comercial es entregado en
el formato del módulo de carga; no se entregan las versiones fuente y objeto.
• Tener el código destino en un fichero con enlace dinámico facilita el camino para compartir
código de forma automática. El sistema operativo puede reconocer que más de una aplicación
utiliza el mismo código destino porque carga y enlaza dicho código. Puede utilizar esta información para cargar una única copia del código destino y enlazarlo a ambas aplicaciones, en lugar de tener que cargar una copia para cada aplicación.
07-Capitulo 7
16/5/05
17:04
Página 337
Gestión de la memoria
337
• Facilita a los desarrolladores de software independientes extender la funcionalidad de un sistema operativo ampliamente utilizado, tal como Linux. Un desarrollador puede implementar una
nueva función que puede ser útil a una variedad de aplicaciones y empaquetarla como un módulo de enlace dinámico.
Con enlace dinámico en tiempo de ejecución, algunos de los enlaces son pospuestos hasta el
tiempo de ejecución. Las referencias externas a los módulos destino quedan en el programa cargado.
Cuando se realiza una llamada a un módulo ausente, el sistema operativo localiza el módulo, lo carga
y lo enlaza al módulo llamante.
Se ha visto que la carga dinámica permite que se pueda mover un módulo de carga entero; sin
embargo, la estructura del módulo es estática, permaneciendo sin cambios durante la ejecución del
proceso y de una ejecución a otra. Sin embargo, en algunos casos, no es posible determinar antes de
la ejecución qué módulos objeto se necesitarán. Esta situación es tipificada por las aplicaciones de
procesamiento de transacciones, como las de un sistema de reserva de vuelos o una aplicación bancaria. La naturaleza de la transacción especifica qué módulos de programa se requieren, y éstos son cargados y enlazados con el programa principal. La ventaja del uso de un montador dinámico es que no
es necesario asignar memoria a unidades del programa a menos que dichas unidades se referencien.
Esta capacidad se utiliza para dar soporte a sistemas de segmentación.
Una mejora adicional es posible: una aplicación no necesita conocer los nombres de todos los
módulos o puntos de entrada que pueden llamarse. Por ejemplo, puede escribirse un programa de dibujo para trabajar con una gran variedad de trazadores, cada uno de los cuales se gestiona por un controlador diferente. La aplicación puede aprender el nombre del trazador que está actualmente instalado en el sistema por otro proceso o buscando en un fichero de configuración. Esto permite al usuario
de la aplicación instalar un nuevo trazador que no exista en el tiempo en que la aplicación se escribió.
07-Capitulo 7
16/5/05
17:04
Página 338
08-Capitulo 8
12/5/05
16:23
Página 339
CAPÍTULO
8
Memoria virtual
8.1.
Hardware y estructuras de control
8.2.
Software del sistema operativo
8.3.
Gestión de la memoria de UNIX y Solaris
8.4.
Gestión de la memoria en Linux
8.5.
Gestión de la memoria en Windows
8.6.
Resumen
8.7.
Lectura recomendada y páginas web
8.8.
Términos clave, cuestiones de repaso, y problemas
Apéndice 8A Tablas Hash
08-Capitulo 8
340
12/5/05
16:23
Página 340
Sistemas operativos. Aspectos internos y principios de diseño
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
En el Capítulo 7 se vieron los conceptos de paginación y segmentación y se analizaron sus limitaciones. Ahora vamos a entrar a discutir el concepto de memoria virtual. Un análisis de este concepto es
complicado por el hecho de que la gestión de la memoria es una interacción compleja entre el hardware del procesador y el sistema operativo. Nos centraremos primero en los aspectos hardware de
la memoria virtual, observando el uso de la paginación, segmentación, y combinación de paginación y segmentación. Después veremos los aspectos relacionados con el diseño de los servicios de
la memoria virtual en el sistema operativo.
◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆
8.1. HARDWARE Y ESTRUCTURAS DE CONTROL
C
omparando la paginación sencilla y la segmentación sencilla, por un lado, tenemos una distinción entre particionamiento estático y dinámico, y por otro, tenemos los fundamentos de comienzo de la gestión de la memoria. Las dos características de la paginación y la segmentación que son la clave de este comienzo son:
1. Todas las referencias a la memoria dentro un proceso se realizan a direcciones lógicas, que se
traducen dinámicamente en direcciones físicas durante la ejecución. Esto significa que un proceso puede ser llevado y traído a memoria de forma que ocupe diferentes regiones de la memoria principal en distintos instantes de tiempo durante su ejecución.
2. Un proceso puede dividirse en varias porciones (páginas o segmentos) y estas porciones no
tienen que estar localizadas en la memoria de forma contigua durante la ejecución. La combinación de la traducción de direcciones dinámicas en ejecución y el uso de una tabla de páginas
o segmentos lo permite.
Ahora veamos cómo comenzar con la memoria dinámica. Si las dos características anteriores
se dan, entonces es necesario que todas las páginas o todos los segmentos de un proceso se encuentren en la memoria principal durante la ejecución. Si la porción (segmento o página) en la que se
encuentra la siguiente instrucción a buscar está y si la porción donde se encuentra la siguiente dirección de datos que se va a acceder también está, entonces al menos la siguiente instrucción se podrá
ejecutar.
Consideremos ahora cómo se puede realizar esto. De momento, vamos a hablar en términos generales, y usaremos el término porción para referirnos o bien a una página o un segmento, dependiendo si estamos empleando paginación o segmentación. Supongamos que se tiene que traer un nuevo
proceso de memoria. El sistema operativo comienza trayendo únicamente una o dos porciones, que
incluye la porción inicial del programa y la porción inicial de datos sobre la cual acceden las primeras
instrucciones acceden. Esta parte del proceso que se encuentra realmente en la memoria principal
para, cualquier instante de tiempo, se denomina conjunto residente del proceso. Cuando el proceso
está ejecutándose, las cosas ocurren de forma suave mientras que todas las referencias a la memoria
se encuentren dentro del conjunto residente. Usando una tabla de segmentos o páginas, el procesador
siempre es capaz de determinar si esto es así o no. Si el procesador encuentra una dirección lógica
que no se encuentra en la memoria principal, generará una interrupción indicando un fallo de acceso
a la memoria. El sistema operativo coloca al proceso interrumpido en un estado de bloqueado y toma
el control. Para que la ejecución de este proceso pueda reanudarse más adelante, el sistema operativo
necesita traer a la memoria principal la porción del proceso que contiene la dirección lógica que ha
causado el fallo de acceso. Con este fin, el sistema operativo realiza una petición de E/S, una lectura
a disco. Después de realizar la petición de E/S, el sistema operativo puede activar otro proceso que se
ejecute mientras el disco realiza la operación de E/S. Una vez que la porción solicitada se ha traído a
08-Capitulo 8
12/5/05
16:23
Página 341
Memoria virtual
341
la memoria principal, una nueva interrupción de E/S se lanza, dando control de nuevo al sistema operativo, que coloca al proceso afectado de nuevo en el estado Listo.
Al lector se le puede ocurrir cuestionar la eficiencia de esta maniobra, en la cual a un proceso que
se puede estar ejecutando resulta necesario interrumpirlo sin otro motivo que el hecho de que no se
ha llegado a cargar todas las porciones necesarias de dicho proceso. De momento, vamos a posponer
esta cuestión con la garantía de que la eficiencia es verdaderamente posible. En su lugar, vamos a
ponderar las implicaciones de nuestra nueva estrategia. Existen dos implicaciones, la segunda más
sorprendente que la primera, y ambas dirigidas a mejorar la utilización del sistema:
1. Pueden mantenerse un mayor número de procesos en memoria principal. Debido a que
sólo vamos a cargar algunas de las porciones de los procesos a ejecutar, existe espacio para
más procesos. Esto nos lleva a una utilización más eficiente del procesador porque es más probable que haya al menos uno o más de los numerosos procesos que se encuentre en el estado
Listo, en un instante de tiempo concreto.
2. Un proceso puede ser mayor que toda la memoria principal. Se puede superar una de las
restricciones fundamentales de la programación. Sin el esquema que hemos estado discutiendo, un programador debe estar realmente atento a cuánta memoria está disponible. Si el programa que está escribiendo es demasiado grande, el programador debe buscar el modo de estructurar el programa en fragmentos que pueden cargarse de forma separada con un tipo de
estrategia de superposición (overlay). Con la memoria virtual basada en paginación o segmentación, este trabajo se delega al sistema operativo y al hardware. En lo que concierne al programador, él está trabajando con una memoria enorme, con un tamaño asociado al almacenamiento en disco. El sistema operativo automáticamente carga porciones de un proceso en la
memoria principal cuando éstas se necesitan.
Debido a que un proceso ejecuta sólo en la memoria principal, esta memoria se denomina memoria real. Pero el programador o el usuario perciben una memoria potencialmente mucho más grande
—la cual se encuentra localizada en disco. Esta última se denomina memoria virtual. La memoria
virtual permite una multiprogramación muy efectiva que libera al usuario de las restricciones excesivamente fuertes de la memoria principal. La Tabla 8.1 recoge las características de la paginación y la
segmentación, con y sin el uso de la memoria virtual.
PROXIMIDAD Y MEMORIA VIRTUAL
Los beneficios de la memoria virtual son atractivos, ¿pero el esquema es verdaderamente práctico?
En su momento, hubo un importante debate sobre este punto, pero la experiencia de numerosos sistemas operativos ha demostrado, más allá de toda duda, que la memoria virtual realmente funciona. La
memoria virtual, basada en paginación o paginación más segmentación, se ha convertido, en la actualidad, en una componente esencial de todos los sistemas operativos contemporáneos.
Para entender cuál es el aspecto clave, y por qué la memoria virtual era la causa de dicho debate,
examinemos de nuevo las tareas del sistema operativo relacionadas con la memoria virtual. Se va a
considerar un proceso de gran tamaño, consistente en un programa largo más un gran número de vectores de datos. A lo largo de un corto periodo de tiempo, la ejecución se puede acotar a una pequeña
sección del programa (por ejemplo, una subrutina) y el acceso a uno o dos vectores de datos únicamente. Si es así, sería verdaderamente un desperdicio cargar docenas de porciones de dicho proceso
cuando sólo unas pocas porciones se usarán antes de que el programa se suspenda o se mande a zona
de intercambio o swap. Se puede hacer un mejor uso de la memoria cargando únicamente unas pocas
porciones. Entonces, si el programa salta a una destrucción o hace referencia a un dato que se en-
Programa dividido en páginas
por el compilador o el sistema de
gestión de la memoria
Fragmentación interna dentro de
los marcos
Sin fragmentación externa
El sistema operativo debe
mantener una tabla de páginas
por cada proceso mostrando en
el marco que se encuentra cada
página ocupada
El sistema operativo debe
mantener una lista de marcos
libres
El procesador utiliza el número
de página, desplazamiento para
calcular direcciones absolutas
No se necesita mantener todas
las páginas del proceso en los
marcos de la memoria principal
para que el proceso se ejecute.
Las páginas se pueden leer bajo
demanda
Programa dividido en páginas
por el compilador o el sistema de
gestión de la memoria
Fragmentación interna dentro de
los marcos
Sin fragmentación externa
El sistema operativo debe
mantener una tabla de páginas
por cada proceso mostrando en
el marco que se encuentra cada
página ocupada
El sistema operativo debe
mantener una lista de marcos
libres
El procesador utiliza el número
de página, desplazamiento para
calcular direcciones absolutas
Todas las páginas del proceso
deben encontrarse en la
memoria principal para que el
proceso se pueda ejecutar, salvo
que se utilicen solapamientos
(overlays)
Todos los segmentos del proceso
deben encontrarse en la
memoria principal para que el
proceso se pueda ejecutar, salvo
que se utilicen solapamientos
(overlays)
El procesador utiliza el número
de segmento, desplazamiento
para calcular direcciones
absolutas
El sistema operativo debe
mantener una lista de huecos en
la memoria principal
El sistema operativo debe
mantener una tabla de
segmentos por cada proceso
mostrando la dirección de carga
y la longitud de cada segmento
Fragmentación externa
Sin fragmentación interna
Los segmentos de programa se
especifican por el programador al
compilador (por ejemplo, la
decisión se toma por parte el
programador)
Memoria principal no
particionada
Segmentación sencilla
La lectura de un segmento a
memoria principal puede requerir
la escritura de uno o más
segmentos a disco
No se necesitan mantener todos
los segmentos del proceso en la
memoria principal para que el
proceso se ejecute. Los
segmentos se pueden leer bajo
demanda
El procesador utiliza el número
de segmento, desplazamiento
para calcular direcciones
absolutas
El sistema operativo debe
mantener una lista de huecos en
la memoria principal
El sistema operativo debe
mantener una tabla de
segmentos por cada proceso
mostrando la dirección de carga
y la longitud de cada segmento
Fragmentación externa
Sin fragmentación interna
Los segmentos de programa se
especifican por el programador al
compilador (por ejemplo, la
decisión se toma por parte el
programador)
Memoria principal no
particionada
Segmentación con memoria virtual
16:23
La lectura de una página a
memoria principal puede requerir
la escritura de una página a disco
Memoria principal particionada
en fragmentos pequeños de un
tamaño fijo llamados marcos
Paginación con memoria virtual
12/5/05
Memoria principal particionada
en fragmentos pequeños de un
tamaño fijo llamados marcos
Paginación sencilla
Características de la paginación y la segmentación.
342
Tabla 8.1.
08-Capitulo 8
Página 342
Sistemas operativos. Aspectos internos y principios de diseño
08-Capitulo 8
12/5/05
16:23
Página 343
Memoria virtual
343
cuentra en una porción de memoria que no está en la memoria principal, entonces se dispara un fallo.
Éste indica al sistema operativo que debe conseguir la porción deseada.
Así, en cualquier momento, sólo unas pocas porciones de cada proceso se encuentran en memoria, y por tanto se pueden mantener más procesos alojados en la misma. Además, se ahorra tiempo
porque las porciones del proceso no usadas no se expulsarán de la memoria a swap y de swap a la
memoria. Sin embargo, el sistema operativo debe ser inteligente a la hora de manejar este esquema.
En estado estable, prácticamente toda la memoria principal se encontrará ocupada con porciones de
procesos, de forma que el procesador y el sistema operativo tengan acceso directo al mayor número
posible de procesos. Así, cuando el sistema operativo traiga una porción a la memoria, debe expulsar
otra. Si elimina una porción justo antes de que vaya a ser utilizada, deberá recuperar dicha porción de
nuevo casi de forma inmediata. Un abuso de esto lleva a una condición denominada trasiego (thrashing): el sistema consume la mayor parte del tiempo enviando y trayendo porciones de swap en lugar
de ejecutar instrucciones. Evitar el trasiego fue una de las áreas de investigación principales en la
época de los años 70 que condujo una gran variedad de algoritmos complejos pero muy efectivos. En
esencia, el sistema operativo trata de adivinar, en base a la historia reciente, qué porciones son menos
probables de ser utilizadas en un futuro cercano.
Este razonamiento se basa en la creencia del principio de proximidad, que se presentó en el Capítulo 1 (véase especialmente el Apéndice 1A). Para resumir, el principio de proximidad indica que
las referencias al programa y a los datos dentro de un proceso tienden a agruparse. Por tanto, se resume que sólo unas pocas porciones del proceso se necesitarán a lo largo de un periodo de tiempo corto. También, es posible hacer suposiciones inteligentes sobre cuáles son las porciones del proceso que
se necesitarán en un futuro próximo, para evitar este trasiego.
Una forma de confirmar el principio de proximidad es observar el rendimiento de los procesos en
un entorno de memoria virtual. En la Figura 8.1 se muestra un famoso diagrama que ilustra de forma
clara los principios de proximidad [HATF 72]. Nótese que, durante el tiempo de vida de un proceso
las referencias se encuentran acotadas a un subconjunto de sus páginas.
Así pues, vemos que el principio de proximidad sugiere que el esquema de memoria virtual debe
funcionar. Para que la memoria virtual resulte práctica y efectiva, se necesitan dos ingredientes. Primero, debe existir un soporte hardware para el esquema de paginación y/o segmentación. Segundo, el
sistema operativo debe incluir código para gestionar el movimiento de páginas y/o segmentos entre la
memoria secundaria y la memoria principal. En esta sección, examinaremos los aspectos hardware y
veremos cuáles son las estructuras de control necesarias, que se crearán y mantendrán por parte del
sistema operativo pero que son usadas por el hardware de gestión de la memoria. Se examinarán los
aspectos correspondientes al sistema operativo en la siguiente sección.
PAGINACIÓN
El término memoria virtual se asocia habitualmente con sistemas que emplean paginación, a pesar de
que la memoria virtual basada en segmentación también se utiliza y será tratada más adelante. El uso
de paginación para conseguir memoria virtual fue utilizado por primera vez en el computador Atlas
[KILB62] y pronto se convirtió en una estrategia usada en general de forma comercial.
En la presentación de la paginación sencilla, indicamos que cada proceso dispone de su propia tabla de páginas, y que todas las páginas se encuentran localizadas en la memoria principal. Cada entrada en la tabla de páginas consiste en un número de marco de la correspondiente página en la memoria
principal. Para la memoria virtual basada en el esquema de paginación también se necesita una tabla
de páginas. De nuevo, normalmente se asocia una única tabla de páginas a cada proceso. En este
caso, sin embargo, las entradas de la tabla de páginas son más complejas (Figura 8.2a). Debido a que
344
12/5/05
16:23
Página 344
Sistemas operativos. Aspectos internos y principios de diseño
34
32
30
28
26
24
22
Números de página
08-Capitulo 8
20
18
Tiempo de ejecución
Figura 8.1.
Comportamiento de la paginación.
sólo algunas de las páginas de proceso se encuentran en la memoria principal, se necesita que cada
entrada de la tabla de páginas indique si la correspondiente página está presente (P) en memoria principal o no. Si el bit indica que la página está en memoria, la entrada también debe indicar el número
de marco de dicha página.
La entrada de la tabla de páginas incluye un bit de modificado (M), que indica si los contenidos
de la correspondiente página han sido alterados desde que la página se cargó por última vez en la memoria principal. Si no había ningún cambio, no es necesario escribir la página cuando llegue el momento de reemplazarla por otra página en el marco de página que actualmente ocupa. Pueden existir
también otros bits de control en estas entradas. Por ejemplo, si la protección y compartición se gestiona a nivel de página, se necesitarán también los bits para este propósito.
Estructura de la tabla de páginas. El mecanismo básico de lectura de una palabra de la memoria
implica la traducción de la dirección virtual, o lógica, consistente en un número de página y un des-
08-Capitulo 8
12/5/05
16:23
Página 345
Memoria virtual
345
Dirección virtual
Número de página
Desplazamiento
Entrada de la tabla de páginas
Número de marco
P M Otros bits de control
(a) Únicamente paginación
Dirección virtual
Desplazamiento
Segmento de página
Entrada de la tabla de segmentos
Longitud
P M Otros bits de control
Comienzo de segmento
(b) Únicamente segmentación
Dirección virtual
Segmento de página
Número de página
Desplazamiento
Entrada de la tabla de segmentos
Bits de control
Comienzo de segmento
Longitud
Entrada de la tabla de páginas
P M Otros bits de control
Número de marco
P bit de presente
M bit de modificado
(c) Combinación de segmentación y paginación
Figura 8.2.
Formatos típicos de gestión de memoria.
plazamiento, a la dirección física, consistente en un número de marco y un desplazamiento, usando
para ello la tabla de páginas. Debido a que la tabla de páginas es de longitud variable dependiendo
del tamaño del proceso, no podemos suponer que se encuentra almacenada en los registros. En lugar
de eso, debe encontrarse en la memoria principal para poder ser accedida. La Figura 8.3 sugiere una
implementación hardware. Cuando un proceso en particular se encuentra ejecutando, un registro contiene la dirección de comienzo de la tabla de páginas para dicho proceso. El número de página de la
dirección virtual se utiliza para indexar esa tabla y buscar el correspondiente marco de página. Éste,
combinado con la parte de desplazamiento de la dirección virtual genera la dirección real deseada.
Normalmente, el campo correspondiente al número de página es mayor que el campo correspondiente
al número de marco de página (n > m).
En la mayoría de sistemas, existe una única tabla de página por proceso. Pero cada proceso
puede ocupar una gran cantidad de memoria virtual. Por ejemplo, en la arquitectura VAX, cada
proceso puede tener hasta 231 = 2 Gbytes de memoria virtual. Usando páginas de 29 = 512 bytes,
que representa un total de 222 entradas de tabla de página por cada proceso. Evidentemente, la cantidad de memoria demandada por las tablas de página únicamente puede ser inaceptablemente
grande. Para resolver este problema, la mayoría de esquemas de memoria virtual almacena las ta-
346
12/5/05
16:23
Página 346
Sistemas operativos. Aspectos internos y principios de diseño
blas de páginas también en la memoria virtual, en lugar de en la memoria real. Esto representa que
las tablas de páginas están sujetas a paginación igual que cualquier otra página. Cuando un proceso
está en ejecución, al menos parte de su tabla de páginas debe encontrarse en memoria, incluyendo
la entrada de tabla de páginas de la página actualmente en ejecución. Algunos procesadores utilizan un esquema de dos niveles para organizar las tablas de páginas de gran tamaño. En este esquema, existe un directorio de páginas, en el cual cada entrada apuntaba a una tabla de páginas. De
esta forma, si la extensión del directorio de páginas es X, y si la longitud máxima de una tabla de
páginas es Y, entonces un proceso consistirá en hasta X ≥ Y páginas. Normalmente, la longitud máxima de la tabla de páginas se restringe para que sea igual a una página. Por ejemplo, el procesador
Pentium utiliza esta estrategia.
La Figura 8.4 muestra un ejemplo de un esquema típico de dos niveles que usa 32 bits para la dirección. Asumimos un direccionamiento a nivel de byte y páginas de 4 Kbytes (212), por tanto el espacio de direcciones virtuales de 4 Gbytes (232) se compone de 220 páginas. Si cada una de estas páginas se referencia por medio de una entrada la tabla de páginas (ETP) de 4-bytes, podemos crear una
tabla de página de usuario con 220 la ETP que requiere 4 Mbytes (222 bytes). Esta enorme tabla de páginas de usuario, que ocupa 210 páginas, puede mantenerse en memoria virtual y hacerse referencia
desde una tabla de páginas raíz con 210 PTE que ocuparía 4 Kbytes (212) de memoria principal. La Figura 8. 5 muestra los pasos relacionados con la traducción de direcciones para este esquema. La página raíz siempre se mantiene en la memoria principal. Los primeros 10 bits de la dirección virtual se
pueden usar para indexar en la tabla de páginas raíz para encontrar la ETP para la página en la que
está la tabla de páginas de usuario. Si la página no está en la memoria principal, se produce un fallo
de página. Si la página está en la memoria principal, los siguientes 10 bits de la dirección virtual se
usan para indexar la tabla de páginas de usuario para encontrar la ETP de la página a la cual se hace
referencia desde la dirección virtual original.
Dirección física
Dirección virtual
Nro. Página # Desplazamiento
Nro. Marco # Desplazamiento
Registro
Puntero tabla
de páginas
n bits
Tabla de páginas
Programa
Nro. Página#
08-Capitulo 8
m bits
Desplazamiento
Nro. Marco #
Mecanismo de paginación
Figura 8.3.
Marco de
página
Memoria principal
Traducción de direcciones en un sistema con paginación.
08-Capitulo 8
12/5/05
16:23
Página 347
Memoria virtual
347
4-kbyte de tabla de
páginas de raíz
4-Mbyte de tabla de
páginas de usuario
4-Gbyte de espacio de
direcciones de usuario
Figura 8.4.
Una tabla de páginas jerárquica de dos niveles.
Dirección virtual
10 bits
10 bits
Nro. Marco # Desplazamiento
12 bits
Puntero tabla
de páginas raíz
Tabla de páginas raíz
(contiene en 1024 PTE)
Programa
Figura 8.5.
Marco de
página
Tabla de páginas de 4
Kbytes (contiene en
1024 ETP)
Mecanismo de paginación
Memoria principal
Traducción de direcciones en un sistema de paginación de dos niveles.
Tabla de páginas invertida. Una desventaja del tipo de tablas de páginas que hemos visto es que
su tamaño es proporcional al espacio de direcciones virtuales.
Una estrategia alternativa al uso de tablas de páginas de uno o varios niveles es el uso de la estructura de tabla de páginas invertida. Variaciones de esta estrategia se han usado en arquitecturas
como PowerPC, UltraSPARC, e IA-64. La implementación del sistema operativo Mach sobre RT-PC
también la usa.
08-Capitulo 8
348
12/5/05
16:23
Página 348
Sistemas operativos. Aspectos internos y principios de diseño
Dirección virtual
n bits
Nro. Página # Desplazamiento
Bits de
control
n bits
Función
hasd
m bits
Nro.
ID
Página # proceso
Cadena
0
i
j
2m 1
Tabla de páginas invertida
(una entrada por cada
marco de memoria física)
Figura 8.6.
Nro. Segmento # Desplazamiento
m bits
Dirección real
Estructura de tabla de páginas invertida.
En esta estrategia, la parte correspondiente al número de página de la dirección virtual se referencia por medio de un valor hash usando una función hash sencilla1. El valor hash es un puntero
para la tabla de páginas invertida, que contiene las entradas de tablas de página. Hay una entrada
en la tabla de páginas invertida por cada marco de página real en lugar de uno por cada página virtual. De esta forma, lo único que se requiere para estas tablas de página siempre es una proporción
fija de la memoria real, independientemente del número de procesos o de las páginas virtuales soportadas. Debido a que más de una dirección virtual puede traducirse en la misma entrada de la tabla hash, una técnica de encadenamiento se utiliza para gestionar el desbordamiento. Las técnicas
de hashing proporcionan habitualmente cadenas que no son excesivamente largas —entre una y
dos entradas. La estructura de la tabla de páginas se denomina invertida debido a que se indexan
sus entradas de la tabla de páginas por el número de marco en lugar de por el número de página
virtual.
La Figura 8.6 muestra una implementación típica de la técnica de tabla de páginas invertida. Para
un tamaño de memoria física de 2m marcos, la tabla de páginas invertida contiene 2m entradas, de forma que la entrada en la posición i-esima se refiere al marco i. La entrada en la tabla de páginas incluye la siguiente información:
• Número de página. Esta es la parte correspondiente al número de página de la dirección
virtual.
1
Véase Apéndice 8A para explicaciones sobre hashing.
08-Capitulo 8
12/5/05
16:23
Página 349
Memoria virtual
349
• Identificador del proceso. El proceso que es propietario de está página. La combinación de
número de página e identificador del proceso identifica a una página dentro del espacio de direcciones virtuales de un proceso en particular.
• Bits de control. Este campo incluye los flags, como por ejemplo, válido, referenciado, y modificado; e información de protección y cerrojos.
• Puntero de la cadena. Este campo es nulo (indicado posiblemente por un bit adicional) si no
hay más entradas encadenadas en esta entrada. En otro caso, este campo contiene el valor del
índice (número entre 0 y 2m-1) de la siguiente entrada de la cadena.
En este ejemplo, la dirección virtual incluye un número de página de n bits, con n > m. La función hash traduce el número de página n bits en una cantidad de m bits, que se utiliza para indexar en
la tabla de páginas invertida.
Buffer de traducción anticipada. En principio, toda referencia a la memoria virtual puede causar
dos accesos a memoria física: uno para buscar la entrada la tabla de páginas apropiada y otro para
buscar los datos solicitados. De esa forma, un esquema de memoria virtual básico causaría el efecto
de duplicar el tiempo de acceso a la memoria. Para solventar este problema, la mayoría de esquemas
de la memoria virtual utilizan una cache especial de alta velocidad para las entradas de la tabla de página, habitualmente denominada buffer de traducción anticipada (translation lookaside buffer TLB)2. Esta cache funciona de forma similar a una memoria cache general (véase Capítulo 1) y contiene aquellas entradas de la tabla de páginas que han sido usadas de forma más reciente. La organización del hardware de paginación resultante se ilustra en la Figura 8.7. Dada una dirección virtual, el
procesador primero examina la TLB, si la entrada de la tabla de páginas solicitada está presente
(acierto en TLB), entonces se recupera el número de marco y se construye la dirección real. Si la entrada de la tabla de páginas solicitada no se encuentra (fallo en la TLB), el procesador utiliza el número de página para indexar la tabla de páginas del proceso y examinar la correspondiente entrada de la
tabla de páginas. Si el bit de presente está puesto a 1, entonces la página se encuentra en memoria
principal, y el procesador puede recuperar el número de marco desde la entrada de la tabla de páginas
para construir la dirección real. El procesador también autorizará la TLB para incluir esta nueva entrada de tabla de páginas. Finalmente, si el bit presente no está puesto a 1, entonces la página solicitada no se encuentra en la memoria principal y se produce un fallo de acceso memoria, llamado fallo
de página. En este punto, abandonamos el dominio del hardware para invocar al sistema operativo, el
cual cargará la página necesaria y actualizada de la tabla de páginas.
La Figura 8.8 muestra un diagrama de flujo del uso de la TLB. Este diagrama de flujo muestra
como si una página solicitada no se encuentra en la memoria principal, una interrupción de fallo de
página hace que se invoque a la rutina de tratamiento de dicho fallo de página. Para mantener la simplicidad de este diagrama, no se ha mostrado el hecho de que el sistema operativo pueda activar otro
proceso mientras la operación de E/S sobre disco se está realizando. Debido al principio de proximidad, la mayoría de referencias a la memoria virtual se encontrarán situadas en una página recientemente utiliza y por tanto, la mayoría de referencias invocarán una entrada de la tabla de páginas que
se encuentra en la cache. Los estudios sobre la TLB de los sistemas VAX han demostrado que este
esquema significa una importante mejora del rendimiento [CLAR85, SATY81].
Hay numerosos detalles adicionales relativos a la organización real de la TLB. Debido a que la
TLB sólo contiene algunas de las entradas de toda la tabla de páginas, no es posible indexar simple-
2
N. de T. Aunque la traducción más apropiada del translation lookaside buffer quizás sea la de buffer de traducción anticipada,
en la literatura en castellano se utilizan las siglas TLB de forma generalizada para describir dicha memoria. Por ello, y para no causar
confusión con otros textos, a lo largo del presente libro utilizaremos dichas siglas para referirnos a ella.
08-Capitulo 8
350
12/5/05
16:23
Página 350
Sistemas operativos. Aspectos internos y principios de diseño
Memoria
secundaria
Memoria principal
Dirección virtual
Nro. Página # Desplazamiento
TLB
Acierto TLB
Desplazamiento
Cargar
página
Tabla de páginas
Fallo TLB
Nro. Marco # Desplazamiento
Dirección real
Fallo de página
Figura 8.7.
Uso de la TLB.
mente la TLB por medio de número página. En lugar de eso, cada entrada de la TLB debe incluir un
número de página así como la entrada de la tabla de páginas completa. El procesador proporciona un
hardware que permite consultar simultáneamente varias entradas para determinar si hay una conciencia sobre un número de página. Esta técnica se denomina resolución asociativa (asociative mapping)
que contrasta con la resolución directa, o indexación, utilizada para buscar en la tabla de páginas en
la Figura 8.9. El diseño de la TLB debe considerar también la forma mediante la cual las entradas se
organizan en ella y qué entrada se debe reemplazar cuando se necesite traer una nueva entrada. Estos
aspectos deben considerarse en el diseño de la cache hardware. Este punto no se contempla en este libro; el lector podrá consultar el funcionamiento del diseño de una cache para más detalle en, por
ejemplo, [STAL03].
Para concluir, el mecanismo de memoria virtual debe interactuar con el sistema de cache (no la
cache de TLB, sino la cache de la memoria principal). Esto se ilustra en la Figura 8.10. Una dirección
virtual tendrá generalmente el formato número de página, desplazamiento. Primero, el sistema de memoria consulta la TLB para ver si se encuentra presente una entrada de tabla de página que coincide.
Si es así, la dirección real (física) se genera combinando el número de marco con el desplazamiento.
Si no, la entrada se busca en la tabla de páginas. Una vez se ha generado la dirección real, que mantiene el formato de etiqueta (tag)3 y resto (remainder), se consulta la cache para ver si el bloque que
contiene esa palabra se encuentra ahí. Si es así, se le devuelve a la CPU. Si no, la palabra se busca en
la memoria principal.
3
Véase en la Figura 1.17. Normalmente, una etiqueta son los bits situados más a la izquierda de una dirección real. Una vez
más, para un estudio más detallado sobre las caches, se refiere al lector a [STAL03].
08-Capitulo 8
12/5/05
16:23
Página 351
Memoria virtual
351
Comienzo
Retorno a
instrucción con fallo
CPU verifica la TLB
¿Entrada de
la tabla de páginas
entre TLB?
Sí
No
Éxodo adaptable
depósito
Subrutina de tratamiento
de fallo página
El SO solicita a la
CPU la lectura de las
páginas desde dico
No
¿Página
memoria
principal?
Sí
La CPU activa el
hardware de E/S
Actualizar TLB
Página transferida
del disco a memoria
¿Memoria
llena?
No
La CPU genera la
dirección física
Sí
Realizar reemplazo
de página
Actualizar las
tablas de páginas
Figura 8.8.
Operación de paginación y TLB [FURH87].
El lector podrá apreciar la complejidad del hardware de la CPU que participa en una referencia a
memoria sencilla. La dirección virtual se traduce a una dirección real lo cual implica una referencia a
la entrada de la tabla de páginas, que puede estar en la TLB, en la memoria principal, o en disco. La
palabra referenciada puede estar en la cache, en la memoria principal, o en disco. Si dicha palabra referenciada se encuentra únicamente en disco, la página que contiene dicha palabra debe cargarse en la
memoria principal y su bloque en la cache. Adicionalmente, la entrada en la tabla de páginas para dicha página debe actualizarse.
Tamaño de página. Una decisión de diseño hardware importante es el tamaño de página a usar. Hay
varios factores a considerar. Por un lado, está la fragmentación interna. Evidentemente, cuanto mayor es
el tamaño de la página, menor cantidad de fragmentación interna. Para optimizar el uso de la memoria
principal, sería beneficioso reducir la fragmentación interna. Por otro lado, cuanto menor es la página,
mayor número de páginas son necesarias para cada proceso. Un mayor número de páginas por proceso
significa también mayores tablas de páginas. Para programas grandes en un entorno altamente multipro-
08-Capitulo 8
352
12/5/05
16:23
Página 352
Sistemas operativos. Aspectos internos y principios de diseño
Dirección virtual
Nro. Página # Desplazamiento
5
502
Dirección virtual
Nro. Página # Desplazamiento
5
502
Nro. Página # Entradas PT
19
511
37
27
14
1
211
5
37
90
37
37
502
Nro. Segmento # Desplazamiento
Dirección real
TLB
37
502
Nro. Segmento # Desplazamiento
Dirección real
Tabla de páginas
(a) Traducción directa
Figura 8.9.
(b) Traducción asociativa
Resolución directa vs. asociativa para las entradas en la tabla de páginas.
gramado, esto significa que determinadas partes de las tablas de página de los procesos activos deben
encontrarse en la memoria virtual, no en la memoria principal. Por tanto, puede haber un fallo de página
doble para una referencia sencilla a memoria: el primero para atraer la tabla de página de la parte solicitada y el segundo para atraer la página del propio proceso. Otro factor importante son las características
físicas de la mayoría de los dispositivos de la memoria secundaria, que son de tipo giratorio, favoreciendo tamaños de página grandes para mejorar la eficiencia de transferencia de bloques de datos.
Aumentando la complejidad de estos aspectos se encuentra el efecto que el tamaño de página tiene en relación a la posibilidad de que ocurra un fallo de página. Este comportamiento en términos generales, se encuentra recogido en la Figura 8.11a que se basa en el principio de proximidad. Si el tamaño de página es muy pequeño, de forma habitual habrá un número relativamente alto de páginas
disponibles en la memoria principal para cada proceso. Después de un tiempo, las páginas en memoria contendrán las partes de los procesos a las que se ha hecho referencia de forma reciente. De esta
forma, la tasa de fallos de página debería ser baja. A medida que el tamaño de páginas se incrementa,
la página en particular contendrá información más lejos de la última referencia realizada. Así pues, el
efecto del principio de proximidad se debilita y la tasa de fallos de página comienza a crecer. En algún momento, sin embargo, la tasa de fallos de página comenzará a caer a medida que el tamaño de
la página se aproxima al tamaño del proceso completo (punto P en el diagrama). Cuando una única
página contiene el proceso completo, no habrá fallos de página.
Una complicación adicional es que la tasa de fallos de página también viene determinada por el número de marcos asociados a cada proceso. La Figura 8.11b muestra que, para un tamaño de página fijo,
la tasa de fallos cae a medida que el número de páginas mantenidas en la memoria principal crece4. Por
4
El parámetro W representa el conjunto de trabajo, un concepto que se analizará en la Sección 8.2.
12/5/05
16:23
Página 353
Memoria virtual
Operaciones en la TBL
Dirección virtual
Nro. Página # Desplazamiento
TLB
Fallo TLB
Acierto
TLB
Operaciones en la cache
Direción real
Etiqueta
Resto
Cache
Acierto
Valor
Fallo
Memoria
principal
Tabla de páginas
Valor
Operaciones en la TLB y en la cache.
Tasa de fallos de página
Figura 8.10.
Tasa de fallos de página
08-Capitulo 8
P
(a) Tamaño de página
W
P tamaño del proceso entero
W conjunto de trabajo
N número total de páginas en proceso
Figura 8.11.
N
(b) Número de marcos de página reservados
Comportamiento típico de la paginación de un programa.
353
08-Capitulo 8
354
12/5/05
16:23
Página 354
Sistemas operativos. Aspectos internos y principios de diseño
tanto, una política software (la cantidad de memoria reservada por cada proceso) interactúa con decisiones de diseño del propio hardware (tamaño de página).
La Tabla 8 .2 contiene un listado de los tamaños de páginas que tienen determinadas arquitecturas.
Para concluir, el aspecto de diseño del tamaño página se encuentra relacionado con el tamaño de
la memoria física y el tamaño del programa. Al mismo tiempo que la memoria principal está siendo
cada vez más grande, el espacio de direcciones utilizado por las aplicaciones también crece. Esta tendencia resulta más evidente en ordenadores personales y estaciones de trabajo, donde las aplicaciones
tienen una complejidad creciente. Por contra, diversas técnicas de programación actuales usadas para
programas de gran tamaño tienden a reducir el efecto de la proximidad de referencias dentro un proceso [HUCK93]. Por ejemplo,
• Las técnicas de programación orientada a objetos motivan el uso de muchos módulos de datos
y programas de pequeño tamaño con referencias repartidas sobre un número relativamente alto
de objetos en un periodo de tiempo bastante corto.
• Las aplicaciones multihilo (multithreaded) pueden presentar cambios abruptos en el flujo de
instrucciones y referencias a la memoria fraccionadas.
Tabla 8.2.
Ejemplo de tamaños de página.
Computer
Tamaño de página
Atlas
512 palabras de 48-bits
Honeywell-Multics
1024 palabras de 36-bits
IBM 370/XA y 370/ESA
4 Kbytes
Familia VAX
512 bytes
IBM AS/400
512 bytes
DEC Alpha
8 Kbytes
MIPS
4 Kbytes hasta 16 Mbytes
UltraSPARC
8 Kbytes hasta 4 Mbytes
Pentium
4 Kbytes o 4 Mbytes
PowerPc
4 Kbytes
Itanium
4 Kbytes hasta 256 Mbytes
Para un tamaño determinado de una TLB, a medida que el tamaño del proceso crece y la proximidad de referencias decrece, el índice de aciertos en TLB se va reduciendo. Bajo estas circunstancias, la TLB se puede convertir en el cuello de botella del rendimiento (por ejemplo, véase
[CHEN92]).
Una forma de incrementar el rendimiento en la TLB es utilizar una TLB de gran tamaño, con
más entradas. Sin embargo, el tamaño de TLB interactúa con otros aspectos del diseño hardware,
por ejemplo la cache de memoria principal o el número de accesos a memoria por ciclo de instrucción [TALL92]. Una de las principales pegas es que el tamaño de la TLB no tiene la misma tendencia de crecimiento que el tamaño de la memoria principal, en velocidad de crecimiento. Como alternativa se encuentra el uso de tamaños de página mayores de forma que cada entrada en la tabla
de páginas referenciada en la TLB apunte a un bloque de memoria relativamente grande. Pero acabamos de ver que el uso de tamaños de página muy grandes puede significar la degradación del
rendimiento.
08-Capitulo 8
12/5/05
16:23
Página 355
Memoria virtual
355
Sobre estas consideraciones, un gran número de diseñadores han investigado la posibilidad de
utilizar múltiples tamaños de página [TALL92, KHAL93], y diferentes arquitecturas de microprocesadores dan soporte a diversos tamaños de página, incluyendo MIPS R4000, Alpha, UltraSPARC, Pentium, e IA-64. Los tamaños de página múltiples proporcionan la flexibilidad necesaria
para el uso de la TLB de forma eficiente. Por ejemplo, regiones contiguas de memoria de gran tamaño dentro del espacio direcciones del proceso, como las instrucciones del programa, se pueden
proyectar sobre un reducido número de páginas de gran tamaño en lugar de un gran número de páginas de tamaño más pequeño, mientras que las pilas de los diferentes hilos se pueden alojar utilizando tamaños de página relativamente pequeños. Sin embargo, la mayoría de sistemas operativos
comerciales aún soportan únicamente un tamaño de página, independientemente de las capacidades
del hardware sobre el que están ejecutando. El motivo de esto se debe a que el tamaño de página
afecta a diferentes aspectos del sistema operativo; por tanto, un cambio a un modelo de diferentes
tamaños de páginas representa una tarea significativamente compleja (véase [GANA98] para más
detalle).
SEGMENTACIÓN
Las implicaciones en la memoria virtual. La segmentación permite al programador ver la memoria como si se tratase de diferentes espacios de direcciones o segmentos. Los segmentos pueden
ser de tamaños diferentes, en realidad de tamaño dinámico. Una referencia a la memoria consiste en
un formato de dirección del tipo (número de segmento, desplazamiento).
Esta organización tiene un gran número de ventajas para el programador sobre los espacios de direcciones no segmentados:
1. Simplifica el tratamiento de estructuras de datos que pueden crecer. Si el programador no
conoce a priori el tamaño que una estructura de datos en particular puede alcanzar es necesario hacer una estimación salvo que se utilicen tamaños de segmento dinámicos. Con la
memoria virtual segmentada, a una estructura de datos se le puede asignar su propio segmento, y el sistema operativo expandirá o reducirá el segmento bajo demanda. Si un segmento que necesita expandirse se encuentre en la memoria principal y no hay suficiente
tamaño, el sistema operativo poder mover el segmento a un área de la memoria principal
mayor, si se encuentra disponible, o enviarlo a swap. En este último caso el segmento al
que se ha incrementado el tamaño volverá a la memoria principal en la siguiente oportunidad que tenga.
2. Permite programas que se modifican o recopilan de forma independiente, sin requerir que el
conjunto completo de programas se re-enlacen y se vuelvan a cargar. De nuevo, esta posibilidad se puede articular por medio de la utilización de múltiples segmentos.
3. Da soporte a la compartición entre procesos. El programador puede situar un programa de utilidad o una tabla de datos que resulte útil en un segmento al que pueda hacerse referencia desde otros procesos.
4. Soporta los mecanismos de protección. Esto es debido a que un segmento puede definirse para
contener un conjunto de programas o datos bien descritos, el programador o el administrador
de sistemas puede asignar privilegios de acceso de una forma apropiada.
Organización. En la exposición de la segmentación sencilla, indicamos que cada proceso tiene su
propia tabla de segmentos, y que cuando todos estos segmentos se han cargado en la memoria principal, la tabla de segmentos del proceso se crea y se carga también en la memoria principal. Cada entrada de la tabla de segmentos contiene la dirección de comienzo del correspondiente segmento en la
16:23
Página 356
Sistemas operativos. Aspectos internos y principios de diseño
memoria principal, así como la longitud del mismo. El mismo mecanismo, una tabla segmentos, se
necesita cuando se están tratando esquemas de memoria virtual basados en segmentación. De nuevo,
lo habitual es que haya una única tabla de segmentos por cada uno de los procesos. En este caso sin
embargo, las entradas en la tabla de segmentos son un poco más complejas (Figura 8.2b). Debido a
que sólo algunos de los segmentos del proceso pueden encontrarse en la memoria principal, se necesita un bit en cada entrada de la tabla de segmentos para indicar si el correspondiente segmento se encuentra presente en la memoria principal o no. Si indica que el segmento está en memoria, la entrada
también debe incluir la dirección de comienzo y la longitud del mismo.
Otro bit de control en la entrada de la tabla de segmentos es el bit de modificado, que indica si los
contenidos del segmento correspondiente se han modificado desde que se cargó por última vez en la
memoria principal. Si no hay ningún cambio, no es necesario escribir el segmento cuando se reemplace de la memoria principal. También pueden darse otros bits de control. Por ejemplo, si la gestión
de protección y compartición se gestiona a nivel de segmento, se necesitarán los bits correspondientes a estos fines.
El mecanismo básico para la lectura de una palabra de memoria implica la traducción de una dirección virtual, o lógica, consistente en un número de segmento y un desplazamiento, en una dirección física, usando la tabla de segmentos. Debido a que la tabla de segmentos es de tamaño variable,
dependiendo del tamaño del proceso, no se puede suponer que se encuentra almacenada en un registro. En su lugar, debe encontrarse en la memoria principal para poder accederse. La Figura 8.12 sugiere una implementacione hardware de este esquema (nótese la similitud con la Figura 8.3). Cuando un proceso en particular está en ejecución, un registro mantiene la dirección de comienzo de la
tabla de segmentos para dicho proceso. El número de segmento de la dirección virtual se utiliza para
indexar esta tabla y para buscar la dirección de la memoria principal donde comienza dicho segmento. Ésta es añadida a la parte de desplazamiento de la dirección virtual para producir la dirección
real solicitada.
Dirección física
Dirección virtual
1
Nro seg. # desplazamiento = d
Base + d
Registro
Puntero a tabla
de segmento
Tabla de segmentos
1
d
Segmento
356
12/5/05
Nro seg. #
08-Capitulo 8
Longitud Base
Programa
Figura 8.12.
Mecanismo de
segmentación
Memoria principal
Traducción de direcciones en un sistema con segmentación.
12/5/05
16:23
Página 357
Memoria virtual
357
PAGINACIÓN Y SEGMENTACIÓN COMBINADAS
Paginación y segmentación, cada una tiene sus propias ventajas. La paginación es transparente al
programador y elimina la fragmentación externa, y por tanto proporciona un uso eficiente de la memoria principal. Adicionalmente, debido a que los fragmentos que se mueven entre la memoria y el
disco son de un tamaño igual y prefijado, es posible desarrollar algoritmos de gestión de la memoria más sofisticados que exploten el comportamiento de los programas, como veremos más adelante. La segmentación sí es visible al programador y tiene los beneficios que hemos visto anteriormente, incluyendo la posibilidad de manejar estructuras de datos que crecen, modularidad, y dar
soporte a la compartición y a la protección. Para combinar las ventajas de ambos, algunos sistemas
por medio del hardware del procesador y del soporte del sistema operativo son capaces de proporcionar ambos.
En un sistema combinado de paginación/segmentación, el espacio de direcciones del usuario se
divide en un número de segmentos, a discreción del programador. Cada segmento es, por su parte, dividido en un número de páginas de tamaño fijo, que son del tamaño de los marcos de la memoria
principal. Si un segmento tiene longitud inferior a una página, el segmento ocupará únicamente una
página. Desde el punto de vista del programador, una dirección lógica sigue conteniendo un número
de segmento y un desplazamiento dentro de dicho segmento. Desde el punto de vista del sistema, el
desplazamiento dentro del segmento es visto como un número de página y un desplazamiento dentro
de la página incluida en el segmento.
La Figura 8.13 sugiere la estructura para proporcionar soporte o la combinación de paginación
y segmentación (nótese la similitud con la Figura 8.5). Asociada a cada proceso existe una tabla de
segmentos y varias tablas de páginas, una por cada uno de los segmentos. Cuando un proceso está
en ejecución, un registro mantiene la dirección de comienzo de la tabla de segmentos de dicho proceso. A partir de la dirección virtual, el procesador utiliza la parte correspondiente al número de
segmento para indexar dentro de la tabla de segmentos del proceso para encontrar la tabla de pági-
Dirección virtual
Nro seg. # Nro. Página # Desplazamiento
Nro seg. # Desplazamiento
Puntero a tabla
de segmentos
Tabla de
la página
1
Programa
Figura 8.13.
Mecanismo de
segmentación
1
Nro. seg#
Dirección física
Nro. seg#
08-Capitulo 8
Mecanismo
de páginación
Desplazamiento
Página de
segmentos
Memoria
principal
Traducción de direcciones en un sistema con segmentación/paginación.
08-Capitulo 8
358
12/5/05
16:23
Página 358
Sistemas operativos. Aspectos internos y principios de diseño
nas de dicho segmento. Después, la parte correspondiente al número de página de la dirección virtual original se utiliza para indexar la tabla de páginas y buscar el correspondiente número de marco. Éste se combina con el desplazamiento correspondiente de la dirección virtual para generar la
dirección real requerida.
En la Figura 8.2c se muestran los formatos de la entrada en la tabla de segmentos y de la entrada
en la tabla de páginas. Como antes, la entrada en la tabla de segmentos contiene la longitud del segmento. También contiene el campo base, que ahora hace referencia a la tabla de páginas. Los bits de
presente y modificado no se necesitan debido a que estos aspectos se gestionan a nivel de página.
Otros bits de control sí pueden utilizarse, a efectos de compartición y protección. La entrada en la tabla de páginas es esencialmente la misma que para el sistema de paginación puro. El número de página se proyecta en su número de marco correspondiente si la página se encuentra presente en la memoria. El bit de modificado indica si la página necesita escribirse cuando se expulse del marco de
página actual. Puede haber otros bits de control relacionados con la protección u otros aspectos de la
gestión de la memoria.
PROTECCIÓN Y COMPARTICIÓN
La segmentación proporciona una vía para la implementación de las políticas de protección y compartición. Debido a que cada entrada en la tabla de segmentos incluye la longitud así como la dirección base, un programa no puede, de forma descontrolada, acceder a una posición de memoria principal más allá de los límites del segmento. Para conseguir compartición, es posible que un segmento se
encuentre referenciado desde las tablas de segmentos de más de un proceso. Los mecanismos están,
por supuesto, disponibles en los sistemas de paginación. Sin embargo, en este caso la estructura de
páginas de un programa y los datos no son visible para el programador, haciendo que la especificación de la protección y los requisitos de compartición sean menos cómodos. La Figura 8.14 ilustra los
tipos de relaciones de protección que se pueden definir en dicho sistema.
También es posible proporcionar mecanismos más sofisticados. Un esquema habitual es utilizar
la estructura de protección en anillo, del tipo que indicamos en el Capítulo 3 (Problema 3.7). En este
esquema, los anillos con números bajos, o interiores, disfrutan de mayores privilegios que los anillos
con numeraciones más altas, o exteriores. Normalmente, el anillo 0 se reserva para funciones del núcleo del sistema operativo, con las aplicaciones en niveles superiores. Algunas utilidades o servicios
de sistema operativo pueden ocupar un anillo intermedio. Los principios básicos de los sistemas en
anillo son los siguientes:
• Un programa pueda acceder sólo a los datos residentes en el mismo anillo o en anillos con menos privilegios.
• Un programa puede invocar servicios residentes en el mismo anillo o anillos con más privilegios.
8.2. SOFTWARE DEL SISTEMA OPERATIVO
El diseño de la parte de la gestión de la memoria del sistema operativo depende de tres opciones fundamentales a elegir:
• Si el sistema usa o no técnicas de memoria virtual.
• El uso de paginación o segmentación o ambas.
• Los algoritmos utilizados para los diferentes aspectos de la gestión de la memoria.
08-Capitulo 8
12/5/05
16:23
Página 359
Memoria virtual
Dirección
0
359
Memoria principal
20K
Activador
35K
Acceso no
permitido
50K
Proceso A
80K
90K
Proceso B
Instrucción de salto
(no permitido)
Referencia a dato
(permitido)
140K
Proceso C
Referencia a dato
(no permitido)
190K
Figura 8.14.
Relaciones de protección entre segmentos.
Las elecciones posibles para las dos primeras opciones dependen de la plataforma hardware disponible. Así, las primeras implantaciones de UNIX no proporcionaban memoria virtual porque los
procesadores sobre los cuales ejecutaban no daban soporte para paginación o segmentación. Ninguna
de estas técnicas es abordable sin una plataforma hardware para traducción de direcciones y otras
funciones básicas.
Hay también dos comentarios adicionales sobre estas dos primeras opciones: primero, con la excepción de los sistemas operativos de algunas plataformas como los ordenadores personales antiguos,
como MS-DOS, y de otros sistemas de carácter especializado, todos los sistemas operativos importantes proporcionan memoria virtual. Segundo, los sistemas de segmentación pura son en la actualidad realmente escasos. Cuando la segmentación se combina con paginación, la mayoría de los aspectos de la
gestión de la memoria relativos al diseño sistema operativo se encuadran en el área de la paginación5.
De esta forma, en esta sección nos concentraremos en los aspectos asociados a la paginación.
Las elecciones relativas a la tercera opción entran dentro del dominio del software del sistema
operativo y son el objeto de esta sección. La Tabla 8.3 muestra los elementos de diseño clave que se
5
La protección y la compartición, en un sistema combinado de segmentación/paginación, se suelen delegar habitualmente a nivel de segmento. Abordaremos estas cuestiones en capítulos posteriores.
08-Capitulo 8
360
12/5/05
16:23
Página 360
Sistemas operativos. Aspectos internos y principios de diseño
Tabla 8.3.
Políticas del sistema operativo para la memoria virtual.
Política de recuperación
Bajo demanda
Paginación adelantada
Gestión del conjunto residente
Tamaño del conjunto residente
Fijo
Variable
Política de ubicación
Ámbito de reemplazo
Global
Política de reemplazo
Local
Algoritmos básicos
Óptimo
Política de limpieza
FIFO
Bajo demanda
Usada menos recientemente (LRU)
Limpieza adelantada
Del reloj
Buffers de página
Control de carga
Grado de multiprogramación
van a examinar. En cada caso, el aspecto central es el rendimiento: Se tratará de minimizar la tasa de
ocurrencia de fallos de página, porque los fallos de página causan una considerable sobrecarga sobre
el software. Como mínimo, esta sobrecarga incluye la decisión de qué página o páginas residentes se
van a reemplazar, y la E/S del intercambio o swap de dichas páginas. También, el sistema operativo
debe planificar la ejecución de otro proceso durante la operación de E/S de la página, causando un
cambio de contexto. De la misma forma, intentaremos organizar determinados aspectos de forma que,
durante el tiempo de ejecución de un proceso, la probabilidad de hacer referencia a una palabra en
una página que no se encuentre presente sea mínima. En todas estas áreas indicadas en la Tabla 8.3,
no existe una política que sea mejor que todas las demás. Como se verá, la tarea de gestión de la memoria en un entorno de paginación es endiabladamente compleja. Adicionalmente, el rendimiento de
un conjunto de políticas en particular depende del tamaño de la memoria, de la velocidad relativa de
la memoria principal y secundaria, del tamaño y del número de procesos que están compitiendo por
los recursos, y del comportamiento en ejecución de los diferentes programas de forma individual.
Esta última característica depende de la naturaleza de la aplicación, el lenguaje de programación y el
compilador utilizado, el estilo del programador que lo escribió, y, para un programa interactivo, el
comportamiento dinámico del usuario. Así pues, el lector no debe esperar de ningún modo una respuesta definitiva aquí. Para sistemas pequeños, el diseño de sistema operativo debe intentar elegir un
conjunto de políticas que parezcan funcionar «bien» sobre una amplia gama de condiciones, basándose en el conocimiento sobre el estado actual del sistema. Para grandes sistemas, particularmente
mainframes, el sistema operativo debe incluir herramientas de monitorización y control que permitan
al administrador de la instalación ajustar ésta para obtener «buenos» resultados en base a las condiciones de la instalación.
POLÍTICA DE RECUPERACIÓN
La política de recuperación determina cuándo una página se trae a la memoria principal. Las dos alternativas habituales son bajo demanda y paginación adelantada (prepaging). Con paginación bajo
demanda, una página se trae a memoria sólo cuando se hace referencia a una posición en dicha página. Si el resto de elementos en la política de gestión de la memoria funcionan correctamente, ocurriría
08-Capitulo 8
12/5/05
16:23
Página 361
Memoria virtual
361
lo siguiente. Cuando un proceso se arranca inicialmente, va a haber una ráfaga de fallos de página.
Según se van trayendo más y más páginas a la memoria, el principio de proximidad sugiere que las
futuras referencias se encontrarán en las páginas recientemente traídas. Así, después de un tiempo, la
situación se estabilizará y el número de fallos de página caerá hasta un nivel muy bajo.
Con paginación adelantada (prepaging), se traen a memoria también otras páginas, diferentes
de la que ha causado el fallo de página. La paginación adelantada tiene en cuenta las características
que tienen la mayoría de dispositivos de memoria secundaria, tales como los discos, que tienen tiempos de búsqueda y latencia de rotación. Si las páginas de un proceso se encuentran almacenadas en la
memoria secundaria de forma contigua, es mucho más eficiente traer a la memoria un número de páginas contiguas de una vez, en lugar de traerlas una a una a lo largo de un periodo de tiempo más amplio. Por supuesto, esta política es ineficiente si la mayoría de las páginas que se han traído no se referencian a posteriori.
La política de paginación adelantada puede emplearse bien cuando el proceso se arranca, en cuyo
caso el programador tiene que designar de alguna forma las páginas necesarias, o cada vez que ocurra
un fallo de página. Este último caso es el más apropiado porque resulta completamente invisible al
programador. Sin embargo, la completa utilidad de la paginación adelantada no se encuentra reconocida [MAEK87].
La paginación adelantada no se debe confundir con el swapping. Cuando un proceso se saca de la
memoria y se le coloca en estado suspendido, todas sus páginas residentes se expulsan de la memoria.
Cuando el proceso se recupera, todas las páginas que estaban previamente en la memoria principal retornan a ella.
POLÍTICA DE UBICACIÓN
La política de ubicación determina en qué parte de la memoria real van a residir las porciones de la
memoria de un proceso. En los sistema de segmentación puros, la política de ubicación es un aspecto
de diseño muy importante; políticas del estilo mejor ajuste, primer ajuste, y similares que se discutieron en el Capítulo 7, son las diferentes alternativas. Sin embargo, para sistemas que usan o bien paginación pura o paginación combinada con segmentación, la ubicación es habitualmente irrelevante debido a que el hardware de traducción de direcciones y el hardware de acceso a la memoria principal
pueden realizar sus funciones en cualquier combinación de página-marco con la misma eficiencia.
Existe otro entorno en el cual la ubicación tiene implicación importante, y es un tema de investigación y desarrollo. En aquellos sistemas llamados multiprocesadores de acceso a la memoria no uniforme (nonuniform memory access multiprocessors-NUMA), la memoria distribuida compartida de la
máquina puede referenciarse por cualquier otro procesador dentro de la misma máquina, pero con un
tiempo de acceso dependiente de la localización física y que varía con la distancia entre el procesador
y el módulo de la memoria. De esta forma, el rendimiento depende significativamente de la distancia
a la cual reside el dato en relación al procesador que va a utilizar [LARO92, BOLO89, COX89]. Para
sistemas NUMA, una estrategia de ubicación automática aceptable es aquella que asigna las páginas
al módulo de la memoria que finalmente proporcionará mejor rendimiento.
POLÍTICA DE REEMPLAZO
En la mayoría de los libros sobre sistemas operativos, el tratamiento de la gestión de la memoria incluye una sección titulada «política de reemplazo», que trata de la selección de una página en la memoria principal como candidata para reemplazarse cuando se va traer una nueva página. Este tema es,
a menudo, difícil de explicar debido a que hay varios conceptos interrelacionados:
08-Capitulo 8
362
12/5/05
16:23
Página 362
Sistemas operativos. Aspectos internos y principios de diseño
• ¿Cuántos marcos de página se van a reservar para cada uno de los procesos activos?
• Si el conjunto de páginas que se van a considerar para realizar el reemplazo se limita a aquellas del mismo proceso que ha causado el fallo de página o, si por el contrario, se consideran
todos los marcos de página de la memoria principal.
• Entre el conjunto de páginas a considerar, qué página en concreto es la que se va a reemplazar.
Nos referimos a los dos primeros conceptos como la gestión del conjunto residente, que se trata
en la siguiente subsección, y se ha reservado el término política de reemplazo para el tercer concepto,
que se discutirá en esta misma subsección.
El área de políticas de reemplazo es probablemente el aspecto de la gestión de la memoria que
ha sido más estudiado. Cuando todos los marcos de la memoria principal están ocupados y es necesario traer una nueva página para resolver un fallo de página, la política de reemplazo determina qué
página de las que actualmente están en memoria va a reemplazarse. Todas las políticas tienen como
objetivo que la página que va a eliminarse sea aquella que tiene menos posibilidades de volver a tener una referencia en un futuro próximo. Debido al principio de proximidad de referencia, existe a
menudo una alta correlación entre el histórico de referencias recientes y los patrones de referencia
en un futuro próximo. Así, la mayoría de políticas tratan de predecir el comportamiento futuro en
base al comportamiento pasado. En contraprestación, se debe considerar que cuanto más elaborada
y sofisticada es una política de reemplazo, mayor va a ser la sobrecarga a nivel software y hardware
para implementarla.
Bloqueo de marcos. Es necesario mencionar una restricción que se aplica a las políticas de reemplazo antes de indagar en los diferentes algoritmos: algunos marcos de la memoria principal pueden
encontrarse bloqueados. Cuando un marco está bloqueado, la página actualmente almacenada en dicho marco no puede reemplazarse. Gran parte del núcleo del sistema operativo se almacena en marcos que están bloqueados, así como otras estructuras de control claves. Adicionalmente, los buffers de
E/S y otras áreas de tipo critico también se ponen en marcos bloqueados en la memoria principal. El
bloqueo se puede realizar asociando un bit de bloqueo a cada uno de los marcos. Este bit se puede almacenar en la tabla de marcos o también incluirse en la tabla de páginas actual.
Algoritmos básicos. Independientemente de la estrategia de gestión del conjunto residente (que se
discutirá en la siguiente subsección), existen ciertos algoritmos básicos que se utilizan para la selección de la página a reemplazar. Los algoritmos de reemplazo que se han desarrollando a lo largo de la
literatura son:
• Óptimo.
• Usado menos recientemente (least recently used-LRU).
• FIFO (first-in-first-out).
• Reloj.
La política óptima de selección tomará como reemplazo la página para la cuál el instante de la
siguiente referencia se encuentra más lejos. Se puede ver que para esta política los resultados son el
menor número de posibles fallos de página [BELA66]. Evidentemente, esta política es imposible de
implementar, porque requiere que el sistema operativo tenga un perfecto conocimiento de los eventos
futuros. Sin embargo se utiliza como un estándar apartir del cual contrastar algoritmos reales.
La Figura 8.15 proporciona un ejemplo de la política óptima. El ejemplo asume una reserva de
marcos fija (tamaño del conjunto residente fijo) para este proceso de un total de tres marcos. La eje-
08-Capitulo 8
12/5/05
16:23
Página 363
363
Memoria virtual
cución de proceso requiere la referencia de cinco páginas diferentes. El flujo de páginas referenciadas
por el programa antes citado es el siguiente:
2 3 2 1 5 2 4 5 3 2 5 2
lo cual representa que la primera página a la que se va a hacer referencia es la 2, la segunda página la
3, y así en adelante. La política óptima produce tres fallos de página después de que la reserva de
marcos se haya ocupado completamente.
La política de reemplazo de la página usada menos recientemente (LRU) seleccionará como
candidata la página de memoria que no se haya referenciado desde hace más tiempo. Debido al principio de proximidad referenciada, esta página sería la que tiene menos probabilidad de volverá a tener
referencias en un futuro próximo. Y, de hecho, la política LRU proporciona unos resultados casi tan
buenos como la política óptima. El problema con esta alternativa es la dificultad en su implementación. Una opción sería etiquetar cada página con el instante de tiempo de su última referencia; esto
podría ser en cada una de las referencias a la memoria, bien instrucciones o datos. Incluso en el caso
de que el hardware diera soporte a dicho esquema, la sobrecarga sería tremenda. De forma alternativa
se puede mantener una pila de referencias a páginas, que igualmente es una opción costosa.
La Figura 8.15 muestra un ejemplo del comportamiento de LRU, utilizando el mismo flujo de referencias a páginas que en el ejemplo de la política óptima. En este ejemplo, se producen cuatro fallos de página.
La política FIFO trata los marcos de página ocupados como si se tratase de un buffer circular, y
las páginas se remplazan mediante una estrategia cíclica de tipo round-robin. Todo lo que se necesita
Flujo de
páginas
referenciales
OPT
LRU
FIFO
RELOJ
2
3
2
1
5
2
4
5
3
2
5
2
2
2
3
2
3
2
3
1
2
3
5
F
2
3
5
4
3
5
F
4
3
5
4
3
5
2
3
5
F
2
3
5
2
3
5
2
2
3
2
3
2
3
1
2
5
1
F
2
5
1
2
5
4
F
2
5
4
3
5
4
F
3
5
2
F
3
5
2
3
5
2
2
2
3
2
3
2
3
1
5
3
1
F
5
2
1
F
5
2
4
F
5
2
4
3
2
4
F
3
2
4
3
5
4
F
3
5
2
F
2*
2*
3*
2*
3*
2*
3*
1*
5*
3
1
F
5*
2*
1
F
5*
2*
4*
F
5*
2*
4*
3*
2
4
F
3*
2*
4
3*
2
5*
F
3*
2*
5*
F = fallo de página después de que la reserva de marcos se haya llenado inicialmente
Figura 8.15.
Comportamiento de cuatro algoritmos de reemplazo de páginas.
08-Capitulo 8
364
12/5/05
16:23
Página 364
Sistemas operativos. Aspectos internos y principios de diseño
es un puntero que recorra de forma circular los marcos de página del proceso. Por tanto, se trata de
una de las políticas de reemplazo más sencilla de implementar. El razonamiento tras este modelo,
además de su simplicidad, es el reemplazo de la página que lleva en memoria más tiempo: una página
traída a la memoria hace mucho tiempo puede haber dejado de utilizarse. Este razonamiento a menudo es erróneo, debido a que es habitual que en los programas haya una zona del mismo o regiones de
datos que son utilizados de forma intensiva durante todo el tiempo de vida del proceso. Esas páginas
son expulsadas de la memoria y traídas de nuevo de forma repetida por un algoritmo de tipo FIFO.
Continuando con el mismo ejemplo de la Figura 8.15, la política FIFO genera un total de seis fallos de página. Nótese que el algoritmo LRU reconoce que a las páginas 2 y 5 se hace referencia con
mayor frecuencia que a cualquier otra página, mientras que FIFO no lo hace.
Mientras que la política LRU alcanza unos resultados similares a la política óptima, es difícil de
implementar e impone una sobrecarga significativa. Por otro lado, la política FIFO es muy sencilla de
implementar pero su rendimiento es relativamente pobre. A lo largo de los años, los diseñadores de
sistemas operativos han intentado un gran número de algoritmos diferentes para aproximarse a los resultados obtenidos por LRU e intentando imponer una sobrecarga más reducida. Muchos de estos algoritmos son variantes del esquema denominado política del reloj.
En su forma más sencilla la política del reloj requiere la inclusión de un bit adicional en cada uno
de los marcos de página, denominado bit de usado. Cuando una página se trae por primera vez a la
memoria, el bit de usado de dicho marco se pone a 1. En cualquier momento que la página vuelva a
utilizarse (después de la referencia generada con el fallo de página inicial) su bit de usado se pone a
1. Para el algoritmo de reemplazo de páginas, el conjunto de todas las páginas que son candidatas
para reemplazo (de este proceso: ámbito local; toda la memoria principal: ámbito global6) se disponen como si se tratase de un buffer circular, al cual se asocia un puntero. Cuando se reemplaza una
página, el puntero indica el siguiente marco del buffer justo después del marco que acaba de actualizarse. Cuando llega el momento de reemplazar una página, el sistema operativo recorre el buffer para
encontrar un marco con su bit de usado a 0. Cada vez que encuentra un marco con el bit de usado a 1,
se reinicia este bit a 0 y se continúa. Si alguno de los marcos del buffer tiene el bit de usado a 0 al comienzo de este proceso, el primero de estos marcos que se encuentre se seleccionará para reemplazo.
Si todos los marcos tienen el bit a 1, el puntero va a completar un ciclo completo a lo largo del buffer,
poniendo todo los bits de usado a 0, parándose en la posición original, reemplazando la página en dicho marco. Véase que esta política es similar a FIFO, excepto que, en la política del reloj, el algoritmo saltará todo marco con el bit de usado a 1. La política se domina política del reloj debido a que se
pueden visualizar los marcos de página como si estuviesen distribuidos a lo largo del círculo. Un gran
número de sistemas operativos han empleado alguna variante de esta política sencilla del reloj (por
ejemplo, Multics [CORB68]).
La Figura 8.16 plantea un ejemplo del mecanismo de la política del reloj. Un buffer circular con n
marcos de memoria principal que se encuentran disponibles para reemplazo de la página. Antes del
comienzo del reemplazo de una página del buffer por la página entrante 727, el puntero al siguiente
marco apunta al marco número 2, que contiene la página 45. En este momento la política del reloj comienza a ejecutarse. Debido a que el bit usado de la página 45 del marco 2 es igual a 1, esta página
no se reemplaza. En vez de eso, el bit de usado se pone a 0 y el puntero avanza. De forma similar la
página 191 en el marco 3 tampoco se remplazará; y su bit de usado se pondrá a 0, avanzando de nuevo el puntero. En el siguiente marco, el marco número 4, el bit de usado está a 0. Por tanto, la página
556 se reemplazará por la página 727. El bit de usado se pone a 1 para este marco y el puntero avanza
hasta el marco 5, completando el procedimiento de reemplazo de página.
6
El concepto de ámbito se discute en la subsección «Ámbito de reemplazo», más adelante.
08-Capitulo 8
12/5/05
16:23
Página 365
Memoria virtual
n1
365
Primer marco
del buffer circular
que es candidato
al reemplazo
0
Pág. 9 Pág. 19
Usado 1 Usado 1
1
Pág. 1
Usado 1
Siguiente
puntero
de marco
8
Pág. 45
Usado 1
Pág. 222
Usado 0
Pág. 33
Usado 1
7
Pág. 191
Usado 1
2
3
Pág. 556
Usado 0
4
Pág. 67 Pág. 13
Usado 1 Usado 0
6
5
(a) Estado del buffer justo ante del reemplazo de una página
n1
0
Pág. 9 Pág. 19
Usado 1 Usado 1
1
Pág. 1
Usado 1
Pág. 45
Usado 0
8
Pág. 222
Usado 0
Pág. 33
Usado 1
7
Pág. 191
Usado 0
2
3
Pág. 727
Usado 1
Pág. 67 Pág. 13
Usado 1 Usado 0
6
4
5
(b) Estado del buffer justo después del siguiente reemplazo de página
Figura 8.16.
Ejemplo de operación de la política de reemplazo del reloj.
El comportamiento de la política del reloj se encuentra ilustrado en la Figura 8.15. La presencia
de un asterisco indica que el correspondiente bit de usado es igual a 1, y la flecha indica cuál es la posición actual del puntero. Nótese que la política del reloj intenta proteger los marcos 2 y 5 de un posible reemplazo.
La Figura 8.17 muestra los resultados del experimento realizado por [BAER80], que compara los
cuatro algoritmos que se han comentado; se asume que el número de marcos de página asignados a
cada proceso es fijo. El resultado se basa en la ejecución de 0,25 x 106 referencias en un programa
FORTRAN, utilizando un tamaño de página de 256 palabras. Baer ejecutó el experimento con unas
reservas de 6, 8, 10, 12, y 14 marcos. Las diferencias entre las cuatro políticas son más palpables
366
12/5/05
16:23
Página 366
Sistemas operativos. Aspectos internos y principios de diseño
Fallos de página por cada 1000 referencias
08-Capitulo 8
Figura 8.17.
40
35
FIFO
30
RELOJ
25
LRU
20
15
OPT
10
5
0
6
10
8
Número de marcos reservados
12
14
Comparativa de algoritmos de reemplazo local con reserva de marcos fija.
cuando el número de marcos reservados es pequeño, estando FIFO por encima en un factor de 2 veces peor que el óptimo. Las cuatro curvas mantienen la misma forma de comportamiento que el ideal
mostrado en la Figura 8.11b. Con intención de ejecutar de forma eficiente, sería deseable encontrarse
en el lado derecho de la curva (con una tasa de fallos de página pequeña) mientras que al mismo
tiempo se mantiene una necesidad de reservar relativamente pocos marcos (hacia el lado izquierdo de
la curva). Estas dos restricciones indican que el modo deseable de operación estaría aproximadamente en la mitad de la curva.
[FINK88] tanbién reporta unos resultados prácticamente idénticos, de nuevo mostrando una desviación máxima en torno a un factor de 2. La estrategia de Finkel consistía en simular el efecto de varias políticas en una cadena de referencias a páginas generada sintéticamente de un total de 10.000 referencias seleccionadas dentro del espacio virtual de 100 páginas. Para aproximarse a los efectos del
principio de próximidad de referencia, se impuso el uso de una distribución exponencial de probabilidad para hacer referencia a una página en concreto. Finkel indica que se podría concluir que no tiene
mucho sentido elaborar algoritmos de reemplazo de páginas cuando sólo hay un factor de 2 en juego.
Pero remarca que esta diferencia puede tener un efecto considerable en los requisitos de memoria principal (si se quiere evitar que el rendimiento del sistema operativo se degrade) o para el propio rendimiento del sistema operativo (si se quiere evitar el requisito de una memoria principal mucho mayor).
El algoritmo del reloj también se ha comparado con estos otros algoritmos cuando la reserva de
marcos es variable y se aplican ámbitos de reemplazamiento tanto global como local (véase la siguiente explicación relativa a las políticas de reemplazo) [CARR81, CARR 84]. El algoritmo del reloj se encuentra muy próximo en rendimiento al LRU.
El algoritmo del reloj puede hacerse más potente incrementando el número de bits que utiliza7.
En todos los procesadores que soportan paginación, se asocia un bit de modificado a cada una de las
páginas de la memoria principal y por tanto con cada marco de la memoria principal. Este bit es necesario debido a que, cuando una página se ha modificado, no se la puede reemplazar hasta que se haya
escrito de nuevo a la memoria secundaria. Podemos sacar provecho de este bit en el algoritmo del reloj de la siguiente manera. Si tenemos en cuenta los bits de usado y modificado, cada marco de página cae en una de las cuatro categorías siguientes:
7
Por otro lado, si se reduce el número de bits utilizados a 0, el algoritmo del reloj degenera a uno de tipo FIFO.
08-Capitulo 8
12/5/05
16:23
Página 367
Memoria virtual
367
• No se ha accedido recientemente, no modificada (u = 0; m = 0)
• Accedida recientemente, no modificada (u = 1; m = 0)
• No se ha accedido recientemente, modificada (u = 0; m = 1)
• Accedida recientemente, modificada (u = 1; m = 1)
Con esta clasificación, el algoritmo del reloj puede actuar de la siguiente manera:
1. Comenzando por la posición actual del puntero, recorremos el buffer de marcos. Durante el recorrido, no se hace ningún cambio en el bit de usado. El primer marco que se encuentre con (u
= 0; m = 0) se selecciona para reemplazo.
2. Si el paso 1 falla, se recorre el buffer de nuevo, buscando un marco con (u = 0; m = 1). El primer marco que se encuentre se seleccionará para reemplazo. Durante el recorrido, se pondrá el
bit de usado a 0 en cada uno de los marcos que se vayan saltando.
3. Si el paso 2 también falla, el puntero debe haber vuelto a la posición original y todo los marcos en el conjunto tendrán el bit de usado a 0. Se repite el paso 1 y, si resulta necesario el paso
2. Esta vez, se encontrará un marco para reemplazo.
En resumen, el algoritmo de reemplazo de páginas da vueltas a través de todas las páginas del
buffer buscando una que no se haya modificado desde que se ha traído y que no haya sido accedida
recientemente. Esta página es una buena opción para reemplazo y tiene la ventaja que, debido a que
no se ha modificado, no necesita escribirse de nuevo en la memoria secundaria. Si no se encuentra
una página candidata en la primera vuelta, el algoritmo da una segunda vuelta al buffer, buscando
una página modificada que no se haya accedido recientemente. Incluso aunque esta página tenga
que escribirse antes de ser remplazada, debido al principio de proximidad de referencia, puede no
necesitarse de nuevo en el futuro próximo. Si esta segunda pasada falla, todos los marcos en el buffer se encuentran marcados como si no hubiesen sido accedidos recientemente y se realiza una tercera pasada.
Esta estrategia se ha utilizado en el esquema de memoria virtual de las versiones antiguas de los
Macintosh [GOLD89], mostrados en la Figura 8.18. La ventaja de este algoritmo sobre el algoritmo
del reloj básico es que se les otorga preferencia para el reemplazo a las páginas que no se han modificado. Debido a que la página que se ha modificado debe escribirse antes del reemplazo, hay un ahorro de tiempo inmediato.
Buffering páginas. A pesar de que las políticas LRU y del reloj son superiores a FIFO, ambas incluyen una complejidad y una sobrecarga que FIFO no sufre. Adicionalmente, existe el aspecto relativo a que el coste de reemplazo de una página que se ha modificado es superior al de una que no lo ha
sido, debido a que la primera debe escribirse en la memoria secundaria.
Una estrategia interesante que puede mejorar el rendimiento de la paginación y que permite el
uso de una política de reemplazo de páginas sencilla es el buffering de páginas. La estrategia más representativa de este tipo es la usaba por VAX VMS. El algoritmo de reemplazo de páginas es el FIFO
sencillo. Para mejorar el rendimiento, una página remplazada no se pierde sino que se asigna a una de
las dos siguientes listas: la lista de páginas libres si la página no se ha modificado, o la lista de páginas modificadas si lo ha sido. Véase que la página no se mueve físicamente de la memoria; al contrario, la entrada en la tabla de páginas para esta página se elimina y se coloca bien en la lista de páginas
libres o bien en la lista de páginas modificadas.
La lista de páginas libres es una lista de marcos de páginas disponibles para lectura de nuevas páginas. VMS intenta mantener un pequeño número de marcos libres en todo momento. Cuando una
08-Capitulo 8
368
12/5/05
16:23
Página 368
Sistemas operativos. Aspectos internos y principios de diseño
página se va a leer, se utiliza el marco de página en la cabeza de esta lista, eliminando la página que
estaba. Cuando se va a reemplazar una página que no se ha modificado, se mantiene en la memoria
ese marco de página y se añade al final de la lista de páginas libres. De forma similar, cuando una página modificada se va a escribir y reemplazar, su marco de página se añade al final de la lista de páginas modificadas.
El aspecto más importante de estas maniobras es que la página que se va a reemplazar se mantiene en la memoria. De forma que si el proceso hace referencia a esa página, se devuelve al conjunto
residente del proceso con un bajo coste. En efecto, la lista de páginas modificadas y libres actúa
como una cache de páginas. La lista de páginas modificadas tiene también otra función útil: las páginas modificadas se escriben en grupos en lugar de una a una. Esto reduce de forma significativa el
número de operaciones de E/S y por tanto el tiempo de acceso disco.
Una versión simple de esta estrategia de buffering de páginas la implementa el sistema operativo
Mach [RASH88]. En este caso, no realiza distinción entre las páginas modificadas y no modificadas.
Política de reemplazo y tamaño de la cache. Como se ha comentado anteriormente, cuando el
tamaño de la memoria principal crece, la proximidad de referencia de las aplicaciones va a decrecer.
En compensación, los tamaños de las caches pueden ir aumentando. Actualmente, grandes tamaños
de caches, incluso de varios megabytes, son alternativas de diseño abordables [BORG90]. Con una
Primer marco del
buffer circular para
este proceso
n1
Página 7
no accedida
recientemente;
modificada
9
8
Próximo
reemplazo
0
Página 9
no accedida
1
recientemente;
Página
94
modificada
no accedida
recientemente;
no modificada
Página 13
no accedida
recientemente;
no modificada
Página 95
accedida
recientemente;
no modificada
Página 96
accedida
recientemente;
no modificada
Página 47
no accedida
recientemente;
no modificada
Página 46
no accedida
recientemente;
modificada Página 121
accedida
recientemente;
7
no modificada
6
Figura 8.18.
2
3
Último
reemplazo
Página 97
no accedida
recientemente;
modificada
Página 45
accedida
recientemente;
no modificada
4
5
El algoritmo de reemplazo de páginas del reloj [GOLD89].
08-Capitulo 8
12/5/05
16:23
Página 369
Memoria virtual
369
cache de gran tamaño, el reemplazo de páginas de la memoria virtual puede tener un impacto importante en el rendimiento. Si el marco de página destinado al reemplazo está en cache, entonces el bloque de cache se pierde al mismo tiempo que la página que lo contiene.
En sistemas que utilizan algún tipo de buffering de páginas, se puede mejorar el rendimiento de
la cache añadiendo a la política de reemplazo de páginas una política de ubicación de páginas en el
buffer de páginas. La mayoría de sistemas operativos ubican las páginas seleccionando un marco
procedente del buffer de páginas, de forma arbitraria; utilizando habitualmente una disciplina de
tipo FIFO. El estudio reportado en [KESS92] muestra que una estrategia de reemplazo de páginas
cuidadosa puede significar entre un 10 y un 20% menos de fallos de cache si se compara con un reemplazo simple.
En [KESS92] se examinan diferentes algoritmos de reemplazo de páginas. Estos detalles están
más allá del ámbito de este libro, debido a que dependen de detalles de la estructura de las caches y
sus políticas. La esencia de estas estrategias consiste en traer páginas consecutivas a la memoria principal de forma que se minimice el número de marcos de página que se encuentran proyectados en las
mismas ranuras de la cache.
GESTIÓN DEL CONJUNTO RESIDENTE
Tamaño del conjunto residente. Con la memoria virtual paginada, no es necesario, y en algunos
casos no es ni siquiera posible, traer todas las páginas de un proceso a la memoria principal para preparar su ejecución. Debido a que el sistema operativo debería saber cuántas páginas traerse, esto es,
cuánta memoria principal debería reservar para un proceso en particular. Diferentes factores entran en
juego:
• Cuanto menor es la cantidad de memoria reservada para un proceso, mayor es el número de
procesos que pueden residir en la memoria principal a la vez. Esto aumenta la probabilidad de
que el sistema operativo pueda encontrar al menos un proceso listo para ejecutar en un instante dado, así por tanto, reduce el tiempo perdido debido al swapping.
• Si el conjunto de páginas de un proceso que están en memoria es relativamente pequeño, entonces, en virtud del principio de proximidad de referencia, la posibilidad de un fallo de página es mayor (véase Figura 8.11.b).
• Más allá de un determinado tamaño, la reserva de más memoria principal para un determinado
proceso no tendrá un efecto apreciable sobre la tasa de fallos de página de dicho proceso, debido al principio de proximidad de referencia.
Teniendo en cuenta estos factores, se pueden encontrar dos tipos de políticas existentes en los sistemas operativos contemporáneos. La política de asignación fija proporciona un número fijo de marcos de memoria principal disponibles para ejecución. Este número se decide en el momento de la carga inicial de proceso (instante de creación del proceso) y se puede determinar en base al tipo de
proceso (interactivo, por lotes, tipo de aplicación) o se puede basar en las guías proporcionadas por el
programador o el administrador del sistema. Con la política de asignación fija, siempre que se produzca un fallo de página del proceso en ejecución, la página que se necesite reemplazará una de las
páginas del proceso.
Una política de asignación variable permite que se reserven un número de marcos por proceso
que puede variar a lo largo del tiempo de vida del mismo. De forma ideal, a un proceso que esté causando una tasa de fallos de página relativamente alta de forma continua, indicativo de que el principio
proximidad de referencia sólo se aplica de una forma relativamente débil para este proceso, se le
otorgarán marcos de página adicionales para reducir esta tasa de fallos; mientras tanto, a un proceso
08-Capitulo 8
370
12/5/05
16:23
Página 370
Sistemas operativos. Aspectos internos y principios de diseño
con una tasa de fallos de páginas excepcionalmente baja, indicativo de que el proceso sigue un comportamiento bien ajustado al principio de proximidad de referencia, se reducirán los marcos reservados, con esperanza de que esto no incremente de forma apreciable la tasa de fallos. El uso de políticas
de asignación variable se basa en el concepto de ámbito de reemplazo, como se explicará en la siguiente subsección.
La política de asignación variable podría parecer más potente. Sin embargo, las dificultades de
esta estrategia se deben a que el sistema operativo debe saber cuál es el comportamiento del proceso
activo. Esto requiere, de forma inevitable, una sobrecarga software por parte del sistema operativo y
depende de los mecanismos hardware proporcionados por la propia plataforma del procesador.
Ámbito de reemplazo. La estrategia del ámbito de reemplazo se puede clasificar en global y local.
Ambos tipos de políticas se activan por medio de un fallo de página cuando no existen marcos de página libres. Una política de reemplazo local selecciona únicamente entre las páginas residentes del
proceso que ha generado el fallo de página. Para la identificación de la página a reemplazar en una
política de reemplazo global se consideran todas las páginas en la memoria principal que no se encuentren bloqueadas como candidatos para el reemplazo, independientemente de a qué proceso pertenezca cada página en particular. Mientras que las políticas locales son más fáciles de analizar, no
existe ninguna evidencia convincente de que proporcionen un rendimiento mejor que las políticas
globales, que son más atractivas debido a la simplicidad de su implementación con una sobrecarga
mínima [CARR84, MAEK87].
Existe una correlación entre el ámbito de reemplazo y el tamaño del conjunto residente (Tabla
8.4). Un conjunto residente fijo implica automáticamente una política de reemplazo local: para mantener el tamaño de conjunto residente, al reemplazar una página se debe eliminar de la memoria principal otra del mismo proceso. Una política de asignación variable puede emplear claramente la política de reemplazo global: el reemplazo de una página de un proceso en la memoria principal por la de
otro causa que la asignación de memoria para un proceso crezca en una página mientras que disminuye la del otro. Se verá también que la asignación variable y el reemplazo local son una combinación
válida. Se examinan ahora estas tres combinaciones.
Asignación fija, ámbito local. En este caso, se parte de un proceso que se encuentra en ejecución
en la memoria principal con un número de marcos fijo. Cuando se da un fallo de página, el sistema
operativo debe elegir una página entre las residentes del proceso actual para realizar el reemplazo. Se
utilizarían los algoritmos de reemplazo que se han visto en la sección precedente.
Con la política de asignación fija, es necesario decidir por adelantado la cantidad de espacio reservado para un proceso determinado. Esto se puede hacer en base al tipo de aplicación y al tamaño
del programa. Las desventajas de esta estrategia son de dos tipos: si las reservas resultan ser demasiado pequeñas, va a haber una alta tasa de fallos de página, haciendo que el sistema multiprogramado
completo se ralentice. Si las reservas, por contra, resultan demasiado grandes, habrá muy pocos programas en la memoria principal y habrá mucho tiempo del procesador ocioso o mucho tiempo perdido en swapping.
Asignación variable, ámbito global. Esta combinación es, probablemente, la más sencilla de implementar y ha sido adoptada por un gran número de sistemas operativos. En un momento determinado, existen un número de procesos determinado en la memoria principal, cada uno de los cuales tiene
una serie de marcos asignados. Normalmente, el sistema operativo también mantiene una lista de
marcos libres. Cuando sucede un fallo de página, se añade un marco libre al conjunto residente de un
proceso y se trae la página a dicho marco. De esta forma, un proceso que sufra diversos fallos de página crecerá gradualmente en tamaño, lo cual debería reducir la tasa de fallos de página global del
sistema.
08-Capitulo 8
12/5/05
16:23
Página 371
Memoria virtual
Table 8.4.
371
Gestión del conjunto residente.
Reemplazo Local
Reemplazo Global
Asignación Fija
• El número de marcos asignados
a un proceso es fijo.
• Las páginas que se van a
reemplazar se eligen entre los
marcos asignados al proceso.
• No es posible
Asignación Variable
• El número de marcos asignados
a un proceso pueden variarse
de cuando en cuando.
• Las páginas que se van a
reemplazar se eligen entre los
marcos asignados al proceso.
• Las páginas que se van a
reemplazar se eligen entre todos
los marcos de la memoria
principal esto hace que el
tamaño del conjunto residente
de los procesos varíe.
La dificultad de esta estrategia se encuentra en la elección de los reemplazos cuando no existen
marcos libres disponibles, el sistema operativo debe elegir una página que actualmente se encuentra
en la memoria para reemplazarla. Esta selección se lleva a cabo entre todos los marcos que se encuentran en la memoria principal, a excepción de los marcos bloqueados como son los usados por el
núcleo. Utilizando cualquiera de las políticas vistas en la sección anterior, se tiene que la página seleccionada para reemplazo puede pertenecer a cualquiera de los procesos residentes; no existe ninguna disciplina predeterminada que indique qué proceso debe perder una página de su conjunto residente. Así pues, el proceso que sufre la reducción del tamaño de su conjunto residente no tiene
porqué ser el óptimo.
Una forma de encontrar una contraprestación a los problemas de rendimiento potenciales de la
asignación variable con reemplazo de ámbito global, se centran en el uso de buffering de páginas. De
esta forma, la selección de una página para que se reemplace no es tan significativa, debido a que la
página se puede reclamar si se hace referencia antes de que un nuevo bloque de páginas se sobrescriba.
Asignación variable, ámbito local. La asignación variable con reemplazo de ámbito local intenta
resolver los problemas de la estrategia de ámbito global. Se puede resumir en lo siguiente:
1. Cuando se carga un nuevo proceso en la memoria principal, se le asignan un cierto número de
marcos de página a su conjunto residente, basando en el tipo de aplicación, solicitudes del programa, u otros criterios. Para cubrir esta reserva se utilizará la paginación adelantada o la paginación por demanda.
2. Cuando ocurra un fallo página, la página que se seleccionará para reemplazar pertenecerá al
conjunto residente del proceso que causó el fallo.
3. De vez en cuando, se reevaluará la asignación proporcionada a cada proceso, incrementándose
o reduciéndose para mejorar al rendimiento.
Con esta estrategia, las decisiones relativas a aumentar o disminuir el tamaño del conjunto residente se toman de forma más meditada y se harán contando con los indicios sobre posibles demandas
futuras de los procesos que se encuentran activos. Debido a la forma en la que se realiza esta valoración, esta estrategia es mucho más compleja que la política de reemplazo global simple. Sin embargo,
puede llevar a un mejor rendimiento.
Los elementos clave en la estrategia de asignación variable con ámbito local son los criterios que
se utilizan para determinar el tamaño del conjunto residente y la periodicidad de estos cambios. Una
08-Capitulo 8
372
13/5/05
19:01
Página 372
Sistemas operativos. Aspectos internos y principios de diseño
estrategia específica que ha atraído mucha atención en la literatura es la denominada estrategia del
conjunto de trabajo. A pesar de que la estrategia del conjunto de trabajo pura sería difícil implementar, es muy útil examinarla como referencia para las comparativas.
El conjunto de trabajo es un concepto acuñado y popularizado por Denning [DENN68, DENN70,
DENN80b]; y ha tenido un profundo impacto en el diseño de la gestión de la memoria virtual. El
conjunto de trabajo con parámetro D para un proceso en el tiempo virtual t, W(t, D) es el conjunto de
páginas del proceso a las que se ha referenciado en las últimas D unidades de tiempo virtual.
El tiempo virtual se define de la siguiente manera. Considérese la secuencia de referencias a memoria, r(1), r(2), ... , en las cuales r(i) es la página que contiene la i-esima dirección virtual generada
por dicho proceso. El tiempo se mide en referencias a memoria; así t=1,2,3... mide el tiempo virtual
interno del proceso.
Se considera que cada una de las dos variables de W. La variable D es la ventana de tiempo virtual a través de la cual se observa al proceso. El tamaño del conjunto trabajo será una función nunca
decreciente del tamaño de ventana. El resultado que se muestra en la Figura 8.19 (en base a
[BACH86]), demuestra la secuencia de referencias a páginas para un proceso. Los puntos indican las
unidades de tiempo en las cuales el conjunto de trabajo no cambia. Nótese que para mayor tamaño
ventana, el tamaño del conjunto trabajo también es mayor. Esto se puede expresar en la siguiente relación:
W(t, D+1) … W(t, D)
El conjunto trabajo es también una función del tiempo. Si un proceso ejecuta durante D unidades
de tiempo, y terminando el tiempo utiliza una única página, entonces |W(t, D)|=1. Un conjunto trabajo también puede crecer hasta llegar a las N páginas del proceso si se accede rápidamente a muchas
páginas diferentes y si el tamaño de la ventana lo permite. De esta forma,
1 £ |W(t, D)| £ min(D,N)
La Figura 8.20 indica cómo puede variar el tamaño del conjunto de trabajo a lo largo del tiempo para un valor determinado de D. Para muchos programas, los periodos relativamente estables
del tamaño de su conjunto de trabajo se alternan con periodos de cambio rápido. Cuando un proceso comienza a ejecutar, de forma gradual construye su conjunto de trabajo a medida que hace
referencias a nuevas páginas. Esporádicamente, debido al principio de proximidad de referencia,
el proceso deberá estabilizarse sobre un conjunto determinado de páginas. Los periodos transitorios posteriores reflejan el cambio del programa a una nueva región de referencia. Durante la fase
de transición algunas páginas del antiguo conjunto de referencia permanecerán dentro de la ventana, D, causando un rápido incremento del tamaño del conjunto de trabajo a medida que se van referenciando nuevas páginas. A medida que la ventana se desplaza de estas referencias, el tamaño
del conjunto de trabajo se reduce hasta que contiene únicamente aquellas páginas de la nueva región de referencia.
Este concepto del conjunto de trabajo se puede usar para crear la estrategia del tamaño conjunto
residente:
1. Monitorizando el conjunto de trabajo de cada proceso.
2. Eliminando periódicamente del conjunto residente aquellas páginas que no se encuentran en el
conjunto de trabajo, esto en esencia es una política LRU.
3. Un proceso puede ejecutar sólo si su conjunto trabajo se encuentra en la memoria principal
(por ejemplo, si su conjunto residente incluye su conjunto de trabajo).
08-Capitulo 8
12/5/05
16:23
Página 373
Memoria virtual
Secuencia de
referencias a
páginas
2
Tamaño de ventana, D
3
4
5
24
24
24
24
24
15
24 15
24 15
24 15
24 15
18
15 18
24 15 18
24 15 18
24 15 18
23
18 23
15 18 23
15 18 23
15 18 23
24
23 24
18 23 24
•
•
17
24 17
23 24 17
18 23 24 17
15 18 23 24 17
18
17 18
24 17 18
•
18 23 24 17
24
18 24
•
24 17 18
•
18
•
18 24
•
24 17 18
17
18 17
24 18 17
•
•
17
17
18 17
•
•
15
17 15
17 15
18 17 15
24 18 17 15
24
15 24
17 15 24
17 15 24
•
17
24 17
•
•
17 15 24
24
•
24 17
•
•
18
24 18
17 24 18
17 24 18
15 17 24 18
Figura 8.19.
373
Conjunto de trabajo de un proceso definido por el tamaño de ventana.
Esta estrategia funciona debido a que parte de un principio aceptado, el principio de proximidad
de referencia, y lo explota para conseguir una estrategia de la gestión de la memoria que minimice los
fallos de página. Desgraciadamente, persisten varios problemas en la estrategia del conjunto trabajo:
1. El pasado no siempre predice el futuro. Tanto el tamaño como la pertenencia al conjunto de
trabajo cambian a lo largo del tiempo (por ejemplo, véase la Figura 8.20).
2. Una medición verdadera del conjunto de trabajo para cada proceso no es practicable. Sería necesario, por cada proceso, asignar un sello de tiempo a cada referencia a una página que asigne
el tiempo virtual del proceso y que mantenga una lista ordenada por tiempo de las páginas de
cada uno de ellos.
3. El valor óptimo de D es desconocido y en cualquier caso puede variar.
Sin embargo el espíritu de esta estrategia sí es válido, el gran número de sistemas operativos intentan aproximarse a la estrategia del conjunto de trabajo. La forma de hacer esto es centrarse no en las referencias exactas a páginas y sí en la tasa de fallos de página. Como se ilustra en la Figura 8.11b, la tasa
de fallos de páginas cae a medida que se incrementa el tamaño del conjunto residente de un proceso. El
tamaño del conjunto de trabajo debe caer en un punto de esta curva, indicado por W en la figura. Por
tanto, en lugar de monitorizar el tamaño del conjunto trabajo de forma directa se pueden alcanzar resultados comparables monitorizando la tasa de fallos de página. El razonamiento es el siguiente: si la tasa
374
12/5/05
16:23
Página 374
Sistemas operativos. Aspectos internos y principios de diseño
D
Tamaño del conjunto de trabajo
08-Capitulo 8
Tiempo
Transitorio
Transitorio
Estable
Figura 8.20.
Transitorio
Estable
Transitorio
Estable
Estable
Gráfico típico del tamaño del conjunto de trabajo [MAEK87].
de fallos de páginas de un proceso está por debajo de un determinado límite, el sistema de forma global
se puede beneficiar de asignar un tamaño de conjunto residente menor para este proceso (debido a que
se dispone de un mayor número de marcos de páginas libres para otros procesos) sin dañar al proceso
en cuestión (causando un incremento de sus fallos de página). Si la tasa de fallos de página entonces
está por encima de un umbral máximo, el proceso puede beneficiarse de un incremento en el tamaño de
su conjunto residente (y que así se produzca un menor número de fallos) sin degradar el sistema.
Un algoritmo que sigue esta estrategia es el algoritmo de frecuencia de fallos de página (page
fault frequency – PFF) [CHU72, GUPT78]. El algoritmo necesita un bit de usado que se encuentre
asociado a cada página de memoria. Este bit se pondrá a 1 cuando se haya accedido a la página.
Cuando se produce un fallo de página, el sistema operativo anotará el tiempo virtual desde el último
fallo de página para dicho proceso; esto se puede realizar manteniendo un contador de las referencias
a páginas. Se fija un umbral F. Si la diferencia de tiempo con el último fallo de página es menor que
éste, entonces se añade una página al conjunto residente del proceso. En otro caso, se descartan todas
los páginas con el bit de usado a 0, y se reduce el tamaño del conjunto residente de forma acorde.
Mientras tanto, se pone a 0 el bit de usado del resto de las páginas. Esta estrategia se puede refinar
usando dos umbrales: un umbral máximo que se utiliza para disparar el crecimiento del conjunto residente, y un límite inferior que se utiliza para disparar la reducción de tamaño del conjunto residente.
El tiempo entre fallos de página es recíproco a la tasa de fallos de página. A pesar de ello parecería mejor mantener una medida a lo largo de la ejecución de la tasa de fallos de página, sin embargo
la utilización de una medida de tiempo sencilla es un compromiso razonable que permite que las decisiones relativas al tamaño del conjunto residente se basen en la tasa de fallos de página. Si una estrategia de este estilo se complementa con el buffering de páginas, se puede conseguir como resultado
un rendimiento bastante bueno.
Sin embargo, existe un fallo grave en la estrategia adoptada por PFF, que hace que su comportamiento no sea bueno durante los periodos transitorios cuando se produce un desplazamiento hacia
08-Capitulo 8
12/5/05
16:23
Página 375
Memoria virtual
375
una nueva región de referencia. Con PFF ninguna página sale del conjunto residente antes de que hayan pasado F unidades de tiempo virtual desde su última referencia. Durante estos periodos entre dos
regiones de referencia, la rápida sucesión de fallos de página hace que el conjunto residente del proceso crezca antes de que las páginas de la antigua región de referencia se expulsen; los súbitos picos
en las solicitudes de memoria pueden producir desactivaciones y reactivaciones de procesos innecesarias, que se corresponden con cambios de proceso y sobrecargas de swapping no deseables.
Una estrategia que intenta manejar este fenómeno de transición entre regiones de referencia con
una sobrecarga relativamente baja comparado con PFF es la política de conjunto de trabajo con
muestreo sobre intervalos variables (variable-interval sampled working set – VSWS) [FERR83].
La política VSWS evalúa el conjunto de trabajo del proceso en instantes de muestreo basados en el
tiempo virtual transcurrido. Al comienzo del intervalo de muestreo, los bits de usado de las páginas
residentes de procesos se ponen a 0; al final, sólo las páginas a las que se ha hecho referencia durante
el intervalo mantendrán dicho bit a 1; estas páginas se mantienen dentro del conjunto residente del
proceso a lo largo del siguiente intervalo, mientras que las otras se descartan. De esta forma el tamaño del conjunto residente solamente decrecerá al final del intervalo. Durante cada intervalo, todas las
páginas que han causado fallo se añaden al conjunto residente; de esta forma el conjunto residente
mantiene un tamaño fijo o crece durante el intervalo.
La política VSWS toma tres diferentes parámetros:
M: la duración mínima del intervalo de muestreo
L: la duración máxima del intervalo de muestreo
Q: el número de fallos de página que se permite que ocurran entre dos instantes de muestreo
La política VSWS es la siguiente:
1. Si el tiempo virtual entre el último muestreo alcanza L, se suspende el proceso y se analizan
los bits de usado.
2. Si, antes de que el tiempo virtual transcurrido llegue a L, ocurren Q fallos de página,
a) Si el tiempo virtual desde el último muestreo es menor que M, se espera hasta que el tiempo virtual transcurrido alcance dicho valor para suspender el proceso y analizar los bits de
usado.
b) Si el tiempo virtual desde el último muestreo es mayor o igual a M, se suspende el proceso
y se analizan sus bits de usado.
Los valores de los parámetros se toman de forma que el muestreo se dispare habitualmente cuando ocurre el fallo de página Q después del último muestreo (caso 2b). Los otros dos parámetros (M y
L) proporcionan fronteras de protección para condiciones excepcionales. La política VSWS intenta
reducir el pico de solicitudes de memoria causadas por una transición abrupta entre dos áreas de referencia, incrementando para ello la frecuencia de muestreo, y por tanto la tasa a la cual las páginas no
utilizadas se descartan del conjunto residente, cuando la tasa de fallos de página, se incrementa. La
experiencia con esta técnica en el sistema operativo del mainframe de Bull, GCOS 8, indica que este
mecanismo es tan sencillo de implementar como PFF y mucho más efectivo [PIZZ89].
POLÍTICA DE LIMPIEZA
La política de limpieza es la opuesta a la política de recuperación; se encarga de determinar cuándo
una página que está modificada se debe escribir en memoria secundaria. Las dos alternativas más co-
08-Capitulo 8
376
12/5/05
16:23
Página 376
Sistemas operativos. Aspectos internos y principios de diseño
munes son la limpieza bajo demanda y la limpieza adelantada. Con la limpieza bajo demanda, una
página se escribe a memoria secundaria sólo cuando se ha seleccionado para un reemplazo. En la política de la limpieza adelantada se escribe las páginas modificadas antes de que sus marcos de páginas se necesiten, de forma que las páginas se puedan escribir en lotes.
Existe un peligro en perseguir cualquiera de estas dos políticas hasta el extremo. Con la limpieza
adelantada, una página se escribe aunque continúe en memoria principal hasta que el algoritmo de reemplazo páginas indique que debe eliminarse. La limpieza adelantada permite que las páginas se escriban en lotes, pero tiene poco sentido escribir cientos o miles de páginas para darnos cuenta de que
la mayoría de ellas van a modificarse de nuevo antes de que sean reemplazadas. La capacidad de
transferencia de la memoria secundaria es limitada y no se debe malgastar con operaciones de limpieza innecesarias.
Por otro lado, con la limpieza bajo demanda, la escritura de una página modificada colisiona con,
y precede a, la lectura de una nueva página. Esta técnica puede minimizar las escrituras de páginas,
pero implica que el proceso que ha sufrido un fallo página debe esperar a que se completen dos transferencias de páginas antes de poder desbloquearse. Esto implica una reducción en la utilización del
procesador.
Una estrategia más apropiada incorpora buffering páginas. Esto permite adoptar la siguiente política: limpiar sólo las páginas que son reemplazables, pero desacoplar las operaciones de limpieza y
reemplazo. Con buffering de páginas, las páginas reemplazadas pueden ubicarse en dos listas: modificadas y no modificadas. Las páginas en la lista de modificadas pueden escribirse periódicamente por
lotes y moverse después a la lista de no modificadas. Una página en la lista de no modificadas puede,
o bien ser reclamaba si se referencia, o perderse cuando su marco se asigna a otra página.
CONTROL DE CARGA
El control de carga determina el número de procesos que residirán en la memoria principal, eso se denomina el grado de multiprogramación. La política de control de carga es crítica para una gestión de
memoria efectiva. Si hay muy pocos procesos residentes a la vez, habrá muchas ocasiones en las cuales todos los procesos se encuentren bloqueados, y gran parte del tiempo se gastarán realizando swapping. Por otro lado, si hay demasiados procesos residentes, entonces, de media, el tamaño de conjunto
residente de cada proceso será poco adecuado y se producirán frecuentes fallos de página. El resultado es el trasiego (thrashing).
Grado de multiprogramación. El trasiego se muestra en la Figura 8.21. A medida que el nivel de
multiprogramación aumenta desde valores muy pequeños, cabría esperar que la utilización del procesador aumente, debido a que hay menos posibilidades de que los procesos residentes se encuentren
bloqueados. Sin embargo, se alcanza un punto en el cual el tamaño de conjunto residente promedio
no es adecuado. En este punto, el número de fallos de páginas se incrementa de forma dramática, y la
utilización del procesador se colapsa.
Existen numerosas formas de abordar este problema. Un algoritmo del conjunto de trabajo o de
frecuencia de fallos de página incorporan, de forma implícita, control de carga. Sólo aquellos procesos cuyo conjunto residente es suficientemente grande se les permite ejecutar. En la forma de proporcionar el tamaño de conjunto residente necesario para cada proceso activo, se encuentra la política
que automática y dinámicamente determina el número de programas activos.
Otra estrategia, sugerida por Denning [DENN80b], se conoce como el criterio de L = S, que ajusta el nivel de multiprogramación de forma que el tiempo medio entre fallos de página se iguala al
tiempo medio necesario para procesar un fallo de página. Los estudios sobre el rendimiento indican
12/5/05
16:23
Página 377
Memoria virtual
377
Utilización del procesador
08-Capitulo 8
Grado de multiprogramación
Figura 8.21.
Efectos de la multiprogramación.
que éste es el punto en el cual la utilización del procesador es máxima. Una política que presenta un
efecto similar, propuesta en [LERO76], es el criterio del 50%, que intenta mantener la utilización del
dispositivo de paginación aproximadamente al 50%. De nuevo, los estudios sobre el rendimiento indican que éste es el punto para el cual la utilización del procesador es máxima.
Otra alternativa es adaptar el algoritmo de reemplazo de páginas del reloj descrito anteriormente
(Figura 8.16). [CARR84] describe la técnica, usando un ámbito global, que implica monitorizar la
tasa a la cual el puntero recorre el buffer circular de marcos. Si la tasa está por debajo de un nivel de
umbral determinado, éste indica alguna (o las dos) circunstancias siguientes:
1. Si están ocurriendo pocos fallos de página, que implican pocas peticiones para avanzar este
puntero.
2. Por cada solicitud, el número de marcos promedio que se recorren por el puntero es pequeño,
que indica que hay muchas páginas residentes a las que no se hace referencia y que son realmente reemplazables.
En ambos casos, el grado de multiprogramación puede incrementarse con seguridad. Por otro
lado, si la tasa de recorrido circular del puntero supera un umbral máximo, indica que la tasa de fallos
de página es alta o que hay dificultad para encontrar páginas reemplazables, lo cual implica que el
grado de multiprogramación es demasiado alto.
Suspensión de procesos. Si se va a reducir el grado de multiprogramación, uno o más de los
procesos actualmente residentes deben suspenderse (enviarlos a swap). [CARR84] proporciona seis
posibilidades:
• Procesos con baja prioridad. Esto implementa una decisión de la política de activación y no
se encuentra relacionada con cuestiones de rendimiento.
• Procesos que provoca muchos fallos. La razón es que hay una gran probabilidad de que la tarea que causa los fallos no tenga su conjunto de trabajo residente, y el rendimiento sufrirá menos si dicha tarea se suspende. Adicionalmente, esta elección trae una ventaja asociada debido
que se bloquea un proceso que estaría a punto de bloquearse de cualquier manera, y que si se
elimina se evita por tanto la sobrecarga del reemplazo de páginas y la operación de E/S.
08-Capitulo 8
378
12/5/05
16:23
Página 378
Sistemas operativos. Aspectos internos y principios de diseño
• Proceso activado hace más tiempo. Éste es el proceso que tiene menor probabilidad de tener
su conjunto de trabajo residente.
• Proceso con el conjunto residente de menor tamaño. Éste requerirá un menor esfuerzo para
cargarse de nuevo. Sin embargo, penaliza a aquellos programas con una proximidad de referencias muy fuerte.
• Proceso mayor. Éste proporciona un mayor número de marcos libres en una memoria que se
encuentra sobrecarga, haciendo que futuras desactivaciones sean poco probables a corto plazo.
• Proceso con la mayor ventana de ejecución restante. En la mayoría de esquemas de activación de procesos, un proceso sólo puede ejecutarse durante una determinada rodaja de tiempo
antes de recibir la interrupción y situarse al final de la lista de Listos. Esta estrategia se aproxima a la disciplina de activación de primero el proceso con menor tiempo en ejecución.
Es como en otras muchas áreas del diseño de sistemas operativos, la selección de la política más
apropiada es una cuestión a considerar y depende de muchos otros factores de diseño en el sistema
operativo así como de las características de los programas que se ejecutarán.
8.3. GESTIÓN DE MEMORIA DE UNIX Y SOLARIS
Debido a que UNIX pretende ser un sistema independiente de la máquina, su esquema de gestión de
memoria variará de un sistema a otro. En las primeras versiones, UNIX utilizaba particionamiento
variable sin ningún esquema de memoria virtual. Las implantaciones actuales de UNIX y Solaris utilizan la memoria virtual paginada.
En SVR4 y Solaris, existen dos esquemas de gestión de memoria separados. El sistema de paginación proporciona las funcionalidades de la memoria virtual para asignar marcos de página en la
memoria principal a los diferentes procesos y también asignar marcos de página a buffers de bloques
de disco. A pesar de que éste es un esquema de gestión de memoria efectivo para los procesos de
usuario y la E/S de disco, un esquema de la memoria virtual paginada es menos apropiado para gestionar la asignación de memoria del núcleo. Por esas cuestiones, se utiliza un asignador de memoria
del núcleo. Se van a examinar estos dos mecanismos en orden.
SISTEMA DE PAGINACIÓN
Estructuras de datos. Para la memoria virtual paginada, UNIX utiliza varias estructuras de datos
que, con pequeñas diferencias, son independientes de la máquina (Figura 8.22 y Tabla 8.5):
• Tabla de páginas. Habitualmente, habrá una tabla de páginas por proceso, con una entrada
por cada página de memoria virtual de dicho proceso.
• Descriptor de bloques de disco. Asociado a cada página del proceso hay una entrada en esta
tabla que indica la copia en disco de la página virtual.
• Tabla de datos de los marcos de página. Describe cada marco de memoria real y se indexa
por medio de un número marco. El algoritmo de reemplazo usa esta tabla.
• Tabla de utilización de swap. Existe una tabla de uso de swap por cada dispositivo de intercambio, con una entrada por cada página de dicho dispositivo.
La mayoría de los campos definidos en la Tabla 8.5 proporcionan su propia descripción. Unos
pocos de ellos requieren una explicación adicional. El campo Edad en la entrada de la tabla de pági-
08-Capitulo 8
12/5/05
16:23
Página 379
Memoria virtual
Número de marco de página
379
Copy Modi- ReferenProteValida
on
ciada
ficada
gida
write
Edad
(a) Entrada de la tabla de páginas
Número de dispositivo
de swap
Número de bloque
del dispositivo
Tipo de
almacenamiento
(b) Descriptor de bloques de disco
Estado de
la página
Contador de Dispositivo
referencias
lógico
Número de
bloque
Puntero
datos MP
(c) Entrada en la tabla de marcos de página
Contador de
referencias
Número de la unidad de
almacenamiento/página
(d) Entrada en la tabla de uso de swap
Figura 8.22.
Formatos de gestión de memoria de UNIX SVR4.
nas es un indicador de cuánto tiempo hace que el programa no ha hecho referencia a este marco. Sin
embargo, el número de bits y la frecuencia de actualización de este campo son dependientes de la implementación. Por tanto, no hay un uso universal de este campo por parte de UNIX para las políticas
de reemplazo de páginas.
El campo de Tipo de Almacenamiento en el descriptor de bloques de disco se necesita por la siguiente razón: cuando un fichero ejecutable se usa por primera vez para crear un nuevo proceso, sólo
parte del programa y de los datos de dicho fichero se cargan en la memoria real. Más tarde, según se
van dando fallos de página, nuevas partes del programa de los datos se irán cargando. Es sólo en el
momento de la carga inicial cuando se crean las páginas de memoria virtual y se las asocia con posiciones en uno de los dispositivos que se utiliza para ello. En ese momento, el sistema operativo indica
si es necesario limpiar (poner a 0) las posiciones en el marco de página antes de la primera carga de
un bloque de programa o datos.
Reemplazo de páginas. La tabla de marcos de página se utiliza para el reemplazo de las páginas.
Se usan diferentes punteros para crear listas dentro esta tabla. Todos los marcos disponibles se enlazan en una lista de marcos libres disponibles para traer páginas. Cuando el número de marcos disponibles cae por debajo un determinado nivel, el núcleo quitará varios marcos para compensar.
El algoritmo de reemplazo páginas usado en SVR4 es un refinamiento del algoritmo del reloj (Figura 8.16) conocido como el algoritmo del reloj con dos manecillas (Figura 8.23), el algoritmo utiliza
el bit de referencia en la entrada de la tabla de páginas por cada página en memoria que sea susceptible de selección (no bloqueada) para un reemplazo. Este bit se pone a 0 cuando la página se trae por
primera vez y se pone a 1 cuando se ha hecho referencia a la página para lectura o escritura. Una de
las manecillas del algoritmo del reloj, la manecilla delantera, recorre las páginas de la lista de páginas
seleccionables y pone el bit de referencia a 0 para cada una de ellas. Un instante después, la manecilla
16:23
Página 380
Sistemas operativos. Aspectos internos y principios de diseño
trasera recorre la misma lista y verifica el bit de referencia. Si el bit está puesto a 1, entonces se ha
hecho referencia a la página desde el momento en que pasó la manecilla delantera por ahí, estos marcos se saltan. Si el bit está a 0, entonces no se ha hecho referencia a dicha página en el intervalo de
tiempo de la visita de las dos manecillas; estas páginas se colocan en la lista de páginas expulsables.
Dos parámetros determinan la operación del algoritmo:
• La tasa de recorrido. La tasa a la cual las dos manecillas recorren la lista de páginas, en páginas por segundo.
• La separación entre manecillas. El espacio entre la manecillas delantera y la trasera.
Estos dos parámetros vienen fijados a unos valores por omisión en el momento de arranque, dependiendo de la cantidad de memoria física. El parámetro tasa de recorrido puede modificarse para
responder a cambios en las diferentes condiciones del sistema. El parámetro puede variar linealmente entre los valores de recorrido lento (slowscan) y recorrido rápido (fastscan) (fijados en el momento de la configuración) dependiendo de que la cantidad de memoria libre variando entre los valores
lotsfree y minfree. En otras palabras, a medida que la memoria libre se reduce, las manecillas del reloj se mueven más rápidamente para liberar más páginas. El parámetro de separación entre manecillas indicado por el espacio entre la manecilla delantera y la trasera, por tanto, junto con la tasa de
recorrido, indica la ventana de oportunidad para usar una página antes de que sea descartable por
falta de uso.
Asignador de memoria de núcleo. El núcleo, a lo largo de su ejecución, genera y destruye pequeñas tablas y buffers con mucha frecuencia, cada uno de los cuales requiere la reserva de memoria
dinámica. [VAHA96] muestra varios ejemplos:
• El encaminamiento para traducción de una ruta puede reservar un buffer para copiar la ruta
desde el espacio usuario.
n entre m aneci
lla s
Final de la lista
de páginas
a ra ci ó
380
12/5/05
Sep
08-Capitulo 8
Figura 8.23.
Comienzo de la
lista de páginas
M
de ane
lan cil
ter la
a
la
cil
ne a
a
M ser
tra
Algoritmo de reemplazo de paginas del reloj de dos manecillas.
08-Capitulo 8
12/5/05
16:23
Página 381
Memoria virtual
381
Tabla 8.5. Parámetros de gestión de memoria de UNIX SVR4.
Entrada de la tabla de páginas
Número de marco de página
Indica el marco de la memoria real.
Edad
Indica cuánto tiempo ha pasado la página en la memoria sin haberse referenciado. La longitud y
contenidos de este campo dependen del procesador.
Copy on write8
Puesto a 1 cuando más de un proceso comparte esta página. Si uno de los procesos escribe en
esta página, se debe hacer primero una copia aparte de la misma para todos los procesos que
aún comparten la página original. Esta funcionalidad permite demorar la operación de copia hasta que sea completamente necesaria y evitar los casos en los cuales no llega a serlo.
Modificada
Indica si la página se ha modificado.
Referenciada
Indica que se ha hecho referencia a esta página. Este bit se pone a 0 cuando la página se carga por
primera vez y se puede reiniciar periódicamente por parte del algoritmo de reemplazo de páginas.
Válida
Indica si la página se encuentra en la memoria principal.
Protegida
Indica si se permite la operación escritura o no.
Descriptor de bloques de disco
Número de dispositivo de swap
Número de dispositivo lógico de almacenamiento secundario que almacena la página correspondiente. Se permite que existan más de un dispositivo para ser utilizados como swap.
Número de bloque del dispositivo
Posición del bloque de la página en el dispositivo de swap.
Tipo de almacenamiento
El dispositivo de almacenamiento puede ser una unidad de swap o un fichero ejecutable. En el último caso, existe una indicación que denota si la memoria virtual asignada debe borrarse o no.
Entrada en la tabla de marcos de página
Estado de la página
Indica si este marco se encuentra disponible o si está asociado a una página. En este último caso,
especifica si la página se encuentra en dispositivo de swap, en fichero ejecutable, o en una operación de E/S.
Contador de referencias
Número de procesos que hacen referencia a esta página.
Dispositivo lógico
Dispositivo lógico que contiene una copia de la página.
Número de bloque
Posición del bloque de la copia de la página sobre el dispositivo lógico.
Punteros datos MP
Puntero a otros datos que apuntan a otras entradas en la tabla de marcos de página. Con este
puntero se puede construir la lista de páginas libres o una lista hash de páginas.
Entrada en la tabla de uso de swap
Contador de referencias
Número de entradas en la tabla de página que apunta a esta página en el dispositivo de swap.
Número de la unidad de almacenamiento/página
Identificador de la página en la unidad almacenamiento
8
N. del T. El término copy on write se ha dejado en inglés puesto que es la forma más habitual de denotarlo, incluso en la literatura en castellano.
08-Capitulo 8
382
12/5/05
16:23
Página 382
Sistemas operativos. Aspectos internos y principios de diseño
• La rutina allocb() reserva un buffer para flujos de tamaño arbitrario.
• Muchas implementaciones de UNIX reservan estructuras zombie para recoger el estado de salida e información de la utilización de recursos sobre procesos finalizados.
• En SVR4 y en Solaris, el núcleo reserva muchos objetos (como estructuras del proc, v-nodos,
y bloques de descripción de fichero) de forma dinámica y bajo demanda.
La mayoría de sus bloques son significativamente más pequeños que el tamaño típico de una página de la máquina, y por tanto el mecanismo de paginación sería muy deficiente a la hora de reserva
de la memoria dinámica del núcleo. Para el caso de SVR4, se utiliza una modificación del sistema
buddy, descrito en la Sección 7.2.
En los sistemas buddy, el coste de reservar y liberar un bloque de la memoria es bajo comparado con las políticas de mejor ajuste y primer ajuste [KNUT97]. De esta forma, en el caso de la gestión de la memoria del núcleo, las operaciones de reserva y liberación se deben realizar lo más rápido posible. La desventaja de los sistemas buddy es el tiempo necesario para fragmentar y reagrupar
bloques.
Barkley y Lee de AT&T propusieron una variación conocida como sistema buddy perezoso
[BARK89], y ésta es la técnica adoptada por SVR4. Los autores observaron que UNIX a menudo
muestra un comportamiento estable en las solicitudes de memoria de núcleo; esto es, la cantidad de
peticiones para bloques de un determinado tamaño varía ligeramente a lo largo del tiempo. Por tanto,
si un bloque del tamaño 2i se libera e inmediatamente se reagrupa con su vecino en un bloque del tamaño 2i+1, la próxima vez que el núcleo solicite un bloque de tamaño 2i, implicará la necesidad de dividir el bloque de tamaño mayor de nuevo. Para evitar esta agrupación innecesaria y su posterior división, el sistema buddy perezoso pospone la reagrupación hasta el momento en el que parezca que
resulta necesaria, y en ese momento intenta reagrupar el mayor número de bloques posibles.
El sistema buddy perezoso usa los siguientes parámetros:
Ni = número actual de bloques de tamaño 2i.
Ai = número actual de bloques de tamaño 2i que se encuentran reservados (ocupados).
Gi = número actual de bloques de tamaño 2i que están libres globalmente; estos son los bloques
que se pueden seleccionar para ser reagrupados; si el vecino de uno de estos bloques se
encuentra libre, entonces ambos bloques se pueden agrupar en un bloque libre global de
tamaño 2i+1. Los bloques libres (huecos) en un sistema buddy estándar pueden considerarse libres globales.
Li = número actual de bloques de tamaño 2i que se encuentran libres localmente; estos son los
bloques que no se encuentran como seleccionables para su grabación. Incluso si el vecino
de dicho bloque se encuentra libre, los dos bloques no se reagrupan. En lugar de eso, los
bloques libres locales se mantienen a la espera de una petición futura para un bloque de
dicho tamaño.
La siguiente relación se mantiene:
Ni = Ai + Gi + Li
En general, el sistema buddy perezoso intenta mantener un caudal de bloques libres locales y únicamente solicita la reagrupación si el número de bloques libres locales supera un determinado límite.
Si hay muchos bloques libres locales, entonces existe la posibilidad de que falten bloques libres en el
siguiente nivel. La mayoría del tiempo, cuando se libera un bloque, la reagrupación no se realiza, de
08-Capitulo 8
12/5/05
16:23
Página 383
Memoria virtual
383
forma que se minimizan los costes de gestión y de operaciones. Cuando se va a reservar un bloque,
no se hace distinción alguna entre bloques libres locales y globales; de la misma forma, esto minimiza la gestión.
El criterio que se utiliza para la reagrupación es que el número de bloques libres locales del tamaño determinado no puede exceder el número de bloques reservados para ese tamaño (por ejemplo, se
debe tener Li ≥ Ai). Ésta es una guía razonable para evitar el crecimiento de bloques libres locales, en
los experimentos llevados a cabo por [BARK89] se confirman los resultados de que este esquema
proporciona unas mejoras considerables.
Para implementar este esquema, los autores de definen una variable de demora de la siguiente
forma:
Di = Ai – Li = Ni – 2Li – Gi
La Figura 8.24 muestra este algoritmo.
Valor inicial de Di es igual a 0
después de la operación, el valor de Di se actualiza de la siguiente manera:
(I) si la siguiente operación es una solicitud de reservar un bloque:
si hay bloques libres, se selecciona uno a reserva
si el bloque seleccionado se encuentra libre de forma local
entonces Di := Di + 2
sino Di := Di + 1
en otro caso
primero se toman dos bloques dividiendo un bloque mayor en dos (operación recursiva)
se reserva uno y se marca el otro como libre de forma local
Di permanece sin cambios (pero se puede cambiar D para otros tamaños de bloque debido a la llamada recursiva)
(II) si la siguiente operación es una liberación de un bloque:
Caso Di ≥ 2
se marca como libre de forma local y se libera localmente
Di := Di – 2
Caso Di = 1
se marca como libre de forma global y se libera globalmente; reagrupación si es posible
Di := 0
Caso Di = 0
se marca como libre de forma global y se libera globalmente; reagrupación si es posible
se selecciona un bloque libre local de tamaño 2i y se libera de forma global; reagrupación si es posible
Di := 0
Figura 8.24.
Algoritmo del sistema buddy perezoso.
08-Capitulo 8
384
12/5/05
16:23
Página 384
Sistemas operativos. Aspectos internos y principios de diseño
8.4. GESTIÓN DE MEMORIA EN LINUX
Linux comparte muchas de las características de los esquemas de gestión de la memoria de otras implementaciones de UNIX pero incorpora sus propias características. Por encima de todo, el esquema
de gestión de la memoria de Linux es bastante complejo [DUBE98].
En esta sección, vamos a dar una ligera visión general de dos de los principales aspectos de la gestión
de la memoria en Linux: la memoria virtual de los procesos y la asignación de la memoria del núcleo.
MEMORIA VIRTUAL EN LINUX
Direccionamiento de la memoria virtual. Linux usa una estructura de tablas de páginas de tres
niveles, consistente en los siguientes tipos de tablas (cada tabla en particular tiene el tamaño de una
página):
• Directorio de páginas. Un proceso activo tiene un directorio de páginas único que tiene el tamaño de una página. Cada entrada en el directorio de páginas apunta a una página en el directorio intermedio de páginas. El directorio de páginas debe residir en la memoria principal para
todo proceso activo.
• Directorio intermedio de páginas. El directorio intermedio de páginas se expande a múltiples páginas. Cada entrada en el directorio intermedio páginas apunta a una página que contiene una tabla de páginas.
• Tabla de páginas. La tabla de páginas también puede expandirse a múltiples páginas. Cada
entrada en la tabla de páginas hace referencia a una página virtual del proceso.
Para utilizar esta estructura de tabla de páginas de tres niveles, una dirección virtual en Linux
se puede ver cómo consistente en cuatro campos (Figura 8.25) el campo más a la izquierda (el más
significativo) se utiliza como índice en el directorio de páginas. El siguiente campo sirve como índice en el directorio intermedio de páginas. El tercer campo se utiliza para indexar en la tabla de
páginas. Y con el cuarto campo se proporciona el desplazamiento dentro de la página de la memoria seleccionada.
La estructura de la tabla de páginas en Linux es independiente de plataforma y se diseñó para
acomodarse al procesador Alpha de 64 bits, que proporciona soporte hardware para tres niveles de
páginas. Con direcciones de 64 bits, la utilización de únicamente dos niveles de páginas en una arquitectura Alpha resultaría unas tablas de páginas y unos directorios de gran tamaño. La arquitectura
Pentium/x86 de 32 bits tiene un sistema hardware de paginación de dos niveles. El software de Linux
se acomoda al esquema de dos niveles definiendo el tamaño del directorio intermedio de páginas
como 1. Hay que resaltar que todas las referencias a ese nivel extra de indirección se eliminan en la
optimización realizada en el momento de la compilación, no durante la ejecución. Por tanto, no hay
ninguna sobrecarga de rendimiento por la utilización de un diseño genérico de tres niveles en plataformas que sólo soportan dos niveles.
Reserva de páginas. Para mejorar la eficiencia de la lectura y escritura de páginas en la memoria
principal, Linux define un mecanismo para manejar bloques de páginas contiguas que se proyectarán
sobre bloques de marcos de página también contiguos. Con este fin, también se utiliza el sistema
buddy. El núcleo mantiene una lista de marcos de página contiguos por grupos de un tamaño fijo; un
grupo puede consistir en 1, 2, 4, 8, 16, o 32 marcos de páginas. A lo largo del uso, las páginas se asignan y liberan de la memoria principal, los grupos disponibles se dividen y se juntan utilizando el algoritmo del sistema buddy.
08-Capitulo 8
12/5/05
16:23
Página 385
Memoria virtual
385
Dirección virtual
Directorio global
Directorio intermedio
Tabla de páginas
Tabla de
páginas
Directorio
intermedio
de páginas
Directorio
de páginas
Registro
cr3
Desplazamiento
Marco de
página en
memoria
física
Figura 8.25.
Traducción de direcciones en el esquema de memoria virtual de Linux.
Algoritmo de reemplazo de páginas. El algoritmo de reemplazo de páginas de Linux se basaba
en el algoritmo del reloj descrito en la Sección 8.2 (véase Figura 8.16). En el algoritmo del reloj sencillo, se asocia un bit de usado y otro bit de modificado con cada una de las páginas de memoria principal. En el esquema de Linux, el de usado se reemplaza por una variable de 8 bits. Cada vez que se
accede a una página, la variable se incrementa. En segundo plano, Linux recorre periódicamente la
lista completa de páginas y decrementa la variable de edad de cada página a medida que va rotando
por todas ellas en la memoria principal. Una página con una edad de 0 es una página «vieja» a la que
no se ha hecho referencia desde hace algún tiempo y es el mejor candidato para un reemplazo. Cuando el valor de edad es más alto, la frecuencia con la que se ha accedido a la página recientemente es
mayor y, por tanto, tiene menor posibilidad de elegirse para un reemplazo de esta forma, el algoritmo
de Linux es una variante de la política LRU.
RESERVA DE MEMORIA DEL NÚCLEO
La gestión de la memoria del núcleo se realiza en base a los marcos de página de la memoria principal. Su función básica es asignar y liberar marcos para los diferentes usos. Los posibles propietarios
de un marco incluyen procesos en espacio de usuario (por ejemplo, el marco es parte de la memoria
virtual de un proceso que se encuentra actualmente residiendo en la memoria real), datos del núcleo
reservados dinámicamente, código estático del núcleo, y la cache de páginas9.
Los fundamentos de la reserva de memoria de núcleo para Linux son los mecanismos de reserva de páginas ya usados para la gestión de la memoria virtual de usuario. Como en el caso del
esquema de la memoria virtual, se utiliza el algoritmo buddy de forma que la memoria del núcleo
9
La cache de páginas tiene propiedades similares a un buffer de disco, descrito en este capítulo, así como de cache disco, que
se verá en el Capítulo 11. Se pospone la discusión sobre la cache de páginas de Linux a dicho Capítulo 11.
08-Capitulo 8
386
12/5/05
16:23
Página 386
Sistemas operativos. Aspectos internos y principios de diseño
se pueda reservar y liberar en unidades de una o más páginas. Debido a que el tamaño mínimo de
memoria que se pueda reservar de esta forma es una página, la reserva de páginas únicamente sería insuficiente debido a que el núcleo requiere pequeños fragmentos que se utilizarán durante un
corto periodo de tiempo y que son de diferentes tamaños. Para ajustarse a estos pequeños tamaños, Linux utiliza un esquema conocido como asignación por láminas (slab allocation)
[BONW94] dentro una página ya reservada. En una máquina Pentium/x86, el tamaño página es de
4 Kbytes y los fragmentos dentro una página se pueden asignar en tamaños de 32, 64, 128, 252,
508, 2040, y 4080 bytes.
La asignación por láminas es relativamente compleja y no se va a examinar en detalle aquí; una
buena descripción se pueda encontrar en [VAHA96]. En esencia, Linux mantiene un conjunto de listas enlazadas, una para cada tamaño del fragmento. Todos los fragmentos se pueden dividir y agregar
de una manera similar a la indicada por el algoritmo buddy, y también se pueden mover entre las diferentes listas de la forma correspondiente.
8.5. GESTIÓN DE MEMORIA EN WINDOWS
El gestor de memoria virtual en Windows controla la forma en la que se reserva la memoria y cómo
se realiza la paginación. El gestor de memoria se ha diseñado para funcionar sobre una variada gama
de plataformas y para utilizar tamaños de páginas que van desde los 4 Kbytes hasta los 64 Kbytes.
Las plataformas Intel, PowerPC, y MIPS tienen 4096 bytes por página y las plataformas DEC Alpha
tienen 8192 bytes por página.
MAPA DE DIRECCIONES VIRTUALES EN WINDOWS
Cada proceso de usuario en Windows puede ver un espacio de direcciones independiente de 32 bits,
permitiendo 4 Gbytes de memoria por proceso. Por omisión, una parte de esta memoria se encuentra
reservada para el sistema operativo, de forma que en realidad cada usuario dispone de 2 Gbytes de espacio virtual de direcciones disponibles y todos los procesos comparten los mismos 2 Gbytes de espacio de sistema. Existe una opción que permite que el espacio de direcciones crezca hasta los 3 Gbytes, dejando un espacio de sistema de únicamente 1 Gbytes. En la documentación de Windows se
indica que esta característica se incluyó para dar soporte a aplicaciones que requieren un uso intensivo de grandes cantidades de memoria en servidores con memorias de varios gigabytes, en los cuales
un espacio de direcciones mayor puede mejorar drásticamente el rendimiento de aplicaciones de soporte como la decisión o data mining.
La Figura 8.26 muestra el espacio de direcciones virtuales que, por efecto, ve un usuario. Consiste en cuatro regiones:
• 0x00000000 a 0x0000FFFF. Reservada para ayudar a los programadores a capturar asignaciones de punteros nulos.
• 0x00010000 a 0x7FFEFFFF. Espacio de direcciones disponible para el usuario. Este espacio
se encuentra dividido en páginas que se pueden cargar de la memoria principal.
• 0x7FFF0000 a 0x7FFFFFFF. Una página de guarda, no accesible por el usuario. Esta página
hace que al sistema operativo le resulte más fácil verificar referencias a punteros fuera de
rango.
• 0x80000000 a 0xFFFFFFFF. Espacio de direcciones de sistema. Este área de 2 Gbytes se utiliza por parte del Ejecutivo de Windows, el micronúcleo y los manejadores de dispositivos.
08-Capitulo 8
12/5/05
16:23
Página 387
Memoria virtual
387
PAGINACIÓN EN WINDOWS
Cuando se crea un proceso, puede, en principio, utilizar todo el espacio de usuario de 2 Gbytes (menos 128 Kbytes). Este espacio se encuentra dividido en páginas de tamaño fijo, cualquiera de las cuales se puede cargar en la memoria principal. En la práctica, una página se puede encontrar, a efectos
de gestión, en los presentes estados:
• Disponible. Páginas que no están actualmente usadas por este proceso.
• Reservada. Conjunto de páginas contiguas que el gestor de memoria virtual separa para un
proceso pero que no se cuentan para la cuota de memoria usada por dicho proceso. Cuando un
proceso necesite escribir en la memoria, parte de esta memoria reservada se asigna al proceso.
• Asignada. Las páginas para las cuales el gestor de la memoria virtual ha reservado espacio en
el fichero de paginación (por ejemplo, el fichero de disco donde se escribirían las páginas
cuando se eliminen de la memoria principal).
La distinción entre la memoria reservada y asignada es muy útil debido a que (1) minimiza la
cantidad de espacio de disco que debe guardarse para un proceso en particular, manteniendo espacio
libre en disco para otros procesos; y (2) permite que un hilo o un proceso declare una petición de una
cantidad de memoria que puede proporcionarse rápidamente si se necesita.
Región de 64 Kbytes
para asignaciones de
punteros nulos
(no accesible)
0
espacio de direcciones
de usuario de 2 Gbytes
(no reservado, utilizable)
Región de 64 Kbytes
para asignaciones
de punteros erróneos
(no accesible)
Región de sistema
operativo de
2 Gbytes
0xFFFFFFFF
Figura 8.26.
Espacio de direcciones virtuales habitual de Windows.
08-Capitulo 8
388
12/5/05
16:23
Página 388
Sistemas operativos. Aspectos internos y principios de diseño
El esquema de gestión del conjunto residente que utiliza Windows es de asignación varia
Descargar