joaquín luque rodríguez ana verónica medina …personal.us.es/jluque/libros y apuntes/1994...
TRANSCRIPT
Joaquín Luque RodríguezAna Verónica Medina Rodríguez
DESARROLLO DEPROTOCOLOS
UNIVERSIDAD DE SEVILLADEPARTAMENTO DE TECNOLOGÍA ELECTRÓNICA
Joaquín Luque RodríguezAna Verónica Medina Rodríguez
DESARROLLO DEPROTOCOLOS
Universidad de SevillaDepartamento de Tecnología Electrónica
Servicio de PublicacionesSevilla, 1994
* Facultad de Informática y EstadísticaAvenida Reina Mercedes s/n
41012-Sevilla. SPAIN.( 455 27 86
DESARROLLO DE PROTOCOLOS Página 1
1.- Conceptos de Ingeniería de Protocolos.
Se denomina Ingeniería de Protocolos al conjunto de
actividades que, partiendo de unos requisitos de
comunicación de un sistema informático, es capaz de generar
un protocolo de comunicaciones ejecutable que cumple los
requisitos establecidos de una manera fiable y eficiente
(fig. 1). Las actividades más significativas que tienen
lugar dentro de la Ingeniería de Protocolos son las
siguientes (fig. 2):
a) Síntesis. Partiendo de la descripción informal de
los requisitos de comunicaciones genera una
especificación del protocolo. La especificación de un
protocolo comprende 5 partes distintas:
- Los servicios proporcionados por el protocolo;
- Las suposiciones sobre el entorno en el cual se
va a ejecutar el protocolo;
Figura 1.- Contexto de la Ingeniería de Protocolos
DESARROLLO DE PROTOCOLOS Página 2
- El vocabulario de mensajes usados por el
protocolo;
- El formato de cada uno de los mensajes del
vocabulario;
- Las reglas de procedimiento que garantizan la
consistencia del intercambio de mensajes; y
Figura 2.- La Ingeniería de Protocolos
DESARROLLO DE PROTOCOLOS Página 3
Cada una de estas partes de la especificación debe ser
descrita con la menor ambigüedad posible, recurriendo,
cuando sea posible, a técnicas de descripción formal de
protocolos (FDTs: Formal Description Techniques), entre
las que se pueden destacar:
- Máquinas de estados finitos (FSM: Finite State
Machines).
- Máquinas de estados finitos extendidas (EFSM:
Extended Finite State Machines).
- Redes de Petri.
- Lógica temporal.
- Lenguajes de programación.
- Lenguajes de especificación. Entre estos
destacan los siguientes lenguajes normalizados:
- Estelle (ISO), basado en EFSM.
- LOTOS (ISO), basado en lógica temporal.
- SDL (CCITT), basado en EFSM.
- ASN.1 (ISO y CCITT) para descripción de
PDUs.
b) Validación. Esta actividad se propone descubrir los
errores de autoconsistencia lógica que pudiese tener la
especificación, en un esfuerzo por encontrar aquellos
errores que pueden existir en cualquier protocolo, con
independencia de la función específica que realice.
Entre estos errores destacan:
- Bloqueos (deadlocks): el protocolo llega a un
estado del que no puede salir.
DESARROLLO DE PROTOCOLOS Página 4
- Círculos viciosos (livelocks): el protocolo
ejecuta indefinidamente una secuencia de pasos
repetitiva sin realizar ningún progreso efectivo.
- Diseño incompleto: no se especifican respuestas
para todos los sucesos posibles en cada estado.
c) Verificación. Es la tarea de comprobar que el
protocolo especificado realiza correctamente las
funciones para las que fue diseñado, y esto no sólo en
condiciones normales, sino también ante la presencia de
cualquier combinación de fallos. En la práctica la
diferencia entre la validación y la verificación no es
siempre clara, por lo que a veces se engloban como una
sola actividad.
d) Análisis de prestaciones. El objetivo de la
Ingeniería de Protocolos no es sólo obtener protocolos,
sino hacer que éstos sean los más eficientes que sea
posible. Para ello la especificación es analizada desde
este punto de vista, midiendo parámetros tales como
eficiencia, retardo, longitud de colas y otros, para
sugerir modificaciones en la especificación que
optimicen las prestaciones.
e) Implementación. Una vez obtenida una especificación
fiable y eficaz del protocolo, y a partir de ella, se
procede a la construcción de un protocolo ejecutable.
Son numerosas las cuestiones y criterios que habrá que
tener en cuenta en esta fase, las cuales serán objeto
de desarrollo en posteriores apartados.
DESARROLLO DE PROTOCOLOS Página 5
f) Generación de pruebas. Aunque la especificación haya
sido depurada y sea fiable y eficiente, el complejo
problema de la implementación puede introducir de nuevo
errores o ineficacias en el producto final. Esta
actividad se encarga de generar la batería de pruebas a
las que deberá someterse el protocolo ejecutable.
Existen diversas técnicas para ello e incluso un
lenguaje de especificación de las pruebas normalizado
por ISO con el nombre de TTCN (Tree and Tabular
Combined Notation).
g) Pruebas. El protocolo ejecutable, antes de su
aprobación definitiva, debe ser probado con respecto a
dos criterios:
- Pruebas de homologación (conformance testing):
el protocolo ejecutable es una implementación fiel
de la especificación y cumple las funciones que en
ella se expresan.
- Pruebas de prestaciones: el protocolo ejecutable
tiene unas prestaciones adecuadas (tiempos de
respuesta, capacidad de tráfico, etc.).
Los principios discutidos anteriormente pueden
formularse en las siguientes diez reglas básicas para el
desarrollo de protocolos [HOL 91]:
1.- Asegúrese de que el problema está bien definido. Todos
los criterios de diseño, requisitos y restricciones,
deben ser formulados antes de comenzar el desarrollo.
DESARROLLO DE PROTOCOLOS Página 6
2.- Defina los servicios que deben realizarse en cada nivel
o módulo de abstracción antes de decidir que
estructuras se usarán para llevar a cabo estos
servicios (el qué viene antes que el cómo).
3.- Diseñe la funcionalidad externa de cada nivel o módulo
antes que el comportamiento interno. Considere primero
la solución cómo una caja negra y decida como debe
interaccionar con su entorno. Una vez hecho esto puede
ya decidir como se organiza internamente cada caja
negra. Probablemente consistirá en cajas negras más
pequeñas que pueden ser tratadas de forma similar.
4.- Mantenga la simplicidad. Los protocolos rebuscados son
más propensos a contener errores que los simples; son
más difíciles de implementar, más difíciles de
verificar, y normalmente menos eficientes. Existen muy
pocos problemas realmente complejos en el desarrollo de
protocolos. Los problemas que parecen complejos son con
frecuencia problemas simples entrelazados. La tarea del
ingeniero de protocolos consiste en identificar los
problemas más simples, separarlos, y resolverlos
individualmente.
5.- No mezcle cuestiones que son independientes.
6.- No introduzca restricciones innecesarias. Un buen
desarrollo debe ser fácilmente extensible. Un buen
desarrollo resuelve una clase de problemas y no tan
sólo un problema aislado.
7.- Antes de implementar el protocolo, valide y verifique
DESARROLLO DE PROTOCOLOS Página 7
el protocolo, y analice sus prestaciones, comprobando
que se cumplen los requisitos impuestos.
8.- Implemente el protocolo.
9.- Realice las pruebas de homologación y prestaciones de
la implementación obtenida.
10.- No se salte las reglas 1 a 7.
La regla que se incumple con más frecuencia es claramente la
regla número 10.
2.- El problema de la implementación.
El problema de la implementación de un protocolo,
superada ya la fase de especificación del mismo, presenta
interesantes cuestiones que conviene abordar. Existen
principalmente tres vías para obtener un protocolo
ejecutable a partir de una especificación: automática,
semiautomática o manual (fig. 3). Por la vía automática la
especificación, que debe haber sido descrita completamente
mediante FDTs, se somete a un compilador del lenguaje en el
que está descrita la especificación, el cual genera el
protocolo ejecutable requerido. En el procedimiento manual
existen tres fases:
a) En primer lugar se establecen los criterios o
estrategias para implementar correctamente los
protocolos.
b) A continuación se realiza un diseño informático
fruto del cual se obtiene un documento de diseño.
DESARROLLO DE PROTOCOLOS Página 8
c) Por último se procede a codificar el diseño anterior
en algún lenguaje de programación convencional del que
se deriva el protocolo ejecutable.
El procedimiento semiautomático es una mezcla de los
dos anteriores. En efecto, parte de una especificación del
protocolo descrita mediante una FDT para la cual se dispone
de un compilador adecuado. Sin embargo, si la especificación
original no es completa (por ejemplo no incluye la gestión
de memoria, la interfaz con módulos externos, etc.) será
necesario completarla de forma manual, siguiendo para ello
los tres pasos anteriores: definición de criterios, diseño
Figura 3.- La implementación de protocolos
DESARROLLO DE PROTOCOLOS Página 9
informático y codificación. El resultado puede ser bien una
modificación de la especificación original para completarla,
o bien la elaboración de módulos ejecutables que en unión de
los obtenidos automáticamente forman el conjunto del
protocolo ejecutable.
Cualquiera que sea el método empleado la construcción
de un protocolo pasa necesariamente por una fase de
definición de criterios de implementación, aunque según el
grado de automatización del proceso, estos criterios estarán
subsumidos en mayor o menor medida dentro de la herramienta
de generación. Conviene pues centrarse en tales criterios,
entre los cuales destacan los siguientes (fig. 4):
- Representación de arquitecturas multicapa.
- Representación de máquinas de estados.
- Representación de multiplicidad de máquinas iguales
del mismo nivel.
- Tratamiento de cabeceras y colas.
- Relación entre SDUs y PDUs.
- Gestión de memoria.
- Manejo del hardware de comunicaciones.
- Interfaz con módulos externos.
Cada uno de estos aspectos merece un estudio detenido
que se aborda en los próximos apartados.
DESARROLLO DE PROTOCOLOS Página 10
Figura 4.- Definición de criterios de implementación
DESARROLLO DE PROTOCOLOS Página 11
Representación decapas
Una capa porproceso
Claridad conceptual
Fácil mantenimiento/sustitución
Facilidad de pruebas
Varias capasen un procesoúnico
Utilización de código común
Ocupa menos espacio
Más rápido
Mayor transparencia
Representación deprocesos
Tareas Mayor claridad conceptual
Fácil mantenimiento/sustitución
Facilidad de prueba
Inconvenientes Sistema Operativo multitarea obligatorio
Mayor espacio
Elevado número de cambios de contexto
Pérdida de control de ejecución
Módulos Obligatoria en Sistemas Operativos monotarea
Necesita sistema de gestión de módulos
Mayor rapidez (no hay cambios de contexto)
Menor espacio
Mejor control de ejecución
Comunicación entrecapas
Asíncrona Monocola Ahorra espacio
Centraliza funciones de gestión de colas
Lógica de gestión más compleja
Multicola
Dificultades Control de flujo.Soluciones:
Colas de tamaño fijo
Primitivas de control deflujo
Asig. de memor.controlada
Ejec. atómica.Solución:
Asig. de prioridades alos eventos
SíncronaTransferencia de control con el IDU
Ventajas Elimina el problema del control de flujo
Elimina el problema de la ejecución atómica
Inconvenientes Dificultades de coordinación
Limita el paralelismo
Eleva el número de cambios de contexto.
Tabla 1.- Representación de arquitecturas multicapa
DESARROLLO DE PROTOCOLOS Página 12
2.1.- Representación de arquitecturas multicapa.
Cuando se aborda el problema de implementar protocolos
que comprenden más de una capa del modelo OSI hay que
fijarse principalmente en 3 aspectos (tabla 1): a) forma de
representación de las capas; b) forma de representación de
los procesos; y c) forma de comunicación entre capas.
a) Representación de las capas. A este respecto caben dos
soluciones. La primera de ellas propugna que cada capa sea
representada mediante un proceso (fig. 5a), tenga éste la
forma informática que tenga (módulo, tarea). Este enfoque
proporciona una gran claridad conceptual, facilita el
mantenimiento y/o la sustitución de la capa y se adapta
perfectamente a las técnicas de prueba de la capa. Por otra
parte, cabe que varias capas sean agrupadas en un sólo
proceso (fig. 5b), optimizando espacio, tiempo de ejecución,
posibilitando la utilización de código común, y haciendo más
transparente al usuario la constitución interna del
protocolo. Sin embargo con esta solución es más difícil la
sustitución y la prueba de una capa aislada.
DESARROLLO DE PROTOCOLOS Página 13
b) Representación de los procesos. El término proceso
utilizado hasta ahora para representar una o varias capas es
deliberadamente ambiguo. Su concreción informática puede
tomar dos formas: tarea o módulo. Entendemos por tarea una
unidad de programación que el sistema operativo considera
como un ente autónomo para la ejecución y que, por tanto,
constituye la unidad elemental en la planificación de
actividades del sistema. Un módulo es un subconjunto de una
tarea y no puede ser ejecutado autónomamente por el sistema
operativo. Si se opta por representar cada proceso mediante
una tarea (fig. 6a) se obtienen unos límites claros y
definidos, con lo que ello repercute en cuanto a claridad
conceptual, facilidad de mantenimiento y/o sustitución, y
posibilidades de prueba. Sin embargo esta solución presenta
Figura 5.a.- Una capa por proceso Figura 5.b.- Varias capas porproceso
DESARROLLO DE PROTOCOLOS Página 14
algunos inconvenientes entre los que cabe destacar:
Figura 6.a.- Un proceso por tarea
Figura 6.b.- Varios procesos por tarea
DESARROLLO DE PROTOCOLOS Página 15
- Sólo puede utilizarse cuando el sistema operativo es
multitarea.
- Mayor ocupación en memoria debido a que normalmente
cada tarea incorpora una determinada cantidad de código
de tamaño más o menos fijo para su interfaz con el
exterior.
- Fuerte sobrecarga del sistema ya que en un protocolo
es corriente que el control de la ejecución tenga que
estar contínuamente transfiriéndose de un proceso a
otro, por lo que, en esta solución, se transferiría el
control de una tarea a otra. El proceso de cambio de
tarea, denominado "context switching" (cambio de
contexto), involucra una serie de operaciones del
sistema operativo que consumen considerables recursos
del sistema si se realizan con frecuencia.
- Hay una cierta pérdida de control de la ejecución de
los procesos. En efecto, los criterios por los cuales
el sistema operativo transfiere control a una tarea
(proceso) está normalmente marcado por rígidas normas
que, en el mejor de los casos, permiten algún tipo de
parametrización (prioridad, porción de tiempo de CPU,
etc.). Sin embargo, la elección de un adecuado conjunto
de parámetros de ejecución es una tarea delicada que
además sólo permite un control muy reducido sobre los
procesos.
DESARROLLO DE PROTOCOLOS Página 16
La otra posibilidad consiste en identificar cada uno de
los procesos con un módulo, constituyendo todos ellos una
tarea única (fig. 6b). Esta solución es obligatoria cuando
no se dispone de un sistema operativo multitarea por lo que
el diseñador debe proporcionar un sistema de gestión de
módulos de acuerdo con las facilidades que le proporcione el
sistema operativo y el lenguaje de programación que utilice
(algunos lenguajes como el ADA facilitan en gran medida la
gestión de módulos). En general esta solución ocupa menos
espacio, es más rápida (al eliminar los "context
switchings") y permite un mejor control de la ejecución de
los procesos.
c) Comunicación entre capas. La comunicación de información
entre dos capas adyacentes puede ser de dos tipos: síncrona
o asíncrona. La comunicación asíncrona se basa en la
utilización de una o varias colas donde se introducen las
IDUs (Interface Data Units: Unidades de Datos de la
Interfaz). Dependiendo de los recursos de comunicación entre
procesos (tareas/módulos) que proporcionen el sistema
operativo y el lenguaje de programación, puede implementarse
con una cola (fig. 7a) o par de colas (fig. 7b) para el
conjunto de todas las conexiones entre las dos capas
(sistema monocola) o como un par de colas (fig. 7c) para
cada una de dichas conexiones (sistema multicola). El
sistema monocola en general tiende a ahorrar espacio de
almacenamiento y centraliza las funciones de gestión de
DESARROLLO DE PROTOCOLOS Página 17
colas, pero por el contrario necesita una lógica algo más
compleja que permita determinar a qué conexión entre capas
corresponde cada IDU.
La comunicación asíncrona entre capas presenta dos
dificultades. La primera de ellas es el control de flujo
entre capas, es decir, cómo hacer que una capa no genere, en
Figura 7.a.- Comunicación con una cola
Figura 7.b.- Comunicación con un par de colas
Figura 7.c.- Comunicación multicola
DESARROLLO DE PROTOCOLOS Página 18
promedio, más información de la que la otra capa es capaz de
digerir. Existen tres formas posible de abordar esta
cuestión:
- Si se utiliza una técnica multicola (cada conexión
emplea un par de colas), puede asignarse a estas colas
un tamaño fijo por lo que si una capa las encuentra
llenas deberá esperarse, adecuando de esta forma su
ritmo al del otro nivel.
- Si se utilizan colas ilimitadas, o bien la técnica
monocola, es necesario que existan primitivas
específicas de control de flujo entre capas deteniendo
o reanudando el flujo de información a través de la
interfaz para cada conexión concreta.
- Por último, una solución alternativa consiste en
hacer que la asignación de memoria para una IDU se
realice de alguna forma controlada por la capa que lo
va a recibir. De esta forma si dicha capa no se halla
preparada para procesarlo simplemente no concede la
memoria necesaria para almacenar el IDU, con lo que el
proceso en la capa de origen se detiene.
La segunda de las dificultades que surgen como
consecuencia de la comunicación asíncrona entre capas es el
denominado "ejecución atómica de eventos". Consiste en el
hecho de que una petición de servicios de una capa a otra,
realizada mediante la intercomunicación de una IDU, no se
realiza en el mismo momento que se introduce en la cola
DESARROLLO DE PROTOCOLOS Página 19
correspondiente, pues puede haber otras IDU en la cola que
se procesen con anterioridad. Para determinados servicios de
una capa este hecho puede ser significativo. Por ejemplo, de
acuerdo con muchas especificaciones, cuando una capa lanza
una solicitud de desconexión, la conexión deja de existir
inmediatamente, no enviándose ninguna PDU más. Sin embargo,
si la cola contiene alguna solicitud previa de envío de
PDUs, la solicitud de desconexión no se tendrá en cuenta, en
general, hasta después de haber enviado todas las PDUs de la
cola. La implementación no concuerda con la especificación.
Una posible solución a este problema está en la asignación
de prioridades en la cola de IDUs, con lo cual una solicitud
de desconexión de alta prioridad se atenderá antes que una
solicitud de envío de PDUs.
La comunicación síncrona entre capas es aquella que se
realiza sin el uso de colas de almacenamiento. Cuando una
capa desea pasar una información a otra capa detiene su
ejecución y le pasa el control junto con la IDU. El control
sólo le es devuelto una vez procesada completamente la
información. Aunque este enfoque evita los problemas de
control de flujo y ejecución atómica de eventos, que
existían en la comunicación asíncrona, introduce serias
dificultades de coordinación entre procesos, limitando el
grado de paralelismo en la ejecución de los mismos y
elevando el número de "context switchings" cuando los
procesos se representan mediante tareas.
DESARROLLO DE PROTOCOLOS Página 20
Tratamientode eventos
Orden de tratamiento FIFO Simple
Con prioridades
Aleatorio Simple
Con prioridades
Efecto de la lectura Permanece en la cola.Requiere:
Orden explícita de extracción
Lectura más compleja
Especificación de eventos adescartar
Desaparece de la cola.Requiere:
Almacenamiento interno deeventos
Especificación de eventosalmacenados
Representación deesperas
En tareas independientes: utiliza recursos del Sistema Operativo
En rutinas: necesitagestor de módulos
Simple
Transferencia cuando debe actuar
Transferencia de orden aleatorio
En corrutinas Necesita gestor de módulos Simple
Transferencia cuando debe actuar
Transferencia en orden aleatorio
No soportado en todos los lenguajes
Descripcióndetransiciones
En tablas (estado,evento, condición)
Más lento
Fácil generación automática
En código Selecciones (estado, evento, condición)
Selecciones (evento, estado, condición)
Representación detemporizadores
En proceso externo Con comprobación periódica
Con llamada al Sistema Operativo
En proceso interno
Tabla 2.- Representación de máquinas de estados
DESARROLLO DE PROTOCOLOS Página 21
2.2.- Representación de máquinas de estados.
Una vez analizados los problemas de la representación
de arquitecturas multicapas, donde pueden existir múltiples
tipos de máquinas de estados, es el momento de estudiar la
forma de representación de una máquina de estados. A este
respecto son 4 las cuestiones en las que conviene fijarse
(tabla 2): a) tratamiento de los eventos; b) representación
de las esperas; c) descripción de las transiciones; y d)
representación de los temporizadores (timers).
a) Tratamiento de los eventos. Los eventos de entrada a una
máquina provienen de otras máquinas o de la interfaz con el
exterior. En general estos eventos son almacenados en una
cola con disciplina de FIFO (First-In, First-Out: el primer
evento que entra es el primer evento que sale). De esta
forma al acceder a la cola se extrae siempre el más antiguo
(fig. 8a). Este criterio puede ser matizado, si el protocolo
lo requiere, por la existencia de un parámetro de prioridad,
de tal forma que se tome siempre en consideración el evento
más prioritario, sea o no el más antiguo (fig. 8b). A
cualquiera de los dos mecanismos anteriores puede añadirse
un factor de "no determinismo". Esto consiste en extraer de
la cola un evento por criterio aleatorio de entre los de
mayor prioridad (fig. 8c). El acceder aleatoriamente a la
cola tiene sobre todo interés en procesos de validación,
verificación, análisis de prestaciones y pruebas, ya que
DESARROLLO DE PROTOCOLOS Página 22
permite explorar combinaciones de eventos muy diversos.
Otro aspecto a considerar en el tratamiento de los
eventos es el efecto que tiene sobre la cola el proceso de
lectura. Una solución consiste en considerar que la lectura
de la cola da información del evento que hay que procesar
pero deja inalterada la cola (fig. 9a). De esta forma si la
máquina, por encontrarse en el estado adecuado, toma en
consideración el evento, debe realizarse una operación
explícita de extracción del evento de la cola. Si por el
Figura 8.a.- Tratamiento de eventos con FIFO
Figura 8.b.- Tratamiento de eventos con FIFO priorizado
Figura 8.c.- Tratamiento de eventos no determinista
DESARROLLO DE PROTOCOLOS Página 23
contrario la máquina no pudiese procesar el evento, éste
seguiría estando en la cola. La máquina debe entonces leer
de nuevo la cola para ver si existe algún otro evento que sí
pueda ser procesado. Por tanto, los eventos no tratados en
un estado son conservados. Este sistema requiere:
- una orden explícita de extracción de eventos de la
cola.
- un mecanismo más complejo de lectura de la cola.
- una especificación de los eventos que, por carecer de
sentido, deban ser descartados (borrados de la cola) en
cada uno de los estados.
Figura 9.a.- Lectura de eventos sin extracción
Figura 9.b.- Lectura de eventos con extracción
DESARROLLO DE PROTOCOLOS Página 24
La alternativa es que la lectura de la cola suponga
automáticamente la extracción del evento (fig. 9b). Si la
máquina no lo puede procesar en ese estado pero el evento no
debe ser rechazado es necesario que se proceda a un
almacenamiento temporal interno al módulo y a una posterior
consideración de los eventos almacenados. Por tanto, los
eventos no tratados en un estado son descartados. Este
sistema requiere:
- un mecanismo de gestión del almacenamiento interno de
eventos.
- una especificación de los eventos que, por no poder
ser tratados en un estado, deban ser almacenados
internamente.
b) Representación de las esperas. Existen principalmente
tres métodos de representar las situaciones de espera de las
máquinas de estados y el correspondiente control del ciclo
de ejecución de las mismas. La primera de estas formas es la
que se utiliza cuando cada máquina de estados se representa
mediante una tarea independiente, siendo el sistema
operativo el que controla la ejecución de las distintas
tareas (fig. 6a). En este caso la espera se ejecuta haciendo
uso de las facilidades del sistema operativo para suspender
la ejecución de tareas y reanudarlas cuando se produce algún
suceso predeterminado (expiración de un timer, activación de
un semáforo, etc.). En este caso la estructura, para un
ejemplo con dos máquinas, sería la siguiente:
main() /* Máquina A */
DESARROLLO DE PROTOCOLOS Página 25
{ for (;;) {
espera_a(); lee_evento_a(); procesa_evento_a(); }
}
main() /* Máquina B */{
for (;;) { espera_b(); lee_evento_b(); procesa_evento_b(); }
}
Cuando las máquinas se representan mediante rutinas de
una misma tarea (fig. 6b), y por tanto se necesita un gestor
de módulos específico, la espera de una máquina puede
representarse de dos formas. En primer lugar, puede
utilizarse una salida de la rutina que codifica el módulo y
la devolución del control al gestor de módulos. En este caso
la estructura sería esquemáticamente la siguiente:
main() /* Gestor de módulos */{ for (;;) {
maquina_a();maquina_b();
}}
maquina_a() /* Máquina A */{
lee_evento_a(); procesa_evento_a();
}
maquina_b() /* Máquina B */{
lee_evento_b(); procesa_evento_b();
DESARROLLO DE PROTOCOLOS Página 26
}
La versión aquí expuesta presenta un gestor de módulos
bastante simple. Algunas modificaciones comunes al gestor
incluyen:
- la transferencia de control a las máquinas únicamente
cuando tienen algún evento que tratar, y
- la activación de las máquinas en orden aleatorio,
principalmente en simulación.
Por último, y también cuando las máquinas se
representan mediante módulos de una tarea única, pueden
representarse las esperas mediante transferencias de control
entre corrutinas. Para ello, en primer lugar, el gestor de
módulos arranca todas y cada una de las máquinas de estados.
Este proceso es similar a la activación de una tarea desde
el sistema operativo. Para arrancar un módulo se reserva un
espacio en la pila (moviendo el Stack Pointer SP) y se llama
a la rutina que representa la máquina. Dicha rutina, al
igual que una tarea del sistema operativo, no debe finalizar
(devolver el control con un "return") sino que cuando quiera
realizar una espera, deberá transferir el control al gestor
de módulos mediante la técnica de corrutinas. Una vez
arrancadas todas las máquinas, el gestor de módulos les va
transfiriendo control también según la técnica de
corrutinas. La estructura genérica es la siguiente:
main() /* Gestor de módulos */{
DESARROLLO DE PROTOCOLOS Página 27
arranca_maquina_a(); arranca_maquina_b(); for (;;) {
corrutina_maquina_a();corrutina_maquina_b();
}}
maquina_a() /* Máquina A */{ for (;;) {
espera_a(); lee_evento_a(); procesa_evento_a();
}}
maquina_b() /* Máquina B */{ for (;;) {
espera_b(); lee_evento_b(); procesa_evento_b();
}}
arranca_maquina_a(){ reserva_espacio_pila(); valor=setjmp(punto_salto_gestor_modulos); if (valor==0) maquina_a();}
arranca_maquina_b(){ reserva_espacio_pila(); valor=setjmp(punto_salto_gestor_modulos); if (valor==0) maquina_b();}corrutina_maquina_a(){ valor=setjmp(punto_salto_gestor_modulos); if (valor==0) longjmp(punto_salto_maquina_a,1);}
corrutina_maquina_b(){ valor=setjmp(punto_salto_gestor_modulos); if (valor==0) longjmp(punto_salto_maquina_b,1);}
DESARROLLO DE PROTOCOLOS Página 28
espera_maquina_a(){ valor=setjmp(punto_salto_maquina_a); if (valor==0) longjmp(punto_salto_gestor_modulos,1);}
espera_maquina_b(){ valor=setjmp(punto_salto_maquina_b); if (valor==0) longjmp(punto_salto_gestor_modulos,1);}
La reserva de espacio en la pila es una condición
intrínseca al uso de corrutinas. En efecto, en la figura 10
puede observarse la distribución de la pila sin reserva de
espacio (fig. 10a), con los conflictos que acarrea, y con
reserva de espacio (fig. 10b). Es de notar también que las
funciones "setjmp" y "longjmp" con las que se han
implementado en C la técnica de corrutinas son estándares en
ANSI C y en UNIX, por los que pueden encontrarse en gran
número de plataformas y sistemas operativos. Otros lenguajes
proporcionan mecanismos similares. En algunos, sin embargo,
no es posible utilizar este recurso.
Figura 10.a.- Pila con corutinas si reserva de espacio
DESARROLLO DE PROTOCOLOS Página 29
c) Descripción de las transiciones. Las transiciones pueden
ser expresadas de dos formas diferentes: mediante tablas o
mediante código. En muchos documentos de especificación de
protocolos se expresan las máquinas de estados mediante
tablas (estado, evento, condición). Para cada posible
combinación de estado y evento, y para cada condición
adicional que se imponga, se expresan tabularmente las
acciones a realizar y el próximo estado. La otra forma de
representar las transiciones es expresarlas directamente en
el código usando sentencias anidadas de selección ("switch"
en C o similares). Estas sentencias pueden tomar una
estructura de (estado, evento, condición), o bien de
(evento, estado, condición). Con este sistema el protocolo
se ejecuta a mayor velocidad, ya que no hay que ir
interpretando las tablas de transiciones. Por el contrario
el método de tablas es muy adecuado cuando se realiza una
generación automática de código.
Figura 10.b.- Pila con corrutina y reserva de espacio
DESARROLLO DE PROTOCOLOS Página 30
Una cuestión que debe tenerse en cuenta, sea cual sea el
método de representación de transiciones elegido, es la
necesidad de representar también el proceso o la transición
de inicialización de la máquina de estados.
La mejor forma de entender cada una de estas opciones
es mediante un ejemplo simple. Supongamos para ello que se
desea construir un juego que dispone de dos luces, roja y
verde, y dos pulsadores, también rojo y verde. Cada segundo,
se enciende aleatoriamente una de las lámparas. Si el
jugador pulsa el botón del mismo color que la lámpara, ésta
se apaga. Transcurrido un segundo se vuelve a encender
aleatoriamente otra (o la misma) lámpara. El juego termina
cuando, por descuido del jugador, se encienden las dos
lámparas, momento en el cual una nueva pulsación no apaga
ninguna lámpara. Este sencillo juego puede modelarse de
acuerdo con la máquina de estados de la figura 11. La misma
máquina se expresa de forma tabular en la figura 12. Los
programas anexos "maqsimp1", "maqsimp2" y "maqsimp3"
presentan las tres alternativas de representación de
transiciones aplicados a ese ejemplo. Por último el programa
anexo "maqsimp" es una simulación ejecutable del mencionado
juego.
DESARROLLO DE PROTOCOLOS Página 31
Figura 11.- Máquina de estados de un juego simple
DESARROLLO DE PROTOCOLOS Página 32
d) Representación de los temporizadores (timers). Uno de los
elementos presentes con gran frecuencia en las máquinas de
estados que describen protocolos de comunicaciones es el de
los temporizadores o timers. Si bien son similares a otros
eventos, su tratamiento presenta algunas singularidades que
conviene señalar. Fundamentalmente son dos las formas de
abordar la representación de los temporizadores:
- mediante proceso externo al módulo, o
- mediante proceso interno al módulo.
En el primero de estos enfoques (fig. 13a) la
activación o cancelación de un timer se traducen en una
actuación de la máquina de estados hacia un proceso externo.
En este proceso externo el temporizador puede ser tratado de
dos formas:
Figura 12.- Tablas de estado de un juego simple
DESARROLLO DE PROTOCOLOS Página 33
1) Una posibilidad consiste en almacenar el instante en
el que expira el timer e irlo comparando periódicamente
(en cada activación del proceso de gestión de timers)
con el valor del reloj (real o simulado). Una vez
superado el plazo impuesto por el timer se genera el
evento correspondiente en la máquina original.
2) La segunda posibilidad de tratamiento del timer en
el proceso externo es solicitar al sistema operativo
que avise (active el proceso de gestión de timers)
cuando expire el evento. Una vez que esto ocurre se
genera el evento correspondiente en la máquina
original.
Figura 13.a.- Gestión externa de timers
DESARROLLO DE PROTOCOLOS Página 34
La representación de los temporizadores mediante
proceso interno al módulo (fig. 13b) supone que la cola (o
colas) de entrada de eventos a la máquina tiene un parámetro
que indica la "hora de entrada en vigor" de cada evento. De
esta forma un evento normal tendrá hora de entrada en vigor
igual a cero o igual a la hora en la que se generó, con lo
cual puede ser procesado inmediatamente. Por el contrario la
activación de un timer supone la inclusión en la cola de un
evento de expiración del timer con una hora de entrada en
vigor igual a la hora actual más la duración del timer.
Hasta que el reloj (real o simulado) no supera la hora de
entrada en vigor de un evento, éste no es tomado en
consideración. El proceso de la cancelación de un timer
supone la extracción del evento de la cola.
Figura 13.b.- Gestión interna de timers
DESARROLLO DE PROTOCOLOS Página 35
2.3.- Representación de máquinas múltiples.
Una cuestión que se plantea frecuentemente en el
desarrollo de protocolos es que, si bien el funcionamiento
de una determinada capa es descrita mediante una única
máquina de estados, dicha máquina se halla múltiples veces,
en estados y con valores de variables distintas,
correspondiendo con la existencia de múltiples usuarios. Por
ejemplo un protocolo de transferencia de ficheros puede ser
descrito por una máquina única, también denominada máquina-
tipo. Pero si varios programas de un sistema multitarea
intentan utilizar simultáneamente las facilidades
proporcionadas por el protocolo, cada uno de ellos necesita
para sí una copia de la máquina de estado, la máquina-
individual, que se encuentra en un estado absolutamente
independiente del estado de las demás máquinas-individuales.
La representación de estas máquinas múltiples puede
realizarse recurriendo, principalmente, a dos métodos (tabla
3):
- mediante procesos independientes.
- mediante un proceso único.
En el primero de estos enfoques (fig. 14) cada una de
las máquinas individuales se plasma mediante un proceso
(tarea independiente o módulo de un programa). La variable
que representa el estado de la máquina, así como el resto de
DESARROLLO DE PROTOCOLOS Página 36
las variables usadas por la maquina deben ser locales al
proceso, mientras que el código es repetido tantas veces
como máquinas-individuales existan. Esta solución implica
una ocupación de espacio relativamente alta.
Procesos independientes Ocupación de memoria alta
Fácil codificación (no hay índices)
Alta velocidad (no hay índices)
Fácil generación automática
Proceso único Variables indexadas Ocupación baja de memoria
Menor velocidad (manejo de índices)
Codificación engorrosa (índices)
Corrutinas Ocupación baja de memoria
Fácil codificación (no hay índices)
Alta velocidad (no hay índices)
Fácil generación automática
Tabla 3.- Representación máquinas múltiples
Si se opta por representar las múltiples máquinas
iguales mediante un único proceso (fig. 15) todas las
máquinas individuales comparten el mismo código, aunque sus
variables son independientes. Esto se traduce en una
importante reducción de espacio. El aspecto más conflictivo
radica en que un código único maneje variables diferentes.
Para ello caben fundamentalmente dos soluciones:
- el uso de variables indexadas, o
- el uso de corrutinas.
La primera de estas alternativas implica que todas las
variables de la máquina sean indexadas, tanto si se definen
como variables globales o como variables locales al proceso.
El índice de las variables hace referencia a la máquina
individual a la que corresponde. Cuando se transfiere
DESARROLLO DE PROTOCOLOS Página 37
control a la máquina-tipo se hace con la indicación de la
máquina individual que se desea ejecutar, lo que se traduce
en el uso de un determinado índice para las variables. Esta
técnica hace la codificación de la máquina más engorrosa y
su ejecución algo más lenta.
Figura 14.a.- Una máquina individual por tarea
Figura 14.b.- Una máquina individual por módulo
DESARROLLO DE PROTOCOLOS Página 38
La segunda alternativa implica el uso de corrutinas
para la codificación de las máquinas-tipo. Si las variables
usadas por la corrutina son de tipo local, éstas estarán
Figura 15.a.- Varias máquinas individuales por tarea
Figura 15.b.- Varias máquinas individuales por módulo
DESARROLLO DE PROTOCOLOS Página 39
ubicadas en la pila.
Si se reservan espacios de pila diferentes para las
variables de cada una de las máquinas individuales de una
máquina-tipo (fig. 16), la ejecución de una máquina
individual se puede efectuar utilizando un único código
correspondiente a la máquina-tipo, el cual utiliza las
Figura 16.- Pila con corutinas y maquinas múltiples
DESARROLLO DE PROTOCOLOS Página 40
variables de la máquina individual pertinente mediante el
uso de la zona adecuada de la pila. Las variables, por
tanto, no tienen que estar indexadas con lo que se
simplifica el código, se aumenta la velocidad de ejecución y
se facilita la generación automática. El programa anexo
"gestor" realiza una implementación de máquinas múltiples
mediante corrutinas, con pequeñas variantes de tipo práctico
con respecto a lo mencionado en estas líneas (ver
comentarios del propio programa).
DESARROLLO DE PROTOCOLOS Página 41
2.4.- Tratamiento de cabeceras y colas.
En un determinado nivel, digamos nivel N, la
información que circula, denominada (N)-PDU, está
constituida, en general, por datos del nivel (N+1) y por una
información de control denominada técnicamente (N)-PCI, y en
forma más coloquial cabeceras y/o colas. El tratamiento de
estas cabeceras y colas supone, en general, un esfuerzo de
cálculo bastante considerable, principalmente para el
receptor, por lo que habrá de estudiarse y optimizarse al
máximo. Entre estas técnicas de optimización destacan:
- El uso de cabeceras y colas precalculadas, de forma
que el cómputo efectivo se reduce al mínimo.
- La anticipación y predicción de los posibles valores
de la cabecera y la cola de la próxima PDU, lo que
permite menores tiempos de respuesta y mayor velocidad
de proceso.
- La comprobación temprana de la consistencia y
contenido de la cabecera y la cola de una PDU, evitando
desperdiciar tiempo de CPU en procesar una PDU que será
descartado más adelante.
- El uso de hardware específico para tratamiento
(construcción y deconstrucción) de cabeceras y colas.
DESARROLLO DE PROTOCOLOS Página 42
2.5.- Relación entre SDUs y PDUs.
Las relaciones entre las unidades de datos del servicio
(SDUs) y las unidades de datos del protocolo (PDUs) para
cada una de las capas, pueden ser de tipo simple como las
recogidas en la figura 17. Sin embargo existen también tres
tipos de relación compleja entre SDUs y PDUs, que son los
siguientes (fig. 18):
Figura 17.- Relaciones simples entre SDUs y PDUs
DESARROLLO DE PROTOCOLOS Página 43
- Concatenación/separación en la que varias (N)-PDUs
constituyen una única (N-1)-SDU.
- Empaquetamiento/desempaquetamiento, situación en la
que varias (N)-SDUs constituyen una única (N)-PDU.
- Segmentación/reunificación en la que un único (N)-SDU
es enviado utilizando varios (N)-PDUs.
En general la concatenación y el empaquetamiento
mejoran las prestaciones (la eficiencia) aunque, en
determinadas circunstancias pueden aumentar en alguna medida
el retardo. Sin embargo la segmentación puede reducir
significativamente la eficiencia sin mejorar para nada el
retardo. Adicionalmente la segmentación complica
considerablemente la gestión de la memoria (buffers). Por
ello la segmentación debe reducirse al mínimo indispensable.
Figura 18.- Relaciones complejas entre SDUs y PDUs
DESARROLLO DE PROTOCOLOS Página 44
Una solución obvia es prohibirla, reduciendo con ello los
servicios suministrados por el protocolo. Una alternativa
mucho menos drástica es limitar el uso de la segmentación a
una única capa, con lo cual se limitan los inconvenientes
que plantea.
2.6.- Gestión de memoria.
El flujo de información de una capa a otra que se
produce en todo protocolo de comunicaciones, supone una
importante actividad de gestión de memoria en la que SDUs,
PDUs, IDUs, colas entre capas, variables locales y un largo
etcétera de unidades de información deben ser almacenadas
temporalmente en alguna parte de la memoria. Ello implica la
reserva de una zona de memoria del tamaño adecuado, la
coordinación con el resto de zonas de memoria, la
transferencia de información entre esta zona y el exterior o
entre dos zona distintas y, por último, la liberación de las
zonas una vez que su uso ya no es necesario. Esta gestión de
la memoria de almacenamiento temporal implica pues algunas
cuestiones interesantes, entre las que cabe destacar las 5
siguientes (tabla 4): a) ubicación de las zonas de
almacenamiento; b) gestor de la memoria; c) paso de PDU's
entre capas; d) almacenamiento de PDU's; y e) liberación de
la memoria.
DESARROLLO DE PROTOCOLOS Página 45
a) Ubicación de las zonas de almacenamiento. Para esta
cuestión se cuenta principalmente con dos alternativas: una
única zona global o una zona diferente para cada capa. El
uso de una única zona de memoria en la cual se almacena la
información de todas las capas del protocolo presenta
algunas peculiaridades. En primer lugar su uso sólo puede
realizarse, obviamente, cuando la estructura dada a las
capas (procesos, tareas, módulos, ...) y el contexto en el
que se enmarca (lenguaje, sistema operativo, plataforma,
...) permiten la definición de zonas comunes de memoria.
Cuando se utiliza esta técnica, en general se aprovecha
mejor el espacio, pues el tamaño de la zona manejada tiene
las reservas propias de la situación global más
desfavorable, mientras que si el almacenamiento es local en
cada capa las reservas deben ser dimensionadas al caso más
desfavorable de dicha capa, lo cual en conjunto suele ser
mayor que una reserva global. Por último, si se usa una zona
global debe ponerse especial cuidado en evitar que las
solicitudes de una capa o capas agoten rapidamente la
capacidad de almacenamiento, dejando sin servicio a otras
capas con poca información almacenada. Esto podría incluso
ocasionar un bloqueo del protocolo, al impedir que actúen
por saturación de la memoria las capas encargadas
precisamente de liberar esa memoria.
Si por el contrario se usan zonas locales en cada capa
se evitan los posibles problemas de bloqueo y se consigue
DESARROLLO DE PROTOCOLOS Página 46
una mayor independencia entre capas. El precio a pagar es la
necesidad de reservar mayores espacios para almacenamiento y
la pérdida de la gestión centralizada de la memoria.
Ubicación zonas dealmacenamiento
Una zona global Necesita acceso a zona común
Optimiza espacio (menores reservas)
Gestión centralizada
Cuidar asignación equitativa entre capas
Posibles bloqueos
Una zona por capa Mayor independencia entre capas
Evita problemas de asignación y bloqueos
Gestor de memoria Sistema Operativo Codificación más simple
Cuidado con asignación dinámica
Proceso específico: Mayor control
Pase de PDUs entre capas Por copia Simplicidad
Fuerte sobrecarga de proceso
Por referencia Técnica del impreso
Técnica de dispersión-recopilación
Características Gestión más compleja
Mayor velocidad
Almacenamiento de PDUs Contiguas Simplicidad del gestor
Poco aprovechamiento de la memoria
Eventual necesidad de compactación
No contiguas Gestor más complejo
Buen aprovechamiento de la memoria
Liberación de la memoria Donde Con paso de PDUs por copia: En la capa que recibela PDU
Con paso de PDUs porreferencia
En la capa más baja(transmisión)
En la capa más alta(recepción)
Peligros Aspecto siempre delicado
Retransmisión de PDUs
Fallos que descartan PDUs
Tabla 4.- Gestión de memoria
b) Gestor de la memoria. Se pueden plantear fundamentalmente
dos alternativas en cuanto al encargado de gestionar la zona
DESARROLLO DE PROTOCOLOS Página 47
o zonas de almacenamiento: el sistema operativo o un proceso
específico. El uso de las facilidades de gestión de memoria
que proporcionan el sistema operativo y el lenguaje de
programación que se use se traduce normalmente en una
codificación más simple, pues delega este tipo de funciones,
pero por el contrario redunda en una cierta pérdida de
control. Habrá que tener especial cuidado si además, como
suele ocurrir, estas zonas no tienen reservado un tamaño
fijo, sino que el sistema operativo las toma de las zonas
libres que dinámicamente vaya dejando el propio programa.
Esto en ocasiones puede provocar saturaciones prematuras si
la zona dinámica se ha quedado muy pequeña por el uso que de
ellan han hecho otros módulos del programa, y no
necesariamente de comunicaciones. Si por el contrario se
decide usar un proceso (tarea o módulo) específico para la
gestión de la memoria mejoramos el control de la misma y
evitamos los problemas de la gestión dinámica.
c) Paso de PDU's entre capas. Tres son las formas de
realizar la transferencia de una PDU de una capa a otra: por
copia, mediante la técnica de "impreso" y mediante la
técnica de "dispersión-recopilación". Evidentemente la forma
más directa de realizarlo es mediante copia, es decir, el
nivel N copia en un almacenamiento propio la PDU ofrecida
por el nivel (N+1), añadiéndole la cabecera y cola
correspondiente (fig. 19). Sin embargo esta continua copia
de datos supone, en general, una fuerte sobrecarga que está
DESARROLLO DE PROTOCOLOS Página 48
enérgicamente desaconsejada, y sólo se justifica por la
sencillez de la implementación. En la técnica del impreso
(fig. 20), el nivel más alto que genera una PDU (normalmente
el de aplicación, a excepción de las PDUs de control)
reserva un espacio suficientemente amplio como para albergar
a la SDU y al conjunto de las cabeceras y colas de los
niveles inferiores. De esta forma un nivel le pasa al
siguiente la PDU como un puntero a la zona del impreso donde
comienza la información pertinente a dicho nivel. Por
último, la técnica de dispersión-recopilación utiliza zonas
de memoria no contigua para almacenar cada una de las
distintas partes de una PDU, utilizando para unirlas
punteros de unas zonas a otras (fig. 21a) o una lista
externa de punteros (fig. 21b). Para poder reconstruir la
PDU original a partir de las diferentes porciones es a veces
conveniente incluir junto al puntero la longitud de la zona
referenciada. Las técnicas de paso de PDUs por referencia
proporcionan una mayor velocidad de ejecución al precio de
una gestión más compleja del paso de PDUs entre capas.
d) Almacenamiento de PDU's. La técnica más simple para
almacenar los PDUs consiste en ubicarlas, sin ninguna
transformación, en la zona de memoria correspondiente. Sin
embargo si, como es frecuente, las PDUs pueden ser de
tamaños muy diferentes, la gestión del almacenamiento se
hace compleja si no se quieren desaprovechar los huecos que
dejan vacantes las PDUs después de ser extraidas de la
DESARROLLO DE PROTOCOLOS Página 49
memoria. Este problema, similar al que se produce en la
gestión del espacio de un disco magnético donde se almacenan
y borran ficheros, puede solucionarse bien con una
compactación periódica de la memoria, con la consiguiente
sobrecarga de proceso, o bien mediante la división de la PDU
en trozos de tamaño fijo (relativamente pequeños) y el
almacenamiento de dichos trozos en zonas no contiguas de
memoria. Esta solución, si bien supone un gestor de memoria
más elaborado, optimiza el uso del almacenamiento disponible
con escaso consumo de CPU.
Figura 21.a.- Paso de PDUs por punteros
DESARROLLO DE PROTOCOLOS Página 50
e) Liberación de memoria. Tan importante como la reserva de
la memoria para el almacenamiento de PDUs es la liberación
de la misma cuando ya no resulta necesario su uso. Una
fuente de errores frecuente en el desarrollo de protocolos
radica en una inadecuada liberación de la memoria ocupada.
Por tanto a esta cuestión debe dedicársele un especial
cuidado. Si el paso de PDUs entre capas se realiza mediante
copia la liberación de la memoria correspondiente debe
realizarse, en general, en la capa que recibe el PDU. Si por
el contrario, el paso de PDUs se realiza por referencia
(técnicas de impreso o de dispersión-recopilación), la
memoria deberá ser liberada normalmente en la capa más alta
a la que vaya destinado la PDU (en recepción) o a la capa
inferior del protocolo (en transmisión). No obstante deben
considerarse con precaución dos situaciones especiales. La
Figura 21.b.- Paso de PDUs por lista de punteros
DESARROLLO DE PROTOCOLOS Página 51
primera es aquella situación que se presenta cuando, por
haber ocurrido algún error, un mensaje debe ser transmitido
de nuevo. Un segundo caso, también en presencia de fallos de
comunicación, es la necesidad de descartar una PDU. En estas
situaciones deberá estudiarse con cautela cuales son las
capas encargadas del almacenamiento temporal y de la
liberación de PDUs que pueden ser retransmitidas, tanto en
caso de fallo como en la ausencia del mismo. Igualmente
deberá considerarse con cuidado la capa o capas que
liberarán las PDUs que han sido descartadas por fallo de
transmisión.
DESARROLLO DE PROTOCOLOS Página 52
2.7.- Manejo del hardware de comunicaciones.
Toda implementación de un protocolo tiene que afrontar,
antes o después, el problema del hardware específico de
comunicaciones. Más aún, cuando se habla de estos
dispositivos no debe pensarse tan sólo en un hardware que se
limite a cumplir las funciones del nivel físico, sino que,
cada vez con más frecuencia, puede encontrarse hardware de
comunicaciones desempeñando tareas correspondientes a capas
intermedias del modelo OSI (típicamente niveles de enlace de
datos y de red).
El manejo del hardware de comunicaciones puede
abordarse principalmente desde dos perspectivas (tabla 5):
mediante gestión directa o usando las facilidades del
sistema operativo. La gestión directa del hardware de
comunicaciones puede realizarse, suponiendo que el
dispositivo lo permita, mediante alguna de las siguientes
tres técnicas: por exploración, por interrupción o por DMA.
La técnica de exploración consiste en la comprobación
cíclica del estado en el que se encuentran las etapas de
transmisión y recepción del dispositivo. Cuando el estado es
el adecuado se le inyecta una información a transmitir o se
le retira una información recibida. Aunque es un sistema muy
sencillo es ineficaz, supone una fuerte carga de proceso y
requiere una atención continua, so pena de desbordamiento de
las colas internas del dispositivo.
DESARROLLO DE PROTOCOLOS Página 53
Gestión directa Exploración Ineficaz
Fuerte sobrecarga
Posible pérdida de información
Interrupciones Sistema eficaz
Codificación compleja
DMA Sin interrupciones
Con interrupciones
Usando sistema operativo Sin activación asíncrona
Con activación asíncrona
Características Codificación simple
Mayor coordinación con otrastareas
Mayores tiempos de respuesta
Sobrecarga
Tabla 5.- Manejo del hardware de comunicaciones
Usando la técnica de interrupciones el dispositivo
avisa cuando está listo para transmitir una nueva
información o cuando se ha recibido por la línea algún dato.
Este sistema es en general mucho más adecuado pues no
sobrecarga la CPU y garantiza que no se pierde información,
siempre que la interrupción sea atendida con prontitud. Sin
embargo hay que considerar también que el manejo de
interrupciones es, normalmente, una tarea de una cierta
complejidad.
Por último, si el dispositivo está preparado para ello,
se puede realizar la gestión mediante acceso directo a
memoria (DMA). En esta alternativa la transmisión o
recepción de información se realiza sin el concurso de la
CPU principal, cuyo único cometido está en la iniciación del
proceso y en la recogida de resultados. La finalización del
DESARROLLO DE PROTOCOLOS Página 54
DMA puede detectarse por exploración cíclica o, lo que es
más frecuente, por interrupciones. Cuando el dispositivo
proporciona esta posibilidad, el DMA con interrupciones es,
sin duda, el más eficiente de los sistemas de manejo del
hardware.
Como contrapunto al manejo directo de los dispositivos
de comunicaciones, se puede considerar el uso del sistema
operativo para estas funciones. Una de las tareas típicas de
los sistemas operativos ha sido siempre la gestión de los
dispositivos periféricos para facilitar y coordinar el uso
que de los mismos realizan las distintas tareas. Normalmente
la parte del sistema operativo que realiza la gestión de un
dispositivo concreto recibe el nombre de manipulador
(driver). A ellos se accede mediante la invocación de
primitivas específicas del sistema operativo que permiten la
transmisión y recepción de información. Algunas de estas
primitivas permiten una activación asíncrona de alguno de
los módulos de la tarea que los invocó, permitiendo de esta
forma una más eficaz gestión de las transferencias de
información. El uso del sistema operativo para el manejo de
los dispositivos de comunicaciones supone por un lado una
mayor simplicidad de código y una mejor coordinación con el
resto de tareas, pero por el contrario hay una cierta
pérdida de control, los tiempos de respuesta son más
elevados y hay una cierta sobrecarga debido a la gestión
introducida por el sistema operativo.
DESARROLLO DE PROTOCOLOS Página 55
2.8.- Interfaz con el exterior.
El protocolo de comunicaciones no sólo debe comunicarse
con otros computadores a través de los canales adecuados,
sino que también deberá interrelacionarse con otros procesos
(tareas o módulos) que hagan uso de sus servicios. La forma
que adopte esta interfaz con el exterior dependerá en gran
medida de dónde se ubique el protocolo, pudiendo
distinguirse principalmente dos alternativas (tabla 6): en
un frontal de comunicaciones o en el mismo procesador que el
resto de los procesos. Si se utiliza un frontal de
comunicaciones (o "front-end") la relación con otros
procesos será mediante los mecanismos de comunicación que se
establezcan entre los procesadores respectivos. Dicha
comunicación estará normalmente basada en un protocolo
simple para canales punto a punto, con pocos errores y alta
velocidad. La tarea del frontal será, en este caso, la de
preprocesar la información y manejar los protocolos más
complejos que puedan estar presentes, liberando al
procesador principal de las tareas más pesadas de la
comunicación. La intercomunicación entre el frontal y el
procesador principal suele realizarse por alguno de los
siguientes métodos: por BUS (fig. 22a), normalmente con
acceso directo a memoria (DMA); por red local y procesador
específico en BUS con DMA (fig. 22b); y por línea de alta
velocidad, con o sin procesador específico (fig. 22c).
DESARROLLO DE PROTOCOLOS Página 56
Ubicación En un front-end Por BUS (DMA)
Por red local
Por línea de alta velocidad
En el mismo procesador En el sistema operativo: pordirectivas del Sistema Operativoy zonas globales de memoria
En tareas independientes.:Mediante comunicación entretareas del Sistema Operativo yzonas globales de memoria
En la misma tarea (librería):Mediante llamadas a módulos yzonas comunes de memoria
Tabla 6.- Interfaz con el exterior
Figura 22.a.- Frontal conectado al BUS
Figura 22.b.- Frontal conectado en red local
DESARROLLO DE PROTOCOLOS Página 57
Si se ubica en el mismo procesador que el resto de los
procesos se plantean, a su vez, tres posibles ubicaciones.
En primer lugar cabe que el protocolo se desarrollo como una
parte del sistema operativo, bien porque el desarrolle lo
realice el propio fabricante, o bien porque nos encontremos
ante sistemas operativos que pueden ser ampliados por el
usuario. En este caso el interfaz entre el protocolo y los
demás procesos se realizará mediante el manejo de las
primitivas del propio sistema operativo y, eventualmente, el
uso de zonas globales de memoria. Una segunda alternativa,
en sistemas multitarea, consiste en desarrollar el protocolo
como una o varias tareas autónomas, realizándose en este
caso la interfaz mediante las facilidades de comunicación
entre tareas que proporcione el sistema operativo y,
Figura 22.c.- Frontal conectado por línea de alta velocidad
DESARROLLO DE PROTOCOLOS Página 58
eventualmente también, a través de zonas globales de
memoria. Por último el protocolo puede tomar la forma de una
librería de módulos que se integran en una tarea mediante
llamada siguiendo las reglas del lenguaje de programación
elegido y utilizando zonas de memoria internas a la propia
tarea (aunque puedan estar compartidas entre varios
módulos). La decisión de usar una u otra alternativa depende
en general de consideraciones de diseño globales de la
aplicación y no, tan sólo, del protocolo de comunicaciones.
ANEXOS
DESARROLLO DE PROTOCOLOS Página 60
ANEXOS
Programa GESCOR.C.
Programa ejemplo de gestión de máquinas con corrutinas.
#include <stdio.h>/* Estos include son necesarios para los */#include <setjmp.h>/* procesos de gestión de corrutinas */
main() /* El main tan sólo sirve para dar soporte a la rutina gestor */{ printf("\n\n\n"); gestor_modulos();}
jmp_buf punto_salto_maquina_a;jmp_buf punto_salto_maquina_b;jmp_buf punto_salto_gestor_modulos;
gestor_modulos(){ arranca_maquina_a(100); arranca_maquina_b(100); for (;;) { corrutina_maquina_a(); corrutina_maquina_b(); }}
maquina_a(){ int valor,paso;
printf("Activada máquina A\n"); paso=0; for (;;) { valor=setjmp(punto_salto_maquina_a); if (valor==0) longjmp(punto_salto_gestor_modulos,1); printf("Máquina A. Paso %d\n",++paso); } /* Fin del bucle infinito */} /* Fin del módulo A */
arranca_maquina_a(reserva)int reserva;{ int valor;
reserva--; if (reserva<=0)
DESARROLLO DE PROTOCOLOS Página 61
{ valor=setjmp(punto_salto_gestor_modulos); if (valor==0) maquina_a(); } else arranca_maquina_a(reserva); return;}
corrutina_maquina_a(){ int valor;
valor=setjmp(punto_salto_gestor_modulos); if (valor==0) longjmp(punto_salto_maquina_a,1); return;}
maquina_b(){ int valor,paso;
printf("Activada máquina B\n"); paso=0; for (;;) { valor=setjmp(punto_salto_maquina_b); if (valor==0) longjmp(punto_salto_gestor_modulos,1); printf("Máquina B. Paso %d\n",++paso); } /* Fin del bucle infinito */} /* Fin del módulo B */
arranca_maquina_b(reserva)int reserva;{ int valor;
reserva--; if (reserva<=0) { valor=setjmp(punto_salto_gestor_modulos); if (valor==0) maquina_b(); } else arranca_maquina_b(reserva); return;}
corrutina_maquina_b(){ int valor;
valor=setjmp(punto_salto_gestor_modulos); if (valor==0) longjmp(punto_salto_maquina_b,1); return;}
Programa GESTOR.
DESARROLLO DE PROTOCOLOS Página 62
Programa ejemplo de gestión de corrutinas recursivas.
#include <stdio.h> /* Estos include son necesarios para los */#include <setjmp.h> /* procesos de gestión de corrutinas */
#include <stdlib.h> /* Estos include son necesarios */#include <time.h> /* para los procesos de aleatorización */
main() /* El main tan sólo sirve para dar soporte a la rutina gestor */{ randomize(); printf("\n\n\n"); gestor();}
#define n_tareas_modulo_A 3 /* Estos define deben ser generados de forma*/#define n_tareas_modulo_B 2 /*automática por el generador de código C */#define n_total_tareas 5 /*y coinciden con el número de máquinas
individuales de cada módulo y con elnúmero total de ellas */
#define RESERVA_STACK 100 /* Este define marca el número de vecesque se activa recursivamente la rutina dellamada de módulos antes de llamardefinitavemente al módulo. Es una medidaindirecta de la cantidad de espacioreservado en el stack para la llamada asubrutinas de cada máquina individual */
/* Cada módulo genera un conjunto de instruciones como las siguientes.Su objetivo es establecer los puntos de ida y vuelta paracada una de las máquinas individuales del módulo */
/***** Variables correspondientes al módulo A ******/
jmp_buf salto_ida_modulo_A [n_tareas_modulo_A];jmp_buf salto_vuelta_modulo_A;int arrancando_modulo_A=1; /* Utilizado en el proceso de arranque
del módulo */
/**** Variables correspondientes al módulo B *****/
jmp_buf salto_ida_modulo_B [n_tareas_modulo_B];jmp_buf salto_vuelta_modulo_B;int arrancando_modulo_B=1;
/**** Variables correspondientes al mmi *****/
jmp_buf salto_ida_mmi;jmp_buf salto_vuelta_mmi;
/****************************************************************** Rutina que realiza la gestión de todas las tareas.** En una primera fase inicializa todas las tareas
DESARROLLO DE PROTOCOLOS Página 63
* reservando espacio de stack para ellas y esbleciendo* los puntos de salto a las mismas. La activación de* estas rutinas se hace en forma de corrutinas que,* por tanto, están siempre activas. La llamada a cada* una de las tareas de cada uno de los módulos se hace* recursivamente.** En una segunda fase realiza un bucle infinito para* dar control en orden aleatorio a cada una de las tareas* que componen la especificación.** Adicionalmente deberían incluirse en el proceso de* inicilización como corrutinas y de cesión de control* en cada uno de los pasos del bucle infinito, todas* aquellas rutinas de simulación, almacenamiento,* presentación, y control general de la ejecución de* de la especificación que fuesen necesarias. El orden* de estas rutinas no tiene por que ser aleatorio sino* que normalmente se hará su activación en un orden* determinado al final de la activación de todas las* máquinas individuales.**********************************************************************/
gestor(){ int i,valor; int modulo,tarea;
/**** Fase de inicialización *********/
valor=setjmp(salto_vuelta_modulo_A); /* Establece punto de vuelta de la corrutina */
if (valor==0) llamada_modulo_A(0); /* Activa la primera tarea delprimer módulo */
/***** Fase de cesión cíclico de control ********/
while(1) { for (i=0;i<n_total_tareas;i++) { decide_tarea_aleatoriamente(i,&modulo,&tarea); switch (modulo) { case 0:
valor=setjmp(salto_vuelta_modulo_A); /* Punto de vuelta de lacorrutina */
if (valor==0) longjmp(salto_ida_modulo_A[tarea],1); /* Va a la corrutina
correspondiente a latarea calculada delmódulo A */
break;
case 1:valor=setjmp(salto_vuelta_modulo_B);if (valor==0) longjmp(salto_ida_modulo_B[tarea],1);break;
} /* Fin del switch */ } /* Fin del bucle de llamada a todas las máquinas individuales */
valor=setjmp(salto_vuelta_mmi); if (valor==0) longjmp(salto_ida_mmi,1);
printf("\n");
DESARROLLO DE PROTOCOLOS Página 64
}
return;}
/****************************************************************** Esta rutina calcula aleatoriamente el módulo y la tarea* a los que debe cederseles el control.** Para ello calcula unas tablas aleatorias que son inicializadas* cada vez que se ejecuta un paso en todas las máquinas* individuales.*****************************************************************/
decide_tarea_aleatoriamente(vez,modulo,tarea)int vez,*modulo,*tarea;{ static int tabla_modulo[n_total_tareas]={0,0,0,1,1}; /* Las constantes*/ static int tabla_tarea[n_total_tareas] ={0,1,2,0,1}; /* son obtenidaspor
el generador decódigo C */
static int tabla_modulo_elegido[n_total_tareas]; static int tabla_tarea_elegida[n_total_tareas]; static int tarea_elegida[n_total_tareas];
int i,j,n,contador;
/***** La primera activación en cada paso dispara el cálculo delas tablas aleatorias *****/
if (vez==0) { for (i=0;i<n_total_tareas;i++) tarea_elegida[i]=0; for (i=0;i<n_total_tareas;i++) { n=random(n_total_tareas-i); contador=-1; for (j=0;j<n_total_tareas;j++) {
if (tarea_elegida[j]==0) contador++;if (n==contador){ tarea_elegida[j]=1; tabla_modulo_elegido[i]=tabla_modulo[j]; tabla_tarea_elegida[i]=tabla_tarea[j]; break;}
} /* Fin de la busqueda de la tarea elegida */ } /* Fin del for i */ } /* Fin del calculo de las tablas aleatorias */
/***** Determinación del módulo y tarea basado en las tablas *****/
*modulo=tabla_modulo_elegido[vez]; *tarea=tabla_tarea_elegida[vez]; return;}
/********************************************************************* Módulo A correspondiente al cuerpo de un módulo en ESTELLE*
DESARROLLO DE PROTOCOLOS Página 65
* Aquí figurarán las transiciones, actuaciones y, en* general todos aquellos procesos necesarios para la* ejecución de una máquina individual.** La rutina tiene una primera fase de inicialización* donde va activando recursivamente todas las máquinas* individuales. Cuando finaliza, activa la primera máquina* individual del módulo siguiente.** La rutina no finaliza nunca sino que se le transfiere* y devuelve el control mediante la técnica de corrutinas**********************************************************************/
modulo_A(tarea)int tarea;{ int valor; int paso; int j; char buf[2];
/**** Variables tomadas como ejemplo para comprobar que los valores de las mismas son independientes para cada máquina individual */
j=random(100); buf[0]=random(100); buf[1]=random(100); printf("Entra en módulo A con tarea= %d; j= %d; buf: %d %d\n",
tarea,j,buf[0],buf[1]);
paso=-1; while (1) {
/****** Fase de inicialización de la corrutina *****/
valor=setjmp(salto_ida_modulo_A[tarea]); if (valor==0) if( tarea < (n_tareas_modulo_A-1) && arrancando_modulo_A)
llamada_modulo_A(++tarea,RESERVA_STACK); /* Activa otratarea del mimo módulo */
else {
if (arrancando_modulo_A){ arrancando_modulo_A=0; valor=setjmp(salto_vuelta_modulo_B); if (valor==0) llamada_modulo_B(0,RESERVA_STACK); /* Activa la primera
tarea del módulosiguiente */
}longjmp(salto_vuelta_modulo_A,1);
}
/****** Fase de ejecución de un paso de la máquina individual *****/
paso++; printf("Módulo A: Tarea %d; Paso %d; ",tarea,paso); j++; buf[0]++; buf[1]++; printf("j= %d; Buf: %d %d\n",j,buf[0],buf[1]); } /* Fin del bucle infinito */} /* Fin del módulo A */
DESARROLLO DE PROTOCOLOS Página 66
/*********************************************************************** Rutina para reservar espacio en el stack. Esta rutina debe* ser generada por el generador de código para cada uno de* los módulos de la especificación.** Si la activación de un módulo que se realiza para cada máquina* individual se hiciera directamente llamando a la rutina* correspondiente, las variables del módulo llamado se almacenarían* en el stack a continuación de las variables del módulo llamante* EN EL MOMENTO DE LA LLAMADA. Si posteriormente el módulo llamante* al tener control en alguno de sus ejecuciones, necesitara ampliar* el número de variables almacenados en el stack, machacaría* las variables del módulo llamado.** Para evitarlo, la activación se hace de forma que las variables* del módulo llamado no empiecen a continuación de las del módulo* llamador. Para ello se realiza un cierto número de veces la* llamada recursiva a esta rutina antes de llamar definitivamente* la rutina correspondiente. Con ello las variables de esta rutina* son las que se colocan en la pila una y otra vez a continuación* de las variables del módulo llamante, y serán por tanto las* variables destruidas por el módulo llamante en caso de ampliar* sus necesidades de uso del stack. Las variables destruidas no* necesitan ser conservadas por lo que esta destrucción no afecta* al funcionamiento del sistema.************************************************************************/
llamada_modulo_A(tarea,reserva)int tarea,reserva;{ reserva--; if (reserva<=0) modulo_A(tarea); else llamada_modulo_A(tarea,reserva); return;}
/******** Rutina correspondiente al módulo B *******/
modulo_B(tarea)int tarea;{ int valor; int paso; int k; char baf[2];
k=random(100); baf[0]=random(100); baf[1]=random(100); printf("Entra en módulo B con tarea= %d; k= %d; baf: %d %d\n",
tarea,k,baf[0],baf[1]); paso=-1; while (1) { valor=setjmp(salto_ida_modulo_B[tarea]); if (valor==0) if( tarea < (n_tareas_modulo_B-1) && arrancando_modulo_B)
llamada_modulo_B(++tarea,RESERVA_STACK); else {
if (arrancando_modulo_B){ arrancando_modulo_B=0;
DESARROLLO DE PROTOCOLOS Página 67
valor=setjmp(salto_vuelta_mmi); if (valor==0) llamada_mmi(RESERVA_STACK); /* Al ser el útimo módulo ESTELLE
activa el primer módulo de gestión,en este ejemplo el mmi que, además,es el único */
}longjmp(salto_vuelta_modulo_B,1);
}
paso++; printf("Módulo B: Tarea %d; Paso %d; ",tarea,paso); k++; baf[0]++; baf[1]++; printf("k= %d; Baf: %d %d\n",k,baf[0],baf[1]); }}
/****** Rutina de llamada al módulo B *****/
llamada_modulo_B(tarea,reserva)int tarea,reserva;{ reserva--; if (reserva<=0) modulo_B(tarea); else llamada_modulo_B(tarea,reserva); return;}
/********************************************************************** Rutina que simula el Man Machine Interface (MMI)*********************************************************************/
mmi(){ int tecla;
printf("Entra en el mmi\n"); while (1) { tecla=leetecla(); printf("MMI: Se pulsó la tecla %d\n",tecla); }}
/****** Rutina de llamada al mmi *****/
llamada_mmi(reserva)int reserva;{ reserva--; if (reserva<=0) mmi(); else llamada_mmi(reserva); return;}
/**************************************************************** Rutina de lectura de tecla** Si no se ha pulsado niguna tecla devuelve el control* al gestor de tareas con técnica de corrutina.*
DESARROLLO DE PROTOCOLOS Página 68
* Cuando se pulsa una tecla devuelve el control al MMI* para que ejecute un ciclo no muy largo y velva a llamar* a leetecla. Esta cesión de control se hace con técnica de* subrutina normal.******************************************************************/
leetecla(){ int i,j,valor;
while(!kbhit()) { valor=setjmp(salto_ida_mmi); if (valor==0) longjmp(salto_vuelta_mmi,1); } i=getch(); if (i!=0) return(i); j=getch(); return(j<<8);}
Programa GESCORM.C.
Programa ejemplo de gestión de máquinas múltiples concorrutinas.
/********************************************************************** Programa ejemplo de gestión de máquinas múltiples* con corrutinas*********************************************************************/
#include <stdio.h> /* Estos include son necesarios para los */#include <setjmp.h> /* procesos de gestión de corrutinas */
main() /* El main tan sólo sirve para dar soporte a la rutina gestor */{ printf("\n\n\n"); gestor_modulos();}
#define n_maquinas_tipo_a 3#define n_maquinas_tipo_b 2jmp_buf punto_salto_maquina_a[n_maquinas_tipo_a];jmp_buf punto_salto_maquina_b[n_maquinas_tipo_b];jmp_buf punto_salto_gestor_modulos;
gestor_modulos(){ int i;
DESARROLLO DE PROTOCOLOS Página 69
for (i=0;i<n_maquinas_tipo_a;i++) arranca_maquina_a(i,100); for (i=0;i<n_maquinas_tipo_b;i++) arranca_maquina_b(i,100); for (;;) { for (i=0;i<n_maquinas_tipo_a;i++) corrutina_maquina_a(i); for (i=0;i<n_maquinas_tipo_b;i++) corrutina_maquina_b(i); }}
maquina_a(tarea)int tarea;{ int valor,paso;
printf("Activada máquina A (%d)\n",tarea); paso=0; for (;;) { valor=setjmp(punto_salto_maquina_a[tarea]); if (valor==0) longjmp(punto_salto_gestor_modulos,1); printf("Máquina A (%d). Paso %d\n",tarea,++paso); } /* Fin del bucle infinito */} /* Fin del módulo A */
arranca_maquina_a(tarea,reserva)int tarea,reserva;{ int valor;
reserva--; if (reserva<=0) { valor=setjmp(punto_salto_gestor_modulos); if (valor==0) maquina_a(tarea); } else arranca_maquina_a(tarea,reserva); return;}
corrutina_maquina_a(tarea)int tarea;{ int valor;
valor=setjmp(punto_salto_gestor_modulos); if (valor==0) longjmp(punto_salto_maquina_a[tarea],1); return;}
maquina_b(tarea)int tarea;{ int valor,paso;
printf("Activada máquina B (%d)\n",tarea); paso=0; for (;;) { valor=setjmp(punto_salto_maquina_b[tarea]); if (valor==0) longjmp(punto_salto_gestor_modulos,1); printf("Máquina B (%d). Paso %d\n",tarea,++paso); } /* Fin del bucle infinito */} /* Fin del módulo B */
arranca_maquina_b(tarea,reserva)int tarea,reserva;
DESARROLLO DE PROTOCOLOS Página 70
{ int valor;
reserva--; if (reserva<=0) { valor=setjmp(punto_salto_gestor_modulos); if (valor==0) maquina_b(tarea); } else arranca_maquina_b(tarea,reserva); return;}
corrutina_maquina_b(tarea)int tarea;{ int valor;
valor=setjmp(punto_salto_gestor_modulos); if (valor==0) longjmp(punto_salto_maquina_b[tarea],1); return;}
Programa MAQSIMP1.C.
Representación de una máquina de estados simplemediante tablas.
typedef struct{ int condicion; int accion; int nuevo_estado; void *siguiente_transicion;} transicion;
typedef struct{ int codigo; int parametro;} evento;
#define NULA 0#define NUM_ES_A 2#define NUM_EV_A 2
enum estados_a {ES1_A,ES2_A};enum eventos_a {EV1_A,EV2_A};enum condiciones_a {P1_A,P2_A};enum salidas_a {SAL1_A,SAL2_A,SAL3_A,SAL4_A};
transicion transicion_ES1A_EV1A={NULA,SAL2_A,ES2_A,NULA};transicion transicion_ES1A_EV2A={NULA,NULA,ES1_A,NULA};
DESARROLLO DE PROTOCOLOS Página 71
transicion transicion_ES2A_EV1A={NULA,SAL2_A,ES2_A,NULA};transicion transicion_ES2A_EV2A_1={P1_A,SAL3_A,ES1_A,NULA};transicion transicion_ES2A_EV2A_2={P2_A,SAL4_A,ES2_A,NULA};
transicion maquina[NUM_ES_A][NUM_EV_A];
int estado_a=ES1_A;int accion_inicial_a=SAL1_A;
evento lee_evento();
int estado_luz_roja,estado_luz_verde;
inicia_maquina_a(){ maquina[ES1_A][EV1_A]=transicion_ES1A_EV1A; maquina[ES1_A][EV2_A]=transicion_ES1A_EV2A; maquina[ES2_A][EV1_A]=transicion_ES2A_EV1A; maquina[ES2_A][EV2_A]=transicion_ES2A_EV2A_1; transicion_ES2A_EV2A_1.siguiente_transicion=&transicion_ES2A_EV2A_2;
accion_a(accion_inicial_a);}
maquina_a(){ evento ultimo_evento; transicion transicion_disparada,transicion_anterior;
ultimo_evento=lee_evento(); transicion_disparada=maquina[estado_a][ultimo_evento.codigo]; do { if (condicion_a(transicion_disparada.condicion)) { estado_a=transicion_disparada.nuevo_estado; accion_a(transicion_disparada.accion); } else { transicion_anterior=transicion_disparada; transicion_disparada=*(transicion*)(transicion_disparada.siguiente_transicion); } } while (transicion_anterior.siguiente_transicion!=NULA);} /* Fin del programa */
condicion_a(condicion)int condicion;{ switch (condicion) { case P1_A: if ( hay_solo_una_luz_encendida() &&
boton_y_luz_del_mismo_color() ) return(1); else return(0);
case P2_A: if (!hay_solo_una_luz_encendida() )return(1); else return(0);
}}
accion_a(accion)int accion;
DESARROLLO DE PROTOCOLOS Página 72
{ switch (accion) { case SAL1_A: activa_timer(); break;
case SAL2_A: enciende_luz_roja_o_verde(); activa_timer(); break;
case SAL3_A: apaga_una_luz(); break;
case SAL4_A: informa_perdida_juego(); break;
}}
Programa MAQSIMP2.C.
Representación de una máquina de estados simplemediante selección en código de tipo (estado, evento,condición).
typedef struct{ int codigo; int parametro;} evento;
enum estados_a {ES1_A,ES2_A};enum eventos_a {EV1_A,EV2_A};
int estado_a=ES1_A;
evento lee_evento();
int estado_luz_roja,estado_luz_verde;
inicia_maquina_a(){ activa_timer(); return;}
maquina_a(){ evento ultimo_evento;
DESARROLLO DE PROTOCOLOS Página 73
ultimo_evento=lee_evento(); switch(estado_a) { case ES1_A: switch (ultimo_evento.codigo) { case EV1_A: enciende_luz_roja_o_verde(); activa_timer(); estado_a=ES2_A; break;
case EV2_A: break;
} /* Fin del switch de los eventos del estado ES1_A */ break;
case ES2_A: switch (ultimo_evento.codigo) { case EV1_A: break;
case EV2_A: if ( hay_solo_una_luz_encendida() && boton_y_luz_del_mismo_color()) {
apaga_una_luz();estado_a=ES1_A;
} else if (!hay_solo_una_luz_encendida()) {
informa_perdida_juego(); } break;
} /* Fin del switch de los eventos del estado ES2_A */ break;
} /* Fin del switch de los estados */} /* Fin de la maquina a */
Programa MAQSIMP3.C
Representación de una máquina de estados simplemediante selección en código de tipo (evento, estado,condición).
DESARROLLO DE PROTOCOLOS Página 74
typedef struct{ int codigo; int parametro;} evento;
enum estados_a {ES1_A,ES2_A};enum eventos_a {EV1_A,EV2_A};
int estado_a=ES1_A;
evento lee_evento();
int estado_luz_roja,estado_luz_verde;
inicia_maquina_a(){ activa_timer(); return;}
maquina_a(){ evento ultimo_evento;
ultimo_evento=lee_evento(); switch(ultimo_evento.codigo) { case EV1_A: switch (estado_a) { case ES1_A: enciende_luz_roja_o_verde(); activa_timer(); estado_a=ES2_A; break;
case ES2_A: break;
} /* Fin del switch de los estados del evento EV1_A */ break;
case EV2_A: switch (estado_a) { case ES1_A: break;
case ES2_A: if ( hay_solo_una_luz_encendida() && boton_y_luz_del_mismo_color()) {
apaga_una_luz();estado_a=ES1_A;
} else if (!hay_solo_una_luz_encendida()) {
informa_perdida_juego(); } break;
} /* Fin del switch de los estados del evento EV2_A */ break;
} /* Fin del switch de los eventos */
DESARROLLO DE PROTOCOLOS Página 75
} /* Fin de la maquina a */
Programa MAQSIMP.C.
Programa de simulación de la máquina simple de juegos.
#include <simpro.h>
typedef struct{ int codigo; int parametro;} evento;
enum maquinas {MAQUINA_A};enum estados_a {ES1_A,ES2_A};enum eventos_a {EV1_A,EV2_A};
int estado_a=ES1_A;
int estado_luz_roja,estado_luz_verde;evento ultimo_evento;
main(){ inicializa_lista_sucesos(); inicia_maquina_a(); for (;;) { maquina_a(); lee_teclado(); }}
inicia_maquina_a(){ activa_timer(); return;}
maquina_a(){ lee_evento(); switch(estado_a) { case ES1_A: switch (ultimo_evento.codigo) { case EV1_A: enciende_luz_roja_o_verde(); activa_timer(); estado_a=ES2_A;
DESARROLLO DE PROTOCOLOS Página 76
break;
case EV2_A: break;
} /* Fin del switch de los eventos del estado ES1_A */ break;
case ES2_A: switch (ultimo_evento.codigo) { case EV1_A: enciende_luz_roja_o_verde(); activa_timer(); break;
case EV2_A: if ( hay_solo_una_luz_encendida() && boton_y_luz_del_mismo_color()) {
apaga_una_luz();estado_a=ES1_A;
} else if (!hay_solo_una_luz_encendida()) {
informa_perdida_juego(); } break;
} /* Fin del switch de los eventos del estado ES2_A */ break;
} /* Fin del switch de los estados */} /* Fin de la maquina a */
activa_timer(){ genera_suceso(EV1_A,MAQUINA_A,0,0,5.); return;}
apaga_una_luz(){ int luz;
luz=genera_error(0.5); if (luz) if (estado_luz_roja==1) estado_luz_roja=0; else estado_luz_verde=0; else if (estado_luz_verde==1) estado_luz_verde=0; else estado_luz_roja=0; printf("%5.0f %d %d\n",tiempo_transcurrido(),
estado_luz_roja,estado_luz_verde); return;}
boton_y_luz_del_mismo_color(){ int boton;
boton=ultimo_evento.parametro; /* 0:rojo; 1:verde */ if ( (boton==0) && (estado_luz_roja==1) ) return(1); if ( (boton==1) && (estado_luz_verde==1) ) return(1); return(0);}
DESARROLLO DE PROTOCOLOS Página 77
hay_solo_una_luz_encendida(){ int i;
i=estado_luz_roja+estado_luz_verde; if (i==1) return(1); else return(0);}
enciende_luz_roja_o_verde(){ int luz;
luz=genera_error(0.5); if (luz) estado_luz_roja=1; else estado_luz_verde=1; printf("%5.0f %d %d\n",tiempo_transcurrido(),
estado_luz_roja,estado_luz_verde); return;}
informa_perdida_juego(){ printf("Has perdido la partida. Para salir del juego pulsa x\n"); return;}
lee_evento(){ struct suceso ultimo_suceso; int *puntero;
lee_proximo_suceso(&ultimo_suceso); ultimo_evento.codigo=ultimo_suceso.codigo; puntero=ultimo_suceso.parametros; ultimo_evento.parametro=*puntero; return;}
lee_teclado(){ char tecla; static int boton;
tecla=getch(); if (tecla=='r') { boton=0; genera_suceso(EV2_A,MAQUINA_A,0,&boton,0.); return; } if (tecla=='v') { boton=1; genera_suceso(EV2_A,MAQUINA_A,0,&boton,0.); return; } if (tecla=='x') exit();}