5.1. MACH.

  1. Historia y objetivos de Mach.
  2. Arquitectura de Mach.
  3. Administración de procesos en Mach.
  4. Administración de hilos en Mach.
  5. Planificación de hilos en Mach.

Mach es un ejemplo de sistema operativo basado en un microkernel, que provee gestión de tareas/hilos (task/threads), multitarea multihilo, soporte multiprocesador y comunicación entre procesos con mensajes mediante puertos de acceso. Fue uno de los pioneros en la implementación de hilos. Mach es un sistema operativo portable a diversas arquitecturas paralelas y es compatible con UNIX. La estructura y diseño de Mach ha servido como prototipo para el desarrollo de nuevos sistemas operativos basados en microkernel, como OSF/1, Windows NT o SunOS, de ahí su enorme importancia.

A continuación se presenta una breve historia de Mach, los objetivos de su desarrollo, su arquitectura, la gestión de procesos e hilos que realiza, y la forma de llevar a cabo la planificación de los hilos.

Historia y objetivos de Mach.

En 1975 en la Universidad de Rochester se creó un sistema llamado RIG (Rochester Intelligent Gateway), cuyo objetivo era demostrar que los sistemas operativos se pueden estructurar de forma modular como un conjunto de procesos que se comunican entre sí mediante paso de mensajes, incluso a través de una red. A partir de RIG nació Accent, que añadió protección, transparencia en las operaciones de red, memoria virtual de 32 bits y otras características. La primera versión apareció en 1981.

En 1984, se inició el proyecto Mach a partir de Accent, con el objetivo de que Mach fuese compatible con UNIX, el cual empezaba a dominar en el mercado. Mach tenía ciertas mejoras en relación a Accent como eran el soporte de hilos, mejores mecanismos de comunicación entre procesos, soporte de multiprocesador y un nuevo sistema de memoria virtual.

Durante esa época, la Agencia de Proyectos de Investigación Avanzada del Departamento de Defensa de los EE.UU. (DARPA) buscaba un sistema operativo que soportara multiprocesadores. Se eligió a Carnegie Mellon University (CMU), y mediante fondos de DARPA, Mach se desarrolló mucho más. Se decidió que el sistema fuese compatible con 4.2bsd, uniendo Mach y 4.2bsd en un mismo núcleo. Esto generó un kernel de gran tamaño, pero garantizó la compatibilidad absoluta con UNIX 4.2bsd.

La primera versión de Mach apareció en 1986 para la VAX 11/784, que empleaba 4 CPUs. Después el sistema se portó a otras plataformas como Sun 3, IBM PC/RT, Encore, Sequent, etc. Aunque Mach podía manejar redes, en esa época se empleaba más como sistema operativo de una sola máquina, que como sistema operativo distribuido con un conjunto de máquinas en una LAN.

Después se creó la Open Software Foundation, consorcio liderado por IBM, DEC y Hewlett-Packard, con el fin de competir con AT&T en el mercado UNIX. OSF eligió a Mach 2.5 como la base de su primer sistema operativo OSF/1. El objetivo era controlar la dirección en la que se encaminaba UNIX.

En 1988, el kernel de Mach 2.5 era grande y monolítico, ya que contenía gran parte del código UNIX de Berkeley. En 1989, CMU eliminó todo el código de Berkeley del núcleo y lo recolocó en el espacio de usuario, dejando un microkernel sólo con Mach. Había nacido Mach 3.0, que sería la base de las versiones futuras de OSF.

Los objetivos de Mach han ido variando a lo largo del tiempo, desde el inicio de su concepción, aunque los objetivos principales de Mach son :

  1. Proporcionar una base para la construcción de otros sistemas operativos, como UNIX.
  2. Soporte de un espacio de direcciones de gran tamaño.
  3. Soporte de acceso transparente a los recursos de red.
  4. Soporte de paralelismo hardware (multiprocesadores y multicomputadores) y software.
  5. Realizar un sistema transportable a un gran número de máquinas.

El objetivo es explotar los multiprocesadores y los sistemas distribuidos, a la vez que se pueden emular sistemas ya existentes, como UNIX, MS-DOS y MAC-OS.

Arquitectura de Mach.

El kernel o núcleo de Mach es un micronúcleo que proporciona sólo la administración de procesos, la administración de memoria, la comunicación y la gestión de E/S. El sistema de archivos, gestión de directorios y otros funciones típicas en los sistemas operativos quedan fuera del kernel y se localizan en procesos dentro del espacio de usuario. La idea es que se proporcionen los mecanismos necesarios para que el sistema funcione, pero dejando la política para los procesos de nivel usuario.

El microkernel se diseñó como una base donde se pudiese emular UNIX y otros sistemas operativos. La emulación se realiza mediante una capa software que se ejecuta fuera del núcleo, en el espacio de usuario. Se pueden ejecutar varios emuladores de sistemas al mismo tiempo en una misma máquina.

Mach fue diseñado con la intención de integrar la funcionalidad de un sistema multiprocesador y un sistema distribuido. El UNIX convencional ha sido adaptado en Mach para soportar procesamiento paralelo en sistemas multiprocesador y multicomputador.

El kernel de Mach contiene esencialmente 3 funciones de servicio básicas: planificación del procesador (processor schedulling), comunicación entre procesos (interprocess communication) y gestión de memoria virtual (virtual memory management). El resto de servicios son tareas de servicio (service tasks) que son tratadas como programas independientes de nivel de usuario.

El núcleo de Mach maneja 5 abstracciones : tarea o proceso (task), hilo (thread), puerto (port), mensaje (message), y objetos de memoria (memory objects), que se describen a continuación :

  1. Proceso o tarea : Es un entorno donde se lleva a cabo la ejecución. Posee un espacio de direcciones que emplea un mapa estructurado de objetos de memoria, en el que se encuentran el texto y datos del programa, y una o más pilas. El proceso es la unidad básica para la asignación de recursos, pero no es una entidad ejecutable sino el marco sobre el que se ejecutan los hilos. Como ejemplo, un canal de comunicación pertenece a un único proceso.
  2. Hilo: Es una entidad ejecutable. Posee un contador de programa y un conjunto de registros. Cada hilo es parte de un único proceso. Un proceso con un hilo es similar a un proceso UNIX. Es la entidad básica que se ejecuta dentro del entorno definido por el proceso.
  3. Objeto de memoria : Es una estructura de datos que se puede asociar con el espacio de direcciones de un proceso. Los objetos de memoria ocupan una o más páginas de memoria y constituyen la base del sistema de memoria virtual de Mach.
  4. Puertos : Es una especie de buzón empleado para la comunicación entre procesos.
  5. Mensajes : Son las entidades empleadas en la comunicación entre procesos. Se depositan en los puertos creados para realizar la comunicación.

Figura 5-1 Estructura de la emulación en Mach.

En Mach, la comunicación entre procesos se realiza mediante el paso de mensajes. Los puertos son buzones especiales que un proceso solicita al kernel para poderse comunicar. El puerto se almacena en el núcleo y tiene asociada una cola ordenada de mensajes. Un proceso adquiere una posibilidad para poder enviar o recibir a través de uno de sus puertos. La posibilidad consiste en un puntero al puerto y un conjunto de derechos que el otro proceso tiene respecto al puerto. Cuando un proceso adquiere la posibilidad puede recibir (o emitir según permisos), y el otro proceso puede emitir (o recibir según permiso).Toda la comunicación en Mach se realiza mediante este proceso.

Administración de procesos en Mach.

La parte del kernel de Mach relativa a la administración de procesos se encarga de los procesos, los hilos y la planificación.

Un proceso en Mach consiste en un espacio de direcciones y un conjunto de hilos ejecutándose sobre dicho espacio. Así pues, los procesos son pasivos. La ejecución está asociada a los hilos, que son dinámicos. Los procesos agrupan todos los recursos comunes a un grupo de hilos que cooperan entre sí.

Figura 5-2 Un proceso en Mach.

Un proceso también tiene una serie de puertos especiales y otras propiedades:

- Puerto de Proceso: Es el puerto empleado para la comunicación con el núcleo. Muchos servicios del núcleo son solicitados por el proceso enviando un mensaje a dicho puerto, en vez de realizar una llamada al sistema. De esta forma se reduce el número de llamadas al sistema al mínimo.

- Puerto de Arranque: Se emplea para la inicialización al comienzo de un proceso. El proceso lee dicho puerto de arranque para conocer los nombres de los puertos del núcleo que proporcionan los servicios esenciales.

- Puerto de Excepción: Es empleado por el sistema para informar de errores al proceso, como la división por cero y la ejecución de una instrucción inválida. Es empleado también por los depuradores.

- Puertos Registrados: Proporcionan un método de comunicación entre el proceso y los servidores estándar del sistema.

