5. Administracion De La Memoria
La parte de Windows NT que soporta la gestión de la memoria se denomina Gestor de Máquina Virtual (VMM), y reside en el Executive, por encima del núcleo pero ejecutándose en modo supervisor, como vimos en la introducción.
En Windows NT se utiliza memoria virtual paginada. En algunas máquinas, como las CPU Intel 386 en adelante, combina paginación con segmentación. El tamaño de la página depende de la máquina. En la CPU anterior es de 4 Kb.
Espacio de Direcciones de un Proceso
Todo proceso que se crea en Windows NT posee un espacio de direcciones virtuales de 4 Gb exclusivos de él. Ningún otro podrá acceder a esas direcciones, sencillamente porque ¡no las ve!. Para un proceso, todo lo que ve es suyo, y ve virtualmente 4 Gb. Los 2 Gb superiores están reservados al SO, así como los 64 Kb primeros y los 64 Kb últimos de los 2 Gb inferiores. El resto de los 2 Gb inferiores son para uso del proceso. Distinguimos varias zonas:
Direcciones para las DLL del sistema (NTDLL, KERNEL32, USER32, GDI32 …).
Direcciones para las DLL propias de la aplicación.
Bloques y pilas de los subprocesos.
Imagen del archivo ejecutable: código, datos, cabecera, información del depurador y tabla de activación imagen.
Además, cada proceso posee su propia tabla de implantación de páginas (TIP), a dos niveles. El objetivo de esa tabla (también llamada tabla de traducción) es, dada una dirección virtual, devolver su dirección física asociada. A veces la dirección virtual no tiene correspondencia en memoria física. Entonces se dice que se ha producido un fallo o defecto de página (page fault). En el siguiente apartado vamos a describir cómo funciona el VMM y qué hace cuando se dan los fallos de página.
Funcionamiento del VMM
Cada proceso tiene asignado un número que indica el máximo de páginas físicas que se le pueden conceder. Dicho número es ajustado por el llamado gestor del conjunto de trabajo, del que luego hablaremos. Además, cada proceso lleva asociada una lista que contiene referencias a las páginas físicas a las que se ha accedido menos últimamente.
Cuando el proceso accede a una dirección que no tiene dirección física asociada en la TIP, se produce un fallo de página. Entonces, el VMM consulta el número de páginas que el proceso tiene asignadas. Si no ha llegado al límite, se le concede una nueva página física y se escribe con la correspondiente desde el disco. Esto se denomina paginación bajo demanda. Si por el contrario ya había llegado al límite, entonces hay que descargar una página física al disco para subir a memoria la que ocasionó el fallo. El VMM elige la página víctima de la lista de menos recientemente usadas del proceso.
Nótese que aunque existan páginas libres en la memoria, si el proceso agota su número de páginas asignadas, se producirá el swapping, y además de una de sus propias páginas. Este mecanismo puede parecer ilógico, pero presenta tanto ventajas como inconvenientes. Como problema puede nombrar por ejemplo, que si un proceso estuviera gran parte del tiempo dormido (en espera de un suceso o de una E/S), dicho proceso estaría ocupando páginas físicas que de otro modo podrían ser descargadas. Como ventaja tenemos que, con este esquema, procesos que requieran muchos recursos no dejarán fuera de juego a aquellos cuya demanda de memoria sea escasa. No obstante, el problema planteado es paliado (al menos en gran medida) por una parte del VMM llamada gestor del conjunto de trabajo. Realiza dos funciones fundamentales:
Por un lado, periódicamente revisa las estadísticas sobre el uso de la CPU por cada proceso. Siguiendo este criterio, procede a ajustar el número que indica el máximo de páginas físicas asociadas a cada uno. A aquellos procesos con poca actividad se les bajará el número (lo que acarreará un descargue de sus páginas físicas, si las tiene, cuando sean necesarias), y a aquellos con mucha carga se les subirá el número.
Por otro lado, y también de forma periódica, roba a los procesos páginas físicas, elegidas de entre las menos recientemente usadas por cada uno. Este procedimiento se reliza a la frecuencia necesaria para que los procesos no se ralenticen demasiado por los fallos de página. El objetivo de esto es mantener una reserva de páginas para que una repentina demanda no ocasione una caída en prestaciones del sistema completo. Por ejemplo, cuando se inicia un proceso nuevo se suele requerir una cantidad considerable de páginas.
Para llevar a cabo estas tareas, las páginas físicas se clasifican en una de cuatro listas que son mantenidos por el gestor del conjunto de trabajo:
- lista de páginas modificadas
- lista de páginas no activas
- lista de páginas liberadas
- lista de páginas a cero
Cada página que el gestor roba a un proceso es borrada su lista de menos recientemente usadas e incluida en la lista de modificadas (y las entradas correspondientes de la TIP marcadas como no válidas, de forma que se produzca fallo de página al acceder el proceso a dichas direcciones). Dicha lista contiene páginas robadas que aún están en memoria RAM pero que no han sido escritas a disco (al archivo de paginación). Cuando se produzca un fallo de página, si la dirección física correspondiente estuviera en una de estas páginas, simplemente se volvería a insertar en la lista del proceso y se ajustaría su TIP.
Cuando la lista de modificadas se hace suficientemente grande, otra parte del VMM (el escritor de páginas) copia algunas páginas de la lista al archivo de paginación, las borra de la lista de modificadas y las añade a la lista de inactivas. Ahí están las páginas que han sido robadas, que están en RAM y además en el archivo de paginación. El tratamiento de un fallo de página aquí también sería muy rápido y simple.
Cuando un proceso libera memoria, sus páginas físicas asociadas se añaden a la lista de liberadas. Son, por tanto, potencialmente utilizables sin necesidad de escribirlas en el fichero de swapping pero su contenido no ha sido borrado (están tal y como las dejó el proceso propietario). Periódicamente, estas páginas van siendo inicializadas con ceros,y añadidas a la lista de páginas a cero. El mecanismo de inicialización es para proteger la intimidad del antiguo proceso propietario. Cualquier página que se entrega a un proceso ha de haber sido convenientemente inicializada.
Cuando un proceso requiere memoria física, el VMM comienza cogiendo páginas de la lista de inicializadas. Cuando está vacía, toma de la lista de liberadas, y las inicializa. Cuando ésta se vacía, toma de la lista de inactivas, y las inicializa. Sólo como última opción recurre a las modificadas. Estas últimas requieren escritura en el archivo de paginación junto con inicialización, lo cual es un proceso lento.
En ambientes en los que la memoria es escasa, el gestor del conjunto de trabajo se centra en mantener un conjunto aceptable de páginas disponibles, más que en revisar las estadísticas de uso de la CPU.
Como valores aproximativos, podemos señalar que si el número de páginas a cero más las liberadas más las inactivas suman menos de 20, el gestor robará páginas a procesos que comparten la CPU. Si el número de modificadas supera las 30, procederá a descargar algunas a disco, pasándolas a inactivas.
Archivos Asignados en Memoria
Estudiemos ahora cómo el SO utiliza esta facilidad para cargar el código de un ejecutable y sus bibliotecas DLL asociadas.
Un archivo asignado en memoria es todo aquel archivo para el que se ha reservado una región del espacio de direcciones virtuales de un proceso. Puede estar asignado el archivo completo o sólo una porción del mismo (llamada vista).
En principio, el VMM no asigna ninguna página física para el archivo asignado en memoria. El proceso simplemente supone que en ciertas direcciones de su espacio tiene cargado el fichero, así que cuando acceda a alguna se producirá un fallo de página. Es entonces cuando el VMM le asigna algunas páginas físicas y las copia desde el disco (paginación bajo demanda, como comentábamos antes).
La gestión del VMM de los archivos asignados en memoria es como cualquier otra región del espacio direccionable del proceso, excepto que el swapping se hace directamente sobre el archivo (o sea, el archivo de intercambio es el propio archivo asignado en memoria).
Cuando se arranca un proceso con su código grabado en un fichero, el VMM asigna automáticamente dicho fichero en el espacio de direcciones del proceso. También asigna todas las bibliotecas incluidas explícitamente y todas aquellas a las que se hace referencia en el código. Dentro del ejecutable existe una tabla llamada tabla de activación imagen, incluida por el enlazador, cuyas entradas contienen las funciones de biblioteca que se llaman durante el código. Una vez que se cargan las DLL (bibliotecas) en el espacio, el VMM completa la tabla escribiendo para cada entrada la dirección que ocupa la correspondiente función en el espacio del proceso. Por tanto, cada llamada a función implica una búsqueda en la tabla.
Supongamos ahora que se arranca una segunda instancia del mismo proceso. Entonces el VMM pagina en
el espacio de direcciones del nuevo proceso el fichero y las DLL, pero no vuelve a asignar páginas físicas, sino que ambas instancias comparten todo, al menos en principio. La ventaja de esto es ahorrar memoria, pero el inconveniente más claro es que si uno de los procesos modificara, por ejemplo, alguna variable global de su segmento de datos, el otro la tendría igualmente modificada. Esto ocurre evidentemente también él la pila e incluso en el mismo código (por ejemplo al ejecutar un depurador sobre una de las instancias para meter puntos de ruptura, se modificaría el código, lo cual implicaría que en la otra también).
Para solucionar el problema, Windows NT (y también UNIX) tiene una propiedad denominada "copiar antes de escribir". El VMM intercepta cualquier instrucción de escritura en el archivo mapeado en memoria por parte de las instancias. Cuando ocurre, asigna una o varias páginas físicas para la instancia escritora, y copia los contenidos de las páginas originales en las nuevas. A partir de ahora, esa instancia posee su propia región del archivo para modificar a su antojo. Análogamente para datos y pila.
UNIX también trabaja así, aunque duplica las zonas de datos y pila por defecto, de forma que múltiples instancias comparten, en principio, sólo el código.
Con respecto a la tabla de activación imagen, decir que el VMM asigna las DLL por defecto en direcciones fijas dentro del espacio de direcciones del proceso. Así, múltiples instancias del mismo pueden compartir la misma tabla. No obstante, un proceso puede especificar la dirección base de cada DLL; en ese caso, el proceso tendría su propia tabla en memoria, con lo que resulta más eficiente que todos los procesos tengan las DLL asignadas en las mismas direcciones.
Desde el punto de vista del programador, se pueden asignar en memoria archivos de hasta 264 bytes usando vistas del archivo.
El archivo se abre con CreateFile. Los primero es crear un objeto de asignación de archivo para él, con la función CreateFileMapping indicando el tamaño real del archivo. Después podemos definir diferentes vistas, con CreateViewOfFile. La vista se abandona con UnmapViewOfFile (y fueza al VMM a escribir las modificaciones en disco).
Los archivos asignados en memoria son el único mecanismo de compartición de datos entre procesos. Existe un archivo que crea el objeto de asignación, y el resto de los procesos usarán el mismo objeto referenciado por la función OpenFileMapping. Si como descriptor de archivo se pasa a las funciones 0xFFFFFFFF, entonces el VMM usará el archivo de paginación como medio de compartición de datos.
En una red no es posible usar este mecanismo de compartición de datos, pues el SO no garantiza la coherencia de los mismos. Por ejemplo, una CPU en un ordenador podría modificar el archivo en disco y otra en otro distinto tenerlo en memoria, con lo cual no se percataría de la actualización.
Uso de Memoria Virtual por parte del Programador
Vamos a ofrecer unas pinceladas sobre cómo el programador puede hacer uso explícito del mecanismo de la memoria virtual, y así hacer aplicaciones más eficientes. Se trata sin duda de una forma elegante de programar, ya que permite situar objetos de grandes dimensiones en el espacio de direcciones del proceso, cuyo tamaño real se desconoce al tiempo de compilación. Por ejemplo, una hoja de cálculo.
Consiste en dos etapas: la reserva y la asignación.
La reserva no es más que indicar al SO que una región del espacio de direcciones del proceso se va a destinar a un determinado objeto u objetos. Por tanto no hay ninguna correspondencia con memoria física. El VMM toma nota en un VAD (Virtual Address Descriptor) de la dirección de inicio, número de bytes reservados, y protección de la zona (en efecto, las páginas pueden tener permisos de lectura, lectura/escritura, o ninguno). Si el proceso accede a una de estas direcciones, se elevará una excepción. La reserva se hace siempre por trozos de 64 Kb.
La asignación consiste en hacer corresponder toda o parte de la memoria antes reservada con memoria física. No obstante, el VMM no va a asignar memoria física inmediatamente, sino que simplemente actualizará el VAD para permitir que esa memoria sea ahora accesible, y se asegurará que en el archivo de paginación haya espacio suficiente. Cuando el proceso haga un acceso, se producirá un fallo de página, y es en ese momento cuando el VMM asignará páginas físicas (para la página que produzca el fallo y las vecinas) y actualizará la TIP del proceso.
El problema de usar memoria virtual explícitamente es que el programador debe llevar una lista de la memoria que ha asignado de entre la que ha reservado. Otra solución es instalar un manejador de excepciones, de manera que al acceder a una dirección reservada pero no asignada se eleve una excepción. El manejador de esa excepción hará la asignación y se continuará la ejecución.
Para reservar memoria se llama a la función VirtualAlloc pasándole el indicador MEM_RESERVE, y para asignar, pasándole MEM_COMMIT.
La memoria reservada o asignada se puede liberar con VirtualFree, pero entonces ha de liberarse por completo.
Con VirtualLock indicamos al VMM que no descargue una determinada página a disco cuando el proceso esté ejecutándose (aunque cuando no se ejecute perdamos el control).
Por último señalar que existen funciones del API Win32 que permiten verificar si una dirección virtual tiene correspondencia física o no.
Bloques (heaps)
Los bloques de memoria son muy útiles cuando el programador no necesita usar memoria virtual explícitamente. Podemos tener estructuras de datos distintas en bloques distintos de memoria, de manera que un fallo en la manipulación de estructuras de un tipo no repercuta en las demás.
Además, de este modo se gestionaría más eficientemente la memoria, ya que al borrar estructuras y escribir otras no se provocaría fragmentación interna dentro del bloque (al ser todas las de un tipo del mismo tamaño). Una última razón sería él poder minimizar los fallos de página. Estructuras en un mismo bloque son probables que compartan la misma página, luego podríamos acceder a todas las de un mismo bloque con sólo un fallo de página.
Windows NT soporta cuatro tipos de sistemas de ficheros (SF) disintos, y puede trabajar con los cuatro a la vez (un disco con varias particiones, en cada una un SF distinto). Son los siguientes:
Sistema de Archivos | Sistemas Operativos Soportados |
FAT | DOS, Windows NT, y OS/2 |
HPFS | OS/2 y Windows NT |
NTFS | Windows NT |
CDFS | Windows NT |
NTFS (New-Technology File System): Es el sistema de ficheros nativo de Windows-NT. Como características podemos señalar:
- Permite nombres de archivo de hasta 255 caracteres, sensibles al tipo de letra.
- Permite la gestión de medios de almacenamiento extraordinariamente grandes.
- Incorpora mecanismos para garantizar la seguridad.
- Soporta el concepto de enlace (por compatibilidad con el estándar POSIX).
- Es capaz de recomponerse rápidamente después de una caída del sistema.
- Soporta el estándar Unicode.
El que usa MS-DOS y Windows 16 bits. Es el SF más pobre, y es se mantiene para dar soporte a las aplicaciones DOS.
HPFS (High-Performance File System): Es el que usa el sistema operativo OS/2. Se ha incluido para dar soporte a las aplicaciones OS/2 y complementar así al subsistema del mismo nombre. No es capaz de recomponerse del todo bien después de una caída del sistema ni de asegurar la no corrupción de los datos.
CDFS (CD-ROM File System): Es un SF que Microsoft ha desarrollado exclusivamente para montarse sobre los CD-ROM.
Vamos a centrarnos en el más importante de los cuatro: NTFS. Este sistema de ficheros lleva incorporados muchos conceptos de teoría de bases de datos relacionales.
Proporciona la seguridad, recuperación y tolerancia a fallos en base a la redundancia de datos. De hecho,
implementa los cinco primeros niveles del pseudo-estándar RAID. RAID significa Redundant Arrays of Inexpensive Disks, algo así como "vectores redundantes de discos baratos". Actualmente, el coste de los almacenamientos masivos o secundarios es ínfimo, y ya se pueden encontrar discos de varios Gb por menos de S/.500.00.
A eso se refieren, a mi entender, las siglas. RAID es una "norma" de facto que se basa fundamentalmente en conseguir la integridad de los datos a base de dividirlos en pedazos y repartir los pedazos entre varios discos, junto con informaciones redundantes de comprobación de errores. Cada nivel ofrece una estrategia distinta para conseguir la integridad. Los niveles son:
Nivel 0: los datos de cada fichero se dividen en porciones, las cuales se reparten en un orden fijo entre los distintos discos con los que cuente el sistema. Realmente aquí no se usan códigos de comprobación de errores.
- Nivel 1: de cada disco se realiza una copia o espejo. Es la estrategia que da mayor fiabilidad (pero también, evidentemente, la más costosa).
- Nivel 2: como el 0, pero un algortimo va construyendo una serie de códigos correctores de errores. Esos códigos son igualmente distribuidos entre unos discos destinados especialmente a ese uso.
- Nivel 3: como el 2, pero no se usan códigos correctores sino un simple código de paridad, que puede ser guardado en un mismo disco para todos los ficheros.
- Nivel 4: como el 3, pero dividiendo los ficheros en segmentos de datos más grandes.
- Nivel 5: es el nivel más usual; es igual que el4, pero no usa un disco separado para almacenar los códigos de paridad, sino que divide igualmente esos códigos y los distribuye por los disco, intentando que no coincidan en la misma zona de disco datos de un fichero con sus códigos de paridad correspondientes.
- Nivel 6: igual que el 5 pero se auxilia de elementos hardware, tales como controladoras de disco especiales, fuentes de alimentación.
En NTFS, al igual que en los SF de UNIX, existe una serie de permisos sobre ficheros y directorios, que son los siguientes: lectura (R), escritura (W), ejecución (X), borrado (D), cambio de permisos (P) y ser el nuevo propietario (O). Todo fichero y directorio tienen un propietario, que puede conceder permisos sobre ellos. El Administrador del sistema puede tomar la propiedad de cualquier fichero o directorio sobre NTFS, pero no transferirla de nuevo a ningún otro usuario, a diferencia de UNIX, ni siquiera a su dueño original.
Sistemas de Archivos | Ventajas | Desventajas |
FAT | Poco consumo de sistema. El mejor para discos y/o particiones de menos de 200MB. | El rendimiento decrece con particiones de más 200MB. No se pueden aplicar permisos sobre archivos y directorios. |
HPFS | El mejor para discos y/o particiones entre 200 y 400 MB. Elimina la fragmentación almacenando en un solo bloque el archivo completo. | No es eficiente para menos de 200MB.No soporta hot fixing. No se pueden aplicar permisos sobre archivos y directorios. |
NTFS | El mejor para volúmenes de 400MB o más. Recuperable (registro de transacciones), diseñado para no ejecutarle utilerias de reparación. Es posible establecer permisos y registro de auditoría sobre archivos y directorios. | No recomendable para volúmenes de menos de 400MB. Consume de 1 a 5 MB de acuerdo al tamaño de la partición. |
Ventajas y Desventajas de los Sistemas de Archivos
A continuación vamos a comentar las llamadas al sistema más usuales para crear ficheros, leer de ellos, escribir en ellos, etc.
Creación/Apertura de Ficheros
Para crear/abrir un fichero se usa la llamada al sistema
HANDLE CreateFile (LPTSTR lpszName, DWORD fdwAccess, DWORD fdwShareNode, LPSECURITY_ATTRIBUTES lpsa, DWORD fdwCreate, DWORD fdwAttrsAndFlags, HANDLE hTemplateFile);
lpszName: nombre del archivo a crear o abrir.
fdwAccess: especifica el modo de acceso al archivo: lectura (GENERIC_READ), escritura (GENERIC_WRITE), o ambos.
fdwShareMode: permisos que tendrá la compartición del archivo a abrir: 0 (ningún proceso podrá abrirlo hasta que nosotros lo cerremos), FILE_SHARE_READ (otros procesos pueden abrirlo pero sólo para leer), FILE_SHARE_WRITE (sólo para escribir; no se suele usar), o una combinación de ambos.
lpsa: la típica estructura de seguridad de todo objeto en Windows NT. Sólo tendrá sentido si el archivo se crea en un SF que soporte seguridad, como NTFS.
fdwCreate: una serie de indicadores que especifican una acción:
CREATE_NEW: crea un archivo nuevo, y da error si ya existe
CREATE_ALWAYS: crea un archio nuevo,y si existe lo machaca
OPEN_EXISTING: abre un archivo, y da error si no existe
OPEN_ALWAYS: abre un archivo y si no existe lo crea
TRUNCATE_EXISTING: si el archivo exisye, lo abre pero truncando su tamaño a 0 bytes; si no existe, da una error.
fdwAttrsAndFlags: sirve para dar atributos al fichero (sólo si lo estamos creando) y activar ciertas banderas.
Veamos algunos.
Atributos:
FILE_ATTRIBUTE_HIDDEN: archivo oculto
FILE_ATTRIBUTE_NORMAL: archivo sin atributos especiales
FILE_ATTRIBUTE_READONLY: archivo de sólo lectura
FILE_ATTRIBUTE_SYSTEM: archivo del sistema
FILE_ATTRIBUTE_TEMPORARY: archivo temporal; el Executive intentará mantenerlo en RAM tanto como le sea posible
FILE_ATTRIBUTE_ATOMIC_WRITE: para indicar que los datos de este archivo son críticos; eso hará que el Executive aumente la frecuencia con que escribe estos datos de RAM a disco.
A propósito de los dos últimos indicadores, comentar que el Executive no escribe a disco inmediatamente los cambios realizados a un archivo en RAM, pues degradaría las prestaciones del sistema. Usa un mecanismo de escritura diferida, de manera que se escribe a disco cuando se cierra el archivo, o cuando el sistema está desocupado, o cuando es necesario hacer swapping. Para los ficheros cuyos datos son críticos, necesitamos que las modificaciones sean escritas rápidamente a un soporte no volátil, por si el sistema se cayera.
Banderas:
FILE_FLAG_NO_BUFFERING: con esta bandera le indicamos al Executive que no gestione buffers de memoria con relación a la entrada/salida de este archivo, sino que lea y escriba directamente a disco.
FILE_FLAG_RANDOM_ACCESS: queremos acceso directo al archivo.
FILE_FLAG_SEQUENTIAL_SCAN: acceso secuencial.
FILE_FLAG_WRITE_THROUGH: con este indicador, el SO enviará a disco los datos que hayan sido modificados en memoria, pero los mantendrá en memoria para acelerar las lecturas
FILE_FLAG_POSIX_SEMANTICS: acceso al archivo según el estándar POSIX (por ejemplo, sensible al tipo de letra)
FILE_FLAG_BACKUP_SEMANTICS: cuando un proceso solicita abrir un archivo, el SO normalmente realiza ciertos tests de seguridad para comprobar si el proceso tiene o no los permisos necesarios. Con este indicador se anulan ciertos tests, de manera que el SO comprueba si el proceso tiene permiso para acceder al archivo, y si es así, le permite el acceso pero sólo para realizar una copia de seguridad. Aunque tenga permiso de escritura, le será anulado.
FILE_FLAG_OVERLAPPED: los accesos a los archivos suelen hacerse de manera síncrona (el subproceso duerme hasta que se consuma el acceso). Con este indicador se permite realizar E/S asíncrona, con lo que el subproceso seguirá ejecutándose y el SO le informará cuando la E/S finalice.
hTemplate: el descriptor de un archivo ya abierto; si no es NULL, los atributos y banderas de dicho archivo serán asignados a los de nuestro archivo.
La llamada retorna el descriptor al objeto archivo, o -1 si hubo algún error.
Cierre de Ficheros
Se usa la misma llamada que para cerrar un descriptor a cualquier objeto. El Sistema Operativo decrementará el contador de uso del archivo, y si es 0 lo cerrará definitivamente.
BOOL CloseHandle (HANDLE hObject);
Lectura/escritura a ficheros
Windows NT permite que los subprocesos hagan E/S a ficheros de manera síncrona o asíncrona. EL modo síncrono es el habitual: un subproceso inicia una operación de E/S sobre un fichero; el Executive lo pondrá a dormir hasta que esa E/S de complete. En cambio, en el modo asíncrono, el subproceso que inicia la operación puede seguir ejecutándose, y cuando necesite los datos de la E/S se pondrá voluntariamente a esperar, de manera que si para ese tiempo la E/S se ha completado, obtendrá los resultados inmediatamente.
Se usan las mismas llamadas al sistema para ambos tipos de acceso. En dichas llamadas existirá un parámetro de entrada (lpOverlapped) que, si es NULL, indicará que la llamada es acceso síncrono; si no, indicará acceso asíncrono, dando la dirección de una estructura OVERLAPPED que tiene el siguiente formato:
typedef struct _OVERLAPPED{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
DWORD hEvent;} OVERLAPPED;
Internal: cuando la E/S se completa, el sistema guarda en esa palabra un estado interno.
InternalHigh: cuando la E/S se completa, el sistema guarda ahí el número de bytes transferidos.
Offset,OffsetHigh: indican la posición del byte del archivo donde queremos comenzar el acceso.
hEvent: el descriptor (opcional) de un objeto suceso.
Vamos a suponer que un subproceso A desea realizar E/S asíncrona sobre un fichero, y para ello inicializa el parámetro lpOverlapped de la llamada con la dirección correspondiente a una estructura OVERLAPPED. Entonces sigue ejecutándose. Cuando al Executive le llega la petición de E/S sobre el fichero, pone el estado de este objeto a no señalado. Cuando la E/S finaliza, lo pone a señalado (esto ya lo vimos en el capítulo de los procesos). En algún momento, A necesita los datos de la E/S que inició, con lo que se pone a esperar a que el objeto archivo se ponga a estado señalado (para lo cual usará una llamada tipo WaitFor…Object(s), como ya explicamos). Si, en el momento de hacer la llamada a E/S, el subproceso A hubiera especificado en hEvent un descriptor a un suceso, el Executive pondría a señalados tanto el objeto fichero como dicho objeto suceso, con lo cual el subproceso A tendría la facilidad de esperar por cualquiera de los dos.
La utilidad de esto es en la situación de que el fichero es compartido, y varios subprocesos pueden estar haciendo E/S sobre él y, por consiguiente, poniéndose a estado señalado/no señalado múltiples veces. Especificando un objeto suceso propiedad de A, dicho subproceso sabrá que cuando el suceso esté señalado, seguro que se ha completado su operación de E/S y no la de otro.
Una vez explicados estos matices, pasamos sin más a describir el perfil de las llamadas de lectura/escritura, que son muy parecidas a las que usa UNIX:
BOOL ReadFile (HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
hFile: es el descriptor al archivo.
nNumberOfBytesToRead: es el número de bytes a leer.
nNumberOfBytesRead: es un parámetro de salida que indica el número de bytes que se leyeron en realidad.
lpOverlapped: elige modo síncrono/asíncrono; ya comentado
BOOL WriteFile(HANDLE hFile, CONST VOID * lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);
hFile: es el descriptor al archivo.
lpBuffer: apunta a un área de memoria donde se encuentran los datos a escribir.
nNumberOfBytesToWrite: número de bytes a escribir.
nNumberOfBytesWritten: número de bytes escritos en realidad.
lpOverlapped: elige modo síncrono/asíncrono; ya comentado.
Nos gustaría señalar que en los accesos síncronos existe un puntero de lectura/escritura sobre un archivo para cada subproceso que esté accediendo al mismo. Ese puntero lo asigna el SO en el momento de apertura del archivo cuando un subproceso lo abre para accesos síncronos (o sea, sin especificar el indicador FILE_FLAG_OVERLAPED). El puntero se modifica al leer y al escribir. En el caso de que el acceso síncrono sea también acceso directo, entonces es posible cambiar el puntero usando la llamada SetFilePointer, que no merece la pena comentar.
Nótese que en los accesos asíncronos no existe el puntero, por lo que hemos de indicar la dirección de inicio de cada acceso (recordemos que se indica en la estructura OVERLAPPED).
Existen unas llamadas de lectura/escritura extendidas (ReadFileEx y WriteFileEx), que son muy útiles a la hora de realizar una E/S asíncrona. Permiten que se les pase la dirección de una función de forma que, cuando la E/S asíncrona se complete, se salte a la ejecución de esa función. Para ello, el subproceso ha de estar esperando por el fin de la E/S con una función WaitFor…Object(s) extendida (WaitForSingleObjectEx o WaitForSingleObjectsEx). De hecho, WaitForSingleObject está construida internamente como una llamada a WaitForSingleObjectEx pasándole un parámetro que indica que no queremos uso extendido.
Atributos de Ficheros
Los atributos que se indican al crear un fichero en el parámetro fwdAttrsAndFlags pueden ser consultados con una llamada a:
BOOL GetFileInformationByHandle (HANDLE hFile, LPBY_HANDLE_FILE_INFORMATION lpFileInformation);
Esta llamada devuelve en la estructura apuntada por lpFileInformation toda la información relativa al fichero cuyo descriptor es hFile. La estructura tiene los siguientes campos:typedef struct _BY_HANDLE_FILE_INFORMATION {
DWORD dwFileAtributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD dwVolumeSerialNumber;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD nNumberOfLinks;
DWORD nFileIndexHigh;
DOWRD nFileIndexLow;} BY_HANDLE_FILE_INFORMATION;
dwFileAttributes: los atributos del fichero que se pasaron en el parámetro fdwAttrsAndFlags en el momento de su creación (ver CreateFile)
ftCreationTime: fecha de creación del fichero
ftLastAccessTime: fecha del último acceso al fichero
ftLastWriteTime: fecha de la última escritura al fichero
dwVolumeSerialNumber: número de serie del volumen donde se encuentra el fichero
nFileSizeHigh, nFileSizeLow: 64 bits que indican el tamaño del fichero; por tanto, Windows NT permite ficheros de hasta 264 bytes
nNumberOFLinks: número de enlaces del fichero; este parámetro asegura la compatibilidad con el estándar POSIX. Recordemos que en UNIX cada fichero tiene un número de enlaces que indican los distintos nombres que referencian al mismo fichero dentro del sistema de ficheros. Así se evita la redundancia de datos.
nFileIndexHigh, nFileIndexLow: es un identificador único que el Executive asocia a un fichero en el momento de que algún subproceso lo abre. Si el sistema se apaga y se enciende, el identificador puede no ser el mismo. Sin embargo, en la misma sesión, procesos distintos leerán el mismo identificador. Esto es útil para determinar si dos descriptores distintos referencian en realidad al mismo fichero dentro de un volumen (basta hacer sendas llamadas a GetFileInformationByHandle y ver si el identificador y número de volumen coinciden en las estructuras devueltas por cada una).
Bloqueo de Archivos
Otra llamada muy importante es la que permite el bloqueo de todo o parte de un fichero, de manera que ningún otro subproceso pueda acceder a la región bloqueada.
La llamada para bloquear es:
BOOL LockFile (HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFIleOffsetHigh, DWORD cbLockLow, DWORD cbLockHigh);
hFile: descriptor al archivo a bloquear.
dwFileOffsetLow, dwFileOffsetHigh: dirección de comienzo de la región a bloquear.
cbLockLow, cbLockHigh: tamaño de la región a bloquear.
La llamada para desbloquear es:
BOOL UnlockFile (HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFIleOffsetHigh, DWORD cbUnlockLow, DWORD cbUnlockHigh);
Los parámetros son análogos a los anteriores. Es necesario que un subproceso desbloquee un área que previamente bloqueó antes de que finalice. En caso contrario, estará impidiendo el acceso al fichero a todos los demás subprocesos.
Existen versiones extendidas de ambas llamadas (LockFileEx y UnlockFileEx) que permiten establaces bloqueos pero sólo de escritura (otros subprocesos podrán leer el área bloqueada, pero no escribir en ella).
A lo largo de los capítulos precedentes hemos hablado de los distintos aspectos de la E/S: gestión de las caches, E/S síncrona y asíncrona, drivers de dispositivo, nivel de abstracción de hardware, ficheros y sistemas de ficheros, por citar algunos. Cuando describíamos el administrador de E/S del Executive, hablamos también de que se encargaba de gestionar los mecanismos relacionados con la red. Me parece conveniente dedicar parte de este último capítulo al tema de las comunicaciones en Windows NT, por tratarse de uno de los aspectos donde más fuertemente brilla este SO.
Windows NT soporta trabajo en red con varios protocolos de comunicaciones. Lo más importante es que las facilidades de red están integradas en el SO, lo que lo distingue de DOS y de la mayoría de las versiones de UNIX, en los que las interfaces con la red eran un añadido al SO, y frecuentemente no se adaptaban del todo bien al mismo.
Vamos a describir brevemente qué ocurre en cada uno de los niveles de implementación del modelo OSI:
En el nivel 0 aparece un dispositivo que es la tarjeta de interfaz a la red (Network Interface Card, NIC). El NIC conecta el bus interno de la máquina con la red, sirviendo de interfaz entre el nivel 0 y el 1 (nivel físico). Es contemplado por el SO como un periférico más, controlado a través de su driver correspondiente.
En el nivel 2 (nivel de enlace de datos) aparece un software llamado NDIS (Network Device Interface Specification), que es una interfaz entre los drivers de dispositivo del NIC y los protocolos de transporte.
En los niveles 3 (nivel de red) y 4 (nivel de transporte) Windows NT sitúa el software de los protocolos de transporte. Soporta TCP/IP, NBF, NWLink, DLC y AppleTalk.
En el nivel 5 (de sesión) aparecen dos interfaces con los protocolos de transporte, que son los Windows Sockets (WinSock) y la NetBIOS.
Un socket es un mecanismo para establecer una conexión entre dos máquinas. Actúa como una especie de tubería bidireccional para los datos. Fueron introducidos por primera vez por el UNIX de Berkeley, y Windows NT incorpora una versión especial llamada WinSock. Se utilizan cuando se quiere una comunicación a través del protocolo TPC/IP, IPX/SPX.
La interfaz NetBIOS es usada por aquellas aplicaciones que deseen usar protocolos que se adapten a NetBIOS (como el NBF). Establece conexiones entre distintas máquinas de la red y se encarga de que la transmisión sea fiable una vez establecida la conexión.
En la misma capa de sesión están dos subsistemas integrales gestionados por el administrador de la E/S, denominados el redireccionador y el servidor. El redireccionador es el responsable de enviar peticiones de E/S a lo largo de la red, cuando el fichero o dispositivo solicitados son remotos. Al servidor le llegan peticiones desde los redireccionadores clientes, y las gestiona de modo que alcancen su destino. Tanto servidores como redireccionadores son implementados como drivers del sistema de ficheros. Así, cuando un proceso quiere realizar una E/S, usará las mismas llamadas al sistema para acceso local o remoto, con lo que no necesita conocer la ubicación del recurso (fichero o dispositivo). Pueden existir múltiples parejas de redireccionadores-servidores ejecutándose concurrentemente.
En el nivel 6 (capa de presentación) define como se presenta la red a si misma ante la maquina y sus programas y/o aplicaciones.
En el nivel 7 (capa de aplicación) existe un proceso llamado suministrador por cada redireccionador de la capa 5. Cuando una aplicación hace una llamada de E/S, un software llamado enrutador de suministradores (multiple provider router) determina el suministrador adecuado, y le envía la petición. El suministrador, a su vez, se la pasará al correspondiente redireccionador. Por ejemplo, el gestor de ficheros (del administrador de E/S) es una aplicación que usa los servicios de los suministradores.
El último tema sobre E/S que vamos a tratar es la gestión de entradas del usuario (mediante teclado o ratón). Cuando el sistema se arranca y se crea el proceso subsistema Win32, este proceso crea a su vez un subproceso llamado subproceso de entrada inicial (Raw Input Thread, RIT), que por lo general está inactivo. Cada vez que un usuario pulsa una tecla, o mueve o pulsa el ratón, el driver de dispositivo correspondiente añade un suceso hardware a la cola de mensajes del RIT. Entonces, el RIT despierta, examina el mensaje, determina qué tipo de suceso es (WM_KEY*, WM_?BUTTON*, WM_MOUSEMOVE) y a qué subproceso va dirigido, y lo envía a la cola de mensajes de dicho subproceso. Para determinar el subproceso destino, el RIT examina sobre qué ventana se encontraba el ratón cuando se produjo el suceso, y determina qué subproceso es el propietario de ese objeto ventana. Si es una secuencia de teclas, el RIT busca qué subproceso está actualmente en primer plano, y a él le mandará el mensaje.
No obstante, el RIT también monitoriza qué tipo de entrada es, para que de esta manera se pueda cambiar de contexto (Alt-Tab), o llamar al proceso Administrador de Tareas (si Ctrl-Esc) o visualizar la ventana de diálogo de la seguridad (si Ctrl-Alt-Del), etc.
En la versión 4.0, Microsoft nos dio una alegría al cambiar la interfaz gráfica de Windows NT y sustituirla por Indy, la bonita GUI de Windows 95. Desgraciadamente, NT 4.0 no incluía muchas de las cosas que se venían anunciando desde hacía tiempo, lo que nos dejó un sabor agridulce.
En Windows NT 5.0, Microsoft da un paso de gigante, incluyendo las siguientes novedades importantes:
- Servicios de Directorio al estilo de X.500.
- Modelo de Objetos de Componentes Distribuidos (DCOM).
- Sistema de seguridad Kerberos.
Servicios de Directorio: Active Directory
NT 5.0 incluye un servicio de directorio llamado Active Directory, basado en DNS (Domain Name Server) y LDAP (Lightweight Directoy Access Protocol).
El protocolo DNS da una manera de nombrar máquinas situadas en cualquier parte del planeta y nos permite conocer sus correspondientes direcciones IP, gracias a que estructura los nombres de las máquinas jerárquicamente por dominios, y cada dominio conoce potencialmente las direcciones IP de las máquinas que pertenecen a él. Si quiero conocer la dirección de la máquina mi_servidor.dom1.edu, preguntaré a algún servidor del dominio edu, el cual, o la sabe directamente, o preguntará a algún servidor de dom1, y así.
Con Active Directory vamos a poder localizar cualquier objeto llamándolo por su nombre, y acceder a información sobre él. Un objeto será algo heterogéneo: una máquina en Internet, un fichero, o un proceso en ejecución. La información sobre ese objeto dependerá de la clase a la que pertenezca dicho objeto (por tanto será también algo heterogéneo). El pegamento que aglutina todo esto se llama Active Directory.
La idea no es nueva: el sistema de directorios X-500 permite algo parecido, pero su complejidad ha hecho que no esté muy difundido entre los sistemas operativos. Por ello se desarrolló LDAP, una versión simplificada de X-500.
En Active Directory va a tener, para cada dominio de nuestra LAN, un nombre de dominio al estilo DNS, y uno o varios servidores de dominio (llamados controladores de dominio). Cuando LDAP sea, al igual que DNS, un estándar, podremos acceder a la base de datos de directorios del servidor Active Directory usando un cliente UNIX, OS/2 o Macintosh.
Tener varios controladores de dominio asociados al mismo dominio interesa cuando necesitemos alto rendimiento y baja tasa de errores. Cada controlador va a almacenar la misma base de datos del dominio del directorio. Active Directory asegura la integridad de esas BD, de manera que actualizar una implique la actualización de cada una de sus copias. Para ello se usa un protocolo de comunicación entre controladores. La actualización de las copias se realiza sólo sobre los datos modificados.
La replicación se realiza vía RPCs cuando estamos en un ámbito local, con alta fiabilidad y baja tasa de errores. Para redes a través de líneas telefónicas, se ha incluido la opción del correo electrónico como método para que los controladores se intercambien información de replicación.
En una LAN, la replicación se produce cada 5 minutos. En una WAN, el administrador puede ampliar el intervalo para aumentar así las prestaciones.
Existen varias alternativas a la hora de elegir una API para los clientes Active Directory, destacando ADSI (Active Directory Sservice Interface).
Modelo de Objetos de Componentes Distribuidos (DCOM)
DCOM es una extensión natural a COM. COM es un estándar para la comunicación entre objetos independientemente del lenguaje en el que hayan sido escritos (por ejemplo, objetos Java con obejtos C++). El objeto cliente accederá a los métodos del objeto servidor a través de interfaces COM normalizados. Quizás nos suene más el nombre de componentes ActiveX.
DCOM extiende lo anterior a un ámbito de red; los objetos a comunicarse no tienen porqué compartir la misma máquina.
Pero esto no es nuevo: lo podíamos hacer desde hacía tiempo con las RPC. De hecho, DCOM está construído sobre las RPC; se trata de un estándar de más alto nivel, con el que podremos escribir aplicaciones distribuidas en un entorno de red sin necesidad de conocer todos los entresijos de las RPC, y además con un enfoque orientado a objetos. De hecho, Microsoft también se refiere a DCOM como Object RPC (ORPC).
DCOM se incluyó a Windows NT en su versión 4.0 (también salió en 1.996 una versión para Windows 95), y actualmente la empresa Sofware AG prevé lanzar versiones para Solaris, Linux y otros sistemas a finales de este año. Con esto tenemos que DCOM se está convirtiendo en un estándar importante en la industria, y se podrá utilizar para comunicación entre objetos corriendo en sistemas operativos distintos.
Entonces, ¿qué aporta NT 5.0?
Supongamos que tengo un objeto servidor subsumido en un proceso en mi máquina servidora, y que un cliente quiere acceder a alguno de los métodos que mi objeto exporta como públicos. Entonces, el componente DCOM localiza él solito al objeto servidor en la red, y le manda una RPC. DCOM encuentra el objeto servidor en la red de dos posibles maneras:
En Windows NT 4.0, el cliente ha de conocer dónde está el servidor (lo tiene escrito en el Registro) y se lo dice a DCOM (trivial).
En Windows NT 5.0, DCOM usa Active Directory para hallar la dirección del objeto. Ésta es la gran ventaja sobre el caso anterior. Además de la dirección, puede encontrar más información sobre el objeto servidor.
Servicios de Seguridad: el estándar Kerberos
En UNIX, de la seguridad se encarga un módulo llamado Kerberos, desarrollado por el MIT como parte del Proyecto Atenas. Kerberos es actualmente un estándar en la industria, y Microsoft ha implementado la versión 5 de la norma en NT 5.0.
Como sabemos, NT soporta varios protocolos de seguridad. Existe, no obstante, una interfaz común que los aglutina (la SSPI, Security Service Provider Interface), y que proporciona una API común a los niveles superiores. En NT 4.0, la API cubre los protocolos SSL (Secure Sockets Layer, junto con su versión PCT) y NTLM (NT Lan Manager). En NT 5.0, Kerberos se une al grupo. Los usuarios del nivel de seguridad son otros protocolos, que, usando la API SSPI, acceden a los servicios que ofrece ese nivel, eligiendo el protocolo que deseen de entre los de la lista. Ejemplos de protocolos clientes son HTTP, LDAP, CIFS (usado por el Sistema de Ficheros Distribuido) y RPC.
En entornos de red, las aplicaciones usan primordialmente el protocolo NTLM, que da autentificación, integridad de datos y privacidad. Esto va a cambiar con la introducción del estándar Kerberos.
Kerberos, al igual que NTLM, proporciona autentificación, integridad de datos y privacidad. Entre las mejoras que hace a NTLM se encuentra la autentificación mutua, es decir, que tanto el cliente ha de probar su identidad al servidor como el servidor al cliente. Cada dominio de la red va a tener su servidor Kerberos, que utiliza la base de datos de Active Directory, con lo que se habrá de ejecutar sobre la misma máquina que el controlador de dominio. También puede estar replicado dentro del mismo dominio.
De manera muy general, podemos decir que un usuario que desee acceder a servicios de una máquina remota debe primero hacer logon sobre el servidor Kerberos del dominio correspondiente (o sobre alguna de sus copias, si cabe).
Si los procesos de identificación y declaración de privilegios son correctos, Kerberos entrega al cliente un "ticket que concede tickets" llamado TGT (ticket-granting ticket) de acuerdo a los privilegios del cliente. Usando ese ticket, el cliente podrá de nuevo solicitar a Kerberos otros tickets para acceder a determinados servidores del dominio. Kerberos examinará el TGT del cliente y el servicio que desea; si el TGT es válido para acceder al servicio solicitado, Kerberos entregará al cliente un ticket nuevo para acceder al servicio concreto. El cliente envía ese nuevo ticket a la máquina donde se encuentra el servicio al que desea acceder; el servidor posiblemente lo examinará para comprobar la identificación del usuario (autentificación). Los tickets están todos encriptados.
El estándar Kerberos versión 5 no especifica el contenido de los mensajes que se intercambian para identificar al cliente. Por ello, los mensajes de cada implementación de la norma serán distintos, con lo que podríamos tener problemas al interactuar con Kerberos de otros sistemas operativos.
Andres s. Tanenbaum. Sistemas operativos modernos. 1992. Jose cañete v. Microsoft windows nt. 1997. Al servati. La biblia de intranet. 1997. Erick jimenes m. Sistemas operativos de redes. Www.tecmor.mx/mats-dsc/sor/indice~ 1.html Jose gonzales m. Implementación de una intranet sobre windows nt. Www.globalnet.com.mx/intranet/index.html Microsoft. Documento NT4UNIX.DOC. www.microsoft.com
Trabajo enviado y realizado por: Saul Gonzales
Universidad Nacional Mayor de San Marcos
Página anterior | Volver al principio del trabajo | Página siguiente |