5.4. WINDOWS NT.

  1. Historia de Windows NT.
  2. Objetivos de diseño de Windows NT.
  3. Arquitectura de Windows NT.
  4. Introducción a los procesos de Windows NT.
  5. Introducción a los hilos de Windows NT.
  6. Estructura del kernel de Windows NT.

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.

Historia de Windows NT.

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 :

  1. Portabilidad : Windows NT se debía escribir en un lenguaje que fuese fácilmente transportable a cualquier plataforma (RISC y CISC).
  2. Multiproceso y facilidad de implementación : Se necesitaba que el sistema operativo funcionase en sistemas monoprocesador y multiprocesador, aprovechando la amplia gama de máquinas disponibles en la actualidad, y con facilidades de evolución y escalado.
  3. Proceso distribuido : Se debía permitir la posibilidad de procesamiento distribuido en una red de computadores.
  4. Conformidad POSIX : Debía ser compatible con el estándar IEEE 1003.1 de 1988 que define un interfaz de sistema operativo transportable basado en UNIX (POSIX, Portable Operating System Interface based on UNIX).
  5. Seguridad informática : Se debía cumplir, inicialmente, con el certificado de seguridad C2 del departamento de Defensa Norteamericano, pero las versiones futuras podrían ajustarse a niveles de seguridad mayores.

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.

Objetivos de diseño de Windows NT.

Además, antes de comenzar a escribir el sistema operativo, se fijaron una serie de objetivos de diseño, que fueron los siguientes :

  1. Extensibilidad : El código del sistema operativo debía estar escrito de forma que pueda crecer fácilmente según evolucionen las necesidades del mercado. Por ejemplo, soporte de nuevos dispositivos hardware (CD-ROM, tarjetas de red, etc.) o nuevas tecnología software (nuevos interfaces gráficos de usuarios o entorno de programación orientados a objetos). El código del sistema debía ser íntegro frente a estos cambios. Para conseguir este objetivo se siguió el diseño de Mach (el sistema operativo desarrollado por Carnegie Mellon University), que consistía en crear un sistema operativo base que proporcionaba las primitivas básicas de un sistema operativo, pero son los programas de aplicación quienes proporcionaban las herramientas adicionales al sistema operativo, incluyendo los API. Así Windows NT contiene una parte base mínima, denominada ejecutor que funciona en modo kernel, y una serie de servidores, denominados subsistemas protegidos, que funcionan en modo usuario, y proporcionan la interfaz y las herramientas adicionales del sistema. De esta forma los subsistemas protegidos pueden ser modificados o se pueden añadir nuevos subsistemas sin afectar a la integridad del ejecutor. Además, el ejecutor está diseñado de forma modular, los recursos se representan mediante objetos y se implementan mecanismos de llamada a procedimientos remotos RPC.
  2. Facilidad de transporte : El código debía ser fácilmente transportable de un procesador a otra, según lo establezca el mercado. Por ello la mayor parte del código se escribió en lenguaje C portable, partes en C++, y las partes críticas que se comunican con el hardware en ensamblador. Se encapsuló el código que dependía de la plataforma dentro de una librería de enlace dinámico (DLL) llamada nivel de abstracción de hardware (HAL).
  3. Fiabilidad y robustez : El sistema debe protegerse a sí mismo del mal funcionamiento interno y de fallos externos, comportándose de forma predecible en todo momento.
  4. Compatibilidad : Además de extender la tecnología existente, el interfaz de usuario y los API debían ser compatibles con los sistemas ya existentes de Microsoft (MS-DOS y Windows 3.0). Además, finalmente se decidió que debía existir compatibilidad binaria con las aplicaciones de MS-DOS, Windows 16 bits, OS/2 y LAN Manager, aunque para algunos casos era necesario un emulador. También tendría compatibilidad a nivel de fuentes con las aplicaciones POSIX que siguiesen el estándar IEEE 1003.1. Se portarían los sistemas de ficheros FAT (MS-DOS), HPFS (OS/2), CDFS (CD-ROM) y el nuevo NTFS (Windows NT). La compatibilidad se implementaría en un subsistema protegido particular para cada uno de estos sistemas operativos, al estilo de Mach.
  5. Rendimiento : El sistema debía ser lo más rápido posible en cualquier plataforma, dentro de las limitaciones que imponen el resto de los objetivos de diseño. Los subsistemas protegidos emplean un mecanismo de RPC optimizado : llamadas a procedimiento locales (LPC, Local Procedure Call).

