IIC 2332 -- Sistemas Operativos
Tarea de Multiprogramación

En esta tarea convertirás Nachos en un sistema que puede correr más de un proceso de usuario y que provee algunas llamadas de sistema los cuales se pueden usar en los programas de usuarios. La clave en esta tarea es la implementación de apoyo para espacios múltiples de direcciones.

La tarea vence el viernes 17 de mayo a las 12:00. El plazo para reuniones conmigo es el lunes 13 de mayo (ver aquí).

Introducción

Los archivos para esta tarea están en el directorio code/userprog. Tu primer paso es entender el sistema que ya existe. Tiene la habilidad de ejecutar solamente un programa de usuario a la vez, y provee solamente una llamada de sistema (Halt). Halt para el sistema de Nachos; en sistemas reales generalmente no está disponible para los usuarios normales.

En code/test/halt.c encontrarás un programa sencillo de C (no C++) que llama a Halt. El binario es halt, y puedes ejecutarlo bajo Nachos con el comando

nachos -x ../test/halt
Para crear el binario de Nachos, haz "make depend" y después "make" en el directorio code/userprog. Para crear el binario de halt, haz solamente "make" en code/test.

Usa el argumento "-d a" con Nachos para encender mensajes de depurificación. Debieras seguir la ejecución de Nachos con halt.(1) Después de la llamada a Initialize en threads/system.cc, main llama a StartProcess en userprog/progtest.cc. StartProcess crea un nuevo espacio de direcciones para el proceso de usuario (ver userprog/addrspace.cc) y inicia la ejecución del proceso con la llamada a machine->Run.

Secciones 2.4, 4, y 6.3 de A Road Map Through Nachos también tienen información sobre la operación de Nachos con los programas de usuario.

Multiprogramación I

Para la multiprogramación necesitamos la habilidad de ejecutar los procesos de usuario en cualquier parte de la memoria física. La simulación del hardware MIPS provee apoyo para las tablas de páginas, y por lo tanto cada proceso puede tener una tabla de páginas que traduzca entre el espacio lógico de usuario y las direcciones físicas de la máquina.

Cada proceso de usuario consiste en un thread de kernel y también de un objeto de AddrSpace (address space, o espacio de direcciones). En threads/thread.h encontrarás que la clase Thread tiene un nuevo miembro space (y algunos otros) si USER_PROGRAM es definido. En StartProcess hay también una línea

currentThread->space = space;
para asignar a este miembro.

El objeto de AddrSpace es como un process control block (PCB). Nachos usa este objeto para guardar información individual sobre un proceso. Una parte de esta información es la tabla de páginas del proceso.

En la rutina de construcción AddrSpace en addrspace.cc está la inicialización de la tabla de páginas de un proceso nuevo. La asignación de marcos a páginas es muy sencilla. El código asigna marco 0 a página 0, marco 1 a página 1, etc., con

pageTable[i].virtualPage = i;
pageTable[i].physicalPage = i;
Con esta estrategia de asignación no podemos tener más que un proceso de usuario en el sistema a la vez, ya que un segundo proceso usaría los mismos marcos físicos que el primero.

Una solución es que implementemos una nueva clase para administrar los marcos del sistema. Por ejemplo, podemos tener una clase CoreMap con funciones de membrecía GetFrame y FreeFrame. Si el administrador global de marcos tiene el nombre coreMap, podríamos tener el código siguiente:

pageTable[i].virtualPage = i;
pageTable[i].physicalPage = coreMap->GetFrame();
Por supuesto, cuando destruyamos el objeto de AddrSpace (cuando el proceso termine), debiéramos liberar los marcos con llamadas a FreeFrame.

Debiéramos llenar con ceros cada marco que asignemos a un proceso. También tenemos que pensar en el caso donde no haya más marcos libres en el sistema. Ya que no tenemos la memoria virtual, la creación del nuevo proceso fracasaría en este caso.

Después de la asignación de memoria a un proceso, tenemos que cargar el binario de proceso. Ahora tenemos un nuevo problema. El cargar es relativamente fácil en el Nachos original ya que cada dirección virtual es igual a la dirección física correspondiente. Pero con el uso de coreMap para asignar marcos no sabemos la relación entre las páginas y los marcos del proceso; los marcos no son necesariamente contiguos, por ejemplo.

