mmap内存映射

内存映射通信

一、mmap (memory_map)

1.1 简介

存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成I/O操作。使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。

在这里插入图片描述

1.2 mmap函数
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

参数:
	addr: 指定映射区的首地址。通常传 NULL,表示让系统自动分配
	length:共享内存映射区的大小。(<= 文件的实际大小)
    *如果使用length > 文件大小,那么就会出现总线错误*
	prot: 共享内存映射区的读写属性。PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
	flags: 标注共享内存的共享属性。MAP_SHARED、MAP_PRIVATE  
    		shared 意思是修改会反映到磁盘上
			private 表示修改不反映到磁盘上
	fd: 用于创建共享内存映射区的那个文件的 文件描述符。
offset:默认 0,表示映射文件全部。偏移位置。需是 4k 的整数倍。
返回值:
	成功:映射区的首地址。
	失败:MAP_FAILED (void*(-1)), errno

二、程序例程

2.1 mmap建立映射区
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

void sys_err(const char* str){
    perror(str);
    exit(1);
}

/*
mmap:memorty_map 内存映射试验
*/
int main(int argc,char* argv[]){

    char *p = NULL; //用于接收mmap的返回地址
    int fd; //用于文件的接收

    fd = open("testmmap", O_RDWR | O_CREAT | O_TRUNC, 0664);

    if(fd == -1){//打开失败
        sys_err("open error");
    }
	/*
    lseek(fd, 9, SEEK_END);  //将文件给扩展长度到10
    write(fd, "\0", 1);
    */
    ftruncate(fd,10); //将文件给扩展长度到10

    int len = lseek(fd, 0, SEEK_END);//获取文件的长度
    //将文件全部进行内存映射
    p = (char*)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if(p == MAP_FAILED){
        sys_err("mmap error");
    }
    
    close(fd);

    strcpy(p, "hello\n");//进行写操作
    printf("------%s\n",p);//打印

    int ret = munmap(p,len);//解除映射

    if(ret == -1){
        sys_err("munmap error");
    }

    return 0;
}

运行结果

haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ ls
makefile  mmap_test  mmap_test.c  testmmap
haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ cat testmmap 
hello
haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ ./mmap_test 
------hello

haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ 
2.2 使用注意事项
  1. 用于创建映射区的文件大小为 0,实际指定非 0 大小创建映射区,出 “总线错误”。

  2. 用于创建映射区的文件大小为 0,实际制定 0 大小创建映射区, 出 “无效参数”。

  3. 用于创建映射区的文件读写属性为,只读。映射区属性为 读、写。 出 “无效参数”。

  4. 创建映射区,需要 read 权限。当访问权限指定为 “共享”MAP_SHARED 时, mmap 的读写权

限,应该 <=文件的 open 权限。

只写不行。

  1. 文件描述符 fd,在 mmap 创建映射区完成即可关闭。后续访问文件,用 地址访问。

  2. offset 必须是 4096 的整数倍。(MMU 映射的最小单位 4k )

  3. 对申请的映射区内存,不能越界访问。

  4. munmap 用于释放的 地址,必须是 mmap 申请返回的地址。

  5. 映射区访问权限为 “私有”MAP_PRIVATE, 对内存所做的所有修改,只在内存有效,不会反

应到物理磁盘上。

  1. 映射区访问权限为 “私有”MAP_PRIVATE, 只需要 open 文件时,有读权限,用于创建映射

区即可。

3.3 进行函数封装

借鉴malloc和free函数原型,写自定义函数smalloc,sfree来完成映射区的建立和释放

#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

void sys_err(const char* str){
    perror(str);
    exit(1);
}

//实现类似c语言中的malloc
/**
 * len:创建内存映射的长度
 * filename:文件名
 * void* 返回值,内存映射的首地址
 */
