Linux环境进程间通信——消息队列

参考:https://www.ibm.com/developerworks/cn/linux/l-ipc/part3/

消息队列(也叫做报文队列)能够克服早期unix通信机制的一些缺点。作为早期unix通信机制之一的信号能够传送的信息量有限,后来虽然POSIX 1003.1b在信号的实时性方面作了拓广,使得信号在传递信息量方面有了相当程度的改进,但是信号这种通信方式更像"即时"的通信方式,它要求接受信号的进程在某个时间范围内对信号做出反应,因此该信号最多在接受信号进程的生命周期内才有意义,信号所传递的信息是接近于随进程持续的概念(process-persistent),见附录 1;管道及有名管道及有名管道则是典型的随进程持续IPC,并且,只能传送无格式的字节流无疑会给应用程序开发带来不便,另外,它的缓冲区大小也受到限制。

消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向其中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的(参见附录 1)。

目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。考虑到程序的可移植性,新开发的应用程序应尽量使用POSIX消息队列。

在本系列专题的序(深刻理解Linux进程间通信(IPC))中,提到对于消息队列、信号灯、以及共享内存区来说,有两个实现版本:POSIX的以及系统V的。Linux内核(内核2.4.18)支持POSIX信号灯、POSIX共享内存区以及POSIX消息队列,但对于主流Linux发行版本之一redhad8.0(内核2.4.18),还没有提供对POSIX进程间通信API的支持,不过应该只是时间上的事。

因此,本文将主要介绍系统V消息队列及其相应API。 在没有声明的情况下,以下讨论中指的都是系统V消息队列。

一、消息队列基本概念

  1. 系统V消息队列是随内核持续的,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。因此系统中记录消息队列的数据结构(struct ipc_ids msg_ids)位于内核中,系统中的所有消息队列都可以在结构msg_ids中找到访问入口。
  2. 消息队列就是一个消息的链表。每个消息队列都有一个队列头,用结构struct msg_queue来描述(参见 附录 2)。队列头中包含了该消息队列的大量信息,包括消息队列键值、用户ID、组ID、消息队列中消息数目等等,甚至记录了最近对消息队列读写进程的ID。读者可以访问这些信息,也可以设置其中的某些信息。
  3. 下图说明了内核与消息队列是怎样建立起联系的:
    其中:struct ipc_ids msg_ids是内核中记录消息队列的全局数据结构;struct msg_queue是每个消息队列的队列头。

从上图可以看出,全局数据结构 struct ipc_ids msg_ids 可以访问到每个消息队列头的第一个成员:struct kern_ipc_perm;而每个struct kern_ipc_perm能够与具体的消息队列对应起来是因为在该结构中,有一个key_t类型成员key,而key则唯一确定一个消息队列。kern_ipc_perm结构如下:

struct kern_ipc_perm{   //内核中记录消息队列的全局数据结构msg_ids能够访问到该结构;
key_t   key;    //该键值则唯一对应一个消息队列
uid_t   uid;
gid_t   gid;
uid_t   cuid;
gid_t   cgid;
mode_t  mode;
unsigned long seq;
}

二、操作消息队列


对消息队列的操作无非有下面三种类型:

1、 打开或创建消息队列
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述字,只需提供该消息队列的键值即可;

注:消息队列描述字是由在系统范围内唯一的键值生成的,而键值可以看作对应系统内的一条路经。

2、 读写操作

消息读写操作非常简单,对开发人员来说,每个消息都类似如下的数据结构:

struct msgbuf{
long mtype;  // 必须大于0
char mtext[1];
};

mtype成员代表消息类型,从消息队列中读取消息的一个重要依据就是消息的类型;mtext是消息内容,当然长度不一定为1。因此,对于发送消息来说,首先预置一个msgbuf缓冲区并写入消息类型和内容,调用相应的发送函数即可;对读取消息来说,首先分配这样一个msgbuf缓冲区,然后把消息读入该缓冲区即可。

3、 获得或设置消息队列属性:

消息队列的信息基本上都保存在消息队列头中,因此,可以分配一个类似于消息队列头的结构(struct msqid_ds,见 附录 2),来返回消息队列的属性;同样可以设置该数据结构。





消息队列API

1、文件名到键值

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok (char*pathname, char proj);

它返回与路径pathname相对应的一个键值。该函数不直接对消息队列操作,但在调用ipc(MSGGET,…)或msgget()来获得消息队列描述字前,往往要调用该函数。典型的调用代码是:

key=ftok(path_ptr, 'a');
    ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0);
    …
The  ftok() function uses the identity of the file named by the given pathname (which must refer to an existing, accessible file) and the least
       significant 8 bits of proj_id (which must be non-zero) to generate a key_t type System V IPC key, suitable for use with  msgget(2),  semget(2),
       or shmget(2).

       The resulting value is the same for all pathnames that name the same file, when the same value of proj_id is used. The value returned should be
       different when the (simultaneously existing) files or the project IDs differ.

2、linux为操作系统V进程间通信的三种方式(消息队列、信号灯、共享内存区)提供了一个统一的用户界面:
int ipc(unsigned int call, int first, int second, int third, void *ptr, longfifth);

第一个参数指明对IPC对象的操作方式,对消息队列而言共有四种操作:MSGSND、MSGRCV、MSGGET以及MSGCTL,分别代表向消息队列发送消息、从消息队列读取消息、打开或创建消息队列、控制消息队列;first参数代表唯一的IPC对象;下面将介绍四种操作。

  • int ipc( MSGGET, int first, int second,int third,void *ptr,long fifth);
    与该操作对应的系统V调用为:int msgget( (key_t)first,second)。
  • int ipc( MSGCTL, int first, int second,int third,void *ptr,long fifth)
    与该操作对应的系统V调用为:int msgctl( first,second, (struct msqid_ds*) ptr)。
  • int ipc( MSGSND, int first, int second,int third,void *ptr,long fifth);
    与该操作对应的系统V调用为:int msgsnd( first, (struct msgbuf*)ptr, second, third)。
  • int ipc( MSGRCV, int first, int second,intthird,void *ptr,long fifth);
    与该操作对应的系统V调用为:int msgrcv( first,(struct msgbuf*)ptr, second, fifth,third),

注:本人不主张采用系统调用ipc(),而更倾向于采用系统V或者POSIX进程间通信API。原因如下:

  • 虽然该系统调用提供了统一的用户界面,但正是由于这个特性,它的参数几乎不能给出特定的实际意义(如以first、second来命名参数),在一定程度上造成开发不便。
  • 正如ipc手册所说的:ipc()是linux所特有的,编写程序时应注意程序的移植性问题;
  • 该系统调用的实现不过是把系统V IPC函数进行了封装,没有任何效率上的优势;
  • 系统V在IPC方面的API数量不多,形式也较简洁。

3.系统V消息队列API
系统V消息队列API共有四个,使用时需要包括几个头文件:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>


1)int msgget(key_t key, int msgflg)

参数key是一个键值,由ftok获得;msgflg参数是一些标志位。该调用返回与健值key相对应的消息队列描述字。

在以下两种情况下,该调用将创建一个新的消息队列:

  • 如果没有消息队列与健值key相对应,并且msgflg中包含了IPC_CREAT标志位;
  • key参数为IPC_PRIVATE;

参数msgflg可以为以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果。

IPC_CREAT 新建(如果已创建则返回目前消息队列的id)

IPC_PRIVATE  isn't  a flag field but a key_t type.  If this special value is used for key, the system call ignores everything but the least sig-
       nificant 9 bits of msgflg and creates a new message queue (on success).

If msgflg specifies both IPC_CREAT and IPC_EXCL and a message queue already exists for key, then msgget() fails with errno set to EEXIST.  (This
       is analogous to the effect of the combination O_CREAT | O_EXCL for open(2).)

Upon  creation,  the  least significant bits of the argument msgflg define the permissions of the message queue.  These permission bits have the
       same format and semantics as the permissions specified for the mode argument of open(2).  (The execute permissions are not used.)