Este problema es común en las interacciones entre un proceso y el sistema operativo. Si el sistema operativo tiene una dirección en el espacio de usuario, tiene que traducir la dirección, usando la tabla de páginas del proceso, en una dirección física. Tenemos que añadir una nueva rutina Translate a la clase AddrSpace que traduce una dirección lógica en una dirección física. Entonces, si el sistema operativo quiere (por ejemplo) copiar un string en la dirección virtAddr en el espacio de usuario, puede ejecutar lo siguiente:

physAddr = userSpace->Translate(virtAddr);
char *string = &(machine->mainMemory[physAddr]);
strcpy(copy, string);
Esto no es completamente correcto; el string puede atravesar dos páginas, y si el marco de la segunda página no es contiguo con el de la primera, el sistema operativo escribirá en el marco de otro proceso. Entonces tenemos que copiar cada byte individualmente, con una traducción para la dirección de cada byte. Por supuesto, hay muchas posibilidades para optimizar este proceso.

Para cargar un binario de usuario en Nachos, podemos leer un byte a la vez del archivo y escribir en la dirección física que corresponde a la destinación lógica del byte.

Implementa esta habilidad de cargar y ejecutar un proceso en cualquier parte de la memoria. Tienes que cambiar addrspace.h y addrspace.cc. Puedes implementar también coremap.h y coremap.cc si quieres usar el enfoque precedente para administrar la memoria (la clase BitMap en bitmap.h puede ser de interés para guardar el estado de los marcos del sistema). Asegura que halt aún funciona.

Llamadas de sistema para I/O

Tu próximo paso es la implementación de más llamadas de sistema. Aunque puedes escribir ahora programas de usuario en C que ejecutan algoritmos complejos y correrlos bajo Nachos, ellos no pueden comunicarse contigo. Para eso necesitamos llamadas de sistema para I/O.

En userprog/syscall.h están las declaraciones para las llamadas de sistema de Nachos. Los programas de usuario en C que llaman al sistema debieran incluir este archivo (por ejemplo, ver el código para halt).

Cuando un proceso de usuario haga una llamada de sistema, la CPU genera una interrupción y el control pasa a Nachos en la rutina ExceptionHandler en userprog/exception.cc. Aquí puedes insertar un enunciado de switch usando el valor de type para llamar tu implementación de la llamada de sistema. Debieras usar mi implementación de exception.cc en

/usr/local/nachos/iic2332/tarea2
Necesitarás también el archivo systemcall.h con las declaraciones para las funciones llamadas por ExceptionHandler. Tienes que implementar systemcall.cc.

Para ahora implementa solamente SysCallCreate, SysCallOpen, SysCallWrite, SysCallRead, y SysCallClose, que son las operaciones de I/O (los semánticos están en systemcall.h). Para cada operación, tienes que chequear si todos los argumentos son legales y volver (quizás con un código de error) si no. ¡No debe ser posible que un usuario pueda botar el sistema!

Recuerda también que las argumentos que son direcciones son direcciones en el espacio de usuario. Tienes que traducirlas antes de usarlas. El objeto de AddrSpace corriente (con la tabla de páginas del proceso) está en currentThread->space.

Es más fácil si empiezas con SysCallWrite y el caso donde el output es ConsoleOutput. Para escribir en y leer de la consola puedes usar mi implementación de SynchConsole en synchconsole.h y synchconsole.cc. Usando esta clase el thread de usuario esperará si es necesario. Si usas SynchConsole debieras crear la consola de sistema con el código siguiente, que puedes ubicar en StartProcess:

theConsole = new SynchConsole();
Para probar tu implementación tienes que escribir un programa pequeño en C, por ejemplo:
#include "syscall.h"
main() { Write("Test\n", 5, ConsoleOutput); Halt(); }
(Necesitas la llamada explicita a Halt ya que no tienes todavía una implementación de Exit, y main llamará Exit como un default cuando vuelva.)

