Linux内存整理和学习

        本文仅是根据网络上的资料结合自己对Linux内存的理解形成的一些文档,主要是为Android虚拟机以及Android系统相关的知识做准备,所以更多的是概念性结论性的东西,并没有Liunx内核相关的代码,对于专家来说也是很low的文章,可能还会有一些错误,欢迎大家来批评指正。

           Android是基于Linux内核的,所以在系统层面上更多的是Linux的架构和逻辑,比如对进程的管控,比如进程的内存架构和管理,个人觉得如果想要从系统层面上熟悉Android机制和Android虚拟机,对Linux的掌握是必要条件之一。

        在本文中我从以下几个方面整理了Linux内存相关的知识:

  1. 每个进程中内存占据的大小为4G,如何理解这4G;
  2. 这4G内存的分布情况,每一个类型各有什么特点;
  3. Linux内存的管理的几个方法,每个方法有什么特点;
  4. mmap实现原理和机制。

        了解过Linux内存的人都听过:每一个进程中都有4G的虚拟内存空间,这4G内存空间由2部分组成,0-3G(虚拟地址 0x00000000到0xBFFFFFFF)是用户空间,3G-4G(虚拟地址0xC0000000到0xFFFFFFFF)是内核空间,这句话。其实更加准确的说cpu的寻址能力是2^32就是4G。现在很多的机器更多的是64位的,那这个时候的是否是2^64的地址空间呢?这个不是的,对于64位的虚拟地址为2^48,即用48位来表示虚拟地址空间,用40位来表示物理地址。关于这个可以使用命令cat /proc/cpuinfo来查看,对应的address size 里面可以看到物理和虚拟。

     关于Linux程序的用户空间的虚拟内存分布,网上有很多,说的粗一些可以依次低到高分为代码区、数据区、堆和栈。当然这里要重点说下详细的分区可以分为:保留区、 rodata 段、text段、bss段、.data段、堆、mmap段、命令行参数和环境变量段、栈。

       我们先来看下不同的方法属性对应的相关的内存分布,随便写一个带有各种属性的,并且打印每一个变量的指针,然后cat proc/{pid}/maps对应的结果

     从打印的结果和cat proc/{pid}/maps的结果对应如下:

 

                                                                                              图一

        在cat maps出来每一行列对应的意义就不多说,这里主要想讨论下对应的段和cat maps直接的关系。前面说过每一个应用都包含一些分区和段,那么在这个程序里面每个分区和段都是对应什么呢,从这些打印的变量指针值大致能看出来,但很多时候在cat maps里面并没有区分出对应的段,如果我们要看可以在这些段在虚拟内存中的位置,可以使用readelf –a 去解析这个elf文件(这里只是小试牛刀下,后面学习虚拟机的时候会重点学习elf文件),看每一个段对应的内容,每个段对应虚拟地址的内容如下表所示。

 

 

text:函数

0040000-0040100

rodata:define 常量

bss:方法中未初始化化的数据

00602000-00604000

data:static,

01b8f000-01bbf000

mmap

7f791d2d3000-7f791d2d4000

stack

7fff46e18000-7fff46e39000

环境变量

参数

         这里要特别注意下mmap这个段,因为mmap主要是去读取结构体vm_area_struct,而网上说这个结构体存储的是有名映射内存(此处请教过朴老师,朴老师的说有名匿名都会有,但在匿名的时候貌似不管怎么操作都不会有,朴老师的说法应该是正确的但因为我没有验证到所以先写我的结论),而我将其修改为有名映射在maps中也看不到,这里要说的是如果我们仅是单纯的做映射这样Linux是还不会分配空间的,但如果我们对这块内存做了一个操作,比如将里面的指针加1,这样才能看到,即延迟分配(此处感谢我飞飞兄的指点),如图二。

                                                               图二

 

           Linux分配内存主要有两个比较基础的算法伙伴算法和slab算法,伙伴算法主要用于分配一个页即4k以上的大内存,好处是能减少内存碎片,slab算法主要用于分配小内存,这两个算法是实现malloc的原理。

在linux系统中有很多分配内存的方法,这里先整理一下关于brk()/sbrk()函数的原理和使用,先使用man 查看下该函数

          这个描述很好的解释了brk()和sbrk()函数的原理,这个函数函数是通过改变程序断点的位置,什么是程序断点呢,这个是程序数据段的尾端。前面提了一句虚拟内存粗略的可以分为4个区,按照文档翻译过来是这个断点指的就是数据区和堆的分割点,我们写一个程序,另sbrk(0),会发现此时这个值是指向堆的最后的值如图三。估计帮助文档中所谓的date segment说的并不是Linux虚拟内存的date段。不过这个也很好理解,date区域时编译完成的时候就分配好了,但堆上分配的动态分配的,每一次使用brk()/sbrk()对分配的时候都是通过移动这个点的位置,也正是因为这点,如果使用这两个释放空间也是通过反向移动这个点的值,所以释放的顺序是高地址先释放。

                                                                      图三

