一个或多个进程可向消息队列写入消息,而一个或多个进程可从消息队列中读取消息,这种进程间通讯机制通常使用在客户/服务器模型中,客户向服务器发送请求消息,服务器读取消息并执行相应请求。在许多微内核结构的操作系统中,内核和各组件之间的基本通讯方式就是消息队列。例如,在 MINIX 操作系统中,内核、I/O 任务、服务器进程和用户进程之间就是通过消息队列实现通讯的。
Linux中的消息可以被描述成在内核地址空间的一个内部链表,每一个消息队列由一个IPC的标识号唯一的标识。Linux 为系统中所有的消息队列维护一个 msgque 链表,该链表中的每个指针指向一个 msgid_ds 结构,该结构完整描述一个消息队列。
System V消息队列是Open Group定义的XSI,不属于POSIX标准。System V IPC的历史相对很早,在上个世70年代后期有贝尔实验室的分支机构开发,80年代加入System V的系统内核中,后来商用UNIX系统基本都加入了System V IPC的功能。
System V消息队列相对于POSIX消息队列的区别主要是:
(1)消息缓冲区(msgbuf)
我们在这里要介绍的第一个数据结构是msgbuf结构,可以把这个特殊的数据结构看成一个存放消息数据的模板,它在include/linux/msg.h中声明,描述如下:
/* msgsnd 和msgrcv 系统调用使用的消息缓冲区*/
struct msgbuf {
long mtype; /* 消息的类型,必须为正数 */
char mtext[1]; /* 消息正文 */
};
注意:对于消息数据元素(mtext),不要受其描述的限制。实际上,这个域(mtext)不仅能保存字符数组,而且能保存任何形式的任何数据。这个域本身是任意的,因为这个结构本身可以由应用程序员重新定义:
struct my_msgbuf {
long mtype; /* 消息类型 */
long request_id; /* 请求识别号 */
struct client info; /* 客户消息结构 */
};
我们看到,消息的类型还是和前面一样,但是结构的剩余部分由两个其它的元素代替,而且有一个是结构。这就是消息队列的优美之处,内核根本不管传送的是什么样的数据,任何信息都可以传送。
但是,消息的长度还是有限制的,在Linux中,给定消息的最大长度在include/linux/msg.h中定义如下:
#define MSGMAX 8192 /* max size of message(bytes) */
消息总的长度不能超过8192字节,包括mtype域,它是4字节长。
(2)消息结构(msg)
内核把每一条消息存储在以msg结构为框架的队列中,它在include/ linux/msg.h中定义如下:
struct msg {
struct msg *msg_next; /* 队列上的下一条消息 */
long msg_type; /*消息类型*/
char *msg_spot; /* 消息正文的地址 */
short msg_ts; /* 消息正文的大小 */
};
注意:msg_next是指向下一条消息的指针,它们在内核地址空间形成一个单链表。
(3)消息队列结构(msgid_ds)
当在系统中创建每一个消息队列时,内核创建、存储及维护这个结构的一个实例。
struct msqid_ds
{
struct ipc_perm msg_perm; /*IPC对象的属性信息和访问权限 */
__time_t msg_stime; /* time of last msgsnd command */
__time_t msg_rtime; /* time of last msgrcv command */
__time_t msg_ctime; /* time of last change */
unsigned long int __msg_cbytes; /* 当前队列中消息的字节数 */
msgqnum_t msg_qnum; /* 当前队列中消息的个数 */
msglen_t msg_qbytes; /* 队列允许存放的最大字节数 */
__pid_t msg_lspid; /* pid of last msgsnd() */
__pid_t msg_lrpid; /* pid of last msgrcv() */
//下面是保留字段
#if __WORDSIZE == 32
unsigned long int __unused1;
unsigned long int __unused2;
unsigned long int __unused3;
#endif
unsigned long int __unused4;
unsigned long int __unused5;
};
System V消息队列的创建和使用会使用下面的函数接口:
#include
int msgget(key_t key, int oflg);
//成功返回非负消息队列描述符,失败返回-1
key:消息队列的键,用来创建一个消息队列。System IPC都有一个key,作为IPC的外部标识符,创建成功后返回的描述符作为IPC的内部标识符使用。key的主要目的就是使不同进程在同一IPC汇合。key具体说可以有三种方式生成:
oflg:指定创建或打开消息队列的标志和读写权限(ipc_perm中的mode成员)。我们知道System V IPC定义了自己的操作标志和权限设置标志,而且都是通过该参数传递,这和open函数存在差别,open函数第三个参数mode用于传递文件的权限标志。System V IPC的操作标志包含:IPC_CREAT,IPC_EXCL,权限设置标志如下图:
下面是创建消息队列的测试代码:
#include
#include
#include
#include
#include
#include
using namespace std;
#define PATH_NAME "/tmp/anonymQueue"
int main(int argc, char **argv)
{
key_t key;
int fd;
if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
{
cout<<"open file "<
结果:
实现System V IPC的任何系统都提供两个特殊的程序ipcs和ipcrm。ipcs输出IPC的各种信息,ipcrm则用于删除各种System V IPC。由于System V IPC不属于POSIX标准,所以这两个命令也未被标准化。
System V消息队列的写入消息使用下面的函数接口:
#include
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//成功返回0,失败返回-1
msgp:指向存放消息的缓冲区,该缓冲区中包含消息类型和消息体两部分内容。该缓冲区的结构是由用户定义的,在
struct msgbuf
{
long int mtype; /* type of received/sent message */
char mtext[1]; /* text of the message */
};
缓冲区的开头是一个long型的消息类型,该消息类型必须是一个非负数。紧跟在消息类型后面的是消息体部分(如果消息长度大于0),参考模版中定义的mtext只是说明消息体,该部分可以自定义长度。我们自己的应用都会定义特定的消息结构。
msgsz:缓冲区中消息体部分的长度;
msgflg:设置操作标志。可以为0,IPC_NOWAIT;用于在消息队列中没有可用的空间时,调用线程采用何种操作方式。
标志为IPC_NOWAIT,表示msgsnd操作以非阻塞的方式进行,在消息队列中没有可用的空间时,msgsnd操作会立刻返回。并指定EAGAIN错误;
标志为0,表示msgsnd操作以阻塞的方式进行,这种情况下在消息队列中没有可用的空间时调用线程会被阻塞,直到下面的情况发生:
System V消息队列的读取消息使用下面的函数接口:
#include
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
//成功返回接收到的消息的消息体的字节数,失败返回-1
msqid:消息队列的描述符;
msgp:指向待存放消息的缓冲区,该缓冲区中将会存放接收到的消息的消息类型和消息体两部分内容。该缓冲区的结构是由用户定义的,和msgsnd相对应。
msgsz:缓冲区中能存放消息体部分的最大长度,这也是该函数能返回的最大数据量;该字段的大小应该是sizeof(msg buffer) - sizeof(long);
msgtyp:希望从消息队列中获取的消息类型。
msgflg:设置操作标志。可以为0,IPC_NOWAIT,MSG_NOERROR;用于在消息队列中没有可用的指定消息时,调用线程采用何种操作方式。
标志为IPC_NOWAIT,表示msgrcv操作以非阻塞的方式进行,在消息队列中没有可用的指定消息时,msgrcv操作会立刻返回,并设定errno为ENOMSG。
标志为0,表示msgrcv操作是阻塞操作,直到下面的情况发生:
标志为MSG_NOERROR,表示接收到的消息的消息体的长度大于msgsz长度时,msgrcv采取的操作。如果设置了该标志msgrcv在这种情况下回截断数据部分,而不返回错误,否则返回一个E2BIG错误。
下面是关于消息队列读写的测试代码:
#include
#include
#include
#include
#include
#include
using namespace std;
#define PATH_NAME "/tmp/anonymQueue"
key_t CreateKey(const char *pathName)
{
int fd;
if ((fd = open(PATH_NAME, O_CREAT, 0666)) < 0)
{
cout<<"open file "<
对System V消息队列的删除,属性的设置和获取等控制操作要使用下面的函数接口:
msqid:消息队列的描述符;
cmd:控制操作的命令,SUS标准提供以下三个命令:
buf:指向msqid_ds结构的指针;
下面是测试代码:
在Linux 2.6.18下的执行结果为:
关于消息队列中允许存放最大的字节数可以通过IPC_SET命令进行修改,该修改只能针对本消息队列生效。如下测试代码:
在Linux 2.6.18下的执行结果为:
对System V IPC,系统往往会存在一些限制,对于消息队列,在Linux2.6.18中,系统内核存在以下限制:
对于System V消息队列一般内核还有一个限制:系统范围内的最大消息数,在Linux下这个限制由msgmnb*msgmni决定。
上面已经说过可以通过IPC_SET来设置使用中的消息队列的最大字节数。但是要在系统范围内对内核限制进行修改,在Linux下面可以通过修改/etc/sysctl.conf内核参数配置文件,然后配合sysctl命令来对内核参数进行设置。例如下面示例: