Windows NT es un sistema operativo multitarea y multiproceso preemptivo, en el cual se ejecutan concurrentemente diferentes procesos, y cada proceso puede tener diferentes hilos de ejecución a nivel kernel.
A continuación se presenta una breve historia de Windows NT, los objetivos de su desarrollo, su arquitectura, la estructura de su núcleo y la gestión de procesos e hilos que realiza.
En Octubre de 1988, Bill Gates, director de Microsoft, encargó a David N. Cutler el desarrollo del sistema operativo de nueva tecnología (NT, New Technology) de Microsoft para la década de los noventa. David Cutler ya había sido, anteriormente, el director de desarrollo del sistema operativo VAX/VMS de Digital, y a principios de 1989 se inició el desarrollo de Windows NT.
Se fijaron los siguientes requisitos básicos para el nuevo sistema :
Inicialmente Windows NT ofrecería un interfaz de usuario parecido al de OS/2 (sistema operativo que Microsoft comenzó a desarrollar en sus primeras versiones junto a IBM, pero finalmente abandonó el proyecto, siendo esta última la que continuaría su desarrollo), y ofrecería también su API (Application Programming Interface, o interfaz de programación de aplicaciones). Pero a mitad del desarrollo, la versión 3.0 de Microsoft Windows se introdujo en el mercado, siendo un rotundo éxito, en contraste con OS/2, que no conectó con el gran público. Por ello, Microsoft rectificó su política y decidió enfocar Windows NT como sistema evolutivo de Windows 3.0. De esta forma Windows NT tendría un interfaz gráfico similar a Windows 3.0 y ofrecería el API Win32 (extensión del API Win16 de Windows 3.0). El nuevo API ofrecería herramientas avanzadas de sistema operativo a las aplicaciones, gracias a características como procesos multihilo, sincronización, seguridad, E/S y gestión de objetos.
Además, antes de comenzar a escribir el sistema operativo, se fijaron una serie de objetivos de diseño, que fueron los siguientes :
El diseño de Windows NT se realizó empleando un modelo cliente/servidor para proporcionar múltiples entornos de sistemas operativos (inicialmente, Windows 16 bits, Windows 32 bits, MS-DOS, OS/2 y POSIX), un modelo de objetos para manejar los recursos del sistema de una forma uniforme, y un modelo de multiproceso simétrico (SMP, Symmetric MultiProcessing) para conseguir el máximo rendimiento en entornos multiprocesador.
Como características de multiproceso, se pueden destacar las siguientes :
La estructura de Windows NT se puede dividir en dos partes : la parte en modo usuario del sistema, que corresponde a los subsistemas protegidos o servidores, y la parte en modo kernel, que corresponde al ejecutor. Los subsistemas se comunican, entre ellos y con los clientes, por medio de paso de mensajes a través del ejecutor.
Existen dos tipos de subsistemas protegidos en NT : los subsistemas de entorno, que son servidores en modo usuario que proporcionan un API específico para un sistema operativo (subsistemas de Win16, Win32, OS/2, MS-DOS y POSIX), y los subsistemas integrales, que son servidores (en modo usuario o modo kernel) que realizan funciones importantes dentro del sistema operativo (subsistema de seguridad, subsistema de red, etc.). Cada subsistema protegido proporciona un API a las aplicaciones (clientes u otros servidores). Un servidor implementa la rutina de un API mediante el mecanismo LPC del ejecutor.
El ejecutor de Windows NT se ejecuta en modo kernel, y constituye un sistema operativo completo por sí mismo (salvo por la falta del interfaz de usuario, que se implementa en el subsistema Win32). Está formado por componentes, que poseen servicios del sistema, que pueden ser llamados por subsistemas de entorno y otros componentes del ejecutor, y servicios internos (sólo pueden ser llamados por componentes del ejecutor). El ejecutor no se ejecuta en un proceso propio, sino que se ejecuta en el contexto de un proceso existente. Así, cuando un hilo llama a un servicio del sistema, el kernel toma el control del hilo que se estaba ejecutando, invoca el código del sistema apropiado, lo ejecuta, y devuelve el control al código que se estaba ejecutando antes de la interrupción. Los componentes del ejecutor están diseñados de una forma modular e independiente, lo que facilita su mantenimiento.
Las responsabilidades de los componentes son :
Windows NT manipula todos los recursos como objetos. Los objetos pueden compartirse y residen en el espacio de memoria asignado al sistema operativo. Por ejemplo, si un usuario comienza a ejecutar una aplicación basada en Win32, el subsistema Win32 llama al administrador de procesos para crear un proceso donde se ejecutará la aplicación y abrir un descriptor hacia él. El administrador de procesos, a su vez, llama al administrador de objetos para crear un objeto proceso y un objeto hilo, tras lo cual la aplicación comienza a ejecutarse.
Un proceso en Windows NT representa una unidad de posesión de recursos y el trabajo a realizar, y consta de :
Los procesos se implementan mediante objetos y se accede a ellos empleando servicios de objetos. Los objetos proceso y los objetos hilo tienen características de sincronización internas. El administrador de procesos de NT no mantiene relaciones padre/hijo o de otro tipo entre los procesos que crea, a diferencia de otros sistemas operativos. Un proceso puede tener múltiples hilos de ejecución, dentro de su espacio de direcciones. Los hilos de un proceso de usuario se ejecutan en modo usuario, y solo pueden tener acceso al sistema operativo realizando llamadas a los servicios del sistema.
El objeto proceso, al igual que otros objetos, contiene una cabecera que es creada e inicializada por el administrador de objetos :
ATRIBUTO | PROPOSITO |
Identificador de Proceso | Valor único que identifica al proceso de cara al SO. |
Token de acceso | Objeto del ejecutor que contiene información de seguridad sobre la petición del proceso. |
Prioridad Base | Prioridad de ejecución básica para los hilos del proceso. |
Afinidad con la CPU por defecto | Conjunto de CPUs sobre los que ejecutar los hilos. |
Límites de Cuota | Máxima cantidad de memoria del sistema, espacio de paginado de ficheros, y tiempo de CPU que pueden emplear los hilos del proceso. |
Tiempo de Ejecución | Tiempo total consumido por los hilos del proceso. |
Contadores de E/S | Registro del Nº y tipo de operaciones de E/S realizadas por los hilos del proceso. |
Contadores de Operaciones VM. | Registro del Nº y tipo de operaciones de memoria virtual realizadas por los hilos del proceso. |
Puerto de Excepción/Depurador | Canales de comunicación interproceso a los que el administrador de procesos envía un mensaje cuando un hilo del proceso provoca una excepción. |
Estado de Salida | Motivo de la terminación del proceso. |
Un proceso es propietario de un espacio de direcciones, archivos, asignaciones de memoria dinámica, hilos y otros recursos. Los recursos creados durante la vida del proceso son destruidos al finalizar éste. El proceso como tal no ejecuta código, sino que proporciona el espacio de direcciones en el que se ejecutan los hilos.
Un proceso representa un trabajo que el sistema debe realizar, mientras que un hilo representa una de las muchas subtareas necesarias para llevar a cabo el trabajo.
Un hilo es una unidad de ejecución dentro de un proceso, con su contador de programa independiente, y es la unidad planificada por el kernel de Windows NT. Los hilos se pueden planificar de forma independiente en procesadores diferentes, ejecutándose en paralelo. Los hilos de un proceso residen dentro de su espacio de direcciones y comparten los recursos asignados al proceso. Un hilo consta de un identificador único, denominado ID Cliente, y un contexto de hilo, formado por :
Un procesador sólo puede ejecutar un hilo en un momento dado. Sin embargo, Windows NT es un sistema operativo que emplea multitarea preemptiva, y da la impresión al usuario de ejecutar todos los hilos al mismo tiempo mediante la conmutación de contexto realizada por el kernel, de la siguiente forma :
Todo proceso se crea con un único hilo inicial, pero el programa puede crear hilos adicionales si lo requiere. Al igual que los procesos, los se implementan mediante objetos. Un objeto hilo consta de los siguientes atributos :
ATRIBUTO | PROPOSITO |
ID de Cliente | Valor único que identifica al hilo cuando invoca un servidor. |
Contexto de Hilo | Valores de registros y otros datos volátiles que definen el estado de ejecución del hilo. |
Prioridad Base | Límite más bajo de la prioridad dinámica del hilo. |
Afinidad con la CPU | Conjunto de CPUs sobre los que ejecutar el hilo. Es un subconjunto del mismo campo del objeto proceso relacionado con el hilo. |
Tiempo de Ejecución | Tiempo de ejecución acumulado consumido por el hilo en modo usuario y en modo kernel. |
Estado de Alerta | Flag que indica si el hilo debería ejecutar una llamada de procedimiento asíncrona (APC). |
Contador de Suspensión | No. de veces que la ejecución del hilo ha sido suspendida sin ser reanudada. |
Token de imitación | Token de acceso temporal que permite a un hilo realizar operaciones en nombre de otro proceso (es utilizado por los subsistemas). |
Puerto de Terminación | Canal de comunicación entre procesos al que el administrador de procesos envía un mensaje cuando un hilo termina (es utilizado por los subsistemas). |
Estado de Salida | Motivo de la terminación del hilo. |
Cada hilo posee una prioridad de ejecución base que oscila entre dos niveles por encima o por debajo de la prioridad base del proceso. La prioridad de los hilos es dinámica y comienza en la prioridad base del hilo, para variar en forma ascendente en función del trabajo. Windows NT soporta 32 niveles de prioridad divididos en dos clases :
Cuando un proceso posee varios hilos que necesitan intercambiar información o coordinar su ejecución, se emplea la sincronización, de tal forma que unos hilos esperen a que otros hilos realicen la acción deseada (liberar un fichero, finalizar de escribir en un buffer compartido, etc.). La sincronización se realiza mediante objetos de sincronización que pueden ser : objetos proceso, hilo, fichero, evento, par de eventos, semáforo, temporizador, o mútex. En un momento dado un objeto de sincronización puede estar en uno de dos estados posibles : señalado y no señalado. Estos estados se definen de formas distintas según el tipo de objeto. Por ejemplo, un objeto hilo está en el estado no señalado durante su existencia, y el kernel lo coloca al estado señalado cuando finaliza. Cada tipo de objeto de sincronización tiene unas reglas de comportamiento asociadas diferentes que se deben tener en cuenta a la hora de programar.
Tipo de Objeto | Pasa al estado señalado cuando | Efecto sobre los hilos que esperan |
Proceso | Termina el último hilo. | Todos los hilos liberados. |
Hilo | Termina el hilo. | Todos los hilos liberados. |
Fichero | Completa las operaciones de E/S. | Todos los hilos liberados. |
Evento | El hilo establece el evento. | Todos los hilos liberados. |
Par de Eventos | El cliente o el hilo servidor dedicados establecen el evento. | Otro hilo dedicado liberado. |
Semáforo | El contador del semáforo llega a 0. | Todos los hilos liberados. |
Temporizador | Se alcanza el tiempo establecido o expira el intervalo de tiempo. | Todos los hilos liberados. |
Mútex | El hilo libera el mútex. | Un hilo liberado. |
Los programadores que escriben las aplicaciones para Win32, OS/2, POSIX o el resto de subsistemas de entorno, nunca ven los procesos o hilos nativos de NT. Los diferentes subsistemas escudan a los programadores de ellos, emulando el API que esperan las aplicaciones cliente del subsistema, de tal forma que el programador sólo ve los procesos del tipo de subsistema para el que programa. Las características internas de la estructura de procesos del ejecutor de NT permiten que entornos diferentes de sistema operativo coexistan dentro de Windows NT. Los subsistemas de entorno emplean servicios nativos para realizar su trabajo. A continuación se presenta la relación entre la creación de un proceso desde una aplicación y la creación de un proceso nativo de NT.
Como se puede observar, diferentes entornos de sistema operativo devuelven diferentes resultados cuando se crea un proceso, y además cada uno varía en la forma de administrar los procesos, las reglas para su creación, así como las relaciones entre los procesos, y la posibilidad o no de procesos multihilo. Todo ello debe ser tenido en cuenta por el ejecutor de NT, para permitir su coexistencia e implementación, y se logra mediante la llamada de creación de procesos nativos NTCreateProcess, a la cual se pasa información sobre la jerarquía de procesos, herencia, inicialización del espacio de direcciones, identificación del proceso, y un indicador de soporte multihilo o no.
El kernel realiza las funciones fundamentales de Windows NT, y está desarrollado con la filosofía de microkernel al igual que Mach. El kernel es la parte del ejecutor de NT que implementan los mecanismos del sistema (los algoritmos para realizar las tareas), pero las políticas (qué tareas se deben realizar y cuando) son implementadas por otros componentes del ejecutor, de esta manera el kernel es más fácil de mantener.
El kernel realiza cuatro tareas fundamentales :
El kernel tiene algunas características especiales respecto a otras partes del ejecutor. Su ejecución no es preemptiva, nunca es paginado fuera de la memoria principal, se ejecuta siempre en el modo kernel del procesador, y es pequeño y compacto. Fuera del kernel el ejecutor representa los hilos y otros recursos compartidos como objetos. Los objetos necesitan alguna sobrecarga de política, como descriptores (handles) de objeto para manipularlos, comprobaciones de seguridad para protegerlos, cuotas de recurso a deducir, y mecanismos de reserva y liberación de memoria para alojarlos. En el kernel, esta sobrecarga es eliminada, implementando objetos más simples, los objetos del kernel, que ayudan al procesamiento central de control del kernel y soportan la creación de objetos del ejecutor. La mayoría de objetos del nivel del ejecutor encierran uno o más objetos del nivel kernel. Los objetos del kernel se dividen en dos grupos : los objetos de control, que permiten controlar las funciones del sistema operativo (incluyen al objeto proceso del kernel), y los objetos de dispatcher, que incorporan las características de sincronización y afectan a la planificación de los hilos (incluyen el hilo del kernel, el mútex de kernel, el evento de kernel, el par de eventos de kernel, el semáforo de kernel y temporizador de kernel, todos ellos empleados para construir los respectivos objetos más complejos que se proporcionan al modo usuario).
Un hilo es una entidad ejecutable que se ejecuta en el espacio de direcciones de un proceso, utilizando los recursos reservados para el proceso. El kernel realiza la planificación de los hilos, que consiste en llevar el control de los hilos que están listos para ejecutarse, seleccionando el orden en el que lo harán. Cuando se producen las condiciones adecuadas, el kernel selecciona un nuevo hilo para ejecución realiza un cambio de contexto, que consiste en salvar el estado máquina volátil asociado al hilo actual, cargar el del hilo seleccionado, y poner en marcha la ejecución del nuevo hilo. El dispatcher es el encargado de realizar esta función.
El dispatcher trabaja con una visión de los hilos diferente a la que tienen los programas o el resto del sistema de sus objetos hilo, ya que trabaja con una versión reducida, llamada objeto hilo del kernel, que está contenido dentro de un objeto hilo del ejecutor y representa sólo la información que el kernel necesita para realizar la ejecución de un hilo. De igual forma el kernel implementa una versión mínima del objeto proceso, el objeto proceso del kernel.
Así pues, como se puede observar, la filosofía de Windows NT en la forma de implementar los hilos de nivel usuario en el kernel, es asociar cada hilo de nivel usuario de un proceso con un hilo de nivel kernel del proceso.
Los hilos pueden estar en diferentes estados e ir progresando en su ejecución, cambiando de un estado a otro según la carga del sistema en el momento de la ejecución. Los estados posibles de un hilo son :
Cuando no existe nada por hacer, el kernel asigna un hilo ocioso a cada procesador que simplemente ejecuta un bucle, y verifica si algún hilo a alcanzado el estado de standby.
Existen subsistemas de entorno que permiten múltiples
hilos, como el subsistema Win32 y el subsistema OS/2, pero existen
otros subsistema que sólo permiten un hilo en cada proceso,
como POSIX y Win16.