If a new message queue is created, then its associated data structure msqid_ds (see msgctl(2)) is initialised as follows:
              msg_perm.cuid and msg_perm.uid are set to the effective user ID of the calling process.
              msg_perm.cgid and msg_perm.gid are set to the effective group ID of the calling process.
              The least significant 9 bits of msg_perm.mode are set to the least significant 9 bits of msgflg.
              msg_qnum, msg_lspid, msg_lrpid, msg_stime and msg_rtime are set to 0.
              msg_ctime is set to the current time.
              msg_qbytes is set to the system limit MSGMNB.

调用返回:成功返回消息队列描述字,否则返回-1。

注:参数key设置成常数IPC_PRIVATE并不意味着其他进程不能访问该消息队列,只意味着即将创建新的消息队列。

2)int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg);
向msgid代表的消息队列发送一个消息,即将发送的消息存储在msgp指向的msgbuf结构中,消息的大小由msgze指定。

msgflg标志:

0,表示忽略标志位

IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待。造成msgsnd()等待的条件有两种:

  • 当前消息的大小与当前消息队列中的字节数之和超过了消息队列的总容量;
  • 当前消息队列的消息数(单位"个")不小于消息队列的总容量(单位"字节数"),此时,虽然消息队列中的消息数目很多,但基本上都只有一个字节。

msgsnd()解除阻塞的条件有三个:

  1. 不满足上述两个条件,即消息队列中有容纳该消息的空间;
  2. msqid代表的消息队列被删除;
  3. 调用msgsnd()的进程被信号中断;

struct msgbuf{
long mtype;  // 必须大于0
char mtext[1];
};

The mtext field is an array (or other structure) whose size is specified by msgsz, a non-negative integer value.  Messages of zero length (i.e.,

       no  mtext  field)  are permitted.  The mtype field must have a strictly positive integer value.  This value can be used by the receiving process
       for message selection (see the description of msgrcv() below).


The calling process must have write permission on the message queue in order to send a message.

 Messages  of  zero  length  (i.e.,  no  mtext  field) are permitted.  The mtype field must havea strictly positive integer value.  This value can be used by the receiving
       process for message selection.

If insufficient space is available in the queue, then the default behaviour of msgsnd() is to block until space  becomes
       available.  If IPC_NOWAIT is specified in msgflg, then the call instead fails with the error EAGAIN.

msgsnd and msgrcv are never automatically restarted after being  interrupted by a signal handler, regardless of the setting  of the SA_RESTART flag when establishing a signal handler.

Upon successful completion the message queue data structure is updated as follows:


              msg_lspid is set to the process ID of the calling process.


              msg_qnum is incremented by 1.


              msg_stime is set to the current time.

调用返回:成功返回0,否则返回-1。

The following limits on message queue resources affect the msgsnd() call:

       MSGMAX     Maximum size for a message text: 8192 bytes (on Linux, this limit can be read and modified via /proc/sys/kernel/msgmax).

       MSGMNB     Default  maximum  size  in  bytes  of a message queue: 16384 bytes (on Linux, this limit can be read and modified via /proc/sys/ker-
                  nel/msgmnb).  The superuser can increase the size of a message queue beyond MSGMNB by a msgctl() system call.

       The implementation has no intrinsic limits for the system wide maximum number of message headers (MSGTQL) and for the system wide maximum  size

       in bytes of the message pool (MSGPOOL).


3)int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg);
该系统调用从msgid代表的消息队列中读取一个消息,并把消息存储在msgp指向的msgbuf结构中。

msqid为消息队列描述字;消息返回后存储在msgp指向的地址,msgsz指定msgbuf的mtext成员的长度(即消息内容的长度),msgtyp为请求读取的消息类型;读消息标志msgflg可以为以下几个常值的或:

  • IPC_NOWAIT 如果没有满足条件的消息,调用立即返回,此时,errno=ENOMSG
  • IPC_EXCEPT 与msgtyp>0配合使用,返回队列中第一个类型不为msgtyp的消息
  • IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的msgsz字节,则把该消息截断,截断部分将丢失。

msgrcv手册中详细给出了消息类型取不同值时(>0; <0; =0),调用将返回消息队列中的哪个消息。

msgrcv()解除阻塞的条件有三个:

  1. 消息队列中有了满足条件的消息;
  2. msqid代表的消息队列被删除;
  3. 调用msgrcv()的进程被信号中断;
The  argument  msgsz specifies the maximum size in bytes for the member mtext of the structure pointed to by the msgp argument. If the message
       text has length greater than msgsz
, then the behaviour depends on whether MSG_NOERROR is specified in msgflg.   If  MSG_NOERROR  is  specified,
       then  the message text will be truncated (and the truncated part will be lost); if MSG_NOERROR is not specified, thenthe message isn't removed

       from the queue and the system call fails returning -1 with errno set to E2BIG.

The argument msgtyp specifies the type of message requested as follows:
              If msgtyp is 0, then the first message in the queue is read.
              If msgtyp is greater than 0, then the first message in the queue of type msgtyp is read, unless MSG_EXCEPT was specified in  msgflg,  in
              which case the first message in the queue of type not equal to msgtyp will be read.
              If  msgtyp  is  less than 0, then the first message in the queue with the lowest type less than or equal to the absolute value of msgtyp
              will be read.

The msgflg argument is a bit mask constructed by ORing together zero or more of the following flags:
       IPC_NOWAIT
              Return immediately if no message of the requested type is in the queue.  The system call fails with errno set to ENOMSG.
       MSG_EXCEPT
              Used with msgtyp greater than 0 to read the first message in the queue with message type that differs from msgtyp.
       MSG_NOERROR
              To truncate the message text if longer than msgsz bytes.

       If no message of the requested type is available and IPC_NOWAIT isn't specified in msgflg, the calling process is blocked until one of the fol-
       lowing conditions occurs:

              A message of the desired type is placed in the queue.

              The message queue is removed from the system.  In this case the system call fails with errno set to EIDRM.

              The calling process catches a signal.  In this case the system call fails with errno set to EINTR.

       Upon successful completion the message queue data structure is updated as follows:

              msg_lrpid is set to the process ID of the calling process.

              msg_qnum is decremented by 1.

              msg_rtime is set to the current time.

调用返回:成功返回读出消息的实际字节数,否则返回-1。

4)int msgctl(int msqid, int cmd, struct msqid_ds *buf);
该系统调用对由msqid标识的消息队列执行cmd操作,共有三种cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。

  1. IPC_STAT:该命令用来获取消息队列信息,返回的信息存贮在buf指向的msqid结构中,调用者必须有读权限;
  2. IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf指向的msqid结构中;可设置属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同时,也影响msg_ctime成员。
  3. IPC_RMID:删除msqid标识的消息队列;
The msqid_ds data structure is defined in <sys/msg.h> as follows:

           struct msqid_ds {
               struct ipc_perm msg_perm;     /* Ownership and permissions
               time_t         msg_stime;    /* Time of last msgsnd() */
               time_t         msg_rtime;    /* Time of last msgrcv() */
               time_t         msg_ctime;    /* Time of last change */
               unsigned long  __msg_cbytes; /* Current number of bytes in
                                               queue (non-standard) */
               msgqnum_t      msg_qnum;     /* Current number of messages
                                               in queue */
               msglen_t       msg_qbytes;   /* Maximum number of bytes
                                               allowed in queue */
               pid_t          msg_lspid;    /* PID of last msgsnd() */
               pid_t          msg_lrpid;    /* PID of last msgrcv() */
           };

       The ipc_perm structure is defined in <sys/ipc.h> as follows (the highlighted fields are settable using IPC_SET):

           struct ipc_perm {
               key_t key;            /* Key supplied to msgget() */
               uid_t uid;            /* Effective UID of owner */
               gid_t gid;            /* Effective GID of owner */
               uid_t cuid;           /* Effective UID of creator */
               gid_t cgid;           /* Effective GID of creator */
               unsigned short mode;  /* Permissions */
               unsigned short seq;   /* Sequence number */
           };

调用返回:成功返回0,否则返回-1。

三、消息队列的限制

每个消息队列的容量(所能容纳的字节数)都有限制,该值因系统不同而不同。在后面的应用实例中,输出了redhat 8.0的限制。

另一个限制是每个消息队列所能容纳的最大消息数:在redhad 8.0中,该限制是受消息队列容量制约的:消息个数要小于消息队列的容量(字节数)。

注:上述两个限制是针对每个消息队列而言的,系统对消息队列的限制还有系统范围内的最大消息队列个数,以及整个系统范围内的最大消息数。一般来说,实际开发过程中不会超过这个限制。

四、消息队列应用实例

消息队列应用相对较简单,下面实例基本上覆盖了对消息队列的所有操作,同时,程序输出结果有助于加深对前面所讲的某些规则及消息队列限制的理解。

msgq_send.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <unistd.h>
#include <time.h>

void msg_stat(int,struct msqid_ds );

int main()
{
    int gflags, sflags;
    key_t key;
    int msgid;
    int reval;
    struct msgsbuf
    {
            int mtype;
            char mtext[1024];
    }msg_sbuf;
    struct msqid_ds msg_ginfo;
    char* msgpath = "./msgqueuetest";
    
    key = ftok( msgpath, 'a' );
    gflags = IPC_CREAT;
    msgid = msgget( key, gflags|00666 );
    if( msgid == -1 )
    {
        printf("msg create error\n");
        return -1;
    }
    
    //获取一个消息队列后,输出消息队列缺省属性
    printf( "---------------------create a message queue msgget\n" );
    msg_stat( msgid, msg_ginfo );

    printf( "enter some message to send( no blank space ):" );
    scanf( "%s", msg_sbuf.mtext );
    printf( "message to send: %s\n", msg_sbuf.mtext );
    
    sflags = 0;
    msg_sbuf.mtype = 10;
    //msg_sbuf.mtext[0] = 'a';
    reval = msgsnd(msgid, &msg_sbuf, sizeof( msg_sbuf.mtext ), sflags );
    if ( reval == -1 )
    {
        printf("message send error\n");
    }
    
    return 0;
}
void msg_stat( int msgid, struct msqid_ds msg_info )
{
    int reval;
    sleep(1);//只是为了后面输出时间的方便
    reval = msgctl( msgid, IPC_STAT, &msg_info );
    if ( reval == -1 )
    {
        printf("get msg info error\n");
        return;
    }
    printf( "\n" );
    printf( "current number of bytes on queue is %lu\n", msg_info.msg_cbytes );
    printf( "number of messages in queue is %d\n", ( int )msg_info.msg_qnum );
    printf( "max number of bytes on queue is %d\n", ( int )msg_info.msg_qbytes );
    // 每个消息队列的容量(字节数)都有限制MSGMNB,
    // 值的大小因系统而异。在创建新的消息队列时,
    // msg_qbytes的缺省值就是MSGMNB
    printf( "pid of last msgsnd is %d\n",msg_info.msg_lspid );
    printf( "pid of last msgrcv is %d\n",msg_info.msg_lrpid );
    printf( "last msgsnd time is %s", ctime(&(msg_info.msg_stime)) );
    printf( "last msgrcv time is %s", ctime(&(msg_info.msg_rtime)) );
    printf( "last change time is %s", ctime(&(msg_info.msg_ctime)) );
    printf( "msg uid is %d\n", msg_info.msg_perm.uid );
    printf( "msg gid is %d\n", msg_info.msg_perm.gid );
}
msgq_recv.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <unistd.h>
#include <time.h>

void msg_stat(int,struct msqid_ds );

