Member of The Internet Defense League Últimos cambios
Últimos Cambios
Blog personal: El hilo del laberinto Geocaching

Desarrollo de una Herramienta Software para el Acceso a Redes TCP/IP a través de la Red Telefónica Conmutada

Última Actualización: 28 de Junio de 1.996 - Viernes

Capítulo 2:
El Módulo de Gestión de Mensajes

Este Proyecto Fin de Carrera, por sus propias características, se presta muy bien a una implementación multitarea. Por una parte existen multitud de procesos autocontenidos que intercambian información entre sí mediante el uso de una interfaz bien definida y coherente. Por otra parte la ejecución de estos mismos procesos es, en la mayor parte de los casos, asíncrona. Es decir, la ejecución está dirigida por eventos externos tales como el vencimiento de temporizaciones, la recepción de datagramas a través de alguno de los puertos hardware, la llegada de mensajes provenientes de otros procesos, etc.

Lo ideal, pues, hubiese sido implementar todo el Proyecto en un entorno con capacidad multiproceso. No obstante el objetivo era desarrollar las rutinas de la forma más portable posible y la multitarea no es una funcionalidad disponible en la mayoría de los sistemas personales. Fue por ello por lo que, contando con la experiencia del autor en el proyecto POWERNET [POWERNET] y su continuación en Laboratorio de Comunicación de Datos [LABCD][LABCD-MSG] del 5° Curso en la E.T.S.I.T. de Vigo, se decidió desarrollar un minisistema operativo multitarea, portable, eficiente y fácil de utilizar, sobre el cual implantar este Proyecto en sí.

El resultado de este esfuerzo es el módulo de gestión de mensajes. Gracias a él es posible definir procesos independientes y vías de comunicación entre los mismos. Sus características principales son:

  • Implementación de un esquema de multitarea cooperativa en lenguaje C, portable y eficiente.

  • Implementación de un esquema de paso de mensajes entre procesos.

  • Ejecución dirigida por eventos:
    • Recepción de un mensaje de otro proceso
    • Vencimiento de un temporización
    • Ejecución en tiempos muertos (IDLE)

  • Tres niveles de prioridad con estrategia Round Robin en cada uno de ellos. La prioridad de un proceso puede variar de forma dinámica:
    • Alta prioridad: Siempre que hay un mensaje pendiente para un proceso de esta categoría se procesa lo antes posible.
    • Baja prioridad: Estos procesos se ejecutan cuando hay mensajes pendientes para ellos y no hay ningún proceso de alta prioridad en espera.
    • IDLE: Los procesos IDLE se ejecutan cuando no hay ningún mensaje pendiente. Cualquier proceso puede definirse como IDLE sin perjuicio de que, a su vez, pueda ejecutarse también bajo la recepción de mensajes provenientes de otros procesos, etc. La característica de IDLE simplemente especifica que el proceso en cuestión puede ejecutarse cuando no haya ningún otro compitiendo por la CPU.

  • Entrega diferida de mensajes. Utilizado, por ejemplo, para crear temporizaciones.

  • Profiling del tiempo de ejecución de cada proceso, tiempo medio de espera en cola para los mensajes de cada una de las prioridades, tiempo consumido en el dispatcher y tiempo total consumido en procesos IDLE.

  • Capacidad de mensajes BROADCAST. Esta facilidad resulta especialmente útil en la inicialización y finalización del software.


Interfaz

Este módulo proporciona dos funcionalidades básicas: gestión de procesos e intercambio de mensajes entre los mismos. Para ello se han definido varias estructuras de datos.


proc_id

Este tipo define el identificador de proceso. Cada proceso declarado posee un identificador único a través del cual se puede comunicar con el resto de los procesos del sistema.


msg_id

Este tipo define el identificador de mensaje. Todos los mensajes intercambiados entre los diferentes procesos deben cumplir una serie de normas estrictas que aseguren que el proceso receptor sea capaz de reconocer y procesar adecuadamente los mensajes que recibe.

Los mensajes en sí consisten en estructuras como la siguiente:


  typedef struct {
    proc_id  destino;
    proc_id  remitente;
    msg_id   mensaje;
    campo    campo1;
    campo    campo2;
    campo    campo3;
  } msg;

  typedef union {
    pmbuf   puntero;
    uint32  valor;
  } campo;

Un proceso que desee enviar un mensaje debe indicar en el campo "destino" el identificador del proceso destinatario. El campo "remitente" es inicializado de forma automática por el módulo de gestión de mensajes y permite implementar un esquema de control de acceso. A "mensaje" se le asigna el identificador o tipo de mensaje que se desea enviar, y los campos de datos constituyen los parámetros del mensaje en sí. Como puede observarse, cada campo puede estar constituido por un valor de 32 bits o por un puntero a una estructura MBUF. El significado y contenido de cada parámetro es específico de cada tipo de mensaje en particular.

Los mensajes definidos en este módulo y, por tanto, globales al sistema, son:


