Easy IPC with POSIX message queues

Easy IPC with POSIX message queues

Hey there! Today, I’m waking up that old blog with another post about C development! What? You’re not happy? Of course you are. In this article, I’ll make a quick overview on on particular IPC technique: the POSIX message queue. First of all, for those who don’t know:

  1. On UNIX systems, each process is given a specific memory and address space, which cannot be accessed by other processes.
  2. Because of 1., processes can’t communicate using their respective memory space.
  3. Several threads of the same process share the same memory and address space, therefore they can communicate naturally.
  4. Techniques and tools to allow two or more processes to exchange information are referred to as IPC (Inter-Process Communication).

On most UNIX systems, you’ll find the following IPC tools:

  1. Signals
  2. Pipes
  3. Shared memory
  4. Memory-mapped files
  5. Sockets (UNIX and TCP/IP)
  6. Message queues

Now as I said earlier, in the article, we will focus on the latter: message queues.

What is a message queue?

Well… the name is rather self-explanatory: a message queue is a structure in which several processes can send and retrieve data. This is how it usually happens:

  1. Process A create the queue, and gives it a particular identifier: the key.
  2. Process B gains access to the queue using the key.
  3. Process A and B send and receive messages to and from the queue.
  4. Process A destroys the queue.

Resources necessary to use message queues can be included through the following header files:

What are these queue keys?

Each message queue registered by the kernel is associated to a key, a unique identifier thanks to which processes can access and alter it. This key is represented by a variable of type key_t (which looks just like an int, of course). In most scenarios, this key can either be:

  • Set to IPC_PRIVATE, a constant which creates a private message queue. Queues created this way can only be accessed by the creating process, and its children.
  • Generated automatically from a filename, using ftok.

To use ftok, your processes need to know what filename to use in order to generate the key. Let’s say your programs know they need to use /etc/debian_version (which would be disastrous for portability). The ftok call would be:

The second parameter, referred to as “the ID” is another value use for key generation. Your programs will get the same key value if and only if they use the same file and ID value to generate a key. In fact, ftok uses both the file name and the least significant 8 bits of the ID value to generate a key.

Accessing a queue by its IPC key

Once both programs have generated the queue’s key, they can gain access to it using msgget. This function will return a queue ID, which will be used later to designate the key while the process uses the queue.

Note: if you’re using a private queue, there will be no key to use (since you’ll set it to IPC_PRIVATE). For this reason, once your main (father) process has gained access to the queue, the children process will need to use the ID returned by msgget to manipulate the queue.

The flags argument is used to specify the way the queue has to be created/accessed, and what permissions should be applied to it. These flags can be made up from the following values:

  1. IPC_CREAT, which will have the queue created if it does not exist yet.
  2. IPC_EXCL, which will cause msgget to fail if a queue already exists with the given key.
  3. An octal representation of permission bits.

Note: unlike files, message queues don’t need to be opened and closed. Once the queue ID is known, the program can access it directly. This ID can be shared between a process and its children.

Sending and receiving messages

The first thing you need to do is to define a structure for the queue’s messages. The first member of your structure must be a long variable. For instance, all the following structures are correct:

In a queue, all messages are ordered according to their mtype value. This value is usually described as the message priority: a message of type 1 is more important than a message of type 2. However, you may use this value as a message classifier. Some implementations actually give a specific number n to each process, and all messages of type n are considered to be destined to process n.

Once you have defined your structure, you can use msgsnd and msgrcv to send and receive messages through the queue:

A few things to note here:

  1. msgsnd uses the queue ID provided by msgget, not the key generated with ftok.
  2. The second argument is a pointer to the message structure defined earlier.
  3. The third argument is the size of this structure, not including the size of mtypeFor this reason, it is important to subtract sizeof(long) to the value!
  4. The last argument is another set of flags, usually set to 0. You can set it to IPC_NOWAIT if you want msgsnd to return immediately (non-blocking operation).

Now, receiving happens pretty much the same way:

Now, this call is a little trickier:

  1. The first three arguments are the same as msgsnd.
  2. The second argument is the message type.
  3. The third argument are the reception flags, which can be IPC_NOWAIT (non-blocking operation), MSG_NOERROR (will truncate the message if it does not fit in the structure), and/or MSG_EXCEPT.

The type argument and the MSG_EXCEPT flag actually work together:

  • If the message type is strictly superior to zero, then msgrcv will look for a message with a matching mtype. The MSG_EXCEPT flag will cause this behaviour to reverse, and have msgrcv find a message which does not have a matching mtype. If several messages match the criteria, one of them will be picked on a first-in-first-out basis.
  • If the message type is zeromsgrcv will return the oldest message in the queue (first in), regardless of their types.
  • If the message type is strictly inferior to zeromsgrcv will pick the message with the lowest priority inferior to the absolute value of the argument. For instance, passing -2 will retrieve a message of type 1 (prioritarily) or 2.

Destroying a queue

In order to destroy a queue, you must send it a IPC_RMID command. This can be done using the msgctl function:

IPC_RMID isn’t the only command you can send to a queue. The third argument is used as a parameter for two other commands, but in this case, it will be ignored (which is why I set it to NULL).

Manipulating queue metadata

Each queue has a specific set of parameters that define it. Just like the stat call for files, two msgctl commands allow you to retrieve and edit these parameters: IPC_STAT and IPC_SET. However, these two commands require you to pass a pointer to a msgid_ds structure as the third parameter of msgctlIPC_STAT will write to this structure, while IPC_SET will read from it. Here is its definition:

The definition of the ipc_perm structure can be found here. Now, you can use msgctl to retrieve this structure, edit it, and set it again:

And here you go, your queue now belongs to user 1000!

Using message queues

Message queues are a very powerful tool for inter-process communications, but it is important to note that it comes with no security whatsoever. For this reason, you must use separate components such as semaphores or mutexes to protect your queue from concurrency and corruption problems. With this in mind, you’re good to go!

I’ll try to post more articles concerning IPC (or just… more articles in general) in the future.