|
|
Este documento está disponible en los siguientes idiomas: English Castellano Deutsch Francais Nederlands Russian Turkce |
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)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:
|
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.
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.
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:
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.
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 */
};
struct message {
long mtype; /* message type */
long sender; /* sender id */
long receiver; /* receiver id */
struct info data; /* message content */
...
};
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)
length = sizeof(struct message) - sizeof(long);
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)
Se puede eliminar una cola a través del uso de la primitiva
msgctl() con el flag IPC_RMID
msgctl(qid, IPC_RMID, 0)
#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);
}
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;
}
|
Contactar con el equipo de LinuFocus
© Leonardo Giordani, FDL LinuxFocus.org |
Información sobre la traducción:
|
2003-07-21, generated by lfparser version 2.34