[LinuxFocus-icon]
Hogar  |  Mapa  |  Indice  |  Busqueda

Noticias | Arca | Enlaces | Sobre LF
convert to palmConvert to GutenPalm
or to PalmDoc

[Leonardo]
por Leonardo Giordani
<leo.giordani(at)at.libero.it>

Sobre el autor:

Estudiante de Ingeniería en Telecomunicaciones en la Escuela Politécnica de Milán, trabaja como administrador de redes y está interesado en la programación (sobretodo en Ensamblador y C/C++). Desde 1999 trabaja casi exclusivamente bajo Linux/Unix.



Taducido al español por:
Carlos González Pérez <charly(at)galpon.org>

Contenidos:

 

Programación Concurrente - Colas de mensajes (1)

[run in paralell]

Resumen:

Esta serie de artículos tiene como propósito el introducir al lector el concepto de multitarea y su implementación en el sistema operativo Linux. Comenzando desde los conceptos teóricos sobre las bases de la multitarea, terminaremos escribiendo una aplicación completa demostrando la comunicación entre procesos, con un simple pero eficiente protocolo de comunicaciones.

Prerrequisitos para entender este artículo:

  • Un conocimiento mínimo de la shell.
  • Conocimiento básico del lenguaje C (sintaxis, bucles, librerias)
También sería buena idea leer otros artículos de esta serie, que aparecieron en las 2 números anteriores de LinuxFocus (Noviembre 2002 y Enero 2003).
_________________ _________________ _________________

 

Introducción

En los artículos anteriores introducimos el concepto de programación concurrente y estudiamos una solución inicial al problema de la comunicación entre procesos: los semáforos. Como hemos visto, el uso de semáforos nos permite administrar el acceso a recursos compartidos,  así como sincronizar "rudamente" dos o más procesos.

Sincronizar procesos significa temporizar sus trabajo, no como un modo de referecia absoluta (dando un tiempo exacto en el cual el proceso debe empezar sus operaciones) pero sí de forma relativa, donde podamos decidir que proceso debe actuar primero y cual lo hará  después.

Utilizar semáforos para esto es complejo y limitado: complejo porque cada proceso debe manejar un semáforo por cada otro proceso con el que tenga que sincronizarse. Limitado porque no nos permite intercambiar parámetros entre los procesos. Consideremos, por ejemplo, crear un nuevo proceso: este evento debe ser notificado a cada proceso en ejecución, pero los semáforos no permiten  a los procesos enviar esa información.

El control de concurrencia de accesos a recursos compartidos a través de semáforos nos puede llevar hacia un bloqueo continuo  de un proceso, cuando uno de los otros procesos libera el recurso y lo bloquea antes de que otros puedan hacer uso de él: como hemos visto, en el mundo de la programación concurrente no es posible saber a priori que procesos serán ejecutados y cuando.

Estas brebes notas nos permiten entender rápidamente que los semáforos no son herramientas adecuadas para manejar sincronizaciones complejas. Una solución elegante para esto son las colas de mensajes: en este artículo estudiaremos la teoría de la comunicación entre procesos y escribiremos un pequeño programa usando las primitivas de SysV.

 

Teoría de Colas de Mensajes

Cada proceso puede crear una o más estructuras llamadas colas; Cada estructura puede esperar uno o más mensajes de diferentes tipos, que pueden venir de diferetes fuentes y pueden contener información de toda naturaleza; todos pueden enviar un mensaje a las colas de las cuales conozcan su identificador. Los procesos pueden acceder secuencialmente a la cola, leyendo los mensajes en orden cronológico (desde el más antigüo, el primero, al más reciente, el último en llegar), pero selectivamente, esto es, considerando sólo los mensajes de un cierto tipo: esta última característica nos da un tipo de control de la prioridad sobre los mensajes que leemos.

El uso de las colas es similar a una implementación simple de un sistema de correo: cada proceso tiene una dirección con la cual puede operar con otros procesos. El proceso puede entonces leer los mensajes mandados a su dirección en un orden preferencial y actuando de acuerdo con lo que le ha sido notificado.

La sincronización entre dos procesos puede ser así llevada a cabo simplemente usando mensajes entre los dos: los recursos seguirán aún teniendo semaforos para permitir a los procesos conocer sus estados, pero la temporización entre procesos sera realizada directamente. Entenderemos de forma inmediata que el uso de las colas de mensajes simplifica muchísimo lo que en un principio era  un pronblema extremadamente  complejo.

Antes de que podamos implementar el lenguaje C las colas d emensajes es necesario hablar sobre otro problema relativo a la sincronización: la necesidad de un protocolo de comunicación.

 

Creando un protocolo

