Vous êtes sur la page 1sur 29

Gestión de memoria Minix 3

• 4 Gestión de memoria y procesos de usuario


4.1 Organización de la memoria (Tanenbaum 4.7.1)
4.2 Tratamiento de mensajes (Tanenbaum 4.7.2)
4.3 Estructuras de datos y algoritmos del Gestor de Procesos.
(Tanenbaum 4.7.3)

4.4 Las llamadas al sistema fork, exit, y wait (Tanenbaum 4.7.4)


4.5 La llamada al sistema execve (Tanenbaum 4.7.5)
4.6 La llamada al sistema brk (Tanenbaum 4.7.6)
4.7 Tratamiento de señales (Tanenbaum 4.7.7)
4.8 Otras llamadas al sistema (Tanenbaum 4.7.8)

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

•No utiliza paginación


Modo usuario •Posible activación del uso de Intercambio
•Comunicación por paso de mensajes
Gestor de •Implementa la estrategia de asignación de memoria:
Procesos Qué procesos residen en memoria y dónde
•Dos funciones básicas:
• Gestión de laTabla de procesos y de la lista de huecos
• Implementación de las llamadas al sistema:
fork, wait, execve, exit, brk, getpid, signal, kill,
alarm, pause, getuid, getgid, setuid, setgid…

Modo supervisor

Núcleo •Implementa el mecanismo de asignación de memoria:


de Minix Copia de mapas de memoria

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.

La gestión de memoria en Minix 3 es simple. En primer lugar, no se utiliza paginación en


absoluto y en segundo lugar, aunque el código fuente completo incluye la posibilidad del
uso de intercambio de memoria (“swapping”), por simplicidad, no se estudiará esta opción,
ya que en la actualidad, la cantidad de memoria disponible en los sistemas, hace que en la
práctica, ésta sea raramente necesaria.
El Gestor de Procesos (“Process Manager”) es el proceso de Minix que se encarga de
gestionar la memoria así como de manejar las llamadas al sistema relacionadas con la
gestión de procesos. Algunas como “fork, exec y brk” muy relacionadas con la gestión de
memoria, otras relacionadas con señales, y otras con características de los procesos,
como el usuario y grupo propietario, tiempos de uso, etc. En cuanto a las estructuras de
datos gestionadas, destacan la Tabla de Procesos, con sus campos específicos y la Lista
de Huecos, la cual mantiene los huecos de memoria libres, ordenados ascendentemente
por dirección de memoria. Sin el uso de Intercambio, la posición en memoria de un
proceso no cambia durante toda su ejecución y tampoco aumenta o disminuye su
asignación de espacio. Esta estrategia, aunque discutible, obedece principalmente a tres
factores: 1) El deseo de mantener el sistema sencillo, 2) La arquitectura original del IMB
PC (Intel 8088) y 3) Conseguir una fácil portabilidad a otros sistemas con distinto
hardware.
Hay que señalar un aspecto que diferencia a Minix de muchos otros sistemas operativos.
El GP no es parte del núcleo (kernel), si no que es un proceso que corre en el espacio de
usuario y se comunica con el núcleo mediante el mecanismo estándar del paso de
mensajes. Esto permite separar la estrategia del mecanismo de asignación de memoria.
La decisión de qué proceso y dónde se ubicará en memoria (estrategia) es
responsabilidad del GP. El establecimiento de los mapas de memoria (mecanismo) lo lleva
a cabo la Tarea del Sistema, la cual forma parte del núcleo.

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

• Bit de I+D juntos o separados


Pila • Tamaños de memoria:
Prog. A gap Total, Código, Datos, y BSS
I+D juntos Segmento (El tamaño de memoria total en I+D juntos
Datos+bss comprende la cantidad de memoria total
necesaria para el proceso. En I+D separados,
Código
éste, no incluye el tamaño del código)
Minix

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 →

Hijo de A Prog. C Mapas de memoria: (I+D juntos)


Prog. B Prog. B (a) Originalmente,
Prog. B
(b) Después de fork,
(c) Después de execv del hijo.
Prog. A Prog. A Prog. A
Minix Minix Minix

