zoukankan      html  css  js  c++  java
  • CMA,memory compaction,migration and fragmation

    CMA

    A reworked contiguous memory allocator

    http://lwn.net/Articles/447405/

    June 14, 2011

         分配大块物理上连续内存的问题一直在被讨论。虚拟内存,天生的在系统上分散使用内存页,内核运行一会儿就只剩下很少的连续空闲的页。多年以来,内核开发者处理这个问题的方法是尽量避免对大的连续物理页分配的依赖。只有相当少的内核代码会尝试分配多于两个物理连续页。

         最近,对大的连续页分配的需求持续增长。一个是大页和THP,另一个是有新麻烦的旧事:不能执行分散/聚集DMA操作的硬件。任何只能对物理连续区域做DMA的设备(没有IOMMU)需要一个物理连续的缓存。这种需求一般出现在相对来说比较低端的硬件,有人可能希望这种硬件会随着时间流逝越来越少。但我们现在看到的却是在获得能力提升的同时仍有连续DMA缓存需求的设备。举个例子,能获取全高清数据的视频捕捉引擎能执行很多转换,但它仍需要一个连续的缓存来保存结果。高清视频的出现加重了这个问题--这些物理连续的缓存比以前更大也更难分配了。
         大约一年以前,LWN发表过想要解决这个问题的CMA(contiguous memory allocator)补丁。这个补丁仍然遵循了传统的为满足大的分配请求的特殊目的而在启动的时候保留一部分内存的方法。几年以前,这种技术就已经被用在“bigphysarea”补丁中了,它简单的传给内核“mem=”启动参数来保留一部分物理内存不被使用。安卓的pmem驱动也是从一个保留的区域里分配内存。近20年的经验证明这种方法是可行的。不足是保留的内存就不能再被其他地方使用了;如果设备没有使用这部分内存,它就保持闲置。这种浪费越来越不被内核开发者,还有用户所欢迎。
         因为这个和其他的原因,CMA补丁未被合并。但是问题尚未解决,而现在也没人继续研究这两种方法。CMA补丁的最新版看起来有点不同,尽管还有一些问题需要解决,这个补丁还是有更大的机会合入主线的。
         CMA分配器仍然可以工作在一个保留的内存区域,但很明显这不是大家想要的操作模式。作为代替,新的CMA尝试维护内存区域,当有需求的时候连续的内存就会被创建。为实现这个目的,CMA依赖于内存管理代码中的“迁移类型”机制。
         每一个区里,页块中的页被标记为(或非)可移动的或可重声明的。可移动页一般是指页缓存或匿名页,它们通过页表和页缓存基树来被访问。只要页表和基树做相应的改动,这些页的内容就可以被移到其他地方。可重声明页是指可能会按需返回给内核的页,它们保存数据结构比如inode缓存。不可移动页一般是内核直接指向的页,比如,通过kmalloc()获得的内存就不能在不破坏数据的情况下被移动。
         内存管理子系统会尽量把可移动页放在一起。(因为)如果想通过移动页来释放大块内存,只需要一个不可移动的页就可以摧毁所有的努力。通过把可移动页聚在一起,内核希望能按要求释放大块区域从而避免了这种问题。内存压缩代码依赖这些可移动页区域来工作。
         CMA通过增加一个新的“CMA”的迁移类型来扩展这种机制,它就像“movable”类型,但有两点不同。“CMA”类型是粘贴的,被标记为CMA的页就不会再被内核改变了。内存分配器不再从CMA区域分配不可移动页,并且只有在其它方法不可用的情况下才会为其他使用来分配CMA页。所以,被标记为用作CMA的内存区域只包含可移动页,并且有相对较多的空闲页。
         换句话说,被标记用作CMA的内存仍然可以被系统的其他部分使用,但有个限制是它只能包含可移动页。当一个驱动需要连续的内存时,CMA分配器会尝试从其中一个内存区域中分配足够的页来创建适当大小的连续缓存。如果那些内存区域的页是可移动的(现实中有时不会满足),驱动就会获得它需要的缓存。但这个缓存不需要时,内存可以被用作其他目的。
         有人可能会疑惑为何需要这种机制,内存压缩已经可以为THP创建大块物理连续的内存了。答案是DMA缓存有一些跟大页不同的需求。它们可能更大,举个例子,大多数架构上THP是2MB,而DMA缓存能达到10MB或更多。也有可能存在只能映射DMA缓存到特定内存范围的奇葩硬件--CMA的开发者Marek Szyprowski貌似就有这种硬件。最后,一个2MB的大页必须2MB对齐,而DMA缓存的对齐要求则更宽松一些。CMA分配器可以获得正好是需要的数量的内存(不用像伙伴分配器那样扩展到下一个2的幂的大小)而不用担心过度严格的对齐要求。
         CMA补丁提供了一些函数以方便创建内存区域和为特定范围的内存建立“内容/上下文”。然后有cm_alloc()和cm_free()来获取和释放缓存。但是,设备驱动最好不要直接调用CMA,而是把感知CMA集成到DMA函数里。当一个驱动调用比如dma_alloc_coherent()函数,CMA会被自动使用来满足要求。大多数情况下,这就可以满足需求了。
         剩余的关于CMA的疑问中的一个是关于如何在第一时间创建特殊区域。当前的方案是希望在系统的板文件(board file:系统相关的设置)中加入一些特殊的调用,这很像ARM的做事方法。社区的目的是不再使用板文件,所以得找其它方法。就像Arnd Bergmann指出的,把这些信息移到设备树也不是一个选项;这其实就是一个策略的问题。Arnd正在推动可以在大多数系统上设置一些合理的默认设置的方法,特殊挑战的奇葩系统的修复可以随后加上。
         最终的结果是这个补丁进入主线之前起码还得再有一轮迭代。但是CMA满足了一个真实的需求。这些代码有潜力使物理连续内存分配更加可靠,同时最小化对系统其它部分的影响。看起来值得拥有。

    Compaction

    Memory compaction

    http://lwn.net/Articles/368869/

    January 6, 2010

         长期存在的内存碎片化问题被讨论过好几次了。简短来说:随着系统运行,各个用户之间的页趋于分散,导致找到成组的物理连续页越来越难。社区在尽可能的避免高阶(多页)内存分配的需求方面做了大量工作,从来防止大部分内核功能被页碎片化破坏。但仍然存在需要高阶分配需求的存在,需要这种分配的代码在碎片化的系统上会失败。
         值得注意的是,某种程度上,这个问题事实上正变得越来越糟糕。当前的处理器并不局限于4K大小的页,它们可以在一个进程的一部分地址空间中使用更大的页(大页)。使用大页可以带来性能优势,这是由减少处理器TLB压力来获得的。但是使用大页需要系统可以提供物理连续的内存区域,这些内存不但要足够大而且还要适当的对齐。在运行了一段时间的系统上找到这种空间具有相当大的挑战。
         几年以来,内核开发者为减缓这个问题做了大量努力,带来了“ZONE_MOVABLE”和“lumpy reclaim”技术。但是还有更多的可以做,特别是在修复碎片化来恢复更大的大块内存方面。在这方面沉寂了一段时间后,Mel Gorman最近带着实现了内存压缩的补丁归来了。我们大体看一下这个补丁是如何工作的。
         想象这样一个很小的内存区:


         这里,白色的页是空闲的,红色的已经被分配使用了。就像我们看到的,这个区已经碎片化了,从这个区中分配一个4个页的块会失败。事实上,甚至两个页的分配也会失败,因为没有一对空闲页是适当对齐的。
         是时候调用内存压缩代码了。这些代码通过两个独立的算法来工作:第一个算法从区的底部开始并创建一个可移动的已分配页的链表:


         同时,在区的顶部,另一个算法创建一个可被页迁移使用的空闲页链表:


         最终在朝着区的中间的方向上会相遇。剩下的就是在相遇点上调用页迁移代码(不再只是用在NUMA系统上了)把已使用的页移到区顶部的空闲区中,产生像这样的一个图:


         现在我们有了一个好的八页的连续的空闲页,可以用来满足高阶分配的要求。
         当然,这里的图片相对于现实中发生的已经做过简化了。一开始,内存区会更大,那意味着有更多的工作需要做,但产生的空闲区域也会更大。
         但所有的这些只有在有问题的页是可移动的才能工作。不是所有的页都会按照希望的可以移动,只有那些通过一个间接的层来寻址和未被钉住的页才可以移动。所以,大部分用户空间页--通过用户虚拟地址来访问--可以移动,所需要做的是对相关的页表项做相应的调整。大部分被内核直接使用的内存是不能移动的--但一些是可以重声明的,意味着它们可以按需被释放。只要一个不可移动的页就可以毁坏一个连续的内存段。好消息是内核已经把可移动的和不可移动的页分开了,所以,事实上,不可移动页的问题比想象中的要小。
         内存压缩算法可以通过两者之一来触发。一是往/proc/sys/vm/compact_node(CentOS 6.5上是一个只写的compact_memory)中写入节点号,就会在指定的NUMA节点上调用内存压缩。另一个是在分配高阶页失败时,这种情况下,相对于通过直接重声明来释放页,内存压缩会被优先使用。如果没有明确的触发,内存压缩不会被调用,移动页会有消耗,所以在不需要的时候最好避免使用。
         Mel运行了一些简单的测试显示,在内存压缩使能的情况下,他可以把超过90%的系统内存分配成大页同时减少重声明活动的数量。所以这看起来是有用的工作。但是这是内存管理代码,所以合入主线的时间不好提前预计。

    Migration

    Page migration

    http://lwn.net/Articles/157066/

    October 25, 2005

         NUMA系统从设计上就有相对于特定节点(一组处理器)的本地内存。尽管所有的内存都是可访问的,但是本地内存工作起来比远程内存更快。内核考虑到NUMA的这种行为会尽量为进程分配本地内存,同时尽量避免在节点间移动进程。但是有时进程必须移动,从而导致本地分配优化反而变成了坏的。在这种情况下,如果在进程被移到了一个新的节点时,进程的内存也可以随之移动那就好了。
         内存迁移补丁已经存在一段时间了。最新版是Christoph Lameter发布的。这个补丁其实并没有解决整个问题,不过它志在建立一个足够最终的完全的迁移方案被引入的基础架构。
         这个补丁不会自动的为被移动的进程迁移内存,而是把迁移的决定留给用户空间。一个新的系统调用:

    long migrate_pages(pid_t pid, unsigned long maxnode,
                           unsigned long *old_nodes,
                           unsigned long *new_nodes);

         这个调用会试图把任何属于给定进程的页从old_nodes移到new_nodes。同时也有一个新的MPOL_MF_MOVE选项给set_mempolicy()系统调用,这会起到相同的左右。任何一种方法,用户空间都可以请求为给定进程释放一批节点。这种操作可以被执行以响应一个明确的进程移动本身(举个例子,被一个系统调度守护进程移动),或响应其他事件,比如一个节点即将关闭和移除。

         目前实现还比较简单:代码会重复扫描进程的内存并试图强制每个需要迁移的页交换出去。当进程在页上发生错误,然后相应页就会被分配到目前进程所在的节点。强制换出进程实际上执行了一些流程,开始它略过被锁定的页只关注那些可以容易释放的页。然后它等待被锁定的页并把最后的页换出内存。
    通过换出设备迁移页不是在NUMA系统上最有效的方法。补丁的后期目标是增加直接节点到节点迁移,当然还有其他特性。但是同时,开发者想把这个补丁合入2.6.15.但是Andrew Morton发表了一些保留意见:他想看到一个关于这个补丁如何可靠工作的解释。有很多东西会阻止页迁移,包括被用户空间锁住的页,执行直接IO的页,还有更多。Christoph响应说补丁最终会解决这些问题。这个声明是否能说服从而合入2.6.15还有待观察。

    VM followup: page migration and fragmentation avoidance(part)

    http://lwn.net/Articles/160201/

    November 8, 2005

         页迁移是把进程的页从系统的一部分移到另一部分。一般,在NUMA节点间移动页的动机是希望提升性能。上次提到它是通过强制目标页换出到swap设备工作的。当相应的进程在页上发生错误,这些页就会被换回到希望的节点上。这种技术可以工作但不是最优的:避免把页写到磁盘然后再读回来将会更好。
         Christoph Lameter带来了直接迁移补丁,它把swap设备放到了一边。让我们看看为什么这个补丁不首先使用这种方式,直接页迁移不单单引入了复制数据。第一步,选定目标页后,锁定页以防止其它人访问它。页可能正在执行IO,所以内核必须等待IO完成。然后真正的迁移才开始执行。
         内核必须为页建立一个换出缓存,即使它想避免把页写入swap。如果进程在它正在被移动的过程中发生错误,这个缓存就可以保证一切正常。然后所有对这个页的引用(页表项)都取消映射。幸运的话,所有的引用都没了,如果由于某种原因引用还在,页就不能移动。
         事实上,移动一个页包括复制一部分页状态,复制页数据本身,然后复制剩余的状态。旧页被清理并释放。如果有任何写回已经在新页上排队,它们就会被执行。然后就是简单的清理,页成功的移动了。
         如果内核在目标节点上用光了空闲页,那就退回到基于swap的机制。所以补丁前期的阶段仍然有用。
         使用这些代码,内核可以保持一个进程的页在本地内存中。迁移代码在热插拔内存方面也证明是有用的,它需要指定范围内所有的页都必须被回收。事实上,这些代码有一部分本来就是为热插拔应用而写的。但是,从这点上,迁移有一个很好的基础。对NUMA系统来说,移动页失败会导致更坏的性能,但没什么特别坏的地方。而对热插拔内存,这种失败会完全阻碍一个内存删除操作。100%的确定可以移动一个范围的所有页仍然是个很难的问题,目前还没有完整的方案。

    Fragmentation 

    Kswapd and high-order allocations

    http://lwn.net/Articles/101230/

    September 8, 2004

         内核里的核心内存分配机制是基于页的,它会试图找到一定数量连续的页以响应一个请求(这个地方的“一定数量”是指2的幂)。但是随之系统运行了一段时间,对多个连续页的更高阶分配请求变得很难满足。虚拟内存系统碎片化了物理内存以至于空闲页都相互分离了。
    好奇的读者可以查询/proc/buddyinfo看看目前空闲页是多么碎片化了。在一个1G的系统上,我看到:

    Node 0, zone   Normal 258 9 5 0 1 2 0 1 1 0 0

         在这个系统上,258单个页可立即分配,但只有9个连续的双页和5个4连续页。如果需要很多高阶分配的东西到来,可用内存很快就会用光,这些分配可能就会失败。

         Nick Piggin最近看到了这个问题并发现有一个地方可以做提升。问题在于kswapd进程,它在后台运行并为内存分配器提供空闲页(通过释放用户页)。目前的kswapd代码只是注意可用空闲页的数量,如果这个数目足够高,kswapd就休息而不管这些页是否是互相连续的。这会导致即使高阶分配失败,但系统也不打算做任何特别的努力来释放更多的连续页。
         Nick的补丁很直接,它只是简单的阻止kswapd休息直到一个足够数量的高阶分配是可用的。
         但是有人指出kswapd使用的方法其实没有真的改变:它选择页来释放但没考虑这些页是否可以合并成更大的组。结果,它可能不得不释放很多页才偶尔创建一些高阶的页组。在以前的内核,没有更好的可用方法。但2.6内核包含翻转映射代码。使用翻转映射,就有可能定位连续内存来释放并在这方面大幅度提升系统性能。
         Linus反对这种方法因为它推翻了目前的页替换策略,这种策略尽力释放在不远的将来不会使用的页。改变这种策略来定位连续的块会使高阶分配更容易,但它同样也会通过释放有用的页而对整个系统的性能不利。所以,Linus说,如果一个“反碎片化”模式一定要实现,那它应该很少运行并作为一个独立的进程。
         另一个针对这个问题的方法是简单的在一开始就避免高阶分配。转向4K的内核栈是这个方向上的进展,它消除了为进程执行一个两页的分配。在目前的内核,高阶分配最大的用户之一是高性能网络适配器驱动。这些适配器处理的大包不适合单个页,所以内核必须执行多页分配来保存这些包。
         实际上,这种分配只有在驱动(和它的硬件)不能处理分散在内存里的“非线性”包的时候才需要。大多数现代硬件可执行分散/聚集DMA操作,所以不关心包是不是保存在单个的连续内存里。但是,当向驱动写入时使用硬件的分散/聚集功能不但需要额外的工作,并且对很多驱动来说,这些工作还没完成。但是,从需求方面解决高阶分配问题可能比给页重声明代码增加另外一个东西更有效率。

    Active memory defragmentation

    http://lwn.net/Articles/105021/

    October 5, 2004

         在内核里,高阶分配是指试图为一个需要多于单个物理连续块的应用获取多个连续的页。这种分配一直是内核的一个问题,一旦系统运行了一段时间,物理内存一般被碎片化从而几乎没有连续空闲页存在。上个月,Nick Piggin尝试对kswapd做修改来一定程度上缓解这个问题。但是,也有其他人的方案。
         其中之一是Marcelo Tosatti,他发布了一个给内核增加主动内存反碎片化的补丁。在一个高的级别上,它使用的算法相对简单:为获得N阶空闲块,首先找到最大的、比N略小的块,然后试着重定位块前后的页的内容。如果有足够的页可被移动,一个更大的空闲页块就会被创建。
         自然地,这种方法仔细看的话会相当复杂。不是所有的页时可重定位的,举个例子,那些被锁住或保留的,是不能动的。这个补丁也排除了正在写回的页,除非写回IO完成,否则这些页是不能移动的。很多更复杂的例子,比如移动属于非线性映射的页,这个补丁目前也不能处理。
         如果一个页是不能重定位的,就必须先把它锁住然后把它的内容复制到一个新页上。然后所有引用旧页的页表必须重新指向新页。翻转映射信息,如果有的话,就必须被正确设置。如果在swap里有这个页的复制,那这个复制必须连接到新页。诸如此类。Marcelo的补丁针对很多更复杂的情况只是简单地拒绝移动这些页。即使如此,Marcelo报告了在创建大的连续空闲内存的好的结果。
         当然,有些小故障,包括在SMP系统上的问题。但是Marcelo说,不用担心:

    但是在UP上它工作的很好,可以很轻松的创建大的物理连续的内存区域。

         有人指出这个补丁跟另一项工作有一些共同的特性:支持热插拔内存。当内存要从系统中移除,目前存储在这块内存里的所有的页必须被重定位。本质上,热插拔内存补丁寻找并创建正好覆盖一片特定物理地址的一大块空闲内存。

         Dave Hansen描述了增加热插拔内存支持的两个补丁--一个来自IBM,另一个来自FUJITSU。每个都有它的优点和不足。
         在Marcelo和热插拔补丁之间,有很多在移动页来释放大块内存的经验。在任何一个合入主线之前,把这些补丁的优点合到一起可能是必须的。但最终的结果可能是对高阶分配问题的一个终结。

    Yet another approach to memory fragmentation

    http://lwn.net/Articles/121618/

    February 1, 2005

         很多开发者尝试解决内存碎片化和在内核中分配大的连续的内存块的问题。最近的方法包括Marcelo Tosatti的主动反碎片化和Nick Piggin的kswapd提升。现在Mel Gorman采用不同的方法加入争论。
         在很高的级别上,内核如下图的方式管理空闲页。


         系统的物理内存被分成区:在x86系统上,这些区包括可被ISA设备访问的小的空间(ZONE_DMA),正常内存区(ZONE_NORMAL)和内核非直接访问的内存(ZONE_HIGHMEM)。NUMA系统通过为每个节点创建区来进一步区分。每个节点里,内存被分成块(chunks)并按“阶数”(order)排列--每个块的大小的基于2算法。对每一个阶数都有一个这种大小的可用的块的链表。所以,这个数组的底部,阶数0链表包含单个页,阶数1链表包含双页,等等,直到系统的最大阶数。当一个给定阶数的分配申请到来时,一个块会从相应的链表上取下。如果这种大小的块没有了,一个更大的块会被拆分。当块被释放了,伙伴分配器试着把它们跟它们附近的块合并来创建更高阶的块。
         在现实中的linux系统,随着时间流逝,较大的块趋于被拆分以至于较大的分配变得很难。在一个运行的系统上可以通过/proc/buddyinfo看到很多0阶的页可用,但相对少的更大的块。为此,高阶分配在一个运行了一段时间的系统上更可能会失败。
         Mel的方法是把内存分配划分为三种,用新增加的GFP_标志来标明,这些标志可以在内存请求的时候提供。带__GFP_USERRCLM标志的内存分配用于用户空间,且很容易重声明。一般,重声明一个用户空间的页就是把它写到后备存储(如果它已经被修改了)。__GFP_KERNRCLM标志用在可重声明的内核内存,比如从slab中获取的和用在可按需释放的缓存里的。最后,未被标明的分配是被认为用简单的方法无法重声明的。
         然后,伙伴分配器的数据结构被扩展成这样:


         当分配器被初始化,所有未经使用的内存还没碎片化,free_area_global域指向一个长的最大大小块内存的链表。三个free_area数据--每种分配类型一个--一开始是空的。每个分配请求会在相应的可用free_area数组得到满足,否则,一个来自free_area_global的MAX_ORDER块就会被拆分。块未被分配的部分会被放到跟目前分配类型相同的数组里。
         当内存被释放并且块被合并,它们仍然留在相应类型数组里直到达到最大大小,然后被放回到全局数据里。
         这种组织的一个直接好处是最难回收的页--那些内核不可重声明的--被组织到自己的块里。一个钉住的页就能阻止一个大块的合并,所以,聚集这些难的内核也让剩余内存的管理更容易。除了这个,这种组织让主动页释放变得可能。如果一个高阶请求不能被满足,只是简单的从一个较小的块并释放其附近的页。但主动释放尚未在Mel当前的补丁里实现。
    即使没有主动释放,这个补丁也帮助了内核满足大的分配。Mel给出了他跑的测试结果。没打补丁,160次10阶分配申请里成功3次,打补丁后,81次成功。所以,新的分配技术和数据结构确实帮助了这种情况。但是,接下来发生的还有待观察,要想让这个补丁合入主线貌似尚有一个很大的障碍需要克服。

    Fragmentation avoidance

    http://lwn.net/Articles/158211/

    November 2, 2005

         Mel Gorman的避免碎片化补丁把所有的内存分配划分为三类:“用户可重声明”,“内核可重声明”和“内核不可重声明”。这个主意是通过把可重声明分配分到一起来支持多个连续页分配。如果没有连续内存可用,可以通过强制换出可重声明页创建一个。由于不可重声明页都被分到了它们自己的地方,一个这种页阻碍一个连续空闲页创建的几率相对很小。
         Mel最近发布了第19版避免碎片化补丁并请求合入-mm内核。这个请求引起了关于这个补丁是否是好主意的大讨论。对这部分代码是否属于内核好像还存在相当多的不确定。有一些需要避免碎片化的原因,在每个原因上的争论也不同。
         这些原因的第一个是它增加了内核中高阶分配的可能性。没人否认Mel的补丁达到了这个目的,但有些开发者声明说一个更好的方法是简单的消除这种分配。事实上,大多数多页分配在以前被处理了。但有一些仍存在,包括两页的内核栈仍然在大多数系统上被默认使用。当内核栈分配失败,它会阻塞新进程的创建。内核可能最终在所有情况下都转而使用单页栈,但一些高阶分配仍会遗留。把请求的内存分成单页的块也不是总会成功。
         下一个原因,跟第一个有很大的关联,就是大页。大页机制被用来在大型系统上为某些应用提升性能,目前只有很少用户,但是如果大页很容易工作的话情况可能会改变。大页不能在缺乏大的且适当对齐的连续内存的系统上分配。事实上,它们很难在运行了一段时间的系统上创建。分配大页失败相对来说没多大事,应用程序只需简单的用常规页并承受性能打击。但是,如果你有一个大页机制,让它更可靠的工作是很值得的。
         避免碎片化补丁对高阶分配和大页都有帮助。但是存在它是不是这个问题的正确方法的争论。经常被讨论的可选方法是为可重声明内存创建一个或更多新的内存区。这种方法会用到已内建在内核里的区系统,所以避免了一个新层的创建。一个基于区的系统也可能会避免潜在的避免碎片化补丁带来的性能影响(尽管未经证实)。考虑到这种影响出现在很关键的应用场景--内核编译--这个争论在内核开发者中引起了共鸣。
         但是基于区的方法并不是没有问题。目前的内存区是静态的,结果,人们不得不决定怎么划分内存为可重声明和不可重声明的。这种调整看起来很难用可靠的方法实现。在过去,区系统也是很多性能问题的来源,大多数与区之前的分配平衡相关。增加区系统的复杂性和增加区很可能让这些问题重现。
         另一个避免碎片化的动机是带来不同限制的对热插拔内存的支持。这种特性在高可用系统上有用,但它同时在虚拟化领域用的很多。一个跑了很多虚拟的linux实例的主机可以通过热插拔的方式在不同实例之间转移内存资源以满足各个实例的要求。
         在内存可以从一个运行着的系统从移除之前。它的内容必须被移到别处--至少,如果你还想你的系统能继续运行的话。避免碎片化补丁能通过只把可重声明的分配放到可能会移除的内存上来起到帮助。只要一个区域里的所有页都可以被重声明,那这个区域就是可移动的。
         一个很不同的争论出现了:Ingo Molnar强调任何声明支持热插拔内存的机制都要保证100%的成功率。目前的代码不必按照这个标准,但是需要一个照着这个目标的清晰的路径。否则,内核开发者就是在冒险推广一个还没提供可靠支持的特性。避免碎片化的支持者希望合并补丁以解决90%的问题,把剩余的90%留到以后(评论:这是内核特性有技巧的东西,对一个解决方法来说,第二个90%一般比第一个90%更难,有时甚至有第三个90%需要考虑。)。然而,Ingo担心第二个90%,想知道怎么实现它。
         为什么目前的补丁不能提供100%的可靠性如果只把可重声明内存放在可热插拔区域?有可能锁定已经被标明可重声明的页,这包括DMA操作和被用户空间明确锁住的页。也有可能在内核用光不可重声明内存时出现问题。不是让一个不可重声明的分配失败,内核会在可重声明区域分配页。这种回退在防止破坏内核其他方面的可靠性方面是必须的。 但是在一个可重声明区域里的一个不可重声明页会阻止系统清空这块区域。
         这个问题可以通过完全放弃不可重声明分配来解决。这可以通过改变内核地址空间工作方式来做。目前,内核运行在一个单独的连续的虚拟地址空间,它是直接映射到物理内存的--经常使用一个单独的大页表项。(vmalloc()是例外,不在讨论之列)。如果内核使用就像系统其他地方使用的常规大小的页,它的内存就不再需要是物理连续的了。然后,如果一个内核页阻碍了,它就可以简单的被移动一个更方便的地方。
         这种方法除了在根本上改变了内核的内存模型外,还有两个小问题。更高的转换缓存使用导致的性能破坏和为储存内核页表的内存数量的增加。某些内核操作--特别是DMA--不能容忍物理地址可能在任意时间发生改变。所以不得不增加一个新的API,驱动可以用它来申请物理上钉住的区域,并被内核告知把它们释放。换句话说,破坏内核的地址空间会带来大量的蠕虫障碍。如果没有强烈的动机是不会被接受的,热插拔内存还不是一个足够引人注目的理由。
         所以对避免碎片化还没有最终的结论。但是近期看,Andrew Morton的避免争论机制可能会阻止补丁进入-mm树。但是对这种能力的渴望是有正当理由的,问题也不会消失。除非有人提出更好的方法,不然很难永远不合并Mel的补丁。

    More on fragmentation avoidance

    http://lwn.net/Articles/159110/

    November 8, 2005

         上周关于避免碎片化的文章总结如下:

     但是对这种能力的渴望是有正当理由的,问题也不会消失。除非有人提出更好的方法,不然很难永远不合并Mel的补丁。

         但是一个能阻止补丁进入内核的事情是Linus的反对,它就发生在这个补丁上。他认为避免碎片化是“完全没用的”并总结说:

     不要这样做。我们从没这样做,我们一直很好。

         根据Linus的想法,正确的方法是在有释放大的,连续内存的需求的系统(很少)上创建一个特殊的内存区。在这个区里不能进行内核内存分配,所以它只包含用户空间页。这些页在需要的时候相对容易移动,所以大部分需求会被满足。会需要一些内核调优,但这是为运行高特殊化应用程序的代价。

         这种方法不被所有的人接受。Andi Kleen提醒:     

     一个负载用光了内核可分配页时你有两个选择。要么使用可重声明区或让分配失败。第一个意味着大页之类的是不可靠的,第二个意味着所有的关于受限lowmem的问题将会复现。

         其他人则提醒为所有类型负载来调优一个机器是很难的,特别是在有很多用户的系统上。尽管有反对,看起来主动避免碎片化不会很快进入2.6内核。

    VM followup: page migration and fragmentation avoidance(part)

    http://lwn.net/Articles/160201/

    November 8, 2005

         这种方法的其中之一可能是主动内存反碎片化,它不在可能会移除的内存区域中执行不可移动的内存分配。当我们上周看主动反碎片化时,这个补丁看起来有麻烦了。反碎片代码的负载可能太高,而且还有很多开发者(包括Linus)感觉这种功能应该用内核的区系统来实现,而不是在内存分配器中增加一个新的层。
         但是反碎片化的作者Mel Gorman不会轻易放弃。他发表了一个新的反碎片化补丁的轻量级版本,他希望它会更容易被接受。就像他描述的:     

       这是一个简化了的反碎片方法,它简单的试着让内核分配以2^(MAX_ORDER-1)为组和容易重声明的分配以2^(MAX_ORDER-1)为组。它不使用起平衡作用的可调整的特殊保留内存,也没在主路径里引入新的分支。对小内存系统来说,它可以通过一个配置选项关掉。它一共增加了275行代码同时只对主路径做了最小的改动。

         这个版本的补丁增加了一个新的GFP标志(__GFP_EASYRCLM),它的存在标明一个在需要的时候会很容易的被内核回收的分配。它用于用户空间页(一般可强制换出到后备存储中)和一些其他的场合,比如一些内核缓存(buffer)。伙伴分配器已经可以跟踪成块的内存,新的代码简单的控制在一些块上的可重声明分配,同时把不可重申明分配放在其他块上。它希望通过这种方法解决一个不可移动的页阻塞大的连续区域的释放的问题。

         这补丁通过创建一个跟踪每个大块内存上执行那种分配的“usemap”数组来工作。Mel也不得不拆分用来执行快速单页分配的per-CPU空闲链表,现在有两个这样的链表,每种分配类型一个。然后就只是依靠__GFP_EASYRCLM标志在适当的内存里执行分配了。
         这个版本当然减少了反碎片化补丁的足迹和负载。但还不是其他一些人希望的基于区的方法。所以主动反碎片化是否比它的前者更易被接受还有待观察。

    Avoiding - and fixing - memory fragmentation

    http://lwn.net/Articles/211505/

    November 28, 2006

         内存碎片化是一个很久的内核编程问题。随着系统运行,页分配给各个进程从而导致内存碎片化。一个运行了很长时间的繁忙的系统可能只有很少的物理上连续的页了。由于linux是虚拟内存系统,碎片化一般也不是问题,物理上分散的内存可以通过页表实现虚拟的连续。
         但是存在一些需要物理上连续内存的场合。这包括大的内核数据结构(除了通过vmalloc()分配的)和任何必须为外部设备保持连续的内存。如果一个大的(高阶)内存块在需要的时候不可用,一些东西就会失败,用户也开始考虑转向BSD。
         几年以来,大家考虑了很多针对内存碎片化的方法,但还没有一个被合并。给核心内存管理代码增加任何负载都很难被接受。但是这并不意味着人们停止了尝试。在这个领域最持久的人之一是Mel Gorman,他已经在一个反碎片化补丁上工作了好几年。Mel带着他的补丁的27版回来了,重新命名为“页集群化”。这个版本的补丁吸引了一些兴趣并可能合入主线。
         Mel补丁的核心内容仍然是一些类型的内存比另一些更容易重声明。比如,一个为文件系统做缓存的页是准备好被删掉和被重新使用的,而一个保存一个进程的task数据结构的页则被钉住了。一个顽固的页就能阻止一整个大内存块被用作物理连续的整体。但是如果所有容易重声明的页被放在一起,不可重声明的页被放在另一个单独的内存区域,创建一个更大的空闲内存块就更容易了。
         所以Mel的补丁把每个内存区分成了三类内存块:不可重声明的,容易重声明的和可移动的。“可移动的”是这个补丁里的一个新的特性,它被用在那些使用内核的页迁移机制就可以容易的转移到其他地方的页。很多情况下,移动一个页可能比重声明它更容易,因为不需要用到后备存储设备。通过这种方式给页分组页可以使更大的块的创建发生在当一个进程从一个NUMA节点迁移到另一个时。
         所以,这个补丁里,可移动的页(使用__GFP_MOVABLE标记的)一般是那些属于用户空间进程的页。移动一个用户空间页只是复制数据和修改页表项。所以相对容易。而可重声明页(使用__GFP_RECLAIMABLE标记)则一般属于内核。它们要么是短暂存在(比如,一些只在IO操作期间存在的DMA缓存)的分配或者是在需要的时候被删掉(各种缓存)的分配。其他任何东西都被认为是很难重声明的。
         通过简单的用这种方式把不同种类的分配分组,Mel得到了一些好的结果:     

    在基准和压力测试里,我们可以在测试后找到80%的内存用于连续的块。作为比较,一个桌面系统上的标准内核可以得到小于1%的可用作大页的内存和在压力测试结束后大约8-12%的内存用作大页。

         在过去,Linus一直反对减少内存碎片化的努力。但这次他的评论更注重细节:分配默认是可移动的还是不可移动的?答案应该是“不可移动的”,因为有人总是不得不为了确保一个特定的分配是可移动的而做一些努力。由于讨论已经到了这个程度,一些避免碎片化补丁可能找到了进入内核的方法。

         针对碎片化的一个相关的方法是Andy Whitcroft发表的但最初是Peter Zijlstra的“块状重声明”机制。linux里的内存重声明一般是通过一个最少-最近-使用(LRU)链表,想法是,如果需要删除一个页,用最少最近使用的页会最小化扔掉一个很快就用到的页的几率。但是这种机制会释放随机分散在物理地址空间的页,让创建更大的空闲内存快更难。
    块状重声明补丁尝试通过轻微的修改LRU算法来解决这个问题。当需要内存时,还是像以前一样从LRU链表上选择下一个目标。然后重声明代码会观察目标附近的页(它们足够构成一个更高阶的块)并同时试着释放它们。如果成功了,块状重声明会很快创建一个更大的空闲块的同时重声明最小数量的页。
         很明显,这种方法在附近的页可以被释放的情况下会工作的更好。结果显示,它跟一个集群化机制,比如像Mel Gorman那样的,结合的很好。LRU变形的方法会有性能影响,因为附近的页在块状重声明代码运行的时候可能正处在重负荷下。一个最小化这种影响的方法是块状重声明代码只在内核满足大块内存申请有困难时才执行。
         是否--何时--这些补丁被合入还有待观察。核心内存管理补丁需要很小心,它们很容易在暴露给现实负载时导致混乱。但是问题不会自己消失,所以有些东西迟早会发生。

      

    Short topics in memory management

    http://lwn.net/Articles/224829/

    March 6, 2007

         内存管理在2.6.x版本内核中是个相对平静的话题。很多最坏的问题已经解决了,内存管理的开发者继续做其他事情了。但是这并不意味着没有问题了,事实上,有些问题将要热起来了。一些最近的讨论揭露了一些可能导致在不远的将来引起新的兴趣的压力。
         Mel Gorman的避免碎片化补丁在过去已经讨论好几次了。Mel的核心思想是区分哪些可以容易移动或重声明的页并把它们分组。可移动的页包括那些分配给用户空间的页,移动它们只需要修改相关的页表项。可重声明页包括可以按需释放的内核缓存。把它们放在一起让内核释放大的内存块更容易,这会对使能高阶分配或清空整个内存区域都有用。
         在过去,Mel补丁的审核者在它工作方式上有不同意见。一些人支持为不同的分配类型维护单独的空闲链表,而其他的感觉这种内存分区的方法正是内核的区系统的创建目的。所以这次,Mel发布了两个补丁:基于链表的分组机制和一个仅限于可移动分配的新的ZONE_MOVABLE区。
         这次的不同是这两个补丁是设计一起工作的。默认的,没有可移动区,所以基于链表的机制处理把相似分配放在一起的全部工作。管理员可以在启动的时候用kernelcore=选项配置ZONE_MOVABLE,它用来指定*不*放在这个区里的内存的数量。另外,Mel发布了一些关于这些补丁是怎么影响性能的有理解力的信息。用不同寻常的方法,Mel使用了一些视频,这些视频显示了内存分配是如何响应使用不同分配机制的系统压力的。演示是有说服力的,但是为了在将来进入内核而创建多媒体演示是不必要的。
         这些补丁找到了它们进入-mm树的方法,但Andrew Morton仍然不清楚它们是否有价值的。另外,他还担心它们与其他相关工作的适应情况,特别是内存热拔除和每容器内存限制。尽管解决这两个问题的补丁已经发布了,但都还没达到合入内核的程度。
         Mel的工作很明显对内存的热移除是有帮助的--可能被移除的内存可以被限于可移动的和可重声明的分配,这会运行它在需要的时候被清空。但不是所有人都认为热拔除是一个有用的特性。特别是Linus反对这种想法。热拔除最大的潜在用途是在虚拟化,它允许一个管理器可以在客户端在需求改变的时候移动内存资源。Linus指出大多数虚拟化机制已经拥有允许客户端增加和删除单个页的机制,所以,他说,没必要再为内存变动提供其他支持。
         这种技术的另一个用途是允许系统通过在一些内存不使用的时候把它们关掉来保存电量。很明显,必须在关闭之前把所有有用的数据移出内存。Linus更加不同意这种做法:

    整个DRAM电源的故事就是一个给容易受骗的孩子们的睡前故事。不要陷进去。它是不真实的。支持它的硬件现在还不存在,或许几年后也不存在。真正的修复在其他地方...

         简短说来,Linus认为关掉整个NUMA节点比单独的内存更行得通。尽管如此,Mark Gross发布了一个包含一些基本的反碎片化技术的使能内存掉电的补丁。Mark说:

    内存电源管理是没用的除非你有利用它的工作环境。验证过的工作环境不是桌面环境。但是,有一些感兴趣的用户有一些合适的工作环境,这些工作环境让使能内存掉电补丁进入社区是值得的。这些工作环境一般在用内存利用率来跟踪流量负载的网络元素和服务器里。

         有人也建议常驻集合大小(resident set size)限制(一般跟容器联系在一起)可以解决很多反碎片化工作想要解决的问题。Rik van Riel回应抱怨说RSS限制会加重目前内存管理系统正在经历的可伸缩性问题。这引起了以前不知道这些问题的人的--比如Andrew--疑问。Rik用一些相对模糊的例子回应,很明显,他的特定的行为受限于经历这些问题的用户的协议。

         这导致了关于在没有测试实例来验证内存管理问题的情况下解决这些问题是否说的通的大讨论。Rik争论说修复测试实例会破坏现实世界的东西。Andrew回应说:

    我不相信一个没能力提供哪怕一个简单的测试实例的人或组织会有能力在不破坏东西的情况下修复这样的问题。

    Rik为让讨论继续提供了一些有问题的工作场景。

         Andrew的观点之一是在内核里修复由特定场景引起的内存管理问题总是很难,内核不总是有信息来知道哪些页会很快被用到和哪些会被删掉。可能,他说,正确的方法是让用户空间更容易的联系它将来希望的需求。为了这个目的,他为测试提出了一个页缓存管理工具。它作为一个LD_PRELOAD库来工作,截取文件相关的系统调用,跟踪应用程序使用量并通知内核在使用后从缓存里扔掉页。结果是一般操作(比如复制内核树)可以在不强制其他有用数据换出页缓存的情况下被实现。
         有一些对这个的有怀疑的回复。也存在一些关于怎么引入更智能的、特定应用的策略到这个工具的兴趣和讨论。比如,一个可能的备份工具策略会强制输出文件立即刷出内存,跟踪从其他文件读出的页并强制它们刷出--但它们必须还没有在页缓存里,等等。是否有人会用这个工具解决真实的工作环境问题还有待观察,但是有潜力。内核不总是什么都知道的。

    (转载本站文章请注明作者和出处 http://www.cnblogs.com/baiyw/,请勿用于任何商业用途)

  • 相关阅读:
    关系型数据库和非关系型数据库的区别
    总结篇3-python数据结构和算法
    总结篇2-python进阶
    总结篇1-python基础
    测试sql星级判定函数
    1、Anyproxy简介
    Python内置logging模块-- 日志
    python+ selenium 绕过浏览器检测
    python-selenium,解决 遇到阿里无痕登录验证
    seldom
  • 原文地址:https://www.cnblogs.com/baiyw/p/4272132.html
Copyright © 2011-2022 走看看