Linux下IPC方式之共享存储映射(mmap)
1. 共享存储映射(mmap)
把文件中的某一段映射到内存上
mmap函数原型:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数:
addr 建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
length 映射区长度
prot
PROT_READ 可读
PROT_WRITE 可写
flags
MAP_SHARED 共享的,对内存的修改会影响到源文件
MAP_PRIVATE 私有的
fd 文件描述符
offset 偏移量
返回值
成功 返回 可用内存的首地址
失败 返回 MAP_FAILED
释放内存区
#include <sys/mman.h>
int munmap(void *addr, size_t length);
addr 传mmap返回值
length mmap创建的长度
返回值
成功:0; 失败:-1
实例(MAP_SHARED的作用是,你修改了内存,会影响文件)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<string.h>
int main(){
int fd=open("men.txt", O_RDWR);
//创建映射区
char *mem=(char*)mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(mem==MAP_FAILED){
perror("mmap err");
return -1;
}
//拷贝数据
strcpy(mem, "hello");
//释放mmap
munmap(mem, 8);
close(fd);
return 0;
}
2. mmap九问
- 如果更改mem变量的地址,释放的时候munmap,传入mem还能成功吗?
- 如果对mem越界操作会怎样?
- 如果文件偏移量随便填个数会怎样?
- 如果文件描述符先关闭,对mmap映射有没有什么影响?
- open的时候,可以新创建一个文件来创建映射区吗?
- open文件选择O_WRONLY,可以吗?
- 当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择PROT_READ|PROT_WRITE吗?
- mmap什么情况下会报错?
- 如果不判断返回值会怎么样?
1.如果更改mem变量的地址,释放的时候munmap,传入mem还能成功吗?
问题一的测试:(不能改,否则会释放失败)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<string.h>
int main(){
int fd=open("men.txt", O_RDWR);
//创建映射区
char *mem=mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(mem==MAP_FAILED){
perror("mmap err");
return -1;
}
//拷贝数据
strcpy(mem, "hello");
mem++;
//如果释放失败
if (munmmap(mem, 8) < 0) {
perror("munmmap err");
}
close(fd);
return 0;
}
运行结果:
2.如果对mem越界操作会怎样?
问题二的测试:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/mman.h>
#include<string.h>
int main(){
int fd=open("men.txt", O_RDWR);
//创建映射区
char *mem=mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(mem==MAP_FAILED){
perror("mmap err");
return -1;
}
//拷贝数据
strcpy(mem, "hellollllllllllll");
//释放内存
munmmap(mem, 8);
close(fd);
return 0;
}
文件的大小对映射区操作有影响,尽量避免。
3.如果文件偏移量随便填个数会怎样?
char *mem=mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 1000000000);
offset必须是4k的整数倍。
4.如果文件描述符先关闭,对mmap映射有没有什么影响?
没有。因为mmap之后,通道就打通了,文件就没用了。
5.open的时候,可以新创建一个文件来创建映射区吗?
int fd=open("men.txt", O_RDWR|O_TRUNC, 0664);//创建并截断文件
如果新创建的文件是空的,会报错。
如果文件大小不为0,则可以。
6.open文件选择O_WRONLY,可以吗?
不可以,映射到内存的时候隐含一次读操作,如果只有写权限,则会报错
7.当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择PROT_READ|PROT_WRITE吗?
不可以。SHARED的时候,映射区的权限要小于等于open文件的权限。
3. mmap实现父子进程通信
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
int main()
{
// 先创建映射区
int fd = open("mem.txt",O_RDWR);
int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
//int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
if(mem == MAP_FAILED){
perror("mmap err");
return -1;
}
// fork子进程
pid_t pid = fork();
// 父进程和子进程交替修改数据
if(pid == 0 ){
//son
*mem = 100;
printf("child,*mem = %d\n",*mem);
sleep(3);
printf("child,*mem = %d\n",*mem);
}
else if(pid > 0){
//parent
sleep(1);
printf("parent,*mem=%d\n",*mem);
*mem = 1001;
printf("parent,*mem=%d\n",*mem);
//回收子进程
wait(NULL);
}
//释放内存
munmap(mem,4);
close(fd);
return 0;
}
运行结果
将上面的注释去掉,改成
//int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
运行结果
父进程并没有读到子进程的数据,子进程也没有读到父进程改的数据。
如果要实现父子进程之间通信,需要将flags设为MAP_PRIVATE
4. 匿名映射
避免打开文件的操作(上面的例子都有调用open函数)
int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
使用示例
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
int main()
{
int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
if(mem == MAP_FAILED){
perror("mmap err");
return -1;
}
pid_t pid = fork();
if(pid == 0 ){
//son
*mem = 101;
printf("child,*mem=%d\n",*mem);
sleep(3);
printf("child,*mem=%d\n",*mem);
}else if(pid > 0){
//parent
sleep(1);
printf("parent,*mem=%d\n",*mem);
*mem = 10001;
printf("parent,*mem=%d\n",*mem);
wait(NULL);
}
munmap(mem,4);
return 0;
}
运行结果
注意:有的Unix系统中没有MAP_ANON,ANONYMOUS这两个宏。此时该怎么办?
此时用这个
/dev/zero
是一个聚宝盆,无限大,用它做匿名映射,你想取多大都可以。
另外一个,/dev/null
是一个无底洞,一般错误信息重定向到这个文件中
小技巧——快速把一个文件头几行的数据重定向到另一个文件中(会覆盖目标文件)
5. mmap实现无血缘进程通信
写端
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
typedef struct _Student{
int sid;
char sname[20];
}Student;
int main(int argc,char *argv[])
{
if(argc != 2){
printf("./a.out filename\n");
return -1;
}
// 1. open file
int fd = open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0666);
//结构体的大小
int length = sizeof(Student);
//将文件大小改变为参数length指定的大小,
//如果原来的文件大小比参数length大,则超过的部分会被删除,
//如果原来的文件大小比参数length小,则文件将被扩展
ftruncate(fd,length);
// 2. mmap
Student * stu = mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
//如果不判断是否出错,会死的很难看
if(stu == MAP_FAILED){
perror("mmap err");
return -1;
}
int num = 1;
// 3. 修改内存数据
while(1){
stu->sid = num;
sprintf(stu->sname,"xiaoming-%03d",num++);
sleep(1);//相当于每隔1s修改一次映射区的内容
}
// 4. 释放映射区和关闭文件描述符
munmap(stu,length);
close(fd);
return 0;
}
读端
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>
typedef struct _Student{
int sid;
char sname[20];
}Student;
int main(int argc,char *argv[])
{
//open file
int fd = open(argv[1],O_RDWR);
//mmap
int length = sizeof(Student);
Student *stu = mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(stu == MAP_FAILED){
perror("mmap err");
return -1;
}
//read data
while(1){
printf("sid=%d,sname=%s\n",stu->sid,stu->sname);
sleep(1);
}
//close and munmap
munmap(stu,length);
close(fd);
return 0;
}
运行结果:
再开一个读的: