NLM.

 

NOVELL

LOADER

MODULES

 

Informática Distribuida.

Antonio Teruel Fernandez.

Ricardo Vítores Delgado.

(Ingenieria Técnica en Informática de Sistemas)

 

ÍNDICE

 

CAPÍTULO 1: INTRODUCCIÓN A LOS NLM

1.1.- Introducción

1.2.- Definición de un NLM

1.2.1.- Aspecto de un NLM

1.2.2.- Compilación de un NLM

1.2.3.- Componentes de programación de un NLM

1.3.- Introducción a las librerías NetWare de interfaz para los módulos NLM

1.3.1.- Razones para el conocimiento del interfaz CLIB

1.3.2.- Categorías generales de las funciones CLIB

1.3.3.- Utilización de las API en las librerías NLM

1.4.- El Enlazador/Cargador

1.4.1.- El fichero Prelude.obj

1.4.2.- Diferencia entre el enlazador NLM y un enlazador convencional

1.4.3.- Utilización del enlazador NLM

1.5.- Programación multiproceso

1.5.1.- Definición de programación multiproceso

1.5.2.- Aspectos fundamentales de la programación multiproceso

1.6.- Variables de contexto NLM

1.6.1.- Definición de contexto NLM

1.6.2.- Establecimiento de los contextos NLM

1.7.- Norma de utilización de pantallas

1.7.1.- Definición de pantalla

1.8.- Librerías NLM

1.8.1.- Definición de librerías NLM

1.8.2.- Creación de una librería NLM

1.8.3.- Utilización de una librería NLM

1.9.- Introducción al SPX II

1.9.1.- Definición del SPX II

1.9.2.- Diferencias entre el SPX II y el IPX/SPX

1.9.3.- Utilización del SPX II

 

CAPÍTULO 2: PROGRAMACIÓN MULTIPROCESO

2.1.- Definición de procesos ligeros y entorno non-preemptive

2.2.- El entorno non-preemtive

2.3.- Algunas rutinas del interfaz C que vamos a ver en este capítulo

2.4.- Ejemplo de un NLM multiproceso

2.4.1.- Parte del servidor

2.4.2.- Parte del cliente

2.4.3.- El proceso ligero principal del NLM Engine (motor)

2.4.3.1.- Grupo de procesos ligeros por defecto

2.4.3.2.- Tareas de preparación (setup)

2.4.4.- Preparación y creación de procesos ligeros adicionales

2.4.5.- La rutina OpenLocalSemaphore

2.4.6.- InitMain: Un proceso ligero adicional del grupo de procesos ligeros por defecto

2.4.7.- Inicialización de los ECB de escucha y envío

2.4.7.1.- ECB de escucha

2.4.7.2.- ECB de envío

2.4.8.- Suspensión de InitMain

2.4.8.1.- Rutinas para suspender un proceso ligero

2.4.8.2.- Funcionamiento de WaitOnLocalSemaphore

2.4.9.- Desbloqueo de InitMain.

2.4.10.- Creación de un grupo de procesos ligeros en Main

2.4.11.- Inicialización de los ECB de escucha y activación de la escucha

2.4.12.- Suspensión de EngineMain

2.4.13.- Desbloqueo de EngineMain y procesamiento de peticiones

2.4.13.1.- Definición de opNode

2.4.13.2.- Búsqueda de un opNode vacío

2.4.14.- Salida del NLM Engine

2.4.14.1.- La función WaitOnLocalSemaphore y el semáforo de cierre

2.4.14.2.- La rutina Signal

2.4.14.3.- La rutina atexit

2.5.- Listado del fichero Engine.c

 

CAPÍTULO 3: PROGRAMACIÓN BÁSICA DEL SISTEMA DE FICHEROS

3.1.- Visión de conjunto del sistema de ficheros NetWare 4.0

3.2.- El volumen NetWare

3.2.1.- Almacenamiento físico de un volumen

3.2.2.- El sistema de ficheros lógico

3.2.2.1.- La FAT del volumen

3.2.2.2.- La tabla de entradas de directorio del volumen

3.2.3.- La memoria cache del sistema de ficheros

3.2.4.- Control de concurrencia

3.2.5.- Control de transacciones

3.2.6.- Ficheros dispersos

3.2.7.- Compresión

 

CAPÍTULO 4: COMUNICACIONES IPX/SPX Y SPX II

4.1.- Introducción

4.2.- Programación IPX

4.2.1.- Componentes de IPX

4.2.2.- Utilización de rutina de servicio de eventos

4.3.- Utilización de SPX

4.3.1.- Comparación entre IPX y SPX

4.4.- SPX II

4.4.1.- Las ventajas de SPX II

4.4.2.- Introducción a TLI para SPX II

BIBLIOGRAFIA

 

CAPITULO 1: INTRODUCCIÓN A LOS NLM.

1.1.- INTRODUCCIÓN:

Partiendo de la base que para utilizar NLM hemos de utilizar el Sistema Operativo (S.O) Netware haremos una breve introducción de referencia a dicho S.O.

Todas y cada una de las líneas de código del S.O Netware han sido escritas con el propósito de conseguir un alto rendimiento como máxima prioridad. Para ser capaces de realizar las tareas más eficientemente, Netware asume que las aplicaciones (los NLM) que se ejecutan junto al S.O conocen el entorno especializado en el que se encuentran, y actúan de forma adecuada.

En efecto, si un NLM no cede el control de la CPU periódicamente, los demás NLM no podrán ejecutarse, ya que no tienen acceso a la CPU. En un entorno non-preemptive (tanto las aplicaciones como el S.O se ejecutan en su mayor parte sin que puedan ser interrumpidas por otras aplicaciones) como éste, darse cuenta de las consecuencias del acaparamiento de la CPU y, por tanto, entender que las aplicaciones deben de comportarse convenientemente.

En general , el S.O Netware confía en que todos los NLM se comporten debidamente. Esto permite que Netware pueda ejecutarse con un rendimiento mayor.

Los programadores de NLM deben de tener en cuenta que el S.O sobre el que van a trabajar no es un S.O de propósito general.

 

1.2.- DEFINICIÓN DE NLM

Un módulo cargable Netware (NLM) es un programa que se ejecuta en la misma máquina que un servidor Netware o que un router, ejecutándose junto con el S.O Netware o con el software del router.

 

1.2.1.- Aspecto de un NLM:

En su forma ejecutable, un NLM es un fichero que el S.O Netware puede cargar y ejecutar en un servidor Netware. El nombre de este fichero sigue la convenciones de cualquier fichero ejecutable del DOS, salvo que la extensión tiene que ser .NLM, .DSK, .NAM o .LAN. Los NLM que tienen extensión .NLM proporcionan normalmente algún tipo de servicio o función de utilidad, siendo este el tipo en el que el usuario tendrá que programar en la mayoría de las ocasiones.

Los NLM con extensión .DSK son controladores de disco, aquellos con extensión .NAM son módulos del espacio de nombres, y aquellos con extensión .LAN son controladores de tarjetas del interfaz de red.

El código fuente de un NLM es similar al de cualquier otro programa escrito en lenguaje C. Los ficheros necesarios para construir un módulo NLM son los siguientes:

PRELUDE.OBJ

Un fichero de definición (.DEF)

Ficheros fuente en lenguaje C

El fichero PRELUDE.OBJ se enlaza con el NLM y proporciona el código necesario para el inicio del programa, así como la funcionalidad estandard del ANSI C, que incluye funciones como la gestión de argumentos en tiempo de ejecución y la identificación de la función main.

El fichero de definición de un NLM es un fichero de texto que contiene directivas de compilación y enlazado, así como cierta información descriptiva sobre el NLM.

 

1.2.2.- Compilación de un NLM

Un NLM se compila de manera similar a la de cualquier otro programa.

Como se indicó anteriormente, es necesario enlazar el fichero PRELUDE.OBJ con el código de los ficheros fuentes, así como especificar una serie de parámetros en el fichero de definición.

El compilador de C y el enlazador que habitualmente se utilizan para construir módulos NLM son los de la casa WATCOM.

 

1.2.3.- Componentes de Programación de un NLM

Los NLM se ejecutan junto con el S.O Netware. Esto significa que ambos utilizan la misma memoria y la misma CPU. El programador puede asignar su propia pila, sus variables y su memoria. También están a su disposición varias librerías con gran numero de funciones de utilidad. Estas librerías permiten al programador acceder a información y servicios que ofrece el S.O Netware.

 

1.3.- INTRODUCCIÓN A LAS LIBRERÍAS NETWARE DE INTERFAZ PARA LOS MÓDULOS NLM

Las librerías que se pueden encontrar escritas a su vez en forma NLM son las siguientes:

CLIB.NLM, DSAPI.NLM, MATHLIB.NLM, MATHLIBC.NLM,AIO.NLM Y NWSNUT.NLM. Los NLM de CLIB y DSAPI son probablemente los mas útiles para el programador. Los NLM de MATHLIB contienen funciones matemáticas y el NLM de AIO proporciona funciones de entrada/salida asíncronas. El NLM de NWSNUT contiene funciones de pantalla para la consola del servidor. Estas funciones permiten dotar a las pantallas de salida de un NLM del mismo formato que las de las utilidades Netware.

La mayoría de las funciones CLIB son casi idénticas a las de la librería del interfaz con C de Netware que se incluye en los kit de desarrollo Novell para estaciones de trabajo.

No obstante, en las librerías CLIB existen algunas funciones adicionales específicas para los NLM. Estas funciones proporcionan servicios como el control de procesos ligeros y acceso al sistema de ficheros.

La librería DSAPI contiene las funciones necesarias para ver, modificar y manipular la base de información de directorio Netware.

 

1.3.1.- Razones para el Conocimiento del Interfaz CLIB

Son muchas las razones para conocer la librería de interfaz CLIB.

La más importante es que el programador no necesita ni escribir ni probar código para realizar una serie de funciones de uso frecuente.

Dado que los NLM funcionan en un entorno especial ( el S.O Netware), hacen falta hooks especiales en el S.O para que las NLM puedan interaccionar con él. Muchas de las funciones que los NLM realizan son del tipo de las que solicita un cliente en una estación de trabajo Netware.

Funciones de este tipo son las de petición de entrada/salida, el bloqueo de ficheros, la solicitud de información sobre el servidor Netware, etc . Todas estas funciones pueden solicitarse directamente usando el interfaz del protocolo del núcleo Netware, pero la librería CLIB simplifica mucho el proceso.

 

1.3.2.- Categorías Generales de las Funciones CLIB

A continuación vamos a describir brevemente, por orden alfabético, cada una de las categorías en las que se clasifican estas funciones:

Número de conexión y servicios de gestión de tareas. El servidor de Netware que ejecuta los NLM necesita un modo de identificar a cada NLM y a cada tarea dentro de un NLM. Así , el NLM que solicita un servicio puede ser identificado unívocamente.

Servicios API para librerías. Estas funciones permiten al programador crear sus propias librerías.

Servicios avanzados. Permite gestionar arrays dinámicos, múltiples procesos ligeros y los recursos de los clientes, asociar atributos a ficheros y gestionar memoria.

Servicios de auditoria. Permiten la creación y el análisis posterior de una secuencia de eventos que suceden en el servidor Netware.

Servicios del Bindery. Estos son los servicios de enlaces tradicionales que usan las versiones anteriores a Netware 4.0 para poder acceder a la base de datos de identificación de objetos.

Servicios de capacitación. Permiten a los NLM mostrar sus mensajes de salida en diferentes idiomas.

Servicios de comunicación. Son las funciones necesarias para el establecimiento y mantenimiento de comunicaciones tanto del tipo cliente/servidor como la que se realiza entre módulos de la misma categoría.

Servicios de conexión. Permiten obtener información sobre las conexiones estandard de los clientes.

Servicios de contabilidad. Permiten conocer la cantidad de recursos del servidor Netware que usan los NLM o los clientes en las estaciones de trabajo.

Servicios directos del sistema de ficheros. Proporcionan acceso directo al sistema de ficheros

Servicios de entrada/salida del S.O. Realizan funciones de entrada/salida que no requieren buffer. Se utilizan normalmente en combinación con funciones de entrada/salida de tipo stream.

Servicios de entrada/salida de tipo stream. Se utilizan en combinación con las funciones de entrada/salida del S.O.

Servicios del interfaz de usuario Netware con módulos NLM. Proporciona un interfaz de usuario para la salida de consola por pantalla de formato bastante similar al que utilizan las utilidades de Netware.

Servicio de mensajes. Realizan tareas como el envío de mensajes de difusión y el envío de mensajes de consola a la pantalla de un cliente Netware.

Servicios de migración de datos. Permite cambiar de sitio los datos de un fichero manteniendo al mismo tiempo activa e intacta la información de almacenaje e identificación del fichero.

Servicios de la partición del DOS. El disco del servidor Netware puede dedicar una partición al S.O DOS. Esta partición se puede acceder utilizando estas funciones.

