1、概述
System V消息队列使用消息队列标识符(message queue identifier)标识。具有足够特权的任何进程都可以往一个给定队列放置一个消息,具有足够特权的任何进程都可以从一个给定队列读出一个消息。跟Posix消息队列一样,在某个进程往一个队列中写入一个消息之前,不求另外某个进程正在等待该队列上一个消息的到达。
对于系统中的每个消息队列,内核维护一个定义在<sys/msg.h>头文件中的信息结构。
struct msqid_ds
{
struct ipc_perm msg_perm; /* read_write perms: Section 3.3 */
struct msg *msg_first; /* ptr to first message on queue */
struct msg *msg_last; /* ptr to last message on queue */
msglen_t msg_cbytes; /* current # bytes on queue */
msgqnun_t msg_qnum; /* current # of messages on queue */
msgien_t msg_qbytes; /* max # of bytes allowed on queue */
pid_t msg_lspid; /* pid of last msgsnd() */
pid_t msg_lrpid; /* pid of last msgrcv() */
time_t msg_stime; /* time of last msgsnd() */
time_t msg_rtime; /* time of last msgrcv() */
time_t msg_ctime; /* time of last magct1() (that changed the above) */
};
我们可以将内核中某个特定的消息队列画为一个消息链表,如图6-1所示。假设有一个具有三个消息的队列,消息长度分别为1字节、2字节和3字节,而且这些消息就是以这样的顺序写入该队列的。再假设这三个消息的类型(type)分别为100,200和300。
2、msgget 函数
msgget 函数用于创建一个新的消息队列或访问一个已存在的消息队列。
#include <sys/msg.h>
int msgget(key_t key, int oflag);
// 返回:若成功则为非负标识符,若出错则为-1
返回值是一个整数标识符,其他三个msg函数就用它来指代该队列。它是基于指定的key产生的,而key既可以是ftok的返回值,也可以是常值IPC_PRIVATE。
oflag读写权限值的组合。它还可以与IPC_CREAT或IPC_CREAT | IPC_EXCL按位或。
当创建一个新消息队列时,msqid_ds 结构的如下成员被初始化。
- msg_perm 结构的uid和cuid成员被设置成当前进程的有效用户ID, gid和cgid成员被设置成当前进程的有效组ID。
- oflag中的读写权限位存放在msg_perm.mode中。
- msg_qnum、msg_lspid、msg_lrpid、msg_stime和msg_rtime被置为0。
- msg_ctime被设置成当前时间。
- msg_qbytes被设置成系统限制值。
3、msgsnd 函数
使用msgget打开一个消息队列后,我们使用msgsnd往其上放置一个消息。
#include <sys/msg.h>
int msgsnd(int msqid, const void *ptr, size_t length, int flag);
// 返回:若成功则为0,若出错则为-1
其中msqid是由msgget返回的标识符。ptr是一个结构指针,该结构具有如下的模板,它定义在<sys/msg.h>中
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext 1]; /* message data */
};
4、msgrcv 函数
使用msgrcv函数从某个消息队列中读出一个消息。
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *ptr, size_t length, long type, int flag);
// 返回:若成功则为读入缓冲区中数据的字节数,若出错则为-1
其中ptr参数指定所接收消息的存放位置。跟msgsnd一样,该指针指向紧挨在真正的消息数据之前返回的长整数类型字段
length指定了由ptr指向的缓冲区中数据部分的大小。这是该函数能返回的最大数据量。该长度不包括长整数类型字段。
type指定希望从所给定的队列中读出什么样的消息。
- 如果type为0,那就返回该队列中的第一个消息。既然每个消息队列都是作为一个FIFO(先进先出)链表维护的,因此ype为0指定返回该队列中最早的消息。
- 如果type大于0,那就返回其类型值为type的第一个消息。
- 如果tpe小于0,那就返回其类型值小于或等于type参数的绝对值的消息中类型值最小的第一个消息。
5、msgctl 函数
msgctl 函数提供在一个消息队列上的各种控制操作。
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buff);
// 返回:若成功则为0,若出错则为-1
msgctl 函数提供3个命令。
- IPC_RMID 从系统中删除由msqid指定的消息队列。当前在该队列上的任何消息都被丢弃。
- IPC-SET 给所指定的消息队列设置其msqid_ds结构的以下4个成员: msg_perm.uid、msg_perm.gid、 msg_perm.mode 和msg_qbytes。 它们的值来自由buff参数指向的结构中的相应成员。
- IPC-STAT (通过buff参数)给调用者返回与所指定消息队列对应的当前msgid_ds结构。
例子:
下面程序创建一个消息队列,往该队列中放置一个含有1字节数据的消息,发出msgctl的IPC_STAT命令,使用system函数执行ipcs命令,最后使用msgctl的IPC_RMID命令删除该队列。
// ctl.c
#include "unpipc.h"
int main(int argc, char **argv)
{
int msqid;
struct msqid_ds info;
struct msgbuf buf;
msqid = Msgget(IPC_PRIVATE, SVMSG_MODE | IPC_CREAT);
buf.mtype = 1;
buf.mtext[0] = 1;
Msgsnd(msqid, &buf, 1, 0);
Msgctl(msqid, IPC_STAT, &info);
printf("read-write: %03o, cbytes = %lu, qnum = %lu, qbytes = %lu\n",
info.msg_perm.mode & 0777, (ulong_t) info.msg_cbytes,
(ulong_t) info.msg_qnum, (ulong_t) info.msg_qbytes);
system("ipcs -q");
Msgctl(msqid, IPC_RMID, NULL);
exit(0);
}
执行结果:
6、简单的程序
6.1 msgcreate 函数,创建一个消息队列
// msgcreate.c
#include "unpipc.h"
int main(int argc, char **argv)
{
int c, oflag, mqid;
oflag = SVMSG_MODE | IPC_CREAT;
while ( (c = Getopt(argc, argv, "e")) != -1)
{
switch (c)
{
case 'e':
oflag |= IPC_EXCL;
break;
}
}
if (optind != argc - 1)
err_quit("usage: msgcreate [ -e ] <pathname>");
mqid = Msgget(Ftok(argv[optind], 0), oflag);
exit(0);
}
6.2 msgsnd 函数,将消息放置到队列中
// msgsnd.h
#include "unpipc.h"
int main(int argc, char **argv)
{
int mqid;
size_t len;
long type;
struct msgbuf *ptr;
if (argc != 4)
err_quit("usage: msgsnd <pathname> <#bytes> <type>");
len = atoi(argv[2]);
type = atoi(argv[3]);
mqid = Msgget(Ftok(argv[1], 0), MSG_W);
ptr = Calloc(sizeof(long) + len, sizeof(char));
ptr->mtype = type;
Msgsnd(mqid, ptr, len, 0);
exit(0);
}
6.3 msgrcv 函数,从队列中读出一个消息。
-n 命令行选择指定非阻塞
-t 命令行选项指定msgrcv函数的type参数
#include "unpipc.h"
#define MAXMSG (8192 + sizeof(long))
int main(int argc, char **argv)
{
int c, flag, mqid;
long type;
ssize_t n;
struct msgbuf *buff;
type = flag = 0;
while ( (c = Getopt(argc, argv, "nt:")) != -1)
{
switch (c)
{
case 'n':
flag |= IPC_NOWAIT;
break;
case 't':
type = atol(optarg);
break;
}
}
if (optind != argc - 1)
err_quit("usage: msgrcv [ -n ] [ -t type ] <pathname>");
mqid = Msgget(Ftok(argv[optind], 0), MSG_R);
buff = Malloc(MAXMSG);
n = Msgrcv(mqid, buff, MAXMSG, type, flag);
printf("read %d bytes, type = %ld\n", n, buff->mtype);
exit(0);
}
6.4 msgrmid 函数,删除一个消息队列
#include "unpipc.h"
int main(int argc, char **argv)
{
int mqid;
if (argc != 2)
err_quit("usage: msgrmid <pathname>");
mqid = Msgget(Ftok(argv[1], 0), 0);
Msgctl(mqid, IPC_RMID, NULL);
exit(0);
}
6.5 例子执行
创建一个消息队列并往其中写入三个消息:
不以FIFO顺序读取消息:
6.6 msgrcvid 函数
要访问一个System V消息队列,调用msgget并不是必须的:我们只需知道该消息队列的标识符(使用ipcs极易得到),并拥有该队列的读权限。
例子:
#include "unpipc.h"
#define MAXMSG (8192 + sizeof(long))
int main(int argc, char **argv)
{
int mqid;
ssize_t n;
struct msgbuf *buff;
if (argc != 2)
err_quit("usage: msgrcvid <mqid>");
mqid = atoi(argv[1]);
buff = Malloc(MAXMSG);
n = Msgrcv(mqid, buff, MAXMSG, 0, 0);
printf("read %d bytes, type = %ld\n", n, buff->mtype);
exit(0);
}
执行结果:
7、小结
System V消息队列与Posix消息队列类似。新的应用程序应考虑使用Posix消息队列,不过大量的现有代码使用System V消息队列。然而把一个应用程序从使用System V消息队列重新编写成使用Posix消息队列不是件难事。 Posix消息队列缺失的主要特性是从队列中读出指定优先级的消息的能力。这两种消息队列都不使用真正的描述符,从而造成在消息队列上使用select或poll的困难。