Linux系统的OOM Killer处理机制

  最近有位 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 外还可以优化系统(优化 Debian 5,优化 CentOS 5.x),让系统尽可能使用少的内存以便应用程序(如 MySQL) 能使用更多的内存,还有一个临时的办法就是调整内核参数,让 MySQL 进程不容易被 OOM killer 发现。

  我们可以通过一些内核参数来调整 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" >> /etc/sysctl.conf

  我们知道了在用户空间可以通过操作每个进程的 oom_adj 内核参数来调整进程的分数,这个分数也可以通过 oom_score 这个内核参数看到,比如查看进程号为981的 omm_score,这个分数被上面提到的 omm_score_adj 参数调整后(-15),就变成了3:

  # cat /proc/981/oom_score

  18

  # echo -15 > /proc/981/oom_score_adj

  # cat /proc/981/oom_score

  3

  下面这个 bash 脚本可用来打印当前系统上 oom_score 分数最高(最容易被 OOM Killer 杀掉)的进程:

  # vi oomscore.sh

  #!/bin/bash

  for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do

  printf "%2d %5d %sn"

  "$(cat $proc/oom_score)"

  "$(basename $proc)"

  "$(cat $proc/cmdline | tr '' ' ' | head -c 50)"

  done 2>/dev/null | sort -nr | head -n 10

  # chmod +x oomscore.sh

  # ./oomscore.sh

  18 981 /usr/sbin/mysqld

  4 31359 -bash

  4 31056 -bash

  1 31358 sshd: [email protected]/6

  1 31244 sshd: vpsee [priv]

  1 31159 -bash

  1 31158 sudo -i

  1 31055 sshd: [email protected]/3

  1 30912 sshd: vpsee [priv]

  1 29547 /usr/sbin/sshd -D

分类:服务器_操作系统教程 时间:2012-01-04 人气:15
本文关键词:
分享到:

相关文章

  • linux kill 关闭进程命令 2012-08-16

    杀死进程最安全的方法是单纯使用kill命令,不加修饰符,不带标志。 首先使用ps -ef命令确定要杀死进程的PID,然后输入以下命令: # kill -pid 注释:标准的kill命令通常都能达到目的。终止有问题的进程,并把进程的资源释放给系统。然而,如果进程启动了子进程,只杀死父进程,子进程仍在运行,因此仍消耗资源。为了防止这些所谓的“僵尸进程”,应确保在杀死父进程之前,先杀死其所有的子进程。 确定要杀死进程的PID或PPID # ps -ef | grep httpd 以优雅的方式结束进程

  • Linux内核剖析之进程简介 2014-01-26

    1、概念 1.1 什么是进程? 进程是程序执行的一个实例,可以看作充分描述程序已经执行到何种程度的数据结构的汇集。 从内核观点看,进程的目的就是担当分配系统资源(CPU时间,内存等)的实体。 我们熟悉的fork()库函数,它有两种用法: (1)、一个父进程希望复制自己,使父子进程执行不同的代码段,常用于网络服务程序。 (2)、一个进程要执行一个不同的程序,fork()后立即exec(),如shell。 1.2 什么是线程? 有时候,一个进程希望有多个执行流,如一款麻将游戏,三个由电脑控制的人都应

  • Linux中使用cgroups控制内存 2012-05-02

      cgroups 中有个 memory 子系统,用于限制和报告进程的内存使用情况 。   其中,很明显有两组对应的文件,一组带 memsw ,另一组不带   代码如下:   memory.failcnt   memory.limit_in_bytes   memory.max_usage_in_bytes   memory.usage_in_bytes   memory.memsw.failcnt   memory.memsw.limit_in_bytes   memory.memsw.max

  • 按内存占用排序和按CPU占用排序的Linux TOP命令 2012-05-29

      Linux TOP命令默认是CPU占用排序,按M可以切换到按内存占用排序。这是系统维护和电脑内存维护。可以切换的。不同的用法,达到不同的效果。以下是关于这两个的详细说明。   P – 以 CPU 占用率大小的顺序排列进程列表   M – 以内存占用率大小的顺序排列进程列表   在系统维护的过程中,随时可能有需要查看 CPU 使用率,并根据相应信息分析系统状况的需要。在 CentOS 中,可以通过 top 命令来查看 CPU 使用状况。运行 top 命令后,CPU 使用状态会以全屏的方式显示,

  • Linux信号列表详解 2013-11-30

      软件们运行如下命CPU看到Linux支持的信号列表:   '$ kill -l   1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL   5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE   9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2   13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD   18) SIGCONT 19) SIGS

  • Linux操作系统上常用的系统管理命令 2014-08-06

    对于Linux系统来说,无论是中央处理器、内存、磁盘驱动器、键盘、鼠标,还是用户等都是文件,Linux系统管理的命令是它正常运行的核心。熟悉了Linux常用的文件处理命令以后,这一讲介绍对系统和用户进行管理的命令。 df 1.作用 df命令用来检查文件系统的磁盘空间占用情况,使用权限是所有用户。 2.格式 df [options] 3.主要参数   -s:对每个Names参数只给出占用的数据块总数。   -a:递归地显示指定目录中各文件及子目录中各文件占用的数据块数。若既不指定-s,也不指定-a

  • 每天一个linux命令(44):top命令 2014-09-14

    每天一个linux命令(44):top命令 相关链接: 每天一个linux命令(1):ls命令 http://www.2cto.com/os/201210/163049.html; 每天一个linux命令(2):cd命令 http://www.2cto.com/os/201210/163050.html; 每天一个linux命令(3):pwd命令 http://www.2cto.com/os/201210/163462.html; 每天一个linux命令(4):mkdir命令 http://ww

  • Android开发之Android体系架构介绍 2012-07-17

    在Android中,整个框架由应用、应用框架、原生库、Android实时库、硬件抽象层、Linux内核等若干部分组成。 其中最核心的Android虚拟机部分也已经开放源码。对开发者而言,如果期望在深度定制的基础上开发出差异化、高度竞争力的产品,需要在应用框架、原生库、硬件抽象层、Linux内核等方面有较深入的理解。图1显示了Android的体系架构。 图1 Android体系架构 1、核心服务 所谓Android的核心服务主要包括熵服务(Entropy Service)、电源管理器(Power

  • 还在杀进程?Android你不知道的那些事儿 2013-10-04

    你真的了解Android吗? 你真的了解Android吗? 谷歌为我们带来了一个不同于iOS的Android新系统,如今,昔日的菜鸟已成一方霸主,使用Android手机的人也越来越多,但是,你真的了解Android吗? Android已从昔日菜鸟变为一方霸主 其实从很多方面,Android系统的机制都和其他系统也很大不同,更多的时候,用户是把使用其他系统的经验带到了Android手机上,例如很多人买到Android手机的第一件事就是安装“高级任务管理器”或者“Auto task killer”等

Copyright (C) quwantang.com, All Rights Reserved.

趣玩堂 版权所有 京ICP备15002868号

processed in 0.317 (s). 10 q(s)