Un proceso, independientemente del estado de sus hilos, puede ser ejecutable, en cuyo caso los hilos que además no estén bloqueados se pueden planificar y ejecutar, o bien el proceso puede estar bloqueado, con lo cual los hilos no se pueden ejecutar, sin importar el estado en el que se encuentren. Un proceso también consta de una serie de parámetros de planificación, como es la especificación de los procesadores donde se pueden ejecutar los hilos, y la prioridad preestablecida. Además, cada proceso tiene asociadas una serie de estadísticas, como cantidad de memoria consumida, tiempos de ejecución de sus hilos, etc.

Como anécdota decir que un proceso Mach no tiene uid, gid, vector de señales, directorio raíz, directorio de trabajo o un vector con los descriptores de ficheros, como ocurre en los procesos UNIX. Toda esta información es manejada por el paquete de emulación de UNIX por lo que el núcleo no conoce su existencia.

Mach proporciona un pequeño conjunto de primitivas para la gestión de procesos. La mayoría de estas primitivas emplean el puerto de proceso, en vez de realizar llamadas directas al sistema. Las primitivas principales son:

Administración de hilos en Mach.

En Mach los hilos son las entidades activas, la parte dinámica de los procesos. Los hilos ejecutan las instrucciones y gestionan los registros y su espacio de direcciones. Un hilo pertenece a un único proceso. Un proceso no puede realizar nada si no tiene al menos un hilo. Todos los hilos de un proceso comparten el mismo espacio de direcciones y todos los recursos del proceso.

Sin embargo, un hilo también tiene recursos particulares (locales a cada hilo) como el Puerto del Hilo que es un puerto análogo al puerto del proceso, que el hilo utiliza para la comunicación con el núcleo, para llamar a ciertos servicios del kernel específicos para hilos, como la salida al finalizar el hilo. Como los puertos son recursos, cada hilo tiene acceso a los puertos de sus hermanos, con lo que cada hilo puede controlar al resto si es necesario.

Los hilos son gestionados por el kernel, son "hilos pesados", en contraposición con los llamados "hilos ligeros" de nivel usuario (no conocidos por el kernel). La creación y destrucción de hilos se realiza en el núcleo empleando ciertas estructuras de datos, que proporcionan mecanismos básicos para el manejo de múltiples actividades en el mismo espacio de direcciones.

Los hilos se ejecutan mediante tiempo compartido en un sistema uniprocesador, mientras que en un sistema multiprocesador se pueden ejecutar varios hilos al mismo tiempo. Este paralelismo obliga a que la exclusión mutua, la sincronización y la planificación resulten de gran importancia con el objetivo de alcanzar el mejor rendimiento y garantizar la corrección. Estos aspectos han resultado primordiales en los últimos diseños de Mach, buscando su ejecución en sistemas multiprocesador. Mach no proporciona en el propio núcleo mecanismos explícitos para lograr la sincronización entre hilos que acceden a recursos compartidos, sino que normalmente se construyen paquetes de nivel usuario para explotar las características de la sincronización hardware.

Al igual que un proceso, un hilo puede ser bloqueado o ejecutable. Cada hilo posee un contador que se incrementa o decrementa de forma similar al contador de proceso. Si el contador es 0, el hilo se puede ejecutar, pero si es mayor que 0, el hilo debe esperar a que otro lo reduzca. De esta forma un hilo puede controlar a los demás.

Existen varias primitivas para el control de los hilos que proporciona la interfaz básica del núcleo. Por encima de estas primitivas se pueden construir paquetes de hilos de nivel de usuario, como es el caso del paquete C-Threads que se explica en la sección de paquetes de hilos.

Planificación de hilos en Mach.

La ejecución en multiprocesadores ha influido en el sistema de planificación de Mach. Lo que se hace es dividir los procesadores del sistema en conjuntos de procesadores mediante software. Cada CPU pertenece a un único conjunto. Un hilo se puede asignar a un conjunto de procesadores. De esta forma tenemos un conjunto de procesadores con un conjunto de hilos asociados para ejecución. La función del algoritmo de planificación es asignar los hilos a las CPU del conjunto de forma justa y eficaz. Cada conjunto de procesadores está aislado de cualquier otro conjunto de procesadores. Empleando este mecanismo los procesos tienen el control de sus hilos. Un proceso puede asignar un hilo de cálculos intensivos a un conjunto de procesadores dedicados, sin hilos adicionales, garantizando la ejecución continua del hilo. El proceso puede reasignar los hilos a otros conjuntos de procesadores dinámicamente, de forma que se mantenga la carga de trabajo balanceada.

