Monografias.com > Computación > Sistemas Operativos
Descargar Imprimir Comentar Ver trabajos relacionados

Arquitectura dell So Windows NT




Enviado por saul-gonzales



Partes: 1, 2

    Indice
    1.
    Prólogo


    3. Diseño De Windows
    Nt

    4.
    Procesos

    5. Administracion De La
    Memoria

    6. Sistema De
    Archivos

    7. Entrada Y
    Salida

    8. Windows Nt
    5

    9. Bibliografia

    1.
    Prólogo

    El presente trabajo trata sobre la Arquitectura de
    Windows NT. La
    investigación se ha llevado a cabo desde
    cuatro puntos de vista que son los componentes señalados
    por Andrew Tanenbaum para el enfoque del estudio de un Sistema
    Operativo, es decir: procesos,
    administración de la memoria,
    ficheros y entrada/salida. Se ha comenzado con un capítulo
    dedicado a conceptos básicos y otro a la arquitectura
    global del sistema, para
    seguidamente estudiar en detalle cada uno de los cuatro puntos
    antes citados.

    Se puede decir que Windows NT
    representa la primera propuesta de la empresa
    Microsoft por
    construir un Sistema Operativo
    serio, capaz de competir con los dinosaurios
    clásicos como UNIX o VMS (o al
    menos intentarlo). Para ello se ha elegido un enfoque clienteservidor, con la
    intención de alcanzar el sueño de los Sistemas
    Distribuidos.

    Windows NT no se puede considerar todavía un
    sistema operativo
    de carácter
    doméstico. Pero el objetivo de
    sus diseñadores parece ser que lo sea dentro de muy pocos
    años, y de hecho, para que los programadores vayan
    entreteniéndose en el uso de las llamadas al sistema (el
    llamado Win32), han construido un Sistema Operativo de poco
    confiable. Hablamos, cómo no, de Windows
    95.

    La arquitectura de Windows 95/98 no
    tiene absolutamente nada que ver con la de Windows NT. No
    es que sea un mal trabajo pero deja bastante que
    desear.

    Durante la elaboración de este trabajo nos hemos
    encontrado con varias dificultades principalmente por la falta de
    fuentes
    bibliográficas que trataran el diseño
    con la suficiente profundidad como para satisfacer el nivel que
    se buscaba. Los libros que se
    encontraron, la mayoría se refieren a la
    Administración del Sistema a alto nivel, o bien de la
    programación bajo Windows NT en lenguaje C++,
    por lo que he tenido que recurrir a diversos artículos de
    la Internet,
    principalmente al nodo de Microsoft en
    Internet. Por
    ello, pedimos disculpas por las posibles erratas que puedan
    existir.

    Para tratar ciertos temas dentro de cada capítulo
    se ha creído conveniente apoyarnos en la descripción de las llamadas al sistema, ya
    que, a nuestro entender, de esta manera todo resulta mucho
    más fácil de explicar y de comprender.

    2. Introduccion Y
    Conceptos Basicos

    Eventos A Través Del Tiempo

    A finales de los años 40's y a principios de los
    años 50's las computadoras
    masivas, eran controladas por tubos al vacío inestables.
    Todo la programación se hacía directamente
    en lenguaje de
    máquina porque la industria no
    había avanzado lo suficiente para necesitar Sistemas
    Operativos. Con la aparición del transistor a
    mediados de los 50's, las computadoras
    se fueron haciendo más y más confiables.

    Lenguajes crudos como Ensamblador y
    Fortran aparecieron, pero un Sistema Operativo (S.O.), tal como
    los conocemos ahora, aún no. Para accesar a la
    programación de la maquinaria se manejaron tarjetas
    perforadas.

    1960's. Cuando IBM introdujo la computadora
    System/360 intentó tomar el mercado
    científico y el comercial. Cuando en este proyecto
    surgieron problemas de
    conflictos por
    la arquitectura, se inició el desarrollo de
    un software que
    resolviera todos aquellos conflictos, el
    resultado fue un muy complejo sistema operativo. Luego AT&T
    trató de desarrollar a Multics, un Sistema Operativo que
    soportara cientos de usuarios de tiempo
    compartido, pero falló. Más adelante
    científicos de la computación desarrollaron Unics, que
    sería monousuario. Ello marca el
    nacimiento de Unix (1969), el
    primero de los sistemas
    operativos modernos.

    1980's. En este tiempo la
    arquitectura de las computadoras, circuitos LSI
    (Large Scale Integration) abrieron el paso para una nueva
    generación de computadoras. DOS de Microsoft aparece en
    1981 dominando este mercado de las
    PCs inmediatamente, aunque el sistema UNIX, predomina en las
    estaciones de trabajo.

    1990's. Aumenta el uso de conexiones en redes, equipos de
    trabajo y aplicaciones distribuidas, los cuales surgen en la
    década anterior, con ello los Sistemas
    Operativos como Unix, Windows NT, etc., soportan muchos clientes, dando
    así el nacimiento de la Computación en Red.

    Sistema Operativo

    Introducción

    Software básico que controla y administra los
    recursos de una
    computadora.
    El sistema operativo tiene tres grandes funciones:
    coordina y manipula el hardware de la
    computadora, como la memoria, las
    impresoras,
    las unidades de disco, el teclado o el
    mouse;
    organiza los archivos en
    diversos dispositivos de
    almacenamiento, como discos flexibles, discos duros,
    discos compactos o cintas magnéticas, y gestiona los
    errores de hardware y la pérdida
    de datos.

    ¿Cómo funciona un sistema
    operativo?

    Los Sistemas
    Operativos controlan diferentes procesos de la
    computadora.
    Un proceso
    importante es la interpretación de los comandos que
    permiten al usuario comunicarse con el ordenador. Algunos
    intérpretes de instrucciones están basados en
    texto y exigen
    que las instrucciones sean tecleadas. Otros están basados
    en gráficos, y permiten al usuario comunicarse
    señalando y haciendo clic en un icono.

    Los Sistemas
    Operativos pueden ser de tarea única o multitarea. Los
    sistemas
    operativos de tarea única, más primitivos,
    sólo pueden manejar un proceso en
    cada momento. Por ejemplo, cuando la computadora está
    imprimiendo un documento, no puede iniciar otro proceso ni
    responder a nuevas instrucciones hasta que se termine la
    impresión.

    Todos los Sistemas Operativos modernos son multitarea y
    pueden ejecutar varios procesos simultáneamente. En la
    mayoría de los ordenadores sólo hay una UCP; un
    Sistema Operativo multitarea crea la ilusión de que varios
    procesos se ejecutan simultáneamente en la UCP. El
    mecanismo que se emplea más a menudo para lograr esta
    ilusión es la multitarea por segmentación de tiempos, en la que cada
    proceso se ejecuta individualmente durante un periodo de tiempo
    determinado. Si el proceso no finaliza en el tiempo asignado, se
    suspende y se ejecuta otro proceso. Este intercambio de procesos
    se denomina conmutación de contexto. El sistema operativo
    se encarga de controlar el estado de
    los procesos suspendidos. También cuenta con un mecanismo
    llamado planificador que determina el siguiente proceso que debe
    ejecutarse. El planificador ejecuta los procesos basándose
    en su prioridad para minimizar el retraso percibido por el
    usuario. Los procesos parecen efectuarse simultáneamente
    por la alta velocidad del
    cambio de
    contexto.

    Los Sistemas Operativos pueden emplear memoria
    virtual para ejecutar procesos que exigen más memoria principal
    de la realmente disponible. Con esta técnica se emplea
    espacio en el disco duro
    para simular la memoria
    adicional necesaria.

    Sistemas Operativos actuales

    Los sistemas operativos empleados normalmente son UNIX,
    Macintosh OS, MS-DOS, OS/2 y
    Windows-NT. El UNIX y sus clones permiten múltiples tareas
    y múltiples usuarios. Su sistema de archivos
    proporciona un método
    sencillo de organizar archivos y permite la protección de
    archivos. Sin embargo, las instrucciones del UNIX no son
    intuitivas. Otros sistemas operativos multiusuario y multitarea
    son OS/2, desarrollado inicialmente por Microsoft Corporation e
    International Business Machines (IBM) y Windows-NT, desarrollado
    por Microsoft. El sistema operativo multitarea de las
    computadoras Apple se denomina Macintosh OS. El DOS y su sucesor,
    el MS-DOS, son
    sistemas operativos populares entre los usuarios de computadoras
    personales. Sólo permiten un usuario y una
    tarea.

    Sistema Operativo de Red

    A un Sistema Operativo de Red se le conoce como NOS.
    Es el software
    necesario para integrar los muchos componentes de una red en un sistema
    particular, al cual el usuario final puede tener
    acceso.

    Otra definición es la siguiente; es un software
    que rige y administra los recursos,
    archivos, periféricos, usuarios, etc., en una red y lleva el control de
    seguridad de los
    mismos.

    Un NOS maneja los servicios
    necesarios para asegurar que el usuario final tenga o esté
    libre de error al accesar a la red. Un NOS normalmente provee una
    interfaz de usuario que es para reducir la complejidad y
    conflictos al momento de usar la red.

    Dentro del contexto del Sistema Operativo de Red, se
    pueden escribir aplicaciones tales como un sistema de correo
    electrónico pueden ser escritas para que permitan
    "conexiones virtuales" entre entidades de red, sin
    intervención humana directa.

    Diferencia entre un S.O. Distribuido, un S.O. de Red y
    un S.O. Centralizado.

    En un Sistema Operativo de Red, los usuarios saben de la
    existencia de varias computadoras y pueden conectarse con
    máquinas remotas y copiar archivos de una
    máquina a otra, cada máquina ejecuta su propio
    sistema operativo local y tiene su propio usuario o grupo de
    usuarios.

    Por el contrario, un Sistema Operativo Distribuido es
    aquel que aparece ante sus usuarios como un sistema tradicional
    de un solo procesador, aun
    cuando esté compuesto por varios procesadores. En
    un sistema distribuido verd adero, los usuarios no deben saber
    del lugar donde su programa se
    ejecute o del lugar donde se encuentran sus archivos; eso debe
    ser manejado en forma automática y eficaz por el Sistema
    Operativo.

    Además son sistemas autónomos capaces de
    comunicarse y cooperar entre sí para resolver tareas
    globales. Es indispensable el uso de redes para intercambiar
    datos.
    Además de los servicios
    típicos de un Sistema Operativo, un Sistema Distribuido
    debe gestionar la distribución de tareas entre los diferentes
    nodos conectados. También, debe proporcionar los
    mecanismos necesarios para compartir globalmente los recursos del
    sistema.

    Sistemas Operativos Centralizados, de un solo procesador, de un
    solo CPU o incluso
    tradicionales; en todo caso, lo que esto quiere decir es que un
    sistema operativo controla una sola computadora.

    3. Diseño
    De Windows Nt

    Windows NT presenta una arquitectura del tipo cliente-servidor. Los
    programas de
    aplicación son contemplados por el sistema operativo como
    si fueran clientes a los
    que hay que servir, y para lo cual viene equipado con distintas
    entidades servidoras.

    Uno de los objetivos
    fundamentales de diseño fue el tener un núcleo tan
    pequeño como fuera posible, en el que estuvieran
    integrados módulos que dieran respuesta a aquellas
    llamadas al sistema que necesariamente se tuvieran que ejecutar
    en modo privilegiado (también llamado modo kernel, modo
    núcleo y modo supervisor). El resto de las llamadas se
    expulsarían del núcleo hacia otras entidades que se
    ejecutarían en modo no privilegiado (modo usuario), y de
    esta manera el núcleo resultaría una base compacta,
    robusta y estable. Por eso se dice que Windows NT es un sistema
    operativo basado en micro-kernel.

    Es por ello que en un primer acercamiento a la
    arquitectura distinguimos un núcleo que se ejecuta en modo
    privilegiado, y se denomina Executive, y unos módulos que
    se ejecutan en modo no privilegiado, llamados subsistemas
    protegidos.

    Los programas de
    usuario (también llamados programas de aplicación)
    interaccionan con cualquier sistema operativo (SO) a
    través de un juego de
    llamadas al sistema, que es particular de cada SO. En el mundo
    Windows en general, las llamadas al sistema se denominan API
    (Application Programming Interfaces, interfaces para la
    programación de aplicaciones). En Windows NT y en Windows
    95 se usa una versión del API llamada API Win32. Un
    programa
    escrito para Windows NT o Windows 95, y que por consiguiente hace
    uso del API Win32, se denomina genéricamente "programa
    Win32", y de hecho esta denominación es bastante frecuente
    en artículos y libros al
    respecto. Desgraciadamente, y conviene dejarlo claro cuanto
    antes, el término "Win32" tiene tres acepciones (al menos
    hasta ahora) totalmente distintas. Una es el API, otra es el
    nombre de uno de los subsistemas protegidos de Windows NT del que
    hablaremos más adelante, y por último se denomina
    Win32s a una plataforma desarrollada por Microsoft, similar a
    Windows 3.1, pero que usa el API Win32 en vez del API Win16 del
    Windows 3.1.

    Hechas estas aclaraciones, podemos continuar adelante.
    Algunas de las llamadas al sistema, debido a su naturaleza, son
    atendidas directamente por el Executive, mientras que otras son
    desviadas hacia algún subsistema. Esto lo veremos con
    detalle en breve.

    El hecho de disponer de un núcleo rodeado de
    subsistemas que se ejecutan en modo usuario nos permite
    además añadir nuevos subsistemas sin producir
    ningún tipo de confrontación.

    En el diseño de Windows NT han confluido
    aportaciones de tres modelos: el
    modelo
    cliente-servidor, el modelo de
    objetos, y el modelo de multiprocesamiento
    simétrico.

    Modelo cliente-servidor. En la teoría
    de este modelo se establece un kernel que básicamente se
    encarga de recibir peticiones de procesos clientes y
    pasárselas a otros procesos servidores, ambos
    clientes y servidores
    ejecutándose en modo usuario. Windows NT pone el modelo en
    práctica pero no contempla el núcleo como un mero
    transportador de mensajes, sino que introduce en él
    aquellos servicios que sólo pueden ser ejecutados en modo
    kernel. El resto de servicios los asciende hacia subsistemas
    servidores que se ejecutan en modo usuario, independientes entre
    sí, y que por tanto pueden repartirse entre máquinas
    distintas, dando así soporte a un sistema distribuido (de
    hecho, el soportar los sistemas
    distribuidos fue otra de las grandes directivas de
    diseño de este SO).

    Modelo de objetos. Decir que no implementa puramente la
    teoría
    de este modelo, sino que más bien lo que hace es
    simplemente contemplar los recursos (tanto internos como
    externos) como objetos. Más adelante daremos una lista de
    los objetos de Windows NT. Brevemente, señalar que todo
    objeto ha de poseer identidad
    propia (es único y distinguible de todos los
    demás), y una serie de atributos (variables) y
    métodos
    (funciones) que
    modifican sus atributos. Los objetos interaccionan entre
    sí a través del envío de mensajes. No
    sólo existen en Windows NT objetos software
    (lógicos), sino que los dispositivos hardware
    (físicos) también son tratados como
    objetos (a diferencia de UNIX, que recordemos trataba a los
    dispositivos como ficheros).

    Modelo de multiprocesamiento simétrico. Un SO
    multiproceso (o sea, aquel que cuenta con varias CPU y cada una
    puede estar ejecutando un proceso) puede ser simétrico
    (SMP) o asimétrico (ASMP). En los sistemas operativos SMP
    (entre los que se encuentran Windows NT y muchas versiones de
    UNIX) cualquier CPU puede ejecutar cualquier proceso, ya sea del
    SO o no, mientras que en los ASMP se elige una CPU para uso
    exclusivo del SO y el resto de CPU quedan para ejecutar programas
    de usuario. Los sistemas SMP son más complejos que los
    ASMP, contemplan un mejor balance de la carga y son más
    tolerantes a fallos (de manera que si un subproceso del SO falla,
    el SO no se caerá pues podrá ejecutarse sobre otra
    CPU, cosa que en los ASMP no sería posible, con lo que se
    bloquearía el sistema entero).

    Comencemos describiendo los subsistemas protegidos, para
    seguidamente estudiar la estructura del
    Executive.

    Figura 1. El núcleo se ejecuta en
    modo privilegiado (Executive) y en modo no privilegiado
    (subsistemas protegidos)

    Los Subsistemas Protegidos

    Son una serie de procesos servidores que se ejecutan en
    modo usuario como cualquier proceso de usuario, pero que tienen
    algunas características propias que los hacen
    distintos. Al decir subsistemas protegidos nos referiremos, pues,
    a estos procesos. Se inician al arrancar el SO. Los hay de dos
    tipos: integrales y
    de entorno.

    Un Subsistema Integral: es aquel servidor que ejecuta
    una función
    crítica del SO (como por ejemplo el que gestiona la
    seguridad).
    Tenemos los siguientes:

    El Subsistema Proceso de Inicio (Logon
    Process)

    El proceso de inicio (Logon Process) recibe las
    peticiones de conexión por parte de los usuarios. En
    realidad son dos procesos, cada uno encargándose de un
    tipo distinto de conexión:

    El proceso de inicio local: gestiona la conexión
    de usuarios locales directamente a una máquina Windows
    NT.

    El proceso de inicio remoto: gestiona la conexión
    de usuarios remotos a procesos servidores de Windows
    NT.

    Figura 2. Diagrama de Flujo
    del Proceso de Inicio de Windows NT.

    El Subsistema de Seguridad

    Este subsistema interacciona con el proceso de inicio y
    el llamado monitor de
    referencias de seguridad (se tratara en el Executive), y de esta
    forma se construye el modelo de seguridad en Windows
    NT.

    El subsistema de seguridad interacciona con el proceso
    de inicio, atendiendo las peticiones de acceso al sistema. Consta
    de dos subcomponentes:

    La autoridad de
    seguridad local: es el corazón
    del subsistema. En general gestiona la política de seguridad
    local; así, se encarga de generar los permisos de acceso,
    de comprobar que el usuario que solicita conexión tiene
    acceso al sistema, de verificar todos los accesos sobre los
    objetos (para lo cual se ayuda del monitor de
    referencias a seguridad) y de controlar la política de auditorías, llevando la cuenta de los
    mensajes de auditoría generados por el monitor de
    referencias. Las auditorías son una facilidad que
    proporciona Windows NT para monitorizar diversos acontecimientos
    del sistema por parte del Administrador.

    El administrador de
    cuentas: mantiene
    una base de datos con
    las cuentas de todos
    los usuarios (login, claves, identificaciones, etc.). Proporciona
    los servicios de validación de usuarios requeridos por el
    subcomponente anterior.

    Un Subsistema de Entorno: da soporte a aplicaciones
    procedentes de SO distintos, adaptándolas para su
    ejecución bajo Windows NT. Existen tres de este
    tipo:

    El Subsistema Win32

    Es el más importante, ya que atiende no
    sólo a las aplicaciones nativas de Windows NT, sino que
    para aquellos programas no Win32, reconoce su tipo y los lanza
    hacia el subsistema correspondiente. En el caso de que la
    aplicación sea MS-DOS o Windows de 16 bits (Windows 3.11 e
    inferiores), lo que hace es crear un nuevo subsistema protegido
    pero no servidor. Así, la aplicación DOS o Win16 se
    ejecutaría en el contexto de un proceso llamado VDM
    (Virtual DOS Machine, máquina virtual DOS), que no es
    más que un simulador de un ordenador funcionando bajo
    MS-DOS. Las llamadas al API Win16 serían correspondidas
    con las homónimas en API Win32. Microsoft llama a esto WOW
    (Windows On Win32).

    El subsistema soporta una buena parte del API Win32.
    Así, se encarga de todo lo relacionado con la interfaz
    gráfica con el usuario (GUI), controlando las entradas del
    usuario y salidas de la aplicación. Por ejemplo, un buen
    número de funciones de las bibliotecas
    USER32 y GDI32 son atendidas por Win32, ayudándose del
    Executive cuando es necesario.

    El funcionamiento como servidor de Win32 lo veremos un
    poco más adelante, en el apartado de llamadas a procedimientos
    locales.

    El Subsistema POSIX

    La norma POSIX (Portable Operating System Interface for
    Unix) fue elaborada por IEEE para conseguir la portabilidad de
    las aplicaciones entre distintos entornos UNIX. La norma se ha
    implementado no sólo en muchas versiones de UNIX, sino
    también en otros SO como Windows NT, VMS, etc. Se trata de
    un conjunto de 23 normas,
    identificadas como IEEE 1003.0 a IEEE 1003.22, o también
    POSIX.0 a POSIX.22, de las cuales el subsistema POSIX soporta la
    POSIX.1, que define un conjunto de llamadas al sistema en
    lenguaje
    C.

    El subsistema sirve las llamadas interaccionando con el
    Executive. Se encarga también de definir aspectos
    específicos del SO UNIX, como pueden ser las relaciones
    jerárquicas entre procesos padres e hijos (las cuales no
    existen en el subsistema Win32, por ejemplo, y que por
    consiguiente no aparecen implementadas directamente en el
    Executive).

    El Subsistema OS/2

    Igual que el subsistema POSIX proporciona un entorno
    para aplicaciones UNIX, este subsistema da soporte a las
    aplicaciones OS/2. Proporciona la interfaz gráfica y las
    llamadas al sistema; las llamadas son servidas con ayuda del
    Executive.

    El Executive

    No se debe confundir el Executive con el núcleo
    de Windows NT, aunque muchas veces se usan (incorrectamente) como
    sinónimos. El Executive consta de una serie de componentes
    software, que se ejecutan en modo privilegiado, y uno de los
    cuales es el núcleo. Dichos componentes son totalmente
    independientes entre sí, y se comunican a través de
    interfaces bien definidas. Recordemos que en el diseño se
    procuró dejar el núcleo tan pequeño como
    fuera posible, y, como veremos, la funcionalidad del
    núcleo es mínima. Pasemos a comentar cada
    módulo.

    El Administrador de Objetos (Object Manager)

    Se encarga de crear, destruir y gestionar todos los
    objetos del Executive. Tenemos infinidad de objetos: procesos,
    subprocesos, ficheros, segmentos de memoria compartida,
    semáforos, mutex, sucesos, etc. Los subsistemas de entorno
    (Win32, OS/2 y POSIX) también tienen sus propios objetos.
    Por ejemplo, un objeto ventana es creado (con ayuda del
    administrador de objetos) y gestionado por el subsistema Win32.
    La razón de no incluir la gestión
    de ese objeto en el Executive es que una ventana sólo es
    innata de las aplicaciones Windows, y no de las aplicaciones UNIX
    o OS/2. Por tanto, el Executive no se encarga de administrar los
    objetos relacionados con el entorno de cada SO concreto, sino
    de los objetos comunes a los tres.

    El Administrador de Procesos (Process
    Manager)

    Se encarga (en colaboración con el administrador
    e objetos) de crear, destruir y gestionar los procesos y
    subprocesos. Una de sus funciones es la de repartir el tiempo de
    CPU entre los distintos subprocesos (ver el capítulo de
    los procesos). Suministra sólo las relaciones más
    básicas entre procesos y subprocesos, dejando el resto de
    las interrelaciones entre ellos a cada subsistema protegido
    concreto. Por
    ejemplo, en el entorno POSIX existe una relación filial
    entre los procesos que no existe en Win32, de manera que se
    constituye una jerarquía de procesos. Como esto
    sólo es específico de ese subsistema, el
    administrador de objetos no se entromete en ese trabajo y lo deja
    en manos del subsistema.

    El Administrador de Memoria
    Virtual (Virtual Memory Manager)

    Windows NT y UNIX implementan un direccionamiento lineal
    de 32 bits y memoria virtual paginada bajo demanda. El
    VMM se encarga de todo lo relacionado con la política de
    gestión
    de la memoria: determina los conjuntos de
    trabajo de cada proceso, mantiene un conjunto de páginas
    libres, elige páginas víctima, sube y baja
    páginas entre la memoria RAM y el
    archivo de
    intercambio en disco, etc. Una explicación detallada la
    dejaremos para el capítulo de la memoria.

    Facilidad de Llamada a Procedimiento
    Local (LPC Facility)

    Este módulo se encarga de recibir y envíar
    las llamadas a procedimiento
    local entre las aplicaciones cliente y los subsistemas
    servidores.

    Administrador de Entrada/Salida (I/O Manager)

    Consiste en una serie de subcomponentes, que
    son:

    El administrador del sistema de ficheros

    El servidor y el redirector de red

    Los drivers de dispositivo del sistema

    El administrador de caches

    Buena parte de su trabajo es la gestión de
    la
    comunicación entre los distintos drivers de
    dispositivo, para lo cual implementa una interfaz bien definida
    que permite el tratamiento de todos los drivers de una manera
    homogénea, sin que intervenga el cómo funciona
    específicamente cada uno.

    Trabaja en conjunción con otros componentes del
    Executive, sobre todo con el VMM. Le proporciona la E/S
    síncrona y asíncrona, la E/S a archivos asignados
    en memoria y las caches de los ficheros.

    El administrador de caches no se limita a gestionar unos
    cuantos buffers de tamaño fijo para cada fichero abierto,
    sino que es capaz de estudiar las estadísticas sobre la carga del sistema y
    variar dinámicamente esos tamaños de acuerdo con la
    carga. El VMM realiza algo parecido en su trabajo, como veremos
    en su momento.

    Este componente da soporte en modo privilegiado al
    subsistema de seguridad, con el que interacciona. Su misión es
    actuar de alguna manera como supervisor de accesos, ya que
    comprueba si un proceso determinado tiene permisos para acceder a
    un objeto determinado, y monitoriza sus acciones sobre
    dicho objeto.

    De esta manera es capaz de generar los mensajes de
    auditorías. Soporta las validaciones de acceso que realiza
    el subsistema de seguridad local.

    En UNIX, de la seguridad se encargaba un módulo
    llamado el Kerberos (Cancerbero), desarrollado por el MIT como
    parte del Proyecto Atenas.
    Kerberos se ha convertido en una norma de facto, y se
    incorporará a Windows NT en su versión
    5.0.

    El Núcleo (Kernel)

    Situado en el corazón de
    Windows NT, se trata de un micro-kernel que se encarga de las
    funciones más básicas de todo el SO:

    Ejecución de subprocesos

    Sincronización multiprocesador

    Manejo de las interrupciones hardware

    Nivel de Abstracción de Hardware
    (HAL)

    Es una capa de software incluida en el Executive que
    sirve de interfaz entre los distintos drivers de dispositivo y el
    resto del sistema operativo. Con HAL, los dispositivos se
    presentan al SO como un conjunto homogéneo, a
    través de un conjunto de funciones bien definidas. Estas
    funciones son llamadas tanto desde el SO como desde los propios
    drivers. Permite a los drivers de dispositivo adaptarse a
    distintas arquitecturas de E/S sin tener que ser modificados en
    gran medida. Además oculta los detalles hardware que
    conlleva el multiprocesamiento simétrico de los niveles
    superiores del SO.

    Llamadas a Procedimientos
    Locales y Remotos

    Windows NT, al tener una arquitectura cliente-servidor,
    implementa el mecanismo de llamada a procedimiento remoto (RPC)
    como medio de comunicación entre procesos clientes y
    servidores, situados ambos en máquinas distintas de la
    misma red. Para clientes y servidores dentro de la misma
    máquina, la RPC toma la forma de llamada a procedimiento
    local (LPC). Vamos a estudiar en detalle ambos mecanismos pues
    constituyen un aspecto fundamental del diseño de Windows
    NT.

    RPC (Remote Procedure Call)

    Se puede decir que el sueño de los
    diseñadores de Windows NT es que algún día
    se convierta en un sistema distribuido puro, es decir, que
    cualquiera de sus componentes pueda residir en máquinas
    distintas, siendo el kernel en cada máquina el coordinador
    general de mensajes entre los distintos componentes. En la
    última versión de Windows NT esto no es aún
    posible.

    No obstante, el mecanismo de RPC permite a un proceso
    cliente acceder a una función
    situada en el espacio virtual de direcciones de otro proceso
    servidor situado en otra máquina de una manera totalmente
    transparente.

    Vamos a explicar el proceso en conjunto. Supongamos que
    se tiene un proceso cliente ejecutándose bajo una
    máquina A, y un proceso servidor bajo una máquina
    B. El cliente llama a una función f de una biblioteca
    determinada. El código
    de f en su biblioteca es una
    versión especial del código
    real; el código real reside en el espacio de direcciones
    del servidor. Esa versión especial de la función f
    que posee el cliente se denomina proxy. El
    código proxy lo
    único que hace es recoger los parámetros de la
    llamada a f, construye con ellos un mensaje, y pasa dicho mensaje
    al Executive. El Executive analiza el mensaje, determina que va
    destinado a la máquina B, y se lo envía a
    través del interfaz de transporte. El
    Executive de la máquina B recibe el mensaje, determina a
    qué servidor va dirigido, y llama a un código
    especial de dicho servidor, denominado stub, al cual le pasa el
    mensaje. El stub desempaqueta el mensaje y llama a la
    función f con los parámetros adecuados, ya en el
    contexto del proceso servidor. Cuando f retorna, devuelve el
    control al
    código stub, que empaqueta todos los parámetros de
    salida (si los hay), forma así un mensaje y se lo pasa al
    Executive.

    Ahora se repite el proceso inverso; el Executive de B
    envía el mensaje al Executive de A, y este reenvía
    el mensaje al proxy. El proxy desempaqueta el mensaje y devuelve
    al cliente los parámetros de retorno de f. Por tanto, para
    el cliente todo el mecanismo ha sido transparente. Ha hecho una
    llamada a f, y ha obtenido unos resultados; ni siquiera tiene que
    saber si el código real de f está en su biblioteca
    o se encuentra en una máquina situada tres plantas
    más abajo.

    LPC (Local Procedure Call)

    Las LPC se pueden considerar una versión
    descafeinada de las RPC. Se usan cuando un proceso necesita los
    servicios de algún subsistema protegido,
    típicamente Win32. Se intentara descubrir su
    funcionamiento.

    El proceso cliente tiene un espacio virtual de 4 Gb. Los
    2 Gb inferiores son para su uso (excepto 128 Kb). Los 2 Gb
    superiores son para uso del sistema.

    Vamos a suponer que el cliente realiza una llamada a la
    función CreateWindow. Dicha función crea un objeto
    ventana y devuelve un descriptor al mismo. No es gestionada
    directamente por el Executive, sino por el subsistema Win32 (con
    algo de colaboración por parte del Executive, por
    supuesto; por ejemplo, para crear el objeto). El subsistema Win32
    va guardando en su propio espacio de direcciones una lista con
    todos los objetos ventana que le van pidiendo los procesos. Por
    consiguiente, los procesos no tienen acceso a la memoria donde
    están los objetos; simplemente obtienen un descriptor para
    trabajar con ellos. Cuando el cliente llama a CreateWindow, se
    salta al código de esa función que reside en la
    biblioteca USER32.DLL asignada en el espacio de direcciones del
    cliente.

    Por supuesto, ese no es el código real, sino el
    proxy. El proxy empaqueta los parámetros de la llamada,
    los coloca en una zona de memoria compartida entre el cliente y
    Win32, pone al cliente a dormir y ejecuta una LPC. La facilidad
    de llamada a procedimiento local del Executive captura esa
    llamada, y en el subsistema Win32 se crea un subproceso que va a
    atender a la petición del cliente. Ese subproceso es
    entonces despertado, y comienza a ejecutar el correspondiente
    código de stub. Los códigos de stub de los
    subsistemas se encuentran en los 2 Gb superiores (los reservados)
    del espacio virtual del proceso cliente. Aunque no he encontrado
    más documentación al respecto, es muy probable
    que dichos 2 Gb sean los mismos que se ven desde el espacio
    virtual de Win32. Sea como sea, el caso es que el stub
    correspondiente desempaqueta los parámetros del
    área de memoria compartida y se los pasa a la
    función CreateWindow situada en el espacio de Win32.
    Ése sí es el código real de la
    función. Cuando la función retorna, el stub
    continúa, coloca el descriptor a la ventana en la memoria
    compartida, y devuelve el control de la LPC al Executive. El
    subproceso del Win32 es puesto a dormir. El Executive despierta
    al subproceso cliente, que estaba ejecutando código proxy.
    El resto de ese código lo que hace es simplemente tomar el
    descriptor y devolverlo como resultado de la función
    CreateWindow.

    4.
    Procesos

    Definición de Proceso y Sub Proceso

    Debemos tener cuidado con no confundir el proceso en
    Windows NT con el proceso en los SO más clásicos,
    como UNIX. Vamos a intentar dar una definición general de
    lo que entiende Windows NT por proceso y subproceso, aunque
    después iremos perfilando poco a poco ambos
    conceptos.

    Un proceso es una entidad no ejecutable que posee un
    espacio de direcciones propio y aislado, una serie de recursos y
    una serie de subprocesos. En el espacio de direcciones hay
    colocado algún código ejecutable (entre otras
    cosas). Bien, hemos dicho que un proceso es una entidad
    "no-ejecutable". En efecto, no puede ejecutar el código de
    su propio espacio de direcciones, sino que para esto le hace
    falta al menos un subproceso. Por consiguiente, un subproceso es
    la unidad de ejecución de código. Un subproceso
    está asociado con una serie de instrucciones, unos
    registros, una
    pila y una cola de entrada de mensajes (enviados por otros
    procesos o por el SO).

    Cuando se crea un proceso, automáticamente se
    crea un subproceso asociado (llamado subproceso primario). Los
    subprocesos también se llaman "hebras de ejecución"
    (threads of execution). Debe quedarnos muy claro, pues, que lo
    que se ejecutan son subprocesos, no procesos. Los procesos son
    como el soporte sobre el que corren los subprocesos. Y entre los
    subprocesos se reparte el tiempo de CPU.

    Podemos pensar en los subprocesos de Windows NT como los
    procesos de los SO clásicos (aunque existen matices, como
    sabemos). A veces, por comodidad y por costumbre, usaremos ambos
    términos como sinónimos, y diremos que "el proceso
    A llama a CreateWindow", aunque se debe entender que "un
    subproceso del proceso A llama a CreateWindow".

    Un proceso tiene un espacio de direcciones virtuales de
    4 Gb. En algún lugar de ese espacio se halla un
    código ejecutable (que quizás es la imagen de un
    programa en disco). Un proceso puede crear subprocesos, estando
    su número fijado por el sistema. Se dice que muere cuando
    todos sus subprocesos han muerto (incluso aunque el subproceso
    primario haya muerto, si aún existe algún
    subproceso propiedad del
    proceso, el proceso seguirá vivo).

    Planificación del Tiempo de la CPU por Round
    Robin con Prioridades

    Windows NT utiliza la planificación del anillo circular o round
    robin. Esta técnica consiste en que los subprocesos que
    pueden ser ejecutados se organizan formando un anillo, y la CPU
    va dedicándose a cada uno durante un tiempo. El tiempo
    máximo que la CPU va a estar dedicada a cada uno se
    denomina quantum, y es fijado por el Administrador del
    Sistema.

    Si el subproceso está esperando por alguna
    entrada-salida o por algún suceso, la CPU lo pondrá
    a dormir, y pondrá en ejecución al siguiente del
    anillo. Si un subproceso que la CPU está ejecutando
    consume su quantum, la CPU también lo pondrá a
    dormir, pasando al siguiente.

    En Windows NT, existe un rango de prioridades que va del
    1 al 31, siendo 31 la más alta. Todo proceso y subproceso
    tienen un valor de
    prioridad asociado.

    Existe un anillo o cola circular por cada uno de los
    niveles de prioridad. En cada anillo están los subprocesos
    de la misma prioridad. El Executive comienza a repartir el tiempo
    de CPU en el primer anillo de mayor prioridad no vacío. A
    cada uno de esos subprocesos se les asigna secuencialmente la CPU
    durante el tiempo de un quantum, como ya indicamos antes. Cuando
    todos los subprocesos de nivel de prioridad n están
    dormidos, el Executive comienza a ejecutar los del nivel (n-1),
    siguiendo el mismo mecanismo.

    Análogamente, si un subproceso se está
    ejecutando, y llegara uno nuevo de prioridad superior, el
    Executive suspendería al primero (aunque no haya agotado
    su quantum), y comenzaría a ejecutar el segundo
    (asignándole un quantum completo).

    Prioridad de proceso y subproceso

    Un proceso se dice que pertenece a una clase de
    prioridad. Existen cuatro clases de prioridad, que
    son:

    Desocupado. Corresponde a un valor de
    prioridad 4.

    Normal. Corresponde a un valor de prioridad 7 ó
    9.

    Alta. Corresponde a un valor de prioridad 13.

    Tiempo Real. Corresponde a un valor de prioridad
    24.

    La clase "Normal" es la que el Executive asocia a los
    procesos por defecto. Los procesos en esta clase se dice que
    tienen una prioridad dinámica: el Executive les asigna un valor
    de 7 si se están ejecutando en segundo plano, mientras que
    si pasan a primer plano, la prioridad se les aumenta a un valor
    de 9.

    La clase "Desocupado" va bien para procesos que se
    ejecuten periódicamente y que por ejemplo realicen alguna
    función de monitorización.

    La clase "Alta" la tienen procesos tales como el
    Administrador de Tareas (Task Manager). Dicho proceso está
    la mayor parte del tiempo durmiendo, y sólo se activa si
    el usuario pulsa Control-Esc. Entonces, el SO inmediatamente pone
    a dormir al subproceso en ejecución (aunque no haya
    agotado su quantum) y ejecuta al subproceso correspondiente del
    proceso Administrador de Tareas, que visualizará el cuadro
    de diálogo
    característico, mostrándonos las
    tareas actuales.

    La clase "Tiempo Real" no es recomendable que la tenga
    ningún proceso normal. Es una prioridad más alta
    incluso que muchos procesos del sistema, como los que controlan
    el ratón, el teclado, el
    almacenamiento en
    disco en segundo plano, etc. Es evidente que usar esta prioridad
    sin un control extremo puede causar consecuencias
    nefastas.

    Así como un proceso tiene una prioridad oscilando
    entre cuatro clases, un subproceso puede tener cualquier valor en
    el rango [1,31]. En principio, cuando el subproceso es creado, su
    prioridad es la correspondiente a la de la clase de su proceso
    padre. Pero este valor puede ser modificado si el subproceso
    llama a la función

    BOOL SetThreadPriority (HANDLE hThread, int
    nPriority);

    Donde:

    hThread es el descriptor del subproceso

    nPriority puede ser:

    THREAD_PRIORITY_LOWEST : resta 2 a la prioridad del
    padre

    THREAD_PRIORITY_BELOW_NORMAL: resta 1 a la prioridad del
    padre

    THREAD_PRIORITY_NORMAL: mantiene la prioridad del
    padre

    THREAD_PRIORITY_ABOVE_NORMAL: suma 1 a la prioridad del
    padre

    THREAD_PRIORITY_HIGHEST: suma 2 a la prioridad del
    padre

    THREAD_PRIORITY_IDLE: hace la prioridad igual a 1,
    independientemente de la prioridad del padre

    THREAD_PRIORITY_TIME_CRITICAL: hace la prioridad igual a
    15 si la clase de prioridad del padre es desocupada, normal o
    alta; si es tiempo real, entonces hace la prioridad igual a
    31

    De esta manera es como calcula el Executive la prioridad
    de un subproceso. Por tanto, la prioridad de un subproceso es
    relativa a la de su padre (excepto en IDLE y TIME_CRITICAL).
    Mediante suma y resta de la prioridad del padre obtenemos todo el
    rango de prioridades:

    Clase proceso padre

    Prior. Subproceso

    Clase desocupado

    Clase normal en primer
    plano

    Clase normal en segundo
    plano

    Clase alta

    Clase tiempo real

    Crítico en tiempo

    15,00

    15,00

    15,00

    15,00

    31,00

    Más alta

    6,00

    9,00

    11,00

    15,00

    26,00

    Más que normal

    5,00

    8,00

    10,00

    14,00

    25,00

    Normal

    4,00

    7,00

    9,00

    13,00

    24,00

    Menos que normal

    3,00

    6,00

    8,00

    12,00

    23,00

    Más baja

    2,00

    5,00

    7,00

    11,00

    22,00

    Desocupado

    1,00

    1,00

    1,00

    1,00

    16,00

    La ventaja de este sistema de las prioridades relativas
    es que si un proceso cambia su clase de prioridad durante su
    vida, sus subprocesos hijos tendrían sus prioridades
    automáticamente actualizadas.

    Creación y destrucción de
    procesos

    La llamada al sistema que crea un proceso es una de las
    más complejas de todo el API Win32. Vamos a comentarla
    para comprender mejor la forma en la que el Executive
    trabaja.

    Un proceso se crea cuando un subproceso de otro proceso
    hace una llamada a:

    BOOL CreateProcess (LPCTSTR lpszImageName, LPCTSTR
    lpszCommandLine, LPSECURITY_ATTRIBUTES lpsaProcess,
    LPSECURITY_ATTRIBUTES lpsaThread, BOOL fInheritHandles, DWORD
    fdwCreate, LPVOID lpvEnvironment, LPTSTR lpszCurDir,
    LPSTARTUPINFO lpsiStartInfo, LPROCESS_INFORMATION
    lppiProcInfo);

    El Executive crea un espacio virtual de 4 Gb para el
    proceso, y también crea el subproceso primario. Veamos el
    significado de los parámetros:

    lpszImageName: es el nombre del archivo que
    contiene el código ejecutable que el Executive
    asignará en el espacio virtual del proceso. Si es NULL,
    entonces se entenderá que viene dado en el siguiente
    parámetro.

    lpszCommandLine: argumentos para el nombre del
    archivo

    lpsaProcess, lpsaThread y fInheritHandles: los dos
    primeros son punteros con los que podemos dar unos atributos de
    seguridad para el proceso y su subproceso primario. Si pasamos
    NULL, el sistema pondrá los valores
    por defecto. Los parámetros son punteros a una estructura del
    tipo:

    El campo lpSecurityDescriptor se refiere a permisos
    sobre el objeto proceso, pero no he encontrado más
    información al respecto.

    De esta estructura vamos a destacar el campo
    bInheritHandle, que se refiere a la herencia. En
    Windows NT, cualquier objeto que creemos va a tener asociada una
    estructura de este tipo en donde se indicará, con el
    parámetro bInheritHandle, si dicho objeto es heredable o
    no. El objeto es propiedad de
    un proceso. Ese proceso puede crear procesos hijos; dichos
    procesos, por defecto, no heredarán ninguno de los objetos
    de su padre. Los procesos hijos que tengan la capacidad de
    heredar, heredarán aquellos objetos de su padre que tengan
    el campo bInheritHandle a TRUE.

    Ahora bien, un proceso y un subproceso son
    también objetos. Por tanto, ambos objetos podrán
    ser heredables por otros procesos. Si el campo bInheritHandle es
    TRUE en la estructura apuntada por lpsaProcess, entonces el
    proceso que estamos creando será heredable por otros
    procesos hijos de su mismo padre.

    Si el campo bInheritHandle es TRUE en la estructura
    apuntada por lpsaThread, entonces e subproceso primario del
    proceso que estamos creando será igualmente
    heredable.

    Resta explicar el parámetro fInheritHandles. Si
    vale TRUE, entonces el proceso que estamos creando podrá
    heredar todos los objetos heredables de su padre (es decir,
    aquellos objetos cuyo campo bInheritHandle sea TRUE).

    No se debe confundir todo esto con herencia entre
    procesos y subprocesos hijos. Un subproceso siempre podrá
    tener acceso a los objetos creados por el proceso al que
    pertenece (o mejor dicho, creados por algún otro
    subproceso del proceso al que pertenece).

    fdwCreate: es una máscara donde se pueden
    especificar (mediante OR lógica)
    muchos indicadores
    para el proceso a crear. Los más importantes son:la clase
    de prioridad del proceso: IDLE_PRIORITY_CLASS (desocupado),
    NORMAL_PRIORITY_CLASS (normal), HIGH_PRIORITY_CLASS (alta),
    REALTIME_PRIORITY_CLASS (tiempo real)si el proceso va a ser
    dormido al crearse, usando CREATE_SUSPENDED

    lpvEnvironment: apunta a un bloque de memoria que
    contiene cadenas de entorno. Si vale NULL, usará las
    mismas cadenas que su proceso padre. Las cadenas de entorno no
    son más que variables con
    algún valor que usarán los procesos con
    algún fin, (por ejemplo, el directorio home, el path).
    Tiene un significado similar al concepto de
    entorno de UNIX.

    lpszCurDir: cadena con directorio y unidad de trabajo
    para el nuevo proceso.

    lpsiStartInfo: apunta a una estructura bastante grande
    que no vamos a escribir. Los campos de dicha estructura dan
    informaciones al subsistema Win32 como por ejemplo si el proceso
    va a estar basado en GUI o en consola (GUI es con ventanas;
    consola es simulando el modo texto), el
    tamaño de la ventana inicial del proceso, las coordenadas
    de dicha ventana, su tamaño, su título

    En hprocess y hThread el Executive devuelve un par de
    descriptores a los objetos proceso y subproceso primario
    recién creados, y que servirán para hacer
    referencias a los mismos. Cada vez que el Executive crea
    cualquier objeto, asocia con dicho objeto un contador de uso.
    Cada vez que un proceso distinto usa un mismo objeto, incrementa
    el contador en 1. Cada vez que un proceso libera un objeto,
    decrementa el contador en 1. Cuando el contador de uso de un
    objeto es 0, el Executive destruye el objeto. Pues bien, cuando
    CreateProcess devuelve el control, los objetos proceso y
    subproceso primario tienen sus respectivos contadores con valor
    2. De este modo, para que el Executive pueda destruirlos,
    habrán de ser liberados dos veces: una, cuando ellos
    mismos terminen (el contador pasaría a valer 1), y otra,
    cuando el proceso padre los libere (o sea, cuando cierre los
    descriptores; así, sus contadores valdrían 0 y 0, y
    el Executive los destruiría). De aquí es infiere
    que es vital que el proceso padre cierre esos descriptores (si
    no, no se destruirían los objetos y podría
    desbordarse la memoria). La llamada para cerrar descriptores
    es

    BOOL CloseHandle (HANDLE hobject);

    dwProcessId y dwThreadId son unos identificadores
    únicos que el Executive asocia al proceso y subproceso
    primario, respectivamente, análogos al PID en
    UNIX.

    Hasta aquí la llamada al sistema que nos permite
    crear procesos. La llamada para finalizar el proceso es,
    afortunadamente, mucho más simple:

    VOID ExitProcess (UINT fuExitCode); que devuelve el
    entero fuExitCode, que es un código de salida que el
    proceso envía antes de finalizar (que diga si ha
    finalizado con éxito,
    si no, etc). Cuando un proceso termina, se realizan las
    siguientes acciones:

    Todos los subprocesos del proceso finalizan

    Se cierran todos los descriptores de objetos del
    Executive y de Win32 abiertos por el proceso

    El estado del
    objeto proceso para a estar señalado

    El estado de
    terminación del proceso se pone al código de salida
    adecuado

    Hemos dicho que el objeto proceso se pone a estado
    señalado. Un objeto puede tener dos estados:
    señalado y no señalado, estados que utiliza el
    sistema y los propios procesos para varias funciones. El curioso
    lector puede consultar el apartado de "Comunicación entre procesos".

    Creación y destrucción de
    subprocesos

    Análogamente a como nos hemos auxiliado de la
    llamada al sistema CreateProcess para comprender el mecanismo de
    creación de procesos, vamos a hacer lo propio para la
    creación de subprocesos. Un subproceso se crea cuando otro
    subproceso hace una llamada a:

    HANDLE CreateThread (LPSECURITY_ATTRIBUTES lpsa, DWORD
    cbStack, LPTHREAD_START_ROUTINE lpStartAddr, LPVOID
    lpvThreadParm, DWORD fdwCreate, LPDWORD lpIDThread);

    lpsa: tiene el mismo significado que en CreateProcess,
    es decir, un puntero a una estructura SECURITY_ATTRIBUTES, donde
    se especifican permisos del subproceso y si es heredable o
    no.

    cbStack: vamos a aprovechar este parámetro para
    explicar la pila de un subproceso. Todo subproceso tiene una pila
    asociada situada en el espacio virtual del proceso al que
    pertenece. Virtualmente, la pila tiene un tamaño por
    defecto de 1 Mb (y además es el máximo;
    tamaños inferiores pueden ser indicados al enlazar la
    aplicación). De esa pila virtual el subproceso puede tener
    asignada en memoria un trocito. El tamaño de ese trocito
    viene dado por el parámetro cbStack, y por defecto es 1
    página (4 Kb). Técnicamente, se dice que la pila
    tiene reservado un espacio de 1 Mb, y asignado un espacio de
    cbStack bytes. (el significado de ambos términos lo
    veremos detenidamente en el capítulo de administración de la memoria). Si el
    subproceso desborda su trocito de pila asignado en memoria se
    eleva una excepción; el Executive captura la
    excepción y asigna otros cbStack bytes en memoria física para la pila
    del subproceso. Por tanto, la pila crece dinámicamente en
    trozos de cbStack bytes. Lo más eficiente es que cbStack
    sea múltiplo del tamaño de la
    página.

    lpStartAddr, lpThreadParm: ya dijimos que todo
    subproceso ejecuta una porción de código del
    proceso al que pertenece. Este parámetro apunta a la
    función que contiene el código a ejecutar por
    nuestro subproceso. Es posible hacer que varios subprocesos
    tengan la misma función asociada. El perfil de la
    función es fijo:

    El parámetro lpvThreadParm de la función
    es justamente el mismo que se le pasa a CreateThread; de hecho,
    el sistema se limita a pasar dicho parámetro a la
    función asociada al subproceso cuándo éste
    comienza su ejecución.

    El parámetro puede ser un valor de 32 bits o un
    puntero de 32 bits. Puede servir para dar algún tipo de
    inicialización al subproceso.

    El parámetro dwresult de la anterior
    función es un código de salida que el subproceso
    devuelve cuando acaba de ejecutar código. Es similar al
    código que devuelven los procesos al acabar.

    fdwCreate: si vale 0, entonces el subproceso comienza a
    ejecutarse en cuanto esté creado. Si vale
    CREATE_SUSPENDED, entonces se creará, pero
    automáticamente será suspendido.

    lpIDThread: debe ser un puntero a una estructura DWORD,
    donde el Executive pondrá el identificador que le ha
    asignado al subproceso. Es obligatorio pasar una dirección válida.

    El subproceso recién creado iniciará su
    ejecución inmediatamente antes del retorno de CreateThread
    (a menos que hayamos especificado el indicador
    CREATE_SUSPENDED).

    Comunicación y Sincronización de Procesos
    Mediante Objetos

    En Windows NT, los mecanismos clásicos de
    sincronización entre procesos (como los semáforos,
    las regiones críticas, los sucesos, etc.) son tratados como
    objetos. Es más, existen objetos no específicos de
    sincronización pero que también pueden ser usados
    con estos fines. Por tanto, vamos en primer lugar a enumerar
    todos los objetos de sincronización y a dar algunas
    características globales, para posteriormente adentrarnos
    a estudiar los más importantes.

    Podemos sincronizar subprocesos mediante alguno de los
    siguientes objetos:

    • Semáforos
    • Mutexes
    • Sucesos
    • Archivos
    • Procesos
    • Subprocesos
    • Entrada del terminal
    • Notificación de cambio de
      archivo

    Antes hemos mencionado las regiones críticas.
    Aunque Windows NT las incluye como mecanismo de
    sincronización, no las trata explícitamente como
    objeto. No obstante también las estudiaremos en este
    apartado por mantener la homogeneidad.

    Como ya esbozamos en el capítulo de los procesos,
    en cualquier instante un objeto está en uno de dos
    estados: señalado (1) y no señalado (0). Cada
    estado tiene un significado dependiendo de la clase del
    objeto.

    Por ejemplo, en el apartado anterior vimos que durante
    la vida de un proceso o un subproceso su estado era no
    señalado, pero que al morir pasaban al estado
    señalado. De aquí que ambos tipos de objetos sirvan
    para la sincronización. Por ejemplo, un subproceso A puede
    querer dormir hasta que otro proceso/subproceso B acabe; por
    tanto, A dormirá mientras el objeto asociado al B
    esté no señalado. En cuanto B pase a
    señalado, A despertará.

    Igualmente, un subproceso se puede sincronizar con el
    fin de una lectura/escritura en
    un archivo. En general, cuando alguna de estas operaciones
    finalizan, el objeto archivo en cuestión pasa a estado
    señalado.

    El objeto asociado a la entrada de teclado se pone
    señalado cuando existe algo en el buffer de entrada. La
    aplicación de este hecho para sincronización es
    evidente. Un subproceso puede así estar durmiendo mientras
    el buffer esté vacío.

    El resto de objetos los veremos con más detalle a
    lo largo de este capítulo. Pero antes vamos a dar las
    llamadas al sistema que se utilizan para la sincronización
    con objetos.

    Se trata de:

    DWORD WaitForSingleObject (HANDLE hObject, DWORD
    dwTimeout);

    Esta llamada simplemente mantiene al subproceso que la
    realiza dormido hasta que el objeto identificado por hObject pase
    al estado señalado. El parámetro dwTimeout indica
    el tiempo (en ms) que el subproceso quiere esperar por el objeto.
    Si vale 0, entonces la llamada sólo hace una
    comprobación del estado y retorna inmediatamente; si
    devuelve WAIT_OBJECT_0, el objeto estaba señalado; si
    devuelve WAIT_TIMEOUT, el objeto estaba no señalado. Si
    como tiempo metemos INFINITE, el subproceso dormirá hasta
    que el objeto pase a señalado. Hay un par de
    códigos de salida más que comentaremos en su
    momento (cuando expliquemos los mutex).

    DWORD WaitForMultipleObjects (DWORD cObjects, LPHANDLE
    lpHandles, BOOL bWaitAll, DWORD dwTimeout);

    Es parecida a la anterior pero da la posibilidad de
    esperar por varios objetos o bien por alguno de una lista de
    objetos. cObjects indica el número de objetos a comprobar.
    lpHandles apunta a una matriz que
    contiene descriptores a los objetos. El booleano bWaitAll indica
    si queremos esperar a que todos los objetos pasen a estado
    señalado o tan sólo uno, y dwTimeout es el tiempo a
    esperar. Si hay varios objetos que han pasado al estado
    señalado, la llamada coge sus descriptores, toma el menor
    y devuelve su posición dentro de la matriz (sumada
    a un código de retorno análogo a los de
    WaitForSingleObject).

    El tema de sincronización está
    íntimamente relacionado con el de interbloqueos.
    Supongamos que tenemos dos subprocesos A y B compitiendo por dos
    objetos 1 y 2, esperando a que ambos estén
    señalados, para lo cual han hecho sendas llamadas a
    WaitForMultipleObjects. Supongamos que 1 pasa a estado
    señalado. Y supongamos que el Executive decidiera otorgar
    ese hecho al proceso A, colocando a 1 de nuevo a no
    señalado. Mientras tanto, 2 pasa también a
    señalado, y el Executive otorga este hecho a B, y
    también pone a 2 a no señalado. En esta
    situación, ninguno de los dos subprocesos podría
    terminar nunca, pues cada uno estaría esperando a que el
    objeto del otro pasara a señalado. Entonces A y B
    están interbloqueados. Para evitar esta situación,
    el Executive no entrega los objetos hasta que ambos estén
    señalados; en ese momento despertaría a uno de los
    subprocesos. El otro seguiría dormido hasta que el primer
    subproceso terminara de trabajar con los objetos.

    A continuación se estudia cada uno de los objetos
    específicos de sincronización de Windows
    NT.

    Secciones Críticas

    Las secciones críticas son un mecanismo para
    sincronizar subprocesos pertenecientes a un mismo proceso, pero,
    como ya hemos indicado, no son objetos.

    Una sección o región crítica (SC)
    es un trozo de código ejecutable tal que para que un
    subproceso pueda ejecutarlo se requiere que tenga acceso a una
    estructura de
    datos especial y compartida.

    Dicha estructura es del tipo CRITICAL_SECTION, cuyos
    campos no son interesantes, y además no son accesibles
    directamente, sino a través de una llamada al subsistema
    Win32:

    VOID InitializeCriticalSection (LPCRITICAL_SECTION
    lpCriticalSection);

    Veamos algunas llamadas para manejar las SC:

    VOID EnterCriticalSection (LPCRITICAL_SECTION
    lpCriticalSection);

    VOID LeaveCriticalSection (LPCRITICAL_SECTION
    lpCriticalSection);

    La primera sirve para que un subproceso solicite entrar
    en una SC. La segunda permite salir de la SC a un subproceso que
    esté dentro de la misma.

    La función de entrar opera así: la primera
    vez que es llamada, se registra en la estructura CRITICAL_SECTION
    un identificador del subproceso que la posee.

    Si antes de que el subproceso la abandone, el SO entrega
    la CPU a otro (el caso más frecuente), y entonces ese otro
    subproceso hace la llamada para entrar en la SC, entonces la
    función ve que la estructura ya está en uso, con lo
    que el segundo subproceso sería puesto a
    dormir.

    Cuando la CPU vuelva al primero y éste haga una
    llamada para salir, la estructura será asignada al segundo
    subproceso.

    Si un subproceso vuelve a llamar a la función de
    entrar estando ya dentro de la SC, simplemente se
    incrementará un contador de uso asociado con el objeto SC.
    Más tarde, deberá hacer tantas otras llamadas a la
    función de salir para poner el contador a 0; en caso
    contrario, ningún otro subproceso podría ocupar la
    SC.

    La estructura CRITICAL_SECTION y todos los recursos que
    el SO le hubiera asignado se pueden eliminar haciendo una llamada
    a

    VOID DeleteCriticalSection (LPCRITICAL_SECTION
    lpCriticalSection);

    Sería catastrófico usar esta
    función estando un subproceso dentro.

    Exclusión Mutua (Mutex)

    Los objetos exclusión mutua (abreviadamente
    mutex, de mutual exclusión) son muy parecidos a las SC,
    pero permiten sincronizar subprocesos pertenecientes a procesos
    distintos.

    Se crean con la llamada:

    HANDLE CreateMutex (LPSECURITY_ATTRIBUTES lpsa, BOOL
    fInitialOwner, LPTSRT lpszMutexName);

    fInitialOwner: dice si el subproceso que crea el mutex
    ha de ser su propietario inicial o no. Si vale TRUE, entonces
    el estado
    inicial del objeto mutex sería no señalado, por lo
    que todo subproceso que espere por él sería
    inmediatamente puesto a dormir. Si vale FALSE, el mutex se crea
    con estado señalado, por lo que al primer proceso que
    estuviera esperando le sería asignado y podría
    continuar ejecutándose.

    lpszMutexName: apunta a una cadena con el nombre que le
    hemos querido dar al objeto (o NULL si no se pone un
    nombre).

    La llamada devuelve un descriptor al objeto
    creado.

    Si otro subproceso llamara a la función
    pasándole el mismo nombre de mutex, el SO
    comprobaría que ya está creado y devolvería
    otro descriptor al mismo objeto.

    HANDLE OpenMutex (DWORD fwdAccess, BOOL fInherit, LPTSTR
    lpszMutexName);

    Esta función comprobaría si existe
    algún objeto mutex con nombre lpszMutexName. Si es
    así, devolvería un descriptor al mismo. Si no,
    devolvería NULL. El booleano fInherit permite que el mutex
    sea heredado por los subprocesos hijos.

    Si el mutex no tuviera nombre, un subproceso
    podría obtener un descriptor al mismo llamando a
    DuplicateHandle.

    Otra diferencia de los mutex con las SC (y en general
    con cualquier objeto de sincronización en Windows NT) es
    que un subproceso mantiene la propiedad de un mutex hasta que
    quiera liberarlo, pero hasta el punto de que, si el subproceso
    muere y no ha liberado al mutex, éste seguiría
    siendo de su propiedad.

    Así, si un mutex está libre
    (señalado) y es tomado por un subproceso (pasa a no
    señalado), y dicho subproceso finaliza antes de liberarlo,
    el estado del mutex pasaría a señalado; los
    subprocesos que estuvieran esperando por el mutex serían
    despertados pero no se les asignaría el objeto a ninguno,
    sino que con el valor WAIT_ABANDONED de retorno de las llamadas
    WaitFor…Object(s) se les informaría de lo que ha
    sucedido, de que el mutex no ha sido liberado sino abandonado.
    Esto se considera como una situación de fallo en un
    programa.

    Para liberar un mutex usaremos la llamada

    BOOL ReleaseMutex (HANDLE hMutex);

    Donde:

    hMutex: es un descriptor al objeto. La función
    decrementa el contador de uso que el subproceso tiene sobre el
    mutex. Cuando sea 0, el objeto podrá ser asignado al
    primer subproceso que por él esté esperando, igual
    que con las SC.

    Semáforos

    Un semáforo es un objeto distinto de las
    SC y los mutex. A diferencia de ambos, el objeto
    semáforo puede ser
    poseído a la vez por varios subprocesos, y no posee dentro
    ningún identificador del subproceso/s que lo está
    usando. Lo que tiene es un contador de recursos, que indica el
    número de subprocesos que pueden acceder al
    semáforo. Cuando un subproceso toma el objeto
    semáforo, el SO mira si el contador de recursos del mismo
    es 0. De ser así, pone al subproceso a dormir. Si no es 0,
    asigna el semáforo al subproceso y decrementa el contador.
    Cada vez que un subproceso libera el semáforo, se
    incrementa el contador.

    Un semáforo está señalado cuando su
    contador es mayor que 0, y no señalado cuando su contador
    vale 0.

    Un semáforo se crea con la llamada:

    HANDLE CreateSemaphore (LPSECURITY_ATTIBUTES lpsa, LONG
    cSemInitial, LONG cSemMax, LPTSTR lpszSemName);

    cSemInitial es el valor inicial no negativo del contador
    de recursos.

    cSemMax es el valor máximo que puede alcanzar
    dicho contador (por tanto 0 <= cSemInitial <=
    cSemMax)

    lpszSemName es el nombre que le damos al
    objeto.

    HANDLE OpenSemaphore (DWORD fdwAccess, BOOL fInherit,
    LPTSTR lpszName);

    La semántica de esta llamada es análoga a
    la de OpenMutex.

    Para incrementar el contador de referencia del
    semáforo se usa:

    HANDLE ReleaseSemaphore (HANDLE hSemaphore, LONG
    cRelease, LPLONG lplPrevious);

    Donde:

    cRelease indica el número de veces que queremos
    incrementar el contador (el número de veces que liberamos
    el semáforo). A la vuelta de la función, tendremos
    en lplPrevious un puntero al valor anterior del contador. Por
    tanto, si queremos averiguar el valor del contador tenemos que
    modificarlo. Ni siquiera llamando a la función con
    cRelease igual a 0 podríamos saber el valor anterior sin
    modificar el semáforo, pues entonces la función
    devuelve 0 como dato apuntado por lplPrevious.

    Sucesos.

    Los sucesos son objetos utilizados para indicar a los
    subprocesos que ha ocurrido algo en el entorno. Se puede
    distinguir dos tipos de objetos suceso:

    Sucesos con inicialización manual.

    Sucesos con autoinicialización.

    Cualquier objeto suceso podrá estar en estado
    señalado o no señalado. No señalado
    significa que la situación asociada con el objeto
    aún no ha ocurrido. Señalado indica que sí
    se ha producido.

    Ambos tipos de objeto se crean con la misma
    llamada:

    HANDLE CreateEvent (LPSECURITY_ATTIBUTES lpsa, VOOL
    fManualReset, BOOL fInitialState, LPTSTR
    lpszEventName);

    fManualReset a TRUE le indicará al SO que
    queremos crear un suceso de inicialización manual, y a
    FALSE, un suceso de autoinicialización.

    fInitialState indica el estado inicial del objeto; un
    valor TRUE significa que el suceso se creará como obejto
    señalado, y a FALSE como no señalado.

    Como vimos con los otros tipos de objetos de
    sincronización, otros procesos pueden acceder al mismo
    objeto usando CreateEvent y el mismo nombre, o usando la
    herencia, o con DuplicateHandle, o con:

    HANDLE OpenEvent (DWORD fdwAccess, BOOL fInherit, LPTSTR
    lpszName);

    Sucesos con inicialización manual (manual
    reset)

    Este tipo de objetos se usan para que, cuando el suceso
    ocurra, es decir, se ponga a señalado, todos los
    subprocesos que esperaban por ese suceso se despierten a la vez y
    todos puedan seguir ejecutándose. Por tanto, ahora las
    funciones WaitFor…Objext(s) no van a tocar el estado del
    objeto, sino que debe hacerlo el propio subproceso que
    creó el objeto. Dicho subproceso puede usar las
    llamadas:

    BOOL SetEvent (HANDLE hEvent);

    BOOL ResetEvent (HANDLE hEvent);

    que ponen, respectivamente, el suceso identificado por
    hEvent en estado señalado y no señalado. O sea, con
    SetEvent indicaremos que la situación asociada al obejto
    se ha producido, y con ResetEvent indicaremos lo
    contrario.

    Existe una llamada muy útil con este tipo de
    objetos:

    BOOL PulseEvent (HANDLE hEvent);

    que le da un "pulso" al suceso hEvent: lo pone en estado
    señalado, con lo que se libera a todos los subprocesos en
    espera (que se seguirán ejecutando), y entonces lo pone a
    no señalado, con lo que los subprocesos que a partir de
    ese momento esperen por el suceso serán dormidos. Por
    tanto, equivale a SetEvent + liberación +
    ResetEvent.

    Sucesos con autoinicialización
    (auto-reset)

    Con estos objetos las funciones WaitFor…Object(s) van
    a funcionar como con cualquier otro tipo de objeto. Por tanto,
    cuando la situación asociada con el suceso ocurra y
    éste se ponga a señalado (con SetEvent),
    sólo uno de los subprocesos que estaban esperando
    podrá continuar, y su función WaitFor…Object(s)
    correspondiente pondrá el estado del objeto a no
    señalado. La función ResetEvent no tendría
    efecto aquí. La función PulseEvent pondría
    el suceso a señalado, liberaría un sólo
    subproceso de los que están esperando, y pondría el
    suceso a no señalado.

    Partes: 1, 2

    Página siguiente 

    Nota al lector: es posible que esta página no contenga todos los componentes del trabajo original (pies de página, avanzadas formulas matemáticas, esquemas o tablas complejas, etc.). Recuerde que para ver el trabajo en su versión original completa, puede descargarlo desde el menú superior.

    Todos los documentos disponibles en este sitio expresan los puntos de vista de sus respectivos autores y no de Monografias.com. El objetivo de Monografias.com es poner el conocimiento a disposición de toda su comunidad. Queda bajo la responsabilidad de cada lector el eventual uso que se le de a esta información. Asimismo, es obligatoria la cita del autor del contenido y de Monografias.com como fuentes de información.

    Categorias
    Newsletter