Argument Passing
La primera asignación de los programas de usuario es lograr pasar los argumentos de consola a los procesos al momento de mandarlos a ejecutar.
En los programas que maneja PintOS, los argumentos que se le pasan al programa son argc y argv. Estos argumentos se deben de guardar en inicio del stack del proceso, en el caso de PintOS el stack empieza en PHYS_BASE
. El argument passing debe de cumplir el convenio de llamadas a funciones de Intel 80x86:
Caller hace push de los argumentos con un orden inverso (derecha a izquierda).
Caller hace push a la dirección de retorno al tope del stack.
El Callee ejecuta su función.
El Callee guarda su resultado de retorno, si tiene, en EAX (a0).
El Callee hace pop a la dirección de retorno y regresa a esa instrucción.
El Caller saca los argumentos del stack.
El stack pointer en PintOS está representado por el doble puntero **esp
.
PHYS_BASE
empieza en 0xc0000000
¿Cómo se cargan los procesos en PintOS?
Para entender como pasar los argumentos a un proceso, es importante saber como los procesos son cargados por el Sistema Operativo.
El thread padre llama a la función
process_execute(char *file_name)
. En este función se crea el nuevo thread con el nombre defile_name
y se configura para que ejecute la función destart_process(char* file_name)
y le envía al thread como parámetro afile_name
El nuevo thread empieza a ejecutar
start_process(char* file_name)
donde se empieza a cargar el proceso con la funciónload(char *file_name)
En la función
load(char *file_name)
se abre el archivo y se carga a memoria, si esta acción es exitosa, entonces se empieza a cargar los argumentos al stack con la funciónsetup_stack (void **esp, char* file_name)
En la función de
setup_stack (void **esp, char* file_name),
asume que los argumentos estan en el file_name y luego hace un push de cada argumento al stack y los punteros que apuntan a los argumentos.Si todo el proceso de load se realizó de manera exitosa, se empieza a correr el proceso, de lo contrario, se llama a la función
thread_exit()
para destruir el proceso.
Este es el comportamiento deseado, pero no es el comportamiento original que tiene la carga de procesos al iniciar la asignación.
Los primeros pasos antes de pasar los argumentos
La primera tarea para pasar los argumentos es ir a la función process_execute(char* file_name)
. En esta función se crea el thread que maneja al nuevo proceso con el nombre del proceso, pero el filename no solo incluye el nombre sino también incluye la lista de argumentos que se le pasaron en consola o en el syscall de exec.
Por ejemplo: Una llamada a un nuevo proceso puede ser el siguiente: new_process arg1 arg2 arg3
Entonces es necesario separar el nombre del archivo de sus argumentos. Para lograr esto se utiliza la función strtok_r(char* str, char* delim, char** save_ptr).
Y al conseguir el nombre con este tokenizer ya se crea el thread con el token del nombre y se le envía una copia de los parámetros al nuevo thread.
fn_copy
contiene una copia del file_name
En este momento, el control de la creación del proceso queda en manos del thread que se acaba de crear.
El nuevo thread empieza la creación del nuevo proceso en la función start_process(char* filename)
. En la creación del nuevo thread file_name
ya no contiene el nombre del archivo sino contiene la lista de parámetros del proceso. Para cargar el proceso se llama a la función load (const char* file_name, void (*eip) (void), void **esp)
En la función de load, se le pasaron la lista de argumentos en el parámetro defile_name.
En está función se abre el archivo y se carga el código a memoria, y por último se crea el stack.
Para hacer referencia al nombre del proceso se utiliza el nombre del thread actual.
Configurar el stack
En la configuración del stack se deben de pasar los argumentos al proceso y es el paso final antes de empezar a ejecutar el proceso. Está configuración se realiza en la función setup_stack (void **esp, char *args, char *name)
.
**esp
es el stack pointer*args
es la lista de argumentos*name
es el nombre del proceso
Algoritmo para configurar el stack de un proceso.
Indicar que el stack pointer (
**esp
) apunta aPHYS_BASE
(la dirección base de la memoria).Escribir los argumentos del proceso con un orden inverso (derecha a izquierda).
Escribir el nombre del proceso.
Alinear la memoria, respecto a 4 bytes, con 0.
Escribir de la dirección de
argv
Escribir la dirección de cada argumento del proceso.
Escribir la dirección de
argv[0]
Escribir
argc
(la cantidad de argumentos más el nombre)Escribir la dirección de retorno (para estos procesos es 0).
Para mover el stack pointer se hace lo siguiente: *esp -= # bytes
Paso #1
Paso #2
Se voltean los argumentos y se crea un arreglo para guardar las direcciones de cada arreglo. Por cada argumento en la lista de argumentos inversos se debe de bajar el stack pointer. Se baja el stack por el tamaño del arg más 1 porque hay que tomar en cuenta el NULL Terminator de un string.
Paso #3
Paso #4
Se debe de alinear la memoria porque al ingresar los argumentos o el nombre, el stack pudo quedar desalineado a la memoria, en lugar de apuntar al inicio de un word queda apuntando a otra parte de un word.
La operación para alinear la memoria es el módulo 4 del stack pointer para saber a que parte está apuntando. Se utiliza módulo 4 porque la memoria esta compuesta por words de 4 bytes.
La memoria se alinea con 0
Paso #5
Paso #6
El puntero se baja por el tamaño de un puntero char.
Paso #7
Paso #8
La cantidad de argumentos argc se fue contando mientras se escribían los argumentos al stack.
Paso #9
La dirección de retorno es 0 y es un puntero void.
Last updated