Servicios de procesos ligeros. Se utilizan para crear y utilizar procesos ligeros dentro de un NLM.

Servicios de protocolo de anuncio de servicios (SAP). Se utilizan para hacer públicos los servicios proporcionados por los NLM de un servidor.

Servicios del protocolo de gestión de ficheros AppleTalk (AFP). Esta funciones hacen posibles el acceso y manipulación de ficheros creados por un ordenador Apple.

Servicios de seguimiento del sistema de ficheros. Permiten al programador vigilar la actividad del sistema de ficheros.

Servicios de sincronización. Proporcionan la posibilidad e bloquear un fichero para el acceso exclusivo, así como controlar el flujo de los accesos a ficheros mediante semáforos.

Servicios del sistema de ficheros. Realizan operaciones de ficheros específicas de Netware, como recuperación y purgado de ficheros.

Servicios del sistema de gestión de colas. Permiten al programador el uso de cualquier tipo de colas Netware.

Servicios del sistema de seguimiento de transacciones. Permiten seguir los pasos de las modificaciones de datos en el sistema de ficheros. Asimismo, este sistema anula automáticamente las transacciones que no se han realizado en su totalidad.

Servicios de STREAMS de Unix. Proporcionan las funciones necesarias para la transmisión de datos entre un controlador de dispositivo y una aplicación en el entorno de un servidor Netware o de un NLM. También permiten la colocación de módulos software en el camino de transferencia de datos, para la realización automática de operaciones específicas sobre los mismos.

Servicios de utilización de pantallas. Realizan las tareas relacionadas con la salida de consola de un NLM en la pantalla del servidor Netware.

Servicios de transporte. Son los servicios del protocolo de transporte que se proporcionan con Netware para la comunicación a través de la red. Estos servicios comprenden: el interfaz de la capa de transporte, el protocolo de intercambio de paquetes en redes múltiples/protocolo de intercambio de paquetes en secuencia, el protocolo de control de transmisión /protocolo de interne y los sockets de Unix BSD.

 

1.3.3.- Utilización de los API en las Librerías NLM

A continuación vamos a describir el procedimiento para acceder a estas funciones desde un NLM. Una llamada a cualquiera de estas funciones en código C se realiza de la misma manera que una llamada a una función convencional.

Un aspecto que resulta innovador es que las librerías deben de estar ya cargadas en memoria cuando se desee cargar un NLM.

El otro aspecto innovador es la especificación correcta de las directivas de enlazamiento y de otras directivas en el fichero de definición del NLM, antes de proceder a la compilación y enlazamiento del código. Como los NLM utilizan funciones que están en librerías cargables, las referencias externas no se resuelven en tiempo de enlazamiento, sino en tiempo de carga.

El fichero de definición es un fichero de texto con extensión .DEF. En el se especifican una serie de datos como por ejemplo: los ficheros de librería y funciones que se van a importar y exportar, la inclusión opcional de símbolos para la depuración, el nombre del NLM, que aparecerá en la consola del servidor Netware cuando se soliciten los datos de este NLM, etc. A continuación mostramos un ejemplo de fichero .DEF:

DEBUG

DESCRIPTION "Srevidor broker"

IMPORT $ (IMP)

INPUT $ (OBJS)

MAP

MODULE $ (MODS)

OUTPUT $ (TARG)

STACK 8192

TYPE 0

VERSION $ (VER)

La instrucción DEBUG señala que han de incluirse en el ejecutable símbolos para la depuración. La instrucción DESCRIPTION define la cadena de caracteres que se utilizará para identificar este NLM en la consola del servidor Netware. El tamaño de la pila (STACK) y otros parámetros pueden también especificarse en este fichero.

Puede también establecerse que se carguen ciertos ficheros de forma automática mientras se está ejecutando el NLM. Si no se elige la carga automática de las librerías, éstas pueden cargarse manualmente desde la consola o bien en la fase de inicio, mediante la colocación de un comando de carga en el fichero AUTOEXEC.NCE. La directiva para la carga de librerías es importante, ya que las llamadas a estas librerías fallan si los NLM de las librerías no están cargados cuando tienen lugar las llamadas.

 

1.4.- EL ENLAZADOR/CARGADOR

1.4.1.- El Fichero PRELUDE.OBJ

Es necesario enlazar el fichero PRELUDE.OBJ con el código del NLM para que éste se ejecute correctamente. Este fichero hace saber al S.O Netware que el código es un NLM, y realiza la preparación del entorno del programa.

PRELUDE.OBJ se ocupa primordialmente de los estándares de ANSI C para la ejecución de los programas.

1.4.2.- Diferencias entre el Enlazador NLM y un Enlazador Convencional

 

Existen dos versiones del enlazador NLM: una de ellas funciona en modo protegido y la otra en modo real. El uso de una de estas dos versiones es imprescindible si se desea que el NLM sea "capacitado". Esto significa que los mensajes de salida del NLM puede aparecer en varios idiomas, con lo que se da al NLM un carácter internacional. El proceso de "capacitación" del código es el proceso que proporciona los diferentes idiomas.

 

1.4.3.- Utilización del Enlazador NLM

Las directivas del enlazador no son las mismas para las dos versiones del mismo. Es posible llamar al enlazador haciendo referencia en la línea de comando haciendo referencia a un fichero que contenga las directivas que se utilizan de forma habitual. No existe ninguna dificultad especial en la utilización de los enlazadores, siendo su uso similar al de cualquier otro enlazador.

 

1.5.- PROGRAMACIÓN MULTIPROCESO

1.5.1.- Definición de Programación Multiproceso

La programación multiproceso tiene en cuenta la posibilidad de que múltiples procesos ligeros puedan estar ejecutándose simultáneamente sobre el mismo código del NLM. Este tipo de programación es algo necesario en el mundo de los NLM, por lo que el programador debe de tener en cuenta que su programa se ejecutará en un entorno en el que otros programas estarán ejecutándose al mismo tiempo.

 

1.5.2.- Aspectos Fundamentales de la Programación Multiproceso

Cuando se utiliza este tipo de programación, el programador debe de aprender que recursos hay que compartir y como compartirlos. Es necesario que el programador sepa identificar el tipo de tareas apropiadas para un proceso ligero, así como cual es el tipo de trabajo ligero más apropiado: trabajador o normal.

Dos de los aspectos que el programador debe de tener siempre en cuenta son: cómo hacer el código reentrante, y cómo sincronizar los servicios que se proveen de modo que todo suceda en el orden correcto y en el momento adecuado. En un entorno non-preemptive como en el que residen los NLM, es esencial recordar que se debe ceder el control de la CPU a menudo. Si uno solo de los procesos ligeros no tiene esto en cuenta, la consecuencia más probable es la ralentización del sistema o incluso la detención total del servidor.

 

1.6.- VARIABLES DE CONTEXTO NLM

 

1.6.1.-Definición de Contexto NLM

Existen tres tipos distintos de contexto dentro de los NLM. Uno de ellos es global al NLM y a todos los procesos ligeros dentro de él. El segundo se aplica a un grupo de procesos ligeros previamente definido. El último tipo de contexto se aplica a un proceso ligero individual. Un contexto engloba elementos como los siguientes: variables, pantallas, punteros, contadores, referencias, información de control y parámetros.

Es importante tener una idea clara de cual es la información que se guarda en cada nivel del contexto de un NLM, así como de cómo establecer y mantener esta información, y de cuales son los elementos que se ven afectados por el contexto.

 

1.6.2.- Establecimiento de los Contextos NLM

El contexto inicial se establece cuando se escribe main() n el código fuente. Cuando el NLM se carga en memoria este debe de tener por lo menos un proceso ligero, que se crea junto con un grupo de procesos ligeros al que pertenece cuando se ejecuta la función main(). Desde este momento, cada vez que se crean procesos ligeros y grupos de procesos ligeros se establecen contextos adicionales.

 

1.7.-NORMA DE UTILIZACIÓN DE PANTALLAS

 

1.7.1- Definición de Pantalla

En los NLM, las pantallas son pantallas de consola, o más concretamente, la información que en ella se muestra. Los NLM pueden tener varias pantallas, cada una identificada con un número y asociada a un contexto dado.

Las pantallas son el medio por el cual los NLM se comunican con el usuario. Una pantalla puede estar en primer plano, o en segundo plano, en cuyo caso no es visible.

Es posible cambiar de una pantalla a otra de manera sencilla, ya sea en la consola o desde una consola remota. Un NLM puede tener tantas pantallas como precise. Algunas aplicaciones no necesitan pantallas, ya que ninguna de las llamadas que realizan tienen salida por pantalla.

 

1.8.- LIBRERÍAS NLM

 

1.8.1.- Definición de Librería NLM

Una librería NLM es una herramienta de programación útil y potente.

 

1.8.2.- Creación de una Librería NLM

Las librerías se escriben en lenguaje C y se compilan y enlazan como cualquier otro NLM. La diferencia es que no existe un flujo real de control dentro de una librería, ya que cada función es probablemente independiente de las demás.

Dependiendo del propósito de las funciones algunas de estas pueden compartir memoria. Puede ser también que las funciones bloqueen, modifiquen y compartan ficheros en el servidor. Es necesario registrar el NLM como librería al principio del código, así como incluir funciones para salir y cerrar la librería de forma ordenada si las circunstancias así lo requieren.

Es también necesario que todas las funciones estén definidas como exportables, cosa que se consigue con la sentencia EXPORT en el fichero de definición (.DEF) del enlazador.

 

1.8.3.- Utilización de una Librería NLM

Una vez que la librería está escrita y se desea probar sus funciones, puede cargarse en memoria de la misma forma que se carga cualquier otro NLM. El S.O Netware la registra como librería como el programador indicó en el código fuente de la misma. Después el S.O ejecuta una función que a su término continua residente, dejando el código de la librería en memoria, disponible para su uso aunque no se utilice en ese momento.

Los únicos módulos que saben que la librería está cargada y que pueden utilizarla son los de su mismo programador. Es conveniente, por tanto, escribir alguna documentación adicional que ayude a recordar las funciones de la librería, así como sus argumentos, para permitir a los programadores escribir otros NLM que utilicen esa librería.

El programador debe solo incluir en el código fuente las llamadas a las funciones, funciones que habrán sido declaradas externas. En el fichero de definición, hay que utilizar una sentencia IMPORT por cada una de las funciones que se llaman. Esto puede hacerse también incluyendo una referencia a otro fichero de texto que contenga todas las sentencias IMPORT. De este modo, se evita el tener que escribir o copiar todas las sentencias IMPORT para diferentes ficheros de definición.

 

1.9.- INTRODUCCIÓN AL SPX II

 

1.9.1.-Definición del SPX II

El protocolo de intercambio de paquetes en secuencia (SPX) y el protocolo de intercambio de paquetes en redes múltiples (IPX) son protocolos que han estado utilizándose durante mucho tiempo. Al profundizar Netware en entornos de red más diversos, se ha valorado la posibilidad de tener un protocolo punto a punto propio de Netware y con un interfaz que sea similar al de otros protocolos de amplio uso.

SPX II cubre los requisitos de interfaz y de protocolo necesarios para que las aplicaciones existentes puedan ejecutarse en Netware con poca o ninguna modificación del código. No es necesario aprender IPX/SPX, pero se puede seguir utilizando si se desea. El interfaz de programación de SPX II es el interfaz de la capa de transporte (TLI).

 

1.9.2.- Diferencias entre el SPX II y el IPX/SPX

El propósito del SPX es el mismo que el del SPX II, pero el ámbito de éste supera al de SPX. El objetivo primordial de ambos es lograr la comunicación directa entre dos nodos de la red. El grado en el que ambos cubren las necesidades de establecimiento y mantenimiento de la comunicación es, sin embargo, diferente. SPX II introduce mejoras en aspectos como: paquetes de mayor tamaño, implementación de un auténtico protocolo de ventana, y un mejor soporte del interfaz TLI.

 

1.9.3.-Utilización de SPX II

SPX II tiene la ventaja de ser compatible con SPX. Si un nodo SPX II inicia una comunicación, indica su preferencia por SPX II. Si el nodo remoto utiliza SPX, este último simplemente ignora la indicación de preferencia por SPX II, y la comunicación se lleva a cabo normalmente.

 

CAPITULO 2: PROGRAMACIÓN MULTIPROCESO

En este apartado vamos a ver la programación multiproceso (con múltiples procesos ligeros o multithread) mediante la explicación detallada del diseño e implementación de un NLM multiproceso que forma parte de una aplicación de ejemplo de tipo cliente/servidor.

Este apartado comienza con las definiciones de proceso ligero y del sistema operativo (OS) NetWare, de tipo non-preemptive. También se explican y muestran , a través de ejemplos, varios procedimientos relacionados con la planificación de procesos ligeros, entre otros la creación de un proceso ligero o grupo de procesos ligeros, y como tomar prestado un proceso ligero perteneciente al Kernel NetWare.

Los semáforos son una parte importante de la implementación de un NLM multiproceso, por lo que en este capítulo se explica cómo utilizarlos para facilitar los cambios de contexto entre procesos ligeros.

 