MSG_INIT

El objetivo de este mensaje es ser utilizado en un Broadcast para inicializar todos los procesos.

Los valores de los parámetros no tienen ninguna finalidad específica.


MSG_QUIT

El objetivo de este mensaje es ser utilizado en un Broadcast para finalizar todos los procesos. Cuando todos los procesos han terminado de gestionar este mensaje, el dispatcher da por finalizada su ejecución y el programa termina.

Los valores de los parámetros no tienen ninguna finalidad específica.


MSG_IDLE

Este mensaje es recibido por los procesos IDLE cuando no existe ninguna otra tarea útil.

Los valores de los parámetros son indefinidos.


MSG_MBUF

Este es el mensaje empleado cuando un proceso desea transferir a otro la propiedad de un bloque de memoria. El campo1 contiene el puntero a la estructura MBUF que especifica el bloque en cuestión. El resto de los campos no tienen una finalidad específica y pueden ser utilizados para transferir información adicional.


MSG_TIMEOUT

Este mensaje se ha definido con el fin de indicar el término de una temporización. No obstante no hay ninguna obligación de utilizar este mensaje en concreto; el sistema permite retener cualquier mensaje por un tiempo dado antes de su entrega a su destinatario final.

Las rutinas del sistema de gestión de mensajes accesibles desde el exterior son:


void msg_init(void);

Esta rutina es la primera, de este módulo, que debe llamarse. Es la responsable de inicializar la tabla de procesos y las colas de mensajes.


proc_id msg_proc_alta(puint8 id,void (FPOINTER proc)(pmsg),
	uint8 tipo,uint8 prio);

Esta rutina da de alta un proceso y devuelve su identificador. El parámetro "id" proporciona un nombre explicativo asociado al proceso, utilizado para el profiling y la gestión de errores fatales. "proc" es un puntero a una función con el siguiente prototipo:


    void proc(pmsg mensaje);

Dicha rutina constituye el punto de entrada al proceso. Es la que recibe los mensajes del dispatcher y la que éste invoca cuando la ejecución corresponde a ese proceso. "tipo" especifica el tipo de proceso que estamos creando:

  • TIPO_NORMAL
  • TIPO_INIT
  • TIPO_QUIT
  • TIPO_IDLE

Los tres últimos tipos pueden combinarse para crear procesos especiales. "TIPO_INIT" especifica que el proceso en cuestión debe recibir todos los mensajes Broadcast SALVO los "MSG_QUIT". Estos serán recibidos sólo si el proceso es "TIPO_QUIT". En cuanto a "TIPO_IDLE", un proceso con esta característica se ejecutará también cuando no exista ningún otro no "IDLE" compitiendo por la CPU. Ello resulta muy útil, por ejemplo, para la gestión de los puertos de comunicaciones o para implementar esquemas de "recogida de basuras" (Garbage Collection) en background.

Por último el parámetro "prio" fija la prioridad del proceso. Esta prioridad puede variarse de forma dinámica, y su valor puede ser:

  • PRIO_ALTA
  • PRIO_BAJA

Un proceso de alta prioridad se ejecutará siempre que haya un mensaje pendiente para él. Un proceso de baja prioridad, en cambio, sólo se ejecuta cuando haya un mensaje pendiente para él y no haya ningún proceso de alta prioridad esperando.

A los procesos "TIPO_INIT" o "TIPO_QUIT" se les asigna una prioridad alta independientemente de lo que se indique en el campo correspondiente.

Todos los procesos "TIPO_IDLE", cuando la CPU no tiene otras tareas que realizar, se ejecutan según una disciplina Round Robin, independientemente de su prioridad.


void msg_proc_baja(proc_id id);

Este procedimiento permite dar de baja un proceso especificado por su identificador.


void msg_send(pmsg mensaje);

Esta rutina es ejecutada en el contexto del proceso llamante, aunque también puede ser invocada antes de lanzar el dispatcher para, por ejemplo, enviar los mensajes Broadcast iniciales necesarios para arrancar correctamente todos los módulos. Sirve para enviar un mensaje a un proceso. Todos los campos, salvo el del remitente, deben ser inicializados por el proceso transmisor.


void msg_diferido(pmsg mensaje,uint32 milisec);

Esta rutina es la encargada, entre otras cosas, de realizar temporizaciones. Su funcionamiento real consiste en almacenar un mensaje durante un tiempo determinado antes de entregarlo. Cada proceso receptor sólo puede tener pendiente un mensaje diferido. Si se intentan enviar otros sólo se conserva el último. El tiempo se especifica en milisegundos. Un tiempo de CERO milisegundos borra cualquier mensaje que estuviese pendiente de entrega.


void msg_dispatcher(void);