int main()
{
    int gflags, rflags;
    key_t key;
    int msgid;
    int reval;
    struct msgmbuf
    {
        int mtype;
        char mtext[1024];
    }msg_rbuf;
    struct msqid_ds msg_ginfo;
    char* msgpath = "./msgqueuetest";
    
    key = ftok(msgpath,'a');
    gflags = IPC_CREAT;
    msgid = msgget( key, gflags|00666 );
    if( msgid == -1 )
    {
        printf("msg create error\n");
        return -1;
    }
    
    //创建一个消息队列后,输出消息队列缺省属性
    printf( "---------------------create a message queue msgget\n" );
    msg_stat( msgid, msg_ginfo );
    
    rflags = 0;
    reval = msgrcv( msgid, &msg_rbuf, 1024, 10, rflags );
    if ( reval == -1 )
        printf( "read msg error\n" );
    else
        printf( "read from msg queue %d bytes, received message: %s\n", reval, msg_rbuf.mtext );

    printf( "---------------------msgrcv\n" );
    msg_stat( msgid, msg_ginfo );
    
    // 删除消息队列
    reval = msgctl( msgid, IPC_RMID, NULL );
    if ( reval == -1 )
    {
        printf("unlink msg queue error\n");
        return -1;
    }
    printf( "unlink msg queue success\n" );
    return 0;
}
void msg_stat( int msgid, struct msqid_ds msg_info )
{
    int reval;
    sleep(1);//只是为了后面输出时间的方便
    reval = msgctl( msgid, IPC_STAT, &msg_info );
    if ( reval == -1 )
    {
        printf("get msg info error\n");
        return;
    }
    printf( "\n" );
    printf( "current number of bytes on queue is %lu\n", msg_info.msg_cbytes );
    printf( "number of messages in queue is %d\n", ( int )msg_info.msg_qnum );
    printf( "max number of bytes on queue is %d\n", ( int )msg_info.msg_qbytes );
    // 每个消息队列的容量(字节数)都有限制MSGMNB,
    // 值的大小因系统而异。在创建新的消息队列时,
    // msg_qbytes的缺省值就是MSGMNB
    printf( "pid of last msgsnd is %d\n",msg_info.msg_lspid );
    printf( "pid of last msgrcv is %d\n",msg_info.msg_lrpid );
    printf( "last msgsnd time is %s", ctime(&(msg_info.msg_stime)) );
    printf( "last msgrcv time is %s", ctime(&(msg_info.msg_rtime)) );
    printf( "last change time is %s", ctime(&(msg_info.msg_ctime)) );
    printf( "msg uid is %d\n", msg_info.msg_perm.uid );
    printf( "msg gid is %d\n", msg_info.msg_perm.gid );
}
上述两程序分别编译

gcc -g -Wall -Werror ./msgq_send.c -o msgq_send

gcc -g -Wall -Werror ./msgq_recv.c -o ./msgq_recv

touch ./msgqueuetest

运行发送端:

$ ./msgq_send
---------------------create a message queue msgget

current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16384
pid of last msgsnd is 0
pid of last msgrcv is 0
last msgsnd time is Thu Jan  1 08:00:00 1970
last msgrcv time is Thu Jan  1 08:00:00 1970
last change time is Sun Apr  6 15:44:08 2014
msg uid is 1007
msg gid is 1007
enter some message to send( no blank space ):messagequeuetest
message to send: messagequeuetest
查看

$ ipcs

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x62020001 0          501       666        220        0                       

------ Semaphore Arrays --------
key        semid      owner      perms      nsems     
0x620201f9 0          501      666        2         

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x6102cb07 294912     lfm        666        1024         1 
运行接收端:

$ ./msgq_recv
---------------------create a message queue msgget

current number of bytes on queue is 1024
number of messages in queue is 1
max number of bytes on queue is 16384
pid of last msgsnd is 4352
pid of last msgrcv is 0
last msgsnd time is Sun Apr  6 15:44:17 2014
last msgrcv time is Thu Jan  1 08:00:00 1970
last change time is Sun Apr  6 15:44:08 2014
msg uid is 1007
msg gid is 1007
read from msg queue 1024 bytes, received message: messagequeuetest
---------------------msgrcv

current number of bytes on queue is 0
number of messages in queue is 0
max number of bytes on queue is 16384
pid of last msgsnd is 4352
pid of last msgrcv is 4354
last msgsnd time is Sun Apr  6 15:44:17 2014
last msgrcv time is Sun Apr  6 15:44:42 2014
last change time is Sun Apr  6 15:44:08 2014
msg uid is 1007
msg gid is 1007
unlink msg queue success