void *smalloc(int len,char *filename){
    void *p = NULL;
    int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0664);
    if(fd == -1){
        perror("open error");  //文件打开失败
        return NULL;
    }

    ftruncate(fd,len); //对文件进行扩展

    int fileLen = lseek(fd, 0, SEEK_END); //获取文件的长度
    //对文件的全部进行处理成全映射
    p = (char*)mmap(NULL, fileLen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if(p == MAP_FAILED){ //如果没有实现映射的话
        sys_err("mmap error");
        return MAP_FAILED;//实际上就是(void*)(-1)
    }

    return p;
}

/**
 * p:创建的内存映射首地址
 * len:创建内存映射的长度
 */
int sfree(void * p,int len){
    int ret = munmap(p,len);
    if(ret == -1){
        perror("munmap error");
        return -1;
    }
    return 1;
}

int main(int argc,char* argv[]){

    char *p = NULL; //用于接收mmap的返回地址
    //int fd; //用于文件的接收

    p = (char *)smalloc(20, "smallocTest");

    strcpy(p, "hello nihao\n");

    printf("--------%s", p);

    int ret = sfree((void *)p,20);

    if(ret == 1){
        printf("munmap success");
    }else{
        printf("munmap error");
    }

    return 0;
}

函数执行结果:

haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ ./smalloc 
--------hello nihao
munmap successhaitu@ubuntu:/opt/modbus_test/linux_test/mmap$ ls
makefile  mmap_test  mmap_test.c  smalloc  smalloc.c  smallocTest  testmmap
haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ cat smallocTest 
hello nihao
haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ 

三、父子进程之间的mmap通信

父子进程使用 mmap 进程间通信:

父进程 先 创建映射区。 open( O_RDWR) mmap( MAP_SHARED );

指定 MAP_SHARED 权限 fork() 创建子进程。

一个进程读, 另外一个进程写。

#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

void sys_err(const char* str){
    perror(str);
    exit(1);
}

int var = 100;

/*
mmap:memorty_map 内存映射试验
*/
int main(int argc,char* argv[]){

    char *p = NULL; //用于接收mmap的返回地址
    int fd; //用于文件的接收
	pid_t pid;

    fd = open("testmmap", O_RDWR | O_CREAT | O_TRUNC, 0664);

    if(fd == -1){//打开失败
        sys_err("open error");
    }
    /*
    lseek(fd, 9, SEEK_END);  //将文件给扩展长度到10
    write(fd, "\0", 1);
    */
    ftruncate(fd,10); //将文件给扩展长度到10

    int len = lseek(fd, 0, SEEK_END);//获取文件的长度
    //将文件全部进行内存映射
    p = (char*)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if(p == MAP_FAILED){
        sys_err("mmap error");
    }
	close(fd);
    /*
    int flag = unlink("testmmap");  //--将文件删除
    if(flag == -1){
    	perror("unlink error");
    	exit(1);
    }
    */

	pid = fork();//创建子进程

	if(pid == 0){
		*p = 20;  //写共享内存
		var = 1000;
		printf("child ,*p=%d,var=%d\n",*p,var);
	}
	else{
		sleep(1);
		printf("parent,*p=%d,var = %d\n",*p,var);
	}

    int ret = munmap(p,len);//解除映射

    if(ret == -1){
        sys_err("munmap error");
    }

    return 0;
}

执行结果:

haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ ./mmap_parent_son 
child ,*p=20,var=1000
parent,*p=20,var = 100
haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ 
    
注意:如果上面的p = (char*)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
改为:p = (char*)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);那么就无法进行父子进程之间的通信。

四、无血缘关系之间的通信

就上面的程序进行更改

4.1 写进程 mmap_w.c程序
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

void sys_err(const char* str){
    perror(str);
    exit(1);
}

