设为首页收藏本站返回主页

Ubuntu Kylin技术论坛

 找回密码
 立即注册
搜索
查看: 69|回复: 1

Linux内存管理

[复制链接]
  • TA的每日心情
    奋斗
    前天 09:09
  • 签到天数: 36 天

    [LV.5]常住居民I

    发表于 2017-12-6 19:25:27 | 显示全部楼层 |阅读模式
    简单介绍内存管理的基本概念和Linux上分配内存机制。
    1 基本概念
      1.1 地址
        *) 逻辑地址: 指由程序产生的与段相关的偏移地址部分。在C语言指针中,读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址。而数据段的基地址保存在全局描述符表/局部描述符表中。
        *) 线性地址: 段中的偏移地址,加上相应段的基地址就生成一个线性地址。是个中间值。
        *) 物理地址: 寻址总线上的地址。
        *) 虚拟地址: 保护模式下段和段内偏移量组成的地址。而逻辑地址就是代码段内的偏移量,或称进程的逻辑地址。
      1.2 内存
        *) 虚拟内存: 计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用内存,而实际上,他通常被分隔成多个物理内存碎片,还有部分暂时储存在外部磁盘的存储 器上,在需要的时候进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存的使用也更有效 率。
        *) 物理内存: 实际的内存。物理地址被分成离散的单元,成为页(page)。目前大多数系统的页面大小都为4k。
      1.3 地址转换
        Linux采用段页式管理机制,有两个部件用于地址转换:分段部件和分页部件。
        *) 分段部件: 将逻辑地址转换成线性地址。分段提供了隔绝各个代码,数据和堆栈区域的机制。因此多个程序可以同时运行在一个处理器上而不会相互干扰。
        *) 分页部件: 将线性地址转换成物理地址(页表和页目录),若没有分页机制,那么线性地址就是物理地址。
    2 内存分配
        malloc/kmalloc/vmalloc的区别:
        *) kmalloc和vmalloc分配的是内核空间的内存。malloc分配的是用户空间的内存。
        *) kmalloc保证分配的内存在物理上是连续的,vmalloc保证的是在虚拟地址空间上是连续的。
        *) kmalloc申请的内存比较小,一般小于128K,它是基于slab(内存池)的,以加快小内存的申请效率。
    3 常见问题
      3.1 调用malloc函数后,OS会马上分配物理内存吗?
        当使用malloc申请内存时,函数只会返回一个虚拟地址,待进程要使用内存时,OS会发出一个缺页中断,此时,内存管理模块才会为进程分配真正的内存。
      3.2 段式管理和页式管理的优缺点
        在段式存储管理中,将程序的地址空间划分为若干个段(segment),这样每个进程有一个二维的地址空间,相互独立,互不干扰。程序通过分段划分为多个 模块,如代码段,数据段,共享段和堆栈段等。这样做的优点是:可以分别编写和编译源程序的一个文件,并且可以针对不同类型的段采取不同的保护,也可以按段 为断为单位进行共享。段式存储的优点是:没有内碎片,外碎片可以通过内存紧缩来消除,便于实现内存共享。
        在页式存储管理中,将程序的逻辑地址空间划分为固定大小的页,而物理内存同样划分为固定大小的页框。程序加载时,可将任意一页放入内存中的任意一个页框, 这些页框不必连续,从而实现了离散分配。这种管理方式的优点是:没有外碎片,且一个程序不必连续存放,这样就便于改变程序占用空间的大小。
        页式和段式系统有许多相似之处。例如,两者都采用离散的分配方式,且都通过地址映射机构来实现地址变换。但概念上还是有许多区别,主要表现在:
        *) 页是信息的物理单位,分页是为了实现离散分配方式,以减少内存的外零头,提高内存的利用率。或者说,分页只是由于系统管理的需要,而不是用户的需要。段是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了更好的满足用户的需要。
        *) 页的大小固定且有系统决定,吧逻辑地址划分为也好和页内地址两部分,是由机器硬件实现的。段的长度是不固定的,且决定于用户编写的程序,通常由编译系统在对源程序进行编译时根据信息的性质来划分。
        *) 页式系统地址空间是一维的,即单一的线性地址空间。程序员只需利用一个标识符,就可以表示一个地址。分段的作业地址空间是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。
      3.3 malloc什么情况下调用mmap?
        从操作系统的角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk/mmap(不考虑共享内存)。brk是将数据段(.data)的最高地 址指针_edata往高地址推,mmap是在进程的虚拟地址空间中(一般是队和栈中间)找一块空闲的。这两种分配方式都是分配的虚拟内存,没有分配物理内 存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟和物理内存之间的映射关系。
        在标准的C库中,提供了malloc/free函数分配和释放内存,这两个函数底层是由mmap/brk和munmap这些系统调用实现的。默认情况 下,malloc函数分配内存,如果请求内存大于128K(可由M_MMAP_THRESHOLD选项调节),那就不去推_edata指针,而是利用 mmap系统调用,从堆和栈的中间分配一块虚拟内存。这样做主要是因为brk分配的内存需要等到高地址内存释放以后才能释放,而mmap分配的内存可以单 独释放。
      3.4 32位系统,通常情况下,最大虚拟地址和物理地址空间是多少?
        不是用PAE的情况下,最大虚拟地址和物理地址空间均为4G,若使用PAE,则最大虚拟地址空间仍为4G,而物理地址空间可变为64G(x86,32位变36位)。
    4 举例说明内存分配原理
      *) 进程启动的时候,其内存空间的初始布局如图1所示。其中,mmap内存映射文件是在堆和栈的中间。为了简单起见,省略了内存映射文件。_edata指针(glibc里面定义)指向数据段的最高地址。
      *) 进程调用A(malloc(30K))以后,内存空间如图2。malloc函数会调用brk系统调用,将_edata指针往高地址推30K,就完成虚拟地 址分配。这里,只是把_edata指针往高推了30K,A这块内存现在还是没有相应的物理页与之对应。等到进程第一次读写A这块内存的时候,发生缺页中 断,这个时候,内核才分配A这块内存对应的物理页。也就是说,如果使用malloc分配了A,但是从来不访问A,那么,A对应的物理页是不会被分配的。
      *) 进程调用B(malloc(40K))之后,内存空间如图3。
      *) 进程调用C(malloc(200K))以后,内存空间如图4。默认情况下,malloc函数分配内存,如果请求的内存空间大于128K,那么就不去推高_edata指针,而是利用mmap系统调用,从堆和栈的中间分配一块虚拟内存。
      *) 进程调用D(malloc(100K))以后,内存空间如图5。
      *) 进程调用free(C)以后,C对应的虚拟内存和物理内存一起释放。如图6。
      *) 进程调用free(B)以后,如图7所示。B对应的虚拟内存和物理内存都没有释放,因为只有一个_edata指针。如果往回推,那么D就不好办了。B这块内存是可以重用的。如果这个时候在来一个40K的请求,那么malloc很可能把B这块内存返回回去了。
      *) 进程调用free(D)以后,如果8所示。B和D连接起来,变成一块140K的空闲内存。
      *) 默认情况下,当最高地址空间的空闲内存超过128K(可由M_TRIM_THRESHOLD选项调节)时,执行内存紧缩操作(trim)。在上一个步骤free的时候,发现最高地址空闲超过128K,于是内存紧缩,如图9所示。
    5 案例
      5.1 问题现象
        压力测试过程中,出现进程的系统态CPU消耗20,用户态CPU消耗10,系统IDLE大约70,而使用ps -o majflt,minflt -c program命令查看,发现majflt每秒增量为0,而minflt每秒增量大于10000。
      5.2 问题分析
        清楚内存分配的原理,那么被测模块在内核态CPU消耗高的原因就清楚了。代码中,每次请求来都malloc一块2M的内存,默认情况下,malloc调用mmap(大于128K)分配内存。请求结束的时候,调用munmap释放内存。假设每个请求需要6个物理页,那么每个请求就会产生6个缺页中断,在2000的压力下,每秒就产生10000多次的minflt,这些缺页中断不需要读取磁盘。缺页中断在内核态执行,因此进程在内核态CPU消耗很大,缺页中断分散在整个请求的处理过程中,所以表现为分配语句耗时(10us),而整条请求处理时间为1000us,占的比重很小。
      5.3 解决办法
        将动态分配内存改为静态分配内存。或者在启动的时候,用malloc为每个线程分配,然后保存在threaddata里面。但是,由于模块的特殊性(具体不清楚),不能够使用静态分配或者给每个线程分配。另外,Linux下默认栈的大小限制为10M,如果在栈上分配几M的内存,风险很大。
        解决方法如下:
          禁用malloc调用mmap分配内存,禁止内存紧缩。
          在进程启动的时候,加入以下两行代码:
            mallopt(M_MMAP_MAX, 0);                 //禁止malloc调用mmap分配内存
            mallopt(M_TRIM_THRESHOLD, -1);   // 禁用内存紧缩
        加入这两行代码后,使用ps命令观察,压力稳定后,majflt和minflt的增量都为0。

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    小黑屋|手机版|Archiver|Ubuntu Kylin    

    GMT+8, 2017-12-16 05:40 , Processed in 0.211845 second(s), 9 queries , File On.

    Powered by Discuz! X3.3

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表