最近这两次博客总是讲到一个概念:进程间通信。而且两次总结的内容都不一样,让人有点蒙圈了,哈哈。那我们就将进程间通信的一些相关知识总结一下,然后开始我们今天的知识总结。
1.进程间通信,实际上就是几个进程之间进行数据传输(比如进行读写操作、单向传输数据、双向传输数据等等操作,达到一个信息交流的作用。就好比我们人与人之间交流一样,可以有很多方式:面对面聊天、微信、QQ等等)。
2.进程通信的目的:数据传输、资源共享、通知事件、进程控制。
3.进程分类:(如下图)
通过上面知识点的补充,大家一定对“进程间通信”这个概念有了一个整体的了解,接下来我们就来总结一下IPC的三种形式,其中,主要总结消息队列。
一、消息队列
1.消息队列:有类型的数据块
(前面我们说到了管道,不管是匿名管道还是命名管道,它们只能进行单向的数据传输,结合现实生活,我们不难想到,两个 人聊天,也不可能一个人说,另外一个人听。进程间通信也是如此,所以就有了消息队列,它是由操作系统提供的队列)
下面是消息队列的一个简单的原理图
2.消息队列函数
(1)msgget函数
功能:创建和访问一个消息队列
原型:int msgget(key_t key, int msgflg);
参数:key--某个消息队列的名字; msgflg--由九个权限标志构成,它们的使用和创建文件时使用mode模式一样。
返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1
(2)msgctl函数
功能:消息队列的控制函数
原型:int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:msqid:由msgget函数返回的消息队列标识码; cmd:将要采取的动作(有三个可取值),如下图
返回值:成功返回0,失败返回-1
(3)msgsnd函数
功能:把一条消息添加到消息队列中
原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:msgp:一个指针,指针指向准备发送的消息; msgsz:是msgp指向的消息长度(该长度不包含保存消息类型的长整型)
msgflg=IPC_NOWAIT表示队列不等待,返回EAGAIN错误。
返回值:成功返回0,失败返回-1
(4)msgrcv函数
功能:从一个消息队列接收消息
原型:ssize_t msgrcv(int msqid, void * msgp, size_t msgsz, long msgtyp, int msgflg);
参数:msgtyp:它可以实现接收优先级的简单形式
返回值:成功返回实际放到接收缓存区的字符个数,失败返回-1
(5)几点说明:
1)msgtype=0,返回队列第一条信息
2)msgtype>0,返回队列第一条类型等于msgtype的消息
3)msgtype<0,返回队列第一条类型小于等于msgtype绝对值的消息,并且满足条件的消息类型最小的消息
4)msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG
5)msgflg=MSG_NOERROR,消息大小超过msgsz时被截断
6)msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息
3.代码实例
代码测试结构
# ls
client.c comm.c comm.h Makefile server.c
# cat Makefile
.PHONY:;all
all:client server
client:client.c comm.c
gcc -o $@ $^
server:server.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client server
comm.h
#ifdef _COMM_H__
#define _COMM_H__
#include
#include
#include
#include
#include
#define PATHNAME "."
#define PROJ_ID 0x6666
#define SERVER_TYPE 1
#define CLIENT_TYPE 2
struct msgbuf{
long mtype;
char mtext[1024];
};
int createMsgQueue();
int getMsgQueue();
int destroyMsgQueue(int msgid);
int sendMsg(int msgid, int who, char *msg);
int recvMsg(int msgid, int recvType, char out[]);
#endif
comm.c
#include"comm.h"
static int commMsgQueue(int flags)
{
key_t _key = ftok(PATHNAME, PROJ_ID);
if (_key < 0)
{
perror("ftok");
return -1;
}
int msgid = msgget(_key, IPC_CREAT | IPC_EXCL);
if (msgid < 0)
{
perror("msgget");
}
return msgid;
int createMsgQueue()
{
return commMsgQueue(IPC_CREAT | IPC_EXCL | 0666);
}
int getMsgQueue()
{
return commMsgQueue(IPC_CREAT);
}
int destroyMsgQueue(int msgid)
{
if (msgctl(msgid, IPC_RMID, NULL) < 0)
{
perror("msgctl");
return -1;
}
return 0;
}
int sendMsg(int msgid, int who, char *msg)
{
struct msgbuf buf;
buf.mtype = who;
strcpy(buf.mtext, msg);
if (msgsnd(msgid, (void*)&buf, sizeof(buf.mtext), 0) < 0)
{
perror("msgnsnd");
return -1;
}
return 0;
}
int recvMsg(int msgid, int recvType, char out[])
{
struct msgbuf buf;
if (msgrcv(msgid, (void*)&buf, sizeof(buf.mtext), recvType, 0) < 0)
{
perror("msgrcv");
return -1;
}
strcpy(out, buf.mtext);
return 0;
}
}
server.c
#include"comm.h"
int main()
{
int msgid = createMsgQueue();
char buf[1024];
while (1)
{
buf[0] = 0;
recvMsg(msgid, CLIENT_TYPE, buf);
printf("client# %s\n", buf);
printf("Please Enter# ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf));
if (s > 0)
{
buf[s - 1] = 0;
sendMsg(msgid, SERVER_TYPE, buf);
printf("send done,wait recv...\n");
}
}
destroyMsgQueue(msgid);
return 0;
}
client.c
#include"comm.h"
int main()
{
int msgid = getMsgQueue();
char buf[1024];
while (1)
{
buf[0] = 0;
printf("Plese Enter# ");
fflush(stdout);
ssize_t s = read(0, buf, sizeof(buf));
if (s > 0)
{
buf[s - 1] = 0;
sendMsg(msgid, CLIENT_TYPE, buf);
printf("send done,wait recv...\n");
}
recvMsg(msgid, SERVER_TYYPE, buf);
printf("server# %s\n", buf);
}
return 0;
}
4.ipcs&ipcrm命令
由于消息队列和管道的生命周期不同,消息队列的生命周期随内核,所以我们需要手动进行其删除操作,这里我们介绍两个很重要的命令。
(1)ipcs:显示ipc的资源
(2)ipcrm:手动删除ipc资源
二、共享内存
1.共享内存是最快的IPC形式(因为两个进程互相通信时,共享内存没有两次数据的拷贝),一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不在涉及到内核(即就是:进程不在通过执行进入内核的系统调用来传递彼此的数据)
说明:共享内存的生命周期也是随内核,因此需要用命令、接口来删除
2.共享内存的示意图(以两个进程为例)
说明:共享内存如果没有数据,不会被卡住,并且共享内存没有同步和互斥机制。
3.共享内存函数
共享内存也有四个函数,如下:
//shmget函数--创建共享内存
int shmget(key_t key, size_t size, int shmflg);
//shmat函数--将共享内存连接到进程的地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
//shmdt函数--将共享内存段与当前进程脱离
int shmdt(const void *shmaddr);
//shmctl函数--控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
三、信号量
1.信号量主要用于同步和互斥机制的。上一片博客我们已经提到了同步和互斥的概念,下面我们再整体的说一遍。
(1)进程互斥:由于各个进程间要求资源共享,而有些资源需要互斥使用,因此各进程间竞争使用这些资源称为进程的互斥。
(其中,系统中某些资源一次只允许一个资源使用,这样的资源称为临界资源或者互斥资源)
举例如图:
(2)进程同步:指所有进程之间相互配合共同完成一项任务。
2.信号量和P、V原语
(1)互斥:P、V在同一个进程中
同步:P、V在不同的进程中
(2)信号量值含义
S>0:S表示可用资源的个数
S=0:表示无可用资源,无进程等待
S<0:|S|表示等待队列中进程的个数
3.信号量函数
//semget函数--创建和访问一个信号量集
int semget(key_t key, int nsems, int semflg);
//semctl函数--控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
//semop函数--创建和访问一个信号量集
int semop(int semid, struct sembuf *sops, unsigned nsops);