Inicio > Sistemas Operativos > Implementación de Threads – ULT y KLT

Implementación de Threads – ULT y KLT

Los hilos o Threads descriptos en los post anteriores, tienen más de una implementación en la realidad. En forma general, existen dos tipos o dos clases de Threads, aquellos que se manejan a nivel Kernel y los que se manejan a nivel de Usuario.

¿Qué es esto de los niveles del Kernel y de Usuario?

En el mundo de los sistemas operativos existen básicamente dos niveles de operación, el nivel del Kernel o nivel de Sistema Operativo, y el nivel de Usuario, en donde se ejecutan la mayoría de las aplicaciones, entre ellas las que usamos diariamente o mismo las que nosotros fabricamos.

A nivel Kernel, se ejecutan todas las aplicaciones que tengan un cierto grado de compromiso con la integridad del sistema, y aquellos servicios de acceso a todos los dispositivos con los que cuenta el sistema en principio (este tema da para extenderse mucho más, y de seguro será tema de un futuro post :-)).

En el nivel de usuario, es en donde se ejecutan casi todas las aplicaciones de un sistema operativo actual. La manera de acceder a los dispositivos o servicios que ofrece el sistema operativo, generalmente es mediante lo que se conoce como System Call o llamada al sistema, para los amigos: syscall ;-). De esta forma se logra un cierto control sobre los recursos que posee el sistema operativo, ya que las aplicaciones de usuario sólo podrán acceder a los dispositivos o servicios del sistema mediante estos procedimientos. El tema con estas llamadas al sistema es, que muchas de ellas son bloqueantes, ¿y que quiere decir el término bloqueante?: una vez que un proceso pide un recurso de entrada salida por ejemplo, en el caso  de llamar a fread() de C, todo el proceso pasa a estar bloqueado en espera de la respuesta desde la controladora de la unidad de disco, mientras el planificador de tareas (task scheduller) sede el procesador a otro proceso en la lista de espera.

Entonces, volviendo a la implementación de los Threads, los KLT van a tener soporte del sistema operativo, el mismo va a conocer y a disponer de los servicios necesarios para trabajar con Threads. Esto tiene como ventaja que cuando un hilo solicite un recurso de entrada salida, no se bloqueará todo el proceso, sino que sólo el Thread o hilo que invoca este servicio del sistema operativo permanece a la espera de una respuesta por parte de la controladora del dispositivo.

Los ULT, están implementados desde una biblioteca creada a nivel de usuario que se encarga de todos los aspectos de la gestión de los hilos. En este caso, el sistema operativo no tiene por que conocer acerca de la existencia de los hilos en un determinado proceso, ya que este mismo se encargará de gestionar el funcionamiento de los hilos. La ventaja aquí es la no intervención del sistema operativo en esta tarea, lo cual implica la no existencia del cambio de contexto, o context switch (lo cual si existe para los KLT), esto reduce el overhead, pero a cambio, si un ULT pide un recurso de entrada/salida mediante una syscall bloqueante, hará que todo el proceso en donde este reside se bloquee también, ya que el sistema operativo no sabe que existen los hilos dentro de este proceso, y lo único que ve es que dicho proceso pide entrada salida, entonces lo bloquea por completo hasta obtener una respuesta por parte del dispositivo. Existen técnicas que permiten manejar el bloqueo a nivel proceso para poder gestionarlo internamente a nivel hilo de usuario, una de estas técnicas se conoce como Jacketing, básicamente consiste en no pedir de forma directa un recurso que provocará el bloqueo del proceso. Los ULT utilizan en lo posible syscalls no bloqueantes, las cuales se ejecutaran de forma asincronica, estas se manejan mediante una pequeña rutina de consulta que verificará el estado del dispositivo solicitado, y si este se encuentra ocupado, pasará el control a otro hilo en espera, y cuando le llegue nuevamente el turno del hilo que solicitó acceso al dispositivo, este repetirá nuevamente la consulta. Este procedimiento se llevará a cabo hasta que el dispositivo haya terminado con lo que se le pidió.

Un aspecto importante es que los ULT no permiten aprovechar el multiprocesamiento, es decir, no permiten la ejecución de dos o más ULT en procesadores separados (en paralelo), esto se debe a que el Kernel del sistema operativo que no conoce el manejo de Threads que realiza el proceso, asignará un proceso distinto a cada procesador, y como los hilos viven dentro de los procesos, no será posible llevar a cabo la paralelización.

Esto no sucede con el manejo de los KLT, ya que el Kernel conoce como manejar los hilos, y le es posible repartirlos entre los distintos procesadores de estar disponibles.

¿Pero entonces cuáles son más rápidos? ¿Qué es mejor, KLT o UTL?

Bueno la respuesta es: depende, ¿de qué depende?, y… de la naturaleza de la aplicación que se desea implementar.  Los KLT pierden tiempo en el cambio de contexto, lo cual no pasa con los ULT, pero los KLT permiten el uso de múltiples procesadores, lo cual no es posible con los ULT. Como se ve, todo depende de la aplicación en particular de la cual estemos hablando.

Existe también una combinación de ambas implementaciones, por ejemplo Solaris utiliza ambas, KTL y ULT, lo cual permite sacar provecho de las ventajas de una y de la otra, y así lograr un equilibrio que permita obtener un buen rendimiento. Windows 2000/XP, Linux en sus versiones actuales hacen uso de KLT.

Bueno, eso es todo por hoy, a quien le interese seguir leyendo:

  • Sistemas Operativos, de William Stallings
  • Sistemas Operativos, de Silberschatz Galvin
  • Sistemas Operativos, diseño e implentación, de Andrew S. Tanenbaum, Albert S. Woodhull
  • UNIX network programming – Volumen 1 (en especial el capítulo 6 en donde habla del uso de Multiplexacion de Entrada/Salida y Entrada Salida asincrónica), de W. Richard Stevens, Bill Fenner, Andrew M. Rudoff

Todos son buenas opciones para seguir leyendo sobre el tema.

Será hasta el próximo post.

Javier.

Categorías: Sistemas Operativos Etiquetas: ,
  1. 22 May 2009 a las 11:08 AM

    Hola, me gustaría saber que USOS tendrían los ULT de un proceso, tanto en sistemas monoprocesador como en sistemas multiprocesador, ya que los KLT se pueden ejecutar paralelamente en un multiprocesador, pero los ULT?? que ventajas tendrían a parte de la mayor flexibilidad en el cambio de contexto.

    Un saludo!

    • javiersegura
      22 May 2009 a las 12:36 PM

      Principalmente, al ser hilos manejados por una biblioteca al nivel de usuario, vos mismo podes crear tu biblioteca de manejo de Hilos, y crear un planificador con un algoritmo especial de planificación para tu propia aplicación, y no depender del algoritmo de planificación que te brinda el Kernel del sistema operativo en donde vas a implantar tu software. Esa es una ventaja importante, ya que estás incrementando la performance de tu software al elegir el mejor algoritmo de planificación para el manejo de hilos, y no es necesario adaptarte a los algoritmos que se te ofrecen, mismo podrías crear uno tuyo 😉

      Otra ventaja podría ser que los ULT pueden correr en cualquier sistema, ya sea uno que tenga un sistema operativo multiprogramado y sea multiprocesador, o mismo en un sistema que tenga un sistema operativo monoprogramado y sea monoprocesador, ya que la planificación va por cuenta de la biblioteca que trae con sigo la aplicación. Por ejemplo, podes estar corriendo un software concurrente creado en ADA bajo DOS en un 486, ya que ADA es un lenguaje concurrente y trae con sigo mismo un planificador, lo cual hace que ese software pueda manejar hilos en ese sistema, o mediante recompilación bajo Ubuntu corriendo sobre un Core I7 de 8 núcleos, es decir, es totalmente portable entre una plataforma y la otra sin necesidad de cambiar la forma en que se manejan los hilos.

      También, como mencioné en el post, hay sistemas operativos como Solaris, que admiten los dos tipos de Hilos, los ULT y los KLT, también en Linux es posible utilizar hilos KLT (PThreads – POSIX Threads) e hilos ULT (Pth – GNU Portable Threads), si por ejemplo se hecha mano de las dos últimas en una arquitectura de multiples procesadores o núcleos, uno podría tener una aplicación que trabaje con matrices, y necesite realizar cálculos de diagonalización de matrices o cambios de base que impliquen el producto entre matrices, en este caso podrías crear un ULT por cada producto fila-columna, y aprovechar el uso de los ULT sin que el Kernel intervenga en la planificación de estos, y luego, una vez finalizado el cálculo (obviamente todo el proceso de sincronización está implícito, sino el resultado podría ser cualquier cosa dependiendo de como esto se implemente) el resultado puede distribuirse entre hilos KLT para modificar ciertos archivos que dependen de estos cálculos, aprovechando el soporte del Kernel para distribuir hilos en distintos procesadores, y así ejecutar la escritura en los supuestos archivos en paralelo, de forma simultánea. Claro está que siempre es necesario evaluar si tal complejidad es necesaria. En el caso de tener un gran volumen de información a procesar, esta última solución funcionaría de forma aceptable, de lo contrario toda la lógica adicional para realizar la sincronización entre las distintas actividades implicaría un tiempo adicional, el cual habría que considerar a la hora de decidir si es necesario implementar un esquema concurrente y/o paralelo o no.

      Aplicaciones y/o ejemplos, hay muchos, depende de la necesidad y el caso puntual, no para todos los casos es mejor uno que el otro, todo es un balance entre portabilidad y performance dependiendo de la aplicación en particular.

      Espero haber podido contribuir con tus dudas.

      Un saludo,

      Javier.

      • 22 May 2009 a las 2:16 PM

        Muchas gracias, me ha sido de gran ayuda.

      • javiersegura
        22 May 2009 a las 2:47 PM

        No hay por que, me alegro que te haya servido 🙂

        Javier.

  2. dharamel
    10 septiembre 2009 a las 5:30 PM

    hola:

    como se podria hacer un pseudocodigo de esta técnica Jacketing

    • javiersegura
      11 septiembre 2009 a las 5:29 PM

      Los hilos a nivel de usuario son manejados principalmente por la aplicación de usuario, en especial por el planificador que brinda la biblioteca de hilos de usuario, la ventaja principal de esto, es la de no involucrar al sistema operativo momento del cambio de contexto, ya que esta tarea implica un tiempo valioso que se agrega al tiempo de ejecución de la tarea (este tiempo es conocido como overhead), la reducción de este tiempo al mínimo posible es el objetivo de los algoritmos de planificación. Siguiendo con esta idea, para el caso de los hilos de usuario no tendría sentido invocar a una llamada al sistema bloqueante, ya que todo el proceso se bloquearía e internamente todos los hilos desde el punto de vista del proceso también se bloquearían, y la idea en sí (para que el planificador de hilos de usuario pueda trabajar) no es la de bloquear a todo el proceso sino al hilo que hizo la llamada al sistema. Para dar lugar a que esto sea posible, es necesario implementar un mecanismo que permita realizar llamadas al sistema desde hilos de usuario que no bloqueen a todo el proceso, sino que el mismo planificador de hilos de usuario tenga la libertad de bloquear y desbloquear hilos según sea necesario de forma interna a la aplicación.
      La forma en la que esta técnica de Jacketing funciona, es invocando a una llamada al sistema como no bloqueante, y luego ir preguntado cada vez que el hilo que solicitó el servicio del sistema tenga su turno para usar el procesador, si el dispositivo al que solicitó el pedido mediante una llamada al sistema se encuentra ocupado, si es así, el planificador de hilos de usuario va a cambiar el estado de este hilo a bloqueado, va a guardar los datos necesarios en una estructura de datos llamada comúnmente TCB (Thread Control Block, o Bloque de Control de Hilo) para luego, cuando le toque nuevamente el turno a este hilo, saber desde donde debe continuar su ejecución. Una vez hecho esto, el planificador le va a ceder el tiempo de procesador a otro hilo de usuario en espera de ejecución. En realidad esta tarea es solidaria entre los hilos, si un hilo de usuario esta a la espera de un recurso y este da señales de estar ocupado, el mismo hilo llama al planificador para que este continúe con el siguiente hilo según lo indique la política de planificación utilizada. Estará a cargo del planificador y de la política utilizada, el manejo de prioridades y/o consideraciones a tener en cuenta en cuanto a un proceso en espera de un recurso.

      Un ejemplo en pseudo-código de esta técnica sería algo así:

      TreadFunc():
      ······ /// Open No Bloqueante.
      ······ FileDescriptor = Open(FilePath, READ | NON_BLOCK)
      ······ /// Intento de Lectura del Archivo
      ······ Line = Read(FileDescriptor)
      ······ /// Mientras el dispositivo esté ocupado
      ······ While( Line == BUSY ):
      ············ /// Llamada al Planificador de Hilos
      ············ Yield()
      ············ /// Nuevo Intento de Lectura del Archivo
      ············ Line = Read(FileDescriptor)

      Para ver un poco más acerca de técnicas de manejo de entrada salida no bloqueantes, podes consultar el libro: UNIX network programming, Volumen 1, hay una vista restringida del libro en Google Books, en especial en el capítulo 6 en donde habla de Multiplexación de Entrada/Salida en Unix. Este libro está muy bueno y tiene la particularidad de contener muchos ejemplos con los cuales uno puede ir probando cada uno de los temas, el código está en ANSI C, y está orientado a plataformas UNIX y GNU Linux, para el ejemplo de entrada y salida no bloqueantes se ve el tema de Sockets en un modelo práctico cliente/servidor.
      Otra cosa interesante es la biblioteca de ULT GNU Pth, es una muy buena opcion para empezar, como todo proyecto GNU es de código abierto, así que si te interesa el tema podes abrir el código y ver por tu cuenta como están implementadas estas técnicas, si necesitas alguna ayuda el manual de la API GNU Pth también está online.

      Espero te sirva de ayuda,

      Javier.

  1. No trackbacks yet.

Replica a javiersegura Cancelar la respuesta