domingo, 17 de octubre de 2010

2.6 Implementación de procesos en MINIX

Los términos de “procedimiento”, “funcion”, y “rutina” se usaran constantemente. Las llamadas al sistema estarán en escritas, como READ.

Organización del código fuente MINIX

La ruta absoluta hacia los fuentes en C para una plataforma basada en Intel es /usr/src/. En el texto nos referiremos como src/ a ese directorio. Otro directorio importante es/usr/src/include, al cual nos referiremos como include/.

El directorio include/ contiene una cierta cantidad de archivos de cabecera PSOIX estándar, y además tiene los tres directorios:

  • sys/ cabeceras IOSIX adicionales
  • minix/ cabeceras usadas por el SO MINIX
  • ibm/ cabeceras con definicines específicas para los sistemas IBM-PC

A los fines de permitir extensiones a MINIX y programas que corren en el entorno de MINIX, hay otros archivos y subdirectorios en include/. Por ejemplo include/arpa/,include/net/ y su subdirectorio include/net/gen/ permiten extensiones de red.

El directorio src/ contiene otros tres importantes subdirectorios con código fuente del SO:

  • kernel/ capa 1 y 2 (procesos ,mensajes, controladores)
    • servers/mm/ código de administrador de memoria
    • servers/fs/ código del sistema de archivos

Hay otros tres directorios de código fuente que no están impresos ni discutidos en el libro, pero esenciales para producir un sistema en operación:

  • src/lib código fuente para los procedimientos de biblioteca (ej: open, read, etc.)
  • src/tools/ el Makefile y los scripts para iniciar el sistema MINIX
  • src/boot/ el ćodigo para arrancar e instalar un sistema MINIX.

Como MINIX es un sistema operativo experimental, concebido para ser modificado, hay un directorio src/test/ con programas diseñados para verificar completamente el nuevo sistema MINIX recién compilado.

El SO sirve para darle soporte a los mandatos (commands) que van a correr sobre él. En consecuencia hay un enorme directorio src/commands/ con los fuentes de los programas utilitarios: cat, cp, date, ls, pwd y otros 200 más).

MINIX consta de varios programas independientes que se comunican sólo mediante el pasaje de mensajes. Si hay un procedimiento con el mismo nombre en dos de esos programas, no hay ningún conflicto porque se enlazan en ejecutables diferentes. Algunas rutinas de src/lib/ son comunes a las tres partes del SO.

Además, como los drivers son programas independientes del núcleo, agregar un driver Ethernet y el server init se puede hacer después de cargar la imagen. Estos procesos se arrancan desde /etc/rcy se cargan en las regiones de memoria disponible para programas de usuario.

Los archivos de cabecera común

El directorio include/ y sus subdirectorios contienen una colección de archivos que definen constantes, macros y tipos. El estándar IOSIX requiere muchas de esas definiciones, y especifica en cuales archivo debe estar dentro del directorio principal include/ y su subdirectorio include/sys/. Los archivos dentro de esos directorio se llaman de cabecera o encabezado (header files) y tienen extensión .h; se los incluye mediante la sentencia #include en los fuentes C.

Los encabezados que hacen falta para compilar programas de usuario se encuentran principalmente en include/; para compilar programas y utilidades del sistema se usan encabezados que están en include/sys/. La distinción no es tan importante y en la compilación de un programa de usuario se pueden usan archivos pertenecientes a ambos.

Consideraremos cabeceras que son verdaderamente de propósito general, tanto es así que no son referenciados directamente por ningún archivo fuente del sistema MINIX. Los archivos son incluidos en otros archivos de encabezado.

Vamos a estudiar el primer archivo: include/ansi.h

El objetivo de ansi.h es verificar si el compilador que se está usando es conforme al estándar de C, como lo define la ISO, Estándar C se lo conoce como ANSI C. Si usamos un compilador ANSI C, habrá ciertas macros predefinidas como por ejemplo: __STDC__ que debe tener a 1 como valor. Este efecto es el mismo que se obtiene cuando el preprocesador lee la línea:

#define__STDC__1

La macro más importante en include/ansi.h es _PROTOTYPE. Esta macro nos permite escribir los prototipos de la funciones de la forma:

_PROTOTYPE (tipo_devuelto,nombre_func,(tipo_argumento argumento,…))

El segundo archivo en include/ que se incluye en la mayoría de los fuentes MINIX es limits.h. En este archivo se define algunos tamaños básicos, como la cantidad de bits en un entero para el lenguaje C, o límites del sistema operativo como la longitud de un nombre de archivo.

El siguiente grupo de archivos no se incluye en todos los encabezados maestros, pero se usan en muchos archivos en diferentes partes del sistema MINIX. El más importante esunistd.h. Este encabezado define muchas constantes, la mayoría requeridas por IOSIX. Además incluye prototipos de muchas funciones C, incluidas aquellas que se utilizan para acceder a las llamadas al sistema MINIX.

Los temporizadores vigías (watchdog timers) se describen en timers.h, al cual lo incluye el encabezado principal del kernel. Define la struct timer y los prototipos de funciones que se usan para operar sobre listas de timers. Se define el typedef tmr _func_t que es un puntero a función. Dentro de la estructura timer, ese puntero determina la función que se debe invocar cuando expira el tiempo del temporizador.

Los encabezados maestros de las partes principales de las llamadas al sistema de MINIX hacen que se lea inmediatamente espués de leer ansi.h. sys/types.h define muchos tipos de datos que usa MINIX. Todos los nombres de los tipos deben tener un sufijo _t, por un requerimiento IOSIX. Este es unsufijo reservado y no debe usarse como sufijo de nada que no sea un tipo.

Los archivos de cabecera MINIX

Los archivos en include/minix/ se usan para implemetar MINIX en cualquier plataforma. Los de inlcude/ibm/ tienen estructuras y macros específicas para implementar MINIX en máquinas de tipo IBM.

2.5 Perspectiva general de procesos en MINIX

A diferencia de UNIX cuyo núcleo es un programa monolítico, MINIX es un núcleo formado por una colección de proceso que se comunican entre sí y con los proceso de usuario mediante una única primitiva de comunicación interproceso: transferencia de mensajes. Este diseño proporciona una estructura más modular y flexible, lo cual hace que sea sencillo, por ejemplo, cambiar completamente el sistema de archivos sin que sea necesario ni siquiera recompilar el kernel.

La estructura interna de MINIX

MINIX se encuentra estructurado en cuatro capas, cada una de estas capas realiza una función que tiene definida.

El kernel en la capa inferior planifica los proceso y gestiona las transiciones entre los estados listo, corriendo y bloqueado. El kernel maneja también los mensajes entre proceso. El kernel también se ocupa de asistir en el acceso a los puertos de E/S y a las interrupciones, lo cual en los procesadores modernos requiere el uso de instrucciones privilegiadas de modo kernel que no están disponibles para los proceso ordinarios.

Una de las principales funciones de la capa 1 es proporcionar un conjunto de llamadas al kernel para que usen los drivers y servers que están por encima. Entre ellas tenemos la lectura y escritura de puertos de E/S el copiado de datos entre diferentes espacios de direcciones, etc. La implementación de estas llamadas está en la tarea del sistema. Aunque la tarea del sistema y la tarea del reloj están compiladas dentro del espacio de direcciones del kernel, se planifican como proceso separados y tienen sus propias pilas de llamadas (call stack).

La mayor parte del kernel y de las tareas del sistema y del reloj están escritas en lenguaje C.

Los proceso en capa 2 tienen la mayoría de los privilegios, aquellos de capa 3 tienen algunos privilegios, y los de capa 4 no tienen ningún privilegio. Por ejemplo los proceso en capa 2 que se denominan controladores de dispositivo (device drivers), tienen permitido realizar solicitudes a la tarea del sistema para que lea o escriba datos de los puertos de E/S a su favor. Se necesita un controlador para cada tipo de dispositivo: discos, impresoras, terminales, interfaces de red, etc. Los controladores de dispositivo pueden hacer otras llamadas al kernel, como por ejemplo que los datos recien leídos se copien al espacio de direcciones de otro proceso.

La capa 3 contiene proceso que proporcionan servicios que le son útiles a los usuarios.

