blog · git · desktop · images · contact & privacy · gopher


IPC: POSIX Message Queues

During systemd bootup, you can briefly see this:

[  OK  ] Mounted POSIX Message Queue File System.

Let’s have a look at that.

Background

There’s a set of IPC mechanisms available to userland processes, called System V IPC. If offers three important tools:

On Linux, you can have a closer look at them by reading “man svipc”.

This API is rather old. I’m not sure about exact dates here. Since it’s called “SystemV …”, I think it’s safe to assume that it was introduced in the 1980s or maybe 1990s.

Some years later, the interface was refined (if you will). POSIX specified the same set of mechanisms which was, of course, called POSIX IPC. Oracle says, this API was added to Solaris 7. That OS was released in 1998, so the “new” POSIX IPC API isn’t really bleeding edge, either.

The manpages for POSIX IPC are as follows:

Now, it’s 2016 and we all are used to a great level of abstraction in our daily programming, so I find it important to mention that many of these API calls are actually kernel features (on Linux) – not some third-party libraries. Have a look at “man 2 syscalls”. What’s missing is POSIX semaphores and POSIX shared memory. Turns out, they are implemented as file system objects on Linux.

Big problem is, while System V IPC is supported on many operating systems, POSIX API is not. Philip Semanchuk maintains a list of compatible operating systems. Especially, OpenBSD is not on that list. This blog post focuses on message queues, so it’s worth mentioning that OpenBSD has its own messaging system (built on top of sockets): imsg. Maybe more on that in a later posting.

Code example

Let’s see how to use them. Here’s a simple sender:

#include <fcntl.h>
#include <limits.h>
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

int
main(int argc, char **argv)
{
    mqd_t queue;
    struct mq_attr attrs;
    size_t msg_len;

    if (argc < 3)
    {
        fprintf(stderr, "Usage: %s <queuename> <message>\n", argv[0]);
        return 1;
    }

    queue = mq_open(argv[1], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR, NULL);
    if (queue == (mqd_t)-1)
    {
        perror("mq_open");
        return 1;
    }

    if (mq_getattr(queue, &attrs) == -1)
    {
        perror("mq_getattr");
        mq_close(queue);
        return 1;
    }

    msg_len = strlen(argv[2]);
    if (msg_len > LONG_MAX || (long)msg_len > attrs.mq_msgsize)
    {
        fprintf(stderr, "Your message is too long for the queue.\n");
        mq_close(queue);
        return 1;
    }

    if (mq_send(queue, argv[2], strlen(argv[2]), 0) == -1)
    {
        perror("mq_send");
        mq_close(queue);
        return 1;
    }

    return 0;
}

Let’s compile it and run it – message queue names have to start with a slash:

$ gcc -Wall -Wextra -o send send.c -lrt
$ ./send /mytest 'hello, world!'
$

Note that the program immediately returns. What happened to our message? Is it lost because nobody is listening? Let’s examine the queue.

On Linux (with systemd), POSIX message queues are represented in the file system under “/dev/mqueue”:

$ ls -al /dev/mqueue/
total 0
drwxrwxrwt  2 root root     60 May 16 09:40 .
drwxr-xr-x 18 root root  3,060 May 16 09:11 ..
-rw-------  1 void users    80 May 16 10:24 mytest

What’s in that file?

$ cat /dev/mqueue/mytest 
QSIZE:13         NOTIFY:0     SIGNO:0     NOTIFY_PID:0     
$

That’s not our message but it’s information about the queue. “QSIZE:13” indicates that there are 13 bytes waiting in the queue. That’s our “hello, world!”. It means the kernel has received our message and now holds it until someone wants to read it. Let’s do that:

#include <fcntl.h>
#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

int
main(int argc, char **argv)
{
    mqd_t queue;
    struct mq_attr attrs;
    char *msg_ptr;
    ssize_t recvd;
    size_t i;

    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s <queuename>\n", argv[0]);
        return 1;
    }

    queue = mq_open(argv[1], O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR, NULL);
    if (queue == (mqd_t)-1)
    {
        perror("mq_open");
        return 1;
    }

    if (mq_getattr(queue, &attrs) == -1)
    {
        perror("mq_getattr");
        mq_close(queue);
        return 1;
    }

    msg_ptr = calloc(1, attrs.mq_msgsize);
    if (msg_ptr == NULL)
    {
        perror("calloc for msg_ptr");
        mq_close(queue);
        return 1;
    }

    recvd = mq_receive(queue, msg_ptr, attrs.mq_msgsize, NULL);
    if (recvd == -1)
    {
        perror("mq_receive");
        return 1;
    }

    printf("Message: '");
    for (i = 0; i < (size_t)recvd; i++)
        putchar(msg_ptr[i]);
    printf("'\n");
}

Compilation and run:

$ gcc -Wall -Wextra -o recv recv.c -lrt
$ ./recv /mytest
Message: 'hello, world!'
$

There it is.

There’s also the option to let the kernel notify you on the arrival of new messages. This can be done using a signal or the kernel can spawn a new thread (!) in your process.

As you can see, the code does not include calls to “mq_unlink” to destroy the queue. That’s not much of a problem for this demo since you can remove it using “rm”:

$ rm /dev/mqueue/mytest
$ ls -al /dev/mqueue/
total 0
drwxrwxrwt  2 root root    40 May 16 10:51 .
drwxr-xr-x 18 root root 3,060 May 16 09:11 ..
$

Conclusion

When and why would you want to use POSIX message queues?

First of all, I think that the interface is really nice to use. It’s clear and simple. Compare the code above with code for UNIX domain sockets.

Other nice things: Communication happens in an asynchronous manner. You can drop a message and then be on your way. You don’t have to wait for a listener or a handshake. (Of course, this could also be considered a downside because you don’t really know if somebody actually picked up your message.) Finally, messages are delivered as a whole – no need for a read loop.

But then again, as said above, not all systems support this API. You also have to be absolutely sure that you won’t need networking support. POSIX message queues are local only.

IPC mechanisms are quite interesting. Upcoming topics: OpenBSD’s imsg, DBus (yeah, you can’t avoid that), and maybe *BSD’s kqueue. And once it gets merged: kdbus.