本系列文章主要是学习记录Linux下进程间通信的方式。
常用的进程间通信方式:管道、FIFO、消息队列、信号量以及共享存储。
参考文档:《UNIX环境高级编程(第三版)》
参考视频:Linux进程通信 推荐看看,老师讲得很不错
Linux核心版本:2.6.32-431.el6.x86_64
注:本文档只是简单介绍IPC,更详细的内容请查看参考文档和相应视频。
本文介绍利用消息队列进行进程间的通信。
1 IPC对象
IPC对象:消息队列、共享内存和信号量。
存在于内核中而不是文件系统中,由用户控制释放(用户管理ipc对象的生命周期),不像管道那样由内核控制。
IPC对象通过标识符来引用和访问,所有IPC在内核空间中有唯一标识ID,在用户空间中的唯一标识称为key。
可通过[root@192 ~]# ipcs 命令查看。
每个IPC对象都由get函数创建:msgget、shmget、semget,调用get函数必须指定关键字key。
2 介绍
- 消息队列是内核中的一个链表。
- 消息队列存储在内核中,由消息队列标识符标识。
- 用户进程将数据(二进制、文本等)传输到内核后,内核重新添加一些如用户ID、组ID、读写进程的ID和优先级等相关信息后并打成一个数据包称为消息。
- 允许一个或多个进程往消息队列中写消息和读消息,但一个消息只能被一个进程读取,读取完毕后就自动删除。
- 消息队列具有一定的FIFO的特性,消息可以按照顺序发送到队列中,也可以几种不同的方式从队列中读取。每一个消息队列在内核中用一个唯一的IPC标识ID表示。
- 消息队列的实现包括创建和打开队列、读取消息和控制消息队列等四种操作。
3 消息队列属性结构体
1 struct msqid_ds { 2 struct ipc_perm msg_perm; /* Ownership and permissions */ 3 time_t msg_stime; /* Time of last msgsnd(2) */ 4 time_t msg_rtime; /* Time of last msgrcv(2) */ 5 time_t msg_ctime; /* Time of last change */ 6 unsigned long __msg_cbytes; /* Current number of bytes in 7 queue (non-standard) */ 8 msgqnum_t msg_qnum; /* Current number of messages 9 in queue */ 10 msglen_t msg_qbytes; /* Maximum number of bytes 11 allowed in queue */ 12 pid_t msg_lspid; /* PID of last msgsnd(2) */ 13 pid_t msg_lrpid; /* PID of last msgrcv(2) */ 14 };
4 函数原型
1 #include <sys/types.h> 2 #include <sys/ipc.h> 3 #include <sys/msg.h> 4 int msgget(key_t key, int msgflg); 5 说明:创建一个队列或打开一个现有队列。 6 返回:成功返回内核中消息队列的标识ID,出错返回-1。 7 参数key:用户指定的消息队列键值; 8 参数flag:IPC_CTREAT、IPC_EXCL等权限组合。 9 注:若创建消息队列,key可指定键值,也可将之设置为IPC_PRIVATE。若打开进行查询,则key不能为0,必须是一个非零的值,否则查询不到。
1 #include <sys/types.h> 2 #include <sys/ipc.h> 3 #include <sys/msg.h> 4 int msgctl(int msqid, int cmd, struct msqid_ds *buf); 5 说明:消息队列控制函数; 6 返回:成功返回0,出错返回-1; 7 参数msgid:内核中的消息队列ID; 8 参数buf:消息队列属性指针; 9 参数cmd:IPC_STAT:获取消息队列的属性,取此队列的msqid_ds结构,并将其存放在buf指向的结构中;
IPC_SET:设置属性,按由buf指向的结构中的值,设置与此队列相关的结构中的字段;
IPC_RMID:删除队列,从系统中删除该消息队列以及仍在该队列上的所有数据。
1 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 2 说明:将消息添加到消息队列尾端。 3 返回:成功返回0,出错返回-1; 4 参数msqid:内核中的消息队列ID; 5 参数msgp:通用指针,指向需要发送的消息,参数传递的格式: 6 struct msgbuf { 7 long mtype; /* message type, must be > 0 */ 8 char mtext[1]; /* message data */ 9 }; 10 mtype:指定消息的类型,它由一个整数来代表,并且它只能是大于0的整数; 11 mtext:消息数据本身。大小由msgsz指定。 12 Linux中,消息的最大长度是4056个字节,其中包括mtype,它占4个字节; 13 结构体msgbuf用户可自定义,但第一个成员必须是mtype。 14 参数msgsz:指定消息的大小,不包括mtype的大小。 15 参数flag:0:阻塞,阻塞直到有空间可以容纳要发送的消息或从系统中删除了此队列或捕捉到一个信号,并从信号处理程序返回。
IPC_NOWAIT:类似于文件I/O的非阻塞标志。若消息队列已满(或者是队列中的消息总数等于系统限制值,或队列中的字节总数等于系统限制值),在指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN。
1 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); 2 说明:从队列中取消息。 3 返回:成功返回消息的数据部分长度,出错返回-1; 4 参数msgid:消息队列ID; 5 参数msgp:指向消息队列的缓存; 6 参数msgsz:消息缓存的大小,不包括mtype的大小,计算方式:msgsz=sizeof(struct msgbuf)-sizeef(long); 7 参数msgtyp:消息类型;msgtyp==0,获得消息队列中第一个消息;msgtyp>0,获取消息队列中类型为msgtyp的第一个消息;msgtyp<0,获得消息队列中小于或等于msgtyp绝对值的消息(类型最小的)。 8 参数msgflg:0或者IPC_NOWAIT。
5 测试实例
单独创建两个进程,发送消息进程和接收消息进程,它们之间没有关系,通过消息队列来交换数据。
发送消息进程:
1 #include <sys/types.h> 2 #include <sys/ipc.h> 3 #include <sys/msg.h> 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <string.h> 7 8 typedef struct { 9 long type; //消息类型 10 int start; //消息数据本身,包括start和end 11 int end; 12 }MSG; 13 14 //往消息队列中发送消息 15 16 int main(int argc, char *argv[]) 17 { 18 if (argc < 2) { 19 printf("usage: %s key ", argv[0]); 20 exit(1); 21 } 22 23 key_t key = atoi(argv[1]); //key由用户指定 24 // key_t key = ftok(argv[1], 0); //由函数生成key 25 printf("key: %d ", key); 26 27 // 创建消息队列 28 int msg_id; 29 if ((msg_id = msgget(key, IPC_CREAT|IPC_EXCL|0777)) < 0) { 30 perror("msgget error"); 31 } 32 printf("msg id: %d ", msg_id); 33 34 // 定义要发送的消息 35 MSG m1 = {4, 4, 400}; 36 MSG m2 = {2, 2, 200}; 37 MSG m3 = {1, 1, 100}; 38 MSG m4 = {6, 6, 600}; 39 MSG m5 = {6, 40, 6000}; 40 41 //发送消息到消息队列 42 if (msgsnd(msg_id, &m1, sizeof(MSG)-sizeof(long), IPC_NOWAIT) < 0) { 43 perror("msgsnd error"); 44 } 45 if (msgsnd(msg_id, &m2, sizeof(MSG)-sizeof(long), IPC_NOWAIT) < 0) { 46 perror("msgsnd error"); 47 } 48 if (msgsnd(msg_id, &m3, sizeof(MSG)-sizeof(long), IPC_NOWAIT) < 0) { 49 perror("msgsnd error"); 50 } 51 if (msgsnd(msg_id, &m4, sizeof(MSG)-sizeof(long), IPC_NOWAIT) < 0) { 52 perror("msgsnd error"); 53 } 54 if (msgsnd(msg_id, &m5, sizeof(MSG)-sizeof(long), IPC_NOWAIT) < 0) { 55 perror("msgsnd error"); 56 } 57 58 // 发送后去获取消息队列中消息的总数 59 struct msqid_ds ds; 60 if (msgctl(msg_id, IPC_STAT, &ds) < 0) { 61 perror("msgctl errors"); 62 } 63 printf("msgctl totol: %ld ", ds.msg_qnum); 64 65 return 0; 66 }
接收消息进程:
1 #include <sys/types.h> 2 #include <sys/ipc.h> 3 #include <sys/msg.h> 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <string.h> 7 8 typedef struct { 9 long type; //消息类型 10 int start; //消息数据本身,包括start和end 11 int end; 12 }MSG; 13 14 //往消息队列中发送消息 15 int main(int argc, char *argv[]) 16 { 17 if (argc < 3) { 18 printf("usage: %s key type ", argv[0]); 19 exit(1); 20 } 21 22 key_t key = atoi(argv[1]); 23 long type = atoi(argv[2]); 24 25 //获得指定的消息队列 26 int msg_id; 27 if ((msg_id = msgget(key, 0777)) < 0) { 28 perror("msgget error"); 29 } 30 printf("msg id: %d ", msg_id); 31 32 MSG m; 33 if (msgrcv(msg_id, &m, sizeof(MSG)-sizeof(long), type, IPC_NOWAIT) < 0) { 34 perror("msgrcv error"); 35 } else { 36 printf("type: %ld start: %d end: %d ", m.type, m.start, m.end); 37 } 38 39 return 0; 40 }
测试步骤:
1、先分别编译发送进程和接收进程
[root@192 ipc]# gcc -o bin/msg_send msg_send.c
[root@192 ipc]# gcc -o bin/msg_rcv msg_rcv.c
2、发送进程发送5条消息,key是人为指定的10
3、接收进程获取一条消息
由于消息队列中有两条type都为6的消息,可以看出,首先获得的是先发送到消息队列的消息。获取后的消息将从消息队列中删除。
4、接收进程继续获取消息