Para compilar este programa, copia el directorio de test de iic2332/tarea2 a tu directorio de userprog. Haz un "make" en tu directorio de code/bin. Finalmente, edita el Makefile en userprog/test, inserta el nombre de tu nuevo archivo, y haz un "make". Puedes correr tu programa con "../nachos -x programa".

Para las llamadas de I/O tienes que mantener una lista de descriptores válidos de archivos abiertos. Esta lista es una parte del objeto de AddrSpace de cada proceso de usuario. Una llamada a Open añade un descriptor nuevo a la lista. Si un proceso sale sin cerrar todos sus archivos abiertos, tienes que cerrarlos durante la destrucción del proceso. También, tienes que manejar el caso en Read, Write, y Close donde un proceso pasa un descriptor ilegal a la llamada de sistema.

Para crear nuevos identificadores (para uso como descriptores, por ejemplo), yo uso un administrador global de identificadores. Si te interesa, puedes usar id.h y id.cc.

Para probar tu implementación, puedes usar los archivos filesysN.c en test.

Multiprogramación II

La implementación de SysCallExit, SysCallExec, y SysCallJoin completa el apoyo básico para multiprogramación.

Exec crea un nuevo thread y un nuevo objeto de AddrSpace. Entonces llama a Fork. El argumento a Fork puede ser una función que toma como argumento el objeto de AddrSpace y que ejecuta el mismo código que está en la segunda mitad de StartProcess.

Exec tiene que volver un identificador único para el nuevo proceso. Con una llamada a Join con este identificador un proceso puede esperar hasta que su subproceso termine y pueda recibir el código de salida del subproceso (que es el argumento a Exit). El diseño de esta funcionalidad es la parte más complicada de la tarea. Tienes que pensar en que información el proceso debe mantener sobre sus subprocesos, y cómo los subprocesos pueden actualizar esta información cuando salgan. La sincronización es importante aquí, y tienes que usar las abstracciones de la primera tarea. Puedes crear nuevas clases y archivos para manipular esta información; los objetos resultantes deben ser nuevos miembros de AddrSpace.

Debieras cambiar StartProcess para reflejar tu nueva implementación. Por ejemplo, StartProcess puede inicializar algunas variables de sistema y entonces llamar al mismo código al que SysCallExec llama. StartProcess inicia el primer proceso del sistema y Exec todos los otros.

Tú puedes probar el nuevo sistema con shell, kshell, consoleA, y consoleB en el directorio test.

El sistema en este estado no usa la expropiación en la planificación de procesos. Copia el archivo tarea2/system.cc al tu directorio de threads para usar la expropiación siempre. Prueba tu sistema baja esta política.

Otros apuntes

Entrega

Marca tus cambios al sistema con "#ifdef CHANGED" como antes. Crea un archivo README en el directorio code con una explicación del diseño de tu sistema y una guía a los archivos que cambiaste o creaste. Incluye una descripción de algunas (no más de cinco) programas de prueba (en userprog/test) que prueban tu sistema. Si hay errores que queden en tu sistema, avísanos (tú pierdes menos puntos en este caso).

Después de eliminar los archivos de ".o" y otros ejecutables (por ejemplo, nachos y programas de prueba), haz un "tar" de todo el directorio de code con

cd code; tar cvf ../nombre .
donde nombre es como en la primera tarea. Copia el archivo al directorio de entrega en iic2332.

Vamos a desempacar tu archivo de tar, hacer un "make depend" y "make" en userprog, y probar tu sistema con los programas en userprog/test y quizás otros también.

Reuniones de diseño

Para evitar desastres, cada grupo de trabajo tiene que juntarse conmigo para discutir su diseño (solamente los grupos que se juntan conmigo reciben una nota por la tarea). Voy a reservar el 9 y 10 de mayo para este objeto; habrá una hoja de reservas para reuniones. Puedes juntarte conmigo antes de estas fechas, y a más tardar el lunes el 13 de mayo.

Corrección de la tarea

Si quieres aumentar su nota (hasta 20%), puedes implementar una política de planificación más interesante o los threads al nivel de usuario. Habla conmigo antes.

Footnotes

 
(1)
Las copias del código estarán disponibles con Soledad, la nueva secretaria del departamento.

Last edited May 13, 1996, by knabe@ing.puc.cl