外贸网站系统,苏州百度seo关键词优化市场,余姚公司建设网站,建设部注册中心网站想写一篇文章#xff0c;详细的介绍一下mmap#xff0c;主要是原理、用法、mmap泄露来进行介绍。说到mmap#xff0c;首先得从堆空间说起。
申请堆空间
其实#xff0c;不管是 32 位系统还是 64 位系统#xff0c;内核都会维护一个变量 brk#xff0c;指向堆的顶部详细的介绍一下mmap主要是原理、用法、mmap泄露来进行介绍。说到mmap首先得从堆空间说起。
申请堆空间
其实不管是 32 位系统还是 64 位系统内核都会维护一个变量 brk指向堆的顶部所以brk 的位置实际上就决定了堆的大小。Linux 系统为我们提供了两个重要的系统调用来修改堆的大小分别是 sbrk 和 mmap。接下来我们来学习这两个系统调用是如何使用的。我们先来看 sbrk。
sbrk
sbrk 函数的头文件和原型定义如下
#include unistd.hvoid* sbrk(intptr_t incr);
sbrk 通过给内核的 brk 变量增加 incr来改变堆的大小incr 可以为负数。当 incr 为正数时堆增大当 incr 为负数时堆减小。如果 sbrk 函数执行成功那返回值就是 brk的旧值如果失败就会返回 -1同时会把 errno 设置为 ENOMEM。
在实际应用中我们很少直接使用 sbrk 来申请堆内存而是使用 C 语言提供的 malloc 函数进行堆内存的分配然后用 free 进行内存释放。这里你要注意的是malloc 和 free 函数不是系统调用而是 C 语言的运行时库。Linux 上的主流运行时库是 glibc其他影响力比较大的运行时库还有 musl 等。C 语言的运行时库多是以动态链接库的方式实现的。 在 C 语言的运行时库里malloc 向程序提供分配一小块内存的功能当运行时库的内存分配完之后它会使用 sbrk 方法向操作系统再申请一块大的内存。我们可以将 C 语言的运行时库类比为零售商它从操作系统那里批发一块比较大的内存然后再通过零售的方式一点点地提供给程序员使用。
mmap
另一个可以申请堆内存的系统调用是 mmap它是最重要的内存管理接口。mmap 的头文件和原型如下所示
#include unistd.h
#include sys/mman.hvoid *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
我来解释一下上述代码中的各个变量的意义
start 代表该区域的起始地址
length 代表该区域长度
prot 描述了这块新的内存区域的访问权限
flags 描述了该区域的类型
fd 代表文件描述符
offset 代表文件内的偏移值。
mmap 的功能非常强大根据参数的不同它可以用于创建共享内存也可以创建文件映射区域用于提升 IO 效率还可以用来申请堆内存。决定它的功能的主要是 prot, flags和 fd 这三个参数我们分别来看看。
prot 的值可以是以下四个常量的组合
PROT_EXEC表示这块内存区域有可执行权限意味着这部分内存可以看成是代码段它里面存储的往往是 CPU 可以执行的机器码。
PROT_READ表示这块内存区域可读。
PROT_WRITE表示这块内存区域可写。
PROT_NONE表示这块内存区域的页面不能被访问。
而 flags 的值可取的常量比较多你可以通过 man mmap 查看这里我只列举一下最重要的四种可取值常量
MAP_SHARED创建一个共享映射的区域多个进程可以通过共享映射的方式来共享同一个文件。这样一来一个进程对该文件的修改其他进程也可以观察到这就实现了数据的通讯。
MAP_PRIVATE创建一个私有的映射区域多个进程可以使用私有映射的方式来映射同一个文件。但是当一个进程对文件进行修改时操作系统就会为它创建一个独立的副本这样它对文件的修改其他进程就看不到了从而达到映射区域私有的目的。
MAP_ANONYMOUS创建一个匿名映射也就是没有关联文件。使用这个选项时fd 参数必须为空。
MAP_FIXED一般来说addr 参数只是建议操作系统尽量以 addr 为起始地址进行内存映射但如果操作系统判断 addr 作为起始地址不能满足长度或者权限要求时就会另外再找其他适合的区域进行映射。如果 flags 的值取是 MAP_FIXED 的话就不再把addr 看成是建议了而是将其视为强制要求。如果不能成功映射就会返回空指针。
通常我们使用私有匿名映射来进行堆内存的分配。
我们再来看参数 fd。当参数 fd 不为 0 时mmap 映射的内存区域将会和文件关联如果fd 为 0就没有对应的相关文件此时就是匿名映射flags 的取值必须为MAP_ANONYMOUS。
明白了 mmap 及其各参数的含义后你肯定想知道什么场景下才会使用 mmap我们又该怎么使用它。
mmap 的其他应用场景
mmap 这个系统调用的能力非常强大我们在后面还会经常遇到它。在这节课里我们先来了解一下它最常见的用法。 根据映射的类型mmap 有四种最常用的组合 其中私有匿名映射常用于分配内存也就是我们上文讲的申请堆内存而私有文件映射常用于加载动态库。
这里我们重点看看共享匿名映射。我们通过一个例子来了解一下 mmap 是如何用于父子进程之间的通信的它的用法示例代码如下
共享匿名映射 / 匿名共享映射
#include stdio.h
#include sys/mman.h
#include stdlib.h
#include unistd.hint main(int argc,char* argv[])
{pid_t pid;char* shm (char*)mmap(0, 4096, PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS, -1, 0);if(!(pid fork())){sleep(1);printf(child got a message: %s\n,shm);sprintf(shm, %s, hello, father.);exit(0);}sprintf(shm, %s, hello, my child);sleep(2);printf(parent got a message: %s\n, shm);return 0;
}wjwj:~/WORK/Learning/learning/cpp_learning$ gcc test.c -o test.out
wjwj:~/WORK/Learning/learning/cpp_learning$ ./test.out
child got a message: hello, my child
parent got a message: hello, father.在这个过程中我们先是用 mmap 方法创建了一块共享内存区域命名为 shm接着又通过 fork 这个系统调用创建了子进程具体来讲子进程休眠一秒后从 shm 中取出一行字符并打印出来然后又向共享内存中写入了一行消息。
在子进程的执行逻辑之后是父进程的执行逻辑父进程先写入一行消息然后休眠两秒等待子进程完成读取消息和发消息的过程并退出后父进程再从共享内存中取出子进程发过来的消息。 我想请你结合我刚才的讲解来分析一下这个程序运行的结果这样你就理解的更透彻 了。
关于共享匿名映射我们就讲到这里至于 mmap 的另一个组合共享文件映射。它的作用其实和共享匿名映射相似也可以用于进程间通讯。不同的是共享文件映射是通过文件名来创建共享内存区域的这就让没有父子关系的进程也可以通过相同的文件创建共享内存区域从而可以使用共享内存进行进程间通讯。 私有匿名映射 mmap用途小结
mmap最常见的四种用途分别是
私有匿名映射用于分配堆空间 私有文件映射用于加载动态链接库共享匿名映射用于父子进程之间通讯 共享文件映射用于多进程之间通讯。 mmap原理
私有匿名映射 私有匿名映射是最简单的情况 在调用 mmap 时只需要在文件映射区域分配一块内存 然后创建这块内存所对应的 vma 结构这次调用就结束了 。 当访问到这块虚拟内存时由于这块虚拟内存都没有映射到物理内存上就会发生缺页中 断但这一次的缺页中断与 execve 时的缺页中断不一样这次是匿名映射所以关联文件属性为空。此时内核就会调用 do_anonymous_page 来分配一个物理内存并将整个物理页全部初始化为 0然后在页表里建立起虚拟地址到物理地址的映射关系。 私有文件映射 在内核中如果有一个进程打开了一个文件PCB 中就会有一个 struct file 结构与这个文件对应。struct file 结构是与进程相关假如进程 A 与进程 B 都打开了文件 f那么进程 A 中就会有一个 struct file 结构进程 B 中也会有一个。 Linux 的文件系统中有一个叫做 inode 的结构这个结构与具体的磁盘上的文件是一一对应的也就是说对于同一个文件整个内核中只会有一个 inode 结构。所以进程 A 与进程 B 的 file struct 结构都有一个指针指向 inode 结构这就将 file struct 与 inode 结构联系起来了。 在 inode 结构中有一个哈希表以文件的页号为 key以物理内存页为 value。当进程 A 打开了文件 f然后读取了它的第 4 页这时内核就会把 4 和这个物理页放入这个 哈希表中。当进程 B 再打开文件 f要读取它的第 4 页时因为 f 的第 4 页的内容已经被 加载到物理页中了所以就不用再加载一次了。只需要将 B 的虚拟地址与这个物理页建立 映射就可以了如下图所示 我要提醒你的是 哈希表在现代的 Linux 内核中已经被优化成了 Radix tree 和最小堆 的一种优化的数据结构它们比哈希表有更好的时间效率所以你在阅读不同版本的 Linux 内核代码时要注意这个变化 。 如果文件是只读的话那这个文件在物理页的层面上其实是共享的。也就是进程 A 和进程 B 都有一页虚拟内存被映射到了相同的物理页上。但如果要写文件的时候因为这一段内 存区域的属性是私有的所以内核就会做一次写时复制为写文件的进程单独地创建一份 副本。这样一个进程在写文件时并不会影响到其他进程的读。 对于共享库文件代码段的私有属性其实并不影响它在所有进程间共享但如果数据段在 执行的过程发生变化内核就可以通过写时复制机制为每个进程创建一个副本。这就是对 于共享库文件要选择私有文件映射的根本原因。 这里我们就有这样一个结论 私有文件映射的只读页是多进程间共享的可写页是每个进 程都有一个独立的副本创建副本的时机仍然是写时复制 。 共享文件映射 在私有文件映射的基础上共享文件映射就很简单了 对于可写的页面在写的时候不进 行复制就可以了 。这样的话无论何时也无论是读还是写多个进程在访问同一个文件 的同一个页时访问的都是相同的物理页面。 共享匿名映射 在这节课之前你可能会觉得共享匿名映射在父子进程间通讯是最简单的因为父子进程 共享了相同的 mmap 的返回值看上去最直观。但实际上从内核的角度说它却是最复杂的。 原因是 mmap 并不真正分配物理内存它只是分配了一段虚拟内存也就是说只在 PCB 中创建了一个 vma 结构而已。这就导致 fork 在复制页表的时候页表中共享匿名映射区 域都是未映射状态 。 请你设想一下如果内核不做特殊处理的话在父进程因为访问共享内存区域而遇到缺页 中断时内核为它分配了物理页面等子进程再访问共享内存区域时内核也没有办法知 道子进程的虚拟内存应该映射到哪个物理页面上因为缺页中断只能知道当前进程是 谁以及发生问题的虚拟地址是什么这些信息不足够计算出是否有其他进程已经把共享内存准备好了。 在内核中使用虚拟文件系统来解决这个问题之前早期的 Linux 内核中并不支持共享匿名 映射。虚拟文件并不是真实地在磁盘上存在的。它只是由内核模拟出来的但是它也有自 己的 inode 结构。这样一来内核就能在创建共享匿名映射区域时创建一个虚拟文件 并将这个文件与映射区域的 vma 关联起来。 当 fork 创建子进程时子进程会复制父进程的全部 vma 信息。接下来发生的事情就和共 享文件映射完全一样了我们就不再重复了。 至此我们才终于把 mmap 的核心原理分析清楚。 mmap 的功能之所以十分强大主是因为操作系统综合使用写保护中断、缺页中断和文件 机制来实现 mmap 的各种功能 。