Message Queues


Creating A Message Queue - msgget()

In order to use a message queue, it has to be created first. The msgget() system call is used to do just that. This system call accepts two parameters - a queue key, and flags. The key may be one of:

The second parameter contains flags that control how the system call is to be processed. It may contain flags like IPC_CREAT or IPC_EXCL, which behave similar to O_CREAT and O_EXCL in the open() system call, and will be explained later, and it also contains access permission bits. The lowest 9 bits of the flags are used to define access permission for the queue, much like similar 9 bits are used to control access to files. the bits are separated into 3 groups - user, group and others. In each set, the first bit refers to read permission, the second bit - to write permission, and the third bit is ignored (no execute permission is relevant to message queues).

Lets see an example of a code that creates a private message queue:



#include <stdio.h>     /* standard I/O routines.            */
#include <sys/types.h> /* standard system data types.       */
#include <sys/ipc.h>   /* common system V IPC structures.   */
#include <sys/msg.h>   /* message-queue specific functions. */

/* create a private message queue, with access only to the owner. */
int queue_id = msgget(IPC_PRIVATE, 0600); /* <-- this is an octal number. */
if (queue_id == -1) {
    perror("msgget");
    exit(1);
}

A few notes about this code:

  1. the system call returns an integer identifying the created queue. Later on we can use this key in order to access the queue for reading and writing messages.
  2. The queue created belongs to the user whose process created the queue. Thus, since the permission bits are '0600', only processes run on behalf of this user will have access to the queue.


The Message Structure - struct msgbuf

Before we go to writing messages to the queue or reading messages from it, we need to see how a message looks. The system defines a structure named 'msgbuf' for this purpose. Here is how it is defined:


struct msgbuf {
    long mtype;     /* message type, a positive number (cannot be zero). */
    char mtext[1];  /* message body array. usually larger then one byte. */
};


The message type part is rather obvious. But how do we deal with a message text that is only 1 byte long? Well, we actually may place a much larger text inside a message. For this, we allocate more memory for a msgbuf structure then sizeof(struct msgbuf). Lets see how we create an "hello world" message:



/* first, define the message string */
char* msg_text = "hello world";
/* allocate a message with enough space for length of string and */
/* one extra byte for the terminating null character.            */
struct msgbuf* msg =
        (struct msgbuf*)malloc(sizeof(struct msgbuf) + strlen(msg_text));
/* set the message type. for example - set it to '1'. */
msg->mtype = 1;
/* finally, place the "hello world" string inside the message. */
strcpy(msg->mtext, msg_text);

Few notes:

  1. When allocating a space for a string, one always needs to allocate one extra byte for the null character terminating the string. In our case, we allocated strlen(msg_text) more then the size of "struct msgbuf", and didn't need to allocate an extra place for the null character, cause that's already contained in the msgbuf structure (the 1 byte of mtext there).
  2. We don't need to place only text messages in a message. We may also place binary data. In that case, we could allocate space as large as the msgbuf struct plus the size of our binary data, minus one byte. Of-course then to copy the data to the message, we'll use a function such as memset(), and not strcpy().


Writing Messages Onto A Queue - msgsnd()

Once we created the message queue, and a message structure, we can place it on the message queue, using the msgsnd() system call. This system call copies our message structure and places that as the last message on the queue. It takes the following parameters:

  1. int msqid - id of message queue, as returned from the msgget() call.
  2. struct msgbuf* msg - a pointer to a properly initializes message structure, such as the one we prepared in the previous section.
  3. int msgsz - the size of the data part (mtext) of the message, in bytes.
  4. int msgflg - flags specifying how to send the message: to set no flags, use the value '0'.
So in order to send our message on the queue, we'll use msgsnd() like this:


int rc = msgsnd(queue_id, msg, strlen(msg_text)+1, 0);
if (rc == -1) {
    perror("msgsnd");
    exit(1);
}


Note that we used a message size one larger then the length of the string, since we're also sending the null character. msgsnd() assumes the data in the message to be an arbitrary sequence of bytes, so it cannot know we've got the null character there too, unless we state that explicitly.


Reading A Message From The Queue - msgrcv()

We may use the system call msgrcv() In order to read a message from a message queue. This system call accepts the following list of parameters:

  1. int msqid - id of the queue, as returned from msgget().
  2. struct msgbuf* msg - a pointer to a pre-allocated msgbuf structure. It should generally be large enough to contain a message with some arbitrary data (see more below).
  3. int msgsz - size of largest message text we wish to receive. Must NOT be larger then the amount of space we allocated for the message text in 'msg'.
  4. int msgtyp - Type of message we wish to read. may be one of:
  5. int msgflg - a logical 'or' combination of any of the following flags:
Lets then try to read our message from the message queue:



/* prepare a message structure large enough to read our "hello world". */
struct msgbuf* recv_msg =
     (struct msgbuf*)malloc(sizeof(struct msgbuf)+strlen("hello world"));
/* use msgrcv() to read the message. We agree to get any type, and thus */
/* use '0' in the message type parameter, and use no flags (0).         */
int rc = msgrcv(queue_id, recv_msg, strlen("hello world")+1, 0, 0);
if (rc == -1) {
    perror("msgrcv");
    exit(1);
}

A few notes:

  1. If the message on the queue was larger then the size of "hello world" (plus one), we would get an error, and thus exit.
  2. If there was no message on the queue, the msgrcv() call would have blocked our process until one of the following happens:
Now that you've seen all the different parts, you're invited to look at the private-queue-hello-world.c program, for the complete program.


Message Queues - A Complete Example

As an example of using non-private message queues, we will show a program, named "queue_sender", that creates a message queue, and then starts sending messages with different priorities onto the queue. A second program, named "queue_reader", may be run that reads the messages from the queue, and does something with them (in our example - just prints their contents to standard output). The "queue_reader" is given a number on its command line, which is the priority of messages that it should read. By running several copies of this program simultaneously, we can achieve a basic level of concurrency. Such a mechanism may be used by a system in which several clients may be sending requests of different types, that need to be handled differently.

Also see man msgctl.html