0. 前言
进程间通信——序 一文中说到 system V版本,AT&T引进了一种新形式的IPC 功能(信号量、消息队列以及共享内存)。
System IPC 中,对于每一个新建的信号量、消息队列以及共享内存,都有一个在整个系统中唯一的标识符。每个标识符都有唯一对应的关键字,关键字的数据类型由系统定义为key_t。
在终端命令行输入ipcs 命令,可以看到系统所有IPC信息:
------ Message Queues --------
key msqid owner perms used-bytes messages
0x6d003256 73072640 shift 644 1024 1
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 5668864 lightdm 600 524288 2 dest
0x00000000 5767169 lightdm 600 524288 2 dest
------ Semaphore Arrays --------
key semid owner perms nsems
第一列key 就是IPC 的关键字,第二列是IPC 的标识符。
在终端命令行输入ipcs -l来显示IPC 相关的限制:
------ Messages Limits --------
max queues system wide = 32000
max size of message (bytes) = 8192
default max size of queue (bytes) = 16384
------ Shared Memory Limits --------
max number of segments = 4096
max seg size (kbytes) = 18014398509465599
max total shared memory (kbytes) = 18014398442373116
min seg size (bytes) = 1
------ Semaphore Limits --------
max number of arrays = 32000
max semaphores per array = 32000
max semaphores system wide = 1024000000
max ops per semop call = 500
semaphore max value = 32767
这些限制中大多数可以通过重新配置内核来改变。
对于这3中IPC 结构,存在两个问题:
- 该结构在系统范围内起作用,没有引用计数。所以如创建一个消息队列,在最后终止并不会主动删除该消息队列,消息队列中的内容还是存在的。并不像FIFO,虽然最后FIFO文件依然存在,但FIFO中的数据已经被删除。
- 这些结构在系统中没有名字。所以不像FIFO 在最后可以通过rm 删除文件。
对于上述两个问题,IPC 提供了解决办法:
- msgctl、semctl、shamctl对相应的IPC 进行删除
- 命令ipcrm 进行删除对应的ipc
2. ftok()
ftok() 函数用于获得一个IPC 关键字,函数原型:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
ftok函数返回一个以pathname 指向的文件相对应的文件和proj_id 来确定一个IPC 关键字key_t。pathname必须是一个已经存在并具有访问权限的文件,proj_id 只有最低的8个字节是有效的,所以通常用一个ASCII 字符来作为proj_id。
当pathname 和proj_id 完全相同时,每次调用ftok() 都会获取一个相同的关键字。
IPC 关键字可以通过ftok() 函数来获得,也可以设为IPC_PRIVATE,这时操作系统确保创建一个新的IPC,其标识符需要由进程自己记录并告诉其他进程。
3. 消息队列
消息队列是系统内核地址空间中的一个内部的链表。消息可以按照顺序发送到队列中,也可以以集中不同的方式从队列中读取。没一个消息队列用一个唯一的IPC 标识符表示。
消息队列特点:
- 生命周期随内核,消息队列会一直存在,需要我们显示的调用接口删除或使用命令删除
- 消息队列可以双向通信
- 克服了管道只能承载无格式字节流的缺点
来看下数据结构msgbuf,此数据结构需要用户自己定义,但了解系统中有这样一个数据结构是十分重要的,在<sys/msg.h>中,此结构是这样定义的:
struct msgbuf {
__syscall_slong_t mtype; /* type of received/sent message */
char mtext[1]; /* text of the message */
}
数据结构msgbuf 中共有两个元素:
- mtype 指消息的类型,它由一个整数来代表,并且只能是大于0的整数。
- mtext 是消息的数据本身
当然,mtext 字段不但可以存储字符,还可以存储任何其他的数据类型。此字段可以说是完全任意的,由程序员自己定义,例如:
struct msgbuf {
long mtype; /* type of received/sent message */
char request_id; /* request identifier */
struct client info; /* client information structure */
}
mtype 是必须的,其他数据是自己定义的。
4. 消息队列限制
这个在前言中已经讲过,通过ipcs -l 可以查看到系统的限制。
也可以通过cat /proc/sys/kernel/msg* 来查看:
cat /proc/sys/kernel/msgmax //消息最大长度,包括type
cat /proc/sys/kernel/msgmnb //每个消息队列总长度
cat /proc/sys/kernel/msgmni //系统中消息队列总数的上限
5. 创建消息队列
系统调用msgget() 来创建一个新的消息队列,或者存取一个已经存在的消息队列,原型:
#include <sys/msg.h> //里面包含了#include <sys/ipc.h>
int msgget(key_t key, int msgflag);
参数:
- key:某个消息队列的名字,用ftok()产生
- msgflag:有两个选项IPC_CREAT和IPC_EXCL,单独使用IPC_CREAT,如果消息队列不存在则创建之,如果存在则打开返回;单独使用IPC_EXCL是没有意义的;两个同时使用,如果消息队列不存在则创建之,如果存在则出错返回。
返回值:成功返回一个非负整数,即消息队列的标识码,失败返回-1。可以通过errno和perror函数来查看错误信息。
6. 控制消息队列
消息队列标识符的属性被记录在一个msgid_ds的结构体:
struct msqid_ds {
struct ipc_perm msg_perm; /* structure describing operation permission */
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 log __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() */
...
}
通过msgctl() 可以对消息队列进行控制或者一些属性的修改,函数原型:
#include <sys/msg.h> //里面包含了 #include <sys/ipc.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
msqid:由msgget函数返回的消息队列标识码
cmd:有三个可选的值
- IPC_STAT 把msqid_ds结构中的数据设置为消息队列的当前关联值
- IPC_SET 在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值
- IPC_RMID 删除消息队列
返回值: 成功返回0,失败返回-1
7. 发送消息
msgsnd() 向队列发送一条消息,函数原型:
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:
- msgid:由msgget函数返回的消息队列标识码
- msgp:指针指向准备发送的消息
- msgze:msgp指向的消息的长度(不包括消息类型的long int长整型)
- msgflg:默认为0 ,即从队列中取出最长时间的一条消息。
返回值:成功返回0,失败返回-1
消息结构一方面必须小于系统规定的上限,另一方面必须以一个long int长整型开始,接受者以此来确定消息的类型。
8. 接受消息
msgrcv() 用于从消息队列中读取一条消息,函数原型:
#include <sys/msg.h>
int msgrcv(int msqid, const void *msgp, size_t msgsz, long msgtype int msgflg);
参数:
- msgid:由msgget函数返回的消息队列标识码
- msgp:指针指向准备发送的消息
- msgze:msgp指向的消息的长度(不包括消息类型的long int长整型)
- msgtype:指定读取的消息的type
- msgflg:默认为0, 即从队列中取出最长时间的一条消息。
返回值:成功返回0,失败返回-1
注意,参数msgflg 取值为:
0:从队列中取出最长时间的一条消息。
IPC_NOWAIT:当队列没有消息时,调用会立即返回ENOMSG错误。否则,调用进程会被挂起,知道队列中的一条细细满足msgrcv() 的参数要求。
9. 实例
test_msgq.h
#ifndef __TEST_MESSAGE_QUEUE_INCLUDE__
#define __TEST_MESSAGE_QUEUE_INCLUDE__
//include 3 header files for message queue
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define msg_key "msgq_key"
#define msg_len(x,y) sizeof(x)-sizeof(y)
typedef struct{
long type;
char msg[1024];
} msg_buf_t;
#define SERVER_TYPE 123
#define CLIENT_TYPE 456
int get_msg_queue();
int create_msg_queue();
int remove_msg_queue(int msqid);
int send_msg(int msqid, char* msg, int who);
int receive_msg(int msqid, char*msg, int who);
#endif //#ifndef __TEST_MESSAGE_QUEUE_INCLUDE__
- 定义了通过ftok() 获取的文件名:msgq_key
- 定义了消息的数据结构:msg_buf_t
- 定义了server、client的type,用于读取消息
test_msgq.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "test_msgq.h"
static int create_msg_queue_common(int flags)
{
printf("create message queue\n");
key_t key = ftok(msg_key, 'm');
if (key < 0) {
perror("ftok error.\n");
return -1;
}
printf("key is 0x%x\n", key);
int msqid = msgget(key, flags);
if (msqid == -1) {
perror("msgget error\n");
}
printf("msqid is %d\n", msqid);
return msqid;
}
int create_msg_queue()
{
return create_msg_queue_common(IPC_CREAT | IPC_EXCL | 0644);
}
int get_msg_queue()
{
return create_msg_queue_common(IPC_CREAT);
}
int remove_msg_queue(int msqid)
{
printf("remove message queue, msqid is %d\n", msqid);
if(msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl error\n");
return -1;
}
return 0;
}
int send_msg(int msqid, char* msg, int who)
{
msg_buf_t msg_buf;
msg_buf.type = who;
strcpy(msg_buf.msg, msg);
if(msgsnd(msqid, (void*)&msg_buf, msg_len(msg_buf_t, msg_buf.type), 0) == -1) {
perror("msgsnd error\n");
return -1;
}
return 0;
}
test_read.c
#include <stdio.h>
#include <unistd.h>
#include <memory.h>
#include "test_msgq.h"
// server for testing.
int main()
{
printf("main for read message.\n");
int msqid = create_msg_queue();
char msg[1024] = {0};
int n = 40;
while(n > 20) {
receive_msg(msqid, msg, CLIENT_TYPE);
printf("receive from client: %s\n", msg);
memset(msg, 0, sizeof(msg));
sprintf(msg, "send %d from server", n);
send_msg(msqid, msg, SERVER_TYPE);
sleep(1);
n--;
}
return 0;
}
test_read.c 代表server 端。首先收取从client 端发出的消息,同时发送一个server 的消息给client端。
test_write.c
#include <stdio.h>
#include <unistd.h>
#include <memory.h>
#include "test_msgq.h"
//client for testing.
int main()
{
printf("main for write message.\n");
int msqid = create_msg_queue();
char msg[1024] = {0};
int n = 20;
while(n) {
sprintf(msg, "send %d from client", n);
send_msg(msqid, msg, CLIENT_TYPE);
sleep(1);
memset(msg, 0, sizeof(msg));
receive_msg(msqid, msg, SERVER_TYPE);
printf("receive from server: %s\n", msg);
n--;
}
return 0;
}
先发送一个消息给server端,然后接受来自server 的消息。
运行结果:
./test_read
main for read message.
create message queue
key is 0x6d003256
msqid is 73072640
receive from client: send 20 from client
receive from client: send 19 from client
receive from client: send 18 from client
receive from client: send 17 from client
receive from client: send 16 from client
receive from client: send 15 from client
receive from client: send 14 from client
receive from client: send 13 from client
^C
./test_write
main for write message.
create message queue
key is 0x6d003256
msqid is 73072640
receive from server: send 21 from server
receive from server: send 40 from server
receive from server: send 39 from server
receive from server: send 38 from server
receive from server: send 37 from server
receive from server: send 36 from server
receive from server: send 35 from server
^C
注意,第一条信息是消息队列曾经存在的最后一次的消息。
相关博文: