The acronym POSIX stands for Portable Operating System Interface. POSIX message queues are a means by which processes exchange data in the form of messages to accomplish their tasks. They enable processes to synchronise their reads and writes to speed up processes. POSIX message queues are distinct from System V messages. This article outlines all that POSIX message queues can do.
A program is a set of instructions written to accomplish a certain task — it needs to be executed to perform these specific tasks. A running program, one which is in execution, is called a process. A process contains all the information required by the program to run, such as the code text section, the variables associated with the code, the methods/functions, etc. Modern computers allow multiple processes to run at the same time. Often, in such an environment, processes may need to communicate with each other to accomplish their tasks. A process may depend on another process for some information pertinent to its completion or may want to access the same resources. As an example, two processes may want to read and write to the same shared space, and may need to synchronise their reads and writes. Such an operation requires a tool using which the processes can cooperate. Therefore, there is a need to implement mechanisms for these dependent processes to gain access to the information they require.
Inter Process Communication (IPC) involves the implementation of a set of such mechanisms like message passing or shared memory. IPC mechanisms are an efficient way of handling process synchronisation because they entail speeding up, and are an easy way to share common data and allow code segmentation with thread creation. Linux provides a platform that supports several IPC mechanisms such as pipes, FIFO, message queues, shared memory, semaphore and socket programming. Their usage varies according to the application requirements.
If you want to communicate between related processes, with parent-child relationship, then pipe is a suitable communication mechanism. However, in case we need to communicate between unrelated processes, FIFO can be used. Both the named (pipe) and unnamed (FIFO) pipes are half duplex and of zero buffering capacity. If a developer needs to store message based information and later wants to retrieve it, then we have to use message queues. The problem with message queues is that once you retrieve the message, the message is lost; so messages can’t be broadcast and two-way copying increases overhead from user space to kernel space, and vice versa. Shared memory can overcome these problems and is one of the fastest IPC mechanisms, since the information is stored only in the user space. In shared memory, synchronisation between write processes is a big issue, which creates a race condition. So, semaphore is used to synchronise the write access to the shared memory. All these IPC mechanisms can be used within a system, but to communicate between two different systems (with different IP addresses), socket programming is used.
Message queues can be used for communicating between processes or threads. Linux provides two choices for message queues — System V and POSIX. System V message queue is designed for server applications, whereas POSIX message queue is designed for real-time applications. The POSIX MQ has several advantages over the Sys V MQ implementation. Since it is a file based interface to the applications, we can set a priority for each message, which will determine the position of the message in the queue. Asynchronous notification of message arrivals can be done through signals or thread creation, and setting a timeout option will avoid indefinite waiting for sending or receiving messages. These features provide an efficient, predictable and deterministic mechanism to pass data between cooperating processes, which is very important for real-time applications.
In this article, we will discuss POSIX message queues in detail, including the creation of a message queue, sending and receiving a message, setting the priority of a message, and setting timeout to receive a message in the message queue.
System V message queue
Message queues in the System V API are associated with a message queue ID. These IDs are positive integers. The System V message queue is described by the struct msqid_ds structure specified in the header <sys/msg.h>. The members of the structure are shown in Figure 1.
ipc_perm msg_perm: This is a structure that describes the message queue access permissions.
msgqnum_t msg_qnum: This acknowledges to the process the number of messages that are currently in the message queue.
msglen_t msg_qbytes: This describes the ceiling size of messages that can be placed on the message queue. It is in bytes.
pid_t msg_lspid: This is the latest process’s ID that used the msgsnd() system call to send messages.
pid_t msg_lrpid: This is the latest process’s ID that used the msgrcv() system call in order to receive messages.
time _t msg_stime: Timestamp when the most recent msgsnd() system call was made.
time_tmsg_rtime: Timestamp when the most recent msgrcv() system call was made.
time_tmsg_ctime: Timestamp when the most recent change was made to a member of the msqid_ds structure.
System V messages have an associated length and message type unlike in pipes, where data is pumped through the pipe in a byte stream. Messages can be associated with message types, and the sending and receiving processes can place and retrieve these messages, respectively, by a specified message type.
A structure can be defined to describe the specifics of a message a process is trying to send. This structure is used to define the message buffer accessed and manipulated by the sending and receiving processes.
Struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[1]; /* message data */ };
After the creation of message queue, you can view the information by executing the command:
ipcs –q.
Figure 2 shows the message queue details and limitations.
By default, the ceiling of the amount of messages that a System V message queue can contain in Linux is 8192 messages with a maximum size of 16384 bytes. These values are optimised for better performance. But one can alter this information depending on the requirements by changing the kernel parameters, which are stored in /proc/sys/kernel/msgmax or msgmnb files.
Introduction to POSIX message queue
The library function and the corresponding system call details of the POSIX message queue’s API can be found in the mq_overview man page. The kind of message queue you use may depend largely on the purpose of the application. The POSIX standards are the latest, and allow for cleaner interfaces to exchange messages amongst processes.
Real-time applications provide deterministic and deadline constrained responses. The Linux OS provides real-time support based on the POSIX standards 1003.b and 1003.c for running applications in real time. The real-time functions are found in the librt library, which must be included while running the POSIX synchronisation applications that are discussed. While compiling the programs, we must include the –lrt option to include the librt library to use real-time extensions provided by POSIX standards.
POSIX message queues are created in the Linux virtual file system; i.e., POSIX message queues are file based. This message queue file system is mounted onto the system by default or you can mount by using the following commands in super user or sudo mode:
$ mkdir /dev/mqueue $ mount -t mqueue none /dev/mqueue
POSIX message queues have an associated structure called mq_attr; its members are shown in Figure 3. Message queues have attributes that can be accessed and set using the mq_getsetattr system call.
mq_flags: These are used to pass flags to the message queue. They can be used to block the message queue until messages arrive at the queue with the appropriate flags mentioned. It is set once the message queue is open using mq_open.
mq_maxmsg: This indicates the ceiling to the amount of messages that can be sent in the queue to the process.
mq_msgsize: The length of all the messages on the message queue in bytes is mentioned by this parameter.
mq_curmsgs: This returns the number of messages present in the message queue at that instant. When the queue is initialised, it is 0 and changes once it receives messages from the sending process or when the messages are eaten up by the receiving end.
The maximum number of messages that can be placed in the queue is 10 and the maximum size is 8192 bytes. This is mentioned in proc/sys/fs/mqueue/msg_max and proc/sys/fs/mqueue/msgsize_max.
Creating a POSIX message queue
The successful creation of a POSIX message queue returns a message queue descriptor. It returns -1 if there is an error.
The mq_open system call is used to, as the name suggests, open a message queue. It accepts four parameters — the first is of type char to specify the name of the queue, the flag attribute of which can accept O_RDONLY for receiving messages, O_WRONLY for sending messages and O_RDWR to read and write messages to the message queue. O_CREAT is used along with the other flags to create a message queue if it doesn’t already exist. The second parameter, O_NONBLOCK can be used to open the queue in non-blocking mode, where, by default the process blocks until it receives messages in the queue. The third parameter indicates the mode in which a process can access the message queue, i.e., the permissions that are given to a process to modify and alter the queue. The final parameter is a reference to the mq_attr.
Since the POSIX API allows the programmer to have control over the geometry of the queue, the attribute members of the structure mq_attr can be filled according to user requirement. However, attributes such as mq_maxmsg and mq_msgsize must be constrained to the values mentioned in /proc/sys/fs/mqueue/msg_max and proc/sys/fs/mqueue/msgsize_max. The message queue attribute is a structure that consists of four elements of type long integer — the flags for the queue, the ceiling amount of messages that can be allowed in the queue, the ceiling size of the message in bytes, and the number of messages available in the queue.
Here, we declare mq_create of type mqd_t, which is the message queue descriptor, and mq_ret and i of type in to test the return values of the mq_open call. We fill the attribute values of the mq_attr, setting the maximum messages in the queue as 10 and the maximum size of the messages as 8192 bytes.
In mq_open we pass the parameters — message queue name (/mymq, which needs to begin with a “/”); the flags O_CREAT (which creates the message queue if it doesn’t already exist) and O_RDWR to read and write from and to the message queue; the access permission and, finally, the queue attributes.
If the mq_open call returns successfully with a valid queue descriptor value, we print the message: “Message Q is successfully created ….”; otherwise, we print “Message Q is not created….” to indicate that the message queue creation has failed. Figure 4 shown the message creation program. The list of header files showed in the program is the same as in the remaining programs discussed in this article.
Send a message
Sending a message to a message queue involves the library function mq_send. This function accepts four parameters. The first parameter specifies the message queue descriptor pointing to a valid or pre-existing message queue, the second is a reference to a message pointed by a msg_ptr, the third describes the length of the message that is being sent to the receiving process, and the last is used to define the message that needs to be received from the queue depending on the priority of the message that has been mentioned.
The sending process blocks if the message queue has no space. It resumes when the queue contains enough memory for a new message. The messages in the message queue are stored in a way such that they are in a decreasing order of priority. If the O_NONBLOCK flag is specified in the mq_send system call, then the process terminates returning –1, setting the appropriate errno.
In the code snippet shown in Figure 5, we have opened a message queue that already exists — /mymq. The sending process opens the queue in write-only mode (O_WRONLY) and stores the descriptor in mq_snd_open. We then prompt the user to enter a message he/she intends to send to the queue and store it in a buffer buf. We define len as the length of the message being sent.
In the msg_send library function, we pass the message queue descriptor mq_snd_open, the buffer buf which stores the message, the length of the buffer+1 (which can be avoided by mentioning the null terminator at the end of the message) and the priority of the message being sent (which is 0 here). The return value of this call is stored in mq_ret, which returns 0 when it is successful (here we print “Message is sent successfully…”). Else, we print “Message is not sent ….” indicating that the call was unsuccessful.
Receiving a message
The mq_receive library function takes four parameters, the same as mq_send. This function blocks by default until the message queue is populated with messages. If NON_BLOCK is mentioned, then the process fails by setting the errno to EAGAIN. If the call is a success, it returns with the number of bytes of the message received; else, it fails with –1 and the errno is set.
The receiving process requires opening the message queue by providing the name of the message queue, in this case ‘/mymq’, which is opened in read-only mode (O_RDONLY). Once the message queue has been opened and the descriptor is stored in mq_rec_open, we pass this to the mq_receive system call along with the other parameters. The message in the queue is stored in the buffer buf of the receiving process, which is then printed (as shown in Figure 6).
Sending a message with priority
The most important advantage of using POSIX message queues is that messages can be prioritised. Processes may send or extract messages from the queue based on the priority. We shall explore how to make use of this feature in this section.
POSIX priority ranges from 0 – sysconf (_SC_MQ_PRIO_MAX)-1, where ‘sysconf(_SC_MQ_PRIO_MAX)-1’ is 32767. For applications implementing POSIX message queues, the message with the highest priority is delivered immediately to the receiving end. The system call eats up the message with the highest priority in the message queue referenced by the message queue descriptor that has been provided in the system call. The messages in the message queue are stored in such a way that they are in a decreasing order of priority.
In the code snippet given in Figure 7 of the sending process, we open the message queue that already exists in write-only mode (O_WRONLY). We prompt the user to send a message and a priority of the message that needs to be sent. The change made in the mq_send call is that the fourth parameter, indicating the priority, is now a valid integer prio. If the mq_ret call returns 0, the message has been sent successfully; else, it has failed and we print the appropriate messages.
Finally, in the receiving process we open the message queue in read-only mode (O_RDONLY). The only modification to the mq_receive system call made is the fourth parameter of the mq_receive of a particular priority. We then print the message received by the process stored in the buffer and the priority of the received message. Figure 8 depicts the way to receive a higher priority message.
Timed sending and receiving
Other variations of the send and receive functions are mq_timedreceive and mq_timedsend; an addition to these variations is the timespec structure. The timespec construct is defined, and can be accessed and used by the programmer to specify the time ceiling with nanosecond accuracy. These calls are used when we do not use the O_NONBLOCK flag; instead, we specify the time ceiling, which after a countdown blocks messages until there is enough space on the message queue to send them.
In blocking receive, the process blocks a message unless the message is made available in the queue, whereas in non-blocking receive, the process extracts a null or a message that is available from the queue.
In blocking send, the process blocks a message unless the message has been received by the receiving end, and in a non-blocking send, the process places the message on the message queue and terminates or performs another operation.
In the sending and receiving process, we have used mq_timedsend and mq_timedreceive by mentioning the wait time as 5 seconds. In this case, there is a timeout after 5 seconds if the message has not been received or sent.
The sending process opens the message queue in write-only mode. We use the clock_gettime() function and pass the CLOCK_REALTIME parameter to get the approximate time now.
Figure 9 explains the procedure to send messages to the message queue with timeout. In mq_timedsend, the first four parameters remain the same as mq_send, and the fifth added parameter is the time the sending process needs to wait for the receiving process to extract the message from the queue. If -1 is returned, we print the message “Message cannot be sent to the mq…”; else, we print the message “Message is sent successfully…” indicating that the message has been received successfully.
We follow a similar procedure for the mq_timedreceive system call at the receiving end, and mention the time needed for the process to wait before it needs to terminate without having extracted a message from the queue. Figure 10 shows the code for message receiving with the mq_timedreceive system call.
POSIX message queue is suitable for real-time applications since it has the features that can predict the behaviour of the data that communicates between processes. In this article, we have explained the importance of the POSIX message queue, including the procedures to create, send, receive, set priority, and set timeout features for it, along with demo programs.