目录
1、mmap函数主要用途有三个(应用和内核/驱动交互,进程间交互,大规模数据传输/大文件读写) 2
2、使用步骤:所有对mmap返回地址空间的操作只是在内存中才有意义,只有在调用了munmap或者msync时,才把内存中的相应内容写回磁盘文件. 3
3、mmap使用细节(理清 文件(被映射对象)大小、文件物理页大小、mmap映射区的长度length,映射区的可操作范围之间的关系和注意)... 3
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); 5
int munmap( void * addr, size_t len ). 7
int msync( void *addr, size_t len, int flags ). 7
三、mmap内存映射原理 (使用mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。)... 9
两个子程序,这两个子程序编译为mmap_read和mmap_write.两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信... 15
linux 同步IO: sync msync、fsync、fdatasync与 fflush内存和磁盘同步的操作和策略 21
一、Mmap用途、步骤实例、细节、及相关函数
mmap操作提供了一种机制,让用户程序直接访问设备内存。
常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同,数据不通的繁琐过程。因此mmap效率更高。对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。
进程间通信(IPC)之内存映射mmap和共享内存shm
1 共享内存shm是在内存中创建空间,然后每个进程映射到此处;内存映射mmap是创建一个文件,然后每个进程映射到此处;
2 当机器重启时,mmap把文件保存在磁盘上,所以不会丢失,而共享内存shm存储在内存上就会丢失;
1、mmap函数主要用途有三个(应用和内核/驱动交互,进程间交互,大规模数据传输/大文件读写)
1)实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。
2)提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件(将一个普通文件映射)或匿名映射(将特殊文件进行匿名内存映射)到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。
(1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。
同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。
(2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。
3)可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。凡是需要用磁盘空间扩展内存的时候,mmap都可以发挥其功效。
提高大文件的读写效率,使用了内存映射的方法,将磁盘上的文件与进程中的进程虚拟空间进行了映射,减少一次内核空间到用户空间的一次复制。文件内存映射mmap解决大文件快速读写。
2、使用步骤:所有对mmap返回地址空间的操作只是在内存中才有意义,只有在调用了munmap或者msync时,才把内存中的相应内容写回磁盘文件.
* 用open系统调用打开文件, 并返回描述符fd.
* 用mmap建立内存映射, 并返回映射首地址指针start.
* 对映射(文件)进行各种操作, 显示(printf), 修改(sprintf).
*可以通过调用msync实现磁盘上文件内容与共享内存区的内容一致。
* 用munmap(void *start, size_t lenght)关闭内存映射.
* 用close系统调用关闭文件fd.
// 打开目标文件
FILE * tfp = fopen(tname, "w"); // 不存在则创建
// 计算源文件的大小(字节数)
unsigned long byte_num = 1000;
//--------建立 mmap 映射区 --------------
// 获取被复制文件的文件描述符
int tfd = open(tname, O_RDWR|O_CREAT, 0644);
ftruncate(tfd, byte_num); // 将tfd指向的文件的大小改变为byte_num
char *tmem = (char*)mmap(NULL, byte_num, PROT_WRITE, MAP_SHARED,tfd, 0);
if (tmem == MAP_FAILED)
perror("mmap err");
close(tfd); // 内存映射区建立之后,就可以关闭文件描述符
// memcpy
将数据拷贝到
tmem
msync( tmem, byte_num, MS_SYNC );
munmap(tmem, byte_num);
3、mmap使用细节(理清 文件(被映射对象)大小、文件物理页大小、mmap映射区的长度length,映射区的可操作范围之间的关系和注意)
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
start:映射区的开始地址 指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
length:映射区的长度。 映射区的长度 是映射到调用进程地址空间(虚拟内存)的字节数length。(超过文件的物理页大小部分进行操作会有问题,问题如果没有超过length,进程不能对其进行读写,会报SIGBUS错误。如果超过length,进程不能对其读写,会引发SIGSEGV错误。)
prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体
fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。
由于文件大小(合法的物理页对应),文件的物理页大小是物理页大小(page_size)的整倍数,如果文件大小不是物理页大小(page_size)的整倍数,要以物理页大小(page_size)的整倍数为准,只是多出部分用零填充(填充部分:进程可以进行读写过程,不会报错。但是内容在写入前均为0,另外,写入后不会反映在文件中)。
offset:被映射对象内容的起点。文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。要映射的用户空间的内存区域在内核空间中已经分配好的的内存区域中的偏移。大小为PAGE_SIZE的整数倍
1、使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。
2、内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。具体情形参见“情形三”。
3、映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。
在上面的知识前提下,我们下面看看如果大小不是页的整倍数的具体情况:
情形一:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射5000字节到虚拟内存中。
分析:因为单位物理页面的大小是4096字节,虽然被映射的文件只有5000字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此