Linux内存管理—详细讲解 (3)

  如果你要查看的某个进程占用的内存区域,可以使用命令cat /proc//maps获得(pid是你需要查看的进程ID,可以利用上面给出的例子——执行”./test &“,进程ID随之会打印到屏幕,注:为了方便测试请在上文给出的例子return 之前添加sleep(50)函数人该进程睡眠50秒),你可以发现很多类似于下面的数字信息。

  由于程序test使用了动态库,所以除了test本身使用的的内存区域外,还会包含那些动态库使用的内存区域(区域顺序是:代码段、数据段、bss段)。

我们下面只抽出和test有关的信息,除了前两行代表的代码段和数据段外,最后一行是进程使用的栈空间。

在这里插入图片描述


输出数据含义:
以内存区域为例(第一行,注:由于terminal窗口尺寸的原因第一行显示的数据太多用两行来显示):

55a2323aa000-55a2323ab000 r-xp 00000000 08:12 16875 /media/Disk_E/Linux_Workspace/***/Miracle/test/test

上文前三行为该进程的运行时的内存,个列格式分别为:

内存起始地址-结束地址 访问权限 偏移量 主设备号:次设备号 inode 文件(以及绝对路径)

  注意,你一定会发现进程空间只包含三个内存区域,似乎没有上面所提到的堆、bss等,其实并非如此,程序内存段和进程地址空间中的内存区域是种模糊对应,也就是说,堆、bss、数据段(初始化过的)都在进程空间中由数据段内存区域表示。

  在Linux内核中对应进程内存区域的数据结构是: vm_area_struct, 内核将每个内存区域作为一个单独的内存对象管理,相应的操作也都一致。采用面向对象方法使VMA结构体可以代表多种类型的内存区域--比如内存映射文件或进程的用户空间栈等,对这些区域的操作也都不尽相同。

  vm_area_strcut结构比较复杂,关于它的详细结构请参阅相关资料。我们这里只对它的组织方法做一点补充说明。vm_area_struct是描述进程地址空间的基本管理单元,对于一个进程来说往往需要多个内存区域来描述它的虚拟空间,如何关联这些不同的内存区域呢?大家可能都会想到使用链表,的确vm_area_struct结构确实是以链表形式链接,不过为了方便查找,内核又以红黑树(以前的内核使用平衡树)的形式组织内存区域,以便降低搜索耗时。并存的两种组织形式,并非冗余:链表用于需要遍历全部节点的时候用,而红黑树适用于在地址空间中定位特定内存区域的时候。内核为了内存区域上的各种不同操作都能获得高性能,所以同时使用了这两种数据结构。

下图反映了进程地址空间的管理模型:

在这里插入图片描述


  进程的地址空间对应的描述结构是“内存描述符结构”,它表示进程的全部地址空间,包含了和进程地址空间有关的全部信息,其中当然包含进程的内存区域。

进程内存的分配与回收

  创建进程fork()、程序载入execve()、映射文件mmap()、动态内存分配malloc()/brk()等进程相关操作都需要分配内存给进程。不过这时进程申请和获得的还不是实际内存,而是虚拟内存,准确的说是“内存区域”。进程对内存区域的分配最终都会归结到do_mmap()函数上来(brk调用被单独以系统调用实现,不用do_mmap()),内核使用do_mmap()函数创建一个新的线性地址区间。但是说该函数创建了一个新VMA并不非常准确,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。如果不能合并,那么就确实需要创建一个新的VMA了。但无论哪种情况, do_mmap()函数都会将一个地址区间加入到进程的地址空间中--无论是扩展已存在的内存区域还是创建一个新的区域。

  同样,释放一个内存区域应使用函数do_ummap(),它会销毁对应的内存区域。

如何由虚变实

  从上面已经看到进程所能直接操作的地址都为虚拟地址。当进程需要内存时,从内核获得的仅仅是虚拟的内存区域,而不是实际的物理地址,进程并没有获得物理内存,获得的仅仅是对一个新的线性地址区间的使用权。实际的物理内存只有当进程真的去访问新获取的虚拟地址时,才会由“请求页机制”产生“缺页”异常,从而进入分配实际页面的例程。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwzffz.html