mmap

mmap用在linux中什么位置

什么是mmap?

  • 上图说了,mmap是操作这些设备的一种方法,所谓操作设备,比如IO端口(点亮一个LED)、LCD控制器、磁盘控制器,实际上就是往设备的物理地址读写数据。
  • 但是,由于应用程序不能直接操作设备硬件地址,所以操作系统提供了这样的一种机制——内存映射,把设备地址映射到进程虚拟地址,mmap就是实现内存映射的接口。
  • mmap的好处是,mmap把设备内存映射到虚拟内存,则用户操作虚拟内存相当于直接操作设备了,省去了用户空间到内核空间的复制过程,相对IO操作来说,增加了数据的吞吐量。

什么是内存映射

  • 既然mmap是内存映射的接口,那么内存映射是什么呢?
  • 每个进程都有独立的进程地址空间,通过页表和MMU,可将虚拟地址转换为物理地址,每个进程都有独立的页表数据,这可解释为什么两个不同进程相同的虚拟地址,却对应不同的物理地址。

什么是虚拟地址空间

  • 每个进程都有4G的虚拟地址空间,其中3G用户空间,1G内核空间(linux),每个进程共享内核空间,独立的用户空间,下图形象地表达了这点

  • 驱动程序运行在内核空间,所以驱动程序是面向所有进程的。
    用户空间切换到内核空间有两种方法:
    (1)系统调用,即软中断
    (2)硬件中断

虚拟地址空间里面是什么?

  • 了解了什么是虚拟地址空间,那么虚拟地址空间里面装的是什么?看下图
  • 虚拟空间装的大概是上面那些数据了,内存映射大概就是把设备地址映射到上图的红色段了,暂且称其为“内存映射段”,至于映射到哪个地址,是由操作系统分配的,操作系统会把进程空间划分为三个部分:
    (1)未分配的,即进程还未使用的地址
    (2)缓存的,缓存在RAM中的页
    (3)未缓存的,没有缓存在RAM中
  • 操作系统会在未分配的地址空间分配一段虚拟地址,用来和设备地址建立映射,至于怎么建立映射,后面再揭晓。
  • 现在大概明白了“内存映射”是什么了,那么内核是怎么管理这些地址空间的呢?任何复杂的理论最终也是通过各种数据结构体现出来的,而这里这个数据结构就是进程描述符。从内核看,进程是分配系统资源(CPU、内存)的载体,为了管理进程,内核必须对每个进程所做的事情进行清楚的描述,这就是进程描述符,内核用task_struct结构体来表示进程,并且维护一个该结构体链表来管理所有进程。该结构体包含一些进程状态、调度信息等上千个成员,我们这里主要关注进程描述符里面的内存描述符(struct mm_struct mm)

内存描述符

  • 现在已经知道了内存映射是把设备地址映射到进程空间地址(注意:并不是所有内存映射都是映射到进程地址空间的,ioremap是映射到内核虚拟空间的,mmap是映射到进程虚拟地址的),实质上是分配了一个vm_area_struct结构体加入到进程的地址空间,也就是说,把设备地址映射到这个结构体,映射过程就是驱动程序要做的事了。

内存映射的实现

  • 以字符设备驱动为例,一般对字符设备的操作都如下框图

  • 而内存映射的主要任务就是实现内核空间中的mmap()函数。

现在就正式的介绍一下mmap函数

  • mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。

  • 当使用mmap映射文件到进程后,就可以直接操作这段虚拟地址进行文件的读写等操作,不必再调用read,write等系统调用.但需注意,直接对该段内存写时不会写入超过当前文件大小的内容.

  • 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

  • 基于文件的映射,在mmap和munmap执行过程的任何时刻,被映射文件的st_atime可能被更新。如果st_atime字段在前述的情况下没有得到更新,首次对映射区的第一个页索引时会更新该字段的值。用PROT_WRITE 和 MAP_SHARED标志建立起来的文件映射,其st_ctime 和 st_mtime在对映射区写入之后,但在msync()通过MS_SYNC 和 MS_ASYNC两个标志调用之前会被更新。

函数mmap的说明

头文件  <sys/mman.h>
函数原型:
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
  • 条件

    • mmap() 必须以PAGE_SIZE为单位进行映射,而内存也只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行内存对齐,强行以PAGE_SIZE的倍数大小进行映射。
  • 参数说明

start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址。length:映射区的长度。//长度单位是 以字节为单位,不足一内存页按一内存页处理prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起PROT_EXEC //页内容可以被执行PROT_READ //页内容可以被读取PROT_WRITE //页可以被写入PROT_NONE //页不可访问flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下位的组合体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:被映射对象内容的起点。
  • 函数返回值
成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值EACCES:访问出错EAGAIN:文件已被锁定,或者太多的内存已被锁定EBADF:fd不是有效的文件描述词EINVAL:一个或者多个参数无效ENFILE:已达到系统对打开文件的限制ENODEV:指定文件所在的文件系统不支持内存映射ENOMEM:内存不足,或者进程已超出最大内存映射数量EPERM:权能不足,操作不允许ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志SIGSEGV:试着向只读区写入SIGBUS:试着访问不属于进程的内存区

那我们现在来看一下共享内存的代码mmap

  • 范例1给出两个进程通过映射普通文件实现共享内存通信
                 /*-------------map_normalfile1.c-----------*/
#include<sys/mman.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h>
#include<unistd.h>
#include <errno.h>typedef struct{char name[4];int   age;
}people;int main(int argc, char** argv) // map a normal file as shared mem:
{int fd,i;people *p_map;char temp;fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);lseek(fd,sizeof(people)*5-1,SEEK_SET);write(fd,"",1);p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );close( fd );temp = 'a';for(i=0; i<10; i++){temp += 1;memcpy( ( *(p_map+i) ).name, &temp,2 );( *(p_map+i) ).age = 20+i;}printf(" initialize over \n ");sleep(10);munmap( p_map, sizeof(people)*10 );printf( "umap ok \n" );return 0;
}/*-------------map_normalfile2.c-----------*/
#include<sys/mman.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h>
#include<unistd.h>
#include <errno.h>typedef struct{char name[4];int   age;
}people;int main(int argc, char** argv) // map a normal file as shared mem:
{int fd,i;people *p_map;fd=open( argv[1],O_CREAT|O_RDWR,00777 );p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);for(i = 0;i<10;i++){printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );}munmap( p_map,sizeof(people)*10 );return 0;
}
  • 从程序的运行结果中可以得出的结论

    1. 最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小;
    2. 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小,但不完全受限于文件大小。打开文件被截短为5个people结构大小,而在 map_normalfile1中初始化了10个people数据结构,在恰当时候(map_normalfile1输出initialize over 之后,输出umap ok之前)调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值,后面将给出详细讨论。
      注:在linux中,内存的保护是以页为基本单位的,即使被映射文件只有一个字节大小,内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时,进程可以对从mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面以外的地址空间进行访问,则导致错误发生,后面将进一步描述。因此,可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。
    3. 文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义,只有在调用了munmap()后或者msync()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。
  • 范例2:父子进程通过匿名映射实现共享内存

#include<sys/mman.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h>
#include<unistd.h>
#include <errno.h>typedef struct{char name[4];int   age;
}people;int main(int argc, char** argv)
{int i;people *p_map;char temp;p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);if(fork() == 0){sleep(2);for(i = 0;i<5;i++)printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age);(*p_map).age = 100;munmap(p_map,sizeof(people)*10); //实际上,进程终止时,会自动解除映射。exit();}temp = 'a';for(i = 0;i<5;i++){temp += 1;memcpy((*(p_map+i)).name, &temp,2);(*(p_map+i)).age=20+i;}sleep(5);printf( "parent read: the first people,s age is %d\n",(*p_map).age );printf("umap\n");munmap( p_map,sizeof(people)*10 );printf( "umap ok\n" );return 0;
}

mmap优点总结

  • 由上文讨论可知,mmap优点共有一下几点:

    1. 对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高了文件读取效率。
    2. 实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉。
    3. 提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的。
      同时,如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据。
    4. 可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。

深入解剖 linux内存管理之mmap相关推荐

  1. Linux内存管理 brk(),mmap()系统调用源码分析2:brk()的内存释放流程

    Linux brk(),mmap()系统调用源码分析 brk()的内存释放流程 荣涛 2021年4月30日 内核版本:linux-5.10.13 注释版代码:https://github.com/Rt ...

  2. Linux内存管理 brk(),mmap()系统调用源码分析1:基础部分

    Linux内存管理 brk(),mmap(),munmap()系统调用源码分析 基础部分 荣涛 2021年4月30日 内核版本:linux-5.10.13 注释版代码:https://github.c ...

  3. Linux内存管理子系统——mmap内存映射原理分析(dax文件系统的mmap)

    Linux mmap分析 内核版本:linux-5.16 1. 虚拟内存概要及相关内容简介 内存映射是学习过操作系统的大家都耳熟能详的词,理解起来也很简单.所谓"映射"就是为一种事 ...

  4. linux 内存管理(15) - mmap

    了解mmap机制.参考此处 1.概述   mmap 即地址的映射, 是一种内存映射文件的方法,将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系 ...

  5. Linux内存管理之mmap详解

    一. mmap系统调用 1. mmap系统调用 mmap将一个文件或者其它对象映射进内存.文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零.munmap执行相 ...

  6. linux内存管理函数mmap和brk,brk() 和 mmap() 内存映射

    参考博文:http://www.cnblogs.com/huxiao-tee/p/4660352.html x86平台下linux进程虚拟地址空间分布(2.6.7以前版本) mmap区域与栈区域相对增 ...

  7. Linux内存管理基础

    系统启动之Linux内存管理基础 Keywords 非一致内存访问(NUMA)模型.节点(node).内存管理区(Zone).一致内存访问(UMA)模型.内核页表.内存管理区分配器(伙伴系统Buddy ...

  8. Windows内存管理和linux内存管理

    windows内存管理 windows 内存管理方式主要分为:页式管理,段式管理,段页式管理. 页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页:页式管理把内存空间按照页的大小划分成片或 ...

  9. 万字长文,别再说你不懂Linux内存管理了(合辑),30 张图给你安排的明明白白...

    之前写了两篇详细分析 Linux 内存管理的文章,读者好评如潮.但由于是分开两篇来写,而这两篇内容其实是有很强关联的,有读者反馈没有看到另一篇读起来不够不连贯,为方便阅读这次特意把两篇整合在一起,看这 ...

  10. Linux内存管理原理【转】

    转自:http://www.cnblogs.com/zhaoyl/p/3695517.html 本文以32位机器为准,串讲一些内存管理的知识点. 1. 虚拟地址.物理地址.逻辑地址.线性地址 虚拟地址 ...

最新文章

  1. 百面机器学习之模型评估
  2. iOS 提示更新 业务逻辑
  3. xlst 解析 html c,怎樣實現利用xslt把xml文件內容顯示到html文件中?急!
  4. Visual C++——LoadBitmap加载位图的操作过程
  5. getpass函数简单使用
  6. activiti 设置可选处理人_新品速递|高端系列!慧明DF系列线性相位处理专业音箱处理器...
  7. Linux常用命令 -- screen
  8. 关于JS中for循环时,作用域问题和this指针指向的总结
  9. 两台电脑之间如何快速传输几百G的文件?
  10. AI科学计算领域的再突破,昇思MindSpore做“基石”的决心有多强?
  11. datagridview滚动条自动滚动_一个自适应滚动条的实现
  12. 一个用户下表、批量授予权限给另一个用户
  13. numpy中相关系数
  14. MySQL 插入时,出现‘“Incorrect string value: ‘\\xF0\\x9F\\x98\\x85...‘ for column ‘commens‘ at row 3‘
  15. macmini作为远程服务器,我在用我的 Mac mini 做什么
  16. CTF-RSA分解模数N
  17. 广州电信高级前端开发工程师笔试题及答案(国企面试题大全)
  18. 动态创建数组了解各种取值和取地址的问题以及感受内存地址
  19. [java实现]辗转相除法
  20. 线性代数学习笔记——第二十三讲——空间直角坐标系

热门文章

  1. 鼠标在滑块上滚轮控制_直线导轨(滚轮导轨)与线轨(滚珠导轨)的优劣势对比...
  2. mysql sqlserver 拷贝_SQLyog工具进行SQLSERVER表结构或数据拷贝到MySQL数据库
  3. Java编程:排序算法——插入排序
  4. SQL:pgsql新建数据表
  5. cesium:获取点击实体点的坐标位置
  6. JavaScript:网络请求工具库AjaxTool.js
  7. Java集合框架源码解读(3)—LinkedHashMap的实现原理及用法
  8. 论文笔记_S2D.14-2014-NIPS_利用多尺度深度网络从单张图像预测深度图
  9. 一文详解三维重建中的立体匹配
  10. Elasticsearch Index Template(索引模板)