咱们先来看下Linux内存布局,此图比我以前写的那篇文章写的布局更详细html
在linux中,每个进程都被抽象为task_struct结构体,称为进程描述符,存储着进程linux
各方面的信息;例如打开的文件,信号以及内存等等;而后task_struct的一个属性mm_struct管理着进程的全部虚拟内存,称为内存描述符。在mm_struct结构体中,存储着进程各个内存段的开始以及结尾,如上图所示;这个进程使用的物理内存,即常驻内存RSS页数,这个内存使用的虚拟地址空间VSZ页数,还有这个进程虚拟内存区域集合和页表。缓存
从上面这个图能够看出,进程是有代码段Text segment,数据段(已初始化的全局,静态变量),BSS段(未初始化的全局,静态变量),堆,内存映射区以及栈;函数
每一块虚拟内存区(VMA)都是由一块连续的虚拟地址组成,这些地址从不覆盖。一个vm_area_struct实例描述了一块内存区域,包括这块内存区域的开始以及结尾地址;flags标志决定了这块内存的访问权限和行为;vm_file决定这块内存是由哪一个文件映射的,若是没有文件映射,则这块内存为匿名的(anonymous)。上述图中提到的每一个内存段,都对应于一个vm_area_struct结构。以下图所示布局
上图即为/bin/gonzo进程的内存布局。程序的二进制文件映射到代码段和数据段,代码段为只读只执行,不可更改;全局以及静态的未初始化的变量映射到BSS段,为匿名映射,堆和栈也是匿名映射,由于没有相应的文件映射;内存映射区能够映射共享库,映射文件以及匿名映射,因此这块内存段能够是文件映射也能够是匿名映射。并且不一样的文件,映射到不一样的vm_area_struct区。指针
这些vm_area_struct集合存储在mm_struct中的一个单向链表和红黑树中;当输出/proc/pid/maps文件时,只须要遍历这个链表便可。红黑树主要是为了快速定位到某一个内存块,红黑树的根存储在mm_rb域。htm
以前介绍过,线性地址须要经过页表才能转换为物理地址。每一个进程的内存描述符也保存了这个进程页表指针pgd,每一块虚拟内存页都和页表的某一项对应。blog
虚拟内存是不存储任何数据的,它只是将地址空间映射到物理内存。物理内存有内核伙伴系统分配,若是一块物理内存没有被映射,就能够被伙伴系统分配给虚拟内存。刚分配的物理内存叶框多是匿名的,存储进程数据,也多是也缓存,存储文件或块设备的数据。一块虚拟内存vm_area_struct块是由连续的虚拟内存页组成的,而这些虚拟内存块映射的物理内存却不必定连续,以下图所示:进程
如上图所示,有三个页映射到物理内存,还有两个页没有映射,因此常驻内存RSS为12kb,而虚拟内存大小为20kb。对于有映射到物理内存的三个页的页表项PTE的Present标志设为1,而两个没有映射物理内存的虚拟内存页表项的Present位清除。因此这时访问那两块内存,则会致使异常缺页。内存
vma就像应用程序和内核的一个契约。当应用程序申请内存或者文件映射时,内核先响应这个请求,分配或更新虚拟内存;可是这些虚拟内存并无映射到真实的物理内存。而是等到内存访问产生一个内存异常缺页时才真正映射物理内存。即当访问没有映射的虚拟内存时,因为页表项的Present位没有被设置,因此此时会产生一个缺页异常。vma记录和页表项两个在解决内存缺页,释放内存以及内存swap out都起着重要的做用。下面图展现了上述状况:
一、一开始堆中只有8kb的内存,并且都已经映射到物理内存;
二、当调用brk()函数扩展堆时,新的页是没有映射到物理内存的,
三、当处理器须要访问一个地址,并且这个地址在上述刚分配的虚拟内存中,这时产生一个缺页异常;
四、这时进程向伙伴系统申请一页的物理内存,映射到那块虚拟内存上,并添加页表项,设置Present位.
自此,这个内存管理暂时就说到这。总结下:
一、Linux进程的内存布局的每一个段都是有一个vm_area_struct,而这个实例是由连续的虚拟内存地址组成;
二、当请求内存时,先是扩展vm_area_struct或者新分配一个vm_area_struct,可是并不映射物理内存,只有等到访问这块内存时,产生缺页异常,内核才分配物理内存。
本文地址:https://www.linuxprobe.com/linux-memory-layout.html