文件映射IO(mmap-mprotect-msync-munmap)函数

本文介绍在POSIX环境使用文件映射IO操作的方法,文件映射IO又被称为存储映射IO,对于普通文件而言,很多时候它是高效的,它实际减少了数据的复制;同时它也可以用于特殊的地方,用于进程之间的通信,共享内存的一种方式。

我们能够把一个文件想象成一块连续的数据,从纯粹的数据角度来看,任何普通文件都可以这么理解。文件映射实际上是把文件的这块数据与我们程序里的一块内存对应上了,使用我们操作这块内存的时候,看上去实际在操作这个文件。这就是文件映射的概念。这个概念很伟大,它直接避免了内核与用户之间的一层数据复制,所以很多时候,它会比其它方式的文件操作更快一些,尤其对于普通的磁盘文件而言更是如此。

创建映射区:mmap

我们通过函数mmap来告诉操作系统把哪个文件映射哪块内存去,并且设置我们可能对这块内存的不能操作,就是对文件一样。

#include<sys/mman.h>

void* mmap(void* addr, size_t len, int port, int flag, int filedes,
off_t off)

返回值:成功返回被映射的内存地址,失败返回MAP_FIALED

参数 addr

这个只有在极少数情况下才不为0,这个参数告诉内核使用addr指定的值来映射指定文件。当指定为0的时候,告诉内核返回什么地址内其自身决定。除非非常了解系统进程模式,或者对当前环境非常了解,否则的话手工指定这个值总是不可取。

参数 len

指定被映射的内存区域的长度。

参数 port

这个参数对应open函数的权限位,我们可以指定为:PROT_READ,映射区可读;PROT_WRITE,映射区可写;PROT_EXEC,映射区可执行;PROT_NONE,映射区不可访问。由于只能映射已经打开的文件,所以这个权限位不能超出open函数指定的权限,比如说在open的时候指定为只读,那就不能在此时指定PORT_WRITE。

参数 flag

这个参数指定了映射区的其它一些属性,权限的属性已经在port中指定。这里可能存在的典型值有:MAP_FIXED,针对addr属性,如果指定这个位,那么要求系统必需在指定的地址映射,这往往是不可取的;MAP_SHARED,此标志说明指定映射区是共享的,意思就是说对内存的操作与对文件的操作是相对应的,它不能与MAP_PRIVATE标志一直使用,因为它们表达的意图是相反的;MAP_PRIVATE,该标志说明映射区是私用的,此时被映射的内存只能被当前里程使用,当进程操作的内存将会产生原文件的一个副本。

MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。

MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。

MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。

MAP_DENYWRITE //这个标志被忽略。

MAP_EXECUTABLE //同上

MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。

MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。

MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。

MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。

MAP_ANON //MAP_ANONYMOUS的别称,不再被使用。

MAP_FILE //兼容标志,被忽略。

MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。

MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。

MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。

off_toffset:被映射对象内容的起点。

设置与同步映射区:mprotect、msync

在mmap中我们有很多选项来控制最后得到的映射区的一些属性,在调用mmap函数之后,我仍然可以对其中的一些属性进行调整,这通过mprotect函数完成。此外在我们更新了内存的内容之后,这时可能想把这些内容同步到磁盘中的文件,这通过msync函数来完成。

mprotect 函数可以更改一个已经存在的映射区的访问权限。

#include<sys/mman.h>

int mprotect(void* addr, size_t len, int port)

返回值:成功返回0,失败返回-1

参数 addr

这个参数是mmap返回的数值,此时它就是mprotect作用的范围。

参数 len

指定映射区的长度,它需要与mmap中指定相同。

参数 port

在上面我们已经介绍了port的可能取值,mprotect功能就是把这个port指定的属性施加于相应的映射区上。

在映射区的内容更新了,内核并不是实时同步映射区与文件的,相反内核很少主动去同步,除非我们调用了函数msync或者关闭映射区(关闭映射区的时候,也不是立即同步的)。

#include<sys/mman.h>

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

返回:成功为0,失败为-1

参数 addr与len

这两个参数完全等同于mprotect中的相应的参数。

参数 flags

我们可以通过指定flags为不同的值来要求内核进行的相应的同步操作:MS_ASYNC,这实际上不要求内核做什么,让内核自主去执行同步;MS_SYNC,要求内核在返回之前把写操作完成;MS_INVALIDATE,是一个可选的标志,它告诉内核丢弃没有同步的部分。

解除映射区:munmap

在进程寻出或者我们调用munmap的时候,可以解除一个已经存在的映射区。而关闭映射区对应的文件是不会引起映射区的解除的。

#include<sys/mman.h>

int munmap(void* addr, size_t len)

返回:成功返回0,失败返回-1

munmap的参数含义是明显而平凡的,所以在这里不作描述,它们与之前的几个含义完全相同。

注意:信号、进程、页、文件

对映射区的操作可能引起两个信号:SIGSEGV 与 SIGBUS。内核会在进程访问了不可用的内存时发送SIGSEGV信号给进程,指示进程这一异常行为。比如对一个只读的映射区执行写操作将收到这一信号。在映射的时候,如果文件本身的大小没有映射区的长度大,那么在进程第一次访问超过文件大小的内存区域的时候,内核会发送信号SIGBUS信号,之后再次访问此区域之前的地方都可以正常使用,但一旦再次超过,两样也会收到信号SIGBUS。所以一般情况都在映射之前调用诸如lseek+write这样的函数来改变文件现有大小以适合映射区需要的长度。

进程调用fork一类函数的时候子进程会复制父进程的地址空间,所以被映射的区域也会被映射,如此映射区是父子进程共享的。可以通过这种方式实现父子进程的内存共享与通信。

从内核的角度,内存是安页来管理的,所以在映射的时候内核在指定大小的基础上按页向上取整。页的大小是系统相关的,在POSIX环境下可以通过调用sysconf函数来确定。

只有在调用mmap时指定MAP_SHARED的情况文件内容才会真正改变,否则文件内容不会被同步,即使我们调用了msync或解除映射或进程终止。

时间: 2024-10-09 07:00:34

文件映射IO(mmap-mprotect-msync-munmap)函数的相关文章

mmap存储映射IO

mmap,munmap-- #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length); int mprotect(void *addr, size_t len, int prot); int msync(void *addr, size_t length, int fla

C/C++ 使用mmap/munmap函数分配内存

在C/C++ 中常用的内存分配和管理的方式有很多,如智能指针, STL容器, new/delete, malloc/free, brk, sbrk等等,最近研究了一下Unix比较底层的一种内存管理方式mmap/munmap,需要完全自己来维护分配的虚拟内存,没有任何其他辅助的数据结构来帮助维护内存空间. 一.在终端里输入 man mmap 可以查看此函数的API文档,此函数的具体描述如下: void *mmap(void *start,size_t length,int prot,int fla

文件映射mmap

磁盘与内存的映射就是文件映射,说这个问题之前我们先说下swap,因为   这个问题让我很容易想起swap,linux swap 是交换分区的意思,在内存不   够的情况下,操作系统先把内存与磁盘的swap区进行一个"映射",然后把   这些内存解放出来放入内存中,为之后的进程的腾出一块内存空间,等到自   己的进程再次被唤醒时候,再把磁盘里面的内存换进来.这里有文件和内存之间   的映射奥,可是mmap与swap设计思想上是完全不同的,一个针对的物理内存   一个针对的是虚拟内存.  

C语言文件IO操作的一些其它函数

stat 函数 1 1 #include <sys/stat.h> 2 2 #include <unistd.h> 3 3 #include <stdio.h> 4 4 int main() 5 5 { 6 6 struct stat buf; 7 7 stat("1.c", &buf); 8 8 printf("1.c file size = %d\n",(int)buf.st_size); 9 9 return 0;

文件IO详解(十三)---pread函数和pwrite函数详解

pread和pwrite函数是Linux提供的另外一种读取和写入文件的操作.pread函数的操作可以看作是顺序调用了lseek函数和read函数,同样pwrite函数也类似. ====================================================== 函数原型: 函数参数: fd:要操作的文件描述符 buf:在pread函数中表示存储读出数据的内存首地址,在pwrite函数中表示写入数据的内存首地址 count:在pread函数中表示希望读出的字节数,在pwri

mmap、msync

普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作. 刷新变化函数msync() 进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作. 可以通过调用msync()函数来实现磁盘文件内容与共享内存区中的内容一致,即同步操作. addr:文件映射到进程空间的地址: len:映射空间的大小: flags:刷新的参数设置,可以取值MS_ASYNC/ MS_SYNC/ MS_INVALIDA

共享内存的原理:文件映射

mmap()是将指定的文件利用虚拟内存技术映射到内存中去,在内存中建立页表项,但mmap()调用建立的页表项后面地址为空,只有使用到某个地址空间时才给此页面分配空间,要涉及到页面置换,因而需要有一定的物理内存做支撑,内存太小的话刚置入内存中的页面又要被交换到磁盘上,mmap的性能将大打折扣. mmap的优点主要在为用户程序随机的访问,操作,文件提供了一个方便的操作方法:其次就是为不同进程共享大批量数据提供高效的手段:另外就是对特大文件(无法一次性读入内存)的处理提供了一种有效的方法. 文件映射的

存储映射IO

3.6.7.1.mmap函数3.6.7.2.LCD显示和IPC之共享内存3.6.7.3.存储映射IO的特点(1)共享而不是复制,减少内存操作(2)处理大文件时效率高,小文件不划算 总结:存储映射IO其实本质上就是共享内存,和进程间通信的IPC共享内存是一样的,区 别在于: 存储映射IO:是访问设备文件的说法,例如访问LCD设备,我们的进程是一个视频播放器 ,那么他需要将硬盘中的视频数据读取到内存中,然后再把内存中的数据拷贝到LCD驱动 维护的显存中去,之后硬件会自动实现视频的显示:这个是一般的流

Java数据文件映射工具

一 思路 1. 最近公司需要开发许多文件数据同步的程序, 将数据写入文件, 通过SFTP协议同步给其他平台, 本人太懒, 就想弄个一劳永逸的工具. 2. 系统启动时, 创建一个Map结构的容器, 容器中存储文件生成规则与数据Entity的映射属性配置. 3. 文件生成时, 根据根据配置Key查询配置的值(配置值包括文件编码, 文件是否存在文件头, 文件每一列的分隔符), 使用反射机制反射Entity拿到值, 根据配置的规则写入文件. 4. 读取文件时, 根据根据配置Key查询配置的值, 创建Li