|
| |
Sistemas Operativos
Resumen: Tipos de Sistemas Operativos. Sistemas de Archivos. Administración de la Memoria. Administración de Procesos. Principios en el Manejo de Entrada - Salida. Núcleos de Sistemas Operativos. Caso de Estudio: UNIX. Caso de Estudio: VMS. Caso de Estudio: OS/2. Caso de Estudio: WindowsNT. Caso de Estudio: Procesos en Linux
Publicación enviada por Genovece, Claudio y Otros Autores
Indice
1. Introducción
2. Tipos de Sistemas Operativos
3. Sistemas de Archivos
4. Administración de la Memoria
5. Administración de Procesos
6. Principios en el Manejo de Entrada - Salida
7. Núcleos de Sistemas Operativos
8. Caso de Estudio: UNIX
9. Caso de Estudio: VMS
10. Caso de Estudio: OS/2
11. Caso de Estudio: WindowsNT
12. Caso de Estudio: Procesos en Linux
13. Bibliografía.
1. Introducción
A finales de los 40's el uso de computadoras estaba restringido a aquellas
empresas o instituciones que podían pagar su alto precio, y no existían los
sistemas operativos. En su lugar, el programador debía tener un conocimiento y
contacto profundo con el hardware, y en el infortunado caso de que su programa
fallara, debía examinar los valores de los registros y páneles de luces
indicadoras del estado de la computadora para determinar la causa del fallo y
poder corregir su programa, además de enfrentarse nuevamente a los
procedimientos de apartar tiempo del sistema y poner a punto los compiladores,
ligadores, etc; para volver a correr su programa, es decir, enfrentaba el
problema del procesamiento serial ( serial processing ).
La importancia de los sistemas operativos nace históricamente desde los
50's, cuando se hizo evidente que el operar una computadora por medio de
tableros enchufables en la primera generación y luego por medio del trabajo en
lote en la segunda generación se podía mejorar notoriamente, pues el operador
realizaba siempre una secuencia de pasos repetitivos, lo cual es una de las
características contempladas en la definición de lo que es un programa. Es
decir, se comenzó a ver que las tareas mismas del operador podían plasmarse en
un programa, el cual a través del tiempo y por su enorme complejidad se le llamó
"Sistema Operativo". Así, tenemos entre los primeros sistemas
operativos al Fortran Monitor System ( FMS ) e IBSYS .
Posteriormente, en la tercera generación de computadoras nace uno de los
primeros sistemas operativos con la filosofía de administrar una familia de
computadoras: el OS/360 de IBM. Fue este un proyecto tan novedoso y ambicioso
que enfrentó por primera vez una serie de problemas conflictivos debido a que
anteriormente las computadoras eran creadas para dos propósitos en general: el
comercial y el científico. Así, al tratar de crear un solo sistema operativo
para computadoras que podían dedicarse a un propósito, al otro o ambos, puso
en evidencia la problemática del trabajo en equipos de análisis, diseño e
implantación de sistemas grandes. El resultado fue un sistema del cual uno de
sus mismos diseñadores patentizó su opinión en la portada de un libro: una
horda de bestias prehistóricas atascadas en un foso de brea.
Surge también en la tercera generación de computadoras el concepto de la
multiprogramación, porque debido al alto costo de las computadoras era
necesario idear un esquema de trabajo que mantuviese a la unidad central de
procesamiento más tiempo ocupada, así como el encolado (spooling ) de trabajos
para su lectura hacia los lugares libres de memoria o la escritura de
resultados. Sin embargo, se puede afirmar que los sistemas durante la tercera
generación siguieron siendo básicamente sistemas de lote.
En la cuarta generación la electrónica avanza hacia la integración a gran
escala, pudiendo crear circuitos con miles de transistores en un centímetro
cuadrado de silicón y ya es posible hablar de las computadoras personales y las
estaciones de trabajo. Surgen los conceptos de interfaces amigables intentando
así atraer al público en general al uso de las computadoras como herramientas
cotidianas. Se hacen populares el MS-DOS y UNIX en estas máquinas. También es
común encontrar clones de computadoras personales y una multitud de empresas
pequeñas ensamblándolas por todo el mundo.
Para mediados de los 80's, comienza el auge de las redes de computadoras y la
necesidad de sistemas operativos en red y sistemas operativos distribuidos. La
red mundial Internet se va haciendo accesible a toda clase de instituciones y se
comienzan a dar muchas soluciones ( y problemas ) al querer hacer convivir
recursos residentes en computadoras con sistemas operativos diferentes. Para los
90's el paradigma de la programación orientada a objetos cobra auge, así como
el manejo de objetos desde los sistemas operativos. Las aplicaciones intentan
crearse para ser ejecutadas en una plataforma específica y poder ver sus
resultados en la pantalla o monitor de otra diferente (por ejemplo, ejecutar una
simulación en una máquina con UNIX y ver los resultados en otra con DOS ). Los
niveles de interacción se van haciendo cada vez más profundos.
2. Tipos de sistemas operativos
En esta sección se describirán las características que clasifican a los
sistemas operativos, básicamente se cubrirán tres clasificaciones: sistemas
operativos por su estructura (visión interna), sistemas operativos por los
servicios que ofrecen y, finalmente, sistemas operativos por la forma en que
ofrecen sus servicios (visión externa).
Sistemas Operativos por su Estructura
Según [Alcal92], se deben observar dos tipos de requisitos cuando se construye
un sistema operativo, los cuales son:
Requisitos de usuario: Sistema fácil de usar y de aprender, seguro, rápido y
adecuado al uso al que se le quiere destinar.
Requisitos del software: Donde se engloban aspectos como el mantenimiento, forma
de operación, restricciones de uso, eficiencia, tolerancia frente a los errores
y flexibilidad.
A continuación se describen las distintas estructuras que presentan los
actuales sistemas operativos para satisfacer las necesidades que de ellos se
quieren obtener.
Estructura monolítica.
Es la estructura de los primeros sistemas operativos constituídos
fundamentalmente por un solo programa compuesto de un conjunto de rutinas
entrelazadas de tal forma que cada una puede llamar a cualquier otra (Ver Fig.
2). Las características fundamentales de este tipo de estructura son:
Construcción del programa final a base de módulos compilados separadamente que
se unen a través del ligador.
Buena definición de parámetros de enlace entre las distintas rutinas
existentes, que puede provocar mucho acoplamiento.
Carecen de protecciones y privilegios al entrar a rutinas que manejan diferentes
aspectos de los recursos de la computadora, como memoria, disco, etc.
Generalmente están hechos a medida, por lo que son eficientes y rápidos en su
ejecución y gestión, pero por lo mismo carecen de flexibilidad para soportar
diferentes ambientes de trabajo o tipos de aplicaciones.
Estructura jerárquica.
A medida que fueron creciendo las necesidades de los usuarios y se
perfeccionaron los sistemas, se hizo necesaria una mayor organización del
software, del sistema operativo, donde una parte del sistema contenía subpartes
y esto organizado en forma de niveles.
Se dividió el sistema operativo en pequeñas partes, de tal forma que cada
una de ellas estuviera perfectamente definida y con un claro interface con el
resto de elementos.
Se constituyó una estructura jerárquica o de niveles en los sistemas
operativos, el primero de los cuales fue denominado THE (Technische Hogeschool,
Eindhoven), de Dijkstra, que se utilizó con fines didácticos (Ver Fig. 3). Se
puede pensar también en estos sistemas como si fueran `multicapa'. Multics y
Unix caen en esa categoría.
En la estructura anterior se basan prácticamente la mayoría de los sistemas
operativos actuales. Otra forma de ver este tipo de sistema es la denominada de
anillos concéntricos o "rings" (Ver Fig. 4).
En el sistema de anillos, cada uno tiene una apertura, conocida como puerta o
trampa (trap), por donde pueden entrar las llamadas de las capas inferiores. De
esta forma, las zonas más internas del sistema operativo o núcleo del sistema
estarán más protegidas de accesos indeseados desde las capas más externas.
Las capas más internas serán, por tanto, más privilegiadas que las externas.
Máquina Virtual.
Se trata de un tipo de sistemas operativos que presentan una interface a cada
proceso, mostrando una máquina que parece idéntica a la máquina real
subyacente. Estos sistemas operativos separan dos conceptos que suelen estar
unidos en el resto de sistemas: la multiprogramación y la máquina extendida.
El objetivo de los sistemas operativos de máquina virtual es el de integrar
distintos sistemas operativos dando la sensación de ser varias máquinas
diferentes.
El núcleo de estos sistemas operativos se denomina monitor virtual y tiene
como misión llevar a cabo la multiprogramación, presentando a los niveles
superiores tantas máquinas virtuales como se soliciten. Estas máquinas
virtuales no son máquinas extendidas, sino una réplica de la máquina real, de
manera que en cada una de ellas se pueda ejecutar un sistema operativo
diferente, que será el que ofrezca la máquina extendida al usuario (Ver Fig.
5).
Cliente-servidor ( Microkernel)
El tipo más reciente de sistemas operativos es el denominado Cliente-servidor,
que puede ser ejecutado en la mayoría de las computadoras, ya sean grandes o
pequeñas.
Este sistema sirve para toda clase de aplicaciones por tanto, es de propósito
general y cumple con las mismas actividades que los sistemas operativos
convencionales.
El núcleo tiene como misión establecer la comunicación entre los clientes
y los servidores. Los procesos pueden ser tanto servidores como clientes. Por
ejemplo, un programa de aplicación normal es un cliente que llama al servidor
correspondiente para acceder a un archivo o realizar una operación de
entrada/salida sobre un dispositivo concreto. A su vez, un proceso cliente puede
actuar como servidor para otro." [Alcal92]. Este paradigma ofrece gran
flexibilidad en cuanto a los servicios posibles en el sistema final, ya que el núcleo
provee solamente funciones muy básicas de memoria, entrada/salida, archivos y
procesos, dejando a los servidores proveer la mayoría que el usuario final o
programador puede usar. Estos servidores deben tener mecanismos de seguridad y
protección que, a su vez, serán filtrados por el núcleo que controla el
hardware. Actualmente se está trabajando en una versión de UNIX que contempla
en su diseño este paradigma.
Sistemas Operativos por Servicios
Esta clasificación es la más comúnmente usada y conocida desde el punto de
vista del usuario final. Esta clasificación se comprende fácilmente con el
cuadro sinóptico que a continuación se muestra en la Fig. 6.
Monousuarios
Los sistemas operativos monousuarios son aquéllos que soportan a un usuario a
la vez, sin importar el número de procesadores que tenga la computadora o el número
de procesos o tareas que el usuario pueda ejecutar en un mismo instante de
tiempo. Las computadoras personales típicamente se han clasificado en este
renglón.
Multiusuarios
Los sistemas operativos multiusuarios son capaces de dar servicio a más de un
usuario a la vez, ya sea por medio de varias terminales conectadas a la
computadora o por medio de sesiones remotas en una red de comunicaciones. No
importa el número de procesadores en la máquina ni el número de procesos que
cada usuario puede ejecutar simultáneamente.
Monotareas
Los sistemas monotarea son aquellos que sólo permiten una tarea a la vez por
usuario. Puede darse el caso de un sistema multiusuario y monotarea, en el cual
se admiten varios usuarios al mismo tiempo pero cada uno de ellos puede estar
haciendo solo una tarea a la vez.
Multitareas
Un sistema operativo multitarea es aquél que le permite al usuario estar
realizando varias labores al mismo tiempo. Por ejemplo, puede estar editando el
código fuente de un programa durante su depuración mientras compila otro
programa, a la vez que está recibiendo correo electrónico en un proceso en
background. Es común encontrar en ellos interfaces gráficas orientadas al uso
de menús y el ratón, lo cual permite un rápido intercambio entre las tareas
para el usuario, mejorando su productividad.
Uniproceso
Un sistema operativo uniproceso es aquél que es capaz de manejar solamente un
procesador de la computadora, de manera que si la computadora tuviese más de
uno le sería inútil. El ejemplo más típico de este tipo de sistemas es el
DOS y MacOS.
Multiproceso
Un sistema operativo multiproceso se refiere al número de procesadores del
sistema, que es más de uno y éste es capaz de usarlos todos para distribuir su
carga de trabajo. Generalmente estos sistemas trabajan de dos formas: simétrica
o asimétricamente. Cuando se trabaja de manera asimétrica, el sistema
operativo selecciona a uno de los procesadores el cual jugará el papel de
procesador maestro y servirá como pivote para distribuir la carga a los demás
procesadores, que reciben el nombre de esclavos. Cuando se trabaja de manera simétrica,
los procesos o partes de ellos (threads) son enviados indistintamente a
cualesquira de los procesadores disponibles, teniendo, teóricamente, una mejor
distribución y equilibrio en la carga de trabajo bajo este esquema.
Se dice que un thread es la parte activa en memoria y corriendo de un
proceso, lo cual puede consistir de un área de memoria, un conjunto de
registros con valores específicos, la pila y otros valores de contexto. Us
aspecto importante a considerar en estos sistemas es la forma de crear
aplicaciones para aprovechar los varios procesadores. Existen aplicaciones que
fueron hechas para correr en sistemas monoproceso que no toman ninguna ventaja a
menos que el sistema operativo o el compilador detecte secciones de código
paralelizable, los cuales son ejecutados al mismo tiempo en procesadores
diferentes. Por otro lado, el programador puede modificar sus algoritmos y
aprovechar por sí mismo esta facilidad, pero esta última opción las más de
las veces es costosa en horas hombre y muy tediosa, obligando al programador a
ocupar tanto o más tiempo a la paralelización que a elaborar el algoritmo
inicial.
Sistemas Operativos por la Forma de Ofrecer sus Servicios
Esta clasificación también se refiere a una visión externa, que en este caso
se refiere a la del usuario, el cómo accesa los servicios. Bajo esta
clasificación se pueden detectar dos tipos principales: sistemas operativos de
red y sistemas operativos distribuídos.
Sistemas Operativos de Red
Los sistemas operativos de red se definen como aquellos que tiene la capacidad
de interactuar con sistemas operativos en otras computadoras por medio de un
medio de transmisión con el objeto de intercambiar información, transferir
archivos, ejecutar comandos remotos y un sin fin de otras actividades. El punto
crucial de estos sistemas es que el usuario debe saber la sintaxis de un
cinjunto de comandos o llamadas al sistema para ejecutar estas operaciones, además
de la ubicación de los recursos que desee accesar. Por ejemplo, si un usuario
en la computadora hidalgo necesita el archivo matriz.pas que se localiza en el
directorio /software/codigo en la computadora morelos bajo el sistema operativo
UNIX, dicho usuario podría copiarlo a través de la red con los comandos
siguientes: hidalgo% hidalgo% rcp morelos:/software/codigo/matriz.pas .
hidalgo%. En este caso, el comando rcp que significa "remote copy"
trae el archivo indicado de la computadora morelos y lo coloca en el directorio
donde se ejecutó el mencionado comando. Lo importante es hacer ver que el
usuario puede accesar y compartir muchos recursos.
Sistemas Operativos Distribuídos
Los sistemas operativos distribuídos abarcan los servicios de los de red,
logrando integrar recursos ( impresoras, unidades de respaldo, memoria,
procesos, unidades centrales de proceso ) en una sola máquina virtual que el
usuario accesa en forma transparente. Es decir, ahora el usuario ya no necesita
saber la ubicación de los recursos, sino que los conoce por nombre y
simplementa los usa como si todos ellos fuesen locales a su lugar de trabajo
habitual. Todo lo anterior es el marco teórico de lo que se desearía tener
como sistema operativo distribuído, pero en la realidad no se ha conseguido
crear uno del todo, por la complejidad que suponen: distribuír los procesos en
las varias unidades de procesamiento, reintegrar sub-resultados, resolver
problemas de concurrencia y paralelismo, recuperarse de fallas de algunos
recursos distribuídos y consolidar la protección y seguridad entre los
diferentes componentes del sistema y los usuarios. Los avances tecnológicos en
las redes de área local y la creación de microprocesadores de 32 y 64 bits
lograron que computadoras mas o menos baratas tuvieran el suficiente poder en
forma autónoma para desafiar en cierto grado a los mainframes, y a la vez se
dio la posibilidad de intercomunicarlas, sugiriendo la oportunidad de partir
procesos muy pesados en cálculo en unidades más pequeñas y distribuirlas en
los varios microprocesadores para luego reunir los sub-resultados, creando así
una máquina virtual en la red que exceda en poder a un mainframe. El sistema
integrador de los microprocesadores que hacer ver a las varias memorias,
procesadores, y todos los demás recursos como una sola entidad en forma
transparente se le llama sistema operativo distribuído. Las razones para crear
o adoptar sistemas distribuídos se dan por dos razones principales: por
necesidad ( debido a que los problemas a resolver son inherentemente distribuídos
) o porque se desea tener más confiabilidad y disponibilidad de recursos. En el
primer caso tenemos, por ejemplo, el control de los cajeros automáticos en
diferentes estados de la república. Ahí no es posible ni eficiente mantener un
control centralizado, es más, no existe capacidad de cómputo y de
entrada/salida para dar servicio a los millones de operaciones por minuto. En el
segundo caso, supóngase que se tienen en una gran empresa varios grupos de
trabajo, cada uno necesita almacenar grandes cantidades de información en disco
duro con una alta confiabilidad y disponibilidad. La solución puede ser que
para cada grupo de trabajo se asigne una partición de disco duro en servidores
diferentes, de manera que si uno de los servidores falla, no se deje dar el
servicio a todos, sino sólo a unos cuantos y, más aún, se podría tener un
sistema con discos en espejo ( mirror ) a través de la red,de manera que si un
servidor se cae, el servidor en espejo continúa trabajando y el usuario ni
cuenta se da de estas fallas, es decir, obtiene acceso a recursos en forma
transparente.
Ventajas de los Sistemas Distribuídos
En general, los sistemas distribuídos (no solamente los sistemas operativos)
exhiben algunas ventajas sobre los sistemas centralizados que se describen
enseguida.
- Economía: El cociente precio/desempeño de la suma del poder de los
procesadores separados contra el poder de uno solo centralizado es mejor
cuando están distribuídos.
- Velocidad: Relacionado con el punto anterior, la velocidad sumada es muy
superior.
- Confiabilidad: Si una sola máquina falla, el sistema total sigue
funcionando.
- Crecimiento: El poder total del sistema puede irse incrementando al añadir
pequeños sistemas, lo cual es mucho más difícil en un sistema
centralizado y caro.
- Distribución: Algunas aplicaciones requieren de por sí una distribución
física.
Por otro lado, los sistemas distribuídos también exhiben algunas ventajas
sobre sistemas aislados. Estas ventajas son:
- Compartir datos: Un sistema distribuído permite compartir datos más fácilmente
que los sistemas aislados, que tendrian que duplicarlos en cada nodo para
lograrlo.
- Compartir dispositivos: Un sistema distribuído permite accesar
dispositivos desde cualquier nodo en forma transparente, lo cual es
imposible con los sistemas aislados. El sistema distribuído logra un efecto
sinergético.
- Comunicaciones: La comunicación persona a persona es factible en los
sistemas distribuídos, en los sistemas aislados no. _ Flexibilidad: La
distribución de las cargas de trabajo es factible en el sistema distribuídos,
se puede incrementar el poder de cómputo.
Desventajas de los Sistemas Distribuídos
Así como los sistemas distribuídos exhiben grandes ventajas, también se
pueden identificar algunas desventajas, algunas de ellas tan serias que han
frenado la producción comercial de sistemas operativos en la actualidad. El
problema más importante en la creación de sistemas distribuídos es el
software: los problemas de compartición de datos y recursos es tan complejo que
los mecanismos de solución generan mucha sobrecarga al sistema haciéndolo
ineficiente. El checar, por ejemplo, quiénes tienen acceso a algunos recursos y
quiénes no, el aplicar los mecanismos de protección y registro de permisos
consume demasiados recursos. En general, las soluciones presentes para estos
problemas están aún en pañales.
Otros problemas de los sistemas operativos distribuídos surgen debido a la
concurrencia y al paralelismo. Tradicionalmente las aplicaiones son creadas para
computadoras que ejecutan secuencialmente, de manera que el identificar
secciones de código `paralelizable' es un trabajo ardúo, pero necesario para
dividir un proceso grande en sub-procesos y enviarlos a diferentes unidades de
procesamiento para lograr la distribución. Con la concurrencia se deben
implantar mecanismos para evitar las condiciones de competencia, las
postergaciones indefinidas, el ocupar un recurso y estar esperando otro, las
condiciones de espera circulares y , finalmente, los "abrazos
mortales" (deadlocks). Estos problemas de por sí se presentan en los
sistemas operativos multiusuarios o multitareas, y su tratamiento en los
sistemas distribuídos es aún más complejo, y por lo tanto, necesitará de
algoritmos más complejos con la inherente sobrecarga esperada.
Por otro lado, en el tema de sistemas distribuídos existen varios conceptos
importantes referentes al hadware que no se ven en este trabajo:
multicomputadoras, multiprocesadores, sistemas acoplados débil y fuertemente,
etc. En páginas 366 - 376 puede encontrarse material relacionado a estos
conceptos.
3. Tipos de sistemas de archivos
Un sistema de archivos ( file system ) es una estructura de directorios con
algún tipo de organización el cual nos permite almacenar, crear y borrar
archivos en diferenctes formatos. En esta sección se revisarán conceptos
importantes relacionados a los sistemas de archivos.
Almacenamiento Físico de Datos
En un sistema de cómputo es evidente que existe la necesidad por parte de los
usuarios y aplicaciones de almacenar datos en algún medio, a veces por periodos
largos y a veces por instantes. cada aplicación y cada usuario debe tener
ciertos derechos con sus datos, como son el poder crearlos y borrarlos, o
cambialos de lugar; así como tener privacidad contra otros usuarios o
aplicaciones. El subsistema de archivos del sistema operativo se debe encargar
de estos detalles, además de establecer el formato físico en el cual almacenará
los datos en discos duros, cintas o discos flexibles. Debe ser conocido por
todos que tradicionalmente la información en los sistemas modernos se almacena
en discos duros, flexibles y unidades de disco óptico, y en todos ellos se
comparten algunos esquemas básicos para darles formato físico: las superficies
de almacenamiento son divididas en círculos concéntricos llamados
"pistas" y cada pista se divide en "sectores". A la unión lógica
de varias pistas a través de varias superficies "paralelas" de
almacenamiento se les llama "cilindros", los cuales son inspeccionados
al momento de lectura o escritura de datos por las respectivas unidades fisicas
llamadas "cabezas". Las superficies de almacenamiento reciben el
nombre de "platos" y generalmente están en movimiento rotatorio para
que las cabezas accesen a las pistas que los componen. Los datos se escriben a
través de los sectores en las pistas y cilindros modificando las superficies
por medio de las cabezas.
El tiempo que una cabeza se tarda en ir de una pista a otra se le llama
"tiempo de búsqueda" y dependerá de la distancia entre la posición
actual y la distancia a la pista buscada. El tiempo que tarda una cabeza en ir
del sector actual al sector deseado se le llama tiempo de latencia y depende de
la distancia entre sectores y la velocidad de rotación del disco. El impacto
que tiene las lecturas y escrituras sobre el sistema está determinado por la
tecnología usada en los platos y cabezas y por la forma de resolver las
peticiones de lectura y escritura, es decir, los algoritmos de planificación.
Algoritmos de planificación de peticiones
Los algoritmos de planificación de peticiones de lectura y escritura a discos
se encargan de registrar dichas peticiones y de responderlas en un tiempo
razonable. Los algoritmos más comunes para esta tarea son:
- Primero en llegar, primero en ser servido ( FIFO ): Las peticiones son
encoladas de acuerdo al orden en que llegaron y de esa misma forma se van
leyendo o escribiendo las mismas. La ventaja de este algoritmo es su
simplicidad y no causa sobrecarga, su desventaja principal es que no
aprovecha para nada ninguna característica de las peticiones, de manera que
es muy factible que el brazo del disco se mueva muy ineficientemente, ya que
las peticiones pueden tener direcciones en el disco unas muy alejadas de
otras. Por ejemplo, si se están haciendo peticiones a los sectores
6,10,8,21 y 4, las mismas serán resueltas en el mismo orden. _ Primero el más
cercano a la posición actual: En este algoritmo las peticiones se ordenan
de acuerdo a la posición actual de la cabeza lectora, sirviendo primero a
aquellas peticiones más cercanas y reduciendo, así, el movimiento del
brazo, lo cual constituye la ventaja principal de este algoritmo. Su
desventaja consiste en que puede haber solicitudes que se queden esperando
para siempre, en el infortunado caso de que existan peticiones muy alejadas
y en todo momento estén entrando peticiones que estén más cercanas. Para
las peticiones 6,10,8,21 y 4, las mismas serán resueltas en el orden
4,6,8,10 y 21.
- Por exploración ( algoritmo del elevador ): En este algoritmo el brazo se
estará moviendo en todo momento desde el perímetro del disco hacia su
centro y viceversa, resolviendo las peticiones que existan en la dirección
que tenga en turno. En este caso las peticiones 6,10,8,21 y 4 serán
resueltas en el orden 6,10,21,8 y 4; es decir, la posición actual es 6 y
como va hacia los sectores de mayor numeración (hacia el centro, por
ejemplo), en el camino sigue el sector 10, luego el 21 y ese fue el más
central, así que ahora el brazo resolverá las peticiones en su camino
hacia afuera y la primera que se encuentra es la del sector 8 y luego la 4.
La ventaja de este algoritmo es que el brazo se moverá mucho menos que en
FIFO y evita la espera indefinida; su desventaja es que no es justo, ya que
no sirve las peticiones en el orden en que llegaron, además de que las
peticiones en los extremos interior y exterior tendrán un tiempo de
respuesta un poco mayor.
- Por exploración circular: Es una variación del algoritmo anterior, con
la única diferencia que al llegar a la parte central, el brazo regresa al
exterior sin resolver ninguna petición, lo cual proveerá un tiempo de
respuesta más cercana al promedio para todas las peticiones, sin importar
si están cercas del centro o del exterior.
Asignación del espacio de almacenamiento
El subsistema de archivos se debe encargar de localizar espacio libre en los
medios de almacenamiento para guardar archivos y para después borrarlos,
renombrarlos o agrandarlos. Para ello se vale de localidades especiales que
contienen la lista de archivos creados y por cada archivo una serie de
direcciones que contienen los datos de los mismos. Esas localidades especiales
se llaman directorios. Para asignarle espacio a los archivos existen tres
criterios generales que se describen enseguida.
- Asignación contigua: Cada directorio contiene la los nombres de archivos
y la dirección del bloque inicial de cada archivo, así como el tamaño
total de los mismos. Por ejemplo, si un archivo comienza en el sector 17 y
mide 10 bloques, cuando el archivo sea accesado, el brazo se moverá
inicialmente al bloque 17 y de ahí hasta el 27. Si el archivo es borrado y
luego creado otro más pequeño, quedarán huecos inútiles entre archivos
útiles, lo cual se llama fragmentación externa.
- Asignación encadenada: Con este criterio los directorios contienen los
nombres de archivos y por cada uno de ellos la dirección del bloque inicial
que compone al archivo. Cuando un archivo es leído, el brazo va a esa
dirección inicial y encuentra los datos iniciales junto con la dirección
del siguiente bloque y así sucesivamente. Con este criterio no es necesario
que los bloques estén contiguos y no existe la fragmentación externa, pero
en cada "eslabón" de la cadena se desperdicia espacio con las
direcciones mismas. En otras palabras, lo que se crea en el disco es una
lista ligada.
- Asignación con índices ( indexada ): En este esquema se guarda en el
directorio un bloque de índices para cada archivo, con apuntadores hacia
todos sus bloques constituyentes, de mabnera que el acceso directo se
agiliza notablemente, a cambio de sacrificar varios bloques para almacenar
dichos apuntadores. Cuando se quiere leer un archivo o cualquiera de sus
partes, se hacen dos accesos: uno al bloque de índices y otro a la dirección
deseada. Este es un esquema excelente para archivos grandes pero no para
pequeños, porque la relación entre bloques destinados para índices
respecto a los asignados para datos es incosteable.
Métodos de acceso en los sistemas de archivos.
Los métodos de acceso se refiere a las capacidades que el subsistema de
archivos provee para accesar datos dentro de los directorios y medios de
almacenamiento en general. Se ubican tres formas generales: acceso secuencial,
acceso directo y acceso directo indexado.
- Acceso secuencial: Es el método más lento y consiste en recorrer los
componentes de un archivo uno en uno hasta llegar al registro deseado. Se
necesita que el orden lógico de los registros sea igual al orden físico en
el medio de almacenamiento. Este tipo de acceso se usa comunmente en cintas
y cartuchos.
- Acceso directo: Permite accesar cualquier sector o registro
inmediatamente, por medio de llamadas al sistema como la de seek. Este tipo
de acceso es rápido y se usa comúnmente en discos duros y discos o
archivos manejados en memoria de acceso aleatorio. _ Acceso directo
indexado: Este tipo de acceso es útil para grandes volúmenes de información
o datos. Consiste en que cada arcivo tiene una tabla de apuntadores, donde
cada apuntador va a la dirección de un bloque de índices, lo cual permite
que el archivo se expanda a través de un espacio enorme. Consume una
cantidad importante de recursos en las tablas de índices pero es muy rápido.
Operaciones soportadas por el subsistema de archivos
Independientemente de los algoritmos de asignación de espacio, de los métodos
de acceso y de la forma de resolver las peticiones de lectura y escritura, el
subsistema de archivos debe proveer un conjunto de llamadas al sistema para
operar con los datos y de proveer mecanismos de protección y seguridad. Las
operaciones básicas que la mayoría de los sistemas de archivos soportan son:
- Crear ( create ) : Permite crear un archivo sin datos, con el propósito
de indicar que ese nombre ya está usado y se deben crear las estructuras básicas
para soportarlo.
- Borrar ( delete ): Eliminar el archivo y liberar los bloques para su uso
posterior.
- Abrir ( open ): Antes de usar un archivo se debe abrir para que el sistema
conozca sus atributos, tales como el dueño, la fecha de modificación, etc.
_ Cerrar ( close ): Después de realizar todas las operaciones deseadas, el
archivo debe cerrarse para asegurar su integridad y para liberar recursos de
su control en la memoria.
- Leer o Escribir ( read, write ): Añadir información al archivo o leer el
caracter o una cadena de caracteres a partir de la posición actual. _
Concatenar ( append ): Es una forma restringida de la llamada `write', en la
cual sólo se permite añadir información al final del archivo. _ Localizar
( seek ): Para los archivos de acceso directo se permite posicionar el
apuntador de lectura o escritura en un registro aleatorio, a veces a partir
del inicio o final del archivo.
- Leer atributos: Permite obtener una estructura con todos los atributos del
archivo especificado, tales como permisos de escritura, de borrado, ejecución,
etc.
- Poner atributos: Permite cambiar los atributos de un archivo, por ejemplo
en UNIX, donde todos los dispositivos se manejan como si fueran archivos, es
posible cambiar el comportamiento de una terminal con una de estas llamadas.
- Renombrar ( rename ): Permite cambiarle el nombre e incluso a veces la
posición en la organización de directorios del archivo especificado. Los
subsistemas de archivos también proveen un conjunto de llamadas para operar
sobre directorios, las más comunies son crear, borrar, abrir, cerrar,
renombrar y leer. Sus funcionalidades son obvias, pero existen también
otras dos operaciones no tan comunes que son la de `crear una liga' y la de
`destruir la liga'. La operación de crear una liga sirve para que desde
diferentes puntos de la organización de directorios se pueda accesar un
mismo directorio sin necesidad de copiarlo o duplicarlo. La llamada a
`destruir nla liga' lo que hace es eliminar esas referencias, siendo su
efecto la de eliminar las ligas y no el directorio real. El directorio real
es eliminado hasta que la llmada a `destruir liga' se realiza sobre él.
Algunas facilidades extras de los sistemas de archivos
Algunos sistemas de archivos proveen herramientas al administrador del sistema
para facilitarle la vida. Las más notables es la facilidad de compartir
archivos y los sistemas de `cotas'.
La facilidad de compartir archivos se refiere a la posibilidad de que los
permisos de los archivos o directorios dejen que un grupo de usuarios puedan
accesarlos para diferentes operaciones" leer, escribir, borrar, crear, etc.
El dueño verdadero es quien decide qué permisos se aplicarán al grupo e,
incluso, a otros usuarios que no formen parte de su grupo. La facilidad de
`cotas' se refiere a que el sistema de archivos es capaz de llevar un control
para que cada usuario pueda usar un máximo de espacio en disco duro. Cuando el
usuario excede ese límite, el sistema le envía un mensaje y le niega el
permiso de seguir escribiendo, obligándolo a borrar algunos archivos si es que
quiere almacenar otros o que crezcan. La versión de UNIX SunOS contiene esa
facilidad.
Sistemas de Archivos Aislados
Los sistemas de archivos aislados son aquellos que residen en una sola
computadora y no existe la posibilidad de que, aún estando en una red, otros
sistemas puedan usar sus directorios y archivos. Por ejemplo, los archivos en
discos duros en el sistema MS-DOS clásico se puede ver en esta categoría.
Sistemas de Archivos Compartidos o de Red
Estos sistemas de archivos es factible accesarlos y usarlos desde otros nodos en
una red. Generalmente existe un `servidor' que es la computadora en donde reside
el sistema de archivos físicamente, y por otro lado están los `clientes', que
se valen del servidor para ver sus archivos y directorios de manera como si
estuvieran localmente en el cliente. Algunos autores les llaman a estos sistemas
de archivos `sistemas de archivos distribuídos' lo cual no se va a discutir en
este trabajo.
Los sistemas de archivos compartidos en red más populares son los provistos
por Netware, el Remote Filke Sharing ( RFS en UNIX ), Network File System ( NFS
de Sun Microsystems ) y el Andrew File System ( AFS ). En general, lo que
proveen los servidores es un medio de que los clientes, localmente, realicen
peticiones de operaciones sobre archivos los cuales con `atrapadas' por un
`driver' o un `módulo' en el núcleo del sistema operativo, el cual se comunica
con el servidor a través de la red y la operación se ejecuta en el servidor.
Existen servidores de tipo "stateless y no-stateless". Un servidor
"stateless" no registra el estado de las operaciones sobre los
archivos, de manera que el cliente se encarga de todo ese trabajo. La ventaja de
este esquema es que si el servidor falla, el cliente no perderá información ya
que ésta se guarda en memoria localmente, de manera que cuando el servidor
reanude su servicio el cliente proseguirá como si nada hubiese sucedido. Con un
servidor "no-stateless", esto no es posible.
La protección sobre las operaciones se lleva a cabo tanto el los clientes
como en el servidor: si el usuario quiere ejecutar una operación indebida sobre
un archivo, recibirá un mensaje de error y posiblemente se envíe un registro
al subsistema de `seguridad' para informar al administrador del sistema de dicho
intento de violación.
En la práctica, el conjunto de permisos que cada usuario tiene sobre el
total de archivos se almacena en estructuras llamadas `listas de acceso' (
access lists ).
Tendencias actuales
Con el gran auge de las redes de comunicaciones y su incremento en el ancho de
banda, la proliferación de paquetes que ofrecen la compartición de archivos es
común. Los esquemas más solicitados en la industria es el poder accesar los
grandes volúmenes de información que residen en grandes servidores desde las
computadoras personales y desde otros servidores también. Es una realidad que
la solución más socorrida en las empresas pequeñas es usar Novell Netware en
un servidor 486 o superior y accesar los archivos desde máquinas similares.
A veces se requieren soluciones más complejas con ambientes heterogéneos:
diferentes sistemas operativos y diferentes arquitecturas. Uno de los sistemas
de archivos más expandidos en estaciones de trabajo es el NFS, y prácticamente
todas las versiones de UNIX traen instalado un cliente y hasta un servidor de
este servicio. Es posible así que una gran cantidad de computadoras personales
(de 10 a 80 ) accesen grandes volúmenes de información o paquetería (desde 1
a 8 Gygabytes ) desde una sola estación de trabajo, e incluso tener la
flexibilidad de usar al mismo tiempo servidores de Novell y NFS. Soluciones
similares se dan con algunos otros paquetes comerciales, pero basta ya de
`goles'. Lo importante aquí es observar que el mundo se va moviendo poco a poco
hacia soluciones distribuídas, y hacia la estandarización que, muchas veces,
es `de facto'.
4. Administracion de la memoria
En esta sección se describirán las técnicas más usuales en el manejo de
memoria, revisando los conceptos relevantes. Se abarcarán los esquemas de
manejo simple de memoria real, la multiprogramación en memoria real con sus
variantes, el concepto de `overlays', la multiprogramación con intercambio y
los esquemas de manejo de memoria virtual.
Panorama general
Un vistazo al material que se va a cubrir en esta sección se muestra en la
figura 4.1. Es una gráfica en donde se especifican, en términos generales, los
conceptos más importantes en cuanto a las técnicas empleadas en el manejo de
memoria.
Manejo de memoria en sistemas monousuario sin intercambio
Este esquema es aún muy frecuente en México y se usa principalmente en
sistemas monousuario y monotarea, como son las computadoras personales con DOS.
Bajo este esquema, la memoria real es tomada para almacenar el programa que se
esté ejecutando en un momento dado, con la visible desventaja de que si se está
limitado a la cantidad de RAM disponible únicamente. La organización física
bajo este esquema es muy simple: El sistema operativo se ubica en las
localidades superiores o inferiores de la memoria, seguido por algunos
manejadores de dispositivos ( `drivers' ). Esto deja un espacio contiguo de
memoria disponible que es tomado por los programas del usuario, dejando
generalmente la ubicación de la pila (` stack' ) al último, con el objetivo de
que ésta pueda crecer hasta el máximo posible. Estas diferentes opciones se
pueden ver en la figura 4.2. Como es obvio, bajo estos esquemas no se requieren
algoritmos sofisticados para asignar la memoria a los diferentes procesos, ya
que éstos son ejecutados secuencialmente conforme van terminando.
Multiprogramación en memoria real
En los 60's, las empresas e instituciones que habían invertido grandes sumas en
la compra de equipo de cómputo se dieron cuenta rápidamente que los sistemas
en lote invertían una gran cantidad de tiempo en operaciones de entrada y
salida, donde la intervención de la unidad central de procesamiento era prácticamente
nula, y se comenzaron a preguntar cómo hacer que se mantuviera más tiempo
ocupada. Fue así como nació el concepto de multiprogramación, el cual
consiste en la idea de poner en la memoria física más de un proceso al mismo
tiempo, de manera que si el que se está ejecutando en este momento entraba en
un periodo de entrada/salida, se podia tomar otro proceso para que usara la
unidad central de procesamiento. De esta forma, la memoria fisica se dividía en
secciones de tamaño suficiente para contener a varios programas.
De esta manera, si un sistema gastaba en promedio 60% de su tiempo en
entrada/salida por proceso, se podía aprovechar más el CPU. Anterior a esto,
el CPU se mantenía ese mismo porcentaje ocioso; con la nueva técnica, el
tiempo promedio ocioso disminuye de la siguiente forma. Llámese al tiempo
promedio que el CPU está ocupado `grado de multiprogramación'. Si el sistema
tuviese un solo proceso siempre, y éste gastara 60% en entrada/salida, el grado
de multiprogramación sería 1 - 60% = 40% = 0.4. Con dos procesos, para que el
CPU esté ocioso se necesita que ambos procesos necesiten estar haciendo
entrada/salida, es decir, suponiendo que son independientes, la probabilidad de
que ambos estén en entrada/salida es el producto de sus probabilidades, es
decir, 0.6x0.6 = 0.36. Ahora, el grado de multiprogramación es 1 -
(probabilidad de que ambos procesos estén haciendo entrada/salida) = 1 - 0.36 =
0.64.
Como se ve, el sistema mejora su uso de CPU en un 24% al aumentar de uno a
dos procesos. Para tres procesos el grado de multiprogramación es 1 - (0.6) 3 =
0.784, es decir, el sistema está ocupado el 78.4% del tiempo. La fórmula del
grado de multiprogramación, aunque es muy idealista, pudo servir de guía para
planear un posible crecimiento con la compra de memoria real, es decir, para
obtener el punto en que la adición de procesos a RAM ya no incrementa el uso de
CPU.
Dentro del esquema de multiprogramación en memoria real surgieron dos
problemas interesantes: la protección y la relocalización.
El problema de la relocalización
Este problema no es exclusivo de la multiprogramación en memoria real, sino que
se presentó aquí pero se sigue presentando en los esquemas de memoria virtual
también. Este problema consiste en que los programas que necesitan cargarse a
memoria real ya están compilados y ligados, de manera que internamente
contienen una serie de referencias a direcciones de instrucciones, rutinas y
procedimientos que ya no son válidas en el espacio de direcciones de memoria
real de la sección en la que se carga el programa. Esto es, cuando se compiló
el programa se definieron o resolvieron las direcciones de memoria de acuerdo a
la sección de ese momento, pero si el programa se carga en otro dia en una
sección diferente, las direcciones reales ya no coinciden. En este caso, el
manejador de memoria puede solucionar el problema de dos maneras: de manera `estática'
o de manera `dinámica'. La solución `estática' consiste en que todas las
direcciones del programa se vuelvan a recalcular al momento en que el programa
se carga a memoria, esto es, prácticamente se vuelve a recompilar el programa.
La solución `dinámica' consiste en tener un registro que guarde la dirección
base de la sección que va a contener al programa. Cada vez que el programa haga
una referencia a una dirección de memoria, se le suma el registro base para
encontrar la dirección real. Por ejemplo, suponga que el programa es cargado en
una sección que comienza en la dirección 100. El programa hará referencias a
las direcciones 50,52,54. Pero el contenido de esas direcciones no es el
deseado, sino las direcciones 150, 152 y 154, ya que ahí comienza el programa.
La suma de 100 + 50, ...,etcétera se hacen al tiempo de ejecución. La primera
solución vale más la pena que la segunda si el programa contiene ciclos y es
largo, ya que consumirá menos tiempo en la resolución inicial que la segunda
solución en las resoluciones en línea.
El problema de la protección
Este problema se refiere a que, una vez que un programa ha sido caragado a
memoria en algún segmento en particular, nada le impide al programador que
intente direccionar ( por error o deliberadamente ) localidades de memoria
menores que el límite inferior de su programa o superiores a la dirección
mayor; es decir, quiere referenciar localidades fuera de su espacio de
direcciones. Obviamente, este es un problema de protección, ya que no es legal
leer o escribir en áreas de otros programas.
La solución a este problema también puede ser el uso de un registro base y
un registro límite. El registro base contiene la dirección del comienzo de la
sección que contiene al programa, mientras que el límite contiene la dirección
donde termina. Cada vez que el programa hace una referencia a memoria se checa
si cae en el rango de los registros y si no es así se envía un mensaje de
error y se aborta el programa.
Particiones fijas o particiones variables
En el esquema de la multiprogramación en memroia real se manejan dos
alternativas para asignarle a cada programa su partición correspondiente:
particiones de tamaño fijo o particiones de tamaño variable. La alternativa más
simple son las particiones fijas. Dichas particiones se crean cuando se enciende
el equipo y permanecerán con los tamaños iniciales hasta que el equipo se
apague. Es una alternativa muy vieja, quien hacía la división de particiones
era el operador analizando los tamaños estimados de los trabajos de todo el día.
Por ejemplo, si el sistema tenía 512 kilobytes de RAM, podia asignar 64 k para
el sistema operativo, una partición más de 64 k, otra de 128k y una mayor de
256 k. Esto era muy simple, pero inflexible, ya que si surgían trabajos
urgentes, por ejemplo, de 400k, tenían que esperar a otro día o reparticionar,
inicializando el equipo desde cero. La otra alternativa, que surgió después y
como necesidad de mejorar la alternativa anterior, era crear particiones
contiguas de tamaño variable. Para esto, el sistema tenía que mantener ya una
estructura de datos suficiente para saber en dónde habían huecos disponibles
de RAM y de dónde a dónde habían particiones ocupadas por programas en
ejecución. Así, cuando un programa requería ser cargado a RAM, el sistema
analizaba los huecos para saber si había alguno de tamaño suficiente para el
programa que queria entrar, si era así, le asignaba el espacio. Si no,
intentaba relocalizar los programas existentes con el propósito de hacer
contiguo todo el espacio ocupado, así como todo el espacio libre y así obtener
un hueco de tamaño suficiente. Si aún así el programa no cabía, entonces lo
bloqueaba y tomaba otro. El proceso con el cual se juntan los huecos o los
espacios ocupados se le llama `compactación'. El lector se habrá dado cuenta
ya de que surgen varios problemas con los esquemas de particiones fijas y
particiones variables: ø En base a qué criterio se elige el mejor tamaño de
partición para un programa ? Por ejemplo, si el sistema tiene dos huecos, uno
de 18k y otro de 24 k para un proceso que desea 20 k, ø Cual se le asigna ?
Existen varios algoritmos para darle respuesta a la pregunta anterior, los
cuales se ennumeran y describen enseguida.
- Primer Ajuste: Se asigna el primer hueco que sea mayor al tamaño deseado.
- Mejor Ajuste: Se asigna el hueco cuyo tamaño exceda en la menor cantidad
al tamaño deseado. Requiere de una búsqueda exhaustiva.
- Peor Ajuste: Se asigna el hueco cuyo tamaño exceda en la mayor cantidad
al tamaño deseado. Requiere también de una búsqueda exhaustiva.
- El Siguiente Ajuste: Es igual que el `primer ajuste' con la diferencia que
se deja un apuntador al lugar en donde se asignó el último hueco para
realizar la siguiente búsqueda a partir de él.
- Ajuste Rápido: Se mantienen listas ligadas separadas de acuerdo a los
tamaños de los huecos, para así buscarle a los procesos un hueco más rápido
en la cola correspondiente.
Otro problema que se vislumbra desde aquí es que, una vez asignado un hueco,
por ejemplo, con "el peor ajuste", puede ser que el proceso requiriera
12 kilobytes y que el hueco asignado fuera de 64 kilobytes, por lo cual el
proceso va a desperdiciar una gran cantidad de memoria dentro de su partición,
lo cual se le llama `fragmentación interna'.
Por otro lado, conforme el sistema va avanzando en el día, finalizando
procesos y comenzando otros, la memoria se va configurando como una secuencia
contigua de huecos y de lugares asignados, provocando que existan huecos, por
ejemplo, de 12 k, 28k y 30 k, que sumados dan 70k, pero que si en ese momento
llega un proceso pidiéndolos, no se le pueden asignar ya que no son localidades
contiguas de memoria ( a menos que se realice la compactación ). Al hecho de
que aparezcan huecos no contiguos de memoria se le llama `fragmentación
externa'.
De cualquier manera, la multiprogramación fue un avance significativo para
el mejor aprovechamiento de la unidad central de procesamiento y de la memoria
misma, así como dio pie para que surgieran los problemas de asignación de
memoria, protección y relocalización, entre otros.
Los overlays
Una vez que surgió la multiprogramación, los usuarios comenzaron a explorar la
forma de ejecutar grandes cantidades de código en áreas de memoria muy pequeñas,
auxiliados por algunas llamadas al sistema operativo. Es así como nacen los
`overlays'.
Esta técnica consiste en que el programador divide lógicamente un programa
muy grande en secciones que puedan almacenarse el las particiones de RAM. Al
final de cada sección del programa ( o en otros lugares necesarios ) el
programador insertaba una o varias llamadas al sistema con el fin de descargar
la sección presente de RAM y cargar otra, que en ese momento residía en disco
duro u otro medio de almacenamiento secundario. Aunque esta técnica era eficaz
( porque resolvía el problema ) no era eficiente ( ya que no lo resolvía de la
mejor manera ). Esta solución requería que el programador tuviera un
conocimiento muy profundo del equipo de cómputo y de las llamadas al sistema
operativo. Otra desventaja era la portabilidad de un sistema a otro: las
llamadas cambiaban, los tamaños de particiones también. Resumiendo, con esta técnica
se podían ejecutar programas más grandes que las particiones de RAM, donde la
división del código corría a cuenta del programador y el control a cuenta del
sistema operativo.
Multiprogramación en memoria virtual
La necesidad cada vez más imperiosa de ejecutar programas grandes y el
crecimiento en poder de las unidades centrales de procesamiento empujaron a los
diseñadores de los sistemas operativos a implantar un mecanismo para ejecutar
automáticamente programas más grandes que la memoria real disponible, esto es,
de ofrecer `memoria virtual'.
La memoria virtual se llama así porque el programador ve una cantidad de
memoria mucho mayor que la real, y en realidad se trata de la suma de la memoria
de almacenamiento primario y una cantidad determinada de almacenamiento
secundario. El sistema operativo, en su módulo de manejo de memoria, se encarga
de intercambiar programas enteros, segmentos o páginas entre la memoria real y
el medio de almacenamiento secundario. Si lo que se intercambia son procesos
enteros, se habla entonces de multiprogramación en memoria real, pero si lo que
se intercambian son segmentos o páginas, se puede hablar de multiprogramación
con memoria virtual.
La memoria virtual se apoya en varias técnicas interesantes para lograr su
objetivo. Una de las teorias más fuertes es la del `conjunto de trabajo', la
cual se refiere a que un programa o proceso no está usando todo su espacio de
direcciones en todo momento, sino que existen un conjunto de localidades activas
que conforman el `conjunto de trabajo'. Si se logra que las páginas o segmentos
que contienen al conjunto de trabajo estén siempre en RAM, entonces el programa
se desempeñará muy bien.
Otro factor importante es si los programas exhiben un fenómeno llamado
`localidad', lo cual quiere decir que algunos programas tienden a usar mucho las
instrucciones que están cercanas a la localidad de la instrucción que se está
ejecutando actualmente.
Paginación pura
La paginación pura en el majejo de memoria consiste en que el sistema operativo
divide dinámicamente los programas en unidades de tamaño fijo ( generalmente múltiplos
de 1 kilobyte ) los cuales va a manipular de RAM a disco y viceversa. Al proceso
de intercambiar páginas, segmentos o programas completos entre RAM y disco se
le conoce como `intercambio' o `swapping'. En la paginación, se debe cuidar el
tamño de las páginas, ya que si éstas son muy pequeñas el control por parte
del sistema operativo para saber cuáles están en RAM y cuales en disco, sus
direcciones reales, etc; crece y provoca mucha `sobrecarga' (overhead). Por otro
lado, si las páginas son muy grandes, el overhead disminuye pero entonces puede
ocurrir que se desperdicie memoria en procesos pequeños. Debe haber un
equilibrio.
Uno de los aspectos más importantes de la paginación, asi como de cualquier
esquema de memoria virtual, es la forma de traducir una dirección virtual a
dirección real. Para explicarlo, obsérvese la figura
Como se observa, una dirección virtual `v' = ( b,d) está formada por un número
de página virtual `b' y un desplazamiento `d'. Por ejemplo, si el sistema
ofrece un espacio de direcciones virtuales de 64 kilobytes, con páginas de 4
kilobytes y la RAM sólo es de 32 kilobytes, entonces tenemos 16 páginas
virtuales y 8 reales. La tabla de direcciones virtuales contiene 16 entradas,
una por cada página virtual. En cada entrada, o registro de la tabla de
direcciones virtuales se almacenan varios datos: si la página está en disco o
en memoria, quién es el dueño de la página, si la página ha sido modificada
o es de lectura nada mas, etc. Pero el dato que nos interesa ahora es el número
de página real que le corresponde a la página virtual. Obviamente, de las 16
virtuales, sólo ocho tendrán un valor de control que dice que la página está
cargada en RAM, así como la dirección real de la página, denotada en la
figura 4.3 como b' . Por ejemplo, supóngase que para la página virtual número
14 la tabla dice que, efectivamente está cargada y es la página real 2 (
dirección de memoria 8192 ). Una vez encontrada la página real, se le suma el
desplazamiento, que es la dirección que deseamos dentro de la página buscada (
b' + d ).
La tabla de direcciones virtuales a veces está ubicada en la misma meoria
RAM, por lo cual se necesita saber en qué dirección comienza, en este caso,
existe un registro con la dirección base denotada por la letra `a' en la figura
4.3.
Cuando se está buscando una página cualquiera y ésta no está cargada,
surge lo que se llama un `fallo de página' (page fault ). Esto es caro para el
manejador de memoria, ya que tiene que realizar una serie de pasos extra para
poder resolver la dirección deseada y darle su contenido a quien lo pide.
Primero, se detecta que la página no está presente y entonces se busca en la
tabla la dirección de esta página en disco. Una vez localizada en disco se
intenta cargar en alguna página libre de RAM. Si no hay páginas libres se
tiene que escoger alguna para enviarla hacia el disco. Una vez escogida y
enviada a disco, se marca su valor de control en la tabla de direcciones
virtuales para indicar que ya no está en RAM, mientras que la página deseada
se carga en RAM y se marca su valor para indicar que ahora ya está en RAM. Todo
este procedimiento es caro, ya que se sabe que los accesos a disco duro son del
orden de decenas de veces más lentos que en RAM. En el ejemplo anterior se
mencionó que cuando se necesita descargar una página de RAM hacia disco se
debe de hacer una elección. Para realizar esta elección existen varios
algoritmos, los cuales se describen enseguida. _ La primera en entrar, primera
en salir: Se escoge la página que haya entrado primero y esté cargada en RAM.
Se necesita que en los valores de control se guarde un dato de tiempo. No es
eficiente porque no aprovecha ninguna característica de ningún sistema. Es
justa e imparcial. _ La no usada recientemente: Se escoge la página que no haya
sido usada (referenciada) en el ciclo anterior. Pretende aprovechar el hecho de
la localidad en el conjunto de trabajo.
- La usada menos recientemente: Es parecida a la anterior, pero escoge la página
que se usó hace más tiempo, pretendiendo que como ya tiene mucho sin
usarse es muy probable que siga sin usarse en los próximos ciclos. Necesita
de una búsqueda exhaustiva.
- La no usada frecuentemente: Este algoritmo toma en cuenta no tanto el
tiempo, sino el número de referencias. En este caso cualquier página que
se use muy poco, menos veces que alguna otra.
- La menos frecuentemente usada: Es parecida a la anterior, pero aquí se
busca en forma exhaustiva aquella página que se ha usado menos que todas
las demás.
- En forma aleatoria: Elige cualquier página sin aprovechar nada. Es justa
e imparcial, pero ineficiente.
Otro dato interesante de la paginación es que ya no se requiere que los
programas estén ubicados en zonas de memoria adyacente, ya que las páginas
pueden estar ubicadas en cualquier lugar de la memoria RAM.
Segmentación pura
La segmentación se aprovecha del hecho de que los programas se dividen en
partes lógicas, como son las partes de datos, de código y de pila (stack). La
segmentación asigna particiones de memoria a cada segmento de un programa y
busca como objetivos el hacer fácil el compartir segmentos ( por ejemplo librerías
compartidas ) y el intercambio entre memoria y los medios de almacenamiento
secundario.
Por ejemplo, en la versión de UNIX SunOS 3.5, no existían librerías
compartidas para algunas herramientas, por ejemplo, para los editores de texto
orientados al ratón y menús. Cada vez que un usuario invocaba a un editor, se
tenía que reservar 1 megabyte de memoria. Como los editores son una herramienta
muy solicitada y frecuentemente usada, se dividió en segmentos para le versión
4.x ( que a su vez se dividen en páginas ), pero lo importante es que la mayor
parte del editor es común para todos los usuarios, de manera que la primera vez
que cualquier usuario lo invocaba, se reservaba un megabyte de memoria como
antes, pero para el segundo, tercero y resto de usuarios, cada editor extra sólo
consumía 20 kilobytes de memoria. El ahorro es impresionante. Obsérvese que en
la segmentación pura las particiones de memoria son de tamaño variable, en
contraste con páginas de tamaño fijo en la paginación pura. También se puede
decir que la segmentación pura tiene una granularidad menor que la paginación
por el tamanó de segmentos versus tamaño de páginas. Nuevamente, para
comprender mejor la segmentación, se debe dar un repaso a la forma en que las
direcciones virtuales son traducidas a direcciones reales, y para ellos se usa
la figura 4.4. Prácticamente la traducción es igual que la llevada a cabo en
la paginación pura, tomando en consideracióñ que el tamaño de los bloques a
controlar por la tabla de traducción son variables, por lo cual, cada entrada
en dicha tabla debe contener la longitud de cada segmento a controlar. Otra vez
se cuenta con un registro base que contiene la dirección del comienzo de la
tabla de segmentos. La dirección virtual se compone de un número de segmento
(s) y un desplazamiento ( d ) para ubicar un byte (o palabra ) dentro de dicho
segmento. Es importante que el desplazamiento no sea mayor que el tamaño del
segmento, lo cual se controla simplemente checando que ese valor sea mayor que
la dirección del inicio del segmento y menor que el inicio sumado al tamaño.
Una ves dada una dirección virtual v=( s,d ), se realiza la operación b + s
para hallar el registro (o entrada de la tabla de segmentos ) que contiene la
dirección de inicio del segmento en la memoria real, denotado por s'. Ya
conociendo la dirección de inicio en memoria real s' sólo resta encontrar el
byte o palabra deseada, lo cual se hace sumándole a s' el valor del
desplazamiento, de modo que la dirección real ® r = s'+ d.
Cada entrada en la tabla de segmentos tiene un formato similar al mostrado en
la figura 4.5. Se tienen campos que indican la longitud, los permisos, la
presencia o ausencia y dirección de inicio en memoria real del segmento.
Según amplios experimentos [Deitel93] sugieren que un tamaño de páginas de
1024 bytes generalmente ofrece un desempeño muy aceptable. Intuitivamente
parecería que el tener páginas del mayor tamaño posible haría que el desempeño
fuera óptimo pero no es así, a menos que la página fuera del tamaño del
proceso total. No es así con tamaños grandes de página menores que el
proceso, ya que cuando se trae a memoria principal una página por motivo de un
solo byte o palabra, se están trayendo muchísimos más bytes de los deseados.
La dependencia entre el número de fallas respecto al tamaño de las páginas se
muestra en la figura 4.6.
Un hecho notable en los sistemas que manejan paginación es que cuando el
proceso comienza a ejecutarse ocurren un gran número de fallos de página,
porque es cuando está referenciando muchas direcciones nuevas por vez primera,
después el sistema se estabiliza, conforme el número de marcos asignados se
acerca al tamaño del conjunto de trabajo.
El la figura 4.7 se muestra la relación entre el tiempo promedio entre
fallas de página y el número de marcos de página asignados a un proceso. Allí
se ve que el tiempo entre fallas decrece conforme se le asignan más páginas al
proceso. La gráfica se curva en un punto, el cual corresponde a que el proceso
tiene un número de páginas asignado igual al que necesita para almacenar su
conjunto de trabajo. Después de eso, el asignarle a un proceso más páginas
que las de su conjunto de trabajo ya no conviene, ya que el tiempo promedio
entre fallas permanece sin mucha mejora. Un aspecto curioso de aumentar el número
de páginas a un proceso cuando el algoritmo de selección de páginas
candidatas a irse a disco es la primera en entrar primera en salir es la llamada
`anomalía FIFO' a `anomalía de Belady'. Belady encontró ejemplos en los que
un sistema con un número de páginas igual a tres tenía menos fallas de páginas
que un sistema con cuatro páginas. El ejemplo descrito en [Tanxx] es injusto.
Si se mira con cuidado, obviamente si se compara un sistema con 10 páginas
contra otro de 5, ya de inicio el primer sistema tendrá 5 fallos de página más
que el de 5, porque se necesitan diez fallos para cargarlo. A esto debería llamársele
`anomalía de Belady con corrección.
Sistemas combinados
La paginación y la segmentación puras son métodos de manejo de memoria
bastante efectivos, aunque la mayoría de los sistemas operativos modernos
implantan esquemas combinados, es decir, combinan la paginación y la segmentación.
La idea de combinar estos esquemas se debe a que de esta forma se aprovechan los
conceptos de la división lógica de los programas (segmentos) con la
granularidad de las páginas. De esta forma, un proceso estará repartido en la
memoria real en pequeñas unidades (páginas) cuya liga son los segmentos. También
es factible así el compartir segmentos a medida que las partes necesitadas de
los mismos se van referenciando (páginas). Para comprender este esquema,
nuevamente se verá cómo se traduce una dirección virtual en una localidad de
memoria real. Para la paginación y segmentacíon puras se puede decir que el
direccionamiento es `bidimensional' porque se necesitan dos valores para hallar
la dirección real. Para el caso combinado, se puede decir que se tiene un
direccionamiento `tridimensional'. En la figura 4.8 [ Deitel93] se muestran las
partes relevantes para lograr la traducción de direcciones. El sistema debe
contar con una tabla de procesos (TP). Por cada renglón de esa tabla se tiene
un número de proceso y una dirección a una tabla de segmentos. Es decir, cada
proceso tiene una tabla de segmentos. Cuando un proceso hace alguna referencia a
memoria, se consulta TP para encontrar la tabla de segmentos de ese proceso. En
cada tabla de segmentos de proceso (TSP) se tienen los números de los segmentos
que componen a ese proceso. Por cada segmento se tiene una dirección a una
tabla de páginas. Cada tabla de páginas tiene las direcciones de las páginas
que componen a un solo segmento. Por ejemplo, el segmento `A' puede estar
formado por las páginas reales `a','b','c','p' y `x'. El segmento `B' puede
estar compuesto de las páginas `f','g','j','w' y `z'.
Para traducir una dirección virtual v=(s,p,d) donde `s' es el segmento, `p'
es la página y `d' el desplazamiento en la página se hace lo siguiente.
Primero se ubica de qué proceso es el segmento y se localiza la tabla de
segmentos de ese proceso en la TP. Con `s' como índice se encuentra un renglón
( registro) en la tabla de segmentos de ese proceso y en ese renglón está la
dirección de la tabla de páginas que componen al segmento. Una vez en la tabla
de páginas se usa el valor `p' como índice para encontrar la dirección de la
página en memoria real. Una vez en esa dirección de memoria real se encuentra
el byte (o palabra) requerido por medio del valor de `d'.
Ahora, en este esquema pueden haber dos tipos de fallos: por fallo de página
y por fallo de segmento. Cuando se hace referencia a una dirección y el
segmento que la contiene no está en RAM ( aunque sea parcialmente ), se provoca
un fallo por falta de segmento y lo que se hace es traerlo del medio de
almacenamiento secundario y crearle una tabla de páginas. Una vez caragado el
segmento se necesita localizar la página correspondiente, pero ésta no existe
en RAM, por lo cual se provoca un fallo de página y se carga de disco y
finalmente se puede ya traer la dirección deseada por medio del desplazamiento
de la dirección virtual.
La eficiencia de la traducción de direcciones tanto en paginación pura,
segmentación pura y esquemas combinados se mejora usando memorias asociativas
para las tablas de páginas y segmentos, así como memorias cache para guardar
los mapeos más solicitados.
Otro aspecto importante es la estrategia para cargar páginas ( o segmentos )
a la memoria RAM. Se usan más comunmente dos estrategias: cargado de páginas
por demanda y cargado de páginas anticipada. La estrategia de caragdo por
demanda consiste en que las páginas solamente son llevadas a RAM si fueron
solicitadas, es decir, si se hizo referencia a una dirección que cae dentro de
ellas. La carga anticipada consiste en tratar de adivinar qué páginas serán
solicitadas en el futuro inmediato y cargarlas de antemano, para que cuando se
pidan ya no ocurran fallos de página. Ese `adivinar' puede ser que se aproveche
el fenómeno de localidad y que las páginas que se cargan por anticipado sean
aquellas que contienen direcciones contiguas a la dirección que se acaba de
refenciar. De hecho, el sistema operativo VMS usa un esquema combinado para
cargar páginas: cuando se hace referencia a una dirección cuya página no está
en RAM, se provoca un fallo de página y se carga esa página junto con algunas
páginas adyacentes. En este caso la página solicitada se cargó por demanda y
las adyacentes se cargaron por anticipación.
5. Administracion de procesos
Uno de los módulos más importantes de un sistema operativo es la de
administrar los procesos y tareas del sistema de cómputo. En esta sección se
revisarán dos temas que componen o conciernen a este módulo: la planificación
del procesador y los problemas de concurrencia.
Planificación del procesador
La planificación del procesador se refiere a la manera o técnicas que se usan
para decidir cuánto tiempo de ejecución y cuando se le asignan a cada proceso
del sistema. Obviamente, si el sistema es monousuario y monotarea nohay mucho
que decidir, pero en el resto de los sistemas esto es crucial para el buen
funcionamiento del sistema.
Niveles de planificación
En los sistemas de planificación generalmente se identifican tres niveles: el
alto, em medio y el bajo. El nivel alto decide que trabajos (conjunto de
procesos) son candidatos a convertirse en procesos compitiendo por los recursos
del sistema; el nivel intermedio decide que procesos se suspenden o reanudan
para lograr ciertas metas de rendimiento mientras que el planificador de bajo
nivel es el que decide que proceso, de los que ya están listos (y que en algún
momento paso por los otros dos planificadores) es al que le toca ahora estar
ejecutándose en la unidad central de procesamiento. En este trabajo se
revisaran principalmente los planificadores de bajo nivel porque son los que
finalmente eligen al proceso en ejecución.
Objetivos de la planificación
Una estrategia de planificación debe buscar que los procesos obtengan sus
turnos de ejecución apropiadamente, conjuntamente con un buen rendimiento y
minimización de la sobrecarga (overhead) del planificador mismo. En general, se
buscan cinco objetivos principales:
- Justicia o Imparcialidad: Todos los procesos son tratados de la misma
forma, y en algún momento obtienen su turno de ejecución o intervalos de
tiempo de ejecución hasta su terminación exitosa.
- Maximizar la Producción: El sistema debe de finalizar el mayor numero de
procesos en por unidad de tiempo.
- Maximizar el Tiempo de Respuesta: Cada usuario o proceso debe observar que
el sistema les responde consistentemente a sus requerimientos.
- Evitar el aplazamiento indefinido: Los procesos deben terminar en un plazo
finito de tiempo.
- El sistema debe ser predecible: Ante cargas de trabajo ligeras el sistema
debe responder rápido y con cargas pesadas debe ir degradándose
paulatinamente. Otro punto de vista de esto es que si se ejecuta el mismo
proceso en cargas similares de todo el sistema, la respuesta en todos los
casos debe ser similar.
Características a considerar de los procesos
- No todos los equipos de cómputo procesan el mismo tipo de trabajos, y un
algoritmo de planificación que en un sistema funciona excelente puede dar
un rendimiento pésimo en otro cuyos procesos tienen características
diferentes. Estas características pueden ser:
- Cantidad de Entrada/Salida: Existen procesos que realizan una gran
cantidad de operaciones de entrada y salida (aplicaciones de bases de datos,
por ejemplo).
- Cantidad de Uso de CPU: Existen procesos que no realizan muchas
operaciones de entrada y salida, sino que usan intensivamente la unidad
central de procesamiento. Por ejemplo, operaciones con matrices.
- Procesos de Lote o Interactivos: Un proceso de lote es más eficiente en
cuanto a la lectura de datos, ya que generalmente lo hace de archivos,
mientras que un programa interactivo espera mucho tiempo (no es lo mismo el
tiempo de lectura de un archivo que la velocidad en que una persona teclea
datos) por las respuestas de los usuarios.
- Procesos en Tiempo Real: Si los procesos deben dar respuesta en tiempo
real se requiere que tengan prioridad para los turnos de ejecución.
- Longevidad de los Procesos: Existen procesos que tipicamente requeriran
varias horas para finalizar su labor, mientras que existen otros que
solonecesitan algunos segundos.
Planificación apropiativa o no apropiativa (preemptive or not preemptive)
La planificación apropiativa es aquella en la cual, una vez que a un proceso le
toca su turno de ejecución ya no puede ser suspendido, ya no se le puede
arrebatar la unidad central de procesamiento. Este esquema puede ser peligroso,
ya que si el proceso contiene accidental o deliberadamente ciclos infinitos, el
resto de los procesos pueden quedar aplazados indefinidamente. Una planificación
no apropiativa es aquella en que existe un reloj que lanza interrupciones
periodicas en las cuales el planificador toma el control y se decide si el mismo
proceso seguirá ejecutándose o se le da su turno a otro proceso. Este mismo
reloj puede servir para lanzar procesos manejados por el reloj del sistema. Por
ejemplo en los sistemas UNIX existen los 'cronjobs' y 'atjobs', los cuales se
programan en base a la hora, minuto, día del mes, día de la semana y día del
año.
En una planificación no apropiativa, un trabajo muy grande aplaza mucho a
uno pequeño, y si entra un proceso de alta prioridad esté también debe
esperar a que termine el proceso actual en ejecución.
Asignación del turno de ejecución
Los algoritmos de la capa baja para asignar el turno de ejecución se describen
a continuación:
- Por prioridad: Los procesos de mayor prioridad se ejecutan primero. Si
existen varios procesos de mayor prioridad que otros, pero entre ellos con
la misma prioridad, pueden ejecutarse estos de acuerdo a su orden de llegada
o por 'round robin'. La ventaja de este algoritmo es que es flexible en
cuanto a permitir que ciertos procesos se ejecuten primero e, incluso, por más
tiempo. Su desventaja es que puede provocar aplazamiento indefinido en los
procesos de baja prioridad. Por ejemplo, suponga que existen procesos de
prioridad 20 y procesos de prioridad 10. Si durante todo el día terminan
procesos de prioridad 20 al mismo ritmo que entran otros con esa prioridad,
el efecto es que los de prioridad 10 estarán esperando por siempre. También
provoca que el sistema sea impredecible para los procesos de baja prioridad.
- El trabajo más corto primero: Es dificil de llevar a cabo porque se
requiere saber o tener una estimación de cuánto tiempo necesita el proceso
para terminar. Pero si se sabe, se ejecutan primero aquellos trabajos que
necesitan menos tiempo y de esta manera se obtiene el mejor tiempo de
respuesta promedio para todos los procesos. Por ejemplo, si llegan 5
procesos A,B,C,D y E cuyos tiempos de CPU son 26, 18, 24, 12 y 4 unidades de
tiempo, se observa que el orden de ejecución será E,D,B,C y A (4,12,18, 24
y 26 unidades de tiempo respectivamente). En la tabla siguiente se muestra
en que unidad de tiempo comienza a ejecutarse cada proceso y como todos
comenzaron a esperar desde la unidad cero, se obtiene el tiempo promedio de
espera.
Proceso Espera desde Termina Tiempo de Espera
A 0 4 4
B 0 16 16
C 0 34 34
D 0 58 58
E 0 84 84
Tiempo promedio = (4 + 16 + 34 + 58 + 84 )/5 = 39 unidades.
- El primero en llegar, primero en ejecutarse: Es muy simple, los procesos
reciben su turno conforme llegan. La ventaja de este algoritmo es que es
justo y no provoca aplazamiento indefinido. La desventaja es que no
aprovecha ninguna característica de los procesos y puede no servir para
unproceso de tiempo real. Por ejemplo, el tiempo promedio de respuesta puede
ser muy malo comparado con el logrado por el del trabajo más corto primero.
Retomando el mismo ejemplo que en el algoritmo anterior, obtenemos un tiempo
de respuesta promedio (26+44+68+80+84)/5 = 60 unidades, el cual es muy
superior a las 39 unidades que es el mejor tiempo posible.
- Round Robin: También llamada por turno, consiste en darle a cada proceso
un intervalo de tiempo de ejecución (llamado time slice), y cada vez que se
vence ese intervalo se copia el contexto del proceso a un lugar seguro y se
le da su turno a otro proceso. Los procesos están ordenados en una cola
circular. Por ejemplo, si existen tres procesos, el A,B y C, dos repasadas
del planificador darían sus turnos a los procesos en el orden A,B,C,A,B,C.
La ventaja de este algoritmo es su simplicidad, es justo y no provoca
aplazamiento indefinido.
- El tiempo restante más corto: Es parecido al del trabajo más corto
primero, pero aquií se está calculando en todo momento cuánto tiempo le
resta para terminar a todos los procesos, incluyendo los nuevos, y aquel que
le quede menos tiempo para finalizar es escogido para ejecutarse. La ventaja
es que es muy útil para sistemas de tiempo compartido porque se acerca
mucho al mejor tiempo de respuesta, además de responder dinámicamente a la
longevidad de los procesos; su desventaja es que provoca más sobrecarga
porque el algoritmo es más complejo.
- La tasa de respuesta más alta: Este algoritmo concede el truno de ejecución
al proceso que produzca el valor mayor de la siguiente formula:
tiempo que ha esperado + tiempo total para terminar
valor = ___________________________________________
tiempo total para terminar.
Es decir, que dinámicamente el valor se va modificando y mejora un poco las
deficiciencias del algoritmo del trabajo más corto primero.
- Por politica: Una forma de asignar el turno de ejecución es por politica,
en la cual se establece algún reglamento específico que el planificador
debe obedecer. Por ejemplo, una politica podría ser que todos los procesos
reciban el mismo tiempo de uso de CPU en cualquier momento. Esto sig-
nifica, por ejemplo, que si existen 2 procesos y han recibido 20 unidades de
tiempo cada uno (tiempo acumulado en time slices de 5 unidades) y en este
momento entra un tercer proceso, el planificador le dara inmediatamente el
turno de ejecución por 20 unidades de tiempo. Una vez que todos los
procesos están 'parejos' en uso de CPU, se les aplica 'round robin'.
Problemas de Concurrencia
En los sistemas de tiempo compartido (aquellos con varios usuarios, procesos,
tareas, trabajos que reparten el uso de CPU entre estos) se presentan muchos
problemas debido a que los procesos compiten por los recursos del sistema.
Imagine que un proceso está escribiendo en la unidad de cinta y se le termina
su turno de ejecución e inmediatamente después el proceso elegido para
ejecutarse comienza a escribir sobre la misma cinta. El resultado es una cinta
cuyo contenido es un desastre de datos mezclados. Así como la cinta, existen
una multitud de recursos cuyo acceso debe der controlado para evitar los
problemas de la concurrencia.
El sistema operativo debe ofrecer mecanismos para sincronizar la ejecución
de procesos: semáforos, envío de mensajes, 'pipes', etc. Los semáforos son
rutinas de software (que en su nivel más interno se auxilian del hardware) para
lograr exclusión mutua en el uso de recursos. Para entender este y otros
mecanismos es importante entender los problemas generales de concurrencia, los
cuales se describen enseguida.
- Condiciones de Carrera o Competencia: La condición de carrera (race
condition) ocurre cuando dos o más procesos accesan un recurso compartido
sin control, de manera que el resultado combinado de este acceso depende del
orden de llegada. Suponga, por ejemplo, que dos clientes de un banco
realizan cada uno una operación en cajeros diferentes al mismo tiempo.
- El usuario A quiere hacer un depósito. El B un retiro. El usuario A
comienza la transacción y lee su saldo que es 1000. En ese momento pierde
su turno de ejecución (y su saldo queda como 1000) y el usuario B inicia el
retiro: lee el saldo que es 1000, retira 200 y almacena el nuevo saldo que
es 800 y termina. El turno de ejecución regresa al usuario A el cual hace
su depósito de 100, quedando saldo = saldo + 100 = 1000 + 100 = 1100. Como
se ve, el retiro se perdió y eso le encanta al usuario A y B, pero al
banquero no le convino esta transacción. El error pudo ser al revés,
quedando el saldo final en 800.
- Postergación o Aplazamiento Indefinido(a): Esto se mencionó en el
apartado anterior y consiste en el hecho de que uno o varios procesos nunca
reciban el suficiente tiempo de ejecución para terminar su tarea. Por
ejemplo, que un proceso ocupe un recurso y lo marque como 'ocupado' y que
termine sin marcarlo como 'desocupado'. Si algún otro proceso pide ese
recurso, lo verá 'ocupado' y esperará indefinidamente a que se 'desocupe'.
- Condición de Espera Circular: Esto ocurre cuando dos o más procesos
forman una cadena de espera que los involucra a todos. Por ejemplo, suponga
que el proceso A tiene asignado el recurso 'cinta' y el proceso B tiene
asignado el recurso 'disco'. En ese momento al proceso A se le ocurre pedir
el recurso 'disco' y al proceso B el recurso 'cinta'. Ahi se forma una
espera circular entre esos dos procesos que se puede evitar quitándole a la
fuerza un recurso a cualquiera de los dos procesos.
- Condición de No Apropiación: Esta condición no resulta precisamente de
la concurrencia, pero juega un papel importante en este ambiente. Esta
condición especifica que si un proceso tiene asignado un recurso, dicho
recurso no puede arrebatársele por ningún motivo, y estará disponible
hasta que el proceso lo 'suelte' por su voluntad.
- Condición de Espera Ocupada: Esta condición consiste en que un proceso
pide un recurso que ya está asignado a otro proceso y la condición de no
apropiación se debe cumplir. Entonces el proceso estará gastando el resto
de su time slice checando si el recurso fue liberado. Es decir, desperdicia
su tiempo de ejecución en esperar. La solución más común a este problema
consiste en que el sistema operativo se dé cuenta de esta situación y
mande a una cola de espera al proceso, otorgándole inmediatamente el turno
de ejecución a otro proceso.
- Condición de Exclusión Mutua: Cuando un proceso usa un recurso del
sistema realiza una serie de operaciones sobre el recurso y después lo deja
de usar. A la sección de código que usa ese recurso se le llama 'región
crítica'. La condición de exclusión mutua establece que solamente se
permite a un proceso estar dentro de la misma región crítica. Esto es, que
en cualquier momento solamente un proceso puede usar un recurso a la vez.
Para lograr la exclusión mutua se ideo también el concepto de 'región crítica'.
Para logar la exclusión mutua generalmente se usan algunas técnicas para
lograr entrar a la región crítica: semáforos, monitores, el algoritmo de
Dekker y Peterson, los 'candados'. Para ver una descripción de estos
algoritmos consulte
- Condición de Ocupar y Esperar un Recurso: Consiste en que un proceso pide
un recurso y se le asigna. Antes de soltarlo, pide otro recurso que otro
proceso ya tiene asignado.
Los problemas descritos son todos importantes para el sistema operativo, ya
que debe ser capaz de prevenir o corregirlos. Tal vez el problema más serio que
se puede presentar en un ambiente de concurrencia es el 'abrazo mortal', también
llamado 'trabazón' y en inglés deadlock. El deadlock es una condición que
ningún sistema o conjunto de procesos quisiera exhibir, ya que consiste en que
se presentan al mismo tiempo cuatro condiciones necesarias: La condición de no
apropiación, la condición de espera circular, la condición de exclusión
mutua y la condición de ocupar y esperar un recurso. Ante esto, si el deadlock
involucra a todos los procesos del sistema, el sistema ya no podrá hacer algo
productivo. Si el deadlock involucra algunos procesos, éstos quedarán
congelados para siempre.
En el área de la informática, el problema del deadlock ha provocado y
producido una serie de estudios y técnicas muy útiles, ya que éste puede
surgir en una sola máquina o como consecuencia de compartir recursos en una
red.
En el área de las bases de datos y sistemas distribuidos han surgido técnicas
como el 'two phase locking' y el 'two phase commit' que van más allá de este
trabajo. Sin embargo, el interés principal sobre este problema se centra en
generar técnicas para detectar, prevenir o corregir el deadlock.
Las técnicas para prevenir el deadlock consisten en proveer mecanismos para
evitar que se presente una o varias de las cuatro condiciones necesarias del
deadlock. Algunas de ellas son:
- Asignar recursos en orden lineal: Esto significa que todos los recursos
están etiquetados con un valor diferente y los procesos solo pueden hacer
peticiones de recursos 'hacia adelante'. Esto es, que si un proceso tiene el
recurso con etiqueta '5' no puede pedir recursos cuya etiqueta sea menor que
'5'. Con esto se evita la condición de ocupar y esperar un recurso.
- Asignar todo o nada: Este mecanismo consiste en que el proceso pida todos
los recursos que va a necesitar de una vez y el sistema se los da solamente
si puede dárselos todos, si no, no le da nada y lo bloquea.
- Algoritmo del banquero: Este algoritmo usa una tabla de recursos para
saber cuántos recursos tiene de todo tipo. También requiere que los
procesos informen del máximo de recursos que va a usar de cada tipo. Cuando
un proceso pide un recurso, el algoritmo verifica si asignándole ese
recurso todavía le quedan otros del mismo tipo para que alguno de los
procesos en el sistema todavía se le pueda dar hasta su máximo. Si la
respuesta es afirmativa, el sistema se dice que está en 'estado seguro' y
se otorga el recurso. Si la respuesta es negativa, se dice que el sistema
está en estado inseguro y se hace esperar a ese proceso.
Para detectar un deadlock, se puede usar el mismo algoritmo del banquero, que
aunque no dice que hay un deadlock, sí dice cuándo se está en estado inseguro
que es la antesala del deadlock. Sin embargo, para detectar el deadlock se
pueden usar las 'gráficas de recursos'. En ellas se pueden usar cuadrados para
indicar procesos y círculos para los recursos, y flechas para indicar si un
recurso ya está asignado a un proceso o si un proceso está esperando un
recurso. El deadlock es detectado cuando se puede hacer un viaje de ida y vuelta
desde un proceso o recurso. Por ejemplo, suponga los siguientes eventos:
evento 1: Proceso A pide recurso 1 y se le asigna.
evento 2: Proceso A termina su time slice.
evento 3: Proceso B pide recurso 2 y se le asigna.
evento 4: Proceso B termina su time slice.
evento 5: Proceso C pide recurso 3 y se le asigna.
evento 6: Proceso C pide recurso 1 y como lo está ocupando el proceso A,
espera.
evento 7: Proceso B pide recurso 3 y se bloquea porque lo ocupa el proceso C.
evento 8: Proceso A pide recurso 2 y se bloquea porque lo ocupa el proceso B.
En la figura 5.1 se observa como el 'resource graph' fue evolucionando hasta
que se presentó el deadlock, el cual significa que se puede viajar por las
flechas desde un proceso o recurso hasta regresar al punto de partida. En el
deadlock están involucrados los procesos A,B y C.
Una vez que un deadlock se detecta, es obvio que el sistema está en
problemas y lo único que resta por hacer es una de dos cosas: tener algún
mecanismo de suspensión o reanudación [Deitel93] que permita copiar todo el
contexto de un proceso incluyendo valores de memoria y aspecto de los periféricos
que esté usando para reanudarlo otro día, o simplemente eliminar un proceso o
arrebatarle el recurso, causando para ese proceso la pérdida de datos y tiempo.
6. Principios en el manejo de entrada – salida
El código destinado a manejar la entrada y salida de los diferentes periféricos
en un sistema operativo es de una extensión considerable y sumamente complejo.
Resuelve la necesidades de sincronizar, atrapar interrupciones y ofrecer
llamadas al sistema para los programadores. En esta sección se repasarán los
principios más importantes a tomar en cuenta en este módulo del sistema
operativo.
Dispositivos de Entrada - Salida
Los dispositivos de entrada salida se dividen, en general, en dos tipos:
dispositivos orientados a bloques y dispositivos orientados a caracteres. Los
dispositivos orientados a bloques tienen la propiedad de que se pueden
direccionar, esto es, el programador puede escribir o leer cualquier bloque del
dispositivo realizando primero una operación de posicionamiento sobre el
dispositivo. Los dispositivos más comunes orientados a bloques son los discos
duros, la memoria, discos compactos y, posiblemente, unidades de cinta. Por otro
lado, los dispositivos orientados a caracteres son aquellos que trabajan con
secuencias de byes sin importar su longitud ni ningúna agrupación en especial.
No son dispositivos direccionables. Ejemplos de estos dispositivos son el
teclado, la pantalla o display y las impresoras.
La clasificación anterior no es perfecta, porque existen varios dispositivos
que generan entrada o salida que no pueden englobarse en esas categorías. Por
ejemplo, un reloj que genera pulsos. Sin embargo, aunque existan algunos periféricos
que no se puedan categorizar, todos están administrados por el sistema
operativo por medio de una parte electrónica - mecánica y una parte de
software.
Controladores de Dispositivos ( Terminales y Discos Duros)
Los controladores de dispositivos (también llamados adaptadores de
dispositivos) son la parte electrónica de los periféricos, el cual puede tener
la forma de una tarjeta o un circuito impreso integrado a la tarjeta maestra de
la computadora. Por ejemplo, existen controladores de discos que se venden por
separado y que se insertan en una ranura de la computadora, o existen
fabricantes de computadoras que integran esa funcionalidad en la misma tarjeta
en que viene la unidad central de procesamiento (tarjeta maestra).
Los controladores de dispositivos generalmente trabajan con voltajes de 5 y
12 volts con el dispositivo propiamente, y con la computadora a través de
interrupciones. Estas interrupciones viajan por el 'bus' de la computadora y son
recibidos por el CPU el cual a su vez pondrá en ejecución algún programa que
sabrá qué hacer con esa señal. A ese programa se le llama 'manejador de
disposito' (device driver). Algunas veces el mismo controlador contiene un pequeño
programa en una memoria de solo lectura o en memoria de acceso aleatoria no volátil
y re-escribible que interactúa con el correspondiente manejador en la
computadora. En la figura 6.1 se muestra un esquema simple de dispositivos
orientados a bloques y otros a caracteres.
Por ejemplo, la terminal (CRT) tiene un 'chip' que se encarga de enviar
cadenas de bits por medio de un cable serial que a su vez son recibidos por un
controlador de puerto serial en la computadora. Este 'chip' también se encarga
de leer secuencias de bits que agrupa para su despiegue en la pantalla o para
ejecutar algunas funciones de control. Lo importante en todos estos dispositivos
es que se debe ejercer un mecanismo para sincronizar el envío y llegada de
datos de manera concurrente.
Para intercambiar datos o señales entre la computadora y los controladores,
muchas veces se usan registros o secciones predefinidas de la memoria de la
computadora. A este esquema se le llama 'manejo de entrada - salida mapeado por
memoria' (memory mapped I/O). Por ejmplo, para una IBM PC se muestran los
vectores de interrupción y las direcciones para la entrada -salida en la tabla
6.1.
Controlador Dirección(Hex) Vector de Interrupción
Reloj 040 - 043 8
Teclado 060 - 063 9
Disco Duro 320 - 32F 13
Impresora 378 - 37F 15
Monitor Mono 380 - 3BF -
Monitor Color 3D0 - 3DF -
Disco Flexible 3F0 - 3F7 14
Tabla 6.1 Direcciones de Mapeo de Entrada - Salida
Acceso Directo a Memoria (DMA)
El acceso directo a memoria se inventó con el propósito de liberar al CPU de
la carga de atender a algunos controladores de dispositivos. Para comprender su
funcionamiento vale la pena revisar cómo trabaja un controlador sin DMA. Cuando
un proceso requiere algunos bloques de un dispositivo, se envia una señal al
controlador con la dirección del bloque deseado. El controlador lo recibe a
través del 'bus' y el proceso puede estar esperando la respuesta (trabajo síncrono)
o puede estar haciendo otra cosa (trabajo asíncrono). El controlador recibe la
señal y lee la dirección del bus. Envía a su vez una o varias señales al
dispositivo mecánico (si es que lo hay) y espera los datos. Cuando los recibe
los escribe en un buffer local y envía una señal al CPU indicándole que los
datos están listos. El CPU recibe esta interrupción y comienza a leer byte por
byte o palabra por palabra los datos del buffer del controlador (a través del
device driver) hasta terminar la operación.
Como se ve, el CPU gasta varios ciclos en leer los datos deseados. El DMA
soluciona ese problema de la manera siguiente. Cuando un proceso requiere uno o
varios bloques de datos, el CPU envía al controlador la petición junto con el
número de bytes deseados y la dirección de en dónde quiere que se almacenen
de regreso. El DMA actuará como un 'cpu secundario' [Stal92] en cuanto a que
tiene el poder de tomar el control del 'bus' e indicarle al verdadero CPU que
espere. Cuando el controlador tiene listos los datos, el DMA 'escucha' si el
'bus' está libre aprovechando esos ciclos para ir leyendo los datos del buffer
del controlador e ir escribiéndolos en el área de memoria que el CPU le indicó.
Cuando todos los datos fueron escritos, se le envía una interrupción al CPU
para que use los datos. El ahorro con el DMA es que el CPU ya no es interrumpido
(aunque sí puede ser retardado por el DMA) salvando así el 'cambio de
contexto' y además el DMA aprovechará aquellos ciclos en que el 'bus' no fue
usado por el CPU.
El hecho de que los controladores necesiten buffers internos se debe a que
conforme ellos reciban datos de los dispositivos que controlan, los deben poder
almacenar temporalmente, ya que el CPU no está listo en todo momento para
leerlos.
Principios en el Software de Entrada - Salida
Los principios de software en la entrada - salida se resumen en cuatro puntos:
el software debe ofrecer manejadores de interrupciones, manejadores de
dispositivos, software que sea independiente de los dispositivos y software para
usuarios.
Manejadores de interrupciones
El primer objetivo referente a los manejadores de interrupciones consiste en que
el programador o el usuario no debe darse cuenta de los manejos de bajo nivel
para los casos en que el dispositivo está ocupado y se debe suspender el
proceso o sincronizar algunas tareas. Desde el punto de vista del proceso o
usuario, el sistema simplemente se tardó más o menos en responder a su petición.
Manejadores de disposisitivos
El sistema debe proveer los manejadores de dispositivos necesarios para los
periféricos, así como ocultar las peculiaridades del manejo interno de cada
uno de ellos, tales como el formato de la información, los medios mecánicos,
los niveles de voltaje y otros. Por ejemplo, si el sistema tiene varios tipos
diferentes de discos duros, para el usuario o programador las diferencias técnicas
entre ellos no le deben importar, y los manejadores le deben ofrecer el mismo
conjunto de rutinas para leer y escribir datos.
Software independiente del dispositivo
Este es un nivel superior de independencia que el ofrecido por los manejadores
de dispositivos. Aquí el sistema operativo debe ser capaz, en lo más posible,
de ofrecer un conjunto de utilerías para accesar periféricos o programarlos de
una manera consistente. Por ejemplo, que para todos los dispositivos orientados
a bloques se tenga una llamada para decidir si se desea usar 'buffers' o no, o
para posicionarse en ellos.
Software para usuarios
La mayoría de las rutinas de entrada - salida trabajan en modo privilegiado, o
son llamadas al sistema que se ligan a los programas del usuario formando parte
de sus aplicaciones y que no le dejan ninguna flexibilidad al usuario en cuanto
a la apariencia de los datos. Existen otras librerías en donde el usuario si
tiene poder de decisión (por ejemplo la llamada a "printf" en el
lenguaje "C"). Otra facilidad ofrecida son las áreas de trabajos
encolados (spooling areas), tales como las de impresión y correo electrónico.
Relojes
Los relojes son esenciales para el buen funcionamiento de cualquier sistema
porque juegan un papel decisivo en la sincronización de procesos, en la
calendarización de trabajos por lote y para la asignación de turnos de ejecución
entre otras tareas relevantes. Generalmente se cuenta con dos relojes en el
sistema: uno que lleva la hora y fecha del sistema y que oscila entre 50 y 60
veces por segundo y el reloj que oscila entre 5 y 100 millones de veces por
segundo y que se encarga de enviar interrupciones al CPU de manera periódica.
El reloj de mayor frecuencia sirve para controlar el tiempo de ejecución de los
procesos, para despertar los procesos que están 'durmiendo' y para lanzar o
iniciar procesos que fueron calendarizados.
Para mantener la hora y fecha del sistema generalmente se usa un registro
alimentado por una pila de alta duración que almacena estos datos y que se
programan de fábrica por primera vez. Así, aunque se suspenda la energía la
fecha permanece. Para lanzar procesos (chequeo de tiempo ocioso de un
dispositivo, terminación del time slice de un proceso, etc), se almacena un
valor en un registro (valor QUANTUM) el cual se decrementa con cada ciclo del
reloj, y cuando llega a cero se dispara un proceso que ejecutará las
operaciones necesarias (escoger un nuevo proceso en ejecución, verificar el
funcionamiento del motor del disco flexible, hacer eco de un caracter del
teclado, etc).
7. Nucleos de sistemas operativos
Los núcleos (kernels) de los sistemas operativos se pueden ubicar en dos
categorias: monolíticos o micronúcleos (microkernels). El primer tipo de núcleo
es el más tradicionalmente usado, mientras que los micronúcleos forman parte
delas tendencias modernas en el diseño de sistemas operativos.
Para comprender mejor qué diferencias existen entre ambas categorías, se
necesita revisar algunos conceptos.
Trabajos, Procesos y Thread
Estos tres conceptos van definiendo el grado de granularidad en que el sistema
operativo trata a las masas de operaciones que se tienen que realizar. Un
trabajo se conceptualiza como un conjunto de uno o más procesos. Por ejemplo,
si se tiene que hacer el trabajo de correr el inventario, tal vez se subdivida
ese trabajo en varios procesos: obtener la lista de artículos, número en
existencia, artículos vendidos, artículos extraviados, etc. Un proceso se
define como la imagen de un programa en ejecución, es decir, en memoria y
usando el CPU. A este nivel de granularidad, un proceso tiene un espacio de
direcciones de memoria, una pila, sus registros y su 'program counter'. Un
thread es un trozo o sección de un proceso que tiene sus propios registros,
pila y 'program counter' y puede compartir la memoria con todos aquellos threads
que forman parte del mismo proceso.
Objetos
Un objeto es una entidad que contiene dos partes principales: una colección de
atributos y un conjunto de métodos (también llamados servicios). Generalmente
los atributos del objeto no pueden ser cambiados por el usuario, sino solamente
a través de los métodos. Los métodos sí son accesibles al usuario y de hecho
es lo único que él observa: los métodos conforman lo que se llama la
'interfaz' del objeto. Por ejemplo, para el objeto 'archivo' los métodos son
abrir, cerrar, escribir, borrar, etc. El cómo se abre, se cierra, se borra,
etc; está escondido para el usuario, es decir, los atributos y el código están
'encapsulados'. La única forma de activar un método es a través del envío de
mensajes entre los objetos, o hacia un objeto.
Cliente - Servidor
Un cliente es un proceso que necesita de algún valor o de alguna operación
externa para poder trabajar. A la entidad que prove ese valor o realiza esa
operación se le llama servidor. Por ejemplo, un servidor de archivos debe
correr en el núcleo (kernel) o por medio de un proceso 'guardián' al servidor
de archivos que escucha peticiones de apertura, lectura, escritura, etc; sobre
los archivos. Un cliente es otro proceso guardián que escucha esas peticiones
en las máquinas clientes y se comunica con el proceso servidor a través de la
red, dando la apariencia de que se tienen los archivos en forma local en la máquina
cliente.
Núcleo Monolítico
Los núcleos monolíticos generalmente están divididos en dos partes
estructuradas: el núcleo dependiente del hardware y el núcleo independiente
del hardware. El núcleo dependiente se encarga de manejar las interrupciones
del hardware, hacer el manejo de bajo nivel de memoria y discos y trabajar con
los manejadores de dispositivos de bajo nivel, principalmente. El núcleo
independiente del hardware se encarga de ofrecer las llamadas al sistema,
manejar los sistemas de archivos y la planificación de procesos. Para el
usuario esta división generalmente pasa desapercibida. Para un mismo sistema
operativo corriendo en diferentes plataformas, el núcleo independiente es
exactamente el mismo, mientras que el dependiente debe re-escribirse.
Microkernel
Un núcleo con 'arquitectura' micronúcleo es aquél que contiene únicamente el
manejo de procesos y threads, el de manejo bajo de memoria, da soporte a las
comunicaciones y maneja las interrupciones y operaciones de bajo nivel de
entrada-salida. [Tan92]. En los sistemas oprativos que cuentan con este tipo de
núcleo se usan procesos 'servidores' que se encargan de ofrecer el resto de
servicios (por ejemplo el de sistema de archivos) y que utilizan al núcleo a
través del soporte de comunicaciones.
Este diseño permite que los servidores no estén atados a un fabricante en
especial, incluso el usuario puede escoger o programar sus propios servidores.
La mayoría de los sistemas operativos que usan este esquema manejan los
recursos de la computadora como si fueran objetos: los servidores ofrecen una
serie de 'llamadas' o 'métodos' utilizables con un comportamiento coherente y
estructurado. Otra de las características importantes de los micronúcleos es
el manejo de threads. Cuando un proceso está formado de un solo thread, éste
es un proceso normal como en cualquier sistema operativo.
Los usos más comunes de los micronúcleos es en los sistemas operativos que
intentan ser distribuídos, y en aquellos que sirven como base para instalar
sobre ellos otros sistemas operativos. Por ejemplo, el sistema operativo AMOEBA
intenta ser distribuído y el sistema operativo MACH sirve como base para
instalar sobre él DOS, UNIX, etc.
8. Caso De Estudio: UNIX
Unix es uno de los sistemas operativos más ampliamente usados en
computadoras que varían desde las personales hasta las macro. Existen versiones
para máquinas uniprocesador hasta multiprocesadores. Debido a su historia, que
evoluciona en los Laboratorios Bell de AT&T con un simulador de un viaje
espacial en el sistema solar, pasando por su expansión en universidades y la
creación de las versiones más importantes que son la de la Universidad de
Berkeley y el Sistema V de la misma AT&T.
Estandarización de UNIX
Debido a las múltiples versiones en el mercado de UNIX, se comenzaron a
publicar estándares para que todas la s versiones fuesen 'compatibles'. La
primera de ellas la lanzó AT&T llamada SVID (System V Interface Definition)
que defininía cómo deberían ser las llamadas al sistema, el formato de los
archivos y muchas cosas más, pero la otra versión importante, la de Bekeley
(Berkeley Software Distribution o BSD) simplemente la ignoró. Después la IEEE
usó un algoritmo consistente en revisar las llamadas al sistema de ambas
versiones (System V y BSD) y aquellas que eran iguales las definió como estándares
surgiendo así la definición 'Portable Operating System for UNIX' o POSIX, que
tuvo buen éxito y que varios fabricantes adoptaron rápidamente. El estándard
de POSIX se llama 1003.1 Posteriormente los institutos ANSI e ISO se interesaron
en estandarizar el lenguaje 'C' y conjuntamente se publicaron definiciones estándares
para otras áreas del sistema operativo como la interconectividad, el intérprete
de comandos y otras. En la tabla 8.1 se muestran las definiciones de POSIX.
Estándard Descripción
1003.0 Introducción y repaso.
1003.1 Llamadas al sistema.
1003.2 Intérprete y comandos.
1003.3 Métodos de prueba.
1003.4 Extensiones para tiempo real.
1003.5 Lenguaje Ada.
1003.6 Extensiones para la seguridad
1003.7 Administración del Sistema.
1003.8 Acceso transparente a archivos.
1003.9 Lenguaje Fortran.
1003.10 Supercómputo.
Tabla 8.1 Los Estándares de POSIX
Al momento del auge de los estándares de POSIX desgraciadamente se formó un
grupo de fabricantes de computadoras (IBM, DEC y Hewlett-Packard) que lanzaron
su propia versión de UNIX llamada OSF/1 (de Open Software Fundation). Lo bueno
fue que su versión tenía como objetivo cumplir con todas los estándares del
IEEE, además de un sistema de ventanas (el X11), una interfaz amigable para los
usuarios (MOTIF) y las definiciones para cómputo distribuido (DCE) y
administración distribuida (DME). La idea de ofrecer una interfaz amigable en
UNIX no fue original de OSF, ya en la versión 3.5 de SunOS de Sun Microsystems
se ofrecía una interfaz amigable y un conjunto de librerías para crear
aplicaciones con interfaz gráfica técnicamente eficiente y poderosa llamada
SunWindows o SunVIEW. Esta interfaz junto con sus librerías estaban
evolucionando desde la versión para máquinas aisladas hacia una versión en
red, donde las aplicaciones podían estarse ejecutando en un nodo de la red y
los resultados gráficos verlos en otro nodo de la red, pero Sun tardó tanto en
liberarlo que le dio tiempo al MIT de lanzar el X11 y ganarle en popularidad.
AT&T formó, junto con Sun Microsystems y otras compañias UNIX
International y su versión de UNIX, provocando así que ahora se manejen esas
dos corrientes principales en UNIX.
Filosofía de UNIX
Las ideas principales de UNIX fueron derivadas del proyecto MULTICS (Multiplexed
Information and Computing Service) del MIT y de General Electric. Estas ideas
son:
- Todo se maneja como cadena de bytes: Los dispositivos periféricos, los
archivos y los comandos pueden verse como secuencias de bytes o como entes
que las producen. Por ejemplo, para usar una terminal en UNIX se hace a través
de un archivo (generalmente en el directorio /dev y con nombre ttyX).
- Manejo de tres descriptores estándares: Todo comando posee tres
descriptores por omisión llamados 'stdin', 'stdout' y 'stderr', los cuales
son los lugares de donde se leen los datos de trabajo, donde se envían los
resultados y en donde se envían los errores, respectivamente. El 'stdin' es
el teclado, el 'stdout' y el 'stderr' son la pantalla por omisión
(default).
- Capacidades de 'entubar' y 'redireccionar': El 'stdin', 'stdout' y el
'stderr' pueden usarse para cambiar el lugar de donde se leen los datos,
donde se envían los resultados y donde se envían los errores,
respectivamente. A nivel comandos, el símbolo de 'mayor que' (>) sirve
para enviar los resultados de un comando a un archivo. Por ejemplo, en UNIX
el comando 'ls' lista los archivos del directorio actual (es lo mismo que
'dir' en DOS). Si en vez de ver los nombres de archivos en la pantalla se
quieren guardar en el archivo 'listado', el redireccionamiento es útil y el
comando para hacer la tarea anterior es 'ls > listado'. Si lo que se
desea es enviar a imprimir esos nombres, el 'entubamiento' es útil y el
comando sería 'ls | lpr', donde el símbolo "|" ( pipe) es el
entubamiento y 'lpr' es el comando para imprimir en UNIX BSD.
- Crear sistemas grandes a partir de módulos: Cada instrucción en UNIX está
diseñada para poderse usar con 'pipes' o 'redireccionamiento', de manera
que se pueden crear sistemas complejos a través del uso de comandos simples
y elegantes. Un ejemplo sencillo de esto es el siguiente. Suponga que se
tienen cuatro comandos separados A,B,C y D cuyas funcionalidades son:
- A: lee matrices checando tipos de datos y formato.
- B: recibe matrices, las invierte y arroja el resultado en forma matricial.
- C: recibe una matriz y le pone encabezados 'bonitos'
- D: manda a la impresora una matriz cuidando el salto de página, etc.
Como se ve, cada módulo hace una actividad específica, si lo que se quiere
es un pequeño sistema que lea un sistema de ecuaciones y como resultado se
tenga un listado 'bonito', simplemente se usa el entubamiento para leer con el módulo
A la matriz, que su resultado lo reciba el B para obtener la solución, luego
esa solución la reciba el módulo C para que le ponga los encabezados 'bonitos'
y finalmente eso lo tome el módulo D y lo imprima, el comando completo sería '
A | B | C | D '. ø Fácil no ?
Sistema de Archivos en UNIX
El sistema de archivos de UNIX, desde el punto de vista del usuario, tiene una
organización jerárquica o de árbol invertido que parte de una raíz conocida
como "/" (diagonal). Es una diagonal al revés que la usada en DOS.
Internamente se usa un sistema de direccionamiento de archivos de varios
niveles, cuya estructura más primitiva se le llama 'information node' (i-node)
cuya explicación va más allá de este trabajo. El sistema de archivos de UNIX
ofreceun poderoso conjunto de comandos y llamadas al sistema. En la tabla 8.2 se
muestran los comandos más útiles para el manejo de archivos en UNIX vs. VMS.
Comando en UNIX Comando en VMS Utilidad
rm delete borra archivos
cpb copy copia archivos
mv rename renombra archivos
ls dir lista directorio
mkdir create/directory crea un directorio
rmdir delete borra directorio
ln - crea una 'liga simbolica'
chmod set protection maneja los permisos
chown set uic cambia de dueño
Tabla 8.2 Manejo de Archivos en UNIX y VMS
La protección de archivos en UNIX se maneja por medio de una cadena de permisos
de nueve caracteres. Los nueve caracteres se dividen en tres grupos de tres
caracteres cada uno.
RWX RWX RWX
1 2 3
El primer grupo (1) especifica los permisos del dueño del archivo. El
segundo grupo especifica los permisos para aquellos usuarios que pertenecen al
mismo grupo de trabajo que el dueño y finalmente el tercer grupo indica los
permisos para el resto del mundo. En cada grupo de tres caracteres pueden
aparecer las letras RWX en ese orden indicando permiso de leer (READ), escribir
(WRITE) y ejecutar (EXECUTE). Por ejemplo, la cadena completa RWXR-XR-- indica
que el dueño tiene los tres permisos (READ,,WRITE,EXECUTE), los miembros de su
grupo de trabajo tienen permisos de leer y ejecutar (READ,EXECUTE) y el resto
del mundo sólo tienen permiso de leer (READ). Las llamadas al sistema más útiles
en UNIX son 'open', 'close' e 'ioctl'. Sirven para abrir, cerrar archivos; y
establecer las características de trabajo. Por ejemplo, ya que en UNIX las
terminales se accesan a través de archivos especiales, el 'ioctl' (input output
control) sirve para establecer la velocidad, paridad, etc; de la terminal.
El núcleo de UNIX
El núcleo de UNIX (kernel) se clasifica como de tipo monolítico, pero en él
se pueden encontrar dos partes principales [Tan92]:
el núcleo dependiente de la máquina y el núcleo independiente. El núcleo
dependiente se encarga de las interrupciones, los manejadores de dispositivos de
bajo nivel (lower half) y parte del manejo de la memoria. El núcleo
independiente es igual en todas las plataformas e incluye el manejo de llamadas
del sistema, la planificación de procesos, el entubamiento, el manejo de
sentilde;ales, la paginación e intercambio, el manejo de discos y del sistema
de archivos.
Los procesos en UNIX
El manejo de procesos en UNIX es por prioridad y round robin. En algunas
versiones se maneja también un ajuste dinámico de la prioridad de acuerdo al
tiempo que los procesos han esperado y al tiempo que ya han usado el CPU. El
sistema provee facilidades para crear 'pipes' entre procesos, contabilizar el
uso de CPU por proceso y una pila común para todos los procesos cuando
necesitan estarse ejecutando en modo privilegiado (cuando hicieron una llamada
al sistema). UNIX permite que un proceso haga una copia de sí mismo por medio
de la llamada 'fork', lo cual es muy útil cuando se realizan trabajos paralelos
o concurrentes; también se proveen facilidades para el envío de mensajes entre
procesos. Recientemente Sun Microsystems, AT&T, IBM, Hewlett Packard y otros
fabricantes de computadoras llegaron a un acuerdo para usar un paquete llamado
ToolTalk para crear aplicaciones que usen un mismo método de intercambio de
mensajes.
El manejo de memoria en UNIX
Los primeros sistema con UNIX nacieron en máquinas cuyo espacio de direcciones
era muy pequeño (por ejemplo 64 kilobytes) y tenían un manejo de memoria real
algo complejo. Actualmente todos los sistemas UNIX utilizan el manejo de memoria
virtual siendo el esquema más usado la paginación por demanda y combinación
de segmentos paginados, en ambos casos con páginas de tamaño fijo. En todos
los sistemas UNIX se usa una partición de disco duro para el área de
intercambio. Esa área se reserva al tiempo de instalación del sistema
operativo. Una regla muy difundida entre administradores de sistemas es asignar
una partición de disco duro que sea al menos el doble de la cantidad de memoria
real de la computadora. Con esta regla se permite que se puedan intercambiar
flexiblemente todos los procesos que estén en memoria RAM en un momento dado
por otros que estén en el disco. Todos los procesos que forman parte del kernel
no pueden ser intercambiados a disco. Algunos sistemas operativos (como SunOS)
permiten incrementar el espacio de intercambio incluso mientras el sistema está
en uso (en el caso de SunOS con el comando 'swapon'). También es muy importante
que al momento de decidirse por un sistema operativo se pregunte por esa
facilidad de incrementar el espacio de intercambio, así como la facilidad de añadir
módulos de memoria RAM a la computadora sin necesidad de reconfigurar el núcleo.
El manejo de entrada/salida en UNIX
Derivado de la filosofía de manejar todo como flujo de bytes, los dispositivos
son considerados como archivos que se accesan mediante descriptores de archivos
cuyos nombres se encuentran generalmente en el directorio '/dev'. Cada proceso
en UNIX mantiene una tabla de archivos abiertos (donde el archivo puede ser
cualquier dispositivo de entrada/salida). Esa tabla tiene entradas que
corresponden a los descriptores, los cuales son números enteros [Deitel93]
obtenidos por medio de la llamada a la llamada del sistema 'open'. En la tabla
8.3 se muestran las llamadas más usuales para realizar entrada/salida.
Llamada Función
open Obtener un descriptor entero.
close Terminar las operaciones sobre el archivo
lseek Posicionar la entrada/salida.
read,write Leer o escribir al archivo (dispositivo)
ioctl Establecer el modo de trabajo del dispositivo
Tabla 8.3 Llamadas al sistema de entrada/salida
En UNIX es posible ejecutar llamadas al sistema de entrada/salida de dos formas:
síncrona y asíncrona. El modo síncrono es el modo normal de trabajo y
consiste en hacer peticiones de lectura o escritura que hacen que el originador
tenga que esperar a que el sistema le responda, es decir, que le de los datos
deseados. A veces se requiere que un mismo proceso sea capaz de supervisar el
estado de varios dispositivos y tomar ciertas decisiones dependiendo de si
existen datos o no. En este caso se requiere una forma de trabajo asíncrona.
Para este tipo de situaciones existen las llamadas a las rutinas 'select' y
'poll' que permiten saber el estado de un conjunto de descriptores.
9. Caso De Estudio: VMS
El sistema operativo VMS (Virtual Memory System) es uno de los más robustos
en el mercado, aunque es propietario de la compañia Digital Equipment
Corporation. Actualmente con su versión OpenVMS 5.x existe para los
procesadores de las máquinas VAX (CISC) y con el Alpha-chip (RISC). Ofrece un
amplio conjunto de comandos a través de su intérprete Digital Command Language
(DCL), utilidades de red (DECnet), formación de 'clusters' de computadoras para
compartir recursos, correo electrónico y otras facilidades. Es un sistema
operativo multiusuario/multitarea monolítico.
El manejo de archivos en VMS
El sistema de archivos de VMS es jerárquico aunque la descripción de sus
senderos tiene una sintaxis propia. En la figura 9.1 se muestra un ejemplo.
Los archivos en VMS se referencían con la sintaxis 'nombre.tipo;versión',
donde 'nombre' es una cadena de caracteres alfanuméricos, 'tipo' es la extensión
del archivo y se usa generalmente para describir a qué aplicación pertenece
('pas'=pascal, 'for' fortran, etc.) y 'versión' es un número entero que el
sistema se encarga de asignar de acuerdo al número de veces que el archivo ha
sido modificado. Por ejemplo, si se ha editado tres veces el archivo 'lee.pas',
seguro que existirán las versiones 'lee.pas;1', 'lee.pas;2' y 'lee.pas;3'. De
esta forma el usuario obtiene automáticamente una 'historia' de sus archivos.
La protección de los archivos se realiza mediante listas de control de acceso
(Access Control Lists). Se pueden establecer protecciones hacia el dueño del
archivo, hacia los usuarios privilegiados (system), hacia los usuarios que
pertenecen al mismo grupo de trabajo que el dueño y hacia el resto del mundo.
Para cada uno de los anteriores usuarios se manejan cuatro permisos: lectura,
escritura, ejecución y borrado. Por ejemplo, el siguiente comando:
$ set protection=(S:rwed,O:rwed,G:d:W:e) lee.pas
establece que el archivo 'lee.pas' dará todos los permisos al sistema (S:rwed)
y al dueño (O:rwed), mientras que a los miembros del grupo de trabajo le da
permiso de borrar (G:d) y al resto del mundo permiso de ejecución (W:e).
[VMS89].
Una lista de los comandos sobre archivos más útiles en VMS se mostró en la
tabla 8.2, que son bastante mnemónicos en
contraste con los comandos crípticos de UNIX.
En VMS, a través de su 'Record Management System' (RMS) se obtienen las
facilidades para la manipulación de archivos tanto locales como en red. En el
RMS, se provenn facilidades tales como: múltiples modos de acceso a archivos
para lograr accesarlos en forma concurrente y permitiendo su consistencia e
integridad, establecimiento de candados automáticos al momento de apertura para
evitar actualizaciones erróneas y optimización interna en las operaciones de
entrada/salida al accesar los archivos. En el caso de que los archivos no son
locales, sino remotos, se utiliza internamente el protocolo llamado 'Data Access
Protocol' (DAP).
Manejo de procesos en VMS
Soporta muchos ambientes de usuario tales como : Tiempo crítico, desarrollo de
programas interactivos, batch, ya sea de manera concurrente, independiente o
combinado.
El calendarizador VAX/VMS realiza calendarización de procesos normales y de
tiempo real, basados en la prioridad de los procesos ejecutables en el Balance
Set.Un proceso normal es referido a como un proceso de tiempo compartido o
proceso background mientras que los procesos en tiempo real se refieren a los de
tiempo crítico.
En VMS los procesos se manejan por prioridades y de manera apropiativa. Los
procesos se clasifican de la prioridad 1 a la 31, siendo las primeras quince
prioridades para procesos normales y trabajos en lote, y de la 16 a la 31 para
procesos privilegiados y del sistema. Las prioridades no permanecen fijas todo
el tiempo sino que se varían de acuerdo a algunos eventos del sistema. Las
prioridades de los procesos normales pueden sufrir variaciones de hasta 6
puntos, por ejemplo, cuando un proceso está esperando un dispositivo y éste
fue liberado. Un proceso no suelta la unidad central de procesamiento hasta que
exista un proceso con mayor prioridad.
El proceso residente de mayor prioridad a ser ejecutado siempre se selecciona
para su ejecución.Los procesos en tiempo crítico son establecidos por el
usuario y no pueden ser alterados por el sistema. La prioridad de los procesos
normales puede ser alterada por el sistema para optimizar overlap de computación
y otras actividades I/O.
Un aspecto importante del planificador de procesos en VMS es la existencia de
proceso 'monitor' o 'supervisor', el cual se ejecuta periódicamente para
actualizar algunas variables de desempeño y para re-calendarizar los procesos
en ejecución.
Existen versiones de VMS que corren en varios procesadores, y se ofrece
librerías para crear programas con múltiples 'threads'. En específico se
proveen las interfaces 'cma', 'pthread' y 'pthread-exception-returning'. Todas
estas librerías se conocen como DECthreads e incluyen librerías tales como semáforos
y colas atómicas para la comunicación y sincronización entre threads. El uso
de threads sirve para enviar porciones de un programa a ejecutar en diferentes
procesadores aprovechando así el multiproceso.
Servicios del Sistema para el Control de Procesos
El servicio de creado de sistema permite a un proceso crear otro. El proceso
creado puede ser un subproceso o un proceso completamente independiente. (se
necesitan privilegios para hacer esto).
Esto es que le permite a un proceso suspenderse a sí mismo o a otro (también
necesita tener privilegios).
Permite a un proceso reanudar a otro si es que este tiene privilegios para
hacerlo.
Permite que se borre el proceso mismo o a otro si es que es un subproceso, o
si no tiene que tener privilegios de borrado.
Permite que el proceso mismo se ponga prioridad o a otros, para el
calendarizador.
Permite que el proceso escoja de dos modos: el modo por default es cuando un
proceso requiere un recurso y está ocupado y espera a que esté desocupado, y
el otro modo es cuando está ocupado el recurso, el proceso no espera y notifica
al usuario que el recurso no se encuentra disponible en ese momento en lugar de
esperar.
Es cuando un proceso se hace inactivo pero está presente en el sistema. Para
que el proceso continue necesita de un evento para despertar.
Esto activa a los procesos que estan hibernando.
Es cuando se aborta un proceso.
Este puede |