分享

Linux内存管理

 风雪夜归人_95 2014-12-16
内存运行机制
1)linux系统会不时地进行页面交换操作,保持尽可能多的空闲物理内存,即使并不需要内存,linux会交换出暂时不用的内存页面。

2)内核根据“最近最经常使用”算法,仅仅将一些不经常使用的页面文件交换到虚拟内存。有时我们会看到这么一个现象:Linux物理内存还有很多,但是交换空间也使用了很多。其实,这并不奇怪,例如,一个占用很大内存的进程运行时,需要耗费很多内存资源,此时就会有一些不常用页面文件被交换到虚拟内存中,但后来这个占用很多内存资源的进程结束并释放了很多内存时,刚才被交换出去的页面文件并不会自动的交换进物理内存,除非有这个必要,那么此刻系统物理内存就会空闲很多,同时交换空间也在被使用,就出现了刚才所说的现象了。

3)交换空间的页面在使用时会首先被交换到物理内存,如果此时没有足够的物理内存来容纳这些页面,它们又会被马上交换出去,如此以来,虚拟内存中可能没有足够空间来存储这些交换页面,最终会导致Linux出现假死机、服务异常等问题,Linux虽然可以在一段时间内自行恢复,但是恢复后的系统已经基本不可用了。

内存监控常用命令
Linux监控内存最常使用的命令有free、top等。如图:
 第一行:
total:物理内存的总大小
used:已经分配的物理内存大小
free:空闲的物理内存大小(未被操作系统分配)
shared:多个进程共享的内存大小,主要用于进程间的通讯
buffers/cached:共同表示磁盘缓存的大小
第二行Mem:代表物理内存使用情况
第三行(-/+ buffers/cached):代表磁盘缓存使用状态
第四行:Swap表示交换空间内存使用状态

        第二行的数据实际上是从操作系统的角度来看的。used仅仅代表操作系统已经分配的物理内存大小,而不一定是已被使用的内存大小。例如,一个应用申请了1000KB的内存,不会立刻全部使用,可能当前只使用了600KB的内存,剩余的400KB就可以被“借”给操作系统来作为缓存使用。这些就以第二行的buffers和cashed的形式表示出来。
        buffers与cached都是内存操作,用来保存系统曾经打开过的文件以及文件属性信息,这样当操作系统需要读取某些文件时,会首先在buffers与cached内存区查找,如果找到,直接读出传送给应用程序,如果没有找到需要数据,才从磁盘读取,这就是操作系统的缓存机制,通过缓存,大大提高了操作系统的性能。但buffers与cached缓冲的内容却是不同的。
           buffers是用来缓冲块设备做的,它只记录文件系统的元数据(metadata)以及 tracking in-flight pages,而cached是用来给文件做缓冲。更通俗一点说:buffers主要用来存放目录里面有什么内容,文件的属性以及权限等等。而cached直接用来记忆我们打开过的文件和程序。
         Linux会在需要内存的时候,或在系统运行逐步推进时,将buffers和cached状态的内存变为free状态的内存,以供系统使用。

    第三行的数据则是从应用程序的角度来看内存分配。这里的“-/+”实际上分别指的是两个部分:
-buffers/cashed  =   used(第二行) - buffers - cached ;   (即当前系统所有程序真实使用的物理内存大小)
+buffers/cashed = buffers + cached;    (暂时借给系统作为缓冲区的内存大小)。
       从上面的论述也可以看出,第三行的free对应的一栏实际上是应用程序可以使用的内存大小,它的值 = free(第二行)+ buffers(第二行) + cached(第二行)。

       如果那些“借给”系统内存的程序需要“借出去”的内存,则从第二行的free一栏分给该程序。如果有必要的话,就会从buffers/cashed中释放内存,同时将释放内存中的数据写回到硬盘中。如果连buffers/cashed也没有了,则从硬盘处借,分配SWAP空间。
      你也可以通过命令行强制要求系统归还这些内存,命令如下:
      echo 3 > /proc/sys/vm/drop_caches     (3代表释放所有bufferes/cashed能释放的空间)
      不过你需要root权限。现象如图:

 可以看见buffers/cashed两个部分的空间大大缩小。

内存管理
内存管理的目标是提供一种方法,为实现各种目的而在各个用户之间实现内存共享。内存管理方法应该实现以下两个功能:
1)最小化管理内存所需的时间
2)最大化用于一般应用的可用内存(最小化管理开销)


内存管理实际上是一种关于权衡的零和游戏。您可以开发一种使用少量内存进行管理的算法,但是要花费更多时间来管理可用内存。也可以开发一个算法来有效地管理内存,但却要使用更多的内存。最终,特定应用程序的需求将促使对这种权衡作出选择。