La tercera capa contiene los servers, que son procesos que proporcionan servicios útiles a los procesos de usuario. Hay dos servidores esenciales. El process manager (PM) lleva adelante las llamadas al sistema de MINIX que involucran el arranque y detención de procesos tales como fork, exec y exit, y las llamadas relacionadas con las señales, como alarm y kill, que pueden alterar el estado de los procesos. El proceso manager también es responsable de gestionar la memoria con la llamada al sistema brk. El file system (FS) se ocupa de todas las llamadas al sistema de archivos, como read, mount y chdir.

Las llamadas al kernel son funciones de bajo nivel provistas por la tarea del sistema como servicio a drivers y servers. La lectura de un puerto de E/S es una llamada al kernel típica. Las llamadas al sistema POSIX como read, fork y unlink son llamadas de alto nivel definidas en el estándar POSIX y que están disponibles para los programas de usuario en la capa 4. Los programas de usuario pueden hacer llamadas POSIX pero no pueden hacer llamadas al kernel.

La capa 4 (superior) contiene todos los proceso de usuario: Shell, editores, compiladores, y programas a.out escritos por el usuario.

Por último, la capa 4 contiene todos los procesos de usuario: el shell, editores, compiladores y todos los programas a.out escritos por los usuarios. Un sistema en peración tiene algunos procesos que vienen y van a medida que los usuarios ingresan, trabajan y se desconectan. Otros procesos se inician con el sistema y perduran, como init, y también habrá varios demonios. Un demonio es un proceso en segundo plano que se se ejecuta periódicamente o que siempre espera por alguna clase de evento, com por ejemplo el arribo de un paquete por la interfaz de red. En cierto sentido, un demonio es un server que se inicia independientemente y corre como proceso de usuario.

Administración de proceso en MINIX

Los procesos en MINIX pueden crear otros procesos y ellos a su vez otros más, lo cual no da un árbol de procesos, del cual init es la raíz. Los servers y los drivers son casos especiales pues algunos de ellos deben iniciarse antes de cualquier proceso de usuario, incluso antes de init.

Cuando se enciende la computadora, el hardware lee en memoria el primer sector de la primer pista del disco de arranque y ejecuta el código que encuentra allí. En un disquete este sector contiene el programa de bootstrap. Es muy pequeño, pues debe caber en un sector (512 bytes). El bootstrap de MINIX carga un programa mayor, denominado boot, cuya misión es cargar el SO en sí mismo.

El caso de los discos rígidos es diferente y requiere un paso intermedio. El disco rígido se divide en particiones, y el primer sector del disco contiene un pequeño programa y la tabla de particiones. Colectivamente a esas dos partes se las denomina master boot record. El pequeño programa del primer sector se ejecuta para leer la tabla de particiones y seleccionar la partición activa. La partición activa tiene un bootstrap en su primer sector, el cual se carga y se ejecuta para arrancar la copia de boot en esa partición, exactamente como se hace cuando se arranca desde disquete.

Durante la fase de inicialización el kernel arranca la tarea del sistema y la tarea del reloj, y luego el PM y el FS. El PM y el FS cooperan luego para iniciar los otros servers y drivers que forman parte de la imagen de arranque, Cuando todos han funcionado y se han inicializado, se bloquean a la espera de algo para hacer.

service es la interfaz de usuario del RS. El RS arranca un programa ordinario y lo convierte en un proceso del sistema.

Las dos llamadas al sistema principales en MINIX para la gestión de procesos son fork y exec. fork es la única manera de crear un nuevo proceso. exec permite que un proceso ejecute un determinado programa. Cuando un programa se ejecuta se le asigna una porción de memoria cuyo tamaño se especifica en la cabecera del archivo del programa. El proceso mantiene como propia a dicha cantidad de memoria durante toda su ejecución, si bien la distribución entre segmento de datos, segmento de pila y espacio sin usar puede variar en el tiempo.

Toda la información sobre el proceso se mantiene en la tabla de procesos, que está divdida entre el kernel, el PM, y el FS, de manera que cada uno de ellos tiene los campos de la tabla que necesita. Cuando un nuevo proceso viene a la existencia (por fork), o cuando uno viejo termina (por exit o una señal), el PM actualiza primero su parte de la tabla de proceso y luego envía mensajes al fs y al kernel para pedirles que hagan lo mismo.

