The UNIX Time-Sharing System - D.M. Ritchie y K. Thompson Traducción de las secciones V y VI por: M. Bordese y W. Alini Setiembre 2003 - Sistemas Operativos FaMAF - UNC V. PROCESOS E IMÁGENES Una imagen es un ambiente de ejecución de computadora. Incluye una imagen de memoria, valores de registros generales, estado de archivos abiertos, directorio actual y demás. Una imagen es el estado actual de una pseudo-computadora. Un proceso es la ejecución de una imagen. Mientras el procesador está ejecutando un proceso, la imagen debe residir en la memoria principal; durante la ejecución de otros procesos permanece en la memoria principal hasta la aparición de un proceso activo y de prioridad más alta que lo fuerza a ser intercambiado al disco (swap). La parte de memoria de usuario de una imagen está dividida en tres segmentos lógicos. El segmento de texto del programa empieza en la ubicación 0 del espacio virtual de direcciones. Durante la ejecución, este segmento está protegido contra escritura y una copia de él es compartida entre todos los procesos de ejecución del mismo programa. En el primer byte límite de protección de hardware por encima del segmento de texto del programa en el espacio virtual de direcciones empieza un espacio no compartido, con permiso de escritura cuyo tamaño puede ser extendido por una llamada de sistema. Empezando en la dirección más alta en el espacio virtual de direcciones hay un segmento de pila, el cual crece hacia abajo automáticamente mientras el stack pointer fluctúe. 5.1 Procesos Excepto cuando el sistema se está autocargando para operar, un nuevo proceso sólo puede empezar a existir por el uso de la llamada a sistema fork: processid = fork () Cuando fork es ejecutado, el proceso se divide en dos procesos en ejecución independientes. Los dos procesos tienen copias independientes de la imagen de memoria original, y comparten todos los archivos abiertos. Los nuevos procesos difieren sólo en que uno es considerado el proceso padre: en el padre, el 'processid' devuelto en realidad identifica al proceso hijo y nunca es 0, mientras que en el hijo, el valor retornado es siempre 0. Como los valores devueltos por fork para los procesos padre e hijo son distinguibles, cada proceso puede determinar si es el padre o el hijo. 5.2 Tuberías (pipes) Los procesos deben relacionarse con sus familiares usando las mismas llamadas a sistema 'read' y 'write' que son usadas por el sistema de archivos de entrada y salida. La llamada: filep = pipe() devuelve un file descriptor 'filep' y crea un canal de interprocesos llamado pipe. Este canal, como otros archivos abiertos, es pasado de procesos padres a hijos en la imagen por la llamada fork. Un 'read' usando un pipe file descriptor espera hasta que otro proceso escriba usando el file descriptor por el mismo pipe. En este punto, los datos son pasados entre las imágenes de los dos procesos. Ninguno de los procesos necesita saber que hay un pipe involucrado en vez de un archivo común. Si bien la comunicación interprocesos vía pipes es una herramienta muy valiosa (ver Sección 6.2), no es un mecanismo general completo, pues el pipe debe ser instaurado por un ancestro común de los procesos involucrados. 5.3 Ejecución de programas Otra primitiva de sistema trascendental es invocada por execute(file, arg1, arg2, ... , argn) la cual solicita al sistema leer y ejecutar el programa cuyo nombre es 'file', pasándole los argumentos 'arg1', 'arg2', ..., 'argn'. Todo el código y los datos en el proceso invocando 'execute' es reemplazado desde 'file', excepto archivos abiertos, directorio actual y relaciones interprocesos que permanecen inalterables. Sólo si la llamada falla, por ejemplo porque 'file' no pudo ser encontrado o porque su bit de permiso de ejecución no estaba habilitado, la primitiva 'execute' hace un return; se parece más a una instrucción de salto ("jump") que a una llamada a subrutina. 5.4 Sincronización de procesos Otra llamada a sistema de control de procesos: processid = wait(status) causa a quien lo llama suspender la ejecución hasta que uno de sus hijos complete la ejecución. Luego 'wait' devuelve el 'processid' del proceso terminado. Se devuelve error si el proceso que llama no tiene descendientes. También está disponible cierto estado del proceso hijo. 5.5 Terminación Por último: exit(status) termina un proceso, destruye su imagen, cierra sus archivos abiertos, y generalmente lo hace desaparecer. El padre es informado mediante la llamada 'wait', y se le deja disponible 'status'. Los procesos también pueden terminar como resultado de varias acciones ilegales o señales generadas por usuarios. VI. EL INTÉRPRETE DE COMANDOS (SHELL) Para la mayoría de usuarios, la comunicación con el sistema es llevada adelante gracias a la ayuda de un programa llamado shell. El shell es un intérprete de línea de comandos: lee líneas tipeadas por el usuario y las interpreta como pedidos de ejecución de otros programas. (El shell es descripto íntegramente en algún lado [9], con lo que en esta sección se discutirá sólo la teoría de su funcionamiento.) En un formato simple, una línea de comandos consiste en el nombre del comando seguido por argumentos al mismo, todo separado por espacios: command arg1 arg2 ... argn El shell divide el nombre del comando y los argumentos en palabras separadas. Luego un archivo de nombre 'command' es buscado; 'command' puede ser el nombre de ruta incluyendo el caracter "/" para especificar cualquier archivo en el sistema. Si 'command' es encontrado, es llevado a la memoria y ejecutado. Los argumentos recogidos por el shell están accesibles para el comando. Cuando el comando hubo terminado, el shell reanuda su propia ejecución, e indica que está listo para aceptar otro comando mostrando el prompt. Si el archivo 'comando' no pudo ser encontrado, el shell generalmente prefija una palabra como '/bin' a 'comando' e intenta encontrar el archivo de nuevo. El directorio '/bin' contiene comandos destinados a ser ejecutados generalmente. (La secuencia de directorios a ser buscados puede cambiar a pedido del usuario). 6.1 Entrada/Salida standard La discusión de E/S citada anteriormente en la sección III parece implicar que cada archivo usado por un programa debe ser abierto o creado por el programa a fin de obtener un file descriptor del archivo. Los programas ejecutados por el shell, sin embargo, se ponen en marcha con tres archivos abiertos con los file descriptors 0, 1 y 2. Al empezar un programa su ejecución, el archivo 1 es abierto para escritura, y es más conocido como el archivo de salida estándar. Excepto por circunstancias indicadas más abajo, este archivo es la terminal de usuario. De esta manera, programas que quieran escribir información comúnmente usan el file descriptor 1. Inversamente, el archivo 0 se pone en marcha para lectura, y los programas que quieran leer mensajes tipeados por el usuario leen este archivo. El shell es capaz de cambiar las asignaciones estándar de estos file descriptors que son la impresora de terminal y el teclado. Si uno de los argumentos de un comando es precedido por ">", el file descriptor 1 se referirá, a lo largo de la duración del comando, al archivo nombrado después del ">". Por ejemplo: ls comúnmente lista los nombres de los archivos del directorio actual. El comando: ls >there crea un archivo llamado 'there' y ubica el listado allí. Así, el argumento significa "ubica la salida en 'there'". Por otra parte: ed comúnmente ingresa el editor, el cual toma pedidos del usuario via su teclado. El comando ed