(a) (b) (c)

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)

/* -------------------------------- Tabla de procesos ---------------------------------------


Esta tabla tiene una entrada por proceso. Contiene toda la información de gestión de
procesos para cada proceso. Entre otras cosas, define los segmentos de código, datos y pila.
Valores uids y gids, y varios flags.
*/
EXTERN struct mproc {
struct mem_map mp_seg[NR_LOCAL_SEGS]; /* mapa del código, datos y pila */
char mp_exitstatus; /* guarda status cuando el proceso acaba */
char mp_sigstatus; /* guarda nº señal de procesos matados */
pid_t mp_pid; /* identificador de proceso */
pid_t mp_procgrp; /* pid de grupo de procesos (usado para señales)*/
pid_t mp_wpid; /* pid del proceso que se está esperando */
int mp_parent; /* indice del proceso padre */
/* Tiempos de usuario y sistema de los hijos. Contabilizado en ‘exit’ del hijo. */
clock_t mp_child_utime; /* tiempo de usuario acumulado de los hijos */
clock_t mp_child_stime; /* tiempo de sistema acumulado de los hijos */
/* uids y gids reales y efectivos. */
uid_t mp_realuid; /* uid real del proceso */
uid_t mp_effuid; /* uid efectivo del proceso */
gid_t mp_realgid; /* gid real del proceso */
gid_t mp_effgid; /* gid efectivo del proceso */
/* Identificacion de fichero para compartir. */
ino_t mp_ino; /* nº de inodo de fichero */
dev_t mp_dev; /* nº de dispositivo del sistema de ficheros */
time_t mp_ctime; /* cambio de tiempo en inodo */
. . .
/* Continua. Sig. Transp…*/

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)

/* Continuación anterior transparencia */


. . .
/* Información de manejo de señales. */
sigset_t mp_ignore; /* 1 significa ignorar la señal, 0 no */
sigset_t mp_catch; /* 1 significa capturar la señal, 0 no */
sigset_t mp_sig2mess; /* 1 significa transformar en mensaje de notificación */
sigset_t mp_sigmask; /* señales a bloquear */
sigset_t mp_sigmask2; /* copia segura de ‘mp_sigmask’ */
sigset_t mp_sigpending; /* señales pendientes de manejar */
struct sigaction mp_sigact[_NSIG + 1]; /* como en ‘sigaction(2)’ */
vir_bytes mp_sigreturn; /* dirección de la librería C funcion ‘__sigreturn’ */
struct timer mp_timer; /* temporizador ‘watchdog’ para ‘alarm(2)’ */
/* compatibilidad con anteriores versiones para señales. */
sighandler_t mp_func; /* todas las señales vectorizadas a una única func. de usuario */
unsigned mp_flags; /* flag bits */
vir_bytes mp_procargs; /* puntero a argumentos en pila iniciales del proceso */
struct mproc *mp_swapq; /* cola de procesos esperando intercambio */
message mp_reply; /* mensaje de respuesta para enviar */
/* Prioridad de planificación. */
signed int mp_nice; /* ‘nice’ es PRIO_MIN..PRIO_MAX, estándard 0. */
char mp_name[PROC_NAME_LEN]; /* nombre de proceso */
} mproc[NR_PROCS];

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.

El vector “mp_seg” permite traducir las referencias de direcciones virtuales a direcciones


