Beruflich Dokumente
Kultur Dokumente
11/02/09
Índice general
Índice general 1
I Introducción 4
1 Introducción 5
2 Estructuras de SO 6
II Gestión de Procesos 7
3 Procesos 8
3.1. Concepto de proceso . . . . . . . . . . . . . . . . . . . . . . . . 8
3.2. Planicación de Procesos . . . . . . . . . . . . . . . . . . . . . 9
3.3. Operaciones sobre los Procesos . . . . . . . . . . . . . . . . . . 11
3.4. Comunicación interprocesos . . . . . . . . . . . . . . . . . . . . 13
3.5. Ejemplos de sistemas IPC . . . . . . . . . . . . . . . . . . . . . 16
3.6. Comunicación en SO's cliente-servidor . . . . . . . . . . . . . . 18
4 Hebras (Threads) 21
4.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.2. Modelos multihebras . . . . . . . . . . . . . . . . . . . . . . . . 22
4.3. Bibliotecas de Threads . . . . . . . . . . . . . . . . . . . . . . . 22
4.4. Consideraciones sobre Threads . . . . . . . . . . . . . . . . . . 24
4.5. Ejemplo de SO : Linux . . . . . . . . . . . . . . . . . . . . . . . 26
1
ÍNDICE GENERAL 2
5 Planicación de CPU 28
5.1. Conceptos básicos . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.2. Criterios de Planicación . . . . . . . . . . . . . . . . . . . . . 29
5.3. Algoritmos de Planicación . . . . . . . . . . . . . . . . . . . . 30
5.4. Planicación de Sistemas Multiprocesador . . . . . . . . . . . . 33
5.5. Planicación de threads . . . . . . . . . . . . . . . . . . . . . . 34
5.6. Ejemplo de SO : Planicación en Linux . . . . . . . . . . . . . 35
5.7. Evaluación de Algoritmos . . . . . . . . . . . . . . . . . . . . . 35
6 Sincronización de Procesos 38
6.1. Fundamentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6.2. Sección crítica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
6.3. Hardware de sincronización . . . . . . . . . . . . . . . . . . . . 40
6.4. Semáforos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
6.5. Problemas clásicos de sincronización . . . . . . . . . . . . . . . 44
6.6. Monitores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7 Interbloqueos 51
7.1. Modelo de sistema . . . . . . . . . . . . . . . . . . . . . . . . . 51
7.2. Caracterización de los interbloqueos . . . . . . . . . . . . . . . 51
7.3. Metodos para tratar los interbloqueos . . . . . . . . . . . . . . 52
7.4. Prevención de interbloqueos . . . . . . . . . . . . . . . . . . . . 53
7.5. Evasión de Interbloqueos . . . . . . . . . . . . . . . . . . . . . . 54
7.6. Detección de Interbloqueos . . . . . . . . . . . . . . . . . . . . 56
7.7. Recuperación de un interbloqueo . . . . . . . . . . . . . . . . . 58
9 Memoria Virtual 72
9.1. Fundamentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
9.2. Paginación bajo demanda . . . . . . . . . . . . . . . . . . . . . 73
9.3. Copia durante la Escritura ( Copy-on-Write , CoW ) . . . . . . 75
9.4. Sustitución de Páginas . . . . . . . . . . . . . . . . . . . . . . . 76
9.5. Asignación de Marcos . . . . . . . . . . . . . . . . . . . . . . . 81
9.6. Sobrepaginación . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.7. Archivos Mapeados en Memoria . . . . . . . . . . . . . . . . . . 83
9.8. Asignación de la Memoria del Kernel . . . . . . . . . . . . . . . 84
9.9. Otras consideraciones . . . . . . . . . . . . . . . . . . . . . . . 85
¡Advertencia!
1 Las notas inutiles son defectos mentales del propio autor , disfrutenlos o sepan per-
donarlo. Si , esta es tambien una nota inutil.
3
Parte I
Introducción
4
Capítulo 1
Introducción
5
Capítulo 2
Estructuras de SO
6
Parte II
Gestión de Procesos
7
Capítulo 3
Procesos
3.1.1. El Proceso
Un proceso es un programa en ejecución. El proceso incluye :
La pila y el cumulo de memoria crecen uno hacia abajo y el otro hacia arriba
, consumiendo la misma sección de espacio adicional del memoria1 .
Un programa es una entidad pasiva , mientras que un proceso es una entidad
activa. Un programa se convierte en proceso cuando se carga en memoria.
Los procesos generados por otros procesos ( child-process) son considerados
secuencias separadas de sus predecesores. Esto sucede habitualmente para la
mayoría de los procesos.
8
CAPÍTULO 3. PROCESOS 9
3.2.2. Planicadores
El proceso de selección de procesos se realiza mediante planicadores. El
planicador a largo plazo seleccionan de la cola principal y los cargan en
memoria. El planicador de corto plazo o planicador de CPU selec-
ciona de entre los procesos listos para ejecutarse y lo asigna a un procesador.
La principal diferencia entre ellos es la frecuencia con la que se ejecutan.
Por lo tanto el de corto plazo debe ser muy rápido mientras el de largo plazo
puede durar mas tiempo.
El de largo plazo controla la cantidad de procesos en memoria ( multipro-
gramación) mientras el de corto plazo controla el tiempo que cada proceso
utiliza la cpu ( shared-time).
Existen procesos que son limitados por E/S ( I/O Bound) y otros por CPU
(CPU Bound), el de largo plazo es el encargado de seleccionar una adecuada
mezcla de ellos.
En algunos sistemas el de largo plazo no es necesario, sino que todo el
proceso es realizado por el de corto plazo. Mientras que en algunos sistemas
shared-time pueden introducir uno de medio plazo 7 . Este se encargaría de
eliminar ciertos procesos de memoria y reducir el grado de multiprogramación
, considerado ventajoso en ciertos casos. Luego estos se volverían a cargar , a
esto se le llama esquema de intercambio.
útil. Este tiempo depende mucho del soporte de hardware. También a mayor
complejidad tenga el SO , mayor sera el tiempo que tardara el context-switch.
int main ( )
{
pid . t pid ;
/* b i f u r c a un p r o c e s o h i j o */
pid = fork () ;
if ( pid < 0 ) /* e r r o r */
{
}
else i f ( p i d == 0 ) /* p r o c e s o h i j o */
{
}
else /* p r o c e s o p a d r e */
{
/* E s p e r a a que s u h i j o t e r m i n e */
w a i t ( NULL ) ;
p r i n t f (" Hijo Completado " ) ;
El padre abandona el SO y este no permite que los hijos estén vivos sin
su padre. Esto es conocido como terminación en cascada.
10 Nota
del autor :Esta ultima como vimos antes puede ser obtenida mediante el valor
devuelto por fork , cada vez que creamos un hijo.
CAPÍTULO 3. PROCESOS 13
typedef struct {
. . .
} item ;
int in = 0;
int out = 0;
3.4.2. Mensajería
Proporciona un mecanismo que permite a los procesos comunicarse y sin-
cronizar sus acciones , siendo especialmente útil en entornos distribuidos. Los
mensajes enviados por un proceso pueden tener tamaño jo o variable , siendo
la primera es fácil de implementar a nivel de sistema pero complica la tarea de
programación mientras la segunda es la inversa , por lo cual es un elemento a
tener en cuenta en el diseño de SO's.
SI los procesos P y Q desean comunicarse debe existir un enlace de comu-
nicaciones ( conexión) entre ellos , el cual puede implementarse de la siguiente
manera :
Comunicación directa o indirecta.
Comunicación sincrónica o asincrónica.
Almacenamiento en Buer explicito o automatico.
3.4.2.1. Nombrado
Comunicación directa Cada proceso que desea comunicarse debe nombrar
de forma explicita al receptor o transmisor de la comunicación. Esto se
realiza de las siguiente forma :
Para saber que proceso recibirá que mensaje dependerá de cual de los siguientes
metodos se utilice :
Eliminar un buzón.
CAPÍTULO 3. PROCESOS 16
3.4.2.2. Sincronización
Veamos el envió y recepción desde el punto de vista de la sincronía :
Envió sincrónico : El proceso que envió se bloquea hasta que no se
reciba su mensaje.
Envió asincrónica : El proceso continua enviando.
Recepción sincrónica : Se bloquea hasta que hay un mensaje disponible.
Recepción asíncrona : Extrae continuamente mensajes validos o , en
su defecto , nulos.
Estos metodos son combinables entre si.
return 0;
3.5.2. Mach
SO basado en mensajes , El kernel de Mach permite la creación y destrucción
de múltiples tareas , similares a los procesos , pero tienen múltiples threads
de control. Todos los tipos de comunicación se realizan mediante mensajes ,
incluidas las SYSCall's , utilizando buzones denominados puertos. Al crearse
una tarea se crean 2 buzones para ella :
solo se garantiza solo entre los del mismo usuario, puede haber intercalados
mensajes de otros usuarios.
Los mensajes contan de una cabecera de longitud ja , seguida de unos datos
de longitud variable. La cabecera indica la longitud del mensaje e incluye dos
nombres de buzón, uno de ellos es el del buzón al que se esta enviando. La
hebra emisora espera una respuesta , la hebra receptora se le pasa el nombre
del buzón del emisor ( el segundo nombre ); la hebra emisora utiliza este como
dirección de retorno.
La parte variable de un mensaje es una lista de elementos de datos con tipo.
El tipo de objeto es importan ya que e puede enviarse objetos denidos por el
sistema tales como derechos de propiedad o acceso de recepción , etc.
Si el buzón esta lleno , la hebra emisora tiene cuatro opciones :
Esperar un tiempo.
entrantes que tengan una marca temporal20 que ya este en el historial son
ignorados , permitiendo así que el cliente envíe su mensaje cuantas veces desee
y este seguro de que este solo se ejecuto 1 sola vez.
Estos mensajes de conrmación son comunes en redes. El cliente reenviara
la llamada hasta recibir uno de estos mensajes.
Para poder lograr la comunicación , el esquema RPC requiere conocer los
puertos del cliente y del servidor. Para lograr esto existen 2 metodos :
Que las direcciones de puerto de los servicios sean jas tanto para el
cliente como para el servidor , por lo tanto quedaran denidas desde
tiempo de compilación.
20 En la Sección 18.1 del libro se estudia la generación de marcas temporales , tema que
no se presentada en esta sintesis.
21 Ver Fig 3.21 , tiene un ejemplo muy ilustrativo del proceso de llamada de la RPC
Capítulo 4
Hebras (Threads)
4.1. Introducción
Un Thread es una unidad básica de utilización de la CPU compuesta por un
thread-ID , program-counter , un conjunto de registros y una pila. Comparte
con otros threads del mismo proceso la sección de código , de datos y otros
recursos que tenga el proceso en posesión1 .
4.1.1. Ventajas
Podemos agruparlas en 4 categorías :
21
CAPÍTULO 4. HEBRAS (THREADS) 22
4.3.1. Pthreads
Se basa en el estándar POSIX (IEEE 1003.1c). Se trata de una especicación
para el comportamiento de los threads , no una implementación, lo que da
libertad a los diseñadores.
Veamos los elementos de pthreads presentes en el Listing 4.1 :
pthread_t t i d ; / * i d e n t i f i c a d o r * /
pthread_attr_t a t t r ; / * a t r i b u t o s * /
i f ( a r g c != 2 )
{
f p r i n t f ( s t d e r r , " uso : a . out <v a l o r e n t e r o >\n" ) ;
return − 1;
}
i f ( a t o i ( argv [ 1 ] ) < 0 )
{
f p r i n t f ( s t d e r r , " %d debe s e r >= 0 " , a t o i ( argv [ 1 ] ) ) ;
return − 1;
}
/* c o n f i g u r a r a t r i b u t o s */
p t h r e a d _ a t t r _ i n i t ( &a t t r ) ;
/* c r e a r e l t h r e a d */
p t h r e a d _ c r e a t e ( &t i d , &a t t r , r u n n e r , argv [ 1 ] ) ;
/* e s p e r a r q u e e l t h r e a d t e r m i n a */
p t h r e a d _ j o i n ( t i d , NULL ) ;
return 0 ;
}
CAPÍTULO 4. HEBRAS (THREADS) 24
int i , upper ;
sum = 0 ;
upper = a t o i ( param ) ;
pthread_exit (0) ;
4.4.2. Cancelación
La cancelación termina un thread antes de que se complete. Es muy común
en cualquier programa que use threads. Existen 2 formas de cancelación :
CAPÍTULO 4. HEBRAS (THREADS) 25
indicador signicado
CLONE_FS Se comparte información de FileSystem.
CLONE_VM Se comparte información de memoria.
CLONE_SIGHAND Se comparte los descriptores de señales.
CLONE_FILES Se comparte el conjunto de archivos.
11 Nota del Autor : De esta manera de querer agregarse nuevos elementos a la task_struct
, luego solo habría que agregar un nuevo indicador para ese elemento para así decidir si se
comparte o no .
Capítulo 5
Planicación de CPU
28
CAPÍTULO 5. PLANIFICACIÓN DE CPU 29
5.1.4. Despachador
Es un modulo que proporciona el control de la CPU a los procesos selec-
cionados. Esta función implica :
Cambio de contexto.
Cambio al modo usuario
Salto a la posición correcta dentro del programa de usuario para reiniciar
dicho programa
Este debe ser muy rápido , ya que se invoca en cada conmutación de proceso.
Este tiempo se conoce como latencia de despacho.
El numero de colas.
Cuando la activa esta vacía , vuelve a ser llenada con todos los procesos de la
caducada , la cual es vaciada.
Linux implementa la planicación en tiempo real como se dene en POSIX.1b.
A las de tiempo real se les asigna prioridades estáticas , las restantes son dinámi-
cas las cuales pueden variar ±5 . La interactividad se determina si estos 5 se
suman o restan. El recalculo de la prioridades se produce cuando se intercam-
bian las matrices.
n = λW
5.7.3. Simulaciones
Para una evaluación precisa podemos usar simulaciones. Estas se puede
realizar en maquinas virtuales preparadas para modicar su comportamiento en
cuanto a recursos y otros aspectos , los cuales pueden generarse aleatoriamente
, determinan el rango posible. Este rango son distribuciones matemáticas o
empíricas. Los resultados de las medidas denen la distribución y luego son
usados para controlar la simulación.
Sin embargo el uso de distribuciones puede ser impreciso ya que estas no
tienen en cuenta el orden de los elementos . Para esto podemos usar cintas de
traza. Para crear una cinta de traza se monitorea el sistema real y se registra una
CAPÍTULO 5. PLANIFICACIÓN DE CPU 37
5.7.4. Implementación
La única forma de evaluar un algoritmo es codicandolo , incluyendolo en
un SO y ver como funciona. La principal dicultad es su alto coste , no solo
en codicación y modicaciones , sino también en que los usuarios tengan que
soportar el cambio continuo del sistema.
Otro problema es que el entorno en donde se ejecuta esta sujeto a cam-
bios. Los algoritmos mas exibles son los que pueden ser modicados por los
administradores de sistemas o los usuarios, de modo que puedan ser ajustados
a sus necesidades. Unix permite que el administrador ajuste parametros de
planicación para cada conguración concreta del sistema. Solaris proporciona
el comando dispadmin para que el administrador modique los parametros de
las clases de planicación.
Otro método consiste en utilizar aplicaciones que permitan modicar la
prioridad de los procesos y threads. La desventaja es que no permite mejorar
el rendimiento en otras situaciones mas generales.
Capítulo 6
Sincronización de Procesos
6.1. Fundamentos
El acceso concurrente a datos compartidos puede producir incoherencias de
datos. Veamos el caso de productor-consumidor que presentamos en la Sección
3.4.1 utilizando el mismo Buer limitado , con al diferencia que en vez de tener
máximo BUFFER_SIZE −1 , tendremos una variable entera counter inicializada
con el valor 0. Veamos los códigos del productor y del consumidor :
/* produce un e l e m e n t o en nextProduced */
while ( counter == BUFFER_SIZE )
; /* s k i p */
buffer [ in ] = nextProduced ;
in = ( in + 1) % BUFFER_SIZE ;
c o u n t e r ++;
; /* s k i p */
nextConsu me d = b u f f e r [ out ] ;
counter −−;
}
38
CAPÍTULO 6. SINCRONIZACIÓN DE PROCESOS 39
intacto , sino que podrían tener un valor entero en [counter − 1, counter + 1]1 .
Esto se ve claramente cuando chequeamos el lenguaje maquina2 de counter++
y counter−− :
registro1 = registro1 + 1
counter = registro1
registro2 = registro2 − 1
counter = registro2
Vemos claramente que podría producirse una intercalación entre las lineas
de los 2 códigos , por ejemplo ejecutamos las 2 primeras de cada código y luego
ejecutamos la ultima de cada uno , generando que el valor de counter quede
incrementado o decrementado , pero nunca intacto.
Esta situación se denomina condición de carrera ( race condition ) ,
necesitamos asegurar que solo un proceso a la vez pueda acceder a los recursos
compartidos.
do {
/ * Sección de entrada */
. . . . Sección critica
/ * Sección de salida */
. . . . Sección restante
} while ( true ) ;
Existen varias estructuras del kernel que están sujetas a posibles race-conditions.
Para esto existen 2 metodos generales para gestionar las secciones criticas en
los SO's4 :
boolean rv = * target ;
* target = TRUE ;
return rv ;
/* s e c c i ó n de e n t r a d a */
while ( TestAndSet (& l o c k ) )
; /* s k i p */
/* S e c c i ó n c r í t i c a */
. . . . . . . .
/* S e c c i ó n de s a l i d a */
lock = FALSE ;
// S e c c i ó n r e s t a n t e
} while (TRUE) ;
Otra función seria Swap, en el Listing 6.7 , la cual es para realizar el inter-
cambió entre 2 valores.
boolean temp = *a ;
*a = *b ;
*b = tempo ;
boolean lock ;
waiting [ i ] = TRUE ;
key = TRUE ;
/* S e c c i ó n de Entrada */
while ( waiting [ i ] && key )
key = T e s t A n d S e t (& l o c k ) ;
waiting [ i ] = FALSE ;
/* S e c c i ó n c r í t i c a */
j = ( i +1) %n;
j = ( j +1) %n;
/* S e c c i ó n de s a l i d a */
if ( j == i )
lock = FALSE ;
else
wainting [ j ] = FALSE ;
// S e c c i ó n r e s t a n t e
} while (TRUE) ;
6.4. Semáforos
Un semaforo es una variable entera a la que , salvo la inicialización , solo
se acceder mediante las siguientes operaciones :
6 Esto se produce únicamente en la sección de salida , donde se libera el cerrojo.
7 Esto ultimo se concede cuando el proceso actual esta en la sección de salida.
CAPÍTULO 6. SINCRONIZACIÓN DE PROCESOS 43
wait() signal ()
wait (S)
{ signal (S)
while ( S <= 0) {
; S++;
S −−; }
6.4.1. Utilización
Los So's diferencian entre 2 tipos de semaforos :
También estos pueden ser utilizados para resolver diversos problemas de sin-
cronización.
6.4.2. Implementación
La desventaja de la denición dada es que requiere espera activa la cual
es provocada por el ciclo while de wait() , desperdiciando ciclos de CPU. Es-
tos semaforos se denominan cerrojo mediante bucle sin n ( spinlock ).
La ventaja es que no requiere ningún context-switch cuando los procesos es-
peran para recibir el cerrojo , lo cual hace a este método muy recomendable
si las esperas por el cerrojo son cortas. Se emplean a menudo para entornos
multiprocesador. Para salvar la espera activa , redeniremos las operaciones :
wait() signal ()
Esto permite que los procesos se bloqueen a si mismos usando wait() , para
pasar así a la cola de espera y dejando libre la CPU. Estos son luego reiniciados
8 Ver Fig6.9
9 Esto esalgo que el programador esta obligado a cumplir para asegurar el correcto
funcionamiento del mutex.
CAPÍTULO 6. SINCRONIZACIÓN DE PROCESOS 44
por un proceso que sale de su sección critica mediante signal () , poniendo así
en la cola de preparados10 .
La lista de procesos en espera puede implementarse mediante un campo
de enlace en cada PCB. Cada semaforo contiene un entero y un puntero a la
lista de PCB correspondiente. Para garantizar la espera limitada podríamos
utilizar una cola FIFO, pero podría usarse cualquier otro ya que el semaforo
no depende de que estrategia de gestión se elija.
Debemos desactivar las interrupciones de los procesadores para poder eje-
cutar wait() y signal () . Este método es perfectamente aplicable en entornos
monoprocesador , pero en los multiprocesador hemos visto que no. Para este
ultimo caso conviene usar los semaforos spinlock para asegurar la ejecución
atomica.
No hemos eliminado por completo la espera activa , sino que la hemos
trasladado a las secciones criticas de las operaciones wait() y signal () , las
cuales son cortas y rara vez se tiene que esperar. En los programas de aplicación
esto no es así ya que las esperas pueden ser largas , la espera activa resulta
extremadamente ineciente.
P0 P1
wait (S) ; w a i t (Q) ;
. .
. .
. .
. . . wait ( f u l l ) ;
// produce un e l e m e n t o n e x t p w a i t ( mutex ) ;
. . . . . .
w a i t ( empty ) ; // e l i m i n a b u f f e r a n e x t c
w a i t ( mutex ) ; . . .
. . . s i g n a l ( mutex ) ;
// a ñ a d i r n e x t p a l b u f e r s i g n a l ( empty ) ;
.. . . .
s i g n a l ( mutex ) ; // consume n e x t c
signal ( full ) ; ..
Escritor Lector
do
{
w a i t ( mutex ) ;
r e a d c o u n t ++;
do { if ( readcount == 1 )
w a i t ( wrt ) ;
w a i t ( wrt ) ;
signal ( mutex ) ;
. . .
. . .
// s e r e a l i z a l a e s c r i t u r a
// s e r e a l i z a l a l e c t u r a
..
. . .
s i g n a l ( wrt ) ;
} while (TRUE) ;
w a i t ( mutex ) ;
readcount −−;
if ( readcount == 0 )
s i g n a l ( wrt ) ;
s i g n a l ( mutex ) ;
} while (TRUE) ;
CAPÍTULO 6. SINCRONIZACIÓN DE PROCESOS 46
int readcount ;
6.6. Monitores
El uso incorrecto de semaforos puede dar lugar a errores de temporización
difíciles de detectar , los cuales por ejemplo aparecen en el productor-consumidor
( Sección 6.1 ).
En la solución para la sección critica con semaforos todos los procesos com-
parten un mutex , inicializado en 1. Se debe respetar la secuencia wait(mutex
) ;...; signal (mutex); , sino 2 procesos o mas podrían estar en su sección critica
al mismo tiempo. Para evitar esto se desarrollo una estructura de alto nivel
llamada monitor.
6.6.1. Utilización
Un tipo monitor tiene un conjunto de operaciones con exclusión mutua
dentro de este. También contiene declaraciones de variables cuyos valores de-
nen el estado de una instancia de dicho tipo , junto con los cuerpos de los
procedimientos y funciones que operan sobre estas15 . El monitor es un TAD
Opaco.
La estructura del monitor asegura que solo un proceso este activo a la
vez dentro del mismo16 . Si bien esto nos evita tener que encargarnos de la
sincronización en ciertos casos , en otros requrimos de un mecanismo especial ,
el cual es proporcionado por las estructura condition17 . Las únicas operaciones
aplicables a condition son wait(condition) y signal (condition).
Suponga ahora que , cuando un proceso invoca la operación signal (condition
) , hay un proceso Q en estado suspendido asociado con condition. Si se permite
donde el losofo i tiene que esperar cuando tiene hambre pero no puede con-
seguir los palitos.
La distribución de los palitos se controla mediante el monitor dp :
monitor dp
state [ i ] = HAMBRE;
test ( i ) ;
if ( state [ i ] != COMER )
s e l f [ i ] . wait ( ) ;
state [ i ] = PENSAR ;
test ( ( i +4) % 5) ;
test ( ( i +1) % 5) ;
( s t a t e [ ( ( i +1) % 5) ] != COMER) )
state [ i ] = COMER;
s e l f [ i ] . signal () ;
initialization code ( )
state [ i ] = PENSAR ;
if ( next_count > 0 )
s i g n a l ( next ) ;
else i f
{
s i g n a l ( mutex ) ;
w a i t ( x_sem ) ;
x_count −−;
n e x t _ c o u n t ++;
s i g n a l ( x_sem ) ;
wait ( next ) ;
next_count −−;
}
Interbloqueos
51
CAPÍTULO 7. INTERBLOQUEOS 52
1. Exigir que cada proceso solicite todos sus recursos ( y se le asignen) antes
de comenzar a ejecutarse.
1. La taza de utilización de los recursos puede ser baja , dado que ciertos
recursos estarán asignados pero no en uso.
Max : Una matriz n×m que indica la demanda máxima de cada proceso.
Allocation : Una matriz n × m que indica la cantidad de cada recurso
asignados a cada proceso.
Estas estructuras varían con el tiempo , tanto en tamaño como sus valores.
Para simplicar la lectura diremos que X, Y V Resources :: X ≤ Y ⇐⇒<
∀ i :: X[i] ≤ Y [i] >.
a) Finish [ i ] == false
b) N eedi ≤ W ork
Allocation [ i ] += Request [ i ] ;
Need [ i ] −= Request [ i ] ;
a) Finish [ i ] == false
b) Requesti ≤ W ork
16 Nota del Autor : Esto sucede por que el que tiene menor coste generalmente sigue siendo
el mismo y una forma de evitarlo es agregando esta nueva variable.
Parte III
Gestión de Memoria
60
Capítulo 8
Memoria Principal
8.1. Fundamentos
La memoria esta compuesta por una matriz de bytes , cada uno con su
propia dirección. La CPU extrae instrucciones de la memoria marcadas por el
program-counter, las cuales pueden provocar operaciones adicionales de carga
en memoria. La forma de proceder es extraer la instrucción de memoria , de-
codicarla y extraer los operandos adicionales. Ya procesada , se guardan los
resultados en memoria.
La memoria solo ve un ujo de sus direcciones, no sabe ni como se generan
ni para que se usaran.
61
CAPÍTULO 8. MEMORIA PRINCIPAL 62
La ventaja es que este resulta útil para gestionar programas grandes con
muchas rutinas de uso muy infrecuente.
Este mecanismo de carga dinámica no requiere ningún soporte especial por
parte del So. Es responsabilidad de los usuarios diseñar sus programas para
poder aprovechar este método.
8.2. Intercambio
Los procesos pueden ser intercambiados temporalmente, sacandolos de la
memoria y almacenandolos en un almacén de respaldo9 ( backing-store )
para luego continuar su ejecución.
Los proceso descargados se vuelven a colocar en el mismo espacio de memo-
ria que ocupaban anteriormente. Esta restricción esta dictada por el método de
reasignación de direcciones. Solo en se puede realizar la reasignación en tiempo
de ejecución con facilidad , por que las direcciones físicas se calculan en ese
momento.
El almacén de respaldo debe también poder albergar todas las imágenes de
memoria para todos los usuarios y debe proporcionar un acceso directo a estas
imágenes de memoria. En la cola de procesos preparados estarán no solo los
que estén en memoria principal , sino también los que estén en el almacén de
respaldo.
En los sistemas con intercambio el tiempo de context-switch es muy alto
ya que se debe almacenar una imagen de respaldo10 . Para conseguir un uso
9 Este generalmente es un disco rígido o símil.
10 La cual debe ser actualizada aveces luego de cada ejecución.
CAPÍTULO 8. MEMORIA PRINCIPAL 64
Que dichas operaciones trabajen con bueres del SO , los cuales luego
transferirán los datos al proceso cuando este sea restablecido.
Registro de reubicación 11
Registro de limite 12
La MMU convertirá la dirección lógica dinámicamente sumándole el valor con-
tenido en el registro de reubicación. Esta dirección es la que se envía al memo-
ria13 . Como todas las direcciones se comparan con estos registros , este sistema
nos permite proteger todo lo que este en memoria de posibles accesos ilegales.
Este mecanismo es útil para efectuar carga dinámica de SO de ciertas ruti-
nas poco utilizadas , las cuales serán denominadas código transitorio.
En este esquema el SO mantiene una tabla que indica que particiones es-
tán disponibles. Cuando llega un proceso , buscamos una partición lo su-
cientemente grande para este. Solo le asignamos la cantidad necesaria de esta
partición , dejando el resto en una partición aparte.
Este método genera varios agujeros menores , dispersos por toda la memoria
, los cuales pueden llegar a ser tan pequeños que no sirvan para los procesos en la
cola de entrada. Generalmente las particiones adyacentes son combinadas para
generar particiones mas grandes. Existen 3 formas para asignar las particiones :
Primer Ajuste : Se asigna el primera partición que sea lo suciente-
mente grande.
Mejor Ajuste : Se asigna la partición mas pequeña. Al menos que este
ordenada , debemos recorrer toda la lista. Esto hace que la partición
sobrante sea lo mas pequeña posible
Peor Ajuste : Se asigna el de mayor tamaño. Se requiere exploración
total de la lista , salvo que este ordenada. La ventaja de este método es
que deja particiones muchos mas grandes con la memoria sobrante.
8.3.3. Fragmentación
Existen 2 problemas en la asignación de memoria contigua :
Fragmentación Externa : A medida que asignamos y liberamos par-
ticiones , estas se vuelven cada vez mas pequeñas , hasta llegar a ser
inútiles en el caso de que estos no sean contiguos15 . En varios casos esto
es una situación común y genera graves perdidas de memoria. Esta puede
solucionarse mediante compactación , el cual se encarga de ordenar las
particiones para luego fusionarlas. Esta solo es posible si la reubicación
es dinámica y en tiempo de ejecución.
Fragmentación Interna : Sucede cuando el espacio adicional de par-
tición de un proceso no es lo sucientemente grande para este. La técnica
general consiste en descomponer la memoria en bloques de tamaño jo
y asignar esta en unidades basándose en el tamaño del bloque. Con esto
la memoria asignada a un proceso puede ser un poco mas grande. Esta
cantidad extra es la fragmentación externa.
8.4. Paginación
Es un esquema de gestión de memoria que permite que el espacio de di-
recciones físicas de un proceso no sea contiguo. Este evita la fragmentación
externa , pero no la interna.
8.4.3. Protección
Se consigue mediante una serie de bits de protección asociados con cada
marco, los cuales se mantienen en la tabla de paginas. Uno dene una pagi-
na como read-write o read-only. Este bit puede ser comprobado al mismo
tiempo que se calcula la dirección física. De producirse un intento de escritura
en un read-only provocara una interrupción de hardware al SO. Este tipo de
protección puede ampliarse y anarse fácilmente.
Se utiliza también un bit valido-invalido el cual determina si la pagina
asociada es del proceso que la intenta acceder. Este bit es congurado por el
SO al cargar un nuevo proceso23 . De realizarse una acceso invalido se enviara
una interrupción al SO.
Este esquema genera el problema de que ciertas posiciones de memoria
que no pertenecen al proceso serán marcadas como validas , a causa de la
fragmentación interna por el tamaño jo de las paginas. Esto se soluciona con
21 VerFig 8.11
22 Porejemplo , las del kernel pueden estár cableadas
23 Por lo que solo las entradas de este que fueron agregadas a la TLB son conguradas
como validas , mientras las otras como invalidas.
CAPÍTULO 8. MEMORIA PRINCIPAL 68
diferencias de las tablas hash en que cada entrada de la tabla hash apunta a
varias paginas en lugar de a una sola29 .
8.6. Segmentación
8.6.1. Método Básico
Ciertos inconvenientes por la incompatibilidad de la vista usuario de la
memoria y la paginación fueron evidentes a la hora de concebir la composición
en segmentos de un programa34 . Esto es claro cuando nosotros escribimos un
programa , el cual esta compuesto por un método principal y un conjunto de
metodos que son llamados por el anterior o por estos mismos de forma interna
, adicionalmente también hay estructuras de datos diversas.
La segmentación es un esquema de gestión de memoria que soporta esta
vision. El espacio lógico de direcciones es una colección de segmentos y en cada
segmento tiene un nombre y una longitud. Las direcciones especican tanto
el nombre del segmento como el desplazamiento dentro de ese segmento. Por
simplicidad los segmentos están numerados y se hace referencia a ellos mediante
ese numero35 .
8.6.2. Hardware
Deberemos denir una implementación para mapear las direcciones bidi-
mensionales denidas por el usuario sobre las físicas. Este mapeo se lleva a
cabo mediante una tabla de segmentos. Cada entrada de la tabla de segmentos
tiene 2 direcciones36 :
Dirección Base : La dirección física inicial del segmento.
Dirección Limite : La longitud de este.
La tabla de segmentos es una matriz de parejas de registros base-limite.
8.7. Resumen
Lo primero a tener en cuenta para determinar el algoritmo de gestión de
memoria es el hardware proporcionado , ya que ciertas comprobaciones de
seguridad y otras acciones no pueden implementarse ecientemente mediante
software.
Para seleccionar un algoritmo debemos tener en cuenta las siguentes con-
sideraciones :
Soporte Hardware : Un simple registro base o una pareja de registros
base-limite alcanza para la partición simple y múltiple. Mientras que para
la paginación y la segmentación se necesitan tablas de mapeo.
Rendimiento : La complejidad del algoritmo afecta el tiempo requerido
para acceder a la memoria física, lo cual aumenta el tiempo de context-
switch. Para los sistemas simples solo necesitamos realizar operaciones
de comparación o de sumas con la dirección lógica , mientras que en la
paginación y segmentación puede ser igual de rápida si utiliza registros de
34 Ver Fig 8.18
35 Nota del Autor : Véase que el compilador gcc con -S te devuelve código Assembler , en
el que vemos la segmentación de las funciones y otras partes de código.
36 Ver Fig 8.19 y 8.20
CAPÍTULO 8. MEMORIA PRINCIPAL 71
alta velocidad para las tablas de mapeo. Pero si las tablas se encuentran
en memoria , los accesos a memoria de usuario se vuelven mucho mas
lentos. Un TLB puede reducir esto.
Memoria Virtual
9.1. Fundamentos
Los algoritmos de gestión de memoria resulta necesarios debido a que las
instrucciones que se estén ejecutando debe estar en la memoria física. El primer
enfoque consiste en colocar el espacio completo de direcciones lógicas dentro
de la memoria física. Los mecanismos de carga dinámica pueden ayudar a
aliviar esta restricción, pero requieren que el programador tome precauciones
especiales y lleve a cabo trabajo adicional.
Este requisito parece razonable , pero también poco deseable ya que limita el
tamaño de los programas. En muchos casos no es necesario tener el programa
completo para poder ejecutarlo. Algunas partes que podrían despreciarse de
este serian :
El código para tratar condiciones de error poco usuales.
Matrices , listas y otras estructuras de datos no que han sido sobre di-
mensionadas, pueden encontrarse de forma parcial.
Incluso de necesitarse todo el programa , puede suceder que no todo el programa
sea necesario al mismo tiempo.
El ejecutar parcialmente un programa nos entrega las siguientes ventajas :
Los programas no estarían restringidos a la memoria física.
Al ocupar menos memoria , se puede ejecutar mas programas aumentando
el nivel de multiprogramación.
Se necesitarían menos operaciones I/O para cargar o intercambiar los
procesos. Esto disminuye el tiempo de context-switch.
La memoria virtual incluye la separación de la memoria lógica con respecto de
la física , lo cual evita la restricción de memoria1 .
El espacio de direcciones virtuales de un proceso hace referencia a la
forma lógica de almacenar un proceso en la memoria , la cual consiste en que
el proceso comienza en una cierta dirección lógica y esta almacenada de forma
contigua en la memoria2 .
1 Ver Fig 9.1.
2 Ver Fig 9.2.
72
CAPÍTULO 9. MEMORIA VIRTUAL 73
Las bibliotecas del sistema pueden ser compartidas por numerosos pro-
cesos. Las bibliotecas se mapean en modo de read-only.
2. Leer la pagina.
3. Reiniciar el proceso.
La técnica CoW14 funciona permitiendo que los procesos padre e hijo com-
partan inicialmente las mismas paginas. Estas paginas compartidas se marcan
como paginas CoW , lo que signica que cualquier proceso escribe en una de
las paginas compartidas , se creara una copia de ella la cual sera mapeada en
el espacio de direcciones del proceso que intento modicarla , el cual modi-
cara esta copia y no la original15 . La técnica CoW es común en varios SO's ,
incluyendo Windows XP , Linux y Solaris.
Es importante jarse la ubicación desde la que se asignara la pagina libre
donde se realizara la copia. Muchos SO's proporcionan un conjunto com-
partido de paginas libres para estos casos. Esas se suelen asignar cuando la
pila o el cumulo del proceso deben expandirse o para realizar CoW. Los SO's
asignan esas paginas utilizando una técnica denominada relleno de ceros ba-
jo demanda. Esta técnica llena de ceros las paginas antes de asignarlas para
eliminar el contenido previo.
Cada pagina pertenece a una de esas cuatro clases , de las cuales siempre
elegimos la clase mas bajo no vacía. Tenemos que recorrer varias veces la cola
antes de encontrar dicha pagina25 .
25 Nota del Autor : Seria mejor usar 4 colas e ir degradando cada una cada cierto tiempo ,
para así solo nos quedaría realizar FIFO en la cola de menor categoría no vacía. El aumento
de estructura no parece ser muy grande e inclusive el tiempo usado para mantenimiento
se compensa con la tener sustitución en tiempo constante , además la cola inferior solo
tendrá que contener una cantidad suciente hasta que se llegue a próximo intervalo donde se
recargara.
CAPÍTULO 9. MEMORIA VIRTUAL 80
9.6. Sobrepaginación
Si el proceso no tiene suciente marcos generara rápidamente fallos de pag-
ina , provocando una alta tasa de paginación ,conocida como sobrepaginación,
lo que determina que estuvo mas tiempo paginandose que haciendo trabajo
util.
26 Signica que la instrucción referencias a una dirección indirecta , la cual referencia a
otra dirección indirecta y así sucesivamente.
CAPÍTULO 9. MEMORIA VIRTUAL 82
Inicialmente todos los objetos de la cache están libres. Cuando hace falta
uno , el asignador asigna cualquiera de estos de la cache correspondiente a la
estructura de datos pedida y luego pone a dicho objeto como usado.
En Linux , un descriptor de procesos es del tipo task_struct. Cuando el ker-
nel crea una nueva tarea , solicita un objeto task_struct. En Linux se chequean
los estados de una franja , llena , vacía y parcialmente llena. El asignador de
franjas trata satisfacer los pedidos con objetos de franjas parcialmente llenas,
si no hay ninguna se usa una franja vacía. En caso de que tampoco haya se
crea una nueva franja.
Este método tiene 2 benecios claves :
1. No se pierde memoria debido a la fragmentación.
2. Las solicitudes de memoria pueden satisfacerse rápidamente. Es particu-
larmente efectivo para gestionar memoria en aquellas situaciones en que
los objetos se asignan y desasignan frecuentemente , como suele ser el
caso con las solicitudes de kernel.
El asignador de slabs apareció por primera ves en el kernel de Solaris 2.4. Debido
a su naturaleza de propósito general se usa también para ciertas solicitudes de
memoria en modo usuario. A partir de la versión 2.2 Linux adopto el sistema
de slabs.
P I = s × (1 − α) y P F = s × α
Por otro lado paginas mas pequeñas reducen la cantidad total de E/S , ya
que se dene mejor la localidad.
En sintesis , con un tamaño de pagina mas pequeño tenemos una mejor
resolución , permitiendo usar solo la memoria necesaria.
Actualmente se utilizan paginas de tamaño grande para acelerar la lectura
y escritura.
mente parte o todo el kernel esta bloqueado en memoria ya que muchos sistemas
no soportan fallos de pagina provocados por el kernel.
Otra utilidad del bloqueo esta relacionada con la sustitución normal de
paginas , al tener en cuenta la prioridad de los procesos. Permite a los procesos
de baja prioridad protejer las paginas adquiridas , activando su bit de bloqueo
, mientras esperan para poder usarlas al menos una vez. Luego se desbloquean
, permitiendo ser tomadas por otros procesos38 .
El peligro del uso del bit de bloqueo es que puede dejar bloqueadas paginas
de forma indenida a causa de algún error de sistema. Para esto Solaris permite
que se proporcionen consejos de bloqueo , pero el SO esta libre para descartar
estos consejos cuando sea conveniente.