laboratorio de ampliaciÓn de sistemas operativos …cevp/aso/fichs/guiones...se usa para realizar...

24
LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS MINIX 2.0.0 SOBRE VIRTUALBOX (tarea 2) (REV. 1, 9 DE MARZO DE 2010)

Upload: others

Post on 26-Apr-2020

8 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS

MINIX 2.0.0 SOBRE VIRTUALBOX (tarea 2) (REV. 1, 9 DE MARZO DE 2010)

Page 2: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 9

TAREA 2

EXPERTO 1 - GESTIÓN DE PROCESOS Y EL RELOJ

ESTRUCTURA DE MINIX ([TANENBAUM97], CAPÍTULO 2).

La planificación de procesos en MINIX tiene mucha relación con la estructura de este sistema operativo, por lo que vamos a recordar cómo es ésta.

1. Microkérnel apoyado en procesos para tareas básicas comunicados mediante paso de mensajes. Esta organización es más modular y permite más fácilmente la distribución de funciones vía red.

2. Estructura en cuatro capas:

3. Kérnel: capas 1 y 2. o Capa 1: Atrapa todas las interrupciones (hardware -externas,internas-, software), realiza la planificación y

cambio de contexto y ofrece a las capas superiores un modelo de procesos secuenciales independientes que se comunican empleando mensajes, mediante la gestión de este intercambio de mensajes. Este código se ejecuta en el modo supervisor de la cpu.

o Capa 2: Formado por los procesos que realizan funciones básicas de E/S. A estos procesos se les denomina tareas y se requiere una para cada tipo de dispositivo: disco, impresora, terminales, interfaces de red y relojes. Si estuvieran presentes otros dispositivos de E/S se necesitaría también una tarea para cada uno. El término tareas expuesto aquí es equivalente con el de manejadores de dispositivos. La tarea de sistema es un caso especial y sirve para cuestiones que los procesos no pueden realizar (por ejemplo, copiado entre zonas de memoria de diferente nivel de protección). Las tareas tienen más prioridad que el resto de procesos al estar en ranuras especiales del PCB. Sin embargo el procesador se encuentra en modo usuario cuando son ejecutadas.

4. Capa 3: Procesos encargados de gestión de memoria, archivos y red. A estos procesos se les denomina servidores. Esta capa es el punto de entrada al sistema, el interfaz con los procesos de usuario (interpretación de las llamadas al sistema). Estos procesos se ejecutan con mayor prioridad que los de usuario (capa 4), pero menor que la de los del kernel, debido al algoritmo de planificación utilizado, no pudiendo acceder directamente a los puertos de E/S ni a zonas de memoria fuera de los segmentos que les son asignados. El administrador de memoria (MM) ejecuta todas las llamadas al sistema que intervienen en la administración de memoria como fork, exec y brk. El sistema de archivos (FS) ejecuta todas las llamadas al sistema relacionadas con archivos, como read, mount, chdir …

5. Capa 4: tarea init (el padre del resto de procesos de usuario) y procesos de usuario. 6. Fichero image: formado por el núcleo más las tareas de las capas 2, 3 y 4 (menos los procesos de usuario, claro).

Está todo junto aunque al inicializarse MINIX se convierten en las diferentes tareas. 7. Privilegios: el kérnel es el de mayor nivel. Las tareas (aunque realicen funciones básicas) tienen menor

nivel de privilegio.

Page 3: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 10

8. En la siguiente figura se muestra de una manera un poco más clara la estructura de MINIX y comunicación entre capas:

GESTIÓN DE PROCESOS EN MINIX

1. Ficheros de cabecera y definiciones de variables más relevantes, relacionados con la gestión de procesos: o a.out.h: extruct exec.- Cabecera de un ejecutable en MINIX. o sys/sigcontext.h: estruct sigregs.- Se usa para almacenar el contexto (estado de los registros de la

CPU) de un proceso. o type.h:

Se declara el tipo mensaje (message). struct stackframe_s.- Tipo de la estructura usada para guardar la información necesaria en un

cambio de contexto. Se declara también la forma de localizar alguno de los registros más importantes, como por

ejemplo:

#define pc context[PC] 

o MINIX/const.h. Declaración de las constantes usadas en MINIX. Las más interesantes son: NR_TASKS.- Número de tareas. Número que identifica a cada proceso servidor. LOW_USER.- Se usa para identificar el primer proceso de usuario. Número de colas de planificación y número de identificación de cada una. Las colas definidas

son: TASK_Q identificada con el número 0. SEVER_Q identificada con el número 1. USER_Q identificada con el número 2.

o config.h. Contantes configurables de MINIX. Se recomienda leer con atención el contenido de este fichero. Las asignaciones más interesantes son:

NR_PROCS.- Número de entradas en la tabla de procesos para procesos de usuarios Constantes para incluir/excluir controladores de dispositivos. Constantes que definen el número máximo de dispositivos (terminales, discos, etc.). Constantes que definen el tipo de dispositivos, como por ejemplo el teclado.

Page 4: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 11

o kernel/glo.h. Se declaran variables globales del kernel: proc_ptr.- Puntero al proceso en ejecución actualmente, más concretamente a su PCB. tasktab[].- Aquí se declara y en table.c se inicializa. Contienen la lista de tareas y procesos

servidores del sistema. Se usa en main.c para inicializar la tabla de procesos. o proc.h. Contiene declaraciones usadas para la gestión de los procesos:

struct proc.- PCB del proceso. Su declaración es la siguiente:

struct proc {   struct stackframe_s p_reg;    /* process' registers saved in stack frame */   reg_t *p_stguard;             /* stack guard word */   int p_nr;                     /* number of this process (for fast access) */   int p_int_blocked;            /* nonzero if int msg blocked by busy task */   int p_int_held;               /* nonzero if int msg held by busy syscall */   struct proc *p_nextheld;      /* next in chain of held‐up int processes */   int p_flags;                  /* P_SLOT_FREE, SENDING, RECEIVING, etc. */   struct mem_map p_map[NR_SEGS];/* memory map */   pid_t p_pid;                  /* process id passed in from MM */   clock_t user_time;            /* user time in ticks */   clock_t sys_time;             /* sys time in ticks */   clock_t child_utime;          /* cumulative user time of children */   clock_t child_stime;          /* cumulative sys time of children */   clock_t p_alarm;              /* time of next alarm in ticks, or 0 */   struct proc *p_callerq;       /* head of list of procs wishing to send */   struct proc *p_sendlink;      /* link to next proc wishing to send */   message *p_messbuf;           /* pointer to message buffer */   int p_getfrom;                /* from whom does process want to receive? */   int p_sendto;   struct proc *p_nextready;     /* pointer to next ready process */   sigset_t p_pending;           /* bit map for pending signals */   unsigned p_pendcount;         /* count of pending and unfinished signals */   char p_name[16];              /* name of the process */   int p_stn;                    /* sub‐task number */ }; 

struct proc prc[NR_TASKS+NR_PROCS].- Tabla de procesos. struct proc *pproc_addr[NR_TASKS+NR_PROCS].- Se usa para realizar accesos rápidos al array

proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro que usa el array de punteros pproc_addr para acceder al PCB de cada proceso.

Devuelve la "dirección" del proceso pasado como argumento. rdy_head, rdy_tail.- Apuntan al primer y último proceso, respectivamente, de la cola de

procesos correspondiente. Son arrays de tamaño 3, un elemento por cada cola de procesos. 2. El fichero kernel/proc.c:

o pick_proc().- Es el planificador de la CPU. Se encarga de seleccionar el proceso a ejecutar: toma el primer proceso preparado en la cola de mayor prioridad (rdy_head[]) y lo asigna a la variable global proc_ptr para su ejecución. En primer lugar examina las colas de tareas y de servidores:

if ( (rp = rdy_head[TASK_Q]) != NIL_PROC) {         proc_ptr = rp;         return;   }   if ( (rp = rdy_head[SERVER_Q]) != NIL_PROC) {         proc_ptr = rp;         return;   } 

Si no existe ningún proceso en esas colas ejecuta la tarea IDLE a no ser que haya alguno de usuario preparado:

Page 5: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 12

  bill_ptr = proc_ptr = proc_addr(IDLE);   if ( (rp = rdy_head[USER_Q]) != NIL_PROC) {         proc_ptr = rp;         bill_ptr = rp;   } 

Notar que la variable bill_ptr apunta al proceso en ejecución si es de usuario a efectos de contabilidad. Así, aunque se ejecute una tarea o un servidor, bill_ptr apuntará al proceso de usuario que solicitó el servicio al sistema operativo que ha provocado la ejecución de la tarea o del servidor.

gwin_proc apunta al proceso (si existe) que tiene la ventana de registros almacenada en memoria. Si existe dicho proceso se le coloca el primero en la cola de preparados, es decir, se le da prioridad.

o ready().- Inserta un nuevo proceso preparado para ejecución al final de la correspondiente cola. A continuación aparece el código que hace esto para procesos de usuario:

  if (rdy_head[USER_Q] == NIL_PROC)         rdy_tail[USER_Q] = rp;   rp‐>p_nextready = rdy_head[USER_Q];   rdy_head[USER_Q] = rp; 

o unready().- Es invocada por un proceso en ejecución que pasará a estado "bloqueado". Precisamente por estar en ejecución estará a la cabeza de la cola asociada. Se invoca a

pick_proc() para seleccionar otro proceso en ejecución. También puede ocurrir que un proceso deje de estar en ejecución si se le envía una señal.

o sched().- Invocada por la tarea de reloj cuando un proceso consumió su cuanto de CPU. Esta función sólo es invocada desde la función lock_sched(). Gestiona la cola de procesos de usuario, pasando el actualmente en ejecución al final de la cola y el siguiente a la cabeza, para que sea el siguiente en ejecutarse, mediante la invocación a pick_proc():

/* The current process has run too long.  If another low priority (user)  * process is runnable, put the current process on the end of the user queue,  * possibly promoting another user to head of the queue.  */    if (rdy_head[USER_Q] == NIL_PROC) return;    /* One or more user processes queued. */   rdy_tail[USER_Q]‐>p_nextready = rdy_head[USER_Q];   rdy_tail[USER_Q] = rdy_head[USER_Q];   rdy_head[USER_Q] = rdy_head[USER_Q]‐>p_nextready;   rdy_tail[USER_Q]‐>p_nextready = NIL_PROC;   pick_proc(); 

Si la cola de preparados está vacía entonces es que el proceso que acabó el cuanto era una tarea o un servidor; en ese caso no se hace nada ya que se confía en que están bien construidos y dejarán la CPU cuando acaben lo que estén haciendo.

3. El fichero kernel/mpx.c: o resume().- Ejecuta el proceso seleccionado (por pick_proc()). Entre otras operaciones recupera el

contexto (estado de la CPU) almacenado en su PCB.

EL RELOJ

Preste atención a las constantes definidas al inicio del fichero kernel/clock.c, en especial a SCHED_RATE, que se corresponde con la duración del quanto.

Page 6: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 13

kernel/clock.c: clock_handler().‐ Es la rutina de servicio de la interrupción (señal) SIGALRM, se ejecutará, por lo tanto, cada vez que esta interrupción (señal) sea generada, es decir, 20 veces por segundo. Tareas más importantes realizadas:

o Suma 1 tick al tiempo de ejecución del proceso actual (rp‐>user_time += ticks;). o A continuación debería actualizar realtime, pero para evitar tener que proteger su acceso con el consumo

de tiempo que ello conlleva, lo que se hace es actualizar variables menos protegidas: pending_ticks y now.

o Se comprueba que si la variable sched_ticks ha llegado a 0. Esta variable cuenta el número de ticks que le quedan al proceso de usuario actualmente en ejecución para acabar el cuanto.

o Si sched_ticks ha llegado a 0 y el proceso es de usuario se invoca a interrupt(CLOCK). Esta función lo que hace es enviar un mensaje de tipo HARD_INT a la tarea de reloj (clock_task()) desencadenando que esta tarea pase a listo, y al ejecutarse, cuando le toque, invoque a do_clocktick. Esta función además de invocar, como se ha indicado anteriormente, a lock_sched() reinicia la variable sched_ticks.

o Se decrementa la variable sched_ticks. Si la variable llega a 0 significa que no había procesos de usuario, por lo que ser reinicializa a SCHED_TICKS, es decir, a 2.

PRÁCTICA PARTE 1 - GESTIÓN DE PROCESOS Y EL RELOJ

EJERCICIO 1: ACCESO AL PCB DE LOS PROCESOS

o Modificar el/los ficheros del directorio kernel para que al arrancar MINIX nos muestre, accediendo al PCB de cada uno, el nombre y el número de proceso asignados a cada una de las tareas y los servidores arrancados. ¿Cuáles de esos procesos son tareas y cuáles son servidores?

o Crear un programa para MINIX que llamaremos prueba. Guardar ese ejecutable en el directorio HOME de un usuario normal (no root). Modificar MINIX para que cada vez que ese proceso sea seleccionado para ejecución se muestre un mensaje indicándolo, mostrando además: el número asignado al proceso y el tiempo de usuario acumulado en ticks. IMPORTANTE: esa información SÓLO se debe mostrar cuando el proceso llamado prueba sea el seleccionado para ejecutarse. Entrar en MINIX, ejecutar el proceso y comprobar que se muestra la salida indicada.

EJERCICIO 2: MODIFICACIÓN DEL QUANTO

