Mensajería

De Wiki de Sistemas Operativos
Saltar a: navegación, buscar

Un mensaje es un contenedor que se emplea para intercambiar información entre dos o más procesos. Los mensajes tienen un cierto formato generalmente compuesto por una cabecera, que contiene información sobre la fuente y el destinatario, y un cuerpo, que contiene información específica.

Los mecanismos de mensajería se refieren al conjunto de funcionalidades que permiten al sistema operativo realizar la entrega de un mensaje a uno o varios procesos. Mediante dicho mecanismo también es posible resolver problemas de concurrencia.

Los sistemas operativos generalmente ofrecen dos llamadas al sistema para que un proceso pueda enviar y recibir mensajes:

  • send(destino, mensaje): Envía un mensaje a un destino.
  • receive(fuente, mensaje): Recibe un mensaje de una fuente.

Una operación compuesta derivada de las primitivas básicas es:

  • sendrec (dest_fuent, mensaje): Envía un mensaje a un destino y recoge la respuesta.

Cualquier proceso que necesite comprobar si tiene mensajes para ser procesados por él debe invocar la llamada al sistema recv.

Formas de identificación

Para identificar la fuente y el destino de nuestro mensaje se pueden emplear dos estrategias.

Denominación Directa

En este caso se emplea un ID que permite identificar a la fuente y al destinatario de manera unívoca en el sistema, por ejemplo, el PID de un proceso. La denominación directa permite tres configuraciones:

  • Unicast, de manera que la comunicación sucede entre una fuente y un destinatario.
  • Broadcast, el emisor envía un mensaje a todos los procesos existentes en el sistema.
  • Multicast, el emisor envía un mensaje a un grupo de procesos, siendo un grupo un subconjunto de procesos existentes en el sistema. El sistema operativo debe ofrecer llamadas al sistema que permitan a los procesos crear grupos y suscribirse a ellos. Un proceso puede suscribirse a tantos grupos como le sea necesario.

El principal inconveniente de la denominación directa es que se pierde el mensaje si el destino no se encuentra.

Denominación Indirecta

Se emplea un elemento intermediario denominado buzón. El sistema operativo ofrece llamadas al sistema que permiten la creación y destrucción de buzones, por ejemplo, CreateMailbox y DestroyMailbox. Una vez creado el buzón, se emplea éste para realizar la comunicación entre procesos.

Con los buzones se pueden emplear cuatro formas diferentes de comunicación:

  • Buzón limitado: De un proceso a otro.
  • Buzón de entrada: De varios a uno.
  • Buzón de salida: De uno a varios.
  • Buzón múltiple: Varios procesos entre sí con el mismo buzón.

El receptor no conoce la identidad de quien envía el mensaje, a menos que el SO se haya encargado de ello. El problema surge cuando un proceso intruso y éste lee los mensajes de los buzones. Para remediarlo el SO establece permisos de acceso o contraseñas. O para evitar que el proceso infiltrado manipule los mensajes se puede también implementar firmas de certificación. y así garantizar la identidad de los remitentes.

Formas de transmisión

Distinguimos tres formas de transmisión: Transmisión por copia, transmisión por referencia y transmisión por copia en caso de escritura.

Transmisión por copia

Si se emplea denominación directa, al invocar el receptor la llamada recv, el mensaje se copia del espacio de memoria del emisor al espacio de receptor. Esta forma de implementar la transmisión tiene un coste de orden lineal <math>O(n)</math>.

Si se emplea denominación indirecta, al invocar send el emisor, el mensaje se copia del espacio de emisor al espacio del buzón, que se encuentra en el espacio de memoria del sistema operativo. Una vez que el receptor invoca receive, se copia el mensaje al espacio de memoria que el receptor ha habilitado para almacenar el mensaje.

SSOOMensajeria1.jpg

Transmisión por referencia

Es una mejora frente al mecanismo de transmisión por copia. Básicamente, en lugar de copiar el mensaje, lo que supone un coste de <math>O(n)</math>, se le devuelve al receptor una dirección de memoria en la que se encuentra el mensaje, lo que supone únicamente 4 u 8 bytes dependiendo del tamaño de palabra del procesador (si es de 32-bits o 64-bits).

Si se emplea junto a la denominación directa, el sistema operativo le devuelve al receptor un puntero a la dirección en la que se encuentra el mensaje. Si el mensaje está en el espacio del emisor, son necesarios mecanismos explícitos de compartición de memoria entre procesos, ya que dos procesos cualesquiera no comparten memoria. Si el mensaje está en el espacio del sistema operativo, éste debe ofrecer a los procesos mecanismos para acceder a su espacio de memoria-

Si se emplea junto a la denominación indirecta, el emisor crea un mensaje en el espacio del sistema operativo y se le ofrece un puntero al receptor. SSOOMensajeria2.jpg

Transmisión por copia en caso de escritura (COW)

Este método permite que los procesos emisor y receptor tengan acceso a la zona de memoria del mensaje de forma controlada. La zona puede pertenecer a uno de los procesos, o al SO, y se accede a ella a través de una ventana en el espacio de direcciones que inicialmente sólo permite consultar la información.

Cuando uno de los procesos intenta modificar el contenido del mensaje, se detecta esta escritura gracias al mecanismo de protección de la memoria. Entonces, el SO hace una copia para uno de ellos, actualiza las referencias, y deja que ambos procesos continúen.