int var = 100;
/*
mmap:memorty_map 内存映射试验
*/
int main(int argc,char* argv[]){

    char *p = NULL; //用于接收mmap的返回地址
    int fd; //用于文件的接收

    fd = open("testmmap", O_RDWR | O_CREAT | O_TRUNC, 0664);  //打开文件testmmap用作映射

    if(fd == -1){//打开失败
        sys_err("open error");
    }
    /*
    lseek(fd, 9, SEEK_END);  //将文件给扩展长度到10
    write(fd, "\0", 1);
    */
    ftruncate(fd,10); //将文件给扩展长度到10

    int len = lseek(fd, 0, SEEK_END);//获取文件的长度
    //将文件全部进行内存映射
    p = (char*)mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if(p == MAP_FAILED){
        sys_err("mmap error");
    }
	close(fd);
	
	while(1){
		memcpy(p,&var,sizeof(var));  //往里面写var值
		var++;  //var值时刻变化
		sleep(1);
	}

    int ret = munmap(p,len);//解除映射

    if(ret == -1){
        sys_err("munmap error");
    }

    return 0;
}
4.2 读进程 mmap_r.c
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

void sys_err(const char* str){
    perror(str);
    exit(1);
}

int var = 100;

/*
mmap:memorty_map 内存映射试验
*/
int main(int argc,char* argv[]){

    char *p = NULL; //用于接收mmap的返回地址
    int fd; //用于文件的接收

    fd = open("testmmap", O_RDONLY);

    if(fd == -1){//打开失败
        sys_err("open error");
    }
    /*
    lseek(fd, 9, SEEK_END);  //将文件给扩展长度到10
    write(fd, "\0", 1);
    */
    //ftruncate(fd,10); //将文件给扩展长度到10

    int len = lseek(fd, 0, SEEK_END);//获取文件的长度
    //将文件全部进行内存映射
    p = (char*)mmap(NULL, len, PROT_READ , MAP_SHARED, fd, 0);

    if(p == MAP_FAILED){
        sys_err("mmap error");
    }
	close(fd);

	while(1){
		printf("%d\n",*p);
		sleep(2);
	}

    int ret = munmap(p,len);//解除映射

    if(ret == -1){
        sys_err("munmap error");
    }

    return 0;
}
4.3 结果
写进程打开
haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ ./mmap_w
读进程显示
haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ ./mmap_r
0
104
106
108
110
112
114
116
118
120
122
124
126
^C
haitu@ubuntu:/opt/modbus_test/linux_test/mmap$ 

注意:多个写端一个读端也没问题,打开多个写进程即可,读进程会读到所有写进程写入的内容。这里要特别注意,内容被读走之后不会消失,所以如果读进程的读取时间间隔短,它会读到很多重复内容,就是因为写进程没来得及写入新内容,内容没有被覆盖掉。

五、匿名映射

实质上mmap是内核借助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核空间多进程共享,因此无血缘关系的进程间也可以使用mmap来完成通信。只要设置相应的标志位参数flags即可。若想实现共享,当然应该使用MAP_SHARED了

int *p = NULL;
p = (int *)mmap(NULL, 40, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);//创建映射
//----- 内存映射内容
munmap(p,40) //清除映射
注意:匿名映射:只能用于 血缘关系进程间通信。

例子程序:

#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

void sys_err(const char* str){
    perror(str);
    exit(1);
}

int var = 100;

/*
mmap:memorty_map 内存映射试验
*/
int main(int argc,char* argv[]){

    char *p = NULL; //用于接收mmap的返回地址
	pid_t pid;
    
    p = (char*)mmap(NULL, 40, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    if(p == MAP_FAILED){
        sys_err("mmap error");
    }

	pid = fork();//创建子进程

	if(pid == 0){
		*p = 20;  //写共享内存
		var = 1000;
		printf("child ,*p=%d,var=%d\n",*p,var);
	}
	else{
		sleep(1);
		printf("parent,*p=%d,var = %d\n",*p,var);
	}

    int ret = munmap(p,40);//解除映射

    if(ret == -1){
        sys_err("munmap error");
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值