zoukankan      html  css  js  c++  java
  • 一些内核内存分配的提升

    http://lwn.net/Articles/658081/

    内核的内存分配要想在大多数情况下工作良好需要很多限制。随着时间推移,这些限制给底层分配代码带来了很大的复杂性。 但是仍然有问题存在,有时候,最好的解决方法是删掉一些复杂性并使用一个更简单的方法。Mel Gorman提的patch已经经过 几轮review,到了一个成熟的阶段;它可以作为一个例子来看看要想在目前的内核工作良好需要什么。

    在这里有问题的分配器是底层页分配器(伙伴分配器)。它处理的最小内存单位是一页(大多数系统是4KB)。slab分配器 (包括kmalloc())建立在页分配器之上;他们有他们的复杂性,在此不考虑。

    页分配器是系统中内存的最终来源,如果这个分配器不能满足一个请求,那内存就不能获得。所以为保证在所有情况下都能 获得内存做了很多努力,尤其是不能等待从其他地方回收一些内存的高优先级调用。高阶分配(那些需要多于一页物理上连 续内存的请求)使问题更复杂化;随着时间推移,内存趋于碎片化,难以找到连续的内存。NUMA系统的内存使用平衡增加了 另一个问题。所有的这些限制必须在分配器本身在不拖慢系统的情况下被处理。解决这个问题需要引入很多复杂的代码,可 怕的经验算法,还有其他;所以内存管理变动很难进入主线一点都不奇怪。

    zone cache

    页分配器把物理内存分成“zone”,每个zone跟有特定特性的内存关联。ZONE_DMA包含在地址区域底部被紧要设备使用的内存, 而ZONE_NORMAL可能包含系统中大多数内存。32位系统有个包含不能直接映射到内核地址空间内存的ZONE_HIGHMEM。每个NUMA 节点也有他们自己的zone集。基于分配请求的特性,页分配器按照特定优先级顺序搜索可用的zone。对于好奇的人,/proc/zoneinfo 提供了系统中使用的zone的很多信息。

    检查一个zone是否有内存满足请求需要比你想象的要多的工作。除了最高优先级请求,一个zone不应该低于特定的watermarks, 一个zone的可用内存与watermark作比较需要大量计算。如果zone回收特性被使能了,检查一个将要空的zone会让内存管理系统 回收zone的内存。因为这个和其他的原因,zonelist cache在2006年加入到2.6.20。这个cache用来记住最近被发现快满的zone, 可以让分配请求避免检查满的zone。

    zonelist cache的作用随着时间被减弱了,Mel Gorman的patch通过降低watermark检查的代价进一步减弱了它。zone回收被指出是 很多负载的性能问题,现在默认是关掉的。但是最大的问题是,如果一个zone不能满足一个高阶分配,它就被标记为满即使有可用 的单个页。接下来的单页分配会跳过这个zone,即使它有足够的能力满足这些分配。这会引起分配不必要的在远端NUMA节点执行, 让性能更坏。

    Mel指出这个问题可以通过增加复杂性解决,但是zonelist cache的好处是有疑问的,所以删掉它会更好。patch删掉了近300行复杂 的内存管理代码并提高了一些benchmark。zone总是被检查也有其他问题;很明显最值得注意的是检查本来避免的zone导致更多的直 接回收工作(执行分配的进程尝试回收内存)。

    原子高阶预留

    在zone里,内存被分为page block,每个可以用描述block怎么被分配的migration type标记。目前的内核其中一个type是MIGRATE_RESERVE ,它标记的内存不能被分配除非请求分配然后会失败。由于标记的是一个物理连续的block区域,所以这个策略的影响是在系统中维护一个 最小数量的高阶页。这也意味着高阶请求(合理的)可以被满足即使内存被碎片化了。

    Mel在2007年的2.6.24开发周期中加入了migration reserve。预留改善了当时的状况,但是最终它依赖于多年前在内核实现的最小watermark 的特性。预留不会主动保留高阶页,它只是简单地阻止在特定内存区域的请求除非没有其它选项,它这样做是想让这个区域保持连续。 预留的方法也早于现在的在避免碎片化和在碎片化发生的时候执行紧缩方面做得更好的内存管理代码。Mel的patch表明这种预留 已经过时,应该删掉它。

    但是为高阶分配预留内存块仍然有价值,碎片化在当前的内核里仍是个问题。所以Mel另一个patch为了这个用不同的方法创建了一个 新的MIGRATE_HIGHATOMIC预留。一开始,这个预留不包含任何页块。如果一个高阶分配在不拆分一整个页块的情况下不能被满足,这 个页块就会被标记为高阶原子预留的一部分,此后,只有高阶分配(只有高优先级的)可以用这个页块来满足。

    内核会限制这个预留的大小约为内存的1%,所以它不会变的过大。页块留在预留里直到内存压力达到单个页分配会失败的程度,在这种情况下 内核会从预留里取出一个页块来满足请求。最终高阶页面预留会更灵活,根据目前的负载来增加或减少。由于高阶页面的需求在不同 系统之前变化很大,根据实际运行调节预留是合理的,结果是更灵活的分配和更高可靠的访问高阶页面。

    但是以免将来内核开发者认为他们对高阶分配可以更放松,Mel提醒说,预留大小受限导致的一个结果是,为长期的高阶分配去访问 预留而投机的滥用原子分配的调用很很快失败。但是他没有给出指示他认为的这些调用是谁。需要记住这种预留的另一个潜在的缺陷是 :由于直到执行一个高阶分配才有页块进入预留,预留可能系统运行了很长时间还是空的。到那时(系统运行很长时间后),内存可能 碎片化很严重了而不能分给预留。如果这种情况在实际使用中出现,可以通过在启动时先把最小数量的内存放到预留来解决。

    高阶预留也让删除高阶页的watermark变得可行。这些watermark为了保证每个zone对每个order都有最少可用的页,分配器会让导致 低于watermark的分配失败,除了最高优先级的分配。这些watermark实现起来相对困难,也可能引起正常优先级分配在即使有足够适配 页的情况下失败。打上Mel的补丁后,代码仍强制单页的watermark,但是对于高阶分配,它仅仅检查一个适配页可用,计算高阶预留来 保证页面为高优先级分配可用。

    Flag day

    内核中的内存分配请求总是分为一组GFP标志,这些标志描述了为满足请求什么可以做很什么不能做。最常用的标志是GFP_ATOMIC和 GFP_KERNEL,但实际上他们基于底层的标志。GFP_ATOMIC是最高优先级请求,它可以使用预留并不允许睡眠。GFP_ATOMIC被定义 为一个位__GFP_HIGH,标记为一个高优先级请求。GFP_KERNEL不能使用预留但可以睡眠,它是__GFP_WAIT(可能睡眠), __GFP_IO(可能启动底层IO),__GFP_FS(可能调用文件系统操作)的组合。整个标志集合很大,可以再include/linux/gfp.h找到。

    有趣的是,最高优先级请求不是标记为__GFP_HIGH,而是通过没有__GFP_WAIT标记。带有__GFP_HIGH标记的请求可能使内存低于watermark, 但是只有非__GFP_WAIT请求可以访问原子预留。这种机制在当前内核中工作的并不好,因为很多子系统可能发起不想等待的分配(经常是 因为他们有分配失败的回调机制),但是这些子系统不需要访问最后的预留。但是,因为没有__GFP_WAIT,这些代码总是会访问这些 预留。

    这个问题,同时想更明确的控制内存分配请求被满足的方式,让Mel重新设计GFP标志集。他增加了一些新的标志:

    • __GFP_ATOMIC标识真正来自不能阻塞或延迟和失败没有回调的原子上下文的请求。如果是__GFP_ATOMIC分配,它可能意味着,比如, 自旋锁可能正在被持有。这些请求可以访问原子预留。
    • __GFP_DIRECT_RECLAIM表明调用者乐意进入直接回收。它意味着请求在需要时会阻塞。这个标志不暗示__GFP_FS或__GFP_IO,它们 必须在适用时被分开指定(但是这种情况下可能用GFP_KERNEL更合理)。
    • __GFP_KSWAPD_RECLAIM说明kswapd可能被唤醒执行回收。唤醒kswapd并不暗示阻塞,但这会在系统中启动影响性能的活动。举个例子, 一个很想分配一个高阶内存块的驱动,如果这个块不可用它可以通过一些单页来获得块。高阶分配可能最好不要用__GFP_KSWAPD_RECLAIM, 因为即使分配失败系统仍然能工作,没有真正的启动积极回收内存的需求。

    用这些标志,代码可以表示绝对不能睡眠和不想睡眠之间的区别。一个请求的“必须成功”的特点从“不睡眠”中区分开来,减少了 不必要的访问原子预留的情况。对GFP_ATOMIC和GFP_KERNEL的用户来说没有改变,但Mel的patch针对很多使用底层GFP标志的调用场合 做了改动。

    总的来说,这个patch涉及到101个文件,删掉了240行代码。幸运的是,很多核心的内存管理算法被简化的同事提高了性能并让系统 更可靠。Mel强烈的基于benchmark的方法为这些工作增加了信心,但这对于一个复杂的内核子系统来说是很大的改动,所以这些patch 会经过很多review。看起来这个进程已经接近尾声,可能会在下一个或两个开发周期进入主线。

  • 相关阅读:
    天猫弹性导航栏
    php 中构造函数和析构函数
    web服务器集群(多台web服务器)后session如何同步和共享
    mycat中schema.xml的一些解释
    mycat高可用集群搭建
    mycat水平分表
    mycat实现mysql数据库的垂直切分
    高并发、大流量解决方案
    nginx负载均衡六种策略
    mysql主从复制实现
  • 原文地址:https://www.cnblogs.com/baiyw/p/5036524.html
Copyright © 2011-2022 走看看