o Crear un proceso que cree dos procesos hijos. Uno con una alta carga de CPU y el otro con una alta carga de entrada y salida. Modificar el cuanto en MINIX y mediante la ejecución del proceso con el comando time comprobar el efecto del valor de cuanto sobre el tiempo que tarda en ejecutarse ese programa.

o Modificar el núcleo de MINIX de tal modo que cuando se ejecute el programa de usuario llamado Q_Largo, a este proceso se le asigne un quanto de CPU el doble de largo que al resto.

Page 7: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 14

EXPERTO 2 - INTERRUPCIONES Y COMUNICACIÓN ENTRE PROCESOS

COMUNICACIÓN ENTRE PROCESOS EN MINIX

1. La invocación a rutinas del Sistema Operativo en MINIX se implementa mediante el paso de mensajes, usando para ello las funciones: send(), receive() y sendrec().

2. Estas funciones están definidas en el fichero fuente en lenguaje ensamblador /usr/src/lib/i386/rts/_sendrec.s  3. Cada proceso sólo se puede comunicar con procesos del nivel inmediatamente inferior o superior:

Así, por ejemplo, los procesos de usuario sólo se pueden comunicar con procesos del nivel adyacente, es decir, con procesos servidores como el administrador de memoria (mm) y sistema de archivos (fs):

4. Internamente el paso de mensajes se implementa mediante interrupciones con un mecanismo similar a la solicitud de servicio en un sistema operativo tradicional (macronúcleo). Una vez que el control está en el núcleo, éste se encarga de enviar el mensaje a su destinatario (previas comprobaciones).

Page 8: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 15

5. Cada vez que se ejecuta cualquiera de las funciones send(), receive() o sendrec() se genera una interrupción (señal) lo que desencadena la ejecución de la función s_call() definida en el fichero kernel/mpx386.s 

6. La utilización de señales para implementar el paso de mensajes se hace para forzar la ejecución del kérnel y que sea éste el que resuelva el paso de mensaje ya que esto implica acceder a información clave (PCB) del proceso emisor y del receptor. Los mensajes son enviados por los procesos de los niveles 2, 3 y 4.

7. s_call()  

Recoge los argumentos de la interrupción de la pila y los almacena en el array params[]. Los argumentos pasados son:

La función que realizó la interrupción (send, receive o sendrec). El proceso destino al que se envía o fuente desde el que se recive. Un puntero al mensaje.

Llama a la función kernel/proc.c:sys_call() pasando los argumentos leídos como argumentos de la función.

8. sys_call()  

Es la encargada de implementar las llamadas de mensajes. Por su nombre se puede deducir lo que se ha indicado al principio de esta sección: que las invocaciones al sistema se hacen mediante paso de mensajes.

Comprueba los argumentos. Dependiendo de la función que generó la interrupción ejecuta:

mini_send(): si la función fue send() o sendrec(). mini_rec(): si la función fue receive() o sendrec().

9. mini_send(): Función encargada de enviar el mensaje al proceso destino. Tiene la siguiente declaración:

PRIVATE int mini_send(caller_ptr, dest, m_ptr) register struct proc *caller_ptr;       /* who is trying to send a message? */ int dest;                       /* to whom is message being sent? */ message *m_ptr;                 /* pointer to message buffer */ 

En primer lugar realiza diferentes comprobaciones ya que, por ejemplo, los procesos de usuario únicamente pueden enviar mensajes a los procesos servidores:

 /* User processes are only allowed to send to FS and MM.  Check for this. */   if (isuserp(caller_ptr) && !issysentn(dest)) return(E_BAD_DEST);   dest_ptr = proc_addr(dest);   /* pointer to destination's proc entry */   if (dest_ptr‐>p_flags & P_SLOT_FREE) return(E_BAD_DEST);      /* dead dest */ 

A continuación realiza una comprobación para evitar el bloqueo mutuo. Si el destino está bloqueado esperando por este mensaje lo copia, actualiza el campo p_flags del destino e

