使用文件也可以完成 IPC,理论依据是,fork 后,父子进程共享文件描述符。也就共享打开的文件。
编程:父子进程共享打开的文件。借助文件进行进程间通信。
测试代码
/*
*
* 父子进程共享打开的文件描述符----使用文件完成进程间通信
*
*/
#include
#include
#include
#include
#include
#include
int main(void)
{
int fd1,fd2;
pid_t pid;
char buf[1024];
char *str="----------test for shared fd in parent child process -----\n";
pid=fork();
if(pid<0){
perror("fork error");
exit(1);
}else if(pid==0){
fd1=open("test.txt",O_RDWR);
if(fd1<0){
perror("open error");
exit(1);
}
write(fd1,str,strlen(str));
printf("child wrote over...\n");
}else{
fd2=open("test.txt",O_RDWR);
if(fd2<0){
perror("open error");
exit(1);
}
sleep(1); //保证子进程写入数据
int len =read(fd2,buf,sizeof(buf));
write(STDOUT_FILENO,buf,len);
wait(NULL);//回收防止僵尸进程
}
return 0;
}
思考,无血缘关系的进程可以打开同一个文件进行通信吗?为什么?
可以,,无血缘关系的进程也可以打开同一个文件进行通信,方法一样,因为这些进程打开的是同一个进程。其实在打开文件时(调用open时),操作系统内核就调用了mmap。因为一个文件只有一个文件结构体(FILE),打开时位于内核,被打开这个文件的多个进程共享。
存储映射 I/O(Memory-mappedI/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取 数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可 在不适用 read 和 write 函数的情况下,使用地址(指针)完成 I/O 操作。
使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过 mmap 函数来实
现。
void* mmap(void* adrr,size_t length,int prot,int flags,int fd,off_toffset);
返回:成功:返回创建的映射区首地址;失败:MAP_FAILED 宏
参数:
addr: 建立映射区的首地址,由 Linux 内核指定。使用时,直接传递 NULL
length: 欲创建映射区的大小
prot: 映射区权限 PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags: 标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区) MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上。 MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。
fd: 用来建立映射区的文件描述符
#include
#include
#include
#include
#include
#include
int main(void)
{
int len,ret;
char *p=NULL;
int fd = open("mytest.txt",O_CREAT|O_RDWR,0644);
if(fd<0){
perror("open error");
exit(1);
}
//确定文件大小
len = ftruncate(fd,4);
if(len == -1){
perror("ftruncate error:");
exit(1);
}
p = mmap(NULL, 4 , PROT_READ|PROT_WRITE , MAP_SHARED, fd, 0);
if(p == MAP_FAILED){
perror("mmap error:");
exit(1);
}
strcpy(p,"abc");//写数据
ret = munmap(p,4);//释放映射区
if(ret==-1){
perror("munmap error:");
exit(1);
}
close(fd);
return 0;
}
同 malloc 函数申请内存空间类似的,mmap 建立的映射区在使用结束后也应调用类似 free 的函数来释放。 int munmap(void *addr,size_t length);
成功:0; 失败:-1
父子等有血缘关系的进程之间也可以通过 mmap 建立的映射区来完成数据通信。但相应的要在创建映射区的时 候指定对应的标志位参数 flags:
#include
#include
#include
#include
#include
#include
int var=100;
int main(void )
{
int *p;
pid_t pid;
int fd;
fd=open("temp",O_RDWR|O_CREAT|O_TRUNC,0644);
if(fd<0){
perror("open error:");
exit(1);
}
unlink("temp"); //删除临时文件目录项,使之具备被释放条件,所有使用该文>件的进程结束后该文件才释放
ftruncate(fd,4); //创建文件大小
p=(int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);//父子进程共享映射区
// p=(int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);//对映射区各自独>占 if(p==MAP_FAILED){ //不是p==NULL时出错
perror("mmap,error");
exit(1);
}
close(fd); //映射区建立完毕,即可关闭文件
//完成数据传递
pid=fork();
if(pid==0){
*p=2000;
var=1000;
printf("child,*p=%d,var = %d\n",*p,var);
}else{
sleep(1);
printf("parent,*p = %d,var = =%d\n",*p,var);
wait(NULL);
int ret= munmap(p,4);
if(ret==-1){
perror("munmap error");
exit(1);
}
}
return 0;
}
结论:父子进程共享:
通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创 建映射区一定要依赖一个文件才能实现。通常为了建立映射区要 open 一个 temp 文件,创建好了再 unlink、close 掉,比较麻烦。 可以直接使用匿名映射来代替。其实 Linux 系统给我们提供了创建匿名映射区的方法,无需依赖一 个文件即可创建映射区。同样需要借助标志位参数 flags 来指定。
使用 MAP_ANONYMOUS(或 MAP_ANON),
int*p=mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
"4"随意举例,该位置表大小,可依实际需要填写。
需注意的是,MAP_ANONYMOUS 和 MAP_ANON 这两个宏是 Linux 操作系统特有的宏。在类 Unix 系统中如无该 宏定义,可使用如下两步来完成匿名映射区的建立。
fd=open("/dev/zero",O_RDWR);
p=mmap(NULL,size,PROT_READ|PROT_WRITE,MMAP_SHARED,fd,0);
示例代码:
#include
#include
#include
#include
#include
#include
int var=100;
int main(void )
{
int *p;
pid_t pid;
//不使用文件参数传-1
p=(int *)mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);//对映射区>各自独占
if(p==MAP_FAILED){ //不是p==NULL时出错
perror("mmap,error");
exit(1);
}
//完成数据传递
pid=fork();
if(pid==0){
*p=2000;
var=1000;
printf("child,*p=%d,var = %d\n",*p,var);
}else{
sleep(1);
printf("parent,*p = %d,var = =%d\n",*p,var);
wait(NULL);
int ret= munmap(p,4);
if(ret==-1){
perror("munmap error");
exit(1);
}
}
return 0;
}
Linux下这两个文件无大小
第一个文件好比聚宝盆,可以随意映射
第二个文件,一般错误洗脑洗重定向到这个文件中
实质上 mmap 是内核借助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核 空间多进程共享,因此无血缘关系的进程间也可以使用 mmap 来完成通信。只要设置相应的标志位参数 flags 即可。 若想实现共享,当然应该使用 MAP_SHARED 了。
读数据
/*
* 非血缘关系进程间通信
*/
#include
#include
#include
#include
#include
#include
#include
struct STU{
int id;
char name[20];
char sex;
};
//出错处理函数
void sys_err(char *str)
{
perror(str);
exit(-1);
}
int main(int argc,char *argv[])
{
int fd;
struct STU student;
struct STU *mm;
if(argc<2){
printf("./a.out file_shared\n");
exit(-1);
}
fd=open(argv[1],O_RDONLY);
if(fd == -1)
sys_err("open error");
mm = mmap(NULL,sizeof(student),PROT_READ,MAP_SHARED,fd,0);
if(mm == MAP_FAILED)
sys_err("mmap error");
close(fd);
while(1){ //读进程
printf("id=%d\tname=%s\t%c\n",mm->id,mm->name,mm->sex);
sleep(2);
}
munmap(mm,sizeof(student));
return 0;
}
写数据
/*
* 非血缘关系之间的通信
*/
#include
#include
#include
#include
#include
#include
#include
#include
struct STU{
int id;
char name[20];
char sex;
};
void sys_err(char *str)
{
perror(str);
exit(1);
}
int main(int argc,char *argv[])
{
int fd;
struct STU student={10,"xiaoming",'m'};
char *mm;
if(argc<2){
printf("./a.out file_shared\n");
exit(-1);
}
fd = open(argv[1],O_RDWR | O_CREAT,0644);
ftruncate(fd,sizeof(student));
mm = mmap(NULL,sizeof(student),PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(mm == MAP_FAILED)
sys_err("mmap");
close(fd);
while(1){
memcpy(mm,&student,sizeof(student));
student.id++; //循环向内存复制
sleep(1);
}
munmap(mm,sizeof(student));
return 0;
}