简介
最近有位 VPS 客户抱怨 MySQL 无缘无故挂掉,还有位客户抱怨 VPS 经常死机,登陆到终端看了一下,都是常见的 Out of memory 问题。这通常是因为某时刻应用程序大量请求内存导致系统内存不足造成的,这通常会触发Linux 内核里的 Out of Memory (OOM) killer,OOM killer 会杀掉某个进程以腾出内存留给系统用,不致于让系统立刻崩溃。如果检查相关的日志文件(/var/log/messages)就会看到下面类似的 Out of memory: Kill process 信息:
... Out of memory: Kill process 9682 (mysqld) score 9 or sacrifice child Killed process 9682, UID 27, (mysqld) total-vm:47388kB, anon-rss:3744kB, file-rss:80kB httpd invoked oom-killer: gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0 httpd cpuset=/ mems_allowed=0 Pid: 8911, comm: httpd Not tainted 2.6.32-279.1.1.el6.i686 #1 ... 21556 total pagecache pages 21049 pages in swap cache Swap cache stats: add 12819103, delete 12798054, find 3188096/4634617 Free swap = 0kB Total swap = 524280kB 131071 pages RAM 0 pages HighMem 3673 pages reserved 67960 pages shared 124940 pages non-shared
Linux 内核根据应用程序的要求分配内存,通常来说应用程序分配了内存但是并没有实际全部使用,为了提高性能,这部分没用的内存可以留作它用,这部分内存是属于每 个进程的,内核直接回收利用的话比较麻烦,所以内核采用一种过度分配内存(over-commit memory)的办法来间接利用这部分 “空闲” 的内存,提高整体内存的使用效率。一般来说这样做没有问题,但当大多数应用程序都消耗完自己的内存的时候麻烦就来了,因为这些应用程序的内存需求加起来超 出了物理内存(包括 swap)的容量,内核(OOM killer)必须杀掉一些进程才能腾出空间保障系统正常运行。用银行的例子来讲可能更容易懂一些,部分人取钱的时候银行不怕,银行有足够的存款应付,当 全国人民(或者绝大多数)都取钱而且每个人都想把自己钱取完的时候银行的麻烦就来了,银行实际上是没有这么多钱给大家取的。
内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码 linux/mm/oom_kill.c, 当系统内存不足的时候,out_of_memory() 被触发,然后调用 select_bad_process() 选择一个 “bad” 进程杀掉,如何判断和选择一个 “bad” 进程呢,总不能随机选吧?挑选的过程由 oom_badness() 决定,挑选的算法和想法都很简单很朴实:最 bad 的那个进程就是那个最占用内存的进程。
/** * oom_badness - heuristic function to determine which candidate task to kill * @p: task struct of which task we should calculate * @totalpages: total present RAM allowed for page allocation * * The heuristic for determining which task to kill is made to be as simple and * predictable as possible. The goal is to return the highest value for the * task consuming the most memory to avoid subsequent oom failures. */ unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg, const nodemask_t *nodemask, unsigned long totalpages) { long points; long adj; if (oom_unkillable_task(p, memcg, nodemask)) return 0; p = find_lock_task_mm(p); if (!p) return 0; adj = (long)p->signal->oom_score_adj; if (adj == OOM_SCORE_ADJ_MIN) { task_unlock(p); return 0; } /* * The baseline for the badness score is the proportion of RAM that each * task's rss, pagetable and swap space use. */ points = get_mm_rss(p->mm) + p->mm->nr_ptes + get_mm_counter(p->mm, MM_SWAPENTS); task_unlock(p); /* * Root processes get 3% bonus, just like the __vm_enough_memory() * implementation used by LSMs. */ if (has_capability_noaudit(p, CAP_SYS_ADMIN)) adj -= 30; /* Normalize to oom_score_adj units */ adj *= totalpages / 1000; points += adj; /* * Never return 0 for an eligible task regardless of the root bonus and * oom_score_adj (oom_score_adj can't be OOM_SCORE_ADJ_MIN here). */ return points > 0 ? points : 1; }
理解了这个算法我们就理解了为啥 MySQL 躺着也能中枪了,因为它的体积总是最大(一般来说它在系统上占用内存最多),所以如果 Out of Memeory (OOM) 的话总是不幸第一个被 kill 掉。解决这个问题最简单的办法就是增加内存,或者想办法优化 MySQL 使其占用更少的内存,除了优化 MySQL 外还可以优化系统,让系统尽可能使用少的内存以便应用程序(如 MySQL) 能使用更多的内存,还有一个临时的办法就是调整内核参数,让 MySQL 进程不容易被 OOM killer 发现。
配置 OOM killer
OOM Killer 的关闭与激活方式:
# echo "0" > /proc/sys/vm/oom-kill # echo "1" > /proc/sys/vm/oom-kill
我们可以通过一些内核参数来调整 OOM killer 的行为,避免系统在那里不停的杀进程。比如我们可以在触发 OOM 后立刻触发 kernel panic,kernel panic 10秒后自动重启系统。
# sysctl -w vm.panic_on_oom=1 vm.panic_on_oom = 1 # sysctl -w kernel.panic=10 kernel.panic = 10 # echo "vm.panic_on_oom=1" >> /etc/sysctl.conf # echo "kernel.panic=10" >> /etc/sysctl.conf
从上面的 oom_kill.c 代码里可以看到 oom_badness() 给每个进程打分,根据 points 的高低来决定杀哪个进程,这个 points 可以根据 adj 调节,root 权限的进程通常被认为很重要,不应该被轻易杀掉,所以打分的时候可以得到 3% 的优惠(adj -= 30; 分数越低越不容易被杀掉)。我们可以在用户空间通过操作每个进程的 oom_adj 内核参数来决定哪些进程不这么容易被 OOM killer 选中杀掉。比如,如果不想 MySQL 进程被轻易杀掉的话可以找到 MySQL 运行的进程号后,调整 oom_score_adj 为 -15(注意 points 越小越不容易被杀):
# ps aux | grep mysqld mysql 2196 1.6 2.1 623800 44876 ? Ssl 09:42 0:00 /usr/sbin/mysqld # cat /proc/2196/oom_score_adj 0 # echo -15 > /proc/2196/oom_score_adj
当然,如果需要的话可以完全关闭 OOM killer(不推荐用在生产环境):
# sysctl -w vm.overcommit_memory=2 # echo "vm.overcommit_memory=2" >>
OOM详解
Linux下允许程序申请比系统可用内存更多的内存,这个特性叫Overcommit。这样做是出于优化系统考虑,因为不是所有的程序申请了内存就立刻使用的,当你使用的时候说不定系统已经回收了一些资源了。不幸的是,当你用到这个Overcommit给你的内存的时候,系统还没有资源的话,OOM killer就跳出来了。
Linux下有3种Overcommit的策略(参考内核文档:vm/overcommit-accounting),可以在/proc/sys/vm/overcommit_memory配置。取0,1和2三个值,默认是0。
0:启发式策略,比较严重的Overcommit将不能得逞,比如你突然申请了128TB的内存。而轻微的Overcommit将被允许。另外,root能Overcommit的值比普通用户要稍微多些。
1:永远允许Overcommit,这种策略适合那些不能承受内存分配失败的应用,比如某些科学计算应用。
2:永远禁止Overcommit,在这个情况下,系统所能分配的内存不会超过swap+RAM*系数(/proc/sys/vm/overcmmit_ratio,默认50%,你可以调整),如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序。
实际上启发策略只有在启用了SMACK或者SELinux模块时才会起作用,其他情况下等于永远允许策略。
选择进程的策略
好了,只要存在Overcommit,就可能会有OOM killer跳出来。那么OOM killer跳出来之后选目标的策略又是什么呢?我们期望的是:没用的且耗内存多的程序被枪。
Linux下这个选择策略也一直在不断的演化。作为用户,我们可以通过设置一些值来影响OOM killer做出决策。Linux下每个进程都有个OOM权重,在/proc/<pid>/oom_adj里面,取值是-17到+15,取值越高,越容易被干掉。
最终OOM killer是通过/proc/<pid>/oom_score这个值来决定哪个进程被干掉的。这个值是系统综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime - start time)和oom_adj计算出的,消耗内存越多分越高,存活时间越长分越低。
Linux在计算进程的内存消耗的时候,会将子进程所耗内存的一半同时算到父进程中。
------------------------------------------------------------------------------
在 32 位CPU 架构下寻址是有限制的。Linux 内核定义了三个区域:
--------------------------------------------------------------------------------
# DMA: 0x00000000 - 0x00999999 (0 - 16 MB)
# LowMem: 0x01000000 - 0x037999999 (16 - 896 MB) - size: 880MB
# HighMem: 0x038000000 - <硬件特定>
---------------------------------------------------------------------------------------
LowMem 区 (也叫 NORMAL ZONE ) 一共 880 MB,而且不能改变(除非用 hugemem 内核)。对于高负载的系统,就可能因为 LowMem 利用不好而引发 OOM Killer 。一个可能原因是 LowFree 太少了,另外一个原因是 LowMem 里都是碎片,请求不到连续的内存区域
对于某个用户页面的请求可以首先从“普通”区域中来满足(ZONE_NORMAL);
如果失败,就从ZONE_HIGHMEM开始尝试;
如果这也失败了,就从ZONE_DMA开始尝试。
这种分配的区域列表依次包括ZONE_NORMAL、ZONE_HIGHMEM和ZONE_DMA区域。
另一方面,对于 DMA 页的请求可能只能从 DMA 区域中得到满足,因此这种请求的区域列表就只包含 DMA 区域
检查当前 low&hight 内存 的值
# egrep 'High|Low' /proc/meminfo
或者
# free -lm
检查LowMem内存碎片
# cat /proc/buddyinfo
Node 0, zone DMA 24 25 13 639 155 0 1 0 1 1 0
内存物理上使用基本没有所谓的碎片问题.读写内存上任何一个字节的速度都是固定的。不存在硬盘的机械寻道慢的问题。
buddy system分配内存的时候并不能保证物理地址连续的. 尤其是非2的整数次冥的页面数量的时候.需要mmu将这些物理地址不连续的内存块在线性空间上拼接起来.
node0 就是有几个内存结点 如果不是NUMA那就是只有一个了
zone 内存区域
DMA 内存开始的16 MB
HighMem 所有大于4G的内存 ,
Normal 介于两者之间的内存
后面11个数字是以4,8,16,32,64,128,256,512,1024,2048,4096kb为单位的所有可分配内存
24*4+25*8+13*16+639*32+155*64+0*128+1*256+0*512+1*1024+1*2048+0*4096(kb)就是可用的内存(可以连续分配的内存地址空间)当low 内存不足时,不管high 内存有多大,oom-killer都会开始结束一些进程保持系统运行。oom的一个原因就是虽然看着有内存,但是无法分配,所以Kill占用内存最大的程序。那都是进程级的,有问题自己解决,用内存池也行。系统都是大块分配给进程并回收,都是整页的,不管多离散,在虚存空间都能映射成连续的,所以没有碎片问题。
检查内存泄漏的工具
# valgrind
OOM Killer 在2.6 是杀掉占用内存最厉害的进程(很容易导致系统应用瘫痪)
2.6新增了一个参数: vm.lower_zone_protection 。这个参数默认的单位为 MB,默认 LowMem 为 16MB。建议设置 vm.lower_zone_protection = 200 甚至更大以避免 LowMem 区域的碎片,是绝对能解决这个问题的(这参数就是解决这个问题出来的)。
OOM Killer 的关闭与激活方式:
# echo "0" > /proc/sys/vm/oom-kill
# echo "1" > /proc/sys/vm/oom-kill
启动时生效:
# vi /etc/sysctl.conf:
vm.oom-kill = 0
vm.lower_zone_protection = 300
(为low memory 保护性分配300M)
内核内存管理
Linux采用“按需调页”算法,支持三层页式存储管理策略。将每个用户进程4GB长度的虚拟内存划分成固定大小的页面。其中0至3GB是用户态空间,由各进程独占;3GB到4GB是内核态空间,由所有进程共享,但只有内核态进程才能访问。
Linux将物理内存也划分成固定大小的页面,由数据结构page管理,有多少页面就有多少page结构,它们又作为元素组成一个数组mem_map[]。
linux低层采用三层结构,实际使用中可以方便映射到两层或者三层结构,以适用不同的硬件结构。最下层的申请内存函数get_free_page。之上有三种类型的内存分配函数
(1)kmalloc类型。内核进程使用,基于slab技术,用于管理小于内存页的内存申请。思想出发点和应用层面的内存缓冲池同出一辙。但它针对内核结构,特别处理,应用场景固定,不考虑释放。不再深入探讨。
(2)vmalloc类型。内核进程使用。用于申请不连续内存。
(3)brk/mmap类型。用户进程使用。malloc/free实现的基础。
malloc系统的内存管理策略
malloc系统有自己的内存池管理策略,malloc的时候,检测池中是否有足够内存,有则直接分配,无则从内存中调用brk/mmap函数分配,一般小于等于128k(可设置)的内存,使用brk函数,此时堆向上(有人有的硬件或系统向下)增长,大于128k的内存使用mmap函数申请,此时堆的位置任意,无固定增长方向。free的时候,检测标记是否是mmap申请,是则调用unmmap归还给操作系统,非则检测堆顶是否有大于128k的空间,有则通过brk归还给操作系统,无则标记未使用,仍在glibc的管理下。glibc为申请的内存存储多余的结构用于管理,因此即使是malloc(0),也会申请出内存(一般16字节,依赖于malloc的实现方式),在应用程序层面,malloc(0)申请出的内存大小是0,因为malloc返回的时候在实际的内存地址上加了16个字节偏移,而c99标准则规定malloc(0)的返回行为未定义。除了内存块头域,malloc系统还有红黑树结构保存内存块信息,不同的实现又有不同的分配策略。频繁直接调用malloc,会增加内存碎片,增加和内核态交互的可能性,降低系统性能。linux下的glibc多为Doug Lea实现
slab:在操作系统的运作过程中,经常会涉及到大量对象的重复生成、使用和释放问题。对象生成算法的改进,可以在很大程度上提高整个系统的性能。在Linux系统中所用到的对象,比较典型的例子是inode、task_struct等,都又这些特点。一般说来,这类对象的种类相对稳定,每种对象的数量却是巨大的,并且在初始化与析构时要做大量的工作,所占用的时间远远超过内存分配的时间。但是这些对象往往具有这样一个性质,即他们在生成时,所包括的成员属性值一般都赋成确定的数值,并且在使用完毕,释放结构前,这些属性又恢复为未使用前的状态。因此,如果我们能够用合适的方法使得在对象前后两次背使用时,在同一块内存,或同一类内存空间,且保留了基本的数据结构,就可以大大提高效率。slab算法就是针对上述特点设计的。
slab算法思路中最基本的一点被称为object-caching,即对象缓存。其核心做法就是保留对象初始化状态的不变部分,这样对象就用不着在每次使用时重新初始化(构造)及破坏(析构)。
面向对象的slab分配中有如下几个术语:
l 缓冲区(cache):一种对象的所有实例都存在同一个缓存区中。不同的对象,即使大小相同,也放在不同的缓存区内。每个缓存区有若干个slab,按照满,半满,空的顺序排列。在slab分配的思想中,整个内核态内存块可以看作是按照这种缓存区来组织的,对每一种对象使用一种缓存区,缓存区的管理者记录了这个缓存区中对象的大小,性质,每个slab块中对象的个数以及每个slab块大小。
l slab块:slab块是内核内存分配与页面级分配的接口。每个slab块的大小都是页面大小的整数倍,有若干个对象组成。slab块共分为三类:
完全块:没有空闲对象。
部分块:只分配了部分对象空间,仍有空闲对象。
空闲块:没有分配对象,即整个块内对象空间均可分配。
在申请新的对象空间时,如果缓冲区中存在部分块,那么首先查看部分块寻找空闲对象空间,若未成功再查看空闲块,如果还未成功则为这个对象分配一块新的slab块。
l 对象:将被申请的空间视为对象,使用构造函数初始化对象,然后由用户使用对象。
Linux下Glibc的内存管理机制大致如下:
从操作系统的角度看,进程的内存分配由两个系统调用完成:brk和mmap。brk是将数据段(.data)的最高地址指针_edata往高地址推,mmap是在进程的虚拟地址空间中找一块空闲的。其中,mmap分配的内存由munmap释放,内存释放时将立即归还操作系统;而brk分配的内存需要等到高地址内存释放以后才能释放。也就是说,如果先后通过brk申请了A和B两块内存,在B释放之前,A是不可能释放的,仍然被进程占用,通过TOP查看疑似”内存泄露”。默认情况下,大于等于128KB的内存分配会调用mmap/mummap,小于128KB的内存请求调用sbrk(可以通过设置M_MMAP_THRESHOLD来调整)。
Glibc的新特性:M_MMAP_THRESHOLD可以动态调整。M_MMAP_THRESHOLD的值在128KB到32MB(32位机)或者64MB(64位机)之间动态调整,每次申请并释放一个大小为2MB的内存后,M_MMAP_THRESHOLD的值被调整为2M到2M + 4K之间的一个值
M_MMAP_THRESHOLD的值动态调整,后续的2MB的内存申请通过sbrk实现,而sbrk需要等到高地址内存释放以后低地址内存才能释放。可以通过显式设置M_MMAP_THRESHOLD或者M_MMAP_MAX来关闭M_MMAP_THRESHOLD动态调整的特性,从而避免上述问题。
当然,mmap调用是会导致进程产生缺页中断的,为了提高性能,常见的做法如下:
1, 将动态内存改为静态,比如采用内存池技术或者启动的时候给每个线程分配一定大小,比如8MB的内存,以后直接使用;
2, 禁止mmap内存调用,禁止Glibc内存缩紧将内存归还系统,Glibc相当于实现了一个内存池功能。
---------------------------------------------------------------------
内存碎片
碎片就是,一个50k的内存,你先吃20k,然后再吃5k,然后释放掉先前吃的那20k,剩下的内存布局就是 {20k 空闲}{5k 占用}{25k 空闲},这样虽然总计有45k空闲,但是malloc(30)都会得不到,因为没有连续的30k空闲内存。
但无论是什么内存碎片整理工具都是无法将那5K往前挪动。
理由是
如果你程序用一个 void *p 指针指向那5K的内存地址,假如用外部的整理工具挪动那5K内存,就需要改动void *p的指向,然而外部的整理工具是无法知道你程序中有哪些指针,或者指针偏移是指向那块内存。
还有一种碎片叫"内碎片"{20k 空闲}{25k 空闲},这种内碎片,这种碎片不必去理会,那是相对于操作系统管理上的碎片,这种碎片对于系统内核是碎片,但对于程序来说不是碎片,程序可以正常使用这样的内存。
解决办法是加载一个内存池,如果程序已经成型,不能往原程序里面加内存池,那还有一个办法
glibc有一个机制,用环境变量LD_PRELOAD可以替换掉一个lib。
堆碎片是由于malloc 和 free函数产生。你就弄一个带内存池的malloc和free,然后LD_PRELOAD替换掉glibc的malloc和free
这样只是把程序封闭起来,防止别的程序对他的碎片干扰。如果程序自身会产生碎片,这方法就无效了。
你还想处理掉自身碎片,那就用CPU陷阱技术
正考虑能不能不用内存池来解决,比如用多进程来处理数据
要处理复杂数据的地方先fork,在子进程里处理,然后通过shm等方式把结果传回来,然后子进程退出,退出时产生的碎片一并清除...这样来看,多进程方式的程序要优于多线程,能够轻松地把内存碎片阿,崩溃阿,推给子进程
linux的确不会出现系统级内存碎片和内存泄漏。
Linux 对每个进程都是大块的提供内存,进程中malloc只在块中分配,不够了可以再要一大块。进程结束时,整块归还。
因此即使进程里有内存泄漏,结束时也都释放了。
linux采用什么分配方式、算法避免内存碎片的?
进程上是分配一个heap区,大约几M,并在PCB中有一个内存链表。
进程malloc是就在heap区进行,如果不够了,用brk系统调用申请新的块加入到进程的heap区,这些块都在PCB资源链表中,进程结束时(不管什么原因),系统都会回收所有资源。
所以OS没有内存就是真没有了,不存在碎片和丢失问题。
------------------------------------------------------------------------------------
SWAP
内存不足,触发oom(out of memory)机制,导致部分服务被迫终止。
kswapd0:是虚拟内存管理中, 负责换页的,如果其它的进程缺少内存可用, 于是kswapd0出来, 执行换页操作。在kswapd中,有2个阀值,pages_hige和pages_low,当空闲内存页的数量低于pages_low的时候,kswapd进程就会扫描内存并且每次释放出32个free pages,直到free page的数量到达pages_high。
kjournald:EXT3文件系统的日志进程,具有3种模式:1.journal---记录所有文件系统上的元数据改变,最慢的一种模式。2.ordered---默认使用的模式,只记录文件系统改变的元数据,并在改变之前记录日志。3.writeback---最快的一种模式,同样只记录修改过的元数据,依赖标准文件系统写进程将数据写到硬盘。
修改ext3工作模式:
# vi /etc/fstab
/dev/hda5 /opt ext3 data=writeback 1 0
列
第1列是需要挂载的文件系统或存储设备,也可以是远程的文件系统。
第2列是挂载点。挂载点,也就是自己找一个或创建一个dir(目录),然后把文件系统<fie sysytem>挂到这个目录上,然后就可以从这个目录中
访问要挂载文件系统。
第3列指定文件系统或分区的类型,下面的文件系统都是目前Linux所能支持的:adfs、befs、cifs、ext3、 ext2、ext、iso9660、kafs、minix、
msdos、vfat、umsdos、proc、reiserfs、swap、 squashfs、nfs、hpfs、ncpfs、ntfs、affs、ufs。
第4列为挂载选项,详细参考man mount. 下面列出一些常用的选项:
auto: 系统自动挂载,fstab默认就是这个选项
ro: read-only
rw: read-write
defaults: rw, suid, dev, exec, auto, nouser, and async.
第5列为dump选项,设置是否让备份程序dump备份文件系统,0为忽略,1为备份。
第6列为fsck选项,告诉fsck程序以什么顺序检查文件系统,如果这里填0,则不检查;挂载点为 / 的(即根分区),必须在这里填写1,其他的都不能填写1。如果有分区填写大于1的话,则在检查完根分区后,接着按填写的数字从小到大依次检查下去。同数字 的同时检查。比如第一和第二个分区填写2,第三和第四个分区填写3,则系统在检查完根分区后,接着同时检查第一和第二个分区,然后再同时检查第三和第四个 分区。
增加SWAP
1、检查当前的分区情况:
#free -m
2、增加交换分区文件及大小,如果要增加2G大小的交换分区,则命令写法如下,其中的 count 等于想要的块大小。
# dd if=/dev/zero of=/home/swap bs=1024 count=2048000
3、设置交换文件:
# mkswap /home/swap
4、立即启用交换分区文件
# swapon /home/swap
5、如果要在引导时自动启用,则编辑 /etc/fstab 文件,添加行:
/home/swap swap swap defaults 0 0
系统下次引导时,它就会启用新建的交换文件,再查看SWAP分区大小发现增加了2G。
---------------------------------------------------------------------------------------
虚拟内存
由于 x86 32 位 CPU ,它使用 32 位数来表示任意惟一的内存地址。这就意味着 x86 体系结构能够表示 2^32个惟一的内存地址(4,294,967,296,或者说4G)。这 4G 地址空间被 Linux 内核分成了物理地址和虚拟地址。 默认的划分是 1:3(1GB 物理,3GB 虚拟)。这就使 Linux 为每个进程提供 3GB 的虚拟地址空间,但它仅允许 1GB 的物理 RAM。
因为操作系统将内存都映射为虚拟内存,所以操作系统的物理内存结构对用户和应用来说通常都是不可见的,应用程序并不分配物理内存,而是向Linux内核请求一部分映射为虚拟内存的内存空间。
但虚拟内存并不一定是映射物理内存中的空间,如果应用程序有一个大容量的请求,也可能会被映射到在磁盘子系统中的swap空间中。
此种方法被称为Swap usage 如下图:
当swap分区使用超过50%时,并不意味着物理内存的使用已经达到瓶颈了,swap空间只是Linux内核更好的使用系统资源的一种方法。
简单理解:Swap usage只表示了Linux管理内存的有效性。对识别内存瓶颈来说,Swap In/Out才是一个比较又意义的依据(vmstat输出),如果Swap In/Out的值长期保持在每秒200到300个页面通常就表示系统可能存在内存的瓶颈
-------------------------------------------------------------------------------------------
参考:
linux有查看和整理内存碎片的工具吗?
http://bbs.chinaunix.net/viewthread.php?tid=1155588&extra=&page=6
OOM killer "Out of Memory: Killed process" SOLUTIONS / SUMMARY
http://www.redaht.com/archives/redhat-list/2007-August/msg00060.html
Linux下OOM Killer机制详解
http://blog.dccmx.com/2011/04/oom-killer-on-linux/
增加swap
http://www.cnblogs.com/ovliverlin/archive/2007/07/23/828671.html
Linux内核内存池管理技术实现分析
http://blog.csdn.net/danforn/article/details/1720791
内存池技术学习笔记
http://www.cppblog.com/bangle/archive/2008/08/26/59915.aspx
GLIBC内存分配机制引发的“内存泄露”
http://www.nosqlnotes.net/archives/105
lowmem_reserve的理解
2.6内核的zone结构中一个成员变量 lowmem_reserve
struct zone
{
/* Fields commonly accessed by the page allocator */
/* zone watermarks, access with *_wmark_pages(zone) macros */
unsigned long watermark[NR_WMARK];
/*
* We don't know if the memory that we're going to allocate will be freeable
* or/and it will be released eventually, so to avoid totally wasting several
* GB of ram we must reserve some of the lower zone memory (otherwise we risk
* to run OOM on the lower zones despite there's tons of freeable ram
* on the higher zones). This array is recalculated at runtime if the
* sysctl_lowmem_reserve_ratio sysctl changes.
*/
unsigned long lowmem_reserve[MAX_NR_ZONES];
kernel在分配内存时,可能会涉及到多个zone,分配会尝试从zonelist第一个zone分配,如果失败就会尝试下一个低级的zone(这里的低级仅仅指zone内存的位置,实际上低地址zone是更稀缺的资源)。我们可以想像应用进程通过内存映射申请Highmem 并且加mlock分配,如果此时Highmem zone无法满足分配,则会尝试从Normal进行分配。这就有一个问题,来自Highmem的请求可能会耗尽Normal zone的内存,而且由于mlock又无法回收,最终的结果就是Normal zone无内存提供给kernel的正常分配,而Highmem有大把的可回收内存无法有效利用。
因此针对这个case,使得Normal zone在碰到来自Highmem的分配请求时,可以通过lowmem_reserve声明:可以使用我的内存,但是必须要保留lowmem_reserve[NORMAL]给我自己使用。
同样当从Normal失败后,会尝试从zonelist中的DMA申请分配,通过lowmem_reserve[DMA],限制来自HIGHMEM和Normal的分配请求。
/*
* results with 256, 32 in the lowmem_reserve sysctl:
* 1G machine -> (16M dma, 800M-16M normal, 1G-800M high)
* 1G machine -> (16M dma, 784M normal, 224M high)
* NORMAL allocation will leave 784M/256 of ram reserved in the ZONE_DMA
* HIGHMEM allocation will leave 224M/32 of ram reserved in ZONE_NORMAL
* HIGHMEM allocation will (224M+784M)/256 of ram reserved in ZONE_DMA
*
* TBD: should special case ZONE_DMA32 machines here - in those we normally
* don't need any ZONE_NORMAL reservation
*/
#ifdef CONFIG_ZONE_DMA
256,
#endif
#ifdef CONFIG_ZONE_DMA32
256,
#endif
#ifdef CONFIG_HIGHMEM
32,
#endif
32,
};
如果不希望低级zone被较高级分配使用,那么可以设置系数为1,得到最大的保护效果
不过这个值的计算非常的奇怪,来自NORMAL的分配,lowmem_reserve[DMA] = normal_size / ratio,这里使用Normal zone size而不是DMA zone size,这点一直没有想明白。
此外,较新的内核源码目录中/Documentation/sysctl/vm.txt,对lowmem_reserve做了非常准确的描述。
http://www.cnblogs.com/GoodGoodWorkDayDayUp/p/3473348.html
http://blog.csdn.net/lby8203xj/article/details/7702514
http://dbanotes.net/database/linux_outofmemory_oom_killer.html