físicas. Dada una dirección virtual y el proceso al que pertenece, es sencillo verificar si se
trata de una dirección que cae dentro del mapa del proceso, y en ese caso traducirla a
dirección física de memoria. Más adelante se muestra una transparencia con un ejemplo
de uso de este vector.
La tabla “mproc” mantiene también otra información necesaria para la gestión de lo
procesos. Esta incluye identificadores de proceso, los uid’s y gid’s (identificadores de
usuario y grupo, reales y efectivos), información sobre las señales, y el estado de
finalización si el proceso quedara en estado “zombie” (cuando muere y su padre no está
esperando por él). Hay campos para un temporizador para “sigalarm” y campos para
contabilizar los tiempos de usuario y sistema empleados por los procesos hijos. (en
anteriores versiones de Minix esto último era responsabilidad del kernel).
La mayoría de los campos están descritos adecuadamente en su comentario. Algunos
campos tratan sobre el manejo de señales, “mp_ignore, mp_catch, mp_sig2mess,
mp_sigmask, mp_sigmask2, y mp_sigpending” son mapas de bits (bitmaps), en los que
cada bit representa una de las señales. Actualmente hay 22 señales definidas, aunque
algunas no están soportadas, según permite el estándar POSIX. La señal 1 se
corresponde con el bit menos significativo. En cualquier caso, POSIX requiere funciones
estándar para añadir o quitar miembros del conjunto de señales representado en estos
“bitmaps”, tal que su manipulación sea transparente en sus detalles al programador. El
array “mp_sigact” es importante para el manejo de señales, tiene un elemento para cada
tipo de señal del tipo “estructura sigaction”.
El campo “mp_flags” se usa para guardar un conjunto variado de bits. El campo es un
entero sin signo, de 16 bits en un procesador de baja capacidad y de 32 procesadores
superiores.

8
Gestión de memoria Minix 3
4.3 Estructuras de datos y algoritmos del Gestor de Procesos (3)

Tabla de procesos Lista de huecos


Gestor de
Procesos

Dirección virtual, física y


tamaño de cada segmento
Proceso “i”
interno, medido todo ello
campo en “clics”.
‘mp_seg’ (1 clic = 1024 bytes)
(datos en hexadecimal)

210 KB (0x34800) Virtual Física Long. Virtual Física Long.


Pila Código 0x00 0xC8 0x00 Código 0x00 0xC8 0x03
208 KB (0x34000) Datos 0x00 0xC8 0x07 Datos 0x00 0xCB 0x04
207 KB (0x33C00) Pila 0x08 0xD0 0x02 Pila 0x05 0xD0 0x02
Datos I+D juntos I+D separados
203 KB (0x32C00)
Código Respecto a dir. fisica de Respecto a la dir. fisica
200 KB (0x32000) datos: D0 – C8 = 08 de datos: D0 - CB = 05

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

Lista de huecos del GP


Prog. A

Dirección base Puntero al siguiente Minix


Tamaño del hueco
(en clics) ‘h_base’ (en clics) ‘h_len’ ‘h_next’

#define NR_PROCS 100


#define NR_HOLES (2*NR_PROCS+4) /* max elementos en tabla ‘hole’*/
#define NIL_HOLE (struct hole *) 0
PRIVATE struct hole {
Declaracion en ‘C’ de
struct hole *h_next; /* puntero al siguiente */
la estructura de datos phys_clicks h_base; /* comienzo del hueco */
para la lista de huecos phys_clicks h_len; /* longitud del hueco */
} hole [NR_HOLES];
PRIVATE struct hole *hole_head; /* puntero al primer hueco */
PRIVATE struct hole *free_slots; /* primera posición sin usar */

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.

Las principales operaciones en la lista de huecos son: a) Reservar una cantidad de


memoria y b) Devolver una cantidad previamente asignada. Para reservar memoria, se
busca en la lista -que se halla ordenada ascendentemente por direcciones de memoria-,
desde el principio en adelante, un hueco que satisfaga la necesidades de memoria. Una
vez seleccionado el hueco, si el tamaño no fuese exacto, se reduciría el tamaño del mismo
en la cantidad solicitada y el hueco permanecería en la lista con el nuevo tamaño residual.
Si el tamaño fuese exacto, el hueco desaparecería de la lista. Este esquema es rápido y
simple pero sufre tanto de fragmentación interna (hasta 1023 bytes de posible desperdicio)
como de fragmentación externa.
Cuando un proceso finaliza, si se han usado espacios I+D juntos, su memoria se libera y
se devuelve a la lista de huecos como un todo. Si los espacios son separados, el
segmento de datos se devuelve a la lista de huecos, pero la liberación del segmento de
código dependerá de que no se encuentre otro proceso que lo esté compartiendo. Si se
encontrara, éste no se liberaría. Los segmentos de código y datos no tienen porqué ser
contiguos, y sería posible por tanto devolver dos regiones de memoria. Para cada región
devuelta se comprueba si en su vecindad hay algún hueco, y si así fuera, se fusionarían
dicho hueco o huecos con la región devuelta, quedando en la lista un único hueco de
tamaño suma de la región o regiones vecinas y la propia región devuelta. De este modo
nunca hay huecos adyacentes en la lista.
En la transparencia se muestra un ejemplo de reserva y liberación de espacio de memoria.
El tamaño del clic, como ya se ha dicho, es de 1024 bytes. Minix ocupa 180 KB, Los
programas ‘A’, ‘B’ y ‘C’ ocupan 40 KB, 50 KB y 100 KB respectivamente, aunque esta
información se puede obtener fácilmente desde la figura.

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;

