UNIX 环境编程 之 内存映射(mmap)

本文详细介绍了UNIX环境下内存映射的概念、mmap函数的使用、内存映射原理,以及mmap与shm共享内存映射的差异。mmap通过减少数据拷贝提高效率,适用于大规模数据传输,但映射大小受限于页大小。同时,文章讨论了mmap在文件映射和共享内存映射中的不同策略,强调了mmap在物理内存管理和数据持久化方面的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.内存映射概念

liunx 中 mmap内存映射主要是指硬盘上文件的位置与进程逻辑地址空间中一块大小相同的区域之间的一一对应。

2.mmap 函数使用

1.函数 mmap()

void * mmap(void * addr ,size_t len, int prot ,int flags,int fd,off_t offset);

其中addr可以指定描述符fd应被映射到进程内空间的首地址,它通常被指定一个空指针,让系统内核自己选择起始地址。而无论什么情况,最终返回的都是描述符fd所映射的内存首地址。
len 是映射到调用进程地址空间的字节数 ,它从被映射文件开始起第offset个字节开始算。通常offset = 0,下图是对应的映射关系
内存映射文件的例子
prot用来指定对映射区域的保护要求,但是它的保护范围不能超过文件open时指定的打开权限比如以只读(PROT_READ)方式打开一个文件,那么以读写(PROT_READ|PROT_WRITE)方式保护内存区域是不合法的
flags用来指定内存区域的多种属性,两个典型的取值是MAP_SHARED和MAP_PRIVATE。
MAP_SHARED 标志指定了进程对内存区域的修改会影响到映射文件。
MAP_PRIVATE时,进程会为该映射内存区域创建一个私有副本,对该内存区的所有操作都是在这个副本上进行的,此时对内存区域的修改并不会影响到映射文件。

2.函数munmap()
从某个进程的地址空间删除一个映射关系

int munmap(void * addr ,size_t len);

其中 addr 是mmap 返回的地址,len是映射区的长度

3.函数msync()
通常情况下,当mmap建立映射时,指定MAP_SHARED标志,如果修改了处于内存映射到某个文件的内存区的某个位置的内容 内核是有对应算法,在稍后的某个时刻,会将修改的内存,更新到对应的文件上。但是更新不是实时的。而msync可实现这中同步

int msync(void * addr ,size_t len,int flags);

其中 addr 和len 通常是指整个内存映射区,不过也可以该内存区的子集
而参数三 flags : 1.MS_ASYNC 执行异步写 指将更新操作提交给内核队列后,就返回。 2.MS_SYNC 则会等写操作完成后,才会返回 。3 MS_INVALIDATE 指内存中修改的还没有更新到文件的,全部失效

3.mmap 内存映射

mmap三个作用:

  • 使用普通文件以提供内存映射的I/O
  • 使用特殊文件以提供匿名的内存映射
  • 使用shm_open以提供无亲缘关系进程间的Posix共享内存区

1.普通文件的内存映射

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

typedef struct data {
	int  chl_id;
	char buf[128];
} data_mmap;

int main(void)
{
	int fd = open( "/root/mmap_test", O_RDWR );
	if(fd <0){
		printf("mmap_test fd error\n");
		return 1;
	}

	data_mmap data ;
	memset(&data,0,sizeof(data_mmap));
	data.chl_id =111;
	memcpy(data.buf,"mmap_test file is exist",strlen("mmap_test file is exist"));
	
	write(fd,(void*)&data,sizeof(data_mmap));
	data_mmap * addr = mmap( NULL, sizeof(data_mmap), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );
	printf("addr:%x\nid:%d\nbuf:%s\n",addr,addr->chl_id,addr->buf);


	addr->chl_id = 222;
	memcpy(addr->buf,"mmap modify the file",strlen("mmap modify the file"));
	msync(addr,sizeof(data_mmap),MS_ASYNC);
	munmap(addr,sizeof(data_mmap));
	return 1;
}

运行结果:
mmap_test 文件 mmap读取结果打印
mmap内存映射读取文件内容
mmap 修改内存映射区后,文件mmap_test内容
mmap内存映射
2.匿名内存映射
(1) 4.4BSD提供匿名映射
它彻底避免了文件的创建打开,其办法是mmap的flag 参数指定 MAP_SHARED|MAP_ANON,把fd 设置为-1,这样的内存初始化为0。

 mmap( NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0 );

(2) SVR4提供/dev/zero 设备文件,我们在open它之后可在mmap调用中使用文件描述符。从该设备读时返回的字节全为0,写往该设备的字节都会被丢弃

int fd =open("/dev/zer",O_RDWR);
void * ptr = mmap( NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0 );

4.mmap 原理

mmap 做内存映射的时候都会有个误区,直接的会认为mmap是把要映射的文件的内容,直接也复制到了映射的内存中了,其实是错误的。
mmap函数内做的工作,并没有把文件的内容载入到内存中去。只是在虚拟内存中分配了地址空间,初始化了相关数据结构。当进程在访问mmap映射的这段地址时,若虚拟内存对应的page没有在物理内存中缓存,则产生"缺页",由内核的缺页异常处理程序处理,将文件对应内容,以页为单位(4096)加载到物理内存,注意是只加载缺页。但也会受操作系统一些调度策略影响,加载的比所需的多。
在这里插入图片描述
在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上。
要是对虚拟内存和物理内存工作方式想深入理解下可查看 物理内存和虚拟内存这篇博客

通过原理可以看到内存映射的一个问题:文件映射的内存区域的大小必须以页大小为单位。比如系统页大小为4096字节,假定映射文件的大小为20字节,那么该页剩余的4076字节全部被填充为0。虽然通过映射地址可以访问并修改剩余字节,但是任何变动都不会在映射文件中反应出来,使用内存映射进行大数据量的拷贝比较有效。

5. mmap优缺点

优点总结:

  • 常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。(例如read(),先系统调用。将文件内容拷贝到内核缓存中,然后再将内核缓存数据拷贝到用户缓存中)而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程(真正的拷贝只在缺页中断的时候拷贝一次,mmap将文件直接映射到用户空间),比read/write效率高
  • 可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是大量的I/O操作就会多次调用数据,内核,用户之间的拷贝,影响效率。而mmap都可以发挥其功效。

缺点:

  • 就是之前提过的文件映射的内存区域的大小必须以页大小为单位。

6.额外补充mmap 文件映射 shm共享内存映射差异

mmap的文件映射:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新。

shm共享内存映射:对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(根据address_space以及偏移量的物理页),如果找到,则直接返回地址,找到的就是物理内存中的共享内存区;如果没有找到,则判断该页是否在交换区(swap area,虚拟内存中),如果在,则执行一个换入操作,将虚拟内存的页缓存载入到物理内存页缓存中;如果上述两种情况都不满足,处理程序将分配新的物理页面(指新的一块共享内存区),并把它插入到page cache中。进程最终将更新进程页表。
共享内存映射过程
从上图的共享内存,也可以发现所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。

因此在比较mmap 文件映射,和shm共享内存映射时可发现

  • mmap文件映射,只有在用到文件映射所需的内容时,才会去将文件的内容拷贝到物理内存。可以充分利用物理内存。而shm 其实共享内存的在使用的过程都是放入物理内存的,当然内核会有调度算法,当共享的内存没有进程用时,会被置换出去,或者交换算法,会将进程用的数据先置换到磁盘,或者swap分区虚拟内存中。应用程序调用到这块共享内存的时候,又会将磁盘或者swap分区的置换回物理内存上。因此当共享内存比较大时,对物理内存要求就比较高
  • 另外mmap文件映射有一个好处是当机器重启,因为mmap把文件保存在磁盘上,这个文件还保存了操作系统同步的映像,所以mmap不会丢失,但是shm就会丢失
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值