每个内存管理器都使用了一种基于堆的分配策略。在这种方法中,大块内存(称为 堆)用来为用户定义的目的提供内存。当用户需要一块内存时,就请求给自己分配一定大小的内存。堆管理器会查看可用内存的情况(使用特定算法)并返回一块内存。搜索过程中使用的一些算法有 first-fit(在堆中搜索到的第一个满足请求的内存块)和 best-fit(使用堆中满足请求的最合适的内存块)。当用户使用完内存后,就将内存返回给堆。(注意 :这里的堆和数据结构中的堆不是一个概念

这种基于堆的分配策略的根本问题是碎片(fragmentation)。当内存块被分配后,它们会以不同的顺序在不同的时间返回。这样会在堆中留下一些洞,需要花一些时间才能有效地管理空闲内存。这种算法通常具有较高的内存使用效率(分配需要的内存),但是却需要花费更多时间来对堆进行管理。

另外一种方法称为 buddy memory allocation,是一种更快的内存分配技术,它将内存划分为 2 的幂次方个分区,并使用 best-fit 方法来分配内存请求。当用户释放内存时,就会检查 buddy 块,查看其相邻的内存块是否也已经被释放。如果是的话,将合并内存块以最小化内存碎片。这个算法的时间效率更高,但是由于使用 best-fit 方法的缘故,会产生内存浪费。

Slab分配器
     Linux 所使用的 slab 分配器的基础是 Jeff Bonwick 为 SunOS 操作系统首次引入的一种算法。
       最高层是cache_chain,这是一个slab缓存的连接列表,这对于best-fit算法非常有用,可以用来查找最适合所需要的分配大小的缓存(遍历列表)。cache_chain每个元素都是一个kmem_cache结构的引用,它定义了一个要管理的给定大小的对象池。
每个缓存都包含一个slabs列表,存在三种slab:slabs_full、slabs_patial、slabs_empty.
    由于对象是从 slab 中进行分配和释放的,因此单个 slab 可以在 slab 列表之间进行移动。例如,当一个 slab 中的所有对象都被使用完时,就从 slabs_partial 列表中移动到 slabs_full 列表中。当一个 slab 完全被分配并且有对象被释放后,就从 slabs_full 列表移动到 slabs_partial 列表。当所有对象都被释放后,就从 slabs_partial 列表移动到 slabs_empty 列表.

内存分配
回收目标
    不是所有的物理内存都可以参与回收的,一般内核代码段、数据段、内核kmalloc()出来的内存,内核线程占用的内存都是不可以回收的,除此之外的内存都是回收的对象。回收的内存主要是由用户态进程占用的内存和内核自己在运行时所使用的一些内存组成。用户态进程占用的内存主要是我们常见的进程代码段,数据段,堆栈等,内核运行使用的内存主要是磁盘高速缓存(如索引节点,目录项高速缓存),页面高速缓存(访问文件时系统生成的页面cache),mmap()文件时所用的有名映射所使用的物理内存。

 回收时机
    1)内存紧缺回收:grow_buffers()无法获取缓冲区页,alloc_page_buffers()无法获取页临时缓冲区首部,__alloc_pages()无法再给定的内存区分配一组连续页框。
    2)周期回收:必要时,激活相应内核线程执行内存回收算法:kswapd()内核线程,检查某个内存管理区的空闲页框数是否已低于pages_high值的标高。events内核线程,一个工作者线程,回收位于高速内存缓存中的所有空闲的slab。 

  回收的分类
    主要回收两类内存:最近最少使用内存以及高速内存缓存中空闲的slab。前者主要包括用户态进程的代码段,数据段,堆栈,文件映射内存,页高速内存,后者主要包括磁盘高速缓存及一些其他的空闲内存高速缓存。

内存紧缺回收及短期回收
    主要调用try_to_free_pages(),该函数会执行一个循环,按照优先级从12到0依次调用shrink_cashes(),shrink_slabs()来回收页面,直到回收至少32个内存页面。
    shrink_caches():调用shrink_zone()对传入的zone链表中的每个zone,进行lru上面的页面回收。
    shrink_slab():对磁盘索引节点cache和目录项索引节点等磁盘高速缓存进行回收,由于磁盘索引节点和目录项索引节点都是从slab高速缓存中分配的,这样就会导致空闲slab的产生,空闲slab后续会在周期性回收的cache_reap工作队列中被回收。估计也就是因为最终会清零空闲slab,才会起这么一个函数名。^_^
    shrink_zone():对内存管理区上的lru链表中的非活跃页面进行回收,在非活跃页面不足的时候,调用refill_inactive_zone()对lru上的inactive链表补充非活跃页面,同时shrink_zone()调用shrink_cache()来进行页面的回收,该函数的具体解析可以参照下面的源码浅析。
    shrink_list():该辅助函数在shrink_cache()中被调用,该函数对在shrink_cache()中传入的非活跃page列表进行遍历,对每个页面进行回收工作,该函数的具体解析可以参考下面的源码解析。
    refill_inactie_zone():该辅助函数根据一定的规则将处于lru active链表上的活跃页面移动到inactive链表上,以补充可以回收的页面,在lru链表里有两类页,一类是属于用户态空间的页,比如用户态进程的代码段,数据段,一类是在页高速缓存中的页,系统为了降低对应用程序的影响,将要优先将页高速缓存页进行回收,同时为了系统整体性能也会适当回收用户态进程页。




    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多