共享存储映射
文件进程间通信
使用文件也可以完成 IPC,理论依据是,fork 后,父子进程共享文件描述符。也就共享打开的文件。
编程:父子进程共享打开的文件。借助文件进行进程间通信。
测试代码
/*** 父子进程共享打开的文件描述符----使用文件完成进程间通信**/#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/wait.h>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
存储映射 I/O(Memory-mappedI/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取 数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可 在不适用 read 和 write 函数的情况下,使用地址(指针)完成 I/O 操作。
使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过 mmap 函数来实
现。
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: 用来建立映射区的文件描述符
-
offset: 映射文件的偏移(4k 的整数倍)
#include<stdio.h>#include<fcntl.h>#include<unistd.h>#include<string.h>#include<stdlib.h>#include<sys/mman.h>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;}
munmap 函数
同 malloc 函数申请内存空间类似的,mmap 建立的映射区在使用结束后也应调用类似 free 的函数来释放。 int munmap(void *addr,size_t length);
成功:0; 失败:-1
mmap 注意事项
思考:
- 可以 open 的时候 O_CREAT 一个新文件来创建映射区吗?
答:可以,但新CREATE出来的文件不行,必须要有实际的大小 - 如果 open 时 O_RDONLY,mmap 时 PROT 参数指定 PROT_READ|PROT_WRITE 会怎样?
答:不行,创建映射区的权限小于等于打开文件的权限,映射区创建的过程中存在一次读文件操作权限不足。 - 文件描述符先关闭,对 mmap 映射有没有影响?
答: 没有影响,文件描述符是操作文件的句柄,有映射区了,就是地址的方式操作,所以文件描述符就没意义了。 - 如果文件偏移量为 1000 会怎样?
答:不行,偏移量必须是一页的大小(4k) - 对 mem 越界操作会怎样?
答:不行,释放映射区可能会失败,只有创建映射区的地址和释放时的地址要是同一个地址 - 如果 mem++,munmap 可否成功?
答:不能,释放映射区的时候要传首地址和映射区大小给munmap,但++后首地址就不是创建时的首地址,只有创建映射区的地址和释放时的地址要是同一个地址 - mmap 什么情况下会调用失败? 8. 如果不检测 mmap 的返回值,会怎样?
答:返回值必须检查,空间不能为0,文件大小和映射区要匹配等等情况下会失败
总结
- 创建映射区的过程中,隐含着一次对映射文件的读操作。
- 当 MAP_SHARED 时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而 MAP_PRIVATE 则无所谓,因为 mmap 中的权限是对内存的限制。
- 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。
- 特别注意,当映射文件大小为 0 时,不能创建映射区。所以:用于映射的文件必须要有实际大小!! mmap 使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
- munmap 传入的地址一定是 mmap 的返回地址。坚决杜绝指针++操作。
- 如果文件偏移量必须为 4K 的整数倍 7. mmap 创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
mmap 父子进程通信
父子等有血缘关系的进程之间也可以通过 mmap 建立的映射区来完成数据通信。但相应的要在创建映射区的时 候指定对应的标志位参数 flags:
- MAP_PRIVATE: (私有映射) 父子进程各自独占映射区;
- MAP_SHARED: (共享映射) 父子进程共享映射区;
编程:父进程创建映射区,然后 fork 子进程,子进程修改映射区内容,而后,父进程读取映射区内容,查验是 否共享
#include<stdio.h> #include<stdlib.h>#include<unistd.h>#include<fcntl.h>#include<sys/mman.h>#include<sys/wait.h>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;}
结论:父子进程共享:
- 打开的文件
- mmap 建立的映射区(但必须要使用 MAP_SHARED)
匿名映射
通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创 建映射区一定要依赖一个文件才能实现。通常为了建立映射区要 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<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/wait.h>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 是内核借助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核 空间多进程共享,因此无血缘关系的进程间也可以使用 mmap 来完成通信。只要设置相应的标志位参数 flags 即可。 若想实现共享,当然应该使用 MAP_SHARED 了。
读数据
/** 非血缘关系进程间通信*/
#include<stdio.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<string.h>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<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/mman.h>
#include<string.h>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;
}