- Descripción de la funcionalidad del llamado al sistema "Fork"
- Parámetros recibidos en el llamado al sistema
- Primer y único parámetro: dirección de la rutina a ejecutar, se trata de una dirección a la memoria de MIPS
- Ejemplo, ver este programa de usuario
#include "syscall.h"
void hijo(int);
int id;
int main(){
id = SemCreate(0);
Fork(hijo);
SemWait(id);
Write("padre", 5, 1);
SemDestroy(id);
Exit(0);
}
void hijo(int dummy){
Write( "hijo", 5, 1 );
SemSignal(id);
}
- En este ejemplo se pasa la dirección del método "hijo" como parámetro del "Fork"
- Este ejemplo utiliza los llamados al sistema de semáforos y si están bien implantados deberá desplegar "hijo padre"
- Funcionamiento esperado
- Este llamado al sistema debe lograr que un nuevo hilo ejecute la rutina especificada por el usuario, "hijo" en el ejemplo y permita al hilo llamador del método o "papa" continuar corriendo, o sea, el "papa" debe seguir con las instrucciones que están luego del "Fork", "SemWait(id);" en nuestro ejemplo, mientras que el hijo debe ejecutar la rutina "hijo"
- Suponemos que el hijo completa la rutina indicada y finaliza
- No podemos suponer ningún orden de ejecución de los hilos en NachOS
- Los hilos de NachOS deben compartir código y datos, pero no pila
- Estructura de los programas de usuario de NachOS
- Estructura de los programas de usuario de NachOS en la arquitectura MIPS en disco ("in-disk image" formato NOFF)
Encabezado (H) |
Texto (TX) |
Datos Inicializados (DI) |
- Estructura del encabezado de un programa de usuario de NachOS
Número mágico |
Descriptor de segmento de código |
Descriptor de segmento de datos inicializados |
Descriptor de segmento de datos no inicializados |
- Estructura de un descriptor de segmento
Tamaño |
Dirección virtual en memoria |
Dirección en archivo |
- Estructura detallada del encabezado (bytes)
Header (40) |
Magic number (4) |
Code Size (4) |
Code Virtual Address (4) |
Code InFile Address (4) |
Init Data Size (4) |
Init Data Virtual Address (4) |
Init Data InFile Address (4) |
Uninit Data Size (4) |
Uninit Data Virtual Address (4) |
Uninit Data InFile Address (4) |
- Espacio de memoria virtual para un proceso de NachOS
- Todos los hilos de NachOS tienen un espacio de memoria virtual donde se encuentra el código, los datos inicializados, los datos no inicializados y la pila. Estas secciones están contiguas en la memoria virtual y son inicializadas en el constructor de "AddrSpace"
- Mapa de memoria virtual de un proceso en NachOS
Text (Code) |
Init Data |
Uninit Data |
Stack |
- Las secciones de "Text" y "Init Data" son leidas directamente del archivo del programa ejecutable, mientras que para las otras secciones solo se reserva el espacio necesario. Ver la descripción de un programa de usuario más arriba. El compilador supone que el programa es cargado en la posición cero de la memoria, por lo que todas las direcciones generadas por el programa se toman a partir de cero, por eso se denominan direcciones virtuales.
- La memoria física se divide en marcos de 128 bytes cada uno, en total la máquina cuenta con 32 marcos, estas direcciones se denominan direcciones físicas. Esta cantidad de puede variar cambiando la constante "NumPhysPages" en "machine.h" del directorio "machine". Es una de las pocas cosas que pueden cambiar del hardware de este proyecto.
#define NumPhysPages 32
- Cada programa de usuario es ejecutado por un hilo dentro del kernel de NachOS, estos son los hilos que manejamos con la clase "Thread" y sus métodos asociados. Cada uno de estos hilos en el proyecto "userprog" cuenta con una variable asociada "space", de tipo "AddrSpace", que representa como está distribuida la memoria del proceso en la memoria física de la máquina.
- Cada variable de tipo "AddrSpace" tiene una tabla de páginas, "pageTable" que establece la relación entre las direcciones virtuales y las direcciones físicas asignadas al hilo. El código original de NachOS carga todos los programas en la posición cero de memoria física, como se muestra en esta parte del constructor de "AddrSpace":
...
// first, set up the translation
pageTable = new TranslationEntry[numPages];
for (i = 0; i < numPages; i++) {
pageTable[i].virtualPage = i;
pageTable[i].physicalPage = i;
pageTable[i].valid = TRUE;
pageTable[i].use = FALSE;
pageTable[i].dirty = FALSE;
pageTable[i].readOnly = FALSE; // if the code segment was entirely on
// a separate page, we could set its
// pages to be read-only
}
...
- "numPages" representa la cantidad total de páginas que tiene este proceso, incluye "Text", "Init Data", "Uninit Data" y "Stack".
- Rutina de arranque para programas de usuario en NachOS
- Similar a otros sistemas operativos, todos los programas de usuario de NachOS son enlazados con una rutina de inicialización, hecha en ensamblador "start.s". En este código se puede estudiar como hace un programa de usuario para comenzar su ejecución, adjuntamos esta parte del código:
/* -------------------------------------------------------------
* __start
* Initialize running a C program, by calling "main".
*
* NOTE: This has to be first, so that it gets loaded at location 0.
* The Nachos kernel always starts a program by jumping to location 0.
* -------------------------------------------------------------
*/
.globl __start
.ent __start
__start:
jal main
move $4,$0
jal Exit /* if we return from main, exit(0) */
.end __start
- Observe que lo primero que se hace es llamar a la rutina "main" con esta instrucción de ensamblador MIPS "jal main", y como lo indican los comentarios, si esta rutina regresa entonces se llama a "Exit", esto garantiza que todos los programas de usuario llamen a "Exit" cuando finalizan. La única forma de que "main" no retorne es que el programa de usuario llame directamente a "Exit"
- Recordamos que todas las instrucciones del ensamblador de MIPS son de cuatro bytes. El primer byte representa el código de operación
- El programa de usuario es cargardo a partir de la posición lógica 0 (cero) de memoria, por lo que el "jal main" queda en esa dirección, en consecuencia, el "move $4,$0" queda en la posición 4 y el "jal Exit" en la 8. Esta es la razón por la cual debemos hacer que la dirección de retorno de la rutina a la que el usuario le hizo Fork sea la dirección 4.
- Advertencia: si por alguna razón el llamado al sistema "Exit" retorna, el simulador continuaría ejecutando instrucciones de la posición 12 en adelante, lo que normalmente produce una excepción de instrucción ilegal ("illegal instruction exception"), que es atrapada por el "ExceptionHandler", pero cuando el simulador se la encuentra finaliza la simulación completa y termina NachOS
- Agregar el método en ExceptionHandler que se va a encargar de atender el llamado al sistema "Fork"
- En "exception.cc", debemos agregar en el método "ExceptionHandler" la opción para atrapar este llamado
case SyscallException:
switch ( type ) {
case SC_Halt: // System call # 0
Nachos_Halt();
break;
// ...
case SC_Write: // System call # 7
Nachos_Write();
break;
// ...
case SC_Fork: // System call # 9
Nachos_Fork();
break;
- También debemos agregar el método "Nachos_Fork" para atender el llamado
void Nachos_Fork() { // System call 9
DEBUG( 'u', "Entering Fork System call\n" );
// We need to create a new kernel thread to execute the user thread
Thread * newT = new Thread( "child to execute Fork code" );
// We need to share the Open File Table structure with this new child
// Child and father will also share the same address space, except for the stack
// Text, init data and uninit data are shared, a new stack area must be created
// for the new child
// We suggest the use of a new constructor in AddrSpace class,
// This new constructor will copy the shared segments (space variable) from currentThread, passed
// as a parameter, and create a new stack for the new child
newT->space = new AddrSpace( currentThread->space );
// We (kernel)-Fork to a new method to execute the child code
// Pass the user routine address, now in register 4, as a parameter
// Note: in 64 bits register 4 need to be casted to (void *)
newT->Fork( NachosForkThread, machine->ReadRegister( 4 ) );
returnFromSystemCall(); // This adjust the PrevPC, PC, and NextPC registers
DEBUG( 'u', "Exiting Fork System call\n" );
} // Kernel_Fork
- Debemos crear el método para ejecutar el código del usuario
// Pass the user routine address as a parameter for this function
// This function is similar to "StartProcess" in "progtest.cc" file under "userprog"
// Requires a correct AddrSpace setup to work well
void NachosForkThread( void * p ) { // for 64 bits version
AddrSpace *space;
space = currentThread->space;
space->InitRegisters(); // set the initial register values
space->RestoreState(); // load page table register
// Set the return address for this thread to the same as the main thread
// This will lead this thread to call the exit system call and finish
machine->WriteRegister( RetAddrReg, 4 );
machine->WriteRegister( PCReg, (long) p );
machine->WriteRegister( NextPCReg, (long) p + 4 );
machine->Run(); // jump to the user progam
ASSERT(FALSE);
}