2.1.- DEFINICIÓN DE PROCESOS LIGEROS Y ENTORNO NON-PREEMTIVE

Un proceso ligero (thread) es un proceso o flujo de ejecución que se ejecuta en el sistema operativo NetWare. El Propio NetWare pone en marcha una serie de procesos internos, como el proceso de entrada desde la línea de comando y el proceso de actualización de la memoria cache. Debido a que NetWare permite que otras compañías añadan al núcleo del sistema operativo otros NLM que proporcionen nuevos servicios, los NLM pueden crear y poner en marcha sus propios procesos ligeros. Todos los NLM comienzan con al menos un proceso ligero, que empieza ejecutarse en la rutina main del NLM (Este proceso ligero no se crea si se trata de un NLM de librería.) Tanto si el NLM contiene uno o mas procesos ligeros, un proceso ligero representa siempre un flujo de ejecución, que acaba cuando el proceso ligero vuelve (return) después de haberse creado.

Tenemos dentro de los módulos NLM :

Los de un solo proceso ligero.

Los que tienen múltiples procesos ligeros.

Un típico ejemplo de proceso con un solo proceso ligero es un programa en C que puede compilarse, enlazarse, y cargarse en el sistema operativo NetWare como un NLM de un solo proceso ligero.

#include <stdio.h>

main()

{

printf( "Hola Informáticos");

}

Por otra parte tenemos los módulos NLM con múltiples procesos ligeros donde la interdependencia entre varios procesos ligeros en un NLM en un problema más complejo que el funcionamiento de un NLM con un solo proceso ligero. Por ejemplo, un determinado proceso ligero funciona en el contexto de su propio entorno, pero también se ve afectado por el contexto del grupo de procesos ligeros al que pertenece. A su vez, el grupo de procesos ligeros se ve afectado por el contexto global del NLM.

Es útil considerar al NLM como una aplicación en la que una serie de procesos ligeros comparten información y se coordinan entre si.

Algunas ventajas de los NLM multiproceso son: código más modular, un mayor número de tareas realizadas por unidad de tiempo, y un mejor tiempo de respuesta.

La mayor modularización resulta de la creación de procesos ligeros para la realización de tareas individuales, en lugar de escribir una tarea larga y compleja.

El mayor número de tareas por unidad de tiempo y el menor tiempo de respuesta son también consecuencia de la existencia de muchas rutinas de pequeño tamaño que realizan una sola tarea o a lo sumo varias tareas sencillas. Estos procesos ligeros distribuyen el trabajo más uniformemente, haciendo posible que el servidor responda más rápidamente y que use el tiempo de CPU de forma más eficiente.

 

2.2.- EL ENTORNO NON-PREEMPTIVE

Como el sistema operativo NetWare es de tipo non-preemptive, los procesos ligeros pueden poseer el control de la CPU el tiempo que deseen, aunque se supone que cooperan entre si y no monopolizarán el procesador. Por tanto, un entorno non-preemptive permite que el sistema use menos tiempo en cambios de contexto que un entorno preemptive. Un NLM multiproceso se beneficia también de un entorno non-preemptive, puesto que cada proceso ligero representa un módulo o tarea que requiere un tiempo de CPU mínimo.

En un entorno non-preemptive los procesos ligeros no tienen que preocuparse de ser expulsados de la CPU, pero deben ceder el control de la CPU frecuentemente y en los momentos oportunos. Un cambio de contexto tiene lugar cuando un proceso ligero termina o cede el control de la CPU, permitiendo a otro proceso ligero ejecutarse. Un buen diseño permite a todos los procesos ligeros de un NLM acceder a la CPU, realizar su tarea, y salir rápidamente. Todo trabajo relacionado con cambios de contexto recae en el OS NetWare, que realiza estas tareas rápidamente y eficazmente. Curiosamente, los NLM que cooperan cediéndose el control de la CPU en los momentos adecuados rinden mejor que aquellos que retienen el procesador durante más tiempo, ya que éstos últimos se arriesgan a ser penalizados por el sistema.

Por último dar una breve información sobre como el sistema operativo identifica a sus procesos. El sistema operativo identifica y registra cada proceso ligero con un bloque de control de procesos (PCB, Process Control Block), una estructura que tiene la información esencial de ese proceso o proceso ligero. No se puede ejecutar un rutina en el sistema operativo si no está en el contexto de un proceso ligero. Un trabajo es una rutina que el sistema operativo debe ejecutar.

 

2.3.- ALGUNAS RUTINAS DEL INTERFAZ C QUE VAMOS A VER EN ESTE CAPÍTULO

En este apartado los que vamos a ver son algunas de la rutinas que luego vamos a utilizar en el NLM de ejemplo que hemos implementado.

BeginThread: Esta función se utiliza para crear o iniciar un nuevo proceso ligero.

#include <process.h>

void BeginThread (void (*func) (void*), void *stack, unsigned stackSize, void *arg);

BeginThreadGroup: Esta rutina permite la creación de un grupo de procesos ligeros que contiene inicialmente un proceso ligero. Esta rutina es idéntica a BeginThread salvo porque el proceso ligero que se crea constituye por sí mismo un grupo de procesos ligeros; este proceso ligero es el proceso principal o primer proceso ligero del grupo.

#include <process.h>

void BeginThreadGroup (void (*func) (void) , void *stack, unsigned stackSize, void *arg);

ThreadSwitch: Esta función provoca un cambio de contexto. El proceso ligero que realiza la llamada pasa de ejecutarse en la CPU a esperar al final de la cola de listos para ejecución.

Dado que los procesos ligeros de baja prioridad sólo se ejecutan si la cola de listos para ejecución está vacía, si el proceso ligero que llama está haciendo espera activa (busy waiting) o bloqueos activos (spin locks) en ese caso debe utilizarse la rutina ThreadSwitchWithDelay en lugar de esta rutina. Hay otra alternativa, que es llamar a la rutina Delay, que tras esperar un intervalo de tiempo especifico en milisegundos, sitúa al proceso ligero al final de la cola de listos para ejecución.

#include <process.h>

void ThreadSwitch (void);

ThreadSwitchWithDelay: Como ya se ha dicho anteriormente, esta rutina también realiza un cambio de contexto. Debe usarse en lugar de ThreadSwitch cuando el proceso ligero llamante está haciendo espera activa o bloqueos activos. El proceso ligero que llama a esta rutina renuncia al procesador pero no se sitúa inmediatamente al final de la cola de listos para ejecución, sino que espera a que se produzca un determinado número, determinado por el sistema operativo, de cambios de contexto.

#include <process.h>

void ThreadSwitchWithDelay (void);

Delay: Otra función que realiza un cambio de contexto, como alternativa a ThreadSwitchWithDelay. Al llamar a esta rutina se especifica el número de milisegundos que el proceso ligero esperará antes de situarse al final de la cola de listos para ejecución.

#include <process.h>

void delay(unsigned milisegundos);

ThreadSwitchLowPriority: Esta rutina también realiza un cambio de contexto, pero en lugar de colocar al proceso ligero en la cola de listos para ejecución lo coloca al final de una cola de listos para ejecución pero de baja prioridad. Esta es la rutina que utilizan los procesos ligeros de baja prioridad, como los que realizan copias de seguridad.

#include <process.h>

void ThreadSwitchLowPriority (void);

ScheduleWorkToDo: esta función permite planificar un trabajo para que sea ejecutado por uno de los procesos ligeros trabajadores del sistema operativo. Lo que se hace es esencialmente tomar prestado un proceso ligero del Kernel. Los trabajos planificados con esta función tienen la máxima prioridad para el acceso a la CPU.

#include <process.h>

int ScheduleWorkToDo (void (*ProcedureToCall) (),

void *WorkData, WorkToDo, *WorkTodo);

OpenLocalSemaphore: Esta rutina asigna un semáforo, que se utiliza para señalizar y despertar a un proceso ligero si ocurre un determinado evento.

#include <nwsemaph.h>

LONG OpenLocalSemaphore (LONG initialValue);

WaitOnLocalSemaphore: El proceso ligero que llama queda suspendido o dormido en este semáforo hasta que otro proceso ligero le despierte, usando la rutina SignalLocalSemaphore, tras haber sucedido un evento determinado.

#include <nwsemaph.h>

int WaitOnLocalSemaphore (LONG semaphoreHandle);

CloseLocalSemaphore: Esta rutina desasigna un semáforo.

#include <nwsemaph.h>

int CloseLocalSemaphore (LONG semaphoreHandle);

SignalLocalSemaphore: esta función permite señalizar o despertar a otro proceso ligero que suspendió su ejecución en este semáforo llamando a la rutina WaitOnLocalSemaphore.

#include <nwsemaph.h>

int SignalLocalSemaphore (LONG semaphoreHandle);

Signal: Se llama a esta función especificando una condición y rutina. La rutina se ejecutará cuando la condición se cumpla.

#include <signal.h>

void(*signal (int sig, void (*func) (int))) (int);

atexit: Esta función permite terminar ordenadamente la ejecución de un NLM. Su único argumento es la rutina de usuario que se ha de ejecutar durante el proceso de terminación normal del NLM.

#include <process.h>

int atexit (void (*func) (void))

 

2.4.- EJEMPLO DE UN NLM MULTIPROCESO

Vamos a utilizar como ejemplo una aplicación distribuida: un gestor de registros de base de datos. Es una aplicación simple con una utilidad práctica limitada como comprobaremos después. Por tanto, no debe considerarse como un ejemplo de gestión de bases de datos o gestión de ficheros. Pero, el diseño modular hace que la aplicación pueda extenderse, con lo que su aplicación práctica es una posibilidad.

 

2.4.1 .- Parte del Servidor

La parte del servidor del gestor de registros de base de datos tiene dos elementos:

El fichero de la base de datos.

Un NLM de tipo multiproceso, al que llamaremos NLM engine (motor), que hace funcionar la base de base de datos.

El NLM engine es la parte de la aplicación en que se centra este apartado. Su misión es acceder y controlar el fichero de la base de datos, realizando tareas como añadir nuevos registros, editar registros antiguos, etc.

 

2.4.2.- Parte del Cliente

La parte del cliente es una aplicación en una estación de trabajo. Esta aplicación proporciona a los usuarios un interfaz que contiene la base de datos. La aplicación se conecta al servidor utilizando IPX (protocolo de intercambio de paquete en redes múltiples), y presenta al usuario una serie de operaciones que el NLM engine puede realizar sobre la base de datos. La aplicación también notifica al usuario los cambios que éste realiza en la base de datos.

 

2.4.3.- El Proceso Ligero Principal del NLM Engine (Motor)

El NLM engine es de tipo multiproceso y su ejecución comienza en la función main. En el listado puede apreciarse que esta función tiene el mismo aspecto que la función main de cualquier otro programa C. El código fuente esta función y de las demás que componen el NLM engine se encuentran en el fichero engine.c en la BBS (Bulletin Board Service, base de datos con software público) de Novell. También aparecen en el listado el fichero de cabecera engine.h y las variables globales declaradas al principio de engine.c.

 

2.4.3.1.- Grupo de procesos ligeros por defecto

Como ocurre en cualquier NLM, el proceso ligero principal del NLM engine pertenece al grupo de procesos ligeros que el interfaz C de NetWare establece por defecto cuando se carga el NLM en memoria. Los procesos ligeros que la función main cree llamando a la rutina BeginThread pertenecerán también a este grupo por defecto.

También puede ocurrir que el proceso ligero principal sea el único en el grupo de procesos ligeros por defecto. Esto ocurrirá, por ejemplo, si el proceso ligero principal llamase a la función BeginThreadGroup sin haber creado antes ningún proceso ligero, creando así un nuevo grupo de procesos ligeros independiente del grupo por defecto.

 

2.4.3.2.- Tareas de preparación (Setup)

Como se puede ver en el listado la función main realiza las tareas iniciales necesarias relacionadas con la red y con el carácter distribuido de la aplicación.

Estas tareas siguen los pasos que se enumeran a continuación. Se indica entre paréntesis la rutina asociada a cada tarea.

1.- Comprobación de que el TTS (Transición Tracking System, sistema de seguimiento de transacciones) está activado (TTSIsAvaliable).

2.- Comprobación de que el nombre de camino del fichero de la base de datos es correcto (ParsePath).

3.- Comprobación de que existe el fichero de la base de datos. En caso negativo, creación del mismo (OpenDataFile, CreateDataFile).

4.- Apertura del socket IPX (IPXOpenSocket).

5.- Comienzo del proceso de anuncio del servicio (AdvertiseService).

 

2.4.4.- Preparación y Creación de Procesos Ligeros Adicionales

Una vez realizadas las tareas de preparación, la función main realiza las tareas que se enumeran a continuación, y cuyo código fuente se muestra en el listado. Se indica entre paréntesis la rutina asociada a cada tarea.

1.- Apertura de semáforos (OpenLocalSemaphore).