Arquitectura de Windows NT.

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.

Figura 5-5 Comunicación cliente/servidor via RPC en Windows NT.

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.

Figura 5-6 Arquitectura de Windows NT.

Las responsabilidades de los componentes son :

Introducción a los procesos de Windows NT.

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 :

ATRIBUTOPROPOSITO
Identificador de ProcesoValor único que identifica al proceso de cara al SO.
Token de accesoObjeto del ejecutor que contiene información de seguridad sobre la petición del proceso.
Prioridad BasePrioridad de ejecución básica para los hilos del proceso.
Afinidad con la CPU por defectoConjunto de CPUs sobre los que ejecutar los hilos.
Límites de CuotaMá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ónTiempo total consumido por los hilos del proceso.
Contadores de E/SRegistro 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/DepuradorCanales 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 SalidaMotivo de la terminación del proceso.

Figura 5-7 Atributos del objeto proceso en Windows NT.

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.

Introducción a los hilos de Windows NT.

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 :

ATRIBUTOPROPOSITO
ID de ClienteValor único que identifica al hilo cuando invoca un servidor.
Contexto de HiloValores de registros y otros datos volátiles que definen el estado de ejecución del hilo.
Prioridad BaseLímite más bajo de la prioridad dinámica del hilo.
Afinidad con la CPUConjunto de CPUs sobre los que ejecutar el hilo. Es un subconjunto del mismo campo del objeto proceso relacionado con el hilo.
Tiempo de EjecuciónTiempo de ejecución acumulado consumido por el hilo en modo usuario y en modo kernel.
Estado de AlertaFlag que indica si el hilo debería ejecutar una llamada de procedimiento asíncrona (APC).
Contador de SuspensiónNo. de veces que la ejecución del hilo ha sido suspendida sin ser reanudada.
Token de imitaciónToken de acceso temporal que permite a un hilo realizar operaciones en nombre de otro proceso (es utilizado por los subsistemas).
Puerto de TerminaciónCanal 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 SalidaMotivo de la terminación del hilo.

Figura 5-8 Atributos del objeto hilo en Windows NT.

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 ObjetoPasa al estado señalado cuando Efecto sobre los hilos que esperan
ProcesoTermina el último hilo. Todos los hilos liberados.
HiloTermina el hilo. Todos los hilos liberados.
FicheroCompleta las operaciones de E/S. Todos los hilos liberados.
EventoEl hilo establece el evento. Todos los hilos liberados.
Par de EventosEl cliente o el hilo servidor dedicados establecen el evento. Otro hilo dedicado liberado.
SemáforoEl contador del semáforo llega a 0. Todos los hilos liberados.
TemporizadorSe alcanza el tiempo establecido o expira el intervalo de tiempo. Todos los hilos liberados.
MútexEl hilo libera el mútex. Un hilo liberado.

Figura 5-9 Comportamiento de los objetos de sincronización de Windows NT.

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.

Figura 5-10 Creación de procesos en los distintos subsistemas de Windows 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.

Estructura del kernel de Windows NT.

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.

Figura 5-11 Relación entre el objeto proceso kernel y el objeto proceso del subsistema Win32.

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.

Figura 5-12 Relación entre los hilos de usuario y los hilos del kernel en Windows NT.

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 :

Figura 5-13 Estados de un hilo en Windows NT.

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.