invoca a src/kernel/proc.c:ready(rp) función que inserta el proceso apuntado por rp al final de la cola de procesos preparados (la que le toque, es decir TASK_Q, SERVER_Q o USER_Q).

  /* Check to see if 'dest' is blocked waiting for this message. */   if ( (dest_ptr‐>p_flags & (RECEIVING | SENDING)) == RECEIVING &&        (dest_ptr‐>p_getfrom == ANY ||         dest_ptr‐>p_getfrom == proc_number(caller_ptr))) {         /* Destination is indeed waiting for this message. */         CopyMess(proc_number(caller_ptr), caller_ptr, m_ptr, dest_ptr,                  dest_ptr‐>p_messbuf);         dest_ptr‐>p_flags &= ~RECEIVING;        /* deblock destination */         if (dest_ptr‐>p_flags == 0) ready(dest_ptr);   } else {         /* Destination is not waiting.  Block and queue caller. */ 

Si el destino no está preparado el invocador se bloquea en estado SENDING y se inserta en la cola del proceso receptor que apunta a todos los procesos que le han enviado un mensaje (cola dest_ptr‐>p_callerq ):

Page 9: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 16

} else {         /* Destination is not waiting.  Block and queue caller. */         caller_ptr‐>p_messbuf = m_ptr;         if (caller_ptr‐>p_flags == 0) unready(caller_ptr);         caller_ptr‐>p_flags |= SENDING;         caller_ptr‐>p_sendto= dest;          /* Process is now blocked.  Put in on the destination's queue. */ #if (MACHINE == SUN)         event_log(EV_MSG_BLOCK, dest_ptr, INVALID_STN, 0, 0); #endif         if ( (next_ptr = dest_ptr‐>p_callerq) == NIL_PROC)                 dest_ptr‐>p_callerq = caller_ptr;         else {                 while (next_ptr‐>p_sendlink != NIL_PROC)                         next_ptr = next_ptr‐>p_sendlink;                 next_ptr‐>p_sendlink = caller_ptr;         }         caller_ptr‐>p_sendlink = NIL_PROC;   } 

Finalmente return(OK) 10. mini_rec(): Función encargada de que un proceso reciba un mensaje que espera.

Tiene la siguiente declaración:

PRIVATE int mini_rec(caller_ptr, src, m_ptr) register struct proc *caller_ptr;       /* process trying to get message */ int src;                        /* which message source is wanted (or ANY) */ message *m_ptr;                 /* pointer to message buffer */ 

Se comprueba si hay un mensaje disponible del emisor indicado. Para ello se recorre la cola p_callerq del receptor (que en este caso es caller_ptr) y si se encuentra el proceso, se desbloquea dicho proceso y se lee el mensaje:

  if (!(caller_ptr‐>p_flags & SENDING)) {         /* Check caller queue. */     for (sender_ptr = caller_ptr‐>p_callerq; sender_ptr != NIL_PROC;          previous_ptr = sender_ptr, sender_ptr = sender_ptr‐>p_sendlink) {         if (src == ANY || src == proc_number(sender_ptr)) {                 /* An acceptable message has been found. */                 CopyMess(proc_number(sender_ptr), sender_ptr,                          sender_ptr‐>p_messbuf, caller_ptr, m_ptr);                 if (sender_ptr == caller_ptr‐>p_callerq)                         caller_ptr‐>p_callerq = sender_ptr‐>p_sendlink;                 else                         previous_ptr‐>p_sendlink = sender_ptr‐>p_sendlink;                 if ((sender_ptr‐>p_flags &= ~SENDING) == 0)                         ready(sender_ptr);      /* deblock sender */                  return(OK);         }     } 

Si no hay un mensaje, el receptor se bloquea en espera de recibirlo:

  /* No suitable message is available.  Block the process trying to receive. */ #if (MACHINE == SUN)   event_log(EV_MSG_BLOCK, caller_ptr, INVALID_STN, 0, 0); #endif   caller_ptr‐>p_getfrom = src;   caller_ptr‐>p_messbuf = m_ptr;   if (caller_ptr‐>p_flags == 0) unready(caller_ptr); 

Page 10: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 17

   caller_ptr‐>p_flags |= RECEIVING; 

AÑADIR NUEVAS LLAMADAS AL SISTEMA

AÑADIR NUEVA LLAMADA AL SISTEMA DENTRO DE MM

Añadir en mm/table.c la entrada en el array call_vec con el nombre de la función. o Primero localizar una entrada libre (p.ej. la 69)

_PROTOTYPE (int (*call_vec[NCALLS]), (void) ) = {   no_sys,    /*  0 = unused */   ...    no_sys,    /* 67 = REVIVE */   no_sys,    /* 68 = TASK_REPLY  */   do_psmem,   /* 69 = un ps desde el gestor de memoria */   no_sys,    /* 70 = unused */   ... }; 

Declarar el prototipo de la llamada en el fichero mm/proto.h … _PROTOTYPE( void panic, (char *format, int num)        ); _PROTOTYPE( void tell_fs, (int what, int p1, int p2, int p3)    );   _PROTOTYPE( int do_psmem, (void)          ); 

Añadir en /usr/include/minix/callnr.h el #define correspondiente. … #define REBOOT     76  /* Las nuevas llamadas que crea el usuario... */  #define PSMEM        69 ...  

Crear el fichero con el código de la función en el directorio mm. El nombre es psmem.c, pero en principio es irrelevante.

o ATENCION a los includes. #include "mm.h" #include <minix/com.h> #include <signal.h> /* para que no de errores */ #include "mproc.h"   int do_psmem (void){     struct mproc *rmc;    printf ("Hola desde el MM\n");     for (rmc = &mproc[0]; rmc < &mproc [NR_PROCS]; rmc ++){     if ( (rmc‐>mp_flags & IN_USE) != 0)       printf ("pid = %d \n", rmc‐>mp_pid);   }    return (OK); } 

Page 11: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 18

Modificar el fichero Makefile para añadir la nueva función (módulo objeto) al servidor MM. # Makefile for mm  ...  OBJ =   main.o forkexit.o break.o exec.o \   signal.o alloc.o utility.o table.o putk.o trace.o getset.o \   psmem.o  ...  psmem.o:  $a psmem.o:  mproc.h psmem.o:  $h/com.h psmem.o:  $h/callnr.h psmem.o:  $i/signal.h 

Compilar MM. Actualizar la biblioteca de funciones estándar.

o Crear /usr/src/lib/posix/_psmem.c #include <lib.h> #include <unistd.h>  PUBLIC int psmem (void){    message m;   return (_syscall (MM, PSMEM, &m)); } 

o Modificar Makefile del directorio /usr/lib/posix OBJECTS  = \   ...   $(LIBRARY)(_waitpid.o) \   $(LIBRARY)(_write.o) \   $(LIBRARY)(_psmem.o) \    ...  $(LIBRARY)(_write.o):  _write.c   $(CC1) _write.c  $(LIBRARY)(_psmem.o):  _psmem.c /usr/include/minix/callnr.h   $(CC1) _psmem.c 

o En el directorio /usr/src/lib ejecutar make install para instalar la nueva biblioteca de funciones conteniendo nuestra psmem().

o Compilar el nuevo núcleo (por ejemplo, yendo al directorio tools y escribiendo make hdboot) Rebotar con un shutdown, cerrando máquina y volver a iniciar.

o Asegurarse de que usa el nuevo kernel. Para probarlo, crear un programa que use la nueva llamada.

#include <stdio.h>  int main (void){    psmem ();   return (0); } 

o Compilar y ejecutar.

AÑADIR UNA LLAMADA AL KERNEL

Teniendo en cuenta que sólo se puede acceder al Kernel desde alguno de los servidores, nunca como aplicación

Page 12: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 19

de usuario, vamos a añadir una llamada al servidor MM que sea reenviada al núcleo. Primero hay que registrar la nueva llamada al sistema en /usr/include/minix/com.h

/* System calls. */ ...  #define SYSTASK           ‐2 /* internal functions */ #  define SYS_XIT        1 /* fcn code for sys_xit(parent, proc) */ ... #  define SYS_MIO       21   /* Mi llamada */   

Modificamos la llamada psmem de antes:

 #include "mm.h" #include <MINIX/com.h> #include <signal.h> /* para que no de errores */ #include "mproc.h"   int do_psmem (void){     struct mproc *rmc;   message mess    printf ("Hola desde MM\n");     for (rmc = &mproc[0]; rmc < &mproc [NR_PROCS]; rmc ++){     if ( (rmc‐>mp_flags & IN_USE) != 0)       printf ("pid = %d \n", rmc‐>mp_pid);   }    mess.m_type = SYS_MIO;   if (sendrec(SYSTASK, &mess) != OK) panic("bad SYS_MIO?", NO_NUM);     return (OK); } 

Incluir el código de la nueva llamada en kernel/system.c ...  /*===========================================================================*  *        sys_task                   *  *===========================================================================*/ PUBLIC void sys_task() { /* Main entry point of sys_task.  Get the message and dispatch on type. */    register int r;    while (TRUE) {   receive(ANY, &m);    switch (m.m_type) { /* which system call */       case SYS_FORK:  r = do_fork(&m);  break;           ...       case SYS_MIO:  r = do_mio (&m);  break;       default:    r = E_BAD_FCN;   }    m.m_type = r;    /* 'r' reports status of call */ 

Page 13: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 20

  send(m.m_source, &m);  /* send reply to caller */   } }  ...  /*===========================================================================*  *        do_mio                *  * Esta función tiene que estar en la sección de código correspondiente al   *  * chip INTEL                    *  *===========================================================================*/ PRIVATE int do_mio(m_ptr) register message *m_ptr; /* pointer to request message */ {    printf ("Esto es un mensaje desde TASKS\n");       return(OK); } 

Recompilar kernel, mm, tools, e instalar con tools/mkboot hdboot. Rebotar y volver a ejecutar el programa de que se usó para probar la función que se insertó en el servidor de memoria.

PRÁCTICA PARTE 2 - CREACIÓN DE UN MECANISMO PROPIO DE INVOCACIÓN AL SISTEMA.

La idea es disponer de nuestro propio mecanismo de invocación a sistema utilizando las interrupciones software disponibles, es decir, no usadas por MINIX. Mediante este mecanismo se podrá acceder al sistema (variables, funciones, tareas, servidores, etc.) para poder desarrollar las modificaciones o herramientas que se planteen en el Proyecto a realizar.

Siguiendo los pasos indicados en los apartados “Añadir nuevas llamadas al sistema”, incorporar una función que al ser invocada desde un programa de usuario permita visualizar información de alguna de las estructuras de datos accesibles sólo desde el núcleo (por ejemplo, la de los elementos del array proc).

Page 14: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 21

EXPERTO 3- GESTIÓN DE MEMORIA EN MINIX

GENERALIDADES

La gestión de la memoria es de tipo segmentación real. Los procesos tienen tres segmentos (texto, datos y pila) que deben cargarse en la memoria física para que el proceso pueda ejecutarse. La gestión del espacio será por lo tanto análoga al mecanismo de particiones múltiples de tamaño variable y de hecho veremos la existencia de listas de huecos libres y ocupados.

Para los procesos servidores (kernel, fs, mm, inet) y tareas la gestión de la memoria es: o Un hueco para el texto o Un hueco para los datos y la pila, junto con el espacio reservado para el crecimiento de dicha pila y de la

zona de datos. Es decir, hay dos huecos para cada proceso, a pesar de que la tabla que lo describe tenga tres entradas ya que estas entradas describen segmentos:

Segmento de texto: hueco de texto. Segmentos de datos y pila: un único hueco.

o Las direcciones virtuales 0 del segmento de texto y del de datos se corresponden con la dirección inicial en la memoria física. Sin embargo la dirección 0 del segmento de pila es la menor de las direcciones posibles para evitar subdesbordamientos que supondrían la escritura en la zona de datos (recordad la existencia de un único hueco para datos+pila). Por ello la dirección virtual inicial del segmento de pila coincide con el tamaño del hueco entre el segmento de datos y el de pila.

El servidor MM se encuentra en la capa número 3 de MINIX. Recuerde la organización:

o Los procesos de usuario solicitan servicios al servidor MM mediante el envío de mensajes que codifican el tipo de llamada al sistema. Pueden utilizar la función /usr/src/lib/other/syscall.c:_syscall() o bien construir su propia invocación.

o A su vez el servidor MM hace uso de las tareas de nivel 2 mediante el envío de mensajes. Para ello hace

uso de las funciones de biblioteca que comienzan con sys_. Se encuentran en /usr/src/lib/syslib.

ARCHIVOS DE CABECERA Y ESTRUCTURAS DE DATOS.

src/mm/mm.h. Es el principal fichero de cabecera que contiene todos los archivos de cabecera que deben ser compilados con el sistema de gestión de memoria.

proto.h. Prototipos de funciones. glo.h. Variables globales del administrador de memoria. Para este fichero la constante EXTERN se sustituye por

una cadena nula.

Page 15: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 22

o mp: puntero a la entrada de la tabla de procesos (de tipo struct mproc) que se corresponde con el proceso cuya llamada al sistema está siendo procesada. Como ya se explicó, la tabla de procesos está dividida entre el kérnel, el sistema de gestión de memoria y el de ficheros. Todas ellas contienen el mismo número de entradas pero con campos diferentes.

o who: índice del proceso en curso (mp = &mproc[who] siendo mproc[] la tabla de procesos). o procs_in_use: número de entradas de la tabla de procesos que están siendo utilizadas, es decir, número

de procesos (y tareas en ejecución). Se utiliza para saber si se pueden crear nuevos procesos. o core_name: nombre del fichero core que será creado si se presenta algún problema. o core_ssets: mapa de bits que describen los problemas (las señales) que provocan la creación del fichero

core. Este fichero contiene una copia de la imagen del proceso en memoria en el momento de ocurrir un error.

mproc.h: tabla de procesos del sistema de gestión de memoria (struct mproc { ...}). o El campo más interesante es el que describe el mapa de memoria del proceso:

  struct mem_map mp_seg[NR_SEGS];/* points to text, data, stack */ 

Hay tres entradas, una para el segmento de texto, otra para el de datos y otra para la pila. El tipo mem_map se encuentra declarado en /usr/include/minix/type.h:

struct mem_map {   vir_clicks mem_vir;           /* virtual address */   phys_clicks mem_phys;         /* physical address */   vir_clicks mem_len;           /* length */ }; 

Cada entrada tiene un campo de dirección virtual, otro de dirección física y un tercero de tamaño. Todos ellos vienen en clicks; un click son 256bytes en MINIX estándar. Se trata de una especie de tabla de segmentos utilizada para convertir direcciones virtuales en físicas.

o mp_flags: indica el estado de una entrada de la tabla de procesos y por consiguiente del proceso asociado. Valores utilizados:

/* Flag values */ #define IN_USE           001    /* set when 'mproc' slot in use */ #define WAITING          002    /* set by WAIT system call */ #define HANGING          004    /* set by EXIT system call */ #define PAUSED           010    /* set by PAUSE system call */ #define ALARM_ON         020    /* set when SIGALRM timer started */ #define SEPARATE         040    /* set if file is separate I & D space */ #define TRACED          0100    /* set if process is to be traced */ #define STOPPED         0200    /* set if process stopped for tracing */ #define SIGSUSPENDED    0400    /* set by SIGSUSPEND system call */ 

o mp_procargs: puntero al principio del array argv[] del proceso. o Otros campos interesantes:

char mp_exitstatus;           /* storage for status when process exits */ pid_t mp_pid;                 /* process id */ int mp_parent;                /* index of parent process */ uid_t mp_realuid;             /* process' real uid */ uid_t mp_effuid;              /* process' effective uid */ 

table.c: la variable más importante es el array de punteros a funciones call_vec. Sirve para invocar a la función de tratamiento de cada mensaje en función de su identificación. En la posición indexada por dicha identificación se almacena el puntero a la función:

(int (*call_vec[NCALLS]), (void) ) = {         no_sys,         /*  0 = unused  */ 

Page 16: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 23

        do_mm_exit,     /*  1 = exit    */         do_fork,        /*  2 = fork    */ etc... 

EL SERVIDOR MM

Se trata de un proceso MINIX encargado de recibir mensajes de solicitud procedentes de los procesos e implementarlas. Está compilado y enlazado de forma independiente al kérnel y, por supuesto, al resto de procesos.

Se encuentra en mm/main.c:

PUBLIC void main() { /* Main routine of the memory manager. */    int error;    mm_init();                    /* initialize memory manager tables */    /* This is MM's main loop‐  get work and do it, forever and forever. */   while (TRUE) {         /* Wait for message. */         get_work();             /* wait for an MM system call */         mp = &mproc[who];          /* Set some flags. */         error = OK;         dont_reply = FALSE;         err_code = ‐999;          /* If the call number is valid, perform the call. */         if (mm_call < 0 || mm_call >= NCALLS)                 error = EBADCALL;         else                 error = (*call_vec[mm_call])();          /* Send the results back to the user to indicate completion. */         if (dont_reply) continue;       /* no reply for EXIT and WAIT */         if (mm_call == EXEC && error == OK) continue;         reply(who, error, result2, res_ptr);   } } 

Como se puede ver, después de una inicialización (mm_init()) se invoca a la función get_work() que bloquea al proceso en espera de un mensaje. Una vez recibido se trata dicho mensaje (error = (*call_vec[mm_call])()) y, en su caso, se envía un mensaje de respuesta.

main.c:mm_init(): rutina de inicialización del sistema de gestión de memoria. o Invoca a la función sys_getmap() que obtiene información acerca del uso de la memoria por parte del

kérnel y por lo tanto de la memoria libre que queda:

  /* Get the memory map of the kernel to see how much memory it uses,    * including the gap between address 0 and the start of the kernel.    */   sys_getmap(SYSTASK, kernel_map);   minix_clicks = kernel_map[S].mem_phys + kernel_map[S].mem_len;  

La variable minix_clicks almacena el tamaño (en clicks) del núcleo. Esta información es necesaria para disponer del resto como memoria libre.

Page 17: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 24

o A continuación se inicializa la tabla de procesos:

  /* Initialize MM's tables. */   for (proc_nr = 0; proc_nr <= INIT_PROC_NR; proc_nr++) {         rmp = &mproc[proc_nr];         rmp‐>mp_flags |= IN_USE;         sys_getmap(proc_nr, rmp‐>mp_seg);         if (rmp‐>mp_seg[T].mem_len != 0) rmp‐>mp_flags |= SEPARATE;         minix_clicks += (rmp‐>mp_seg[S].mem_phys + rmp‐>mp_seg[S].mem_len)                                 ‐ rmp‐>mp_seg[T].mem_phys;   } 

Notar que si el campo mem_len del segmento de texto tiene un valor diferente de cero eso implica que el segmento de texto y el de datos se tratan separadamente lo cual se indica también en el campo mp_flags.

Además la variable minix_clicks se incrementa en la medida en que las diferentes tareas y servidores vayan haciendo uso de la memoria.

La operación de la última línea sirve para calcular el espacio total utilizado por la tarea: véase que se suma la dirección física inicial de la pila más su tamaño con lo que se obtiene la última dirección de dicha tarea. A ese valor se le resta la dirección inicial del segmento de texto que es la primera de la tarea. Esta diferencia es el tamaño de la mencionada tarea.

o Se espera a continuación que el sistema de ficheros ( fs ) informe sobre el tamaño del disco RAM:

  /* Wait for FS to send a message telling the RAM disk size then go "on‐line".    */   if (receive(FS_PROC_NR, &mess) != OK)         panic("MM can't obtain RAM disk size from FS", NO_NUM);    ram_clicks = mess.m1_i1; 

o Se invoca a la función alloc.c:mem_init() encargada de inicializar la lista de agujeros de la memoria. alloc.c:mem_init(). Inicializa las listas de agujeros de memoria. La estructura básica es la tabla hole que

mantiene una lista de agujeros en la memoria física. Está ordenada en orden creciente de dirección física. Contiene direcciones físicas partiendo de la 0x0. Esta estructura es necesaria para evitar asignar memoria que está utilizando el núcleo, los vectores de interrupción y el servidor de memoria.

o hole está declarada en alloc.c. Como se puede observar se ha declarado como estática y esto nos puede resultar extraño. La razón para hacer esto es que se simplifica su gestión:

PRIVATE struct hole {      phys_clicks h_base;           /* where does the hole begin? */   phys_clicks h_len;            /* how big is the hole? */   struct hole *h_next;          /* pointer to next entry on the list */ } hole[NR_HOLES]; 

Su inicialización se realiza en mem_init(). En primer lugar se inicializa el campo h_next:

  /* Put all holes on the free list. */   for (hp = &hole[0]; hp < &hole[NR_HOLES]; hp++) hp‐>h_next = hp + 1;   hole[NR_HOLES‐1].h_next = NIL_HOLE; 

o A continuación se inicializa la lista hole_head que apunta al principio de la lista de agujeros libres en memoria. En un principio está vacía pero inmediatamente le será asignada la memoria:

hole_head = NIL_HOLE; 

Page 18: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 25

o Se hace lo mismo con la lista free_slots que apunta a entradas libres de la tabla hole. Inicialmente la tabla hole se considera vacía completamente y se irá utilizando según se vayan añadiendo los agujeros que el núcleo no utiliza:

  free_slots = &hole[0]; 

o Y, finalmente, se entra en un bucle en el que se va preguntando (mediante mensajes) al núcleo por los trozos de memoria que no está utilizando para así inicializar las tablas utilizando la función free_mem() :

  *free = 0;   for (;;) {         mess.m_type = SYS_MEM;         if (sendrec(SYSTASK, &mess) != OK) panic("bad SYS_MEM?", NO_NUM);         base = mess.m1_i1;         size = mess.m1_i2;         if (size == 0) break;           /* no more? */          free_mem(base, size);         *total = mess.m1_i3;         *free += size;   } 

o Las variables total y free se corresponden con las variables externas total_clicks y free_clicks. alloc.c:free_mem(base, clicks): recibe dos argumentos, la dirección base a partir de donde se quiere liberar y

el número de clicks a liberar. Añade dicha memoria a la tabla hole. Veamos cómo lo hace: o Comprobaciones e inicialización de la variable local new_ptr que se utilizará para la asignación del nuevo

hueco libre. Además free_slots apunta al siguiente elemento de la lista hole y se comienza a comprobar la lista de huecos libres hole_head :

 if (clicks == 0) return;   if ( (new_ptr = free_slots) == NIL_HOLE) panic("Hole table full", NO_NUM);   new_ptr‐>h_base = base;   new_ptr‐>h_len = clicks;   free_slots = new_ptr‐>h_next;   hp = hole_head; 

o Si la lista de huecos libres (ahora apuntada por hp) está vacía o bien la dirección del hueco a liberar es menor que la del primer hueco libre, se coloca el nuevo hueco al principio de la lista hole_head. merge() unirá huecos libres consecutivos:

  if (hp == NIL_HOLE || base <= hp‐>h_base) {         /* Block to be freed goes on front of the hole list. */         new_ptr‐>h_next = hp;         hole_head = new_ptr;         merge(new_ptr);         return;   } 

o Si no va al principio de la lista, se busca su lugar y se inserta:

  /* Block to be returned does not go on front of hole list. */   while (hp != NIL_HOLE && base > hp‐>h_base) {         prev_ptr = hp;         hp = hp‐>h_next;   }    /* We found where it goes.  Insert block after 'prev_ptr'. */   new_ptr‐>h_next = prev_ptr‐>h_next; 

Page 19: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 26

  prev_ptr‐>h_next = new_ptr;   merge(prev_ptr);              /* sequence is 'prev_ptr', 'new_ptr', 'hp' */ 

FORK, EXIT Y WAIT

Fichero src/mm/forkexit.c. Funciones do_fork(), mm_exit() y do_waitpid(). src/mm/forkexit.c:do_fork(). Se trata de la rutina invocada en la creación de un nuevo proceso. Recibe el

puntero (tabla de procesos) al proceso padre, es decir, el que invoca (variable mp ). Realiza estas tareas: o Comprueba que haya sitio en la tabla de procesos (struct mproc). o Se obtiene el tamaño del proceso hijo en función del tamaño del proceso padre. Sólo para los segmentos

de datos y pila ya que el texto se compartirá. Una vez obtenido este tamaño se reserva espacio dentro de la estructura que describe los huecos libres (hole_head). Esto se hace mediante la función alloc_mem() :

  prog_clicks = (phys_clicks) rmp‐>mp_seg[D].mem_len + rmp‐>mp_seg[S].mem_len;   prog_bytes = (phys_bytes) prog_clicks << CLICK_SHIFT;   if ( (child_base = alloc_mem(prog_clicks)) == NO_MEM) return(ENOMEM); 

o A continuación se copia el espacio de memoria del proceso padre en el hijo. Para ello se utiliza la función de biblioteca sys_copy() (ver /usr/src/lib/syslib) que envía un mensaje a la tarea de sistema. Esta tarea hace de interfaz entre los procesos servidores y el núcleo; como ya se ha explicado al principio de este apartado el gestor de memoria envía mensajes utilizando las funciones de biblioteca sys_*. En particular sys_copy() invoca a la función src/kernel/system.c:do_copy() que realiza una copia entre espacios de memoria de diferentes procesos. El segmento de pila se copia a continuación del de datos, tal y como muestra el código:

  /* Create a copy of the parent's core image for the child. */   child_abs = (phys_bytes) child_base << CLICK_SHIFT;   parent_abs = (phys_bytes) rmp‐>mp_seg[D].mem_phys << CLICK_SHIFT;   i = sys_copy(ABS, 0, parent_abs, ABS, 0, child_abs,                rmp‐>mp_seg[D].mem_len << CLICK_SHIFT);   if (i < 0) panic("do_fork can't copy", i);   i = sys_copy(ABS, 0, rmp‐>mp_seg[S].mem_phys << CLICK_SHIFT, ABS, 0,                child_abs + (rmp‐>mp_seg[D].mem_len << CLICK_SHIFT),                rmp‐>mp_seg[S].mem_len << CLICK_SHIFT);   if (i < 0) panic("do_fork can't copy", i); 

o A continuación se encuentra una entrada libre en la tabla de procesos, se copian los valores del proceso padre, se personalizan los propios del proceso hijo y se encuentra un pid no usado:

  /* Find a slot in 'mproc' for the child process.  A slot must exist. */   for (rmc = &mproc[0]; rmc < &mproc[NR_PROCS]; rmc++)         if ( (rmc‐>mp_flags & IN_USE) == 0) break;    /* Set up the child and its memory map; copy its 'mproc' slot from parent. */   child_nr = (int)(rmc ‐ mproc);        /* slot number of the child */   procs_in_use++;   *rmc = *rmp;                  /* copy parent's process slot to child's */    rmc‐>mp_parent = who;         /* record child's parent */   rmc‐>mp_flags &= ~TRACED;     /* child does not inherit trace status */  #if (SHADOWING == 0)   /* A separate I&D child keeps the parents text segmen. The data and stack    * segments must refer to the new copy.    */   if (!(rmc‐>mp_flags & SEPARATE)) rmc‐>mp_seg[T].mem_phys = child_base;   rmc‐>mp_seg[D].mem_phys = child_base; 

Page 20: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 27

   rmc‐>mp_seg[S].mem_phys = rmc‐>mp_seg[D].mem_phys +                      (rmc‐>mp_seg[S].mem_vir – rmp‐>mp_seg[D].mem_vir); #endif   rmc‐>mp_exitstatus = 0;   rmc‐>mp_sigstatus = 0;    /* Find a free pid for the child and put it in the table. */   do {         t = 0;                  /* 't' = 0 means pid still free */         next_pid = (next_pid < 30000 ? next_pid + 1 : INIT_PID + 1);         for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++)                 if (rmp‐>mp_pid == next_pid || rmp‐>mp_procgrp == next_pid) {                         t = 1;                         break;                 }         rmc‐>mp_pid = next_pid; /* assign pid to child */   } while (t); 

o Finalmente se informa al servidor de ficheros y al núcleo sobre la creación del nuevo proceso, cuestión necesaria ya que ambos deben actualizar sus propias tablas de gestión de procesos.

src/mm/forkexit.c:mm_exit() y do_waitpid(). Liberación de recursos y espera a la finalización de otros procesos. Ver código.

PRÁCTICA PARTE III - GESTIÓN DE MEMORIA

EJERCICIO 1: MONITOR DE CREACIÓN DE PROCESOS

Con este ejercicio se pretende profundizar en el estudio y manejo de la tabla de procesos en memoria (array de estructuras mproc).

Añadir a la función mm/forkexit.c:do_fork() las líneas necesarias para mostrar por pantalla la siguiente información cada vez que se cree un nuevo proceso:

o Número de clicks de memoria asignados al nuevo proceso. o PID del proceso padre y del hijo (el nuevo creado). o Tamaños de los segmentos de texto, datos y pila del nuevo proceso creado. Este tamaño se puede dar en

clicks o pasarlo a KBytes operando de manera similar a como se hace en main.c:mm_init(), función que muestra, durante el proceso de arranque de MINIX información acerca de la memoria disponible.

o Direcciones físicas de memoria iniciales de los segmentos de texto, datos y pila tanto del proceso padre como del hijo (el nuevo creado). Comprobar en este caso que, tal y como se vio en Sistemas Operativos al estudiar la función fork, padre e hijo comparten el mismo segmento de texto (la memoria física asignada es la misma).

En la figura se puede ver un ejemplo de lo que se debe mostrar.

Page 21: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 28

o Vamos a utilizar dos terminales virtuales (presionar ATL‐F1 y ALT‐F2 para cambiar de terminal). Una va a hacer las veces de "consola-monitor" y en la otra será donde ejecutemos programas.

o La primera de las dos sesiones (accesible con ALT‐F1) es la que hará de "consola-monitor". NO ABRIR SESIÓN DE USUARIO. Como durante el arranque se crean procesos, para cada uno se mostrará la información indicada, hasta que MINIX acabe de crear lo necesario para que se puedan abrir sesiones de usuario entonces la salida parará. Como curiosidad se puede ver esa información.

o En la segunda consola, abrir una sesión de usuario y desde ella ejecutar programas. En la consola se mostrará la información sobre la memoria reservada para esos procesos. Por ejemplo, puede ser interesante ejecutar "ps ‐l", y como coinciden los PID del padre e hijo mostrados tanto en consola como en la salida del comando.

o Para salir, entrar en la consola como root (basta con pulsar la tecla "intro" y aparecerá el mensaje de "login") y apagar MINIX.

EJERCICIO 2: MONITOR DE LA OCUPACIÓN DE MEMORIA.

Comentar las líneas añadidas en el ejercicio anterior para que no se mezcle la salida por pantalla de ambos ejercicios.

Aquí se va a profundizar en el estudio y manejo de la lista que gestiona la memoria libre (lista de estructuras de tipo hole).

Para poder acceder a esa lista desde cualquier función involucrada en la gestión de memoria se declarará todo lo relacionado con ella como global. Para ello, en primer lugar, copiar las siguientes líneas del alloc.c al fichero mm.h, cambiando su declaración de PRIVATE a EXTERN:

#define NR_HOLES 128 /* max # entries in hole table */ #define NIL_HOLE (struct hole *) 0  PRIVATE struct hole {    phys_clicks h_base; /* where does the hole begin? */   phys_clicks h_len; /* how big is the hole? */   struct hole *h_next; /* pointer to next entry on the list */ } hole[NR_HOLES];   PRIVATE struct hole *hole_head; /* pointer to first hole */ PRIVATE struct hole *free_slots; /* ptr to list of unused table slots */ 

Una vez hecha la copia, comentar las líneas del fichero alloc.c, para que no haya dobles declaraciones incompatibles.

Page 22: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 29

Añadir tanto en la función que crea un nuevo proceso (fork), como en la que lo elimina (exit) las líneas necesarias para que en todo momento se muestre la memoria disponible en la máquina. La salida debe ser algo parecido a lo mostrado en la figura:

Para visualizar esa información con cada programa que ejecutemos, se hará lo mismo que antes: abrir dos consolas: una va a hacer las veces de "consola-monitor" y en la otra será donde se ejecuten los programas.

Page 23: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 30

EL PROYECTO DE LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS

IDEAS PARA LA REALIZACIÓN DEL PROYECTO FINAL

Monitorización sistema de memoria. Uso de fork(). Comando w. Estudio del gestor de memoria. Monitorización. Defragmentador de memoria. Algoritmo de planificación:

o Crear un proceso que permita modificar el cuanto. o Penalizar procesos que hayan consumido mucha CPU. o Penalizar procesos que lleven mucho tiempo en el sistema. o Colas múltiples con realimentación y planificación variable en cada cola. o Apropiativa/no apropiativa. o Algoritmo SJF: apropiativo/no apropiativo.

Comando who. Mecanismo de asignación/liberación de memoria. Best-fit, worst-fit, next-fit. Monitorización de mensajes entre procesos. Comando ps con implicación en MM y en el kérnel. Comando ps con i-nodos. Información de i-nodos. Comando ls. Estudio del tiempo de cpu consumido por cada proceso. Daemon de monitorización. Mapas de memoria del sistema y de cada proceso. Creación de un comando que muestre una traza de lo que va haciendo un determinado ejecutable: traza

<ejecutable>. Añadir al shell: función tabulador, función cursores (historial), etc. Comando top. Inclusión información gestión de memoria. Comando ps añadiendo nuevas entradas al PCB. P.ej. instante de creación. Ver tiempos de espera en

colas. Etc

EJEMPLOS DE PROYECTOS EN CADA UNO DE LOS NIVELES DE DIFICULTAD

NIVEL 1

Creación de un Messenger para MINIX: Fichero pdf Modificación del shell para incluir historial y autocomplementar: Fichero pdf

NIVEL 2

Implementación de 3 colas de planificación de procesos de usuario: Fichero pdf Implementación de un nuevo comando llamado admin, que añade a las salidas del comando ps información sobre

estado de la memoria: Fichero pdf

NIVEL 3

Page 24: LABORATORIO DE AMPLIACIÓN DE SISTEMAS OPERATIVOS …cevp/ASO/fichs/Guiones...Se usa para realizar accesos rápidos al array proc[∙], o sea, a la tabla de procesos. proc_addr.- Macro

2º ITI Gestión Laboratorio ASO MINIX

09/03/2010 Página 31

Modificación del comando ps: Fichero pdf Modificación del comando who: Fichero pdf

CRITERIOS DE EVALUACIÓN

Calificación final: 0.2*Cal_Parte_I + 0.8*Cal_Proyecto

Donde:

Cal_Parte_I es la calificación obtenida en la parte I. Para obtener la máxima calificación (10 puntos) basta con acabar el trabajo propuesto en esta parte en las dos primeras sesiones de laboratorio. Si se tarda una sesión más la calificación será de 5 puntos y de 0 puntos si se tarda más.

Cal_Proyecto es la calificación obtenida por el trabajo realizado en las partes II y III. Ésta se divide de la siguiente manera: 0.2*Documentación + 0.8*Modificación_Realizada. Donde:

o Documentación es la calificación obtenida en los documentos "propuesta de proyecto" y "memoria final". Se valorará el seguimiento de las normas y la expresión escrita (claridad, formato, etc.).

o Modificación_Realizada es la calificación obtenida en la modificación de MINIX realizada. Para obtener esta calificación se valorará la dificultad de la tarea abordada. Se establecen 3 niveles de dificultad, con la siguiente calificación máxima para cada uno:

Nivel 1. Calificación máxima=10 puntos. Nivel 2. Calificación máxima=8 puntos. Nivel 3. Calificación máxima=6 puntos.

EJEMPLOS DE "NIVEL DE DIFICULTAD" EN LOS TRABAJOS:

Nivel 1:

Modificación de los fuentes de MINIX que implique conceptos involucrados en cada uno de los trabajos de expertos realizados en la parte II.

Creación de una herramienta de dificultad alta.

Nivel 2:

Modificación de los fuentes de MINIX que implique conceptos involucrados en una o dos de las partes estudiadas en el trabajo de expertos realizados en la parte II, salvo que el trabajo implique una dificultad alta, que entonces será catalogado como de nivel 1.

Nivel 3:

Modificación de algún comando (ps, ls, who…).