在Linux里面还有一个最为常见的申请和释放内存的方法是malloc()/free(),我们先看下它的描述

       我更多的是希望通过description来说下malloc的实现原理,但这里只介绍了calloc()、realloc()这些calloc()的兄弟函数,并没有介绍相关的原理。因为malloc()的头文件是在stdlib里面,所以可以去/usr/include和http://ftp.gnu.org/gnu/glibc连接里面去找对应的实现。具体的算法和源码可以去参照下面相关的链接,我这里总结一下:malloc核心的实现是使用block这个结构体,计算出要分配的内存空间,通过sbrk()这个方法来实现的。

还有就是relloc()和calloc()两个方法,calloc()主要是申请完内存之后会把里面的元素都默认置成0,而relloc()方法是指remalloc,在之前的基础上重新malloc,如果重新分配的大小比之前的大小要小则可能会数据丢失,如果重新分配的大小比之前的要大,之前的那段空间的指针会变成野指针,注意要释放该指针。relloc(void*ptr,size_t new_size)传入的那个ptr必须为NULL或者malloc的返回值。

关于mmap的实现原理其实从它的用途也可以基本上能猜到一二,它是虚拟内存的映射,而Linux主要是通过文件来实现的,那这个步骤基本上就很明确了,首先是通过虚拟内存找到对应的文件,在让另一个进程的虚拟空间的地址也指向这个文件,当然Linux里面有一个写时给的机制(即上面说的为什么只映射不会分配内存的原因)。

用专业的话是下面几步骤:

  1. 新建一个vm_area_struct的结构体,将这结构体插入到进程的虚拟地址区域或树中;
  2. 通过fd找到对应的内核中该fd对应的文件结构体,并调用内核中的int mmap(struct file *filp,struct vm_area_struct *vma)通过虚拟文件中的inode模块定位到文件磁盘的物理空间;
  3. 通过remap_pfn_range函数建立页表,这样文件地址和虚拟地址的映射关系就建立了;
  4. 到第三步映射已经建立完成,但还没有真正的分配内存,如果有访问这块内存的操作,这个时候会引发缺页异常;
  5. 判定缺页异常并非非法操作时,这个时候内核会发起请求调页的过程;
  6. 调页完成之后,对应的页将从磁盘写到主存,由于用户对这片主存进行写操作,一定时间后系统会把脏页会写到磁盘。

   对我个人而言,这里比较好奇的主要有这几个:

   1. vm_area_struct的数据结构和用法;

   2.建立页表的过程,即remap_pfn_range方法的原理;

   3.缺页异常。

    先说说vm_area_struc,关于vm_area_struct数据结构和用法,本来是想写一个程序使用task_struct找到mm_struct再找到vm_area_struct,遍历每一个vm_area_struct把其vm_start和vm_end打印出来看和第一部分的maps里面的值是不是一样,但Linux内核这块对我来说写起来太耗时间,这就从网上贴上一张图片来说明下这几个结构体的关系吧。

                                                     

                                                                              图四

    第二个remap_pfn_range方法也先放一下等有机会学内核的时候再来重新学习下。

    缺页异常简单的说下吧,给定一个线性地址,MMU 通过页目录表、页表的转换,找到对应的物理地址。在这个过程中,如果因某种原因导致无法访问到最终的物理内存单元,CPU 会产生一次缺页异常。所以这个缺页异常在这里并不是异常,只是当映射完成之后并没有分配真正的物理地址空间,这个时候引发的缺页异常,当然还有的时候是这个地址就是一个错误(或者没有权限)的地址,这个时候就会发生段错误了,具体的处理过程可以参照do_page_fault()这个方法。

       

这篇主要是学习为主,东西大部分是别人的,目的是为了真正学习Android虚拟机和系统的,这里的别人主要是下面的几位同学

 

https://blog.csdn.net/weixin_41576955/article/details/84075908

https://blog.csdn.net/hustyangju/article/details/46330259

https://blog.csdn.net/huiguixian/article/details/6325383?utm_source=blogxgwz6

https://blog.csdn.net/wenqian1991/article/details/27968779

https://blog.csdn.net/sgbfblog/article/details/7772153

https://www.cnblogs.com/Commence/p/5785912.html

http://blog.jobbole.com/91887/

https://www.cnblogs.com/huxiao-tee/p/4660352.html

https://blog.csdn.net/jnu_simba/article/details/11757473

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章