La planificación de hilos en Mach se basa en un sistema de prioridades inverso del 0 (máxima) al 31 (mínima) como en UNIX. Cada hilo tiene tres tipos de prioridad asignadas:

- prioridad base: Es la prioridad establecida por el hilo inicialmente.

- prioridad máxima: Es el mínimo valor (máxima prioridad) que el hilo puede establecer como prioridad base.

- prioridad actual: Es la prioridad real que el núcleo calcula como la suma de la prioridad base y una función que depende del tiempo de utilización reciente de la CPU por el hilo. prioridad_actual = prioridad_base + funcion(Uso_reciente_CPU_hilo)

Cuando se emplea el paquete de nivel usuario C-Threads en Mach en su segunda implementación, como es el caso habitual, los hilos son visibles para el kernel. Los hilos compiten por la CPU entre sí sin tener en cuenta a qué proceso pertenece cada hilo. El planificador tampoco lo tiene en consideración, pues planifica hilos independientemente del proceso al que pertenecen..

Cada conjunto de procesadores tiene asociado un vector de 32 colas de ejecución, correspondientes al rango de prioridades 0-31. En estas colas se encuentran sólo los hilos en estado ejecutable. Cada cola de ejecución tiene asociada tres variables:

- un mútex: se emplea para cerrar el acceso al vector de las colas, y garantizar que la estructura de datos es manejada por una sola CPU a la vez de todo el conjunto de procesadores.

- contador: es un contador del número total de hilos en todas las colas del vector. Si su valor es 0, no existen trabajos a realizar.

- hint: es un indicador de la posición del hilo con mayor prioridad.

Este vector de colas de ejecución para un conjunto de procesadores se llama vector de colas de ejecución glocal. Pero además cada CPU tiene su propio vector de colas de ejecución local. Estas colas de ejecución local contienen aquellos hilos que tienen su ejecución exclusiva en dicha CPU. Estos hilos no se encuentra en una cola de ejecución glocal.

El algoritmo básico de planificación es el siguiente:

- Si un hilo se bloquea, finaliza o agota su quantum de tiempo, la CPU donde se ejecutaba busca primero en su vector de colas de ejecución local, inspeccionando la variable contador, para ver si existen hilos activos. Si el contador es distinto de 0 comienza la búsqueda a partir de la cola de prioridad indicada por la variable hint, por ser de máxima prioridad.

- Si no existen hilos en las colas locales, se repite el algoritmo para el vector de colas de ejecución glocal, pero si tampoco existen hilos, se ejecuta un hilo especial inactivo hasta que exista un hilo listo.

- Si se localiza un hilo ejecutable, se planifica y se ejecuta durante un quantum. Al terminar el quantum se comprueban las colas locales y glocales para ver si existen otros hilos ejecutables de su misma o mayor prioridad, teniendo en cuenta que todos los hilos de la cola local tienen mayor prioridad que los hilos de las colas glocal. Si se localiza un candidato se produce una conmutación de hilos, pero si no es así, se ejecuta de nuevo el hijo durante otro quantum de tiempo.

- El quantum en los sistemas multiprocesador puede ser variable. Cuanto mayor es el número de hilos y menor el número de CPU, menor será el quantum asignado. Esto proporciona tiempos de respuesta bajos para solicitudes cortas, incluso con una gran carga de trabajo, y proporciona una alta eficacia en casos de poca carga.

- Con cada pulso de reloj, la CPU incrementa el valor de la prioridad del hilo en ejecución, lo que hace bajar su prioridad. Los contadores de prioridad de los hilos en espera se decrementan en su valor cada cierto tiempo, lo que hace subir su prioridad para su planificación.

Además de las prioridades, Mach proporciona otra forma de control de planificación adecuada para un conjunto de hilos que cooperan en un trabajo, el gancho (hook). Esto le proporciona a los hilos un control adicional sobre la planificación. El gancho es una llamada al sistema que permite a un hilo reducir su prioridad al mínimo durante unos segundos, lo que da a otros hilos la oportunidad de ejecutarse. Transcurrido ese tiempo, el hilo recupera su prioridad anterior. Además esta llamada permite especificar el hilo sucesor si se desea. Por ejemplo, un hilo puede enviar un mensaje a otro, entregar la CPU y solicitar que el hilo receptor del mensaje se pueda ejecutar a continuación. A este mecanismo se le suele llamar planificación manos-fuera, ya que se salta las colas de ejecución. Empleado de forma adecuada puede mejorar el rendimiento de una aplicación. Es empleado en ocasiones por el núcleo para optimizar algunas acciones.