Un protocolo es un conjunto de reglas que controlan la interacción de los elementos en un conjunto; en el artículo anterior implementamos uno de los más simples protocolos creando un semáforo y ordenando a dos procesos actuar
de acuerdo a sus estados. El uso de las colas de mensajes nos permite implementar protocolos más complejos: es suficiente pensar que cada protocolo de red (TCP/IP, DNS, SMTP, ...) está construido con una arquitectura de intercambio de mensajes, incluso si la comunicación es entre ordenadoresy no interna en uno de ellos. La comparación es compulsiva: no hay ninguana diferencia real entre comunicación entre procesos en la misma máquina y comunicación entre procesos entre distintas máquinas. Como veremos en un próximo artículo extendiendo los conceptos sobre los que estamos hablando a un contesto distribuido (varios ordenadores conectados) es un tema muy sencillo.

Este es un ejemplo sencillo de protocolo basado en intercambio de mensajes: dos procesos A y B se ejecutan concurrentemente y procesan diferentes datos; una vez terminados sus procesos tienen que mezclar sus resultados. Un protocolo simple para dirigir esta interacción puede ser el siguiente:

PROCESO B:


PROCESO A:

Elegir que proceso tiene que mezclar los datos es en este caso totalmente arbitrario; lo más común es que esto se haga en función de la naturaleza de los procesos envueltos (cliente/servidor) pero esta discusión requiere que se le dedique un artículo.

 

Este protocolo es sencillamente extensible al caso de n procesos :  cada proceso menos A trabaja con sus propios datos y luego envía un mensaje a A. Caundo A responde cada proceso le manda sus resultados: la estructura del proceso individual (excepto A) no es modificada.

 

Colas de Mensajes System V

Ahora es el momento de hablar sobre la implementación de estos conceptos en el sistema operativo Linux. Como ya he dicho tenemos una serie de primitivas que nos permiten manejar las estructuras relativas a las colas de mensajes y que trabajan como estas para manejar semáforos: Así que voy a asumir que el lector conoce los conceptos básicos relativos a la creación de procesos, uso de las llamadas del sistema y las llaves IPC.

La estructura base del sistema que describe un mensaje se llama msgbuf ; y está declarada en linux/msg.h

/* message buffer for msgsnd and msgrcv calls */
struct msgbuf {
long mtype; /* type of message */
char mtext[1]; /* message text */
};

El campo mtype representa el tipo demensaje  y es un número estrictamente positivo: la correspondencia entre números y tipos de mensajes  ha de ser establecida a priori y forma parte de la definición del protocolo.  El segundo campo representa el contenido del mensaje, pero no ha de ser considerado en la declaración. La estructura msgbuf puede ser redefinida  asíque puede contener datos complejos; por ejemplo, es posible escribir:
struct message {
long mtype; /* message type */
long sender; /* sender id */
long receiver; /* receiver id */
struct info data; /* message content */
...
};

Antes de enfrentarnos a los argumentos estrictamente relacionados con la teoría de la concurrencia debemos considerar crear el prototipo de un mensaje con el tamaño máximo, fijado a  4056 bytes. Obviamente siempre es posible recompilar el kernel incrementando esta dimensión,  pero hace que la aplicación no sea portable (es más, éste ha sido fijado para garantizar un buen rendimiento e incrementarlo no va a ser buena idea)

Para crear una nueva cola un proceso debe llamar a la función msgget()

int msgget(key_t key, int msgflg)
que recibe como argumente una llave  IPC y algunos flags (banderas),  que por ahora pueden ser puestos a:
IPC_CREAT | 0660
(crea la cola, si no existe, y da acceso al propietario y grupo de usuarios), y este devuelve el identificador de la cola.

Como en artículos anteriores asumiremos que no han aparecido errores,  así que podemos simplificar el código, incluso en un futuro artículo hablaremos sobre código IPC seguro.

Para enviar un mensaje a una cola de la cual conozcamos us identificador, tenemos que utilizar la función msgsnd()

int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg)

donde msqid es el identificador de la cola, msgp es un puntero al mensaje que tenemos que enviar (cuyo tipo es aquí definido como struct msgbuf pero que es el tipo que redefinimos), msgsz la dimensión del mensaje (excluyendo la longitud del tipo mtype que tiene la longitud de un long,  que es normalmente de 4 bytes) y msgflg un flag  relativo a la política de espera. La longitud del mensaje puede ser facilmente encontrada como
length = sizeof(struct message) - sizeof(long);

mientras la política de espera se refiere al caso de la cola completa: si msgflg es puesta a IPC_NOWAIT el proceso emisor no esperará mientras no haya algún espacio disponible y saldrá con algún código de error ; hablaremos sobre cada caso cuando hablemos del tratamiento de errores.

Para leer los mensjaes contenidos en una cola utilizamos la llamada del sistema msgrcv()

int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long mtype, int msgflg)


donde el puntero msgp identifica el búffer donde copiaremos el mensaje leido de la cola y  mtype identica el subconjunto de mensajes  que queremos considerar.

Se puede eliminar una cola a través del uso de la primitiva msgctl() con el flag IPC_RMID

msgctl(qid, IPC_RMID, 0)

Probemos lo que he dicho con un programa simple que crea una cola de mensajes, envía un mensaje y lo lee; controlaremos de esta manera el correcto funcionamiento del sistema.
#include <stdio.h>
#include <stdlib.h>
#include <linux/ipc.h>
#include <linux/msg.h>

/* Redefines the struct msgbuf */
typedef struct mymsgbuf
{
long mtype;
int int_num;
float float_num;
char ch;
} mess_t;

int main()
{
int qid;
key_t msgkey;

mess_t sent;
mess_t received;

int length;

/* Initializes the seed of the pseudo-random number generator */
srand (time (0));

/* Length of the message */
length = sizeof(mess_t) - sizeof(long);

msgkey = ftok(".",'m');

/* Creates the queue*/
qid = msgget(msgkey, IPC_CREAT | 0660);

printf("QID = %d\n", qid);

/* Builds a message */
sent.mtype = 1;
sent.int_num = rand();
sent.float_num = (float)(rand())/3;
sent.ch = 'f';

/* Sends the message */
msgsnd(qid, &sent, length, 0);
printf("MESSAGE SENT...\n");

/* Receives the message */
msgrcv(qid, &received, length, sent.mtype, 0);
printf("MESSAGE RECEIVED...\n");

/* Controls that received and sent messages are equal */
printf("Integer number = %d (sent %d) -- ", received.int_num,
sent.int_num);
if(received.int_num == sent.int_num) printf(" OK\n");
else printf("ERROR\n");

printf("Float numero = %f (sent %f) -- ", received.float_num,
sent.float_num);
if(received.float_num == sent.float_num) printf(" OK\n");
else printf("ERROR\n");

printf("Char = %c (sent %c) -- ", received.ch, sent.ch);
if(received.ch == sent.ch) printf(" OK\n");
else printf("ERROR\n");

/* Destroys the queue */
msgctl(qid, IPC_RMID, 0);
}

Ahora podemos crear dos procesos y permitirles comunicarse a través de una cola de mensajes; un poco sobre los conceptos de bifurcación de procesos: el valor de todas las variables asignadas por el proceso padre es tomado por esos  de los procesos hijos (copia de memoria). Esto significa que debemos crear la cola antes de bifurcar (fork) el proceso padre y el hijo conocerá el identificador de cola para poder así acceder a él.

El código que he escrito crea una cola usada por el proceso hijo y envía sus datos al proceso padre: el hijo generas números aleatorios, los envía al padre y ambos imprimen en la salida estándar.

#include <stdio.h>
#include <stdlib.h>
#include <linux/ipc.h>
#include <linux/msg.h>
#include <sys/types.h>

/* Redefines the message structure */
typedef struct mymsgbuf
{
long mtype;
int num;
} mess_t;

int main()
{
int qid;
key_t msgkey;
pid_t pid;

mess_t buf;

int length;
int cont;

length = sizeof(mess_t) - sizeof(long);

msgkey = ftok(".",'m');

qid = msgget(msgkey, IPC_CREAT | 0660);

if(!(pid = fork())){
printf("SON - QID = %d\n", qid);

srand (time (0));

for(cont = 0; cont < 10; cont++){
sleep (rand()%4);
buf.mtype = 1;
buf.num = rand()%100;
msgsnd(qid, &buf, length, 0);
printf("SON - MESSAGE NUMBER %d: %d\n", cont+1, buf.num);
}

return 0;
}

printf("FATHER - QID = %d\n", qid);

for(cont = 0; cont < 10; cont++){
sleep (rand()%4);
msgrcv(qid, &buf, length, 1, 0);
printf("FATHER - MESSAGE NUMBER %d: %d\n", cont+1, buf.num);
}

msgctl(qid, IPC_RMID, 0);

return 0;
}

Hemos creado así dos procesos, que pueden colaborar de forma elemental a través de un sistema de intercambio de mensajes. No necesitamos un protocolo (formal) porque las operaciones realizadas son muy sencillas; en el próximo artículo hablaremos denuevo sobre colas de mensajes y sobre manejar diferentes tipos de mensajes. Trabajaremos más aún sobre el protocolo de comunicación para poder comenzar a contruir nuestro gran proyecto IPC (un simulador de selector telefónico).

 

Lecturas Recomendadas

 

Formulario de "talkback" para este artículo

Cada artículo tiene su propia página de "talkback". A través de esa página puedes enviar un comentario o consultar los comentarios de otros lectores
 Ir a la página de "talkback" 

Contactar con el equipo de LinuFocus
© Leonardo Giordani, FDL
LinuxFocus.org
Información sobre la traducción:
it --> -- : Leonardo Giordani <leo.giordani(at)at.libero.it>
it --> en: Leonardo Giordani <leo.giordani(at)at.libero.it>
en --> es: Carlos González Pérez <charly(at)galpon.org>

2003-07-21, generated by lfparser version 2.34