LINUX GAZETTE
...making Linux just a little more fun!
Exploring Message Queues, part 1
By Raghu J Menon

Message queues are one of the three IPC (Inter Process Communication) facilities provided by the UNIX operating system apart from semaphores and shared memory. Message queues appeared in an early release of UNIX system V release III as a means of passing messages between processes in an asynchronous way.

Let us look at what a message queue actually means. In order for two or more processes to communicate with each other, one of them places a message in the message queue through some message passing module of the operating system. This message placed can be read by some other process. However the access to the message by the process is preceded by a clause  that the queue and the process share a common key.

A short note on the ipcs command

The ipcs command displays the currently resident ipcs in the operating system. Try typing this command at the prompt, you will obtain an output similar to this

  ------ Shared Memory Segments --------
key shmid owner perms bytes nattch status

------ Semaphore Arrays --------
key semid owner perms nsems status

------ Message Queues --------
key msqid owner perms used-bytes messages

Our interest is in the last one. A short  description of each of the fields will come in handy as we proceed further,

 

Creating a Message Queue

All the IPCs are created using some ipcget() function. Message queues are created using the msgget() function. It takes  2 parameters, the key which signifies the name given to the queue and a flag variable. The flag variable can be either IPC_CREAT or IPC_EXCL. The first of these creates a queue if one does not already exist else it simply ignores the parameter. The second forces an error message to be flashed onto the screen declaring that a queue by that name exists and cloning is unethical in this part of the world. What does the function return? Well i suppose you guessed it, it returns the message queue id (similar to a file descriptor). Now go through the code below and try it on your computer: mesg1.c. The code creates a queue by name 10(This is passed as the first parameter). The key_t data type is nothing but int, do not be confused by it. Now how do you ascertain that a queue has indeed been created. Try the following command at your prompt i.e.  ipcs . This command provides you with data pertaining to the IPCs that are living in your system at present. Scan the message queue section, you see an entry which has a value of 10(in hex) under the key field and a value of 0(usually) under the id field. This entry corresponds to the queue that we created above. If you have any doubts regarding that try the command before running the program, and see for yourself the difference in the output generated.

------ Shared Memory Segments --------
key shmid owner perms bytes nattch status

------ Semaphore Arrays --------
key semid owner perms nsems status

------ Message Queues --------
key                  msqid     owner    perms     used-bytes messages
0x0000000a        0          root       666           0              0

Okay here is an exercise for you, try replacing the flag variable with  IPC_CREAT | IPC_EXCL, recompile and run the code. The result is obvious since a queue by that name already exists we will encounter an error message. Another point to be considered is the value returned by the msgget() function incase a queue cannot be created, as is the case when a queue by the id already exists and we use the IPC_EXCL flag in the call to msgget(). The return  value in such a case is a negative value. If we want the queue with the id to be created we need to remove the one already present with the same id, well just type the following at the command prompt ipcrm msg <id-number>.

Queue Permissions

A queue as created above is more of a church bell that can be rung by anyone. What i actually  mean is that just as files have permission fields that restrict their access and modification by users of the operating system, so do queues. To set the permission fields  for a message queue uses the flag parameter in the msgget() function along with IPC_CREAT and IPC_EXCL. To specify the permissions we need to OR IPC_CREAT with the value in octal that signifies the permission. Try out the code below: mesg.c There is no difference from the previous one except that the second field in the msgget() function has the value 0644 ORed with it. This queue is created with read-write mode for the owner and read only mode for everyone else. Thus we have a queue that is available for every user of the system but only granting read permission. A point to be noted in this context is that it is meaningless to have execute permission granted as a queue cannot be executed only a code can be executed). A read-write permission to all would mean to use the value 0666 instead of the one specified above. 

Where is the queue information stored?

For every queue that we create, the information is stored in the following structure.

The structure has been defined in the file bits/msg.h. For the purpose of file inclusion in our programs though we use the file sys/msg.h

/* Structure of record for one message inside the kernel.
The type `struct msg' is opaque. __time_t is of type long int. All the data 
types are defined in types.h header file*/
struct msqid_ds
{
struct ipc_perm msg_perm; /* structure describing operation permission */
__time_t msg_stime; /* time of last msgsnd (see below ) command */
unsigned long int __unused1;
__time_t msg_rtime; /* time of last msgrcv (see below) command */
unsigned long int __unused2;
__time_t msg_ctime; /* time of last change */
unsigned long int __unused3;
unsigned long int __msg_cbytes; /* current number of bytes on queue */
msgqnum_t msg_qnum; /* number of messages currently on queue */
msglen_t msg_qbytes; /* max number of bytes allowed on queue */
__pid_t msg_lspid; /* pid of last msgsnd() */
__pid_t msg_lrpid; /* pid of last msgrcv() */
unsigned long int __unused4;
unsigned long int __unused5;
};

The first element of the structure is another structure which has the following declaration in bits/ipc.h,for inclusion purpose we use the file sys/ipc.h.

/* Data structure used to pass permission information to IPC operations. */
struct ipc_perm
{
__key_t __key; /* Key. */
__uid_t uid; /* Owner's user ID. */
__gid_t gid; /* Owner's group ID. */
__uid_t cuid; /* Creator's user ID. */
__gid_t cgid; /* Creator's group ID. */
unsigned short int mode; /* Read/write permission. */
unsigned short int __pad1;
unsigned short int __seq; /* Sequence number. */
unsigned short int __pad2;
unsigned long int __unused1;
unsigned long int __unused2;
};
 

