zoukankan      html  css  js  c++  java
  • kernel feature collection

    Fault injection

    http://lwn.net/Articles/209257/

    The framework can cause memory allocation failures at two levels: in the slab allocator (where it affects kmalloc() and most other small-object allocations) and at the page allocator level (where it affects everything, eventually).

    Page owner

    http://lwn.net/Articles/121656/

    The kernel lock validator

    http://lwn.net/Articles/185666/

    The "too small to fail" memory-allocation rule

    http://lwn.net/Articles/627419/

         内核开发者长久以来被告知,除了少数情况外,在系统没有足够的资源的情况下尝试申请内存可能会失败。结果,在设计好的代码里面,每次调用内存分配函数,比如kmalloc(),vmalloc()或__get_free_pages()都伴随着一些小心设计的错误处理代码。但是,内存管理系统的实际实现可能跟上面说的不一样。这种不同可能导致一些不幸的运行时行为,但是修改它可能使情况变得更糟。
         关于这个问题的讨论出现在Tetsuo Handa发表了一个关于如何解决某个出现的特定问题的疑问。事情的大体过程是这样的:

    1. 一个当前占用相对来说比较少内存的进程引用了一个XFS文件系统操作,这个操作需要申请内存。
    2. 内存管理子系统尝试满足内存申请的需求,但是发现没有足够的内存。然后内存子系统首先尝试直接重声明(direct reclaim)内存(强制一部分页换出内存),如果还没有释放足够需要的内存,则继续调用OOM killer。
    3. OOM killer挑选进程并杀死它。
    4. 被杀死的进程在退出的时候必须执行一些在同一个XFS文件系统上的操作。这些操作需要获取一些锁,而这些锁正被引起这个有问题的内存分配的进程(1中的进程)所占用。然后,系统死锁。

         换句话说,申请内存的进程不能进行下去是因为它正等待分配调用返回。这个调用需要有足够的空闲内存才会返回,而空闲内存需要通过其他进程退出才能释放。同时,OOM killer也在等待这个进程退出才能继续(可能)选择其他进程退出。但是这个进程不能退出,因为它需要一些被申请内存的进程占用的锁。系统锁住了,然后系统的所有者开始认真的考虑切换到某个版本的BSD系统了。

         当被问到这个问题时,XFS maintainer Dave Chinner很快回复说为什么内存管理代码会调用OOM killer而不是返回内存分配失败。他说XFS的代码能很好的处理内存分配失败。对他来说,返回内存分配失败可能比随机杀死一个进程并可能锁住系统更好。然后,内存管理系统的maintainer说:

    “(这是因为)有一个不成文的规定:low-order(<=PAGE_ALLOC_COSTLY_ORDER)的GFP_KERNEL内存分配不会失败。这是一个很久以前的决定,现在修改它会很困难。”

    Dave怀疑的回复说:

    “我们总是被告知内存分配不保证成功,曾经,除非__GFP_NOFAIL标志被设置,但是这已经过时了,没人再被允许使用它了。
    很多代码依赖内存分配来继续运行或在低内存的情况下的系统上失败。页缓存就是其中之一,这也就意味这所有的文件系统也存在这种依赖。我们不是显式的要求内存分配失败,我们只是希望内存分配失败出现在低内存的情况下。我们在过去的15年里一直这样设计和coding的。”

         “too small to fail”分配对大多数内核来说是指少于或等于八个连续页的分配,这(八个页)相对比较大。没人确切的知道这条规则是何时进入内核的,它在Git之前就被引入了。Johannes Weiner解释说,当时的想法是这样的,如果这么小的内存分配都不能被满足,那么系统将变得不可用;也不存在除了调用OOM killer之外其他可行的替代方法。这可能是正确的,但是锁住一个内核正在处理内存分配错误的系统也会导致系统不可用。

         讨论中提到的一个方法是给特定的申请增加__GFP_NORETRY标志。这个标志会导致在资源不足的情况下即使小的内存分配申请也会失败。但是,就像Dave提到的,用__GFP_NORETRY去修复潜在的死锁请求就是一个“打狗熊”的游戏,总会有更多的熊熊,并且他们会取得最终的胜利。
          最终的方法可能就是移除“too small to fail”规则并让内存分配函数按照大多数内核开发者想的那样去运行。Johannes的信息里包含了一个采用这种方法的patch:如果直接重声明没有释放任何内存,它就会让无休止的重声明循环退出(并给内存分配申请返回失败)。但是,就像他提到的,经过如此长时间的尝试后order-0分配都不能满足,这太可怕了。
         说它可怕是有两个原因,一个是不是所有的内核开发者都认真检查每次的内存分配并考虑合适的恢复路径。但更糟的是,因为小内存分配不会失败,内核中几乎所有的上千的的错误恢复路径从来没有执行过。开发者可以使用内核中的“fault injection”框架来测试这些代码,但是事实上,几乎没有开发者会这样做。所以这些错误恢复路径代码不仅仅是未被使用到,而且相当大一部分在第一时间都没被测试过。
         如果这个不成文的“too small to fail”规则被移除了,所有的这些错误恢复代码就会第一次被执行。某种程度上,内核将会增加上千行未经测试的并且只会在罕见的系统已出错的情况下运行的代码。毫无疑问,这将会导致很多bug和潜在的安全问题。
          这让内存管理开发者陷入两难的境地。让内存分配代码按照建议的那样运行会给内核引入很多难以调试的问题。但是,“too small to fail”也有它自己的缺点;同时,越来越复杂的内核锁住可能让情况变得更糟;它也会浪费相当长的开发时间来开发从不会执行的错误恢复代码。即使如此,在如此晚的时间点上引入low-order内存分配失败可能太可怕了,即使长期来看可能让内核变得更好。

    Some ado about zero

    http://lwn.net/Articles/340370/

         计算机使用很多零(很多使用零的场合)。在编程事业的早期,我工作在一台有一个特殊的包含零的硬件寄存器的机器;这台机器上的程序员知道他们可以使用他们需要的零而不用担心系统跑飞。同时,在上个世纪,linux内核提供了一个充满零的页。在X86架构上它叫做empty_zero_page,它甚至能被模块使用。有趣的是,这个特殊的页不像在2.6.24之前的内核那样被广泛使用了,但是这种情况可能将要改变。
         在一切运行良好的以前,当内核需要一个充满零的页时,它会使用这个零页。所以,比如,如果一个进程在一个它从未使用过的页上发生了读错误,内核就会简单的映射这个零页到那个地址。当然,写时复制也会用到,如果进程接下来要修改这个页,它就会得到它自己的一份复制。但是推迟新的零页的创建有助于保存零,并防止内核跑飞(溢出?run out)。同时,它也能节省内存,减少缓存压力,消除清除页的需求。
         2007年的内存管理改动增加了对零页的引用计数功能。在多处理器机器上这会有问题。由于所有的处理器共享同一个零页(per-CPU差异是不可能的),它们也都管理相同的引用计数。这会导致缓存行波动,显著的性能影响等严重的问题。作为回应,Nick Piggin评估了很多可能的修复,包括避免对零页的引用计数的特殊技巧或增加per-CPU零页。但合并的补丁却只是简单的清除了大多数对零页的使用。这种修改是这样解释的:

    为匿名读错误映射零页是一个错误的优化:如果一个应用是性能相关的,它不会在新内存上发生太多的读错误,或者至少它很快就会向那块内存写入数据。如果是缓存或者内存使用相关的应用,那么它不会使用到大量的零页(应该使用一个更紧凑的零的表示)。

         当时,对于这个补丁存在一些担心。Linus首先抱怨了导致问题的改变并担心:

    内核一直(差不多从第一天开始)存在零页。这就意味着如果有些应用依赖于它,那么我一点也不奇怪。我就曾经写过依赖于它的测试程序,也许人们也写过其他代码,而这些代码必须工作在一个提供了只读零页的内核上。

         尽管有担忧,Linus还是在2.6.24版内核合并了这个补丁来看看会不会出现问题。接下来的18个月都没出现问题,大部分人都把零页忘了。但是在6月的早些时候,Julian Phillips报告了一个他发现的问题:

    我有一个创建了大量私有匿名映射的程序。这个程序只会向一部分映射写入数据,但是会从所有的映射中读取数据。
    当我在一个2.6.20.7的系统上跑这个程序时,进程好像有足够的内存来保存实际写入的数据(以PAGE_SIZE大小为单位)。当我在一个2.6.24.5的系统上跑它时,它使用的内存的数量一直增长直到整个映射被实际分配了内存(总大小大于物理内存导致了swapping)。我好像看到了读时复制而不是写时复制。

          当然,Julian看到的就是零页被移除后的影响。在老的内核里,数据结构中所有未写入的页都会被映射到零页,一点都不使用额外的内存。而2.6.24内核,所有的这些页每个都有一个只包含零的物理页,这大大增加了内存使用量。

          Hiroyuki Kamezawa报告说他在其他地方也看到了依赖零页的应用。他说在这些地方运行的企业级linux发行版,这些发行版还没有使用新的缺乏零页支持的内核。他担心这些用户升级到新的内核后会遇到Julian碰到的问题。作为回应,他发布了一个给内核恢复零页支持的补丁。
         但是Hiroyuki的零页支持跟以前的不太一样。它避免了零页的引用计数,这个改变会消除最坏的性能问题。但它也增加了一些有趣的特殊case,虚拟内存代码需要为零页小心的测试这些cases;大部分cases可以用新增加的get_user_pages_nonzero()函数处理,这个函数删除指定范围内的任何零页。Linus不喜欢这些特殊的cases,认为它们没必要。作为替代,Linus建议使用相对较新的PTE_SPECIAL标志来标示零页的可选实现。截止到本文发表,使用这种方法更新的补丁还没发布。
         首先写出移除零页支持补丁的Nick Piggin,不想看到内核恢复零页支持。考虑到被影响的用户,他问:

    我们能不能只是让他们别用它(零页)了?为罕见的场景使用零页可能不好,因为那仍会触发错误而且还占用TLB空间。使用更好的算法他们可能会看到更好的性能。

         但是如果恢复零页支持代价不大的话,Linus还是很乐意的。所以如果补丁工作的很好,零页支持回归的可能性很大。但是否会给企业级linux用户带来福音还有待观察,下一代的企业级linux可能会使用2.6.27内核。除非发行版厂商向后移植零页补丁,否则企业级linux用户仍会遇到目前的零页问题。

    Adding a huge zero page

    http://lwn.net/Articles/517465/

         THP(透明大页)特性允许应用程序使用目前大多数处理器支持的更大的页而不需要管理员、开发者或用户显式的配置。总体来说这是一个提升性能的特性:大页减少了系统TLB的压力,让内存访问更快。因为删除了一层页表,它也节省了一些内存。但是,在某些条件下,THP也确实显著增加了应用程序的内存使用量。好消息是目前有个解决方法:充满零的页。
         THP主要用在匿名页--不是为磁盘文件做备份(缓存)的页,它们构成了进程的数据域。当一个匿名内存域被创建或扩展时,其实并没有分配物理页(不管THP是否被使能)。这是因为一个典型的程序的部分地址空间中的很多页从来不会被访问到,在被证明确实需要之前分配页会浪费相当多的时间和内存。所以内核会等到进程试图访问某个特定的页而产生页错误的时候才会为这个页分配内存。
         但是即使这样,仍然可以做一个优化。新的匿名页必须被零填满,在以前使用者留下了的任何数据的页上做任何其他操作都是风险的。程序经常依赖于它们使用的内存的初始化;由于它们知道内存一开始是填满零的,所以它们没有必要自己初始化内存。这些页很多从来就不会被写入,它们在拥有它们的进程的整个生存期间都保持零。一旦意识到这个,我们就不难想到可以通过共享这些零页来节省很多内存。一个零页很像另外一个,所以没必要创建太多。
         所以,如果一个进程通过读取实例化了一个新的页(非大页),内核仍然不会分配一个新的内存页,而是映射到一个特殊页,简称零页,到进程的地址空间。所以,系统上所有进程的所有的未写入的匿名页,实际上都共享了一个特殊的页。不用说,零页是只读的,进程不允许修改它的值。当一个进程想要写入零页时,会产生一个写保护错误,内核然后(最终)会分配一个真实的物理页并把它替换到进程地址空间的合适位置。
         这种行为很容易观察到。正如Kirill Shutemov描述的,一个执行如下代码的进程:

        posix_memalign((void **)&p, 2 * MB, 200 * MB);
        for (i = 0; i < 200 * MB; i+= 4096)
            assert(p[i] == 0);
        pause();

    运行到pause()时,只占用了很少的内存。它申请了200MB内存,但这部分内存都用一个零页代表了。系统照常工作。

         但当THP特性被使能后,这个进程会显示它分配了200MB的内存。当用户使能一项性能提升特性时,两个数量级的内存使用量增长可不是它们希望看到的。所以,Kirill说,有些时候用户不得不把THP关掉。
         问题很简单:没有大零页。THP在任何可能的情况下使用大页;当一个进程在一个新页上发生错误时,内核会尝试给它提供一个大页。由于没有大零页,内核只会简单的申请一个真实的零页(大页)作为替代。这种行为可以让进程继续执行下去,但它也引起了大量不必要的内存分配。THP,换句话说,关掉了在内核内存管理子系统存在了很多年的另一个重要优化。
         一旦问题搞明白了,解决方法也就不难了。Kirill的补丁增加了一个特殊的零填充的大页被用作大零页。只需要一个这样的页,因为THP特性只用到一个大页大小。为读错误使用这个页,内存占用扩增的问题很容易就解决了。
         像往常一样,有抱怨说:这个页好大,如果在THP不用的时候就不分配它就好了。所以,出现了一个懒分配方案,Kirill也增加了一个引用计数,在大零页不需要的时候会被回收。这个引用计数减慢了读错误基准测试1%,所以它值不值得还有待观察。最终,开发者得出结论:大零页一旦被分配就放在那儿比为引用计数付出代价会更好。毕竟,零页出现过类似的问题。
         还没有针对这个补丁的大量评论,实现也相对比较顺利。大零页的增加带来了明显的大量益处,在不久的将来就会被加入到内核,3.8版看起来就不错。

     注:(原文评论)

    Q:

      Why not just keep the small zero page mapping in this case and switch to a huge (no-zero) page on the first write?

    A:

         Because the whole point is to save a layer of page translations. You can't, when you do get a page fault, go back a layer and check whether all of the other pages referenced by that layer all point to the (small) zero page.
         That would kill more performance than you gain by huge pages in the first plale.

    Transparent huge pages in 2.6.38

     http://lwn.net/Articles/423584/

         当前大多数的处理器的MMU可以处理多种大小的页,但是linux内核只使用其中最小的--大部分架构是4096字节。比4KB大的页统称为大页--可以给某些负载提供更好的性能,但是这种性能提升在linux上未被充分利用。不过随着THP特性的合入2.6.38内核,情况开始发生改变。
         大页可以通过减少页错误(一次错误一次性引入大量内存,也就是一次错误会映射比以前更多的内存从而减少页错误)和减少虚拟地址到物理地址转换的代价(为获得物理地址而需要跨越的页表的等级更少)来提升性能。但真正的优势来自完全避免了转换。如果处理器必须转换一个虚拟地址,它就必须遍历多达四级的页表,每一级都可能是缓存冷(cache-cold)的,也就很慢了。为了这个原因,处理器实现了TLB来缓存转换的结果。TLB一般很小,在我老的桌面机器上运行cpuid会显示:

       cache and TLB information (2):
          0xb1: instruction TLB: 2M/4M, 4-way, 4/8 entries
          0xb0: instruction TLB: 4K, 4-way, 128 entries
          0x05: data TLB: 4M pages, 4-way, 32 entries

    一共有128个指令转换条目和32个数据转换条目。这么小的缓存很容易用光,强迫CPU去执行大量的地址转换。一个2MB的大页需要一个TLB条目,以4KB为单位的相同大小的内存则需要512个TLB条目。这样,使用大页可以让程序跑的更快也就没什么可奇怪的了。

         主要的内核地址空间是用大页映射的,降低了内核代码的TLB压力。但是在目前的内核上,用户空间使用大页的唯一方法是通过2010年就存在的hugetlbfs。使用hugetlbfs需要来自应用开发者和系统管理员的大量工作,大页必须在启动的时候创建(这可能是不正确的,原文评论也提到了:“On systems that support them, 1GB and 16GB pages must be reserved at boot, but 2MB, 4MB, and 16MB pages can be allocated any time there is contiguous space.”。同时在CentOS 6.5系统上也看到了可在系统启动后开启hugetlbfs,见下文),应用程序也需要显式的映射它们。那些为使用hugetlbfs而精巧设计的进程只限于那些确实关心和有足够时间与hugetlbfs磨合的进程。
          当前的linux内核假定在一个给定的VMA中所有的页都是相同大小的。为了让THP工作,Andrea不得不放弃了这一假定,所以,大部分补丁都是为了在VMA中兼容混合页大小。然后补丁用一种简单的方法修改了页错误处理函数:当一个错误发生时,内核尝试分配一个大页去满足它。如果分配成功了,大页就会被填充(根据已存在的小页的内容填充,其他地方填充零),在这个新页的地址范围内的任何已存在的小页就会被释放,然后大页被插入VMA。如果没有可用的大页,内核就用小页,应用程序不会发觉这种不同。
          这种方案会透明的增加大页的使用,但是它还没有解决整个问题。大页必须是能被交换的,以免系统很快耗尽内存。不是抱怨交换代码不支持大页,Andrea只是简单的在页需要被重声明的时候把它拆分成小页。许多其他的操作(mprotect(), mlock(), ...)也会导致大页的拆分。
         大页的分配依赖于大的物理上连续的内存的可用性--这是内核开发者从没关心过的。这些物理上连续的内存只有在一些特殊的情况下才会存在--比如一个进程在很多小页上发生错误后。THP补丁想通过增加一个khugepaged内核线程来改善这种情况。这个线程会不定期的分配大页,如果成功了,它就扫描内存看看什么地方能用这个大页来替换小页。所以,可用的大页会很快投入使用,最大化系统上大页的使用。
         当前的补丁只能工作在匿名页上,把大页集成到页缓存的工作尚未完成。它也只能处理一种大页(2MB)。即使如此,我们还是看到了一些有用的性能提升。Mel Gorman在某些场景下的基准测试能提升高达10%左右。一般来说,性能提升尚比不上hugetlbfs,但是THP更有可能被实际使用。
         为使用THP,应用程序不需要做任何修改,但感兴趣的应用开发者可以试着为了THP做一些优化。一个带MADV_HUGEPAGE标志的madvise()调用会标示一个内存区域适合使用大页,而MADV_NOHUGEPAGE标志则表明大页最好用在其他地方(不适宜用在此处)。对想使用大页的应用程序来说,使用posix_memalign()能帮助确保大块分配是大页边界对齐的。
         系统管理员有很多可以调整的选项,它们都在/sys/kernel/mm/transparent_hugepage下面。enabled可以被设置成“always”(总是使用THP),“madvise”(只在MADV_HUGEPAGE标志的VMA中使用THP)或“never”(关闭THP)。另一个选项“defrag”,也使用相同的可选值,它控制着内核是否应该采取积极的内存压缩来使更多的大页可用。也有一些控制khugepaged线程的参数,详见Documentation/vm/transhuge.txt。
         自从合入主线后,THP补丁一路坎坷。这部分代码没有在linux-next分支上出现过,所以当它引起主线内核编译错误时惊到了一些架构maintainers。一些bug也被发现了--对于影响了如此多核心代码的大补丁来说一点都不奇怪。这些问题都被标了出来,所以,尽管2.6.38-rc1的测试者要小心,不过到2.6.38最终发布的时候THP应该会好很多。

     http://developerblog.redhat.com/2014/03/10/examining-huge-pages-or-transparent-huge-pages-performance/

    There are two mechanisms available for huge pages in Linux: the hugepages and Transparent Huge Pages (THP). Explicit configuration is required for the original hugepages mechanism. The newer transparent hugepage (THP) mechanism will automatically use larger pages for dynamically allocated memory in Red Hat Enterprise Linux 6.

    To determine whether the newer Transparent HugePages (THP) or the older HugePages mechanism are being used, look at the output of /proc/meminfo as below:

    $ cat /proc/meminfo|grep Huge
    AnonHugePages:   3049472 kB
    HugePages_Total:       0
    HugePages_Free:        0
    HugePages_Rsvd:        0
    HugePages_Surp:        0
    Hugepagesize:       2048 kB

    The AnonHugePages entry lists the number of pages that the newer Transparent Huge Page mechanism currently has in use. For this machine there are 309472kB, 1489 huge pages each 2048kB in size.

    In this case there are zero pages in the pool of the older hugepage mechanism as shown by HugePages_Total of 0. The HugePages_Free shows how many pages are still available for allocation, which is going to be less than or equal to HugePages_Total. The number of HugePages in use can be computed as HugePages_Total-HugePagesFree. For more information about the configuration of HugePages see Tuning and Optimizing Red Hat Enterprise Linux for Oracle 9i and 10g Databases.

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

  • 相关阅读:
    SparkSQL访问Hive源,MySQL源
    SparkStreaming算子操作,Output操作
    JVM 配置常用参数和常用 GC 调优策略
    SparkStreaming与Kafka,SparkStreaming接收Kafka数据的两种方式
    consul service
    Centos7 vnc
    Centos7 創建快捷方式
    Consul Session
    python consul
    python 形参
  • 原文地址:https://www.cnblogs.com/baiyw/p/4259736.html
Copyright © 2011-2022 走看看