pm_init(); /* Inicializa tablas del Gestor de Procesos */


/* Este el el bucle principal : obtiene trabajo y lo hace, indefinidamente */
while (TRUE) {
get_work(); /* Espera mensaje para el Gestor de Procesos */
/* Comprueba notificaciones del Sistema primero. Casos especiales */
if (call_nr == SYN_ALARM) {
pm_expire_timers(m_in.NOTIFY_TIMESTAMP);
result = SUSPEND; /* no responder */
} else if (call_nr == SYS_SIG) { /* señales pendientes */
sigset = m_in.NOTIFY_ARG;
if (sigismember(&sigset, SIGKSIG)) (void) ksig_pending();
result = SUSPEND; /* no responder */
} /* Sino, si el nº de llamada al Sistema es válido, la lleva a cabo */
else if ((unsigned) call_nr >= NCALLS) {
result = ENOSYS; /* Nº llamada erróneo */
} else {
result = (*call_vec[call_nr])(); /* LLama a la función “do_...” */
}
/* Envía respuesta de finalización al usuario */
if (result != SUSPEND) setreply(who_p, result);

/* 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.

El Gestor de Procesos se compila y monta de forma independiente del


“kernel” y del Gestor de Ficheros, por tanto tiene su propia función “main”
que comienza después de que el kernel haya finalizado su inicialización.
Después de efectuar su propia inicialización, mediante “pm_init()”, se
entra en un bucle, en el cual se llama a “get_work()”, para esperar algún
mensaje entrante. Entonces se llama a una de las funciones do_xxx(), por
medio de la tabla de punteros a función “call_vec” para llevar a cabo la
petición. Finalmente se envía una respuesta, si se requiere.
Este escenario está algo simplificado. Mensajes de notificación pueden
ser enviados a cualquier proceso. Estos están identificados por valores
especiales en el campo “call_nr”. En el código puede verse la
comprobación de estos mensajes y la acción especial que se toma en su
caso. También se comprueba que “call_nr” tenga un valor correcto, ya que
aunque un valor incorrecto es improbable, la comprobación no es costosa
y se evita un error grave.
Finalmente, aunque el comentario de la línea donde aparece “setreply”
dice que se envía la respuesta de finalización, esto es algo más
complicado. La función “setreply()” construye una respuesta en la entrada
de la Tabla de Procesos (TP) del proceso actual. Más adelante (siguiente
transparencia) hay un bucle donde se recorre la TP y se comprueba si hay
respuestas (mensajes) pendientes de enviar y se envían, omitiéndose
aquellas que no pueden enviarse en ese momento.

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)

PRIVATE void get_work() /* Espera el siguiente mensaje y extrae información útil de él */


{
if (receive(ANY, &m_in) != OK) panic(__FILE__,"PM receive error", NO_NUM);
who_e = m_in.m_source; /* quién envió el mensaje */
if(pm_isokendpt(who_e, &who_p) != OK)
panic(__FILE__, "PM got message from invalid endpoint", who_e);
call_nr = m_in.m_type; /* Nº de llamada al Sistema */

/* ‘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) */

mp = &mproc[who_p < 0 ? PM_PROC_NR : who_p]; /* mp es el puntero al ‘slot’ */


if(who_p >= 0 && mp->mp_endpoint != who_e) {
panic(__FILE__, "PM endpoint number out of sync with source",
mp->mp_endpoint);
} /*end-if*/
}

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.

Los procedimientos “get_work” y “setreply” manejan de hecho, la recepción y envío de


mensajes respectivamente. El primero efectúa un pequeño truco para hacer parecer que
un mensaje del kernel lo era en realidad del propio Gestor de Procesos, ya que el kernel
no tiene una entrada (slot) propia en la tabla de procesos. La otra función no envía en
realidad la respuesta, simplemente la prepara, para que pueda ser enviada más tarde,
tal y como ya se ha indicado.

15
Gestión de memoria Minix 3
Las llamadas al sistema (Bucle principal GP. 4/4)

PUBLIC void setreply(proc_nr, result)


int proc_nr; /* Proceso a responder */
int result; /* Resultado de la llamada (normalmente OK, o nº error) */
{
/* Cumplimenta un mensaje de respuesta para ser enviado más tarde a un proceso de usuario.
Las llamadas al Sistema pueden ocasionalmente rellenar otros campos, esto es únicamente
para retornar el valor al bucle “main” y también establecer el flag “must send reply”.
*/
register struct mproc *rmp = &mproc[proc_nr];

if(proc_nr < 0 || proc_nr >= NR_PROCS)


panic(__FILE__,"setreply arg out of range", proc_nr);

rmp->mp_reply.reply_res = result;
rmp->mp_flags |= REPLY; /* Respuesta pendiente */

if (rmp->mp_flags & ONSWAP)


swap_inqueue(rmp); /* Se debe intercambiar el proceso, traer de vuelta a memoria */
}

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

Tell_fs (EXIT) ▪Libera memoria del


SYS_SETALARM

SYS_EXIT
SYS_NICE

SI:Desactivar proceso. Segmento NO : ------------


de Datos + Pila y ▪Estado=zombie.
segmento de Código ▪Envía SIGCHLD
Tarea del si no está más en uso “sig_proc()”
Sistema ▪Guarda en entrada
de TP “exit_status”
Gestor de ▪Desactiva ▪Detiene el
Ficheros alarma proceso

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º 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.

--do_waitpid-- ----SI -----


----SI-----
▪Recorre la TP ----SI ----- return(0)
▪cleanup;
y para cada ¿ Opción
---SI--- ▪return
entrada: “WNOHANG” ?
¿ Está (SUSPEND)
¿ Es un proceso
zombie ?
hijo ?
Gestor de
Procesos

estado=WAITING Return
¿ Está ¿ Tiene return(SUSPEND) (ECHILD)
----SI ----- algún hijo ?
stopped ? return(pid)

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º 19
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.

A la Izquierda, situación antes de que


salga el proceso 12.

A la derecha un poco antes de salir.


La función clean_up limpiaría el
proceso zombie 53, y éste desapa-
recería como hijo de init. El proceso
52 seguiría adoptado por init.

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

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º 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

Las tres fases del tratamiento con señales:


Preparación: El código del programa se prepara para una posible señal.
Respuesta: La señal se recibe y se toma un acción.
Restauración: Se restaura la operación normal del proceso.

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

Dir. Retorno Dir. Retorno Dir. Retorno Dir. Retorno


Vars. locales Vars. locales Vars. locales Vars. locales
del proceso del proceso del proceso del proceso
Regs CPU Regs. CPU
originales Originales
Pila

Dir Ret 2 Dir. Ret 2


Estructura Vars. Locales
Sigframe Sigreturn
Dir. Ret 1
Vars. Locales
Manejador

Tabla Procesos
Marco de pila

Registros Regs. CPU Regs. CPU Registros


CPU modificados Modificados CPU
(originales) CP=manejador CP=manejador originales
Antes Manejador eje- Sigreturn eje- Vuelta a normal
(a) cutandose (b) cutandose (c) (d)
Aníbal Ramírez García - Sistemas Operativos II - Departamento de Informática Aplicada - Capítulo IV. Pag. nº 28
Escuela Universitaria de Informática - Universidad Politécnica de Madrid.

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

Vous aimerez peut-être aussi