The ipc_perm is the structure dealing with user,group id's and permissions.

Controlling The Message Queue

A queue once created can be modified. This implies that the creator or an authorized user can change the permissions and characteristics of the queue. The function msgctl() is used for carrying out the modifications. The function has the following definition.

int msgctl(int msqid, int cmd, struct msqid_ds *queuestat )

The first parameter msqid  is the id of the queue that we intend to modify, the value must be one that already exists.

The cmd argument can be any one of the following.


The following c code will illustrate elements within the structure: qinfo.c The message queue id number is passed as a command line argument. This id is that of an already present queue, so select one from the output of the ipcs command. The msgctl() function fills in the structure pointed to by qstatus which is of type struct msqid_ds. The rest of the code just prints various characteristics of the queue. It will be a good idea to just compile and run the send.c code given at the end and then running the qinfo code.

With all the knowledge that we have gained so far it is time we started communicating with the queues, which is what they are used for.

Sending and Receiving Messages

In order to send and receive messages the UNIX based operating systems provide two functions msgsnd() to send messages and msgrcv() to receive messages. The definition of both the functions are as defined below.

int msgsnd (int msqid, const void *msgp, size_t msgsz,
          int msgflg);

int msgrcv (int msqid, void *msgp, size_t msgsz, long msgtyp,
          int msgflg);

Let us first look at the msgsnd() function, it takes 4 parameters, the first one is the queue id of an existing queue. The 2 argument is the msgp or message pointer that contains the address of the of a structure that holds the message and its type. This structure is described below.

struct message{
    long mtype; //The message type.
    char mesg [MSGSZ];//The message is of length MSGSZ.
};

The 3 parameter MSGSZ is the length of the message sent in bytes. The final parameter msgflg specifies the action to be taken if one or more of the following are true.

What are the actions to be taken in each of these cases?

The above fields are elements of  the msqid_ds structure. The msgrcv() function has an additional parameter in msgtype which is the received message's type as specified by the sending process. Only messages with matching priorities will be printed on the screen. Further explanation is provided in recv.c.

The ensuing programs will give you a perfect idea of what we have been talking till now. The code below presents the idea of message passing between 2 processes. send.c is the code that creates a message queue and puts a message into it. recv.c reads that message from the queue.

send.c

recv.c

How does send.c work?

The code begins by defining a structure msgbuf that will hold the message to be put in the queue. It contains 2 fields as explained earlier, the type field mtype and the message that is stored in an array mtext. A queue is then created using the msget function with a key value 10 and the flag parameter being IPC_CREAT|0666 whereby we give read permission to all users. We give a priority of 1 to the message by setting the mtype field as 1. We then copy the text "I am in the queue" into the array mtext which is our message array. We are ready to send the message to the queue that we just created by invoking the msgsnd() function with the IPC_NOWAIT option (check above for the explanation of the function). At each stage of a function call we check for errors using the perror() function.

 Now recv.c.

This is a straightforward code. This code too begins by defining a structure that will hold the message obtained from the queue. The code proceeds by creating a queue with the key value 10, if it already exists then the queue-id is obtained. A point to be noted here is that only those processes having the same key value as the one we had created in send.c can access the queue. You can draw analogy to  two people holding the key for the same lock. If one them locks it only he or the other person can open it, no one else can (you can of course break it open!). The msgrcv() function then acquires the message from the queue into rbuf which is then subsequently printed out. The fourth argument in msgrcv() is 1, could you figure out why? As explained earlier the program send.c had sent the message with priority 1, in order for the message in queue to be displayed on screen when recv.c is run it should have matching priority, this explains the reason why 1 is passed as the fourth parameter. The fifth parameter msgflag is 0 just ignore it (i say that because that is what is done) or you could do it the right way by specifying it to be IPC_NOWAIT|MSG_NOERROR , with this flag the receiver ignores the error that might be caused due to the inconsistency in the  length of the message received and the length parameter passed. If the received message is of greater length than MSGSZ an error is reported if MSG_NOERROR is not used. Try the ipcs command after running  send.c and later on running recv.c. The outputs will be similar to ones shown below.

After send.c:

------ Shared Memory Segments -------- 
key shmid owner perms bytes nattch status 
------ Semaphore Arrays --------
key semid owner perms nsems status 
------ Message Queues -------- 
key             msqid       owner perms used-bytes messages 
0x0000000a      65536       root  666   19         1 
After recv.c:
------ Shared Memory Segments -------- 
key shmid owner perms bytes nattch status 
------ Semaphore Arrays -------- 
key semid owner perms nsems status 
------Message Queues --------
key           msqid        owner   perms used-bytes messages
0x0000000a    65536        root      666        0            0 

Notice the difference in the fields used-bytes and messages. The message filled into the queue 10 by send.c was consumed by recv.c.

A good variation that you might try out is to check the effect of negative priority values. Modify send.c by entering into the queue more than one message (say 3) with priorities set to 1,2 and 3. Also modify recv.c by setting the priority field to -2 and later -3. What happened? By letting the priority field to be a negative value say -n the recv.c displays all the messages starting from priority 1,2,3.....n. Why do we need it? Well if you set n to be a very large number say 1000,we could get the queue emptied.

In future issues we will explore more complex applications of message queues.

 

[BIO] I am a final year student doing my Btech in Computer Science and Engineering at Government Engineering College Trichur, Kerala, India. For me knowledge is a ceaseless quest for truth.


Copyright © 2003, Raghu J Menon. Copying license https://www.linuxgazette.net/copying.html
Published in Issue 89 of Linux Gazette, April 2003