目录
一,为什么需要进程间通信
1,数据传输;比如一个进程的数据要传输给另一个进程、
2,数据共享:多个进程共享同样的资源
3,通知事件:比如父进程等待子进程的信息通知
4,进程控制:比如一个进程想完全控制另一个进程,比如gdb完全控制进程
进程间通信的本质是,让两个进程看到同样的资源,但进程又具有独立性,不会让其他进程读取到我的数据,所以进程通信一定需要借助第三方资源来实现(OS),进程通信的本质就是拷贝,比如A和B两个进程,拷贝A的数据到OS里面,OS数据拷贝给到B进程,这样两个进程就能看到相同的资源了。
二,进程通讯方式
1,pipe管道
2,System V进程间通信
3,POSIX进程间通信
三,管道
1,什么是管道
管道是用来进程间通信的一种方式--是一种单向的通信方式,比如父子进程通信,父进程写--子进程读,不支持双向交互。
2,匿名管道
使用父子进程来试试进程交互
int pipe(fd[2])创建一个匿名管道,fd[0]表示读端。fd[1]表示写端,关闭父进程的f[1],关闭子进程f[0],单向通讯
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
//child 写 father读
int main()
{
int fd[2]={0};
if(pipe(fd)<0)
{
perror("pipe error\n");
exit(-1);
}
pid_t id=fork();
if(id==0)
{
close(fd[0]);
const char * msg="i am child to send father";
int count=10;
while(count--)
{
write(fd[1],msg,strlen(msg));
sleep(1);
}
close(fd[1]);
exit(0);
}
//father
char buff[64];
close(fd[1]);
while(1)
{
ssize_t s=read(fd[0],buff,sizeof(buff));
if(s>0)
{
buff[s]='\0';
printf("child send to father:%s\n",buff);
}
else if(s==0)
{
printf("end of file\n");
break;
}
else
{
printf("read error\n");
break;
}
}
waitpid(id,NULL,0);
return 0;
}
通过这个程序去跑,就会发现,子进程一直给父进程发送消息,直到子进程不发送了,通过实验,可得到以下结果
a,子进程一直write,父进程不read,一直阻塞,直到缓冲区被写满
b,子进程不write,一直read,read阻塞
c,父进程read关闭,子进程一直写,进程会被杀掉,进程无意义
d,write写完,关闭,read返回值为0
3,命名管道
命名管道适合两个不同的进程交互,通过创建管道,一个进程打开管道只写,一个进程打开管道只读,通过调用进程,就会看到管道的效果,废话不多说--看代码
读端代码,创建管道,读取管道数据并打印
#include"comm.h"
int main()
{
//创建管道,只读方式打开 读取文件
if(mkfifo(FIFE_NAME,0644)<0)//创建管道,默认权限给0644
{
perror("creat fail\n");
}
int fd=open(FIFE_NAME,O_RDONLY);//以只读方式打开管道,
if(fd<0)
{
printf("open fail\n");
}
char msg[128];
while(1)
{
msg[0]=0;
//读取数据
ssize_t s=read(fd,msg,sizeof(msg)-1);
if(s>0)
{
msg[s]=0;
printf("Client#:%s\n",msg);
}
else if(s==0)
{
printf("Client quit\n");
break;
}
else
{
printf("read error\n");
break;
}
}
close(fd);
return 0;
}
写端代码,把数据写入到管道
#include"comm.h"
int main()
{
//写数据
int fd=open(FIFE_NAME,O_WRONLY);
if(fd<0)
{
perror("open fail\n");
return 1;
}
char msg[128];
while(1)
{
msg[0]=0;
printf("please enter#");
fflush(stdout);
ssize_t s=read(0,msg,sizeof(msg));
msg[s-1]=0;
if(s>0)
{
write(fd,msg,s);
}
else if(s==0)
{
write(fd,msg,s);
printf("read end of file\n");
break;
}
else
{
break;
}
}
return 0;
}
细节:当我们把读端关闭时,一直往管道里面写,我们去看管道的文件大小时,会发现大小并未发生改变,这是因为,命名管道和匿名管道一样,都是通过系统提供内存,底层都是系统的内存调用
管道总结:
1,管道内部提供互斥与同步机制
2,如果子进程写段关闭,父进程read就会返回0,代表文件结束
3,如果打开文件的进程关掉了,管道也就会被释放掉
4,管道提供给流式服务
5,管道是半双工通信
6,匿名管道,适合有血缘关系的进程使用,常用于父子
四,共享内存
1,实现原理
通过让不同的进程看到同一份资源,就能让两个进程在这个资源上一个写,一个读,以传送数据为目的。
那进程有没有能力去物理内存映射到进程的地址空间呢?
答案是有的,通过系统提供的API接口,完成开辟空间,建立映射,开辟虚拟地址,返回给用户
如果不释放共享内存,那么共享内存会一直存在,直到关机,因为共享内存的生命周期随内核
2,怎么构造共享内存和使用
1,建立共享内存
1,申请共享内存,开辟地址空间
2,共享内存挂接到地址空间,建立映射关系
2,释放资源给系统
1,去关联共享内存,修改页表,取消映射关系
2,释放共享内存
3,系统函数接口
1,建立共享内存
int shmget(key_t key,size_t size,int shmflg)
key共享内存段的名字,一般使用ftok获得唯一的映射名字,size期待开辟的空间大小,shmlg权限标志位,与文件的标志位类似。返回值返回这个共享内存的唯一id号,失败返回-1
2,共享内存挂接
void *shmat(int shmid,const void *shmaddr,int shmflg)
shmid,共享内存的唯一标识号,shmaddr,期待关联的地址,shmflg 有两个可能取值SHM_RND和SHM_RDONLY,返回值,成功返回指针,指向共享内存的空间,失败返回-1
3,去关联共享内存
int shmdt(const void *shmaddr) ,对上述挂接到内存的指针进行释放,去掉连接
4,释放共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
cmd有三个取值,决定了这个函数要做什么,一般使用IPC_RMID释放共享内存给操作系统。
实现代码:
1,建立共享内存,写入数据
#include"comm.h"
int main()
{
//创建共享内存的唯一Key值
key_t key = ftok(PATH_NAME,PROJ_ID);
if(key<0)
{
perror("ftok\n");
return 1;
}
int shmid=shmget(key,SIZE,IPC_CREAT);
if(shmid<0)
{
perror("shmget\n");
return 2;
}
char *mem=shmat(shmid,NULL,0);
int i=0;
int num=100;
while(num--)
{
mem[i]='A'+i;
sleep(1);
i++;
mem[i]='\0';
}
//释放共享内存
shmdt(mem);
shmctl(shmid,IPC_RMID,0);
return 0;
}
2,读取共享内存信息
#include"comm.h"
int main()
{
key_t key=ftok(PATH_NAME,PROJ_ID);
if(key<0)
{
perror("creat fail\n");
return 1;
}
int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0644);
if(shmid<0)
{
perror("shmid error\n");
return 2;
}
char *mem=shmat(shmid,NULL,0);
int num=100;
while(num--)
{
printf("CLient#:%s\n",mem);
sleep(1);
}
shmdt(mem);
return 0;
}
3,头文件
.PHONY:all
all:client sever
client:client.c
gcc -o $@ $^
sever:sever.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f client sever
但是使用共享内存需要注意的一点是,共享内存没有互斥和同步,有可能这边还没写完,另一边已经开始读取了。
4,命令行下的查看共享内存和释放共享内存
ipcs-m
查看共享内存的使用信息,连接信息,shmid和共享内存的Key值
ipcrm -m shmid
释放共享内存-通过唯一标识符释放,
五,消息队列
1,消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
2,每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
3,特性:IPC资源必须清除,否则不会自动清除,因为消息队列的资源的生命周期随内核。
六 ,总结
1,管道通信的目的在于进程中的数据交互,匿名多用于父子或者有血缘关系的进程,命名管道需要创建管道来实现进程数据的交互,多用于无血缘关系的进程(其最终也是os的调用,与匿名管道区别不大),需要经过内核的调用才能传递信息。
2,共享内存通信,是进程最快的通信方式,不用经过内核处理,但其没有互斥与同步,可能导致数据紊乱,创建的共享内存随系统,不释放就会一直存在。