Se mantiene un único espacio mientras sea posible. Si en muchas ocasiones se accede al mensaje para leer de él, se consigue un ahorro importante de espacio y tiempo. Pero hará falta incluir algún servicio para notificar al SO que ya no se necesita el espacio que haya asignado a la copia del mensaje, y que lo libere.

Formas de comunicación

  • Comportamiento del emisor, send()
    • Síncrona: el proceso emisor que realiza el send() queda bloqueado hasta que el receptor llama a recv() y el proceso bloqueado pasa a estado preparado.
    • Asíncrona: suponemos una estructura de datos con capacidad de almacenamiento limitada, dependiente del proceso destinatario o en el buzón del SO. Al ser enviados, los mensajes irán encolándose para que el proceso receptor pueda gestionarlos cuando pueda. De esta manera se aumenta el rendimiento, pero se requiere de políticas adicionales de tratamiento de los mensajes si la capacidad de almacenamiento de la estructura de datos se ve sobrepasada (PE, eliminación de mensajes, relocalización de memoria, etc..)
  • Comportamiento del receptor, recv()
    • Bloqueante: una llamada a recv() sin mensajes a procesar pasa el proceso llamante a estado bloqueado. Un send() de otro proceso que añada un mensaje a la cola del proceso bloqueado hace que éste pase a estado preparado.
    • No bloqueante: una llamada a recv() sin mensajes a procesar devuelve un mensaje de error, pero la ejecución del proceso llamante continúa.

Entendemos por proceso llamante a aquel que hace uso de las llamadas send() y recv().

Formato de los mensajes

Fijo

Los procesos acuerdan emplear un formato fijo para sus mensajes.

Ejemplo: Simple Network Time Protocol.<ref> Véase http://tools.ietf.org/html/rfc4330, sección 4, Message Format.</ref>

                          1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9  0  1
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |LI | VN  |Mode |    Stratum    |     Poll      |   Precision    |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                          Root  Delay                           |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                       Root  Dispersion                         |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                     Reference Identifier                       |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                                                                |
     |                    Reference Timestamp (64)                    |
     |                                                                |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                                                                |
     |                    Originate Timestamp (64)                    |
     |                                                                |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                                                                |
     |                     Receive Timestamp (64)                     |
     |                                                                |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                                                                |
     |                     Transmit Timestamp (64)                    |
     |                                                                |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                 Key Identifier (optional) (32)                 |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                                                                |
     |                                                                |
     |                 Message Digest (optional) (128)                |
     |                                                                |
     |                                                                |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Variable

  • Text-based human-readable.
  1. Comma-Separated Value (CSV)
  2. Extensible Markup Language (XML)
  3. JavaScript Object Notation (JSON)

Mixto

Los procesos acuerdan emplear mensaje con partes cuyo formato es fijo, como por ejemplo una cabecera inicial, seguido de partes de tamaño variable.

  • Type-Length-Value

Ejemplo: Usado en Netlink para la codificación de atributos de un mensaje.<ref>Ver Communicating between the kernel and user-space in Linux using Netlink Sockets (http://1984.lsi.us.es/~pablo/docs/spae.pdf)</ref>

Type-Length-Value
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
Length Type
Value

Mensajería a través de la red

  • Unicidad del destinatario, el PID ya no es suficiente, habría que decorar la manera de identificar al destino con el nombre del sistema. Por ejemplo: 23134@1984.lsi.us.es, siendo 1984.lsi.us.es el nombre del sistema en el que reside el proceso con PID 23134.
  • Fiabilidad en la transmisión, son necesarios números de secuencia y acuses de recibo para garantizar que no hay pérdida de mensajes.
  • Formato de los datos (http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Data/endian.html):

Estas consideraciones no aplican si el formato empleado a la hora de enviar datos en mensajes es en texto, tal como XML, JSON y CSV. Por tanto, sólo aplica a formatos con campos de tamaño fijo, donde un campo puede tener valores numéricos. Al enviar un entero a través de la red puede ser un problema debido a la forma en el que se representan los datos en la memoria dependiente de la arquitectura. Por ejemplo, un PC con arquitectura Intel x86 emplea formato little-endian, mientras que un SPARC de Sun Microsystems (ahora Oracle) emplea big-endian.

    • Little Endian: Bytes menos significativo al principio.
int main(void)
{
        int a = 10, i;
        char *p = &a;

        for (i=0; i<sizeof(a); i++) {
                printf("posición %d: %.2x\n", i, p[i]);
        }
}

Muestra el siguiente resultado en un PC:

posición 0: 0a (n+3)
posición 1: 00 (n+2)
posición 2: 00 (n+1)
posición 3: 00 (n)
    • Big Endian: Bytes menos significativo al final.
posición 0: 00 (n)
posición 1: 00 (n+1)
posición 2: 00 (n+2)
posición 3: 0a (n+3)

En Internet se sigue la convención de expresar datos en Big Endian, habría que hacer la conversión. Habría que usar ntohl (network to host long).

En little-endian, la implementación de ntohl es la siguiente:

  1. define __constant_ntohl(x) ___constant_swab32((__be32)(x)

Ver /usr/include/linux/byteorder/little_endian.h

mientras que en big-endian es:

  1. define __constant_ntohl(x) ((__u32)(__be32)(x))

Si empleamos formatos textuales, como CSV, XML o JSON, que codifican la información en bytes, no tenemos este problema.

Referencias

Aún sin referencias.


5.8 Ejercicios sincronización y comunicación