2.- Registro de las funciones que serán ejecutados cuando el NLM sea descargado de la memoria (signal).

3.- Creación de un proceso ligero perteneciente al grupo de procesos ligeros por defecto (BeginThread).

4.- Creación de un grupo de procesos ligeros (BeginThreadGroup).

El proceso ligero correspondiente a la función main es relativamente pequeño y realiza pocas tareas, al igual que el resto de los procesos ligeros del NLM engine. Esto se debe en parte a la sencillez de este NLM, pero se debe quizá en mayor medida a la organización modular que impone la programación multitarea. La multitarea fomenta, mediante la división de trabajo en módulos, un diseño de código más limpio y más elegante, y simplifica el mantenimiento, la compatibilidad y la portabilidad. Por último destacar las llamadas a OpenLocalSemaphore, para crear semáforos de contestación (reply), petición (request) y cierre (shutdown).

 

2.4.5.- La Rutina OpenLocalSemaphore

El semáforo de contestación es usado por InitMain, un proceso ligero creado por el proceso ligero principal y en el mismo grupo que esté. El propósito de InitMain es crear un ECB (Event Control Block, bloque de control de evento) de escucha y ECB de envio. Estos dos ECB se usan para aceptar y responder las consultas de los clientes, respectivamente.

El semáforo de cierre es usado por el proceso ligero principal cuando se descarga el NLM de la memoria. Este semáforo y las rutinas signal y atexit garantizan una terminación ordenada del NLM.

El propósito de la rutina OpenLocalSemaphore es la asignación de un semáforo, tras lo cual se devuelve a main un identificador del mismo. Dado el importante papel que desempeñan los semáforos en los cambios de contexto entre procesos ligeros, es fundamental que se creen antes de que main inicie nuevos procesos ligeros o grupos de procesos ligeros. Una vez que los semáforos se han asignado, main puede ya crear un nuevo proceso ligero llamando a BeginThread, como se puede ver en el código.

 

2.4.6.- InitMain: Un Proceso Ligero Adicional del Grupo de Procesos Ligeros por Defecto

InitMain se crea cuando main llama a la rutina BeginThread, y es miembro del grupo de procesos ligeros por defecto. El propósito de InitMain es crear un proceso ligero de escucha y un proceso ligero de envio para recibir y responder a un paquete de consulta enviado desde una estación de trabajo cliente. Cuando se recibe un paquete, InitMain despierta y envía un paquete de respuesta al cliente, para hacerle saber que el servidor está funcionando y dispuesto a atender su petición. Una vez hecho esto, InitMain termina, devolviendo el control a main.

La utilización de una sola recepción y envio simultáneos es adecuada gracias a la velocidad de funcionamiento de IPX.

El NLM engine es capaz de atender múltiples paquetes de petición y respuesta simultáneamente para servir peticiones de operaciones sobre la base de datos. Para este fin, el NLM engine crea procesos ligeros individuales para ejecutar cada operación solicitada por los clientes. El listado muestra la parte del código de InitMain que comprende las tareas que se enumeran a continuación. Se indica entre paréntesis la rutina asociada con cada tarea.

1.- Inicialización de los ECB de escucha y envio.

2.- Activación de la escucha para recibir consultas (IPXReceive).

3.- Suspensión de InitMain en espera de que se produzca la recepción de un paquete (WaitOnLocalSemaphore).

 

2.4.7.- Iniclialización de los ECB de Escucha y Envío

Un ECB es una estructura que usa IPX/SPX para establecer una comunicación de tipo cliente/servidor. Ahora vamos a explicar los campos de esta estructura.

El campo queueHead se inicializa a NULL. Este campo, no utilizado aquí, puede contener un puntero a un puntero a ECB, que se utiliza para mantener una lista encadenada de ECB.

El campo socket se inicializa a INIT_SOCKET, cuyo valor está definido como 0x0090 en el fichero engine.h.

El campo semHandle se inicializa a replySem, que se el identificador del semáforo de contestación que se creó anteriormente.

El campo fragCount se inicializa al número de buffers necesarios para contener un paquete, ya sea entrante o saliente.

 

2.4.7.1.- ECB de escucha

El campo fragList 0.fragAddress se inicializa con la dirección de una cabecera IPX. En esa cabecera se escribirá la información contenida en la cabecera del paquete entrante.

El campo fragList 0.fragSize se inicializa con el tamaño de una cabecera IPX.

El campo fragList 1.fragAddress se inicializa utilizando la dirección de un iPacket (paquete de inicialización). El iPacket está definido en el fichero engine.h de la siguiente manera:

typedef struct initPacket {

char dataFilePath _MAX_PATH ;

} Ipacket;

Como podemos ver, iPacket contiene el path de fichero de base de datos al que el NLM engine accede. Sin embargo, no es el cliente sino el servidor el que rellena este campo en esta versión de la aplicación (aunque el cliente podría utilizarlo en una extensión de la misma).

El campo fragList 1.fragSize se inicializa con el tamaño de un iPacket.

 

2.4.7.2.- ECB de envío

El campo fragList 0.fragAddress se inicializa con la dirección de una cabecera IPX, distinta de la cabecera IPX del ECB de escucha. Es esa cabecera se colocará la información de la cabecera del paquete saliente.

El campo fragList 0.fragSize se inicializa con el tamaño de una cabecera IPX.

El campo fragList 1.fragAddress se inicializa utilizando la dirección de un iPacket (paquete de inicialización). El servidor escribe el path del fichero de la base de datos.

El campo fragList 1.fragSize se inicializa con el tamaño de un iPacket.

 

2.4.8.- Suspensión de InitMain

La rutina WaitOnLocalSemaphore se usa para hacer que InitMain duerma hasta que sea despertado por el sistema operativo a la llegada de un paquete de consulta de un cliente.

 

2.4.8.1.- Rutinas para suspender un proceso ligero

Cuando InitMain duerme, la CPU ejecuta otros procesos ligeros. Es decir, cuando un proceso ligero se suspende, renuncia a la CPU para que otros procesos ligeros se ejecuten.

Cuando un proceso ligero duerme pasa a estar inactivo, y necesita que un evento externo, o bien otro proceso ligero, le despierte. Existen varias rutinas en el interfaz C para suspender un proceso, cada una de ellas adecuada en diferentes circunstancias.

WaitOnLocalSemaphore: El proceso ligero se suspende y queda esperando en un semáforo, que le despertará cuando ocurra el evento externo que esté asociado con ese semáforo, o bien cuando otro proceso llame a la función SignalLocalSemaphore.

SuspendThread: Esta función se utiliza para suspender el proceso ligero que llama u otro proceso ligero. En este último caso es necesario pasar a la rutina como argumento el identificador del proceso ligero que se quiere suspender. El proceso ligero despierta cuando otro proceso ligero llama a la rutina Resume Thread, con el identificador del proceso ligero que duerme.

EnterCritSec: Un proceso ligero llama a esta rutina cuando entra en una sección crítica del código: una cuya ejecución no debe interrumpirse hasta que ese proceso ligero salga de ella. Los demás procesos ligeros se suspenden hasta que el proceso ligero que está ejecutándose llama a la rutina ExitCritSec.

 

2.4.8.2.- Funcionamiento de WaitOnLocalSemaphore

InitMain duerme indefinidamente cuando llama a WaitOnLocalSemaphore, y sólo despierta cuando ocurre el evento externo asociado con el semáforo cuyo identificador se pasó como argumento a esa función. Este semáforo es el de contestación, que es el mismo cuyo identificador figura en el campo correspondiente del ECB de escucha. Por tanto, el semáforo cambiará cuando se reciba un paquete, despertándose InitMain y continuando su ejecución en la línea posterior a la llamada a WaitOnLocalSemaphore.

 

2.4.9.- Desbloqueo de InitMain

Cuando ha llegado un paquete de consulta e InitMain se ha despertado, éste procesa dicho paquete y envía a continuación la respuesta al cliente. Como muestra el listado son las llamadas IPX que se realizan. La rutina IPXGetLocalTarget obtiene la dirección del encaminador (router) más cercano de los que conocen la ruta al cliente que envió el paquete. InitMain pone el nombre del fichero de la base de datos en el paquete de respuesta. A continuación, este paquete se envía al cliente utilizando la rutina IPXSend. Una vez hecho esto, InitMain vuelve a la función main y termina.

 

2.4.10.- Creación de un Grupo de Procesos Ligeros en Main

Justo después de crear InitMain, main llama a BeginThreadGroup para crear un nuevo proceso ligero llamado EngineMain. A diferencia de InitMain, que pertenece al grupo de procesos ligeros por defecto, EngineMain es por si mismo un nuevo grupo de procesos ligeros.

El NLM engine realiza la mayor parte de su trabajo en el contexto del grupo de procesos ligeros EngineMain. Un cliente puede solicitar que el NLM engine realice operaciones como la lectura, edición, o eliminación de un registro de la base de datos, o la adición de un nuevo registro a la misma. La rutina principal para la realización de estas tareas se llama DoOperation. Esta rutina consiste en una sentencia de selección (switch) en la que cada alternativa corresponde a un tipo de operación. DoOperation llama a su vez a otras rutinas, como FindRecord o AddRecord, ya sea actuando como un nuevo proceso ligero.

El objetivo del proceso EngineMain es esencialmente escuchar y recibir paquetes de petición, y bien planificar un trabajo, o bien crear un nuevo proceso ligero, por cada petición.

 

2.4.11.- Inicialización de los ECB de Escucha y Activación de la Escucha

Como puede verse en el listado el proceso ligero EngineMain comienza con un bucle que inicializa y comienza la escucha es seis ECB. Dada la velocidad del protocolo IPX y el modo en el que EngineMain procesa la información, seis es un número suficiente para atender la peticiones de más de cien clientes. Tras la inicialización, que incluye el campo semáforo, EngineMain llama a la rutina IPXReceive para activar la escucha en los ECB.

 

2.4.12.- Suspensión de EngineMain

Una vez que ha comenzado la escucha es los seis ECB, EngineMain entra en un largo bucle cuya condición es el parámetro de cierre (shutdown). Este parámetro se inicializa a cero y pertenece de esta manera hasta el momento en que un usuario introduce UNLOAD engine en la línea de comandos.

Si no se desea descargar el NLM, EngineMain se suspende llamando a WaitOnLocalSemaphore.

El proceso de suspensión y desbloqueo es prácticamente idéntico al que se utiliza en InitMain.

 

2.4.13.- Desbloqueo de EngineMain y Procesamiento de Peticiones

Como podemos ver en el listado, la primera rutina que llama EngineMain después de despertar es IPXGetAndClearQ. Esta rutina devuelve un puntero al primer ECB de una lista encadenada que contiene los ECB que han recibido un paquete. Mientras haya un ECB en la lista, EngineMain entra en un bucle en el que obtiene un nodo de operación (opNode) vacío donde poner la información contenida en el ECB, y bien planifica un trabajo o crea un proceso ligero, para realizar la operación solicitada.

 

2.4.13.1.-Definición de opNode

Un opNode es una estructura compleja que contiene información sobre la petición del cliente. Más concretamente, un opNode es una estructura de tipo ENGINE_OP_NODE, definida al principio del código del NLM motor.

El opNode propiamente dicho tiene dos campos:

Una estructura de tipo IPX_HEADER (cabecera de tipo IPX)

Una estructura iPacket.

Esta última tiene tras campos:

responseCode

operation

record

Estos campos se definen de la siguiente manera:

El campo responseCode contiene un valor calculado por el cliente, que sirve para verificar la integridad de los paquetes que intercambia con el servidor.

El campo operation es el código que indica el tipo de operación que el cliente desea realizar en la base de datos.

El campo record es una estructura que representa un registro de la base de datos. Esta estructura tiene un campo de cabecera y uno de datos.

 

2.4.13.2.- Búsqueda de un opNode vacío

El listado muestra el código de realiza la búsqueda de un opNode libre. Las variables index y opNodeSpinCount se inicializan a cero, y el proceso ligero entra en un blucle en el que se exploran los seis opNodes existentes. El bucle finaliza si se encuentra un opNode vacío.

Un opNode vacío se reconoce porque el valor de su campo operation es cero. Si ese campo contiene un valor como 0x02 (ADD_RECORD) O 0X03 (FIND_RECORD_KEY), ese nodo está siendo utilizado. El opNode vacío se rellena con la información correspondiente, para a continuación planificarse un trabajo a crearse un proceso ligero, para ejecutar la operación solicitada.

Si el opNode que se examina no está vacío, se incrementan la variables index y opNodeSpinCount y se comienza una nueva iteración. Este proceso se repite hasta que la variable index vale 6. Entonces, el proceso ligero fuerza un cambio de contexto llamando a ThreadSwitch para que se ejecuten otros procesos ligeros, que probablemente liberarán alguno de los opNodes en uso.

 

2.4.14.-Salida del NLM Engine