Comunicación entre proceso en MINIX

Se proporcionan tres primitivas para enviar y recibir mensajes, las cuales se pueden llamar mediante procedimientos de la biblioteca C:

  • send(dest, &message); para enviar un mensaje al proceso dest.
  • receive(source, &message); para recibir un mensaje del proceso source (o ANY).
  • Send_rec(src_dst, &message); para enviar un mensaje y esperar la respuesta del mismo proceso.

El segundo parámetro de los mensajes es la dirección local de los datos del mensaje. El mecanismo de pasaje de mensajes en el kernel copia el mensaje desde el emisor hacia el receptor. La respuesta (para Send_rec) sobrescribe el mensaje original. En principio este mecanismo del kernel se podría sustituir por una función que copie los mensajes sobre una red a la función correspondiente en otra máquina, para implemetar un sistema distribuído. En la práctica, esto se vería algo complicado por el hecho de que el contenido del mensaje algunas veces incluye punteros a estructuras de datos de gran tamaño, y entonces el sistema distribuído debería realizar la copia de esos datos también a través de la red.

Cada tarea, driver o proceso tiene permitido el intercambio de mensajes sólo con ciertos procesos. El flujo usual de mensajes es hacia abajo en las capas, y los mensajes pueden ser entre proceso de la misma capa o entre proceso de capas adyacentes. Los procesos de usuario no pueden enviarse mensajes entre sí.

Cuando un proceso envía un mensaje a otro proceso que no lo está esperando, el emisor se bloquea hasta que el receptor hace un receive. En otras palabras, en MINIX se usa el método cita para evitar el problema del buffering de los mensajes enviados pero aún no recibidos. La ventaja de este enfoque es que es simple y elimina la gestión de los buffers (incluso que se acabe el espacio de buffers). Además, como todos los mensajes son de tamaño fijo, determinado al momento de compilación, se previenen en forma estructural los errores causados por la escritura fuera de un buffer.

Existen otras primitivas relacionadas con la comunicación interproceso, pero son menos importantes que send, receive, sendrec, y notify.

Planificación de proceso en MINIX

La planificación de proceso en MINIX tiene prioridades, como tareas, administración de memoria servidor de archivos comparten la misma prioridad y los proceso de usuario.

El sistema de interrupciones es lo que mantiene en operación a un SO multiprogramación. Los procesos se bloquean cuando hacen solicitudes de entrada y de esa manera permiten que otros proceso se ejecuten. Cuando se dispone de la entrada lista, el proceso que está corriendo es interrumpido por el disco, teclado u otro hardware. El reloj también se usa para generar interrupciones que se usan para asegurar que los procesos que no solicitan entradas eventualmente suelten la CPU, para dar oportunidad a otros procesos de que corran. Las capas más bajas de MINIX tienen la función de ocultar esas interrupciones y transformarlas en mensajes. En cuanto a los proceso concierne, cuando un dispositivo de E/S completa una operación, le envía un mensaje a un proceso, despertándolo y tornándolo elegible para correr.

Cada vez que se interrumpe un proceso (por un dispositivo de E/S convencional, o por el reloj, o a causa de una interrupción de software) existe la oportunidad de determinar cuál de los procesos es el que merece disponer de la CPU. Claro que esto también se hace cuando un proceso termina, pero en un sistema como MInIX es más frecuente que ocurran interrupciones de E/S, del reloj o pasaje de mensajes, que terminaciones de proceso.

En cada tic del reloj se verifica para saber si el proceso actual ha corrido por más tiempo del quantum que tenía asignado. Si eso sucede, el planificador lo mueve al final de su cola (lo cual no requiere nada de esfuerzo si es el único en la cola). Luego se elige el siguiente proceso, como se describió más arriba. El proceso desalojado vuelve a correr inmediatamente solamente en el caso que esté solo en su cola y que no haya ningún proceso listo en las colas de mayor prioridad. Si no, el proceso que corre es el que está a la cabeza de la cola no vacía de mayor prioridad. Los drivers y los server esenciales disponen de quanta tan largos que normalmente nunca son desalojados por el reloj.