一 mmap系统调用

1.内存映射

所谓的内存映射就是把物理内存映射到进程的地址空间之内,这些应用程序就可以直接使用输入输出的地址空间,从而提高读写的效率。Linux提供了mmap()函数,用来映射物理内存。在驱动程序中,应用程序以设备文件为对象,调用mmap()函数,内核进行内存映射的准备工作,生成vm_area_struct结构体,然后调用设备驱动程序中定义的mmap函数。

2.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两个标志调用之前会被更新。

用法:

#include <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()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值

EACCES:访问出错

EAGAIN:文件已被锁定,或者太多的内存已被锁定

EBADF:fd不是有效的文件描述词

EINVAL:一个或者多个参数无效

ENFILE:已达到系统对打开文件的限制

ENODEV:指定文件所在的文件系统不支持内存映射

ENOMEM:内存不足,或者进程已超出最大内存映射数量

EPERM:权能不足,操作不允许

ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志

SIGSEGV:试着向只读区写入

SIGBUS:试着访问不属于进程的内存区

参数:

start:映射区的开始地址。

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:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1。

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

3.munmap系统调用

#include <sys/mman.h>

int munmap( void * addr, size_t len ) 
该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。

4.msync系统调用

#include <sys/mman.h>

int msync ( void * addr , size_t len, int flags) 
一般说来,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()实现磁盘上文件内容与共享内存区的内容一致。

二 系统调用mmap()用于共享内存的两种方式

(1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();典型调用代码如下:

[cpp] view plaincopy
  1. fd=open(name, flag, mode);
  2. if(fd<0)
  3. ...
  4. ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);

通过mmap()实现共享内存的通信方式有许多特点和要注意的地方

(2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 
对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可.

三 mmap进行内存映射的原理

mmap系统调用的最终目的是将,设备或文件映射到用户进程的虚拟地址空间,实现用户进程对文件的直接读写,这个任务可以分为以下三步:

1.在用户虚拟地址空间中寻找空闲的满足要求的一段连续的虚拟地址空间,为映射做准备(由内核mmap系统调用完成)

每个进程拥有3G字节的用户虚存空间。但是,这并不意味着用户进程在这3G的范围内可以任意使用,因为虚存空间最终得映射到某个物理存储空间(内存或磁盘空间),才真正可以使用。

那么,内核怎样管理每个进程3G的虚存空间呢?概括地说,用户进程经过编译、链接后形成的映象文件有一个代码段和数据段(包括data段和bss段),其中代码段在下,数据段在上。数据段中包括了所有静态分配的数据空间,即全局变量和所有申明为static的局部变量,这些空间是进程所必需的基本要求,这些空间是在建立一个进程的运行映像时就分配好的。除此之外,堆栈使用的空间也属于基本要求,所以也是在建立进程时就分配好的,如图3.1所示:

图3.1  进程虚拟空间的划分

在内核中,这样每个区域用一个结构struct vm_area_struct 来表示.它描述的是一段连续的、具有相同访问属性的虚存空间,该虚存空间的大小为物理内存页面的整数倍。可以使用 cat /proc/<pid>/maps来查看一个进程的内存使用情况,pid是进程号.其中显示的每一行对应进程的一个vm_area_struct结构.

下面是struct vm_area_struct结构体的定义:

[cpp] view plaincopy
  1. struct vm_area_struct {
  2. struct mm_struct * vm_mm;   /* The address space we belong to. */
  3. unsigned long vm_start;     /* Our start address within vm_mm. */
  4. unsigned long vm_end;       /* The first byte after our end address
  5. within vm_mm. */
  6. /* linked list of VM areas per task, sorted by address */
  7. struct vm_area_struct *vm_next, *vm_prev;
  8. pgprot_t vm_page_prot;      /* Access permissions of this VMA. */
  9. unsigned long vm_flags;     /* Flags, see mm.h. */
  10. struct rb_node vm_rb;
  11. /*
  12. * For areas with an address space and backing store,
  13. * linkage into the address_space->i_mmap prio tree, or
  14. * linkage to the list of like vmas hanging off its node, or
  15. * linkage of vma in the address_space->i_mmap_nonlinear list.
  16. */
  17. union {
  18. struct {
  19. struct list_head list;
  20. void *parent;   /* aligns with prio_tree_node parent */
  21. struct vm_area_struct *head;
  22. } vm_set;
  23. struct raw_prio_tree_node prio_tree_node;
  24. } shared;
  25. /*
  26. * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
  27. * list, after a COW of one of the file pages.  A MAP_SHARED vma
  28. * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
  29. * or brk vma (with NULL file) can only be in an anon_vma list.
  30. */
  31. struct list_head anon_vma_chain; /* Serialized by mmap_sem &
  32. * page_table_lock */
  33. struct anon_vma *anon_vma;  /* Serialized by page_table_lock */
  34. /* Function pointers to deal with this struct. */
  35. const struct vm_operations_struct *vm_ops;
  36. /* Information about our backing store: */
  37. unsigned long vm_pgoff;     /* Offset (within vm_file) in PAGE_SIZE
  38. units, *not* PAGE_CACHE_SIZE */
  39. struct file * vm_file;      /* File we map to (can be NULL). */
  40. void * vm_private_data;     /* was vm_pte (shared mem) */
  41. unsigned long vm_truncate_count;/* truncate_count or restart_addr */
  42. #ifndef CONFIG_MMU
  43. struct vm_region *vm_region;    /* NOMMU mapping region */
  44. #endif
  45. #ifdef CONFIG_NUMA
  46. struct mempolicy *vm_policy;    /* NUMA policy for the VMA */
  47. #endif
  48. };

通常,进程所使用到的虚存空间不连续,且各部分虚存空间的访问属性也可能不同。所以一个进程的虚存空间需要多个vm_area_struct结构来描述。在vm_area_struct结构的数目较少的时候,各个vm_area_struct按照升序排序,以单链表的形式组织数据(通过vm_next指针指向下一个vm_area_struct结构)。但是当vm_area_struct结构的数据较多的时候,仍然采用链表组织的化,势必会影响到它的搜索速度。针对这个问题,vm_area_struct还添加了vm_avl_hight(树高)、vm_avl_left(左子节点)、vm_avl_right(右子节点)三个成员来实现AVL树,以提高vm_area_struct的搜索速度。

  假如该vm_area_struct描述的是一个文件映射的虚存空间,成员vm_file便指向被映射的文件的file结构,vm_pgoff是该虚存空间起始地址在vm_file文件里面的文件偏移,单位为物理页面。

图3.2  进程虚拟地址示意图

因此,mmap系统调用所完成的工作就是准备这样一段虚存空间,并建立vm_area_struct结构体,将其传给具体的设备驱动程序

2 建立虚拟地址空间和文件或设备的物理地址之间的映射(设备驱动完成)

建立文件映射的第二步就是建立虚拟地址和具体的物理地址之间的映射,这是通过修改进程页表来实现的.mmap方法是file_opeartions结构的成员:

int (*mmap)(struct file *,struct vm_area_struct *);

linux有2个方法建立页表:

(1) 使用remap_pfn_range一次建立所有页表.

int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);

返回值:

成功返回 0, 失败返回一个负的错误值
参数说明:

vma 用户进程创建一个vma区域

virt_addr 重新映射应当开始的用户虚拟地址. 这个函数建立页表为这个虚拟地址范围从 virt_addr 到 virt_addr_size.

pfn 页帧号, 对应虚拟地址应当被映射的物理地址. 这个页帧号简单地是物理地址右移 PAGE_SHIFT 位. 对大部分使用, VMA 结构的 vm_paoff 成员正好包含你需要的值. 这个函数影响物理地址从 (pfn<<PAGE_SHIFT) 到 (pfn<<PAGE_SHIFT)+size.

size 正在被重新映射的区的大小, 以字节.

prot 给新 VMA 要求的"protection". 驱动可(并且应当)使用在vma->vm_page_prot 中找到的值.

(2) 使用nopage VMA方法每次建立一个页表项.

struct page *(*nopage)(struct vm_area_struct *vma, unsigned long address, int *type);

返回值:

成功则返回一个有效映射页,失败返回NULL.

参数说明:

address 代表从用户空间传过来的用户空间虚拟地址.

返回一个有效映射页.

(3) 使用方面的限制:

remap_pfn_range不能映射常规内存,只存取保留页和在物理内存顶之上的物理地址。因为保留页和在物理内存顶之上的物理地址内存管理系统的各个子模块管理不到。640 KB 和 1MB 是保留页可能映射,设备I/O内存也可以映射。如果想把kmalloc()申请的内存映射到用户空间,则可以通过mem_map_reserve()把相应的内存设置为保留后就可以。

(4) remap_pfn_range与nopage的区别

  • remap_pfn_range一次性建立页表,而nopage通过缺页中断找到内核虚拟地址,然后通过内核虚拟地址找到对应的物理页
  • remap_pfn_range函数只对保留页和物理内存之外的物理地址映射,而对常规RAM,remap_pfn_range函数不能映射,而nopage函数可以映射常规的RAM。

3 当实际访问新映射的页面时的操作(由缺页中断完成)

(1)  page cache及swap cache中页面的区分:一个被访问文件的物理页面都驻留在page cache或swap cache中,一个页面的所有信息由struct page来描述。struct page中有一个域为指针mapping ,它指向一个struct address_space类型结构。page cache或swap cache中的所有页面就是根据address_space结构以及一个偏移量来区分的。
 
(2) 文件与 address_space结构的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。 

(3) 进程调用mmap()时,只是在进程空间内新增了一块相应大小的缓冲区,并设置了相应的访问标识,但并没有建立进程空间到物理页面的映射。因此,第一次访问该空间时,会引发一个缺页异常。

(4) 对于共享内存映射情况,缺页异常处理程序首先在swap cache中寻找目标页(符合address_space以及偏移量的物理页),如果找到,则直接返回地址;如果没有找到,则判断该页是否在交换区 (swap area),如果在,则执行一个换入操作;如果上述两种情况都不满足,处理程序将分配新的物理页面,并把它插入到page cache中。进程最终将更新进程页表。

     注:对于映射普通文件情况(非共享映射),缺页异常处理程序首先会在page cache中根据address_space以及数据偏移量寻找相应的页面。如果没有找到,则说明文件数据还没有读入内存,处理程序会从磁盘读入相应的页面,并返回相应地址,同时,进程页表也会更新.
(5) 所有进程在映射同一个共享内存区域时,情况都一样,在建立线性地址与物理地址之间的映射之后,不论进程各自的返回地址如何,实际访问的必然是同一个共享内存区域对应的物理页面。

四 总结

1.对于mmap的内存映射,是将物理内存映射到进程的虚拟地址空间中去,那么进程对文件的访问就相当于直接对内存的访问,从而加快了读写操作的效率。在这里,remap_pfn_range函数是一次性的建立页表,而nopage函数是根据page fault产生的进程虚拟地址去找到内核相对应的逻辑地址,再通过这个逻辑地址去找到page。完成映射过程。remap_pfn_range不能对常规内存映射,只能对保留的内存与物理内存之外的进行映射。

2.在这里,要分清几个地址,一个是物理地址,这个很简单,就是物理内存的实际地址。第二个是内核虚拟地址,即内核可以直接访问的地址,如kmalloc,vmalloc等内核函数返回的地址,kmalloc返回的地址也称为内核逻辑地址。内核虚拟地址与实际的物理地址只有一个偏移量。第三个是进程虚拟地址,这个地址处于用户空间。而对于mmap函数映射的是物理地址到进程虚拟地址,而不是把物理地址映射到内核虚拟地址。而ioremap函数是将物理地址映射为内核虚拟地址。

3.用户空间的进程调用mmap函数,首先进行必要的处理,生成vma结构体,然后调用remap_pfn_range函数建立页表。而用户空间的mmap函数返回的是映射到进程地址空间的首地址。所以mmap函数与remap_pfn_range函数是不同的,前者只是生成mmap,而建立页表通过remap_pfn_range函数来完成。

Linux内存映射——mmap相关推荐

  1. Linux内存映射--mmap函数

    Linux提供了内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上), 通过对这段内存的读取和修改, 实现对文件的读取和修改, 先来看一下mmap的函数声明: 头文件: < ...

  2. [转载]linux内存映射mmap原理分析

    转自:http://blog.csdn.net/yusiguyuan/article/details/23388771 内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对 ...

  3. Linux内存映射mmap原理分析

    来自:Joe James 链接:https://blog.csdn.net/joejames/article/details/37958017 一直都对内存映射文件这个概念很模糊,不知道它和虚拟内存有 ...

  4. Linux 内存映射mmap,内存映射mmap

    Table of Contents 1. 什么是mmap 2. 使用方法 2.1. mmap构造器的格式 2.2. 例子1 2.3. 例子2 3. 其它 4. 参考资料 什么是mmap 通常在Unix ...

  5. linux mmap 内存映射 mmap() vs read()/write()/lseek()

    From: http://www.perfgeeks.com/?p=723 通过strace统计系统调用的时候,经常可以看到mmap()与mmap2().系统调用mmap()可以将某文件映射至内存(进 ...

  6. Linux 内存映射之文件映射

    Linux 内存映射之文件映射 文章目录 Linux 内存映射之文件映射 文件映射 匿名映射 文件的私有映射与共享映射 创建映射 mmap() 文件映射的边界情况 正常文件范围的内存映射 超出文件范围 ...

  7. linux内存映射原理,Linux内存管理实践-使用fault()实现内存映射

    内核态与用户态进行数据交互通常是这样一种模型:内核利用自身的特权通过特定的服务程序采集.接收和处理数据:接着,用户态程序和内核服务程序进行数据交互,或接收内核态的数据,或向内核态写入数据.通过传统的那 ...

  8. iOS之深入解析文件内存映射MMAP

    一.常规文件操作 常规文件操作(read/write)有以下重要步骤: 进程发起读文件请求: 内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的 inode: inode ...

  9. 高级OS(八) - Linux内存映射

    高级OS(八) - Linux内存映射 一.题目 二.解答 1.进入Linux 内核(4.19)源代码, 给出task_struct 结构,mm_struct 结构和 vm_area_struct 结 ...

  10. 进程间通信(IPC)之内存映射mmap和共享内存shm

    一.共享内存shm 1 概念:多个进程的地址空间都映射到同一块物理内存,这样多个进程都能看到这块物理内存,实现进程间通信,而且不需要数据的拷贝,所以速度最快. 二.内存映射mmap 1 前言:先介绍一 ...

最新文章

  1. OpenCV(实战)二值图颜色填充(彩色图形、硬币)
  2. 思考:开发者如何挑选最合适的机器学习框架?
  3. ▽算符在球坐标系_球坐标系下的角动量算符
  4. 【web项目】—the type java.io.ObjectInputStream cannot be resolved. It is indirectly referenced from ....
  5. CCKS 2018 | 工业界论坛报告简介
  6. 从零开始学安全(七)●Linux基础命令学习笔记
  7. PickerView 使用
  8. refprop物性库_refprop 9.1 下载-refprop(制冷剂物性查询运算软件)附中文教程 9.1 最新免费版 - 河东下载站...
  9. 【软技能】完全写作指南
  10. Microsoft edge兼容性问题
  11. cocos2dx[3.x](11)——拖尾渐隐效果MotionStreak
  12. 编译java源文件(在cmd下编译)傻瓜式教学
  13. NLP - Gensim
  14. Knowledge Graph Convolutional Networks for Recommender Systems with Label Smoothness Regularization
  15. java json 解析_Java解析JSON的四种方式
  16. 红米3s android6.0,奇兔刷机全网首发红米3S (Redmi 3S) MIUI8 ROM 独家支持一键刷机
  17. 如何查询idea当前项目所有打断点的位置
  18. 心理学大白话:科学理解束缚我们的常见思维
  19. 记录Android Studio连接华为(nova)手机鸿蒙系统踩过的坑
  20. 全国各地土特产一览表(以后好问朋友要礼物了)

热门文章

  1. [转]js和jquery获取窗体高度
  2. 怎么看服务器是实体机还是虚拟机,如何判断一台机器是物理机还是虚拟机
  3. c语言统计字符个数回车字符,C语言统计一串字符中空格键、Tab键、回车键、字母、数字及其他字符的个数(Ctrl+Z终止输入)...
  4. JavaScript常用算法 1
  5. 企业搜索 Solr7 安装部署
  6. 基于Spring Boot 的Blog开发
  7. UITableView介绍 之 复杂cell的高度计算
  8. ORACLE ERP 的前世今生(1)
  9. 转帖:MySql日期格式化
  10. ISA2006的部署和无人职守