进程间通信
为什么要有进程间通信?
早期计算机任务简单一个进程就可以搞定,但是随着计算机的飞速发展,为了完成一个任务需要许多进程来共同协作,这就需要不同进程间来进行数据交互,这就是进程间通信。
进程间通信的目的:
数据传输:一个进程把数据发给另一个进程
资源共享:不同进程间需要用到同一种资源
事件通知:一个进程需要向另一个进程通知了某些事件的发生(子进程退出发给父进程信号)
进程控制:一个进程希望控制另一个进程
进程间通信的本质就是内核维护了一块公共缓冲区
管道
管道是最古老的进程间通信方式,一个进程连接到另一个进程的数据流称为一个管道。
匿名管道
管道创建
#include <unistd.h>
int pipe(int fd[2]); //fd是文件描述符组,fd[0]代表读端,fd[1]代表写端
//创建成功返回0, 失败返回错误代码
我们可以看一个例子,父进程创建一个管道,父进程向管道写数据,子进程从管道读数据:
int main()
{
int pipefd[2] = {-1};
if(pipe(pipefd) < 0){
perror("pipe");
return -1;
}
if(fork() == 0){ //child read data
close(pipefd[1]);
while(1){
char buf[1024] ={0};
read(pipefd[0], buf, sizeof(buf)-1);
printf("buf:[%s]\n", buf);
}
}else{ // father write data
close(pipefd[0]);
while(1){
char *str = "i am father!";
write(pipefd[1], str, strlen(str));
printf("---------------------");
}
wait(NULL);
}
return 0;
}
从结果可以看出来,管道中没有数据,read阻塞,进程暂时停止,一直等到数据来为止
当管道满了,write调用阻塞,直到有进程读走数据
匿名管道的特点:
只能作用于有亲缘关系的进程之间的通信,通常一个匿名管道由一个进程创建,然后该进程调用fork,此后父子进程可以通过管道通信
进程退出,匿名管道释放,所以匿名管道的声明周期随进程
内核会对管道操作进行同步与互斥
管道是半双工的,即数据只能向一个方向流动,双方都需要通信时,需要建立两个管道
命名管道
匿名管道只能在亲缘进程间通信,我们想要让不想关的进程来进行通信,我们可以使用命名管道。
//使用命令创建命名管道
mkfifo filename
//使用函数
int mkfifo(const char *filename, mode_t mode);
命名管道个匿名管道使用方法类似,这里不在多说
消息队列
消息队列是一个进程向另一个进程发送一块数据
每个数据块被认为是有一个类型的,接收进程根据接收的数据块可以有不同的类型值
消息队列每个消息的最大长度是有上限的,可以查看/proc/sys/kernel/msgmax
每个消息队列的总字节数也是有上限的,在/proc/sys/kernel/msgmnb中
系统上消息队列的总是也有一个上限,在/proc/sys/kernel/msgmni中
创建一个消息队列
int msgget(key_t key, int msgflg); //key是消息队列的名字,msgflg是权限
//成功返回消息队列的标识码,失败返回-1
把一条消息添加到消息队列中
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//msqid:由msgget函数返回的消息队列标识码
//msgp:指向准备发送的消息
//msgsz:消息的长度
//msgflg:当消息队列满或者到系统上限时要发生的事
从一个消息队列中接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//msqid:由msgget函数返回的消息队列标识码
//msgp:指向准备接收的消息
//msgsz:消息的长度
//msgtype:实现接收优先级的简单形式
//msgflg:如果消息队列中没有相应类型的时候将要干的事
使用命令ipcs可以显示IPC资源,ipcrm可以手动删除IPC资源
共享内存
共享内存是最快的IPC方式,操作系统会在内核维护一段内存,将这块内存映射到进程的虚拟地址空间,此后进程间的数据传递不在涉及到内核,进程不再通过执行进入内核的系统调用来传递彼此数据
创建共享内存:
int shmget(key_t key, size_t size, int shmflg);
将共享内存段连接到进程地址空间:
void *shmat(int shmid, const void *shmaddr, int shmflg);
//shmaddr是指定连接的地址
将共享内存段与当前进程脱离:
int shmdt(const void *shmaddr);
//shmaddr是由shmat返回的指针
控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
一个进程建立共享内存向其中写数据
int main()
{
//创建共享内存
int shmid = shmget(0x66666, 32, IPC_CREATE|0664);
if(shmid < 0){
perror("shmget");
return -1;
}
//建立映射
void *mat = shmat(shmid, NULL, 0);
if(mat == (void *)-1){
perror("shmat");
return -1;
}
int i = 0;
while(1){
sprintf(mat, "AAAA:%d\n", i++);
sleep(1);
}
shmdt(mat);
//删除共享内存
shmctl(shmid, IPC_RMID, NULL);
return;
}
我们可以看到进程像操纵自己的虚拟地址一样来操作共享内存,所以就决定了共享内存是最快的进程间通信方式