Esta rutina constituye el núcleo del sistema de gestión de mensajes. Debe ser lanzada tras "msg_init" y tras declarar algunos procesos y los mensajes iniciales. Se encarga de enrutar los mensajes y ejecutar los procesos invocados. Comprueba, así mismo, el vencimiento de las temporizaciones en curso y realiza una planificación de CPU en función de los mensajes pendientes de entrega y las prioridades de los procesos. Durante los tiempos muertos se lanzan procesos "IDLE".

Esta rutina se ejecuta hasta que alguna entidad envía un "MSG_QUIT" Broadcast y todos los procesos "TIPO_QUIT" terminan su gestión.

Una vez que finaliza imprime una serie de datos de profiling muy pormenorizados: Porcentaje de tiempo asignado a cada proceso, tiempo medio de espera en cola para los mensajes de cada prioridad, tiempo total invertido en procesos "IDLE" y tiempo total desperdiciado debido a la propia sobrecarga que supone la ejecución del dispatcher.

Adicionalmente se han definido algunos tipos de datos, con fines de apoyo:


PROC_BROADCAST

Cuando un proceso desea enviar un mensaje Broadcast debe especificar este valor en el campo "destino". No se permite enviar mensajes Broadcast diferidos. Los mensajes Broadcast son recibidos por los procesos "TIPO_INIT", salvo si se trata de un mensaje "MSG_QUIT". En dicho caso los receptores serán los procesos "TIPO_QUIT".


PROC_INDEF

Este identificador indica, en el campo "remitente", que no se sabe qué proceso ha enviado el mensaje. Es el caso, por ejemplo, de los mensajes iniciales enviados antes de invocar a la rutina "msg_dispatcher" y antes, por tanto, de que existan procesos propiamente dichos.


proc_id PROC_SELF;

Esta variable contiene el identificador del proceso actualmente en ejecución. Permite que una rutina conozca qué proceso la está ejecutando o bien que un proceso pueda enviarse mensajes a sí mismo o indicar su identidad a otro de forma simple.


puint8 PROC_ID_SELF;

Esta variable es un puntero a la cadena indicada cuando se declaró el proceso actualmente en ejecución y que, se supone, proporciona información útil sobre el mismo (habitualmente el nombre). Este dato es utilizado, por ejemplo, para la impresión de los mensajes de diagnóstico ante errores fatales.


Implementación

Para cada proceso declarado se asigna una estructura interna que contiene toda la información relevante referente a dicho proceso. Esta información incluye tanto los datos especificados al darlo de alta como campos destinados a la gestión del profiling o la entrega de mensajes diferidos. Asimismo se mantienen dos colas de mensajes (una por cada prioridad) que contienen los mensajes en espera de ser entregados e información sobre su instante de llegada al sistema.

Los pasos que realiza el dispatcher son, aproximadamente, los siguientes:

  1. Comprueba si ya ha vencido la temporización de un proceso. Si es así pone el mensaje almacenado en la cola que corresponda.

  2. Realiza los cambios necesarios para que la siguiente vez que se llegue al punto 1 se examine un proceso diferente.

  3. Comprueba si hay algún mensaje en la cola de alta prioridad. Si es así lo entrega y regresa al punto 1.

  4. Comprueba si hay algún mensaje en la cola de baja prioridad. En ese caso ejecuta el proceso correspondiente y regresa al punto 1.

  5. Si el proceso que corresponde examinar en el punto 1 es del tipo "IDLE", lo ejecuta enviándole un mensaje "MSG_IDLE".

  6. Regresa al punto 1.

Los mensajes de cada cola son entregados estrictamente en el orden de llegada al sistema.

Dado que se ha implementado un esquema de multitarea cooperativa no puede garantizarse que los mensajes diferidos sean entregados exactamente en el momento especificado. El instante de entrega viene determinado por la carga del sistema, la precisión del reloj y la prioridad del proceso.

En cualquier caso lo que sí se garantiza es que la entrega nunca se realizará antes del tiempo demandado. Esto resulta muy útil para implementar los TimeOut's.

Por último, se considera la prioridad de los procesos sólo cuando se les envían los mensajes. Un cambio en la prioridad no les afecta una vez que estos han sido almacenados en una cola.


Bibliografía


[LABCD]     Laboratorio de Comunicación de Datos
            Curso 94-95. Vigo, 16 de Marzo de 1.995
            Alvaro Manuel Gómez Vieites
            Carlos Gabieiro Martínez
            Jose Cadavid Jáuregui
            Jesús Cea Avión

[LABCD-MSG] Laboratorio de Comunicación de Datos
            Curso 94-95. Vigo, 16 de Marzo de 1.995
            Módulo de Mensajes
            Alvaro Manuel Gómez Vieites
            Carlos Gabieiro Martínez
            Jose Cadavid Jáuregui
            Jesús Cea Avión

[POWERNET]  Proyecto PowerNet
            Jesús Cea Avión
            Implementación de multitarea cooperativa en lenguaje C
            11 de Abril de 1.990



Python Zope ©1996 jcea@jcea.es

Más información sobre los OpenBadges

Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS