brk/mmap

linux 提供了两个比较重要的系统调用brk 和mmap,用于向内核申请相应用户空间,内核会根据系统运行状态判定是否申请新的VMA来管理新申请的用户空间,brk和mmap在整个系统中都占有非常重要的地位。

  • brk()系统调用 被gblic进行了进一步封装成malloc接口,用户层程序一般都是通过调用malloc,由glibc间接调用brk来向内核申请用户空间。brk申请的用户空间属于堆空间。
  • mmap系统调用也可以向内核申请用户空间,不过与brk不同的是,mmap申请的空间属于mapping映射空间部分。

用户空间部分如下图所示:

mmap

mmap系统调用函数接口如下:

#include <sys/mman.h>void * mmap(void *addr,size_t length, int prot, int flags, int fd,off_t offset)

参数:

  • addr: 指定映射被放置的虚拟地址,如果将addr指定为NULL,那么内核会为映射分配一个合适的地址。如果addr为一个非NULL值,则内核在选择地址映射时会将该参数值作为一个提示信息来处理。不管采用何种方式,内核会选择一个不与任何既有映射冲突的地址。在处理过程中, 内核会将指定的地址舍入到最近的一个分页边界处。
  • length:参数指定了映射的字节数。尽管length 无需是一个系统分页大小的倍数,但内核会以分页大小为单位来创建映射,因此实际上length会被向上提升为分页大小的下一个倍数。
  • prot: 参数掩码,用于指定映射上的保护信息:标记有:
描述
PROT_NONE 区域无法访问
PROT_READ  区域内容可读取
PROT_WRITE  区域内容可修改
PROT_EXEC  区域内容可执行
  •  flags:用于指定映射类型,
flags 描述
MAP_PRIVATE 创建一个私有映射。映射区域中内存发生变化对使用同一映射 的其他进程不可见。对文件映射来讲,所发生的变更将不会反应在底层文件上。
MAP_SHARED 区域中内容上所发生的变更对使用同一个映射区域的其他进程可见
MAP_ANONYMOUS 创建一个匿名映射
MAP_FIXED 原样解释addr
MAP_LOCKED 将映射分页锁进内存
MAP_HUGETLB 创建一个使用巨页的映射
MAP_HUGE_2MB 与MAP_HUGETLB一起使用,映射的巨页的页大小为2MB
MAP_NORESERVE 控制交换空间预留
MAP_POPULATE 填充一个映射的分页
MAP_UNINITALIZED 防止一个匿名映射被清零
MAP_32BIT 仅在x86-64系统下支持,映射空间位于前2G空间
MAP_STACK 目前在linux中没有实现,映射空间为stack 空间
MAP_SHARED_VALIDATE 与MAP_SHARED功能一样,区别就是MAP_SHARED会忽略未知flag设置,而MAP_SHARED_VALIDATE会对flags进行检查,如果是unknow flags将会返回EONNOTSUPP
MAP_SYNC 只有和MAP_SHARED_VALIDATE一起使用才有效,仅支持DAX 文件,如果是其他类型文件将会返回错误。

匿名映射/文件映射

mmap按照映射的类型主要可以分为文件映射和匿名映射。

  • 文件映射:文件映射是将一个文件的一部分直接映射到调用进程的虚拟内存中,一旦一个文件被映射之后就可以通过在相应内存区域中操作字节来访问文件内容了。映射的分页会在需要的时候从文件中(自动)加载。这种映射被称为基于文件映射或内存映射文件。
  • 匿名映射:一个匿名映射没有相应的文件。相反,这种映射的页面会被初始化为0.

同时又按照私有映射(MAP_PRIVATE)和共享映射分别将文件映射和匿名映射划分成不同使用用途:

可见性 文件映射 匿名映射
私有映射(MAP_PRIVATE)  根据文件内存初始化内存,其他进程不可见 内存分配
共享映射(MAP_SHARED) 内存映射I/O:进程间共享内存(IPC) 进程间共享内存(IPC)

匿名映射

匿名映射没有对应文件映射,其创建方法有两种:

  • 在flags中指定MAP_ANONYMOUS并将fd指定为-1
  • 打开/dev/zero设备文件并将得到的文件描述符传递给mmap.。/dev/zero是一个虚拟设备,当从中读取数据时它总是会返回0,而写入到这个设备中的数据总是被丢弃。/dev/zero的一个常见用途使用0来组装一个文件。

文件映射

文件映射创建需要执行下面的步骤:

  • 获取文件的一个描述符,通常通过调用open()来完成。
  • 将文件描述符作为fd参数传入mmap()调用。

执行上述步骤之后mmap()会将 打开的文件内容映射到调用进程的地址空间中。一旦mmap()被调用之后就能够关闭文件描述符,而不会对映射产生任何影响:

offset参数指定了从文件区域中的哪个字节开始映射,它必须是系统分页大小的倍数。将offset指定为0会导致从文件的起始位置开始映射。length参数指定了映射的字节数,offset和length参数一起确定了文件的哪个区域会被映射进内存。

共享文件映射

当多个进程创建同一个文件区域的共享映射时,它们会共享同样的内存物理分页。此外,对映射内存变更将会反应到文件上:

两个进程共享同一个文件映射,当进程A修改文件的内容将会立即被进程B看到。

内存映射I/O

由于共享文件映射中的内容是从文件初始化而来的,并且对映射内存所做出的变更都会自动反应到文件上,因此可以简单地通过访问内存中的文件来执行文件I/O,而依靠内核来确保对内存的变更会传递到映射文件中。(一般来讲,一个程序会定义一个结构化数据类型来与磁盘文件中的内容对应起来,然后使用该数据类型来转换映射的内容。)这项技术被称为内容映射I/O,它是使用read()和write()来访问文件内容这种方法的替代方案。

  • 内存映射I/O映射具备两个潜在的优势:
  • 使用内存访问来取代read()和write()系统调用,可以像访问内存一样访问文件

在一些情况下,它能够比使用传统的I/O系统调用执行文件I/O这种做法提供更好的性能。

内存映射之所以带来性能优势:

  • 正常的read()或write()需要两次传输:一次是在文件和内存高速缓冲区之间,另一次是在高速缓冲区和用户空间缓冲之间。使用mmap()就无限第二次传输。对于输入来讲,一旦内核将相应得文件块映射进内存之后用户进程就能够使用这些数据。对于输出来讲,用户进程仅仅需要修改内存中的内容,然后可以依靠内核内存管理全来自动更新底层文件。
  • 除了节省了内存空间和用户空间之间的一次传输之外,mmap()还能够通过减少所需使用的内存来提升性能。当使用read()或write()时,数据将被保存在两个缓冲区中:一个位于用户空间,另一个位于内核空间。当使用mmap()时,内核空间和用户空间会共享同一个缓冲区。此外,如果多个数据正在同一个文件上执行I/O,那么它们通过使用mmap()就能够共享同一个内核缓冲区,从而又能够节省内存的消耗。

内存映射I/O所带来的性能优势在大型文件中执行重复随机访问时最有可能体现出来。如果顺序访问一个文件,并假设执行I/O时使用的缓冲区大小足够大以至于能够避免执行大量的I/O系统调用,那么与read()和write()相比,mmap带来的性能上的提升就非常有限或者说根本就没有带来性能上的提升。性能提升的幅度之所以非常有限的原因是不管使用何种技术,真个文件的内容在磁盘和内存之间只传输一次,效率的提高主要得益于减少了用户空间和内核空间之间得一次数据传输,并且与磁盘I/O所需得时间相比,内存使用量得降低通常是可以忽略的。

对于小数据量I/O的开销(即映射、分页故障、接触映射以及更新硬件内存管理单元的超前转换缓冲器)实际上要比简单的read()和write()大。此外,有些时候内核难以高效处理可写入映射的回写(在这种情况下,使用msync()或sync_file_range()有助于提高效率)。

MAP_FIXED

MAP_FIXED是mmap() flag中的一个标记位,主要作用就是强制内核原样地解释用户传递的addr地址,按照应用程序指定的地址进行分配空间,而不是仅仅作为一种提升信息。如果 mmap函数指定了MAP_FIXED标记位,addr地址必须是页对齐的。

特别要注意的是如果要增强程序的可移植性,不建议使用MAP_FIXED。因为一旦使用该标记位 就是由用户强制指定分配的用户空间地址,而每个系统千差万别,可移植性较差。

如果在调用mmap()时指定了MAP_FIXED,并且内存区域的起始地址为addr,覆盖的length字节与之前的映射分页重叠了,那么重叠的分页会被新映射替代。使用这个特性可移植地将一个文件的多个部分映射进一块连续的内存区域。

可以利用上述这个特性来增强使用MAP_FIXED的可移植性,具体使用方法:

  • 1:使用mmap()创建一个匿名映射。在mmap()调用中将addr指定为NULL 并且不指定MAP_FIXED标记,这样就允许内核为映射选择一个地址。
  • 2:使用一系列指定MAP_FIXED标记的mmap()调用来将文件区域映射(即重叠)在上一步中创建的映射的不同部分。

尽管可以忽略第一个步骤而直接使用一系列mmap() MAP_FIXED操作来在应用程序选中的地址范围内创建一组连续的映射,但这种做法的可移植性与上面这种两步做法相比可移植性要差。一个应用程序应该避免在固定的地址处创建新映射。上面的第一步避免了可移植性问题的出现,因为这一步让内核选择一个连续的地址范围,然后在该地址范围内创建新映射。

MAP_FIXED安全风险

正如上面说讲述的,MAP_FIXED可以由应用程序来指定映射的虚拟地址,如果映射的虚拟地址空间已经存在,则会将旧的VMA给清掉,重新生成一个新的VMA。虽然很多驱动是这样做的,但是这样操作非常危险,因为应用程序不小心搞错 指定的虚拟地址是一个很关键的空间,则会将整个程序破坏掉,同时也会造成很重要的安全问题。

Any mmap() call allows the calling process to specify an address for the mapping. In normal operation, though, this address is simply a hint that the kernel is free to ignore. MAP_FIXED exists for cases where the mapping really has to be placed at the requested address or the application will fail to work. The kernel takes this flag seriously, to the point that, if there is already another mapping in the given address range, the existing mapping will be destroyed to make room for the new one. This seems like a strange semantic; if an application wants a mapping at a given area, it should probably be able to take responsibility for making room for that mapping. But mmap() is specified to work that way, so that is what happens.

Needless to say, that can be problematic if the application wasn't aware of the conflicting mapping — something that could occur as the result of a bug, address-space layout randomization, disagreements between libraries, or deliberate manipulation by an attacker. The data contained within that mapping (or the overlapping part of it, at least) will be silently dropped on the floor and the new mapping will show up in its place. The chances of things working correctly after that are likely to be fairly small. In some cases, security vulnerabilities can result; see, for example, CVE-2017-1000253. In that case, the kernel's internal use of MAP_FIXED to load programs into memory was exploited to corrupt the stack.

MAP_FIXED_NOREPLACE

为了 解决MAP_FIXED 所带来的风险,内核社区从4.17版本中开始引入新的flag标记位MAP_FIXED_NOREPLACE,该标记位意思是如果指定的虚拟地址 已经存在,则不执行替换覆盖旧的VMA动作,直接返回。如果不是特殊需求,建议使用MAP_FIXED_NOREPLACE增加程序安全性。

A solution can be found in Michal Hocko's MAP_FIXED_SAFE patch set. It adds a new mmap() flag called, surprisingly, MAP_FIXED_SAFE with semantics similar to MAP_FIXED with one exception: the operation will fail if the targeted address range is not free. The kernel's ELF loader is modified to use this new flag when mapping programs into memory; that will cause program loading to fail if two mappings collide, but that is better than the alternative. It is expected that new code would use this new flag in almost all cases, and that older programs would eventually be switched as well

MAP_FIXED解决方案是由Michal Hocko提出 刚开始新的字段命名位MAP_FIED_SAFE意味着是安全操作,代码审核阶段有人提出了该标记位命名并不能让人一看一目了然,正式合入内核中将其修改位MAP_FIXED_NOREPLACE。

参考资料

《linux/UNIX 系统编程手册》

MAP_FIXED_SAFE [LWN.net]

linux mmap系统调用相关推荐

  1. 【Linux 内核 内存管理】Linux 内核堆内存管理 ② ( 动态分配堆内存方式 | brk 系统调用 | mmap 系统调用 | brk 系统调用源码介绍 )

    文章目录 一.Linux 系统 动态分配堆内存 方式 二.brk 系统调用 动态分配堆内存 一.Linux 系统 动态分配堆内存 方式 Linux 系统中 , 提供了 222 种方式 进行 " ...

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

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

  3. 【Linux 内核 内存管理】mmap 系统调用源码分析 ④ ( do_mmap 函数执行流程 | do_mmap 函数源码 )

    文章目录 一.do_mmap 函数执行流程 二.do_mmap 函数源码 调用 mmap 系统调用 , 先检查 " 偏移 " 是否是 " 内存页大小 " 的 & ...

  4. Linux brk(),mmap()系统调用源码分析3:brk()的内存申请流程

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

  5. linux mmap内存文件映射

    一.传统文件访问 unix访问文件的传统方法使用open打开他们,如果有多个进程访问一个文件,则每一个进程在再记得地址空间都包含有该文件的副本,这不必要地浪费了存储空间.下面说明了两个进程同时读一个文 ...

  6. 内存映射(mmap系统调用)

    映射虚拟内存-->物理内存/Swap/文件 文件映射到内存,内存访问取代IO访问 可以映射同一个文件以(进程)共享内存 Linux进程虚拟地址空间---(分成)-->虚拟内存区 虚拟内存区 ...

  7. Linux mmap 详解

    Linux mmap详解 一.mmap()是什么 二.mmap()原理 三.mmap和常规文件操作的区别 四.mmap相关函数 五.mmap使用细节 六.对mmap()返回地址的访问 例子: 情形一: ...

  8. linux内核-系统调用execve()

    读者在linux内核-系统调用fork.vfork与clone中已经看到,进程通常是按其父进程的原样复制出来的,在多数情况下,如果复制出来的子进程不能与父进程分道扬镳,走自己的路,那就没多大意义.所以 ...

  9. linux mmap 函数详解,Linux之mmap函数简介

    本文主要讲述mmap 函数的使用,与驱动中 mmap 函数的实现 mmap 怎么使用,怎么实现,为什么 mmap 可以减少额外的拷贝? 下面简单详情. 一. mmap 的使用#include void ...

最新文章

  1. python 读取excel 内的中文显示为unicode 编码
  2. 微博php-sdk使用教程,腾讯微博api(php-sdk)的使用
  3. 简单超级组计划 打造强悍手臂
  4. 数学:乘法逆元-拓展GCD
  5. tomcat 报错:Error occurred during initialization of VM
  6. Java中的断言 Assert
  7. 《C#高效编程》读书笔记04-使用Conditional特性而不是#if条件编译
  8. (7)Redis-Cluster集群理论及实践【上】
  9. SAP MM TCODE
  10. 算法资料:算法导论_原书第3版(中文)(PDF带书签)
  11. ORACLE11gR2安装XDB
  12. Could not start AVD
  13. 算法设计与分析——背包问题(Java)
  14. 20几岁要懂点经济学【笔记】
  15. 单元测试、集成测试、系统测试、验收测试
  16. 电脑报价管理系统C语言,C语言笔记本电脑销售系统课设(附源码).doc
  17. 【一步教学,一步到位】拼多多社招三面多久给结果
  18. 易语言如何调用c dll文件,易语言调用C++写的DLL
  19. py实现外星人入侵(二次开发)——3.随机生成外星人和方向
  20. 社区医疗app-Ui设计

热门文章

  1. Gerchberg–Saxton算法
  2. Mysql面试常见知识点总结(一)
  3. Django(一)预热
  4. JavaScript 01
  5. TCP/IP学习(30)——L2数据链路层的数据包处理详细流程
  6. JS的console使用
  7. 跟angular2学一键开启项目--关于上个react-redux项目的一键调试
  8. FirefoxOS 1.2 on ZTE Open
  9. 关于public class 类名{ public static void main(String[] args)}的一些说明
  10. 不使用二分法无序查找元素