消息队列与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使用。这两点是管道以及有名管道所不能比的。同样,消息队列可以在几个进程间复用,而不管这几个进程是否具有亲缘关系,这一点与有名管道很相似;但消息队列是随内核持续的,与有名管道(随进程持续)相比,生命力更强,应用空间更大。

附录 1

 在参考文献[1]中,给出了IPC随进程持续、随内核持续以及随文件系统持续的定义:

(1) 随进程持续的(process-persistent)IPC对象一直存在到打开着该对象的最后一个进程关闭该对象为止。例如管道和FIFO就是这种对象。

(2) 随内核持续的(kernel-persistent)IPC对象一直存在到内核重新自举或显式删除该对象为止。例如System V的消息队列、信号量和共享内存区就是此类对象。Posix的消息队列、信号量和共享内存区必须至少是随内核持续的,但也可以是随文件系统持续的,具体取决于实现。

(3) 随文件系统持续的(filesystem-persistent)IPC对象一直存在到显式删除该对象为止。即使内核重新自举了,该对象还是保持其值。Posix消息队列、信号量和共享内存区如果是使用映射文件实现的(不是必需条件),那么它们就是随文件系统持续的。mmap由于是映射的文件,随文件系统持续。

在定义一个IPC对象的持续性时我们必须小心,因为它并不总是像看起来的那样。例如管道内的数据是在内核中维护的,但管道具备的是随进程的持续性而不是随内核的持续性:最后一个将某个管道打开着用于读的进程关闭该管道后,内核将丢弃所有的数据并删除该管道。类似地,尽管FIFO在文件系统中有名字,它们也只是具备随进程的持续性,因为最后一个将某个FIFO打开着的进程关闭该FIFO后,FIFO中的数据都被丢弃。

附录 2

结构msg_queue用来描述消息队列头,存在于系统空间:

struct msg_queue {
    struct kern_ipc_perm q_perm;
    time_t q_stime;         /* last msgsnd time */
    time_t q_rtime;         /* last msgrcv time */
    time_t q_ctime;         /* last change time */
    unsigned long q_cbytes;     /* current number of bytes on queue */
    unsigned long q_qnum;       /* number of messages in queue */
    unsigned long q_qbytes;     /* max number of bytes on queue */
    pid_t q_lspid;          /* pid of last msgsnd */
    pid_t q_lrpid;          /* last receive pid */
    struct list_head q_messages;
    struct list_head q_receivers;
    struct list_head q_senders;
};


结构msqid_ds用来设置或返回消息队列的信息,存在于用户空间;

struct msqid_ds {
    struct ipc_perm msg_perm;
    struct msg *msg_first;      /* first message on queue,unused  */
    struct msg *msg_last;       /* last message in queue,unused */
    __kernel_time_t msg_stime;  /* last msgsnd time */
    __kernel_time_t msg_rtime;  /* last msgrcv time */
    __kernel_time_t msg_ctime;  /* last change time */
    unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */
    unsigned long  msg_lqbytes; /* ditto */
    unsigned short msg_cbytes;  /* current number of bytes on queue */
    unsigned short msg_qnum;    /* number of messages in queue */
    unsigned short msg_qbytes;  /* max number of bytes on queue */
    __kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */
    __kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};

//可以看出上述两个结构很相似。

参考资料

  • UNIX网络编程第二卷:进程间通信,作者:W.Richard Stevens,译者:杨继张,清华大学出版社。对POSIX以及系统V消息队列都有阐述,对Linux环境下的程序开发有极大的启发意义。

  • linux内核源代码情景分析(上),毛德操、胡希明著,浙江大学出版社,给出了系统V消息队列相关的源代码分析。

  • http://www.fanqiang.com/a4/b2/20010508/113315.html,主要阐述linux下对文件的操作,详细介绍了对文件的存取权限位,对IPC对象的存取权限同样具有很好的借鉴意义。

  • msgget、msgsnd、msgrcv、msgctl手册
展开阅读全文

没有更多推荐了,返回首页