进程间通信的分类:
文件
文件锁
管道和匿名管道pipe
信号
消息队列
共享内存
信号量
互斥量
条件变量
读写锁
套接字
两个进程同时向管道中写数据,内核要保证它们是原子的,内核按照页做限制。
进程间共享信息的三种方式:一个通过文件系统,一个通过内核,一个在应用空间
IPC对象的持续性:
随进程持续:一直存在,直到打开的最后一个进程结束。(如pipe和FIFO)
随内核持续:一直存在,直到内核自举或者显式删除。(如System V消息队列、共享内存、信号量)
随文件系统持续:一直存在,直到显式删除,即使内核自举还存在。(POSIX消息队列、共享内存、信号量(如果是使用映射文件来实现))
查看系统的IPC对象以及它们的一些属性可以使用ipcs命令,如下:
其中key表示键,semid表示这个对象的id,owner表示拥有者。共享内存还有个属性是nattch,表示有多少个人连接上共享内存了。
ipcs -l可以查看IPC对象的一些系统限制:
消息队列提供了从一个进程向另一个进程发送一块数据的方法
每个数据块都有一个类型,接收者进程接收的数据块可以有不同的类型值
消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)。
管道是基于数据流的,先进先出。
消息队列是有边界的,可以后进先出。
消息队列有如下三个限制:
cat /proc/sys/kernel/msgmax 最大消息长度限制
cat /proc/sys/kernel/msgmnb 消息队列总的字节数
cat /proc/sys/kernel/msgmni 消息条目数
使用man 2 msgctl可以查看消息队列的一些数据结构:
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) */
};
struct ipc_perm {
key_t __key; /* Key supplied to msgget(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 */
};
消息队列在内核中的表示如下:
msq_ids存储了消息的一些元信息,具体的消息由内核来管理。
消息队列相关的一些API函数:
msgget函数原型如下:
int msgget(key_t key, int msgflg)
功能:用来创建和访问一个消息队列
参数:
key表示某个消息队列的名字
msgflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1
编写如下的msgget测试函数:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #include <errno.h> 7 #include <sys/msg.h> 8 9 int main() 10 { 11 int msgid; 12 13 msgid = msgget(0x1234, 0666); 14 if(msgid < 0) 15 { 16 perror("msgget error"); 17 exit(0); 18 } 19 20 return 0; 21 }
这段程序代表打开一个消息队列,0666代表权限,0代表8进制,执行结果如下:
系统中没有key值为0x1234的消息队列,所以出错返回。我们可以根据errno错误码进行更精确的控制。
我们修改msgget函数的第二个参数,程序如下:
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/msg.h> int main() { int msgid; msgid = msgget(0x1234, 0666 | IPC_CREAT); if(msgid < 0) { perror("msgget error"); exit(0); } printf("create msg queue success\n"); return 0; }
现在的参数表示,如果这个消息队列存在就打开旧的,如果不存在就创建新的。
现在我们的系统中是没有消息队列的,执行结果如下:
使用ipcs查看系统中的消息队列如下:
可以看到确实出现了一个key值为0x1234的消息队列IPC对象。
进一步加参数,如下:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #include <errno.h> 7 #include <sys/msg.h> 8 9 int main() 10 { 11 int msgid; 12 13 msgid = msgget(0x1234, 0666 | IPC_CREAT | IPC_EXCL); 14 if(msgid < 0) 15 { 16 if(errno == EEXIST) 17 { 18 printf("msg is exist\n"); 19 } 20 perror("msgget error"); 21 exit(0); 22 } 23 24 printf("create msg queue success\n"); 25 26 return 0; 27 }
加上IPC_EXCL表示,如果文件存在,若还要创建(加了IPC_CREAT)则返回错误。单独用IPC_EXCL没有意义。
执行结果如下:
我们验证key取IPC_PRIVATE的情况,如下:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #include <errno.h> 7 #include <sys/msg.h> 8 9 int main() 10 { 11 int msgid; 12 13 msgid = msgget(IPC_PRIVATE, 0666 ); 14 if(msgid < 0) 15 { 16 if(errno == EEXIST) 17 { 18 printf("msg is exist\n"); 19 } 20 perror("msgget error"); 21 exit(0); 22 } 23 24 printf("create msg queue success\n"); 25 26 return 0; 27 }
这时候,flag参数的IPC_CREAT和IPC_EXCL都没有作用了,执行结果如下:
此时创建出来的消息队列的key值为0,多次调用这个函数可以创建出多个key为0的消息队列,如下:
私有的消息队列只能用在本进程或者和本进程有血缘关系的进程(通过fork创建)。将msgid传给其他没有血缘的进程也不能用。
创建一个IPC对象的逻辑如下:
msgctl函数:
原型:int msgctl(int msqid, int cmd, struct msqid_ds *buf)
参数:
msqid:由msgget函数返回的消息队列标识码
cmd:将要采取的动作
返回值:成功返回0,失败返回-1
cmd可取的三个值如下:
IPC对象,消息队列是linux负责持久化,我们可以从linux内核中获取消息队列的信息,或者修改消息队列。
msgctl的测试函数如下:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #include <errno.h> 7 #include <sys/msg.h> 8 9 int main() 10 { 11 int msgid; 12 int ret = 0; 13 14 msgid = msgget(0x1234, 0666); 15 if(msgid < 0) 16 { 17 if(errno == EEXIST) 18 { 19 printf("msg is exist\n"); 20 } 21 perror("msgget error"); 22 exit(0); 23 } 24 25 printf("msgid = %d\n", msgid); 26 27 struct msqid_ds buf; 28 memset(&buf, 0, sizeof(buf)); 29 ret = msgctl(msgid, IPC_STAT, &buf); 30 31 if(ret == -1) 32 { 33 perror("msgctl error"); 34 exit(0); 35 } 36 37 printf("mode = %o\n", buf.msg_perm.mode); 38 39 return 0; 40 }
系统中已经存在一个我们以前创建的消息队列,key为0x1234,msgid为0,执行程序,结果如下:
msgsnd函数,消息队列的发送函数:
原型:int msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflg)
参数:
msgid:由msgget返回的消息队列标识码
msgp:是一个指针,指针指向准备发送的消息
msgsz:是msgp指向的消息的长度,这个长度不包含保存消息类型的那个long int长类型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
返回值:成功返回0,失败返回-1
msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误
消息结构在两方面受到制约,首先,它必须小于系统规定的上限值;其次,它必须以一个long int长整数开始,接收者将利用这个长整数确定消息的类型
消息结构参考形式如下:
struct msgbuf
{
long mtype;
char mtext[1];
}
测试函数如下:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #include <errno.h> 7 #include <sys/msg.h> 8 9 struct msgbuf 10 { 11 long mtype; 12 char mtext[1024]; 13 }; 14 15 int main() 16 { 17 int msgid; 18 int ret = 0; 19 20 msgid = msgget(0x1234, 0666); 21 if(msgid < 0) 22 { 23 if(errno == EEXIST) 24 { 25 printf("msg is exist\n"); 26 } 27 perror("msgget error"); 28 exit(0); 29 } 30 31 printf("msgid = %d\n", msgid); 32 33 struct msgbuf buf; 34 memset(&buf, 0, sizeof(struct msgbuf)); 35 buf.mtype = 1; 36 strcpy(buf.mtext, "1111111222222333333"); 37 38 ret = msgsnd(msgid, &buf, 10, IPC_NOWAIT); 39 if(ret < 0) 40 { 41 perror("msgsnd error"); 42 exit(0); 43 } 44 45 return 0; 46 }
消息类型我们定义为1类型,这个是自己选的。系统中已经存在key值为0x1234的消息队列,我们执行完这个函数后,消息就已经写入到了这个消息队列中,我们怎么验证呢?msgctl可以读出消息队列的信息,我们写如下的验证程序:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #include <errno.h> 7 #include <sys/msg.h> 8 9 int main() 10 { 11 int msgid; 12 int ret = 0; 13 14 msgid = msgget(0x1234, 0666); 15 if(msgid < 0) 16 { 17 if(errno == EEXIST) 18 { 19 printf("msg is exist\n"); 20 } 21 perror("msgget error"); 22 exit(0); 23 } 24 25 printf("msgid = %d\n", msgid); 26 27 struct msqid_ds buf; 28 memset(&buf, 0, sizeof(buf)); 29 ret = msgctl(msgid, IPC_STAT, &buf); 30 31 if(ret == -1) 32 { 33 perror("msgctl error"); 34 exit(0); 35 } 36 37 printf("mode = %o\n", buf.msg_perm.mode); 38 printf("bytes in msg queue : %d\n", buf.__msg_cbytes); 39 printf("msg numbers : %d\n", buf.msg_qnum); 40 41 return 0; 42 }
执行结果如下:
这和我们写到消息队列中的消息是吻合的。
msgrcv接收消息函数:
原型:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg)
参数:
msgid:由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备接收的消息
msgsz:是msgp指向的消息的长度,这个长度不含保存消息类型的那个long int长整型
msgtype:它可以实现接收优先级的简单形式
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
返回值:成功返回实际放到接收缓冲区中的字符个数,失败返回-1
msgtype=0 返回队列第一条信息
msgtype>0 返回队列第一条类型等于msgtype的消息
msgtype<0 返回队列第一条类型小于等于msgtype绝对值的消息,并且是满足条件的消息类型最小的消息
msgflg=IPC_NOWAIT,队列没有可读消息时不等待,返回ENOMSG错误。
msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息。
上面我们已经将消息写到了消息队列中,这个消息队列由内核维护,现在我们下一个读消息的程序,如下:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #include <errno.h> 7 #include <sys/msg.h> 8 9 struct msgbuf 10 { 11 long mtype; 12 char mtext[1024]; 13 }; 14 15 int main() 16 { 17 int msgid; 18 int ret = 0; 19 20 msgid = msgget(0x1234, 0666); 21 if(msgid < 0) 22 { 23 if(errno == EEXIST) 24 { 25 printf("msg is exist\n"); 26 } 27 perror("msgget error"); 28 exit(0); 29 } 30 31 printf("msgid = %d\n", msgid); 32 33 struct msgbuf buf; 34 memset(&buf, 0, sizeof(struct msgbuf)); 35 36 ret = msgrcv(msgid, &buf, 1024, 0, IPC_NOWAIT); 37 if(ret < 0) 38 { 39 perror("msgrcv error"); 40 exit(0); 41 } 42 43 buf.mtext[ret] = '\0'; 44 printf("recv msg : %s\n", buf.mtext); 45 46 return 0; 47 }
执行结果如下:
可以看到,读出的消息正是我们写入的。再次执行我们的验证程序(读取消息队列的状态),结果如下:
可以看到,消息已经被我们读走了,现在的队列中已经没有消息了。
综合案例如下:
1 #include <sys/types.h> 2 #include <unistd.h> 3 4 #include <stdio.h> 5 #include <string.h> 6 #include <stdlib.h> 7 #include <errno.h> 8 9 #include <sys/msg.h> 10 11 struct msg_buf 12 { 13 long mtype; 14 char data[255]; 15 }; 16 17 /* 注意long 和 int 在32bit 和 64bit系统之下是不一样的 18 struct msg_buf 19 { 20 long mtype; 21 char data[255]; 22 }; 23 */ 24 25 int main() 26 { 27 key_t key; 28 int msgid; 29 int ret; 30 struct msg_buf msgbuf; 31 int msgtype = getpid(); 32 33 key=ftok("./msgfile",'a'); 34 printf("key =[%x]\n",key); 35 36 printf("sizeof(long):%ld, sizeof(int):%d \n", sizeof(long), sizeof(int)); 37 38 msgid=msgget(key, IPC_CREAT |IPC_EXCL|0666); //通过文件对应 39 40 if(msgid==-1) 41 { 42 if (errno == EEXIST) 43 { 44 printf("EEXIST:.....\n"); 45 key=ftok("./msgfile",'a'); 46 msgid=msgget(key, IPC_CREAT|0666); //通过文件对应 47 } 48 else 49 { 50 printf("create error\n"); 51 perror("msget: \n"); 52 return -1; 53 } 54 55 } 56 printf("msgid:%d \n", msgid); 57 58 msgbuf.mtype = msgtype; // getpid(); 59 60 printf("getpid(): %d \n", getpid()); 61 strcpy(msgbuf.data,"test haha"); 62 ret = msgsnd(msgid,&msgbuf, sizeof(msgbuf.data), IPC_NOWAIT); 63 if(ret==-1) 64 { 65 printf("send message err\n"); 66 perror("senderr"); 67 return -1; 68 } 69 sleep(1); 70 71 memset(&msgbuf,0,sizeof(msgbuf)); 72 73 ret=msgrcv(msgid, &msgbuf, sizeof(msgbuf.data), msgtype, IPC_NOWAIT); 74 if(ret==-1) 75 { 76 printf("recv message err\n"); 77 perror("dd"); 78 return -1; 79 } 80 printf("recv msg =[%s]\n",msgbuf.data); 81 82 }
ftok是一个产生key值的函数,消息队列、共享内存、信号量都可以使用这个key。ftok第一个参数是路径,应用中可以将某个路径设置成一个环境变量。应用程序中就可以统一用getenv获取这个环境变量的值,得到路径名,然后再将路径名传给ftok函数。