El NLM engine se descarga de la memoria cuando se teclea UNLOAD engine en la línea de comando de la consola del servidor. El interfaz C proporciona rutinas que ayudan a terminar el NLM de manera ordenada, es decir, devolviendo al sistema operativo todos los procesos ligeros y recursos utilizados. Los preparativos para esta salida controlada comienzan en la función main, antes de la creación de InitMain.

En primer lugar se crea un semáforo de cierre (shutdownsem) y, a continuación, se llama a las rutinas signal atexit para decirle al sistema operativo cuáles son las rutinas de terminación y cierre.

 

2.4.14.1.-La función WaitOnLocalSemaphore y el semáforo de cierre

La función main quedó suspendida esperando en el semáforo de cierre cuando llamó a la función WaitOnLocalSemaphore. Ya hemos visto cómo se usan los semáforos en combinación con la rutina WaitOnLocalSemaphore en el proceso de suspender y despertar procesos ligeros cuando ocurre un evento externo. Sin embargo, el uso de WaitOnLocalSemaphore es ligeramente distinto en el caso del semáforo de cierre. Debemos hacer notar que los procesos ligeros InitMain y EngineMain llaman a WaitOnLocalSemaphore después de haber inicializado el campo de semáforo del ECB con el identificador de semáforo correspondiente, y duermen hasta que llega un paquete.

 

2.4.14.2.- La rutina signal

La función main no inicializa el campo de semáforo de un ECB con el identificador del semáforo de cierre. En lugar de ello, main llama a la rutina signal antes de llamar a WaitOnLocalSemaphore. Esta rutina se utiliza para avisar al NLM engine de que ha llegado el momento de finalizar su ejecución, para lo cual es necesario devolver todos los recursos y terminar todos los procesos ligeros del NLM. Como puede verse en la función main (que aparece en el listado), la función signal tiene dos argumentos. El primero es una condición, que en este caso toma el valor SIGTERM. El segundo argumento es el nombre de la función que ha de ejecutarse cuando se cumple la condición que indica el primer argumento.

SIGTERM. SIGTERM es una constante definida en el sistema operativo. Esta constante representa la señal que recibe en NLM cuando un usuario introduce el comando UNLOAD para descargar el NLM.

UnloadCleanUp. Esta rutina, que se muestra en el listado es definida por el usuario, y es la que se pasa a signal como argumento. UnloadCleanUp hace tres llamadas a SignalLocalSemaphore para despertar a los distintos procesos que duermen esperando en los semáforos de petición (requestSem), contestación (replySem), y cierre (shutdownSem). Así, todos los procesos ligeros están despiertos y pueden darse cuenta de que el NLM está en el proceso de terminación, con lo que éstos a su vez concluyen de forma ordenada.

 

2.4.14.3.-La rutina atexit

La rutina atexit concluye el proceso de preparación para la terminación comenzando por la rutina signal. El único argumento de esta rutina es el nombre de la función que ha de ejecutarse durante el proceso de terminación normal de un NLM. En este caso esa rutina es ShutdownCleanUp, que se muestra en el listado. Esta rutina cierra todos los semáforos y sockets que abrió el proceso ligero principal justo cuando el NLM engine se cargo en memoria. ShutdowsCleanUp cierra el proceso de anuncio de servicio.

 

2.5.- LISTADO DEL FICHERO ENGINE.C

#include "engine.h"

/*----- Variables Globales -----*/

LONG requestSem, replySem, shutdownSem; // identificadores de semaforos

char dataFileName[_MAX_PATH + 1]; // nombre del fichero de base de datos

WORD engineSocket = ENGINE_SOCKET; // socket de reparto primario

WORD initSocket = INIT_SOCKET; // socket de inicializacion

int shutdown = 0, shutdownOK = 0; // para una descarga de memoria limpia

FILE *fp; // identificador del fichero de base de datos

LONG opNodeSpinCount, opNodeHighSpinCount = 0; // variables de informacion

LONG recordNumber = 0; // n£mero de registros en el fichero

LONG SAPHandle; // protocolo de anuncio de servicios

LONG addInProgress = 0; // control de procesos ligeros de adicion concurrentes

ENGINE_OP_NODE opNodes[6]; // estructuras de control de procesos ligeros

#ifndef _NETWARE_311_

WorkToDo workNodes[6];

#endif

/*************************************************************************

* DoOperation

*

* Esta rutina se reproduce como un proceso ligero asincrono cada vez

* que el motor recibe del cliente un paquete de peticion. El parametro

* es un puntero a una estructura que contiene un codigo de operacion

* y una copia del paquete de peticion.

*

* Dado que DoOperation se reproduce como un proceso ligero, puede

* haber multiples ejemplares de la misma ejecutandose al mismo

* tiempo. Esto permite que el motor atienda muchos clientes al

* mismo tiempo, sin que ningun cliente se vea perjudicado especialmente

* por los demas clientes en terminos de rendimiento.

*

**************************************************************************/

#ifdef _NETWARE_311_

void DoOperation(ENGINE_OP_NODE *callBackNode)

#else

void DoOperation(void *callNode, WorkToDo *workToDo)

#endif

{

int ccode;

#ifndef _NETWARE_311_

ENGINE_OP_NODE *callBackNode;

callBackNode = (ENGINE_OP_NODE *)callNode;

#endif

switch(callBackNode->packet.operation)

{

case 0x07: /* marca un registro existente como borrado */

case 0xf6: /* edita un registro existente */

case 0x02: { AddRecord(callBackNode); break; }

case 0x03: { FindRecordKey(callBackNode); break; }

default: break;

}

callBackNode->packet.operation = 0;

#ifdef _NETWARE_311_

ExitThread(EXIT_THREAD, 0);

#endif

}

void EngineMain(void *param)

{

int ccode, index;

IPX_ECB listenECBs[6]; // los ECB para recibir paquetes

IPX_ECB *thisECB, *tempECB; // punteros para desenlazar los ECB

IPX_ECB *queueHead = NULL; // lista de paquetes recibidos

IPX_HEADER listenHeaders[6]; // cabeceras de paquetes IPX

IPX_HEADER *thisHeader; // puntero para copiar la cabecera a OpNode

lPacket listenPackets[6]; // buffers para los paquetes recibidos

lPacket *thisPacket; // puntero para copiar el paquete a OpNode

rec tempRec; // buffer para actualizar el fichero de base de datos

// en el momento del cierre

if (param)

;

printf("\nRepartidor de EngineMain iniciado");

/* inicializar y activar los ECB de escucha */

for (index = 0; index < 6; index++)

{

opNodes[index].packet.operation = 0;

listenECBs[index].queueHead = &queueHead;

listenECBs[index].semHandle = requestSem;

listenECBs[index].socket = ENGINE_SOCKET;

listenECBs[index].fragCount = 2;

listenECBs[index].fragList[0].fragAddress = &listenHeaders[index];

listenECBs[index].fragList[0].fragSize = sizeof(IPX_HEADER);

listenECBs[index].fragList[1].fragAddress = &listenPackets[index];

listenECBs[index].fragList[1].fragSize = sizeof(lPacket);

ccode = IpxReceive(0, &listenECBs[index]);

}

/* Cuando llegamos aqui, los seis ECB estan escuchando para recibir */

/* paquetes IPX que los clientes envien al engine. */

while(!shutdown)

{

/* Ahora el proceso ligero EngineMain se bloquea o duerme hasta */

/* que un cliente env¡e un paquete de peticion. Cuando llega un */

/* paquete de peticion, el OS enlaza el ECB que describe el paquete */

/* a una lista de bloques ECB en los que se ha recibido. Entonces */

/* el OS despierta al proceso ligero EngineMain se¤alizando el */

/* semaforo requestSem. */

ccode = WaitOnLocalSemaphore(requestSem);

if (shutdown)

break;

/* IpxGetAndClearQ devuelve una lista encadenada de todos los */

/* bloques ECB que describen un paquete recibido. La longitud de */

/* la lista depende del numero de paquetes que el EngineMain ha */

/* recibido. */

thisECB = IpxGetAndClearQ(&queueHead);

while(thisECB)

{

/* Recorre la lista de bloques ECB que describen los paquetes */

/* recibidos y reparte el trabajo a procesos ligeros trabajadores */

/* que realizan las operaciones solicitadas. */

thisHeader = (IPX_HEADER *)thisECB->fragList[0].fragAddress;

thisPacket = (lPacket *)thisECB->fragList[1].fragAddress;

/* Ahora tenemos que planificar un proceso ligero trabajador */

/* para realizar la operacion. Primero buscamos un opNode libre */

/* y rellenamos la estructura de la callback */

index = opNodeSpinCount = 0;

while(1)

{

if (index == 6)

{

ThreadSwitch();

index = 0;

}

if (opNodes[index].packet.operation == 0)

{

ThreadSwitch();

break;

}

index++;

opNodeSpinCount++;

}

if (opNodeSpinCount > opNodeHighSpinCount)

opNodeHighSpinCount = opNodeSpinCount;

/* Ahora que tenemos un nodo de operacion libre, lo rellenamos */

/* para poder arrancar un proceso ligero trabajador para */

/* realizar la operacion. */

memcpy(&opNodes[index].packetHeader, thisHeader,sizeof(IPX_HEADER));

memcpy(&opNodes[index].packet, thisPacket, sizeof(lPacket));

#ifdef _NETWARE_311_

/* Arranca un proceso ligero trabajador y le asigna la */

/* operacion. */

ccode = BeginThread(DoOperation,

NULL,

0x1000,

&opNodes[index]);

#else

/* Si es NetWare 4.0, toma prestado un proceso ligero */

/* trabajador al OS */

ccode = ScheduleWorkToDo(DoOperation,

&opNodes[index],

&workNodes[index]);

#endif

/* Ahora que la operacion se ha asignado a un proceso ligero, */

/* podemos reactivar el ECB para seguir escuchando. Esto hace */

/* que el ECB pueda recibir paquetes incluso antes de que el */

/* proceso ligero trabajador haya realizado la operacion */

/* solicitada! */

tempECB = thisECB;

thisECB = thisECB->next;

ccode = IpxReceive(0, tempECB);

}

}

printf("\nRepartidor de trabajo de Engine Main cerrando...");

/* actualiza la cabecera de la base de datos y cierra el fichero de */

/* datos */

ccode = fseek(fp, 0L, SEEK_SET);

ccode = fread(&tempRec, sizeof(rec), 1, fp);

tempRec.header.recordNumber = recordNumber;

ccode = fwrite(&tempRec, sizeof(rec), 1, fp);

fclose(fp);

shutdownOK = 1;

printf("Cerrado");

return;

}

void InitMain(void *param)

{

IPX_ECB listenECB, sendECB; // los ECB para enviar y recibir

IPX_HEADER listenHeader, sendHeader; // cabeceras IPX para enviar y recibir

iPacket initPacket; // buffer de paquetes

int ccode;

unsigned long transportTime; // para IpxGetLocalTarget

if (param) ;

/* Inicalizar los ECB. Se utiliza el mismo buffer de paquete para */

/* enviar y recibir. En este caso podemos hacer eso ya que este */

/* proceso ligero solo envia un paquete despues de haber recibido */

/* otro, y no puede recibir mas paquetes hasta que ha enviado */

/* el paquete. */

listenECB.queueHead = sendECB.queueHead = NULL;

listenECB.socket = sendECB.socket = INIT_SOCKET;

listenECBsemHandle = replySem;

listenECB.fragCount = 0x02;

listenECB.fragList[0].fragAddress = &listenHeader;

listenECB.fragList[0].fragSize = sizeof(IPX_HEADER);

listenECB.fragList[1].fragAddress = &initPacket;

listenECB.fragList[1].fragSize = sizeof(iPacket);

sendECB.fragCount = 0x02;

sendECB.fragList[0].fragAddress = &sendHeader;

sendECB.fragList[0].fragSize = sizeof(IPX_HEADER);

sendECB.fragList[1].fragAddress = &initPacket;

sendECB.fragList[1].fragSize = sizeof(iPacket);

sendHeader.packetType = 0x04; // indica que es un paquete IPX

while (!shutdown)

{

/* Comenzar a escuchar si vienen paquetes de consulta */

ccode = IpxReceive(0, &listenECB);

/* Dormir hasta que IPX reciba un paquete de consulta */

/* El OS nos despertara cuando entre un paquete */

ccode = WaitOnLocalSemaphore(replySem);

/* Hemos recibido un paquete de consulta! */

printf("\nPaquete de inicializacion recibido");

memcpy(&sendHeader.destNet, &listenHeader.sourceNet, 10);

sendHeader.destSocket = INIT_SOCKET;

/* Obtener la direccion del encaminador mas proximo de los que

conocen la ruta al cliente que envio este paquete */

ccode = IpxGetLocalTarget((unsigned char *)&listenHeader.sourceNet,

&sendECB, &transportTime);

if (ccode)

printf("\nError al obtener la direccion inmediata (InitMain): %#x",

ccode);

/* Poner el nombre del fichero de base de datos en */

/* el paquete de respuesta */

strcpy(initPacket.dataFilePath, dataFileName);

/* Enviar el paquete de respuesta al cliente */

ccode = IpxSend(0, &sendECB);

if (ccode)

printf("\nError al enviar el paquete de inicializacion (InitMain):%#x",ccode);

else

printf("\nPaquete de inicializacion enviado");

}

return;

}

void AddRecord(ENGINE_OP_NODE *opNode)

{

int ccode, index, duplicate = 0;

IPX_ECB sendECB; // ECB for sending confirmation packet

IPX_HEADER sendHeader; // IPX header

sPacket sendPacket; // packet buffer for sending conf. pkt

unsigned long transportTime; // for use with IpxGetLocalTarget

rec tempRec; // buffer for reading data base file

if(opNode->packet.operation == 0x02) /* client adding a new record*/

{

printf("\nReceived AddRecord request...");

while(addInProgress)

ThreadSwitch();

addInProgress = 1;

printf("\nSeeking ...");

ccode = fseek(fp, 0L, SEEK_SET);

if (ccode)

{

printf("\nError seeking to first record (AddRecord)");

return;

}

printf("\n");

tempRec.header.status = 1L;

opNode->packet.record.header.offset = 0L;

while(tempRec.header.status)

{

fread(&tempRec, sizeof(rec), 1, fp);

if (!strcmp(tempRec.header.key, opNode->packet.record.header.key) \ && tempRec.header.status)

duplicate = 1;

if (!tempRec.header.status)

break;

opNode->packet.record.header.offset += sizeof(rec);

if (opNode->packet.record.header.offset >= recordNumber * sizeof(rec))

break;

ThreadSwitch();

}

if (tempRec.header.status != 0 && !duplicate)

{

ccode = ExtendDataFile(0x64);

if (ccode)

return;

}

else

{

while(tempRec.header.offset <= \

((recordNumber + 1) * sizeof(rec)) && \

!duplicate)

{

fread(&tempRec, sizeof(rec), 1, fp);

if (!strcmp(tempRec.header.key,opNode->packet.record.header.key) \

&& tempRec.header.status)

duplicate = 1;

break;

}

}

if (duplicate)

{

printf("\nDuplicate record key found");

opNode->packet.record.header.status = 0x03;

}

else

{

printf("filling record... ");

opNode->packet.record.header.status = 0xffffffff;

opNode->packet.record.header.hashkey = 0;

opNode->packet.record.header.recordNumber =tempRec.header.recordNumber;

opNode->packet.record.data.creationTime = \

opNode->packet.record.data.lastReferenceTime = \

opNode->packet.record.data.lastUpdateTime = time(NULL);

memcpy(opNode->packet.record.data.nodeAddress,

&opNode->packetHeader.sourceNet, 10);

ccode = fseek(fp, opNode->packet.record.header.offset,SEEK_SET);

if (ccode)

{

printf("\nError seeking to new record offset (AddRecord)");

return;

}

}

}

if (opNode->packet.operation == 0xf6)

{

printf("\nReceived UpdateRecord request...");

if (opNode->packet.record.header.offset >= recordNumber *sizeof(rec))

{

printf("\nBad record offset received (UpdateRecord)");

return;

}

ccode = fseek(fp, opNode->packet.record.header.offset, SEEK_SET);

if (ccode)

{

printf("\nError seeking to record offset (UpdateRecord)");

return;

}

opNode->packet.record.data.lastUpdateTime = time(NULL);

memcpy(opNode->packet.record.data.nodeAddress,

&opNode->packetHeader.sourceNet, 10);

}

if (opNode->packet.operation == 0x07)

{

printf("\nReceived DeleteRecord request...");

/* Check for a bad offset */

if (opNode->packet.record.header.offset >= recordNumber * sizeof(rec))

{

printf("\nBad record offset received (DeleteRecord)");

return;

}

ccode = fseek(fp, opNode->packet.record.header.offset, SEEK_SET);

if (ccode)

{

printf("\nError seeking to record offset (DeleteRecord)");

return;

}

opNode->packet.record.header.status = 0;

}

if (!duplicate)

{

printf("TTSBegin ...");

ccode = TTSBeginTransaction();

if (ccode && ccode != 0xff)

{

if(opNode->packet.operation == 0x02)

addInProgress = 0;

return;

}

printf("writing ...");

ccode = fwrite(&opNode->packet.record, sizeof(rec), 1, fp);

if (ccode != 1)

{

TTSAbortTransaction();

if(opNode->packet.operation == 0x02)

addInProgress = 0;

return;

}

else

{

printf("TTSEnd\n");

TTSEndTransaction(&opNode->packet.record.header.transactionNumber);

}

if(opNode->packet.operation == 0x02)

addInProgress = 0;

if (ccode != 1)

{

printf("\nNew record not written to data base");

return;

}

else

{

printf("\nNew record:");

printf("\n\trecordNumber: %#lx",opNode->packet.record.header.recordNumber);

printf("\n\toffset: %#lx", opNode->packet.record.header.offset);

printf("\nKey: %s", opNode->packet.record.header.key);

printf("\nData: %s", opNode->packet.record.data.data);

}

}

if (duplicate && opNode->packet.operation == 0x02)

addInProgress = 0;

sendECB.queueHead = NULL;

sendECB.semHandle = NULL;

sendECB.socket = ENGINE_SOCKET;

sendECB.fragCount = 0x02;

sendECB.fragList[0].fragAddress = &sendHeader;

sendECB.fragList[0].fragSize = sizeof(IPX_HEADER);

sendECB.fragList[1].fragAddress = &sendPacket;

sendECB.fragList[1].fragSize = sizeof(sPacket);

ccode = IpxGetLocalTarget(

(unsigned char*)&opNode->packetHeader.sourceNet,

&sendECB,

&transportTime);

memcpy(&sendHeader.destNet, &opNode->packetHeader.sourceNet, 12);

sendHeader.packetType = 4;

memcpy(&sendPacket.responseCode, &opNode->packet.responseCode, 3);

memcpy(&sendPacket.header, &opNode->packet.record.header,sizeof(rHeader));

ccode = IpxSend(0, &sendECB);

printf("\nOperation Successful.");

return;

}

void FindRecordKey(ENGINE_OP_NODE *opNode)

{

rec searchRec; // buffer for reading data base file

int ccode;

IPX_ECB sendECB; // ECB for sending confirmation packet

IPX_HEADER sendHeader;

lPacket sendPacket;

unsigned long transportTime;

printf("\nReceived FindRecordKey request...");

ccode = fseek(fp, 0L, SEEK_SET);

if (ccode)

return;

while(1)

{

ccode = fread(&searchRec, sizeof(rec), 1, fp);

if (ccode != 1)

break;

if (searchRec.header.status == 0)

continue;

if (strcmp(opNode->packet.record.header.key, searchRec.header.key))

continue;

else

break;

}

if (ccode != 1)

sendPacket.record.header.status = 0L;

else

memcpy(&sendPacket.record, &searchRec, sizeof(rec));

sendECB.queueHead = NULL;

sendECB.semHandle = NULL;

ccode = IpxGetLocalTarget(

(unsigned char*)&opNode->packetHeader.sourceNet,

&sendECB,

&transportTime);

sendECB.socket = ENGINE_SOCKET;

sendECB.fragCount = 2;

sendECB.fragList[0].fragAddress = &sendHeader;

sendECB.fragList[0].fragSize = sizeof(IPX_HEADER);

sendECB.fragList[1].fragAddress = &sendPacket;

if (sendPacket.record.header.status == 0)

sendECB.fragList[1].fragSize = sizeof(sPacket);

else

sendECB.fragList[1].fragSize = sizeof(lPacket);

memcpy(&sendHeader.destNet, &opNode->packetHeader.sourceNet, 12);

sendHeader.packetType = 4;

memcpy(&sendPacket.responseCode, &opNode->packet.responseCode, 3);

ccode = IpxSend(0, &sendECB);

if (sendPacket.record.header.status != 0L)

{

searchRec.data.lastReferenceTime = time(NULL);

ccode = fseek(fp, searchRec.header.offset, SEEK_SET);

if (ccode)

return;

ccode = TTSBeginTransaction();

if (ccode && ccode != 0xff)

return;

ccode = fwrite(&searchRec, sizeof(rec), 1, fp);

if (ccode != 1)

{

TTSAbortTransaction();

return;

}

else

TTSEndTransaction(&searchRec.header.transactionNumber);

printf("Operation Successful");

}

else

printf("Record not found");

return;

}

int CreateDataFile(char *netWarePath)

{

int ccode;

rec temp;

BYTE extendedAttributes;

fp = fopen(netWarePath, "w+b");

if (fp == NULL)

return(-1);

printf("\nData file created ...");

temp.header.status = 0x02L;

temp.header.offset = 0L;

temp.header.recordNumber = 0x64;

strcpy(temp.header.key, "NetWare data base engine");

ccode = fwrite(&temp, sizeof(rec), 1, fp);

if (ccode != 1)

{

fclose(fp);

ccode = unlink(netWarePath);

return(-1);

}

memset(&temp, 0x01, sizeof(rec));

temp.header.status = 0x0L;

for(temp.header.recordNumber = 0; \

temp.header.recordNumber < 0x64; \

temp.header.recordNumber++)

{

temp.header.offset = ftell(fp);

ccode = fwrite(&temp, sizeof(rec), 1, fp);

if (ccode != 1)

{

printf("\nError extending data base file (CreateDataFile)");

return(-1);

}

}

fclose(fp);

printf("file header written.");

printf("\nSetting Transaction bit");

ccode = GetExtendedFileAttributes(dataFileName, &extendedAttributes);

if (ccode)

printf("\nError getting extended attributes");

extendedAttributes |= 0x10; // turn transaction bit ON

if (!ccode)

ccode = SetExtendedFileAttributes(dataFileName,

extendedAttributes);

if (ccode)

printf("\nError setting extended attributes");

return(0);

}

int ExtendDataFile(int newRecords)

{

rec tempRec;

int ccode, index;

long oldOffset;

oldOffset = ftell(fp);

fclose(fp);

fp = fopen(dataFileName, "ab");

if (fp == NULL)

return(-1);

memset(&tempRec, 0x01, sizeof(rec));

tempRec.header.status = 0x0L;

for (index = 1; index <= newRecords; index++)

{

tempRec.header.offset = ftell(fp);

tempRec.header.recordNumber = (recordNumber + index);

ccode = fwrite(&tempRec, sizeof(rec), 1, fp);

if (ccode != 1)

{

printf("\nError extending data base file (ExtendDataFile)");

return(-1);

}

}

recordNumber += newRecords;

ccode = fseek(fp, 0L, SEEK_SET);

if (ccode)

{

printf("\nError reading the data base file header (ExtendDataFile)");

return(-1);

}

tempRec.header.recordNumber = recordNumber;

ccode = fwrite(&tempRec, sizeof(rec), 1, fp);

if (ccode != 1)

{

printf("\nError writing updated header to data base file (ExtendDataFile)");

return(-1);

}

printf("\n%#x New records successfully added to data file", newRecords);

fclose(fp);

fp = fopen(dataFileName, "ab+");

if (fp == NULL)

{

printf("\nError re-opening data file (ExtendDataFile)");

return(-1);

}

fseek(fp, oldOffset, SEEK_SET);

return(0);

}

int OpenDataFile(char *netWarePath)

{

int ccode;

rec temp;

fp = fopen(netWarePath, "r+b");

if (fp == NULL)

return(-1);

printf("\nData file opened...");

ccode = fread(&temp, sizeof(rec), 1, fp);

if (ccode != 1)

{

fclose(fp);

return(-1);

}

if (temp.header.status != 0x02)

{

printf("\nBad status ID on file header");

return(-1);

}

if (strcmp(temp.header.key, "NetWare data base engine"))

{

printf("Bad signature on file header");

return(-1);

}

recordNumber = temp.header.recordNumber;

printf("file signature verified.");

return(0);

}

void UnloadCleanUp(int sig)

{

sig = sig;

shutdown = 1;

SignalLocalSemaphore(replySem);

SignalLocalSemaphore(requestSem);

SignalLocalSemaphore(shutdownSem);

return;

}

void ShutdownCleanUp(void)

{

int ccode;

ccode = CloseLocalSemaphore(replySem);

ccode = CloseLocalSemaphore(shutdownSem);

ccode = CloseLocalSemaphore(requestSem);

ccode = IpxCloseSocket(ENGINE_SOCKET);

ccode = IpxCloseSocket(INIT_SOCKET);

ccode = ShutdownAdvertising(SAPHandle);

return;

}

int main(int argc, char **argv)

{

int ccode;

#ifndef _NETWARE_311_

int index;

#end

DIR *dirStruct, *tempDir;

char server[_MAX_SERVER + 1];

char volume[_MAX_VOLUME + 1];

char directories[_MAX_DIR + 1];

BYTE extendedAttributes;

/* Comprobar si TTS esta activo en el servidor */

ccode = TTSIsAvailable();

if (ccode != 0xff)

{

printf("\nTTS esta desactivado ...");

printf("\nPor favor, activelo y reintente la carga de ENGINE.NLM");

return(ccode;

}

if (argc < 2)

strcpy(dataFileName, "$engine$.dat");

else

strncpy(dataFileName, argv[1], _MAX_PATH);

ccode = ParsePath(dataFileName, server, volume, directories);

if (ccode)

{

printf("\nCamino de fichero de datos no valido... Saliendo");

return(-1);

}

dirStruct = tempDir = opendir(dataFileName);

readdir(tempDir);

if (tempDir == NULL)

ccode = CreateDataFile(dataFileName);

ccode = OpenDataFile(dataFileName);

if (ccode == -1) /* error al crear o abrir el fichero */

{

closedir(dirStruct);

printf("\nError al abrir o crear el fichero de datos... Saliendo");

return(ccode);

}

closedir(dirStruct); /* cerrar el nodo de directorio */

/* abrir los sockets IPX del NLM engine y dos semaforos para el

control de procesos ligeros */

IpxOpenSocket(&engineSocket);

IpxOpenSocket(&initSocket);

printf("\nSockets IPX abiertos.");

requestSem = OpenLocalSemaphore(0L);

replySem = OpenLocalSemaphore(0L);

/* abrir un semaforo que indique cuando cerrar el NLM */

shutdownSem = OpenLocalSemaphore(OL);

printf("\nSemaforos NetWare abiertos.");

/* Comenzar el anuncio de ENGINE en la red */

SAPHandle = AdvertiseService(0x88, "Date_Base_Engine");

printf("\nIdentificador SAP: %#08lx", SAPHandle);

signal(SIGTERM, UnloadCleanUp);

atexit(ShutdownCleanUp);

printf("\nExit procedures registered with OS.");

#ifndef _NETWARE_311_

for(index = 0; index < 6; index++)

{

workNodes[index].workProcedure = DoOperation;

workNodes[index].WorkResourceTag =

AllocateResourceTag(GetNLMHandle(),

"Engine Work To Do",

WorkCallBackSignature);

}

#endif

ccode = BeginThread(InitMain, NULL, 0x1000, NULL);

ccode = BeginThreadGroup(EngineMain, NULL, 0x2000, NULL);

if (ccode)

return(-1);

WaitOnLocalSemaphore(shutdownSem);

while(!shutdownOK)

ThreadSwitch();

return(0);

}

 

CAPITULO 3: PROGRAMACIÓN BÁSICA DEL SISTEMA DE FICHEROS

 

3.1.- VISIÓN DE CONJUNTO DEL SISTEMA DE FICHEROS NETWARE 4.0

El sistema operativo NetWare se ha optimizado con vistas a su utilización como servidor de red. No ocurre lo mismo con sistemas operativos de propósito general como Unix, Windows NT, OS/2, Macintosh, y DOS. El sistema de ficheros de NetWare es diferente a los sistemas de ficheros de los sistemas operativos de propósito general en aspectos como su alto rendimiento, los mecanismos para preservar la integridad de los datos, y la gran capacidad del sistema de ficheros.

El sistema de ficheros NetWare de ha creado de acuerdo con los siguientes objetivos de diseño, en orden de importancia:

Preservar la integridad de los datos.

Conseguir un alto rendimiento.

Proporcionar una capacidad del sistema de ficheros comparable a los ordenadores de tipo mainframe.

Proporcionar a las aplicaciones una amplia gama de servicios.

 

3.2.- EL VOLUMEN NETWARE

El volumen es la principal estructura de datos del sistema de ficheros NetWare. Un volumen se compone de almacenamiento físico, información de los ficheros lógicos (entradas de ficheros y directorios), información del espacio de nombres (para soporte de otros formatos de ficheros además del formato del DOS), y sistemas tolerantes a fallos, que comprenden el Hot-Fix y el seguimiento de transacciones.

Un servidor NetWare 4.0 puede tener hasta 32 volúmenes montados simultáneamente. Cada volumen puede proporcionar hasta 32 TB (terabytes) de almacenamiento físico, siempre que el servidor tenga la suficiente memoria RAM como para almacenar en cache las estructuras de datos de los volúmenes, entre las que están las FAT (File Allocation Table, Tabla de asignación de ficheros).

Un volumen NetWare similar a un sistema de ficheros Unix. Es decir, es posible montar y desmontar un volumen NetWare de la misma manera que en el caso de un sistema de ficheros Unix. Un volumen, una vez montado, proporciona una asociación lógica de unidades de almacenamiento en "ficheros". Sin embargo, la estructura interna de un volumen NetWare es diferente a la de un sistema de ficheros Unix

 

3.2.1.- Almacenamiento Físico de un Volumen

El medio físico que el volumen NetWare hace accesible a las aplicaciones está compuesto de bloques . Un bloque de un volumen es una secuencia de sectores del medio físico subyacente. El tamaño por defecto de un bloque generalmente es 4Kb (8sectores), aunque también se pueden utilizar tamaños mayores.

Un volumen NetWare es una secuencia de bloques, por lo que el volumen es mayor cuanto mayor sea el número del bloques que lo componen. Por lo tanto, podemos ver un volumen como un array de sectores. Cada sector de un bloque representa un área de medio físico subyacente.

Los bloques de un volumen deben estar asociados con el medio físico real. Este medio consiste en "segmentos", que son áreas del mismo que se han asignado y partido para ser utilizadas como parte de un volumen NetWare. Un segmento contiene los sectores de medio físico que el sistema de ficheros NetWare asigna como bloques del volumen. Por lo tanto, la estructura básica de un volumen NetWare es la siguiente:

· Un Segmento de medio físico que se ha asignado para que lo utilice un volumen NetWare.

· Sectores del medio físico de almacenamiento, que están contenidos en el segmento.

· Bloques, compuestos cada uno por un array de sectores.

· El volumen es un array de bloques.

 

3.2.2.- El Sistema de Ficheros Lógico

El sistema de ficheros lógico permite que las aplicaciones NetWare vean los datos almacenados en el medio físico como si estuvieran compuestos por ficheros lógicos. Un fichero lógico es una secuencia de bytes con un comienzo, un final, y una serie de atributos. Estos atributos son el nombre de fichero, la fecha del último acceso, la fecha de creación, su propietario, los derechos de los trustees, etc.

 

3.2.2.1.- La FAT del volumen

Cada volumen NetWare posee dos estructuras de datos que son fundamentales para el sistema de ficheros lógicos: la tabla de asignación de ficheros (FAT) y la tabla de entradas de directorio (DET, Directory Entry Table).

La FAT del volumen contiene una entrada por cada bloque del volumen. Una entrada específica se corresponde directamente con un bloque determinado del volumen, y contiene información que se utiliza para mantener el fichero al que pertenece ese bloque. En esta información se incluye si el bloque está asignado a un fichero, y la situación de la siguiente entrada correspondiente al fichero.

NetWare carga la FAT en memoria cache durante el montaje del volumen. Con ello el sistema de ficheros lógicos realiza las operaciones de entrada/salida de modo más rápido. Además, NetWare mantiene una copia redundante de la FAT del volumen, lo que hace que el sistema de ficheros lógicos sea más tolerante a fallos, como por ejemplo errores hardware o caídas del sistema.

 

3.2.2.2.- La tabla de entradas de direcctorio del volumen

La tabla de entradas de directorio contiene al menos una entrada por cada fichero almacenado en el volumen. Si el volumen soporta varios espacios de nombres, cada fichero posee una entrada en la tabla de entradas de directorio por cada espacio de nombres que soporta el volumen.

 

El proceso de lectura de un fichero comienza por la lectura de su entrada en la tabla de entradas de directorio. En segundo lugar, el sistema de ficheros lógico utiliza la primera entrada de la FAT de ese fichero (en la entrada de directorio) para leer el primer bloque del medio físico correspondiente a ese fichero. A continuación se utiliza la información de la primera entrada de la FAT para acceder a la segunda entrada de la FAT y así poder leer el segundo bloque del medio físico. Este proceso se repite hasta concluir la lectura del fichero.

NetWare utiliza un mecanismo dinámico para cargar en la memoria cache la tabla de entradas de directorio.

 

3.2.3.-La Memoria Cache del Sistema de Ficheros

NetWare incorpora un mecanismo dinámico de memoria cache masiva para los datos de los ficheros que aseguran un alto rendimiento del sistema de ficheros. Cuando una aplicación o cliente lee o escribe datos de un fichero, NetWare copia los bloques apropiados del fichero en la memoria cache (si es que no estaban ya copiados allí). Cuando la memoria cache está totalmente ocupada, se reciclan los buffers cache utilizando el algoritmo LeastRecentlyUsed (utilizado menos recientemente).

La cantidad total de memoria disponible para la cache de ficheros está, en función del tamaño de la memoria física instalada en el servidor.

Una vez que el sistema operativo está funcionando, NetWare almacena continuamente datos de ficheros en la memoria cache según las aplicaciones y los clientes van haciendo referencia a ellos. La cantidad total de memoria que NetWare dedica a la cache de ficheros se ve reducida si los NLM u otros módulos del sistema operativo necesitan asignación dinámica de memoria. por ejemplo, si NetWare tiene que asignar buffers de recepción adicionales para el controlador de LAN, obtiene esa memoria de la dedicada a cache de ficheros, mermando la misma. NetWare 4.0 permite que un NLM devuelva dinámicamente la memoria solicitada al sistema cache de ficheros una vez concluido su uso.

En la mayoría de las circunstancias, el programador no necesita tener en cuenta el funcionamiento del sistema de memoria cache cuando utiliza los API de entrada/salida de ficheros. Sin embargo, es posible, leer datos directamente de los buffers de la cache NetWare, utilizando el API AsyncRead. De este modo los NLM pueden conseguir un mayor incremento de su rendimiento en la lectura de un fichero, al aprovechar el mecanismo del sistema cache.

 

3.2.4.- Control de Concurrencia

Puesto que NetWare es un sistema operativo multiproceso, es posible que varios procesos ligeros intenten acceder a un mismo fichero simultáneamente. NetWare proporciona un juego completo de interfaces API para controlar la concurrencia en las operaciones de entrada/salida de ficheros. Los mecanismos de control comprenden: el bloqueo de bajo nivel de ficheros físicos y registros, el bloqueo de alto nivel de ficheros lógicos y registros, y , también, la manipulación de semáforos. Los NLM pueden obtener estos bloqueos exclusivos o compartidos sobre ficheros enteros o partes de ficheros. Los NLM reciben notificación de que un fichero o parte de un fichero está disponible mediante el uso de semáforos.

El sistema de ficheros NetWare permite hasta 100000 bloqueos de ficheros simultáneos por volumen, y hasta 200000 bloqueos de registros simultáneos por volumen.

 

3.2.5.-Control de Transacciones

El control de transacciones permite hacer que una serie de operaciones de estrada/salida se realicen en forma atómica. Por ejemplo, utilizando el control de transacciones se pueden asociar en una sola transacción una serie de operaciones de escritura sobre ficheros diferentes. NetWare garantiza el éxito todas y cada una de las operaciones de escritura. Por otra parte, si alguna de las operaciones falla, NetWare deshace las operaciones de la transacción es una proposición del tipo "todo o nada"; o bien se realizan todas las operaciones de la transacción, o bien no se realiza ninguna de ellas.

NetWare utiliza sus sistema de seguimiento de transacciones (TTS) para implementar el mecanismo de control de transacciones. Este sistema, que se ha implementado al nivel más bajo del sistema de ficheros lógicos NetWare, incluye un interfaz API de fácil utilización. Una característica fundamental de este interfaz es que gestiona automáticamente los bloques de registro, aliviando a los NLM de esta carga.

 

3.2.6.-Ficheros Dispersos

Un fichero disperso o esparcido es aquél cuya cadena contiene bloques que no han sido asignados. Las partes vacías de un fichero disperso no consumen espacio en el volumen, pero sí cuentan para determinar el tamaño del fichero.

Los ficheros dispersos se usan casi exclusivamente para el sistema de gestión de base de datos que utilizan algún tipo de clave hash para determinar la posición de un registro en el fichero de datos.

NetWare crea ficheros dispersos siempre que un NLM o cliente supera el final de un fichero al recorrer el mismo. Por lo tanto, es posible crear un fichero disperso por accidente, lo que puede llevar a errores difíciles de detectar.

 

3.2.7.-Compresión

El sistema de ficheros NetWare 4.0 posee un sistema de compresión de ficheros inteligente y transparente a los NLM y a los clientes. NetWare utiliza umbrales de fecha de último acceso para determinar si un fichero debe o no comprimirse. El operador del servidor puede utilizar el comando del consola SET para establecer el umbral de compresión.

Cuando un NLM o cliente obtiene acceso a un fichero comprimido, NetWare lo descomprime de modo transparente a la aplicación. La compresión de ficheros tiene lugar como tarea de fondo (en el background) simultáneamente con el funcionamiento normal del servidor. El programador NLM no tiene necesidad de preocuparse del proceso de compresión y descompresión, salvo saber simplemente que existe la compresión de ficheros.

 

CAPITULO 4: COMUNICACIONES IPX/SPX Y SPX II

 

4.1.- INTRODUCCIÓN

El propósito general de este capitulo es introducir las características del protocolo de comunicación punto a punto.

El conocimiento de los protocolos es la base para decidir cual es el protocolo más adecuado para cada aplicación que utilice comunicaciones punto a punto. Se recomienda el uso del interfaz de comunicaciones TLI, a no ser que existan motivos específicos que desaconsejen su empleo. La programación es generalmente más sencilla con TLI, y el código resultante posee un alto grado de portabilidad.

Aunque no todas las aplicaciones NLM requieren las comunicaciones punto a punto, la necesidad de éstas es muy frecuente en un entorno distribuido. Esto es debido a que los módulos NLM se ejecutan en ordenadores que poseen información y servicios que a menudo necesitan otros ordenadores.

Existen dos partes distintas en una aplicación cliente/servidor, aunque ambas forman parte de la misma aplicación. La parte del servidor proporciona el control y el acceso al servicio o a la información deseada. La parte del cliente envía peticiones a la parte del servidor y recibe sus respuestas. La parte del servidor se puede ejecutar bien en una estación de trabajo, o bien como un módulo NLM en un servidor Netware. La parte del cliente puede también ejecutarse en el servidor, pero normalmente se ejecuta en una estación de trabajo remota, y tiene un interfaz de usuario que permite al operador hacer peticiones de servicios. Es frecuente que una aplicación cliente/servidor proporcione acceso a un servidor que se encuentra en un servidor Netware. Por este motivo, muchas veces se emplea un módulo NLM para la parte del servidor de una aplicación cliente/servidor.

 

4.2.- PROGRAMACIÓN IPX

El IPX (intercambio de paquetes en redes múltiples) fue el primer protocolo de comunicaciones que se implementó dentro del entorno Netware.

Se empleaba exclusivamente para comunicar estaciones de trabajo y servidores entre si. IPX es un protocolo no orientado a conexión derivado del protocolo IDP.

IPX se utiliza para enviar y recibir paquetes de información entre las estaciones de trabajo y los servidores participantes en la comunicación. Esta comunicación es no garantizada, ya que no se prevé el envío de un acuse de recibo desde el destino del paquete. Sin embargo, IPX indica si el paquete fue realmente enviado o no.

 

4.2.1.- Componentes de IPX

El interfaz de IPX con la red es un controlador de LAN (red de área local). Para que IPX funcione es necesario que se cargue en memoria con un controlador de LAN específicamente programado para funcionar con IPX. Esto se realiza de manera diferente según se trate de una estación de trabajo o de un servidor Netware.

Las aplicaciones que utilicen IPX deben preparar los paquetes de una manera específica y deben seguir un procedimiento para iniciar la comunicación con IPX. El paquete propiamente dicho consta de una cabecera de 30 bytes y un máximo recomendado de 546 bytes de datos. Además, debe crearse e inicializarse un bloque de control de eventos (ECB) para cada petición IPX. Algunas veces también son necesarias rutinas de servicio de eventos (ESR), normalmente para manejar los paquetes de información entrantes.

Antes de recibir paquetes es necesario abrir un socket. Asimismo, aunque no es obligado, se recomienda la apertura de un socket antes de enviar paquetes. Una vez que se ha preparado un ECB y la información del paquete se puede empezar a enviar (o recibir) paquetes.

El ECB tiene distintos campos que pueden ser consultados o modificados para determinar el status y comportamiento de cada función IPX que se ejecuta.

A continuación se enumeran las funciones IPX que se pueden llamar:

IPXInitialize: Obtiene la dirección de entrada del interfaz IPX.

IPXOpenSocket: Abre un socket, ya sea especificado o dinámico.

IPXcloseSocket: Cierra el socket especificado.

IPXSendPacket: Inicia el envío de un paquete de información

IPXListenForPacket: Permite la recepción de un paquete entrante.

IPXRelinquishControl: Cede temporalmente el control de la CPU

IPXScheduleIPXEvent: Planifica el inicio de un evento IPX o de un evento especial.

IPXCancelEvent: Cancela un evento IPX o un evento especial.

IPXGetInternetworkAddress: Devuelve la dirección IPX (la denominada terna red-nodo-socket) de la tarjeta de interfaz de red que se emplea para acceder a la red.

IPXGetLocalTarget: Devuelve la dirección del primer router en la ruta al nodo destino.

IPXGetInternalMarker: Devuelve un valor que representa un instante en el tiempo.

IPXGetMaxPacketSize: Devuelve el tamaño máximo de un paquete para la topología de LAN que se está utilizando.

Existen otras funciones IPX pero son para entornos especializados o situaciones especiales.

La estructura de la cabecera IPX es la siguiente:

typedef struct IPXHeader {

WORD checksum; /* alto-bajo */

WORD length; /* alto-bajo */

BYTE transportControl; /* utilizado por routers de red */

BYTE packetType; /* tipo de servicio asociado con paquete */

IPXAddress destination; /* dirección de destino del paquete */

IPXAddress source; /* dirección de origen del paquete */

}

El programador debe de proporcionar la dirección de destino, mientras que IPX rellena automáticamente la dirección de origen de los paquetes que se envían. El buffer de datos es simplemente una secuencia de bytes. La memoria asignada a la cabecera debe de ser contigua, pero la parte de datos puede estar formada por fragmentos de memoria no contiguos. El ECB se utiliza para indicar la localización de los fragmentos de memoria que forman el paquete que se va a enviar. En el caso de recepción de paquetes, el ECB se emplea para indicar donde se colocar la información del paquete cuando éste se reciba.

 

4.2.2.- Utilización de Rutinas de Servicio de Eventos

Un uso frecuente de una rutina de servicio de evento es la realización de una acción cuando se recibe un paquete de información.

Cuando se emplea una ESR, ésta se ejecuta automáticamente cuando la función que está utilizando el ECB termina. Las rutinas ESR deben ser cortas y rápidas, puesto que las interrupciones se desactivan durante la ejecución de la ESR.

 

4.3.- UTILIZACION DE SPX

El protocolo SPX (intercambio de paquetes en secuencia) incorpora a la funcionalidad de IPX un mecanismo de plazos y reintentos. ES, además, un protocolo orientado a conexión, y se puede utilizar junto con IPX. SPX incluye todas las características de IPX, aunque para que una aplicación las disfrute, debe estar específicamente programada para utilizar SPX en lugar de IPX. Algunas de las funciones utilizan el mismo interfaz en ambos casos.

 

4.3.1.- Comparación entre IPX y SPX

Las diferencias más notables entre ambos son sus cabeceras y la sobrecarga en tiempo de ejecución de SPX, debida al mecanismo que garantiza la entrega de paquetes. Para garantizar la entrega se realizan varios intentos de envío, y se informa al NLM que hace la llamada si tras un determinado numero de intentos el envío ha fallado. De este modo, el NLM que envía el paquete no tiene que comprobar si el paquete ha llegado a su destino. SPX notifica al NLM el estado del envío.

La cabecera de un paquete SPX contiene 42 bytes. Los campos adicionales de la cabecera SPX contienen la información relativa a la verificación de la entrega del paquete.

El método para inicializar SPX, sus buffers y sus conexiones es diferente del método de inicialización de IPX.

 

4.4.- SPX II

Es una versión actualizada y mejorada de SPX.. Uno de los aspectos más importantes en su diseño fue la compatibilidad hacia atrás.

Los objetivos principales de SPX II son la utilización de paquetes de mayor tamaño, la implementación de un auténtico protocolo de ventana y el soporte del interfaz de la capa de transporte (TLI)

 

4.4.1.- Las Ventajas de SPX II

Lo más importante de SPX II es que es totalmente compatible hacia atrás con SPX. Esto se consigue mediante un sistema de funcionamiento con dos modos. El modo que se va a utilizar se fija cuando se establece la conexión. Un nodo SPX II indica su deseo de comunicarse utilizando SPX II poniendo a 1 un bit de la cabecera SPX.

Estos nodos comprueban ese bit y establecen una conexión SPX II si está activado. Un nodo SPX que se comunique con un nodo SPX II no advertirá ninguna diferencia. El nodo SPX II simplemente funciona en modo SPX a no ser que detecte el bit de SPX II al establecer la conexión.

SPX II incorpora una mejora sustancial en el tamaño de los paquetes, pudiendo utilizar paquetes mayores de los que podía utilizar SPX. Si el mensaje que se va a enviar es más largo que el máximo permitido, debe partirse en paquetes de este tamaño. Cuando el volumen de los datos es grande, es más eficiente utilizar paquetes de mayor tamaño, si es posible. SPX II utiliza automáticamente la ventaja que ofrecen las redes que admiten tamaño de paquetes mayores.

Otra característica de SPX II es que implementa un protocolo de ventana. En un protocolo normal, cuando se envían una serie de paquetes que forman parte de la misma comunicación hay que esperar el paquete de confirmación de recepción para cada paquete individual. Esto ralentiza la comunicación y es frecuentemente innecesario. En un protocolo de ventana, se envían varios paquetes y se espera un solo paquete de confirmación de recepción que confirma la recepción de todos ellos. El numero de paquetes enviados sin esperar la confirmación de recepción es ajustable, y recibe el nombre de tamaño de la ventana. Si no se recibe uno de los paquetes del lote, el receptor envía al emisor una petición de reenvío de ese paquete en particular. Por tanto, este sistema reduce el tráfico en la red y acelera el proceso de comunicación.

 

4.4.2.- Una Introducción a TLI para SPX II

La característica de SPX II más significativa con respecto al desarrollo de aplicaciones es que soporta el interfaz TLI (interfaz de la capa de transporte). Es posible portar cualquier aplicación TLI al entorno SPX de Netware sin tener que reprogramarla completamente para que utilice IPX o SPX.

El API TLI es sencillo tanto en cuanto a su aprendizaje como en cuanto a su utilización. Por tanto, el usuario que esté inicializándose en la programación de comunicaciones punto a punto puede aprender solamente TLI, sin pasar primero por IPX y SPX. La sencillez de TLI hace que el proceso de aplicaciones sea mucho más rápido, ventaja que supera bastante la pequeña ganancia de rendimiento que proporciona IPX y SPX a costa de un mayor esfuerzo de programación. El tamaño de los ficheros ejecutables cuando se utiliza TLI no es muy distinto del que resulta de utilizar IPX o SPX, por lo que este aspecto no es un factor a tener en cuenta.

El método estandard de iniciar y llevar a cabo una comunicación TLI es el siguiente:

1.- Abrir un extremo de conexión TLI (para IPX, SPX o TCP)

2.- Asociar ese extremo a una dirección

3.- Establecer una conexión (si se utiliza SPX o TCP)

4.- Enviar y recibir los datos

5.- Desconectar (si se utiliza SPX o TCP)

Algunas funciones TLI son las siguientes:

t_open: Abre un extremo de conexión TLI.

t_bind: Asocia un extremo de conexión a una dirección.

t_sndudata: Envía un paquete de tipo datagrama (como con IPX).

t_rcvudata: Recibe un paquete de tipo datagrama.

t_connect: Inicia una solicitud de conexión.

t_listen: Espera una solicitud de conexión. (con t_connect)

t_accept: Envía una notificación de aceptación al extremo que solicitó la conexión.

t_rcvconnect: Recibe la respuesta de una solicitud de conexión (enviada con t_accept)

t_snd: Envía un paquete de información por una conexión.

t_rcv: Recibe un paquete de información por una conexión.

t_rcvdis: Desconecta el extremo receptor de una conexión.

t_snddis: desconecta el extremo emisor de una conexión.

t_look: Devuelve el tipo de evento que generó un numero de error TLI (t_errno)

t_error: Imprime un mensaje de error.

La función t_open devuelve un valor entero que se utiliza al llamar a otras funciones TLI. En las comunicaciones TLI, se utiliza un extremo de conexión y un buffer de datos para enviar datos al receptor.

La variable t_errno almacena el código de error correspondiente a la última función TLI que se ha ejecutado. Dos de los posibles valores de este código de error son TNODATA y TLOOK. El primero de ellos indica que se ha ejecutado la función de recepción pero que todavía no se han recibido datos. En este caso, se debe volver a comprobar si los datos han llegado más adelante. El segundo código de error indica que ha ocurrido un error más grave que la simple ausencia de datos.

Si t_errno tiene el valor TLOOK, significa que existe más información sobre el evento que causó el error. Para acceder a esta información se utiliza la función t_look. Entre los eventos que causan un código de error T_LOOK están T_DISCONNECT (la conexión se ha interrumpido o bien ha sido rechazada) y T_UDERR (hubo un error en un datagrama que había sido enviado previamente).

 

BIBLIOGRAFIA:

Guia de Programación Novell NetWare.

Aut. M. Day, M. Koontz y D. Marshall.

De. Anaya.

Información adquirida de Internet en el servidor Novell de Lycos.