消息队列内核结构和msgget、msgctl 函数
一、消息队列
1、消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
2、每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
3、消息队列与管道不同的是,消息队列是基于消息的,而管道是基于字节流的,且消息队列的读取不一定是先入先出。
4、消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI),这三个参数都可以查看:
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ cat /proc/sys/kernel/msgmax
8192
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ cat /proc/sys/kernel/msgmnb
16384
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ cat /proc/sys/kernel/msgmni
1711
二、IPC对象数据结构
内核为每个IPC对象维护一个数据结构
struct ipc_perm {
key_t __key; /* Key supplied to xxxget(2) */
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 */
};
消息队列,共享内存和信号量都有这样一个共同的数据结构。
三、消息队列结构
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t
msg_stime; /* Time of last msgsnd(2) */
time_t
msg_rtime; /* Time of last msgrcv(2) */
time_t
msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
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(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
可以看到第一个条目就是IPC结构体,即是共有的,后面的都是消息队列所私有的成员。
四、消息队列在内核中的表示
消息队列是用链表实现的,这里需要提出的是MSGMAX指的是一条消息的纯数据大小的上限,上图是一个消息队列,则其纯数据总和不能超过MSGMNB,像这样一条消息队列,系统含有的总数不能超过MSGMNI 个。
五、msgget 和 msgctl 函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
(1)int msgget(key_t key, int msgflg);
功能:用来创建和访问一个消息队列
参数
key: 某个消息队列的名字
msgflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1
创建流程如下图所示:
注意,IPC_PRIVATE 不是一个msgflg 而是一个key_t 类型,如果指定key = IPC_PRIVATE,则无论某个 key_value 这个消息队列是否存在,都会再创建一个key_value 消息队列,但他们的标识码msqid是不一样的,且指定IPC_PRIVATE 产生的是私有的消息队列。
写个小程序测试一下这个函数:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
/*************************************************************************
> File Name: basic.c > Author: Simba > Mail: dameng34@163.com > Created Time: Tue 12 Mar 2013 06:54:20 PM CST ************************************************************************/ #include<stdio.h> #include<stdlib.h> #include<sys/ipc.h> #include<sys/msg.h> #include<sys/types.h> #include<unistd.h> #include<errno.h> #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main(void) { int msgid; msgid = msgget(1234, 0666 | IPC_CREAT); if (msgid == -1) ERR_EXIT("msgget"); printf("msgget success\n"); msgid = msgget(1234, 0); printf("msgid=%d\n", msgid); return 0; } |
程序先创建一个消息队列,名字为1234,接着打开这个消息队列,当flags = 0 表示按原来权限打开。
输出如下:simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./msgget
msgget success
msgid=0
我们可以使用命令ipcs -q 查看:
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x000004d2 0 simba 666 0 0
可以看到0x4d2 也就是1234,msqid 为0。
(2)int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:消息队列的控制函数
参数
msqid: 由msgget函数返回的消息队列标识码
cmd:是将要采取的动作,(有三个可取值)
返回值:成功返回0,失败返回-1
cmd 的取值如下:
我们可以通过ipcrm -q 删除一条消息队列,也可以通过msgctl 函数删除,此时设置cmd 为 IPC_RMID,如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/*************************************************************************
> File Name: basic.c > Author: Simba > Mail: dameng34@163.com > Created Time: Tue 12 Mar 2013 06:54:20 PM CST ************************************************************************/ #include<stdio.h> #include<stdlib.h> #include<sys/ipc.h> #include<sys/msg.h> #include<sys/types.h> #include<unistd.h> #include<errno.h> #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main(void) { int msgid; msgid = msgget(1234, 0); if (msgid == -1) ERR_EXIT("msgget"); printf("msgget success\n"); printf("msgid=%d\n", msgid); msgctl(msgid, IPC_RMID, NULL); return 0; } |
如果我们想更改消息队列的一些参数,如权限等,可以通过msgctl 函数,cmd 取值为IPC_SET
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/*************************************************************************
> File Name: basic.c > Author: Simba > Mail: dameng34@163.com > Created Time: Tue 12 Mar 2013 06:54:20 PM CST ************************************************************************/ #include<stdio.h> #include<stdlib.h> #include<sys/ipc.h> #include<sys/msg.h> #include<sys/types.h> #include<unistd.h> #include<errno.h> #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main(void) { int msgid; msgid = msgget(1234, 0); if (msgid == -1) ERR_EXIT("msgget"); printf("msgget success\n"); printf("msgid=%d\n", msgid); struct msqid_ds buf; msgctl(msgid, IPC_STAT, &buf); printf("permission : %o\n", buf.msg_perm.mode); sscanf("600", "%o", (unsigned int *)&buf.msg_perm.mode); msgctl(msgid, IPC_SET, &buf); return 0; } |
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./msgset
msgget success
msgid=32768
permission : 666
simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -q
------ Message Queues --------
key msqid owner perms used-bytes messages
0x000004d2 32768 simba 600 0 0
注:ipcrm --删除ipc对象
ipcrm -m|-q|-s shm_id
%ipcrm -m 105
例如,我们在以0x12345678为KEY创建了一个共享内存,可以直接使用ipcrm -M 0x12345678来删除共享内存区域。
参考:
《TCP/IP详解 卷一》
《UNP》