Académique Documents
Professionnel Documents
Culture Documents
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 1
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
1
Gestión de memoria Minix 3
Visión general
Modo supervisor
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 2
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
2
Gestión de memoria Minix 3
4.1 Organización de la memoria (1)
Fichero ejecutable en disco
Direcciones Altas →
Símbolos (opcional)
Datos (inicializados)
Pila
Código (text)
Prog. B gap Segmento
Cabecera
I+D Separados Datos+bss
Codigo Segmento
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 3
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
En Minix es posible compilar los programas para que éstos usen espacios de códigos y datos (I+D)
juntos o separados. Con espacios I+D juntos todas las partes de un proceso, código (text), datos y
pila, comparten un mismo bloque de memoria (que para el procesador INTEL será un segmento).
Este bloque será asignado o liberado como un todo. Con espacios I+D separados, las partes de
código, datos y pila de un proceso se agrupan en dos bloques (o segmentos para INTEL). En el
primer bloque se ubica únicamente el código. En el otro bloque se ubican los datos y la pila (con un
espacio dinámico entre ellos que denominaremos ‘gap’). Estos dos bloques o segmentos pueden
ser asignados y/o liberados de forma independiente. Por ejemplo, en la llamada al Sistema “fork”, se
ha de asignar la misma cantidad de memoria que usa el padre para el hijo. Si se utilizan espacios
I+D juntos, el proceso es simple, se asigna un único bloque de igual tamaño que tiene el padre para
el hijo. Si se utilizan espacios I+D separados, el bloque de código se puede compartir entre el padre
y el hijo y por tanto no se tiene que asignar. Sólo se requiere asignar un bloque nuevo de igual
tamaño que el del padre para el hijo, conteniendo las áreas de datos y pila (mas gap).
Minix conoce el número de procesos que están usando el mismo segmento de código en un
momento dado. Al finalizar un proceso siempre se libera su bloque de datos y pila (segmento de
INTEL). El bloque (segmento de INTEL) de código sólo se liberaría, si éste bloque pasara a la
situación de no ser usado por ninguno otro proceso.
Los ficheros ejecutables en disco, aparte del código y datos inicializados, incorporan opcionalmente
una tabla de símbolos (para depuración), y también, de forma obligatoria, una cabecera con
información sobre las necesidades de memoria para las distintas partes del proceso, así como de la
necesidad total de memoria, además de si el uso de espacios I+D es juntos o separados. Si los
espacios son juntos, el tamaño de la memoria total se refiere a la cantidad de memoria total que
requiere el proceso (incluye datos+bss+gap+pila). En espacios I+D juntos, la cantidad de memoria
total incluye además el código. Por ejemplo, si un fichero con espacios I+D juntos tiene 4KB de
código, 2KB de datos+bss, 1KB de pila y 40KB memoria total, el gap sería de 33KB. Si fueran
separados el gap sería de 37 KB.
El área BSS son datos del proceso que no requieren inicialización y por ello no se guardan en el
archivo ejecutable.
3
Gestión de memoria Minix 3
4.1 Organización de la memoria (2)
Asignación de memoria.
· Con las llamadas:
Gestor de fork: la misma cantidad que al padre
Procesos execve: la cantidad indicada en la cabecera del fichero ejecutable
Liberación de memoria.
· Cuando un proceso muere, bien mediante exit ó por la llegada
de una señal cuyo tratamiento sea matar al proceso receptor.
· Con execve se libera si la llamada tiene éxito.
Dir. Altas →
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 4
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
En las operaciones normales de MINIX 3 se asigna memoria a un proceso sólo en dos ocasiones:
Con la llamada “fork”, en la que asigna la cantidad de memoria que precisa el hijo y con la llamada
“execve”, en la que se devuelve la memoria usada por la vieja imagen a la lista de huecos libres y
se asigna memoria para la nueva imagen.
La liberación de memoria ocurre en dos ocasiones: a) Cuando un proceso muere, bien sea por la
llamada “exit” o la recepción de una señal y b) Durante la llamada “execve” si ésta tiene éxito.
En la llamada “fork”, dependiendo de si los espacios I+D son juntos o separados, se asigna
memoria para el proceso hijo de una forma u otra. Con espacios juntos, la memoria nueva
asignada constituirá un solo bloque (segmento de Intel), del mismo tamaño que el del padre, el
cual se corresponde con la memoria total utilizada por el padre, es decir, la suma de los tamaños
del código, datos, gap y pila. Con espacios separados, se asigna un nuevo bloque para el hijo de
igual tamaño que el de padre, pero este bloque (segmento) contiene únicamente las áreas de
datos, gap y pila. El bloque de código (segmento) en cambio, no se asigna, ya que puede ser y es,
compartido con el padre.
En la llamada “execve” se asigna al proceso la cantidad de memoria indicada en la cabecera del
fichero ejecutable, teniendo en cuenta si los espacios son juntos o separados, tal y como ya se ha
contado. Hay que tener en cuenta en esta llamada, que si los espacios son separados se complica
un poco la gestión, ya que al liberar la vieja imagen, tendrá que tenerse en cuenta si el bloque de
código está siendo compartido por otro proceso, en cuyo caso no se liberaría éste último. Este
problema no ocurre con espacios juntos, ya que al no haber bloques de código compartidos,
siempre que se libera la imagen de memoria vieja, se libera de forma completa, ya que ésta
constituye un único segmento (o bloque).
Un problema similar ocurre con espacios separados cuando un proceso muere. También se ha de
tener en cuenta si el bloque (segmento) de código está siendo usado por otro proceso o no, para
liberarlo o no, en consecuencia.
Gestión de memoria Minix 3
4.2 Tratamiento de mensajes (1)
Tipo mensaje Parámetros de entrada Valor devuelto
fork ninguno Pid hijo y 0 al hijo
exit exit status Si éxito, ninguna
wait ninguno Status
waitpid Identificador de proceso y flags Status
brk Nuevo tamaño Nuevo tamaño
exec Puntero pila inicial Si éxito, ninguna
kill Identificador proceso y señal Status
alarm Nº segundos a esperar Tiempo que queda
pause ninguno Si éxito, ninguna
sigaction Nº de señal, accion, accion ant. Status
sigsuspend Mascara de señal Si éxito, ninguna
sigpending ninguno Status
sigprocmask Cómo, set y set anterior Status
sigreturn contexto Status
getuid ninguno Uid y uid efectivo
getgid ninguno Gid y gid efectivo
getpid ninguno Pid y pid del padre
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 5
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
El Gestor de Procesos, al igual que el resto de componentes de Minix 3, está dirigido por mensajes.
Después de la inicialización, éste entra en el bucle principal, el cual consiste básicamente en:
Esperar un mensaje, realizar la petición contenida en el mismo y enviar un mensaje de respuesta.
Los mensajes recibidos pertenecen a dos categorías, mensajes de procesos de usuario y mensajes
de notificación del Sistema. Siendo estos últimos usados para la comunicación de alta prioridad
entre el “kernel” y los gestores (servidores de Minix).
La mayoría de los mensajes recibidos por el GP resultan de llamadas al Sistema generadas por los
procesos de usuario. Estos mensajes son los que se muestran en la transparencia.
También sucede, como cabria esperarse, que la mayoría de las llamadas al Sistema que trata el
Gestor de Procesos están relacionadas con la memoria, procesos o señales, no obstante, algunas
que no lo están, como por ejemplo “time, stime, ptrace, etc”, simplemente se han incluido aquí
porque el Gestor de Ficheros ya era bastante grande y complejo.
El mensaje “reboot” tiene efectos en todo el S.O., pero su principal trabajo es enviar señales de
terminación a todos los procesos de forma controlada, por eso lo trata el Gestor de Procesos. Lo
mismo sucede con el mensaje “svrctl” que activa y desactiva el intercambio. Los mensajes
“getsysinfo, getprocnr, memalloc, memfree, y getsetpriority” no están pensados para procesos de
usuario ordinarios, y no forman parte de POSIX.
En un sistema monolítico, las operaciones que realizan el tratamiento de estos mensajes están
compiladas en el kernel como llamadas a función, pero en Minix 3, partes que normalmente son
consideradas del Sistema Operativo, corren en espacio de usuario. Algunas de ellas hacen poco
más que implementar un interfaz a una llamada al “kernel”, término que podemos usar para
peticiones de servicio del kernel vía “Tarea del Sistema”.
5
Gestión de memoria Minix 3
4.2 Tratamiento de mensajes (2)
Tipo mensaje Parámetros de entrada Valor devuelto
setuid Nuevo uid Status
setgid Nuevo gid Status
setsid Nuevo sid Grupo de proceso
getpgrp Nuevo gid Grupo de proceso
time Puntero al lugar donde poner el tiempo actual Status
stime Puntero al tiempo actual Status
times Puntero a buffer para tiempos del proceso e hijo Tiempo desde el arranque
ptrace petición, pid, dirección y datos Status
reboot Cómo (halt, reboot o panic) Si éxito, ninguna
svrctl Petición, datos, (depend. Func.) Status
getsysinfo Petición, datos, (depend. Func.) Status
getprocnr ninguno Nº de proceso
memalloc Tamaño, puntero a dirección Status
memfree Tamaño, dirección Status
getpriority Pid, tipo y valor Prioridad
setpriority Pid, tipo y valor Prioridad
gettimeofday ninguno Tiempo
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 6
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
Existe una estructura de datos fundamental para el procesado de los mensajes: La tabla
“call_vec”, la cual contiene los punteros a los procedimientos que tratan los diferentes
mensajes.
Cuando llega un mensaje al Gestor de Procesos (en el bucle principal), se extrae el tipo
de mensaje y se pone en la variable global “call_nr”. Este valor -el tipo-, se usa como
índice de la tabla “call_vec” para encontrar el puntero al procedimiento que tratará el
mensaje recién llegado, para a continuación, hacer una llamada al mismo y ejecutar la
llamada al Sistema.
El valor devuelto por este procedimiento se incluye en un mensaje que se le envía al
proceso llamante como respuesta, informándole así de la conclusión de la llamada y del
éxito o fracaso (código de error) de la misma.
6
Gestión de memoria Minix 3
4.3 Estructuras de datos y algoritmos del Gestor de Procesos (1)
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 7
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
Existen dos estructuras básicas que usa el Gestor de Procesos: La tabla de procesos
(mantenida en la variable “mproc”) y la tabla de huecos. Algunos de los campos de la tabla
de procesos los necesita el “kernel”, otros el Gestor de Procesos y otros el Gestor de
Ficheros. En Minix 3, cada una de estas tres partes del S.O. tiene su propia tabla de
procesos, en la que únicamente se tienen los campos que necesitan. Salvo algunas
excepciones, las entradas en la tabla se corresponden exactamente. Así la posición ‘k’ de
la tabla del G.P. se refiere al mismo proceso que la posición ‘k’ de la tabla del G.F..
Cuando se crea o destruye un proceso, las tres partes actualizan sus tablas para reflejar la
nueva situación consistentemente. Las excepciones son procesos que no son conocidos
fuera del “kernel”, como las Tareas de Reloj y de Sistema, o bien los “falsos” procesos
IDLE y KERNEL (ocupan entrada pero no tienen código). En la tabla del “kernel”, estas
excepciones tienen asignados números negativos de entrada. Estas entradas no existen
ni en el G.P. ni en el G.F.. Así, lo dicho anteriormente acerca de la posición ‘k’, es
estrictamente cierto para valores mayores o iguales a cero. Otros procesos de Minix como
el Gestor de Procesos y el Gestor de Ficheros tienen asignadas las entradas 0 y 1
respectivamente en todas las tablas.
Un campo a destacar de la tabla de procesos del G.P. es el vector “mp_seg”, el cual tiene
tres elementos para expresar los segmentos de código, datos y pila (no confundir el
término segmento de Minix, que hemos denominado anteriormente ‘bloque’, con el mismo
término segmento de INTEL). Cada elemento a su vez, es una estructura que contiene las
direcciones virtual y física, así como la longitud de cada segmento, todo ello expresado en
‘clics’. El tamaño de un ‘clic’ es dependiente de la implementación, siendo en Minix-3 de
1024 bytes. Todos los segmentos ocupan un número entero de clics y empiezan siempre
en una dirección múltiplo del tamaño del ‘clic’.
7
Gestión de memoria Minix 3
4.3 Estructuras de datos y algoritmos del Gestor de Procesos (2)
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 8
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
8
Gestión de memoria Minix 3
4.3 Estructuras de datos y algoritmos del Gestor de Procesos (3)
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 9
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
El método usado para registrar la ubicación en memoria de los procesos se puede ver en la
transparencia. En este ejemplo se muestra un proceso de 3 KB de código, 4 KB de datos, 1 KB de
hueco (gap) y una pila de 2 KB.
Las direcciones virtuales de los segmentos de código y datos son siempre 0. El tamaño del
segmento de código en el caso de I+D juntos es siempre 0 (ya que el código está en el segmento
de datos, es como decir que no hay segmento de código). El tamaño del segmento de datos, en
este mismo caso, tiene la suma de los tamaños de código y datos (3+4=7).
La dirección virtual de la pila se calcula siempre como la diferencia entre las direcciones físicas del
comienzo de la pila y el comienzo del segmento de datos (que en el caso de I+D juntos coincide
también con el comienzo del segmento de código). Cuando un proceso hace referencia a la
dirección virtual 0, tanto en el espacio de código como en el de datos, se usará la dirección física de
comienzo correspondiente al segmento referido, es decir la dirección 0x32000 (= 200 KB, = 0xC8
clic), si es en el espacio de código, ó 0x32C00 (= 203 KB, = 0xCB clic), si es en el espacio de datos.
Dada una dirección virtual y el espacio al que pertenece, es fácil determinar si dicha dirección virtual
es legal o no (que pertenezca al segmento), y si es legal, determinar cual es su dirección física.
Nótese que la dirección virtual en la que comienza la pila depende de la cantidad total de memoria
reservada para el proceso. Si se usara el comando “chmem” para modificar la cabecera del fichero
para dotar al proceso de más espacio dinámico (más gap), la siguiente vez que se ejecutara, la pila
comenzaría en una dirección virtual más alta. Si la pila creciera 1 clic, la tripleta de pila [0x8, 0xD0,
0x2] pasaría a ser [0x7, 0xCF, 0x3], esto reduciría a la nada el gap si no se modificase la cantidad
total de memoria del proceso (10 KB).
9
Gestión de memoria Minix 3
4.3 Estructuras de datos y algoritmos del Gestor de Procesos (4)
Gestor de
Estructuras ‘hole’ en lista ordenada
Procesos por dirección base ascendente. Prog. C
hole_head
Prog. B
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 10
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
La otra estructura de datos principal del Gestor de Procesos es la tabla de huecos, la cual
mantiene una lista de todos los huecos de memoria en orden ascendente de dirección de
memoria.
Los huecos entre los datos y pila de un proceso no son considerados huecos, ya que ya
han sido asignados a los procesos, y por ello consecuentemente, no figuran en la lista de
huecos.
Tanto la dirección base del hueco como el tamaño del mismo vienen expresados en ‘clics’
en vez de en bytes. La razón de ello es porque es mucho más eficiente. Por ejemplo, si se
usan enteros de 16 bits para almacenar direcciones de memoria, con unidades de 1024
bytes por clic, sería posible hacer referencia hasta 64 MB en vez de sólamente 64 KB.
10
Gestión de memoria Minix 3
4.3 Estructuras de datos y algoritmos del Gestor de Procesos (5)
200
40 clics hole_head 20
C
30 clics free_slots
10 clics 300
10
B
20 clics
340
40
A Null
60 clics
20 clics
200
MINIX
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 11
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
La variable “hole_head” apunta al primer elemento del vector ‘hole’ (de 204 elementos),
el cual contiene el primer hueco.
La variable “free_slots” apunta al primer elemento “hole” libre (slot).
“hole_head” establece el comienzo de la lista de huecos y ‘”free_slots” el de la lista de
elementos “hole” (slots) que no están asociados a ningún hueco de memoria, es decir,
libres.
11
Gestión de memoria Minix 3
4.3 Estructuras de datos y algoritmos del Gestor de Procesos (5)
Memoria clics
254 KB Null
0x0302
(1) D. Base Tamaño
Prog. B
Situación Inicial 0x02D0
0302 00FE
de la memoria y
lista de huecos 500 KB
0x00DC 00DC 01F4
(2) Prog. A 0x00B4
Situación tras Minix hole_head
0x0000
ubicar el programa
‘C’ de 100 KB’
(3)
Memoria clics Memoria clics Situación tras
254 KB Null La muerte de los
0x0302
D. Base Tamaño
Programas ‘B’ y ‘C’
Prog. B
0x02D0
0302 00FE 804 KB Null
400 KB
0x0140 D. Base Tamaño
Prog. C 0x00DC 0140 0190 00DC 0324
0x00DC
Prog. A 0x00B4 Prog. A
0x00B4
Minix hole_head Minix hole_head
0x0000 0x0000
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 12
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
12
Gestión de memoria Minix 3
4.4 Las llamadas al sistema (Bucle principal GP, 1/4)
PUBLIC int main() { /* Rutina “main” del Gestor de Procesos */
int result, s, proc_nr; struct mproc *rmp; sigset_t sigset;
/* Continua . . . . . */
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 13
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
13
Gestión de memoria Minix 3
4.4 Las llamadas al sistema (Bucle principal GP. 2/4)
/* Continuación …… */
swap_in(); /* Quizá un proceso pueda entrar en memoria (intercambio) */
/* Da salida a todos los mensajes pendientes de envío, incluyendo la respuesta a la llamada recien
* hecha arriba. Los procesos no deben estar fuera de la memoria (“not swapped out”)
*/
for (proc_nr=0, rmp=mproc; proc_nr < NR_PROCS; proc_nr++, rmp++) {
/* Mientras tanto, el proceso puede haber muerto por una señal (e.g. si una señal letal
* pendiente hubiera sido desbloqueada) sin que el GP lo supiera. Si la entrada en la TP
* está libre o como “zombie”, no se responde.
*/
if ((rmp->mp_flags & (REPLY | ONSWAP | IN_USE | ZOMBIE)) ==
(REPLY | IN_USE)) {
if ((s=send(rmp->mp_endpoint, &rmp->mp_reply)) != OK) {
printf("PM can't reply to %d (%s)\n",
rmp->mp_endpoint, rmp->mp_name);
panic(__FILE__, "PM can't reply", NO_NUM);
}
rmp->mp_flags &= ~REPLY;
} /* end-if*/
} /*end-for*/
} /*end-while*/
return(OK);
} /*end-main*/
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 14
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
Otro punto a hacer notar es la llamada a “swap_in”. Esta llamada está vacía si la
configuración de Minix no incluye “intercambio de memoria”. Pero si Minix se configura
para incluir todo el fuente con intercambio activado, es aquí donde se realiza la
comprobación de si un proceso puede ser intercambiado a memoria.
14
Gestión de memoria Minix 3
Las llamadas al sistema (Bucle principal GP. 3/4)
/* ‘Slot’ (entrada en la tabla de procesos) del proceso llamador (caller). Se usa el ‘slot’ propio
del Gestor de Procesos si es el kernel el que hace la llamada. Esto puede suceder en el caso de
alarmas sincronas (CLOCK), o señales del kernel pendientes tipo evento (SYSTEM) */
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 15
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
15
Gestión de memoria Minix 3
Las llamadas al sistema (Bucle principal GP. 4/4)
rmp->mp_reply.reply_res = result;
rmp->mp_flags |= REPLY; /* Respuesta pendiente */
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 16
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
16
Gestión de memoria Minix 3
4.4 Las llamadas al sistema, fork, exit y wait: (fork)
Proceso
Proceso de hijo
Usuario
▪ Comprueba tabla
de procesos llena
▪ Reserva memoria ▪ Avisa a la TS
▪ Informa a la ▪ Informa al
para datos y pila para que copie
TS del nuevo GF del nuevo
del hijo el nuevo mapa
proceso proceso
▪ Solicita copiar de memoria
datos a la TS
Gestor de
Procesos
▪ Busca entrada libre
SYS_NEWMAP
en la TP para el hijo
SYS_VIRCOPY
Tell_fs(FORK,.)
y copia la entrada del
SYS_FORK
padre en ella. ▪ Copia el nuevo
▪ Cumplimenta el nuevo mapa desde la
mapa de memoria del TP del GP a la TP
hijo en su entrada. del kernel
▪ asigna un nuevo ‘pid’
Tarea del para el hijo.
Sistema
▪ Crea proceso hijo ▪ Crea proceso hijo
▪ Copia datos y pila
Gestor de en su propia Tabla en su propia Tabla
del padre a la imagen
Ficheros de memoria del hijo
flechas send_rec (…)
Significado de las flechas: send (…)
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 17
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
Cuando se crea un proceso la Tabla de Procesos (TP) debe ser actualizada, lo que
incluye también a las TP’s del Kernel y Gestor de Ficheros (GF). El Gestor de Procesos
(GP) coordina esta actividad. La creación de procesos se efectúa mediante “fork”, tal y
como se muestra, a grandes rasgos, en la transparencia. Es difícil e inconveniente detener
una llamada “fork” en curso, por lo que se han de hacer una serie de comprobaciones para
asegurar que la llamada no falla. En primer lugar (1) se comprueba que haya sitio en la
TP. El GP lleva una contador de procesos creados, así es fácil saber si se puede crear
uno nuevo. Si hay sitio en la TP (2) se reserva memoria para el nuevo proceso, (si I+D
separados no se reserva espacio para el código). Si hubo éxito en la reserva la llamada
“fork” no fallará, entonces (3) se rellena el espacio memoria reservado (copiando la
imagen de memoria del padre). (4) Se ubica una nueva entrada en la TP y se rellena
copiándose de la entrada del padre, actualizando convenientemente los campos
“mp_parent, mp_flags, mp_child_utime, mp_child_stime, (5) mp_seg (nuevo mapa de
memoria), mp_exitstatus y mp_sigstatus” (algunos bits de “mp_flags” se heredan). (6) Se
escoge un “pid”, este paso no es trivial; el “pid” empieza en 1 incrementándose hasta un
valor máximo de 30.000, cuando se alcanza este valor, se vuelve a empezar a partir del 2,
comprobando que no esté en uso, si lo estuviera se incrementaría en 1 y se volvería a
comprobar, y así sucesivamente hasta encontrar uno libre. (7) Se informa a las otras
partes de Minix (kernel y GF) del nuevo proceso creado, para que éstas a su vez puedan
actualizar sus TP’s. Por último, (8) se prepara la respuesta, primero al proceso hijo con
pid=0, y luego al padre, con el “pid” asignado al hijo. El orden en el que se realizan las
respuestas dependerá de la posición que ocupen los procesos padre e hijo en la tabla de
procesos. Se envía primero el que ocupa una posición mas baja.
17
Gestión de memoria Minix 3
4.4 Las llamadas al sistema, fork, exit y wait: (exit) Desaparece,
Proceso No hay respuesta
Proceso de padre
Usuario
▪Contabiliza SI : --- “cleanup”---
tiempos de ▪Libera entrada en la TP
Se desbloquea
cpu al padre ▪decrementa contador
▪Notifica a procesos. ▪ Recorre TP:
la TS del fin ▪Despierta al padre Para cada entrada:
de ejecución devolviendole el pid Si es hijo
hijo->parent = Init
¿ ALARMA ▪Solicita ▪Informa al ▪Informa a
la TS del fin Si zombie & Init waiting
PENDIENTE ? tiempos de GF del fin
del proceso ¿ PADRE “cleanup” del hijo
cpu a la TS del proceso
EN WAIT ?
Gestor de
Procesos
SYS_TIMES
SYS_EXIT
SYS_NICE
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 18
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
Un proceso termina totalmente cuando suceden dos cosas: (1) El proceso ha finalizado (por “exit” o
recepción de una señal), y (2) su padre estaba esperando en un “wait”. Un proceso que ha
finalizado, pero cuyo padre no ha efectuado “wait” por él, entra en un estado conocido como
“zombie”. Su memoria se libera, no se planifica y su posible alarma se desactiva, sin embargo,
todavía ocupa su entrada en la TP. Este estado no suele durar y cuando su padre, eventualmente,
ejecuta “wait”, se produce la liberación de la entrada de la TP y se informa al GF y kernel para que
hagan lo propio.
Si un proceso muriese estando su padre ya muerto, habría un problema, porque un proceso
“zombie” quedaría así para siempre. Para evitar esto, Minix hace que cuando un proceso muere,
todos sus hijos, pasen a ser hijos de “Init”. Al arrancar el Sistema, “Init” lee el fichero “/etc/ttytab”
para obtener la lista de todos los terminales, y hace “fork”, con cambio de imagen a “login”, por
cada terminal. A continuación se bloquea a la espera de la terminación de cualquier proceso hijo y
de este modo “limpiar” cualquier “zombie “huérfano.
La llamada “exit” ejecuta el procedimiento “do_pm_exit”, el cual llama a “pm_exit” que es quien hace
todo el trabajo. Esto es porque a “pm_exit” también se le llama cuando un proceso termina por una
señal, aunque con parámetros de llamada distintos.
El procedimiento “pm_exit” hace lo siguiente: (1) Si el proceso tiene una alarma pendiente la
detiene. (2) Contabiliza al padre los tiempos consumidos por el proceso que acaba. (3) Notifica a la
TS de que el proceso deja de ejecutarse. (4) Notifica al GF, y (5) a la TS después, del fin del
proceso (exit) para que actualicen sus TP,s liberando las entradas del proceso respectivas. (6) Se
libera la memoria del proceso. Para ello, primero se determina si el segmento de código está siendo
usado por algún otro proceso. Si no es así se libera, y a continuación se libera el segmento de datos
y pila. (7) Si el proceso padre le está esperando (en un “wait”), se llama al procedimiento “cleanup”,
el cual libera la entrada del proceso en la TP del GP, decrementa el contador de procesos y
despierta al padre. En caso contrario, deja al proceso en estado “zombie” y envía la señal
SIGCHILD al padre. Después de “cleanup”, tanto si el proceso queda “zombie” como si no, se
recorre la TP buscando cualquier proceso hijo. Si se encuentra alguno le hace hijo de ”Init”. Si “Init”
está “WAITING” y algún hijo está “zombie” se llama al procedimiento “cleanup” para dicho hijo.
18
Gestión de memoria Minix 3
4.4 Las llamadas al sistema, fork, exit y wait: (wait)
Proceso de
Usuario
Esta respuesta se
envía en cleanup.
estado=WAITING Return
¿ Está ¿ Tiene return(SUSPEND) (ECHILD)
----SI ----- algún hijo ?
stopped ? return(pid)
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 19
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
Cuando el proceso padre hace “wait” o “waitpid”, se ejecuta la función “do_waitpid”, la cual rastrea
la TP en busca de algún hijo. Si hay alguno, comprueba si está en estado “zombie”, en cuyo caso,
se llama a la función “cleanup” y la función “do_waitpid” devuelve “SUSPEND”, como código de
retorno (lo que significa que no se manda mensaje de respuesta al padre, sin embargo no quedará
bloqueado porque la respuesta se envía en la función “cleanup”). Si se encuentra algún hijo en
estado “stopped” (siendo trazado), se informa de ello en el mensaje de respuesta y se retorna (hay
mensaje respuesta al padre). Si no se hubiera encontrado ningún hijo en la TP, se devuelve error.
Si hubiera hijos pero ninguno en estado “zombie” o “stopped”, dependiendo de si se ha llamado a
“wait” con la opción de espera (caso normal) o no (WNOHANG), se deja al proceso bloqueado
“WAITING” (se retorna SUSPEND y por tanto no se le responde con mensaje), o se retorna 0 (se
responde con mensaje y el proceso puede continuar).
Cuando un proceso acaba y su padre lo estaba esperando, cualquiera que sea el orden de estos
eventos, se llama al procedimiento “cleanup” para efectuar los últimos pasos. Queda poco por
hacer. El padre es despertado de su “wait” y se le entrega el “pid” del hijo que ha finalizado, así
como su código de salida (exit code). El GP ya ha liberado la memoria del proceso hijo y el kernel
ya ha suspendido la planificación y liberado su entrada en la TP, así como también el GF. En este
punto el proceso hijo se ha ido para siempre.
19
Gestión de memoria Minix 3
4.5 La llamada al sistema execve (1/5)
Al comprobar el tamaño requerido
nunca se tiene en cuenta que la
“C” hace execve() a liberación de la propia imagen
un programa “D” de sumada a algún hueco contiguo
40 clics tamaño 70 clics. podría satisfacer el requisito de
---------------------------
memoria. Si se puede compartir
¡ NO HAY MEMORIA !
C código sólo se pide memoria para
30 clics el segmento de datos+gap+pila. No
se considera nunca la posibilidad
de hacer dos peticiones de memo-
10 clics ria, aún siendo I+D separados.
“B” hace execve()
a un programa “E” I+D Juntos
B
con una pila de
20 clics Tamaños segmentos de “E”
1400 bytes
------------------------
--------------------
¡ NO HAY MEMORIA ! Bytes Clics
A
Código 20.510 --
60 clics
Datos 220
BSS 10.000 31
20 clics Pila 1.400 2
d Total 32.740 32
MINIX
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 20
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
Durante la llamada “execve”, cuando Minix busca un hueco de tamaño suficiente para
satisfacer una petición de memoria, no se tiene en cuenta si la memoria liberada por la
imagen saliente sumada a algún posible hueco adyacente puede satisfacer dicha
petición. Cuando el programa “C” hace “execve” sobre el programa “D”, Minix busca un
hueco de tamaño suficiente para albergar a “D” al completo. Si “D” tiene espacios I+D
separados, la necesidad podría repartirse en principio entre dos huecos, sin embargo
Minix no solicita estos dos huecos, por lo que esto es algo mejorable. Sin embargo
Minix, si contempla la posibilidad de que “D”, con I+D separados, pueda compartir el
código con algún programa ya en memoria, para buscar si es así, un único hueco para
el segmento de datos+gap+pila.
Cuando el programa “B” hace “execve” con una pila de 1.400 bytes, e intenta cambiar la
imagen de memoria por la del programa “E” que tiene en su cabecera los tamaños
siguientes: Text=20.510, Datos=220, BSS=10.000 y Total=32.740 con I+D juntos. La
llamada falla por memoria insuficiente. La explicación es la siguiente: El segmento de
datos incluye el código y datos no inicializados (BSS), su tamaño es 20.510 + 220 +
10.000 = 30.730 que expresados en clics son 31. El tamaño total que son 32.740 bytes
nos debería dejar un espacio para pila y gap de 32.740 – 30.730 = 2.010 bytes, que en
principio sería suficiente para la pila de 1.400 bytes, sin embargo, en el mapa de
memoria los segmentos se requieren en clics. El tamaño del segmento de pila son 2
clics, y el tamaño “Total” en clics, según el campo del fichero es de 32 clics, que es
inferior a la suma del de datos y pila (31+2). El programa “E” tendría que ser ejecutado
con un tamaño de pila máximo de 1 clic si se desea tener éxito en el “execve”. Este
problema surge debido a la perdida de memoria que sufren los segmentos por
fragmentación interna.
20
Gestión de memoria Minix 3
4.5 La llamada al sistema execve (2/5)
envp [ ] execve(“/bin/ls”, argv, envp) \0 t s A 8188
\0 t s a 52
0 / r s u 8184
/ r s u 48
\0 t s A 8188 / = E M 8180
/ = E M 44
/ r s u 8184 O H \0 c 8176
HOME = /usr/ast O H \0 c 40
/ = E M 8180 . g \0 c 8172
. g \0 c 36
O H \0 c 8176 - f \0 l 8168
- f \0 l 32
. g \0 c 8172 - \0 s l 8164
PILA - \0 s l 28
0 8160
- f \0 l 8168
0 24
- \0 s l 8164 8178 8156
42 20
0 8160 0 8152
0 16
8174 8148
argv [ ]
8178 8156
38 12
0 8152 8170 8144
0 34 8
8174 8148 8167 8140
g.c 31 4
8170 8144 8164 8136
f.c 28 0
8156 8132
8167 8140 envp
-l 8136 8128 argv
8164 8136
DATOS
ls crtso 4 8124 argc
CODIGO return 8120
(a) (b) (c)
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 21
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
En la figura de arriba se muestran las operaciones básicas que se llevan a cabo con la pila
durante la llamada “execve”, tomando como ejemplo la ejecución del comando “ls –l f.c g.c” en
la “Shell”. Ésta interpreta dicho comando y efectúa la llamada: “execve (“/bin/ls”, argv, envp)”, la
cual construye la pila inicial en el espacio de usuario tal y como se muestra en la figura de arriba
(a), con direcciones relativas a la dirección virtual 0.
Esta pila se copia tal cual a una variable local dentro del espacio del GP. Éste parchea la pila
modificando las direcciones de los punteros acorde a las direcciones que deberían tener, dentro
de una pila ubicada en la nueva imagen. Suponiendo por ejemplo, que el tamaño de la memoria
total del nuevo programa fuera de 8.192 bytes (la última dirección del programa sería la 8.191),
entonces el GP, sabiendo que dicha pila se situará al final de la memoria disponible para el
programa, modificará las direcciones de todos los punteros dentro de la pila de acuerdo a su
nueva ubicación y mandará a la TS que copie la pila, desde su copia local, a la nueva imagen en
espacio de usuario.
Con la llamada “execve” ya finalizada, la pila de la nueva imagen quedaría tal y como se muestra
en la figura (b), con el puntero de pila en el valor 8.136. Todavía quedaría algo por resolver. El
programa principal a ejecutar tendrá probablemente una función como esta: “main (argc, argv,
envp)”. Para el compilador de “C” “main” es otra función más. El código compilado supone que
los tres parámetros, y la dirección de retorno están en la pila, y no es así como lo ha dejado
“execve”. Por esto los programas no empiezan directamente en “main”. Hay una pequeña rutina
llamada “C run-time, start-off” (crtso) que se monta (link) en la dirección de comienzo 0 y toma
inicialmente el control. Su trabajo es apilar estos tres parámetros y llamar a “main” mediante
“call”. El resultado de la pila se muestra en la figura (c ). Si desde “main” no se invocase “exit”
para finalizar el programa, la dirección de retorno de la pila devolvería el control a la rutina crtso,
la cual efectuaría la llamada a “exit”
21
Gestión de memoria Minix 3
4.5 La llamada al sistema execve (3/5)
- Si exceso en el tamaño de la pila o longitud ruta excesiva. ¡ Error execve !
- Obtiene el nombre del fichero. Lo copia de espacio de usuario a espacio del GP:
sys_datacopy (). Si error ¡ Error execve! Envía SYS_VIRCOPY a la TS.
- Copia la pila del proceso de espacio de usuario a espacio del GP:
sys_datacopy (). Si error ¡ Error execve! Envía SYS_VIRCOPY a la TS.
- Bucle de hasta dos iteraciones. Si es un “script” se ejecuta 2 veces. Si no sólo 1.
▪ Cambia al directorio de usuario: tell_fs (CHDIR,…); Envia CHDIR al GF.
▪ Comprueba si el fichero es ejecutable:
fd=allowed (nombrefichero_ejecutable, …):
access (). Envia ACCESS al GF.
tell_fs (SETUID, superuser,..); Envía SETUID al GF.
fd=open (). Envía OPEN al GF.
tell_fs (SETUID, euid,..); Envia SETUID al GF.
fstat (). Envía STAT al GF.
Si fichero no ejecutable: close(fd); ¡ Error execve ! Si error envía CLOSE al GF.
▪ Lee la cabecera del fichero y obtiene los tamaños de los segmentos
read_header (fd, &txt_bytes, &data_bytes, &bss_bytes, &tot_bytes, ….):
read (fd, &header,..); Envía READ al GF.
Comprueba tamaño mínimo de cabecera, número mágico, si es un script
(“#!” dos primeros caracteres), etc. Si algo inválido se retorna error.
Lee y establece espacios I+D juntos o separados.
Prepara tamaños de los segmentos y comprueba si son coherentes.
- Fin Bucle
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 22
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
La llamada “execve” está implementada por la función “do_exec()”. Después de efectuar algunas
comprobaciones, el GP manda a la TS que copie el nombre del fichero ejecutable, desde el
espacio de usuario al suyo propio. En el mensaje con la orden se incluye además la dirección y
longitud del nombre. A continuación vuelve a ordenar a la TS que copie la pila construida por la
librería de usuario a una variable local propia. En el mensaje con la orden se incluye la dirección
de la pila y su tamaño. Seguidamente se entra en un bucle. Si el fichero es un binario el bucle se
ejecuta una única vez , si es un script dos. Primero veremos el caso de que fichero sea un
binario: 1) El GP envía un mensaje al GF para que cambie el directorio actual al directorio de
trabajo del usuario. 2) Se llama a la función “allowed()” para comprobar si se permite la ejecución.
Si es así, se abre el fichero y se devuelve el descriptor, sino se devuelve error y la llamada falla.
3) Se lee la cabecera del fichero para obtener los tamaños de los segmentos. Si el fichero es un
binario el código devuelto provoca la salida del bucle.
Lo que sucede si el fichero es un script es lo siguiente: Cuando se lee la cabecera del fichero se
comprueban los dos primeros caracteres (#!) que indican que el fichero es un script, en la misma
línea también se especifica el programa que interpretará el script junto con posibles opciones, por
ejemplo: #! /usr/local/bin/perl wT. En este caso el fichero que se tiene que cargar en memoria es
el binario intérprete del script en lugar del script, además se parchea la pila en la función
“patch_stack()” añadiendo a ésta los parámetros indicados en la línea del script (respetando los
que se hubieran especificado en la llamada “execve”).
No se permite que el interprete de un “script” sea a su vez otro “script”, limitando el número de
iteraciones del bucle a 2.
Si hubiera algún error en la cabecera, se devolvería éste y la llamada “execve” fallaría.
22
Gestión de memoria Minix 3
4.5 La llamada al sistema execve (4/5)
- Si hubo algún error en cabecera: close (fd) ¡ Error execve ! Si error envía CLOSE al GF.
- Comprueba si se puede compartir el segmento de código.
- Toma memoria para la nueva imagen, libera la antigua, actualiza el mapa e informa al kernel.
new_mem():
si error “alloc_mem()” ¡ Error execve !
si segmento código saliente no está siendo compartido “free_mem(seg_codigo,..)”
“free_mem(seg_datos,..)”
sys_newmap () Envía SYS_NEWMAP a la TS
Rellena a 0’s BSS, gap y pila: sys_memset (…) Envia SYS_MEMSET a la TS
- Parchea la pila y la copia desde el GP a la nueva imagen de memoria
Parchea la pila : patch_ptr (..)
Copia la pila: sys_datacopy (..) Envía SYS_VIRCOPY a la TS
- Lee de disco el segmento de datos y posiblemente el de código a la nueva imagen de
memoria
Si código compartido se lo salta: lseek (fd,..) Posible envío LSEEK al GF
sino, se leo del disco: rw_seg (R, fd, T,..) Posible envío READ(s) al GF
Leo segmento datos: rw_seg (R, fd, D,..) Envío READ(s) al GF
Cierra el fichero: close (fd) Envía CLOSE al GF
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 23
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
El siguiente paso es comprobar si se puede compartir el código. Para ello el GP se recorre la Tabla
de Procesos buscando un proceso cuyos campos “mp_ino, mp_dev y mp_ctime” indiquen que el
fichero ejecutable es el mismo que el que se está intentando cargar en memoria, además de tener
espacios I+D separados. Si lo encuentra, guarda en una variable (“sh_pm”) esta situación (la cual
refleja I+D separados y código compartido).
La función “new_mem()” comprueba si hay suficiente memoria para la nueva imagen. Si se
comparte código (e I+D separados, “sh_pm”) se busca un hueco suficiente para el segmento de
datos y pila, sino el hueco ha de ser suficientemente grande para incluir además el código.
Si la imagen entrante tuviera I+D separados pero no pudiera compartir código con ningún otro
proceso, por ser dicho código el primero en entrar en memoria, sería factible y en principio
deseable, el solicitar dos huecos de memoria independientes para los dos segmentos del proceso.
Esto supondría una mejora sobre la implementación de Minix ya que actualmente no lo hace así.
Si hubo suficiente memoria (éxito en alloc_mem) se libera la memoria de la imagen saliente (no se
liberaría el segmento de código si este estuviera siendo compartido por algún otro proceso) y se
asigna memoria para la imagen entrante. Se actualiza el mapa de memoria (campo “mp_seg”) y se
informa al kernel con “sys_newmap()”. Finalmente “new_mem()” encarga a la TS la inicialización a
0’s las áreas bss, gap y pila mediante la llamada al kernel “sys_memset()”.
A continuación se parchean las direcciones de los punteros en la pila tal y como ya se ha visto
mediante el procedimiento “patch_ptr()” y se manda a la TS que copie la pila parcheada, desde el
espacio del GP al espacio de usuario de la nueva imagen, con la función “sys_datacopy()”.
Finalmente, se leen y se llevan los segmentos de código y datos de disco a memoria (el segmento
de código sólo se lee y lleva si el código no es compartido [variable “sh_pm”]).
23
Gestión de memoria Minix 3
4.5 La llamada al sistema execve (5/5)
- Comprueba y maneja los bits “setuid” y “setgid”
Si el bit SET_UID del fichero está activo:
usr_efectivo proceso = uid del fichero: tell_fs (SETUID,..); Envía SETUID al GF
Si el bit SET_GID del fichero está activo:
grupo_efectivo proceso = gid del fichero: tell_fs (SETGID,..); Envía SETGID al GF
- Actualiza adecuadamente los campos de la entrada de la TP del proceso
Resetea las señales capturadas. Tratamiento a SIG_DFL. Desenmascara todas las señales.
Establece flag de I+D acorde con el del fichero.
Informa al GF de execve: tell_fs (EXEC,..); Envía EXEC al GF
Establece el campo “mp_name” al nombre del fichero ejecutado (para depuracion, ps, salida ,
etc.)
- Informa al kernel de que el proceso es ejecutable
sys_exec (who, new_sp, name, pc); Envía SYS_EXEC a la TS
- Causa la señal SIGTRAP si el proceso estuviese siendo trazado.
if (TRACED) check_sig (rmp->mp_pid, SIGTRAP);
- No se responde:
return (SUSPEND);
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 24
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
La función “rw_seg()” en vez de leer y copiar a espacio de usuario el fichero bloque a bloque,
utiliza un truco para permitir al GF cargar de una sola vez el segmento al espacio de usuario. La
llamada es decodificada por el GF de una forma ligeramente especial. Hace parecer que la
lectura es de todo el segmento por parte del proceso de usuario mismo. Sólo unas pocas líneas
del comienzo de la rutina de lectura del GF son precisas para tratar este aspecto “extraño”. Con
esto se consigue una apreciable incremento en la velocidad de carga del(de los) segmento(s) .
Por último señalar, que si la llamada ha tenido éxito no se puede responder al proceso que la
invocó. La forma en que el proceso reanuda la ejecución es atípica, no es como respuesta a la
primitiva “send_rec()” que se empleó en la llamada, sino que la TS al recibir el mensaje
SYS_EXEC se encarga de retirar al proceso de la cola de procesos que esperan recibir un
mensaje y ponerlo en la cola de planificación, con el contador de programa inicializado a la
primera instrucción del programa.
24
Gestión de memoria Minix 3
4.6 La llamada al Sistema brk
Proceso de
Usuario
Posible respuesta ▪Restaura los antiguos
con error o no de- valores de los segmentos
pendiendo de la ▪Responde error
comprobación
anterior ▪¿ Caben los segmentos
de datos y pila en el Responde
espacio de usuario ? OK
Gestor de
Procesos
▪Comprueba que el nuevo ------- adjust -------
valor es distinto del SYS_GETINFO ▪Comprueba si el tamaño de
SYS_NEWMAP
anterior y si lo es, que la pila quedaría negativo. ----SI ---
no es inferior al comienzo ▪Calcula el tamaño del gap. Informa a la TS
del segmento de datos. ▪Añade un margen de segu- del nuevo mapa
▪Pide a la TS el valor del ridad para crecimiento pila. de memoria
puntero de pila ▪Actualiza tamaño segmento
Tarea del de datos y tamaño y origen
del segmento de pila.
Sistema
▪Devuelve entre otras
cosas el puntero de pila
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 25
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
Los procedimientos de librería “brk y sbrk” se utilizan para ajustar el límite superior del segmento
de datos. El primero tiene como parámetro la dirección del límite superior del segmento de datos
y la segunda un incremento o decremento del tamaño actual del segmento de datos. Esta última
en realidad lo único que hace es calcular el nuevo tamaño del segmento de datos y llamar a “brk”.
El modelo básico de memoria de Minix es bastante simple, se asigna un espacio contiguo de
memoria para los segmentos de datos y pila al crearse el proceso. Este espacio (segmento intel)
no puede reubicarse, crecer, ni disminuir. Lo único posible es que el segmento de datos aumente
su límite superior a costa del gap desde abajo, o que el segmento de pila crezca hacia abajo a
costa del gap por arriba. Con estas limitaciones la implementación es sencilla. Consiste en
comprobar que los cambios encajan en el espacio de direcciones, ajustar las tablas e informar al
“kernel”.
La parte principal de esta llamada la hace el procedimiento “adjust”, el cual comprueba que los
segmentos de datos y pila no colisionen. Si lo hicieran la llamada fallaría. Al hacer esta
comprobación se añade al segmento de datos unos bytes de margen de seguridad
(SAFETY_BYTES), tal que la decisión de que la pila ha crecido mucho pueda hacerse con
todavía suficiente espacio en la pila (margen de seguridad).
La base del segmento de datos es constante, por tanto sólo se ajusta su tamaño. La pila crece
hacia abajo, si “adjust” descubre que el puntero de pila se ha situado más abajo del origen del
segmento de pila, entonces se cambia tanto el origen como el tamaño.
25
Gestión de memoria Minix 3
4.7 Tratamiento de señales
La estructura “sigaction”:
struct sigaction {
__sighandler_t sa_handler; /* SIG_DFL, SIG_IGN, SIG_MESS, o puntero a función */
sigset_t sa_mask; /* señales a bloquear durante el manejador */
int sa_flags; /* flags especiales */
}
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 26
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
Las señales constituyen una forma de enviar información a un proceso que no la está esperando.
Existe un conjunto definido de señales. Cada señal tiene una acción por defecto, bien matar al
proceso que la recibe o bien ser ignorada, sin embargo, los procesos puede usar llamadas al
Sistema para alterar estas acciones. Un proceso puede hacer que una señal sea ignorada o bien
hacer que se ejecute un tratamiento cuando se reciba, exceptuando en ambos casos la señal
especial “kill”. Así, para el programador hay dos momentos distintos en el que el S.O. trata con
las señales. Una fase de preparación, en la que un proceso modifica la respuesta a una futura
señal, y una fase de respuesta, en la que la señal es generada y se actúa en consecuencia. La
acción puede ser la ejecución de un manejador de señal personalizado. Existe una tercera fase.
Cuando el manejador acaba, una llamada al Sistema especial restaura la operación normal del
proceso “señalado”. El programador no necesita saber de esta fase. El escribe el tratamiento de
señal como una función más. El S.O. se ocupa de los detalles de invocar y terminar el manejador
y gestionar la pila.
Hay varias llamadas al Sistema que en la fase de preparación puede modificar la acción por
defecto de una señal. La más general es “sigaction()” que puede especificar que se ignore,
capture (especificando una función manejadora) o restaure la acción por defecto de una señal.
Otra llamada, “sigprocmask()”, puede bloquear una señal, haciendo que se encole y que actúe
sólo cuando el proceso la desbloquee, en un momento posterior. La fase de preparación se hace
desde el GP. Cada proceso tiene varios “bitmaps”, uno de ellos define las señales a ser
ignoradas, otro las capturadas, etc. También dispone de una tabla de estructuras “sigaction”, de
tantos elementos como posibles señales. Con este campo se puede saber para cada señal la
función de tratamiento (“sa_handler”), las señales bloqueadas durante la ejecución del
tratamiento (“sa_mask”). El valor de “sa_handler” además del de la dirección de una función
puede ser SIG_DFL (tratamiento por defecto) y SIG_IGN (ignorar). Los procesos del Sistema
usan el nuevo tipo de manejador “SIG_MESS”, que le dice al GP que redirija una señal mediante
un mensaje de notificación SYS_SIG. No se requiere la fase tres de restauración para las señales
tipo SYG_MESS.
26
Gestión de memoria Minix 3
4.7 Tratamiento de señales
Señal Descripción Generada por
SIGHUP “Hangup” Llamada al Sistema kill
SIGINT Interrupción TTY
SIGQUIT Abandonar (Quit) TTY
SIGILL Instrución ilegal Kernel (*)
SIGTRAP “Trace trap” Kernel (M)
SIGABRT Terminación anormal TTY
SIGFPE Excepción punto flotante Kernel (*)
SIGKILL “kill” (no puede ser capturada ni ignorada) Llamada al Sistema kill
SIGSEGV Violación de segmento Kernel (*)
SIGPIPE Escritura en un pipe sin nadie para leerlo Gestor de Ficheros
SIGALRM Alarma vencida Gestor de Procesos
SIGTERM Terminación por software, kill() Llamada al Sistema kill
SIGCHILD Proceso hijo finalizado o detenido Gestor de Procesos
SIGKMESS Mensaje del Kernel Kernel
SIGKSIG Señal pendiente del Kernel Kernel
SIGKSTOP Kernel apagando (shutting down) Kernel
(*) Dependientes del Hardware. (M) De Minix, no de Posix.
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 27
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
Cuando se genera una señal intervienen varias partes de Minix. La respuesta empieza en el GP, el
cual sabe qué procesos deben recibir la señal gracias a las estructuras ya mencionadas. Si la señal
estuviera capturada, el GP se la envía al proceso destinatario. Esto requiere salvar el estado del
proceso (para que pueda continuar su ejecución donde se quedó). Esta información la salva en la
pila del proceso “señalado”, comprobando previamente si hay espacio en ella, y encargándoselo a
la TS. La TS, además, manipula el contador de programa para que apunte al comienzo del
manejador de la señal. Cuando el manejador acaba se efectúa una llamada al sistema “sigreturn”.
Mediante esta llamada, el GP y el “kernel” participan en la restauración del contexto del proceso
“señalado” para que pueda retornar a su ejecución. Si la señal no estuviera capturada se ejecuta la
acción por defecto. El GP puede tener que repetir más de una vez estas acciones, ya que una señal
puede tener que ser distribuida a un grupo de procesos.
Minix define todas las señales obligatorias de Pósix estándar, aunque no todas las opcionales.
Tradicionalmente las señales pueden generarse de dos modos: por la llamada al Sistema “kill”, y
por el “kernel”. Las señales “sigint y sigquit” pueden originarse por la pulsación de teclas especiales
del teclado. “sigalrm” es manejada por el GP. “sigpipe” por el GF. El programa “kill” puede enviar
cualquier señal a cualquier proceso. Algunas señales dependen del hardware, por ejemplo el 8086
no soporta la detección de “instrucción ilegal”.
Con independencia del origen de la señal, el GP procesa todas las señales del mismo modo. Para
cada proceso a ser “señalado” se efectúa una serie de comprobaciones. Un proceso puede enviar
una señal a otro si es el superusuario, o si su usuario real o efectivo es igual al usuario real o
efectivo del destinatario. Algunas condiciones impiden el envío. a) El destinatario no puede ser
“zombie”, b) la señal no debe estar ignorada ni bloqueada (no es lo mismo estar bloqueada que
ignorada, las señales bloqueadas se envían si/cuando finaliza la situación de bloqueo), y por último
c) si el espacio de pila es insuficiente, el proceso “señalado” es eliminado (“killed”).
Si se cumplen todas las condiciones, la señal se envía. Si la señal no está capturada no se envía
información al proceso. En ese caso el GP mata al proceso (o ignora la señal si esa fuera su acción
por defecto). Algunas señales no están soportadas en Minix, pero si definidas aunque ignoradas, tal
y como permite el estándar Posix. Estas son: SIGUSR1, SIGUSR2, SIGCONT, SIGSTOP, SIGSTP,
SIGTTIN y SIGTTOU.
27
Gestión de memoria Minix 3
4.7 Tratamiento de señales
Tabla Procesos
Marco de pila
Capturar una señal significa ejecutar una función de tratamiento personalizada para dicha señal
(manejador de la señal). La dirección de este manejador está guardada en la estructura
“sigaction” de la Tabla de Procesos (TP). Modificando el marco de pila (“stackframe” guardado en
la TP) de un proceso “señalado” se consigue que la próxima vez que se le permita ejecutar, se
ejecute el manejador. Modificando la pila del proceso, se consigue que cuando el manejador
acabe, se ejecute la llamada al Sistema “sigreturn” . Esta llamada no la programa el usuario; se
ejecuta después de que el “kernel” haya puesto su dirección en la pila, de tal modo que sea ésta
la dirección de retorno que se saca de la pila cuando termina el manejador. “Sigreturn” restaura el
“stackframe” original del proceso “señalado”, para que éste pueda volver a ejecutarse desde el
punto donde fue interrumpido por la señal.
En (a) se muestra una vista simplificada de la pila y parte de su descriptor de proceso, justo
después de haber sido expulsado por una interrupción. En el momento de la expulsión los
registros de la CPU se copian a la estructura “stackframe” de la TP del kernel del proceso
expulsado. Esta sería la situación en el momento en que la señal es generada. Las señales son
generadas por un proceso diferente al destinatario, por ello este último no puede estar
ejecutándose en ese momento. En la preparación del manejo de la señal, la TS copia el
“stackframe” de su TP en la pila del receptor como una estructura “sigcontext”, preser-vandola,
después se apila una estructura “sigframe”, con información que usará “sigreturn” cuando acabe
el manejador. También apila la dirección Ret1 de la función que ejecutará a la propia “sigreturn” y
otra dirección de retorno, Ret2, que es donde debe volver el programa interrumpido, aunque esta
última no se usa en la ejecución normal. El contador de programa (CP) del “stackframe” de la TP
se modifica para hacer que sea el manejador el que se ejecute cuando el proceso “señalado”
vuelva a ejecución. En (b) se muestra la situación después de la preparación y con el manejador
ejecutándose. (C) muestra la situación con “sigreturn” ejecutándose. El resto de la estructura
“sigframe” ahora son sus variables locales. “Sigreturn” acaba como cualquier otra llamada al
Sistema, permitiendo que el planificador seleccione el siguiente proceso. Eventualmente el
proceso “señalado” será planificado y retomará el control a partir de esta dirección (Ret2). La
misión de “sigreturn” es dejar las cosas como estaban. El “stackframe” de la TP es restaurado a
su estado original, usando la copia de la pila. (d) muestra la situación final, con el proceso
esperando volver a su ejecución en el punto o estado en el que estaba cuando se interrumpió.
28
Gestión de memoria Minix 3
4.7 Tratamiento de señales.
sigaction (int sig, const struct sigaction *act, struc sigaction *oact)
int do_sigaction() {
Proceso de
Usuario Si (sig = SIGKILL) return (OK)
Si (sig <1 o sig > NSIG) return (EINVAL)
Si (oact != NULL)
Pide a la TS que copie la estructura “sigaction” de la
send_rec: SIGACTION TP a “oact” ….. sys_datacopy (…).
Si (act = NULL) return (OK)
Pide a la TS que copie de “act” a la estructura “sigaction”
Gestor de
Procesos de la TP .….. sys_datacopy (…).
Si (act->sa_handler = SIG_IGN)
sigaddset (mp_ignore)
send_rec: SYS_VIRCOPY sigdelset (mp_sigpending, mp_catch, mp_sig2mess)
Una o dos veces sino si (act->sa_handler = SIG_DFL)
sigdelset (mp_ignore, mp_catch, mp_sig2mess)
sino si act->sa_handler= SIG_MESS)
Tarea del
Sistema si (! (mp_flags & PRIV_PROC)) return (EPERM)
sigaddset (mp_catch..)
sigdelset (mp_ignore, mp_sig2mess)
sino
sigaddset (mp_catch)
sigdelset (mP-ignore, mp_sig2mess)
}
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 29
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.
La llamada “sigaction” soporta ambas funciones: sigaction() y signal(), las cuales le permiten a un
proceso alterar su respuesta a las señales. “Sigaction()” es la requerida por POSIX y en general se la
prefiere para la mayoría de los propósitos, aunque “signal()” es “C” estándar y se necesita para
portabilidad con sistemas NO-POSIX. El código comienza con una validación: Que el número de señal
sea válido y que no se intente alterar la respuesta a la señal “sigkill”. A continuación se invoca a la tarea
de sistema para que copie los atributos a la estructura “oact”, si el puntero a ésta no fuera NULL, además
si el puntero a “act” no fuera NULL, se coparian los atributos de esta estructura al espacio del GP. Más
adelante se modifican los mapas de bits mp_catch, mp_ignore y mp_sigpending, de acuerdo a la que la
nueva acción sea ignorar, capturar o restaurar el tratamiento por defecto de la señal. El campo
“sa_handler” de la estructura “sigaction” contiene el puntero a la función que se ejecutará si señal es
capturada, o bien alguno de los valores especiales SIG_IGN o SIG_DFL, definidos en POSIX. También
es posible el valor SIG_MESS que es especifico de Minix. Se utiliza para procesos del sistema. Estos
procesos están normalmente bloqueados a la espera de un mensaje, de este modo, el método normal
por el cual el GP le pide al kernel que ponga un marco de señal en la pila del receptor, será demorado
hasta que un mensaje despierte al receptor. El código SIG_MESS le dice al GP que emita un mensaje de
notificación, el cual es prioritario sobre un mensaje normal. Este mensaje de notificación contiene como
argumento un conjunto de señales pendientes, permitiendose con ello que se pasen multiples señales en
un solo mensaje.
Finalmente, los otros campos de la TP del GP relacionados con señales son rellenados. Para cada
posible señal hay un “bitmap”, sa_mask, en el que se definen que señales deben bloquearse mientras el
manejador de esa señal se esté ejecutando. También hay para cada señal un puntero, sa_handler. Éste
puede contener la dirección de una función de tratamiento, o valores especiales para indicar si la señal
será ignorada, tendrá tratamiento por defecto oo se será usada para generar un mensaje. La dirección
de la rutina de librería que invoca sigreturn cuando termina el manejador es almacenada en
mp_sigreturn. Esta durección es uno de los campos del mensaje recivido por el GP.
29