巧妇能为少米之炊(2)——幽灵侩子手(LMK)

来源:互联网 发布:英文润色软件 编辑:程序博客网 时间:2024/04/28 21:01
使用安卓的朋友可能会遇到过这样的问题,就是某个应用正在使用,突然它被关闭了,或者打开某个应用,然后它就退出了,其实这背后就是LMK(Low Memory Killer)在起作用,所有形象地称之为幽灵刽子手。


1.版本平台

2.概念

3.核心结构及调用

4.总结


版本平台


平台:高通MSM8974
安卓版本:4.4
Linux内核版本:3.4.4
文件路径: android\kernel\drivers\staging\android\Lowmemorykiller.c

概念:


前文已经介绍过,LMK是定时扫描系统中的内存,当系统内存少于某个阀门值的时候,就会选择性的去释放一些内存(杀死进程),这就是你看到当前的程序突然退出的原因,可能有人会问,它系统怎么不做的人性化点,检测到我在使用,不要杀我使用的程序啊,去杀别的啊,这个我们后面慢慢道来~~


核心结构及调用


阀门值

LMK杀死内存就是根据阀门值来选择去杀掉哪些进程,我们先看看与阀门值有关的两个数据结构:
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static int lowmem_adj[6] = {  
  2.     0,  
  3.     1,  
  4.     6,  
  5.     12,  
  6. };  
  7. static int lowmem_adj_size = 4;  
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. static int lowmem_minfree[6] = {  
  2.     3 * 512,    /* 6MB */  
  3.     2 * 1024,   /* 8MB */  
  4.     4 * 1024,   /* 16MB */  
  5.     16 * 1024,  /* 64MB */  
  6. };  
  7. static int lowmem_minfree_size = 4;  
这两个数据结构是仅仅相互关联的,那么他们是怎么相互协作的工作的呢?举个列子:
当前系统空闲内存是63M时,比lowmem_minfree[3]小且大于lowmem_minfree[2],那么当前要杀掉进程的标准的值(每个进程都有一个adj值)就是lowmem_adj[3],也就是说找到所有进程的adj值大于lowmem_adj[3]的进程,然后从他们之中找到adj值最大的杀掉,如果有两个进程的adj一样时,会选择内存占用最多杀掉!数组其他部分以此类推
    Android将进程分为6个等级,foreground(前台进程)、visible(可见进程)、secondary server(次要服务)、hidden(后台进程)、content provider(内容供应节点)、empty(空进程),优先级依次降低,所以可以看到最容易被杀死的就是空进程,最不容易被杀死的就是前台进程,所以当你正在使用着的应用就被无辜杀死的时候,说明此时系统的阀门值已经触底,你是不是开了很多应用?

lowmem_shrink():

LMK示意内核模块的形式注册进内核,在初始化的时候,会初始化注册一个shrink()(关于Linux的shrink的知识这里不赘述),当空闲内存触及到阀门值的时候,就会调用到lowmem_shrink()这个函数,这个函数也就是选择并且杀死进程最核心的操作地方,我们细细来看它的过程和代码:
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1.   p = find_lock_task_mm(tsk);   /*有mm的thread才能操作*/  
  2. if (!p)  
  3.   continue;  
  4. /*得到task对应的oom_score_adj*/  
  5. oom_score_adj = p->signal->oom_score_adj;  
  6. /*比当前的基本标准小就忽略。*/  
  7. if (oom_score_adj < min_score_adj) {  
  8.     task_unlock(p);  
  9.     continue;  
  10. }  
首先会扫描进程列表,查看当前进程是否有mm,只有有mm的进程才能执行以下操作,如果找到了就获得当前进程的oom_score_adj的值(即进程的adj的值),然后判断当前进程的adj值是不是比基准值小(要找到比基准值大的,且是最大的进程杀死,不要忘记前面刚说过哦~
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /*获得此进程的rss内存占用大小*/  
  2. tasksize = get_mm_rss(p->mm);  
  3. task_unlock(p);  
  4. if (tasksize <= 0)  
  5.     continue;  
如果找到一个比基准值大的,那么记录它的内存大小,以备后面遇到两个adj值一样大小的进程可以比较谁内存占据的多就杀死谁。
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (selected) {  
  2.     /*如果当前task的oom score比上次小,则不做处理*/  
  3.     if (oom_score_adj < selected_oom_score_adj)  
  4.         continue;  
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /*如果前后两个task的oom score一样,而且此task 占有  
  2. ;span style="white-space:pre">           </span>内存比上次的task小时,也不做处理。*/  
  3. if (oom_score_adj == selected_oom_score_adj &&  
  4.     tasksize <= selected_tasksize)  
  5.     continue;  
下面的问题就是最简单的问题,两两比较找到一个链表中adj最大的值,如果adj相同,找到占用内存tasksize最大的值,这样就会选择出要杀死的进程是谁了,接着往下看:
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. selected = p;  
  2. selected_tasksize = tasksize;  
  3. selected_oom_score_adj = oom_score_adj;  
  4. lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",  
  5.          p->pid, p->comm, oom_score_adj, tasksize);  
这段代码就是更新selected的值,以便下一次比较,时刻保持selected中的adj值是最大的,并且它的占用内存是最大的,那当然因为下面我要杀死它释放内存了~~
开始杀了:
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (selected) {  
  2.     lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n",  
  3.              selected->pid, selected->comm,  
  4.              selected_oom_score_adj, selected_tasksize);  
  5.     lowmem_deathpending_timeout = jiffies + HZ;  
  6.     send_sig(SIGKILL, selected, 0);         /*发送SIGKILL信号杀死进程*/  
  7.     set_tsk_thread_flag(selected, TIF_MEMDIE);  
  8.     rem -selected_tasksize;  
  9. }  
  10. lowmem_print(4, "lowmem_shrink %lu, %x, return %d\n",  
  11.          sc->nr_to_scan, sc->gfp_mask, rem);  
  12. rcu_read_unlock();  
  13. return rem;  
看到了没,最终就是调用了SIGKILL这个信号让Linux内核去杀死进程的,借刀杀人,此法还是极好的。

修改lowmem_adj和lowmem_minfree的值

Android 4.0 以后,改变了4.0以前操作这两个数组的地方,操作这两个数据大小的文件路径:
android\frameworks\base\services\java\com\android\server\am\ProcessList.java
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. private final int[] mOomAdj = new int[] {  
  2.     FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,  
  3.     BACKUP_APP_ADJ, HIDDEN_APP_MIN_ADJ, HIDDEN_APP_MAX_ADJ  
  4. };  
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // These are the low-end OOM level limits.  This is appropriate for an  
  2.  // HVGA or smaller phone with less than 512MB.  Values are in KB.  
  3.  private final long[] mOomMinFreeLow = new long[] {  
  4.          8192, 12288, 16384,  
  5.          24576, 28672, 32768  
  6.  };  
  7.  // These are the high-end OOM level limits.  This is appropriate for a  
  8.  // 1280x800 or larger screen with around 1GB RAM.  Values are in KB.  
  9.  private final long[] mOomMinFreeHigh = new long[] {  
  10.          49152, 61440, 73728,  
  11.          86016, 98304, 122880  
  12.  };  
  13.  // The actual OOM killer memory levels we are using.  
  14.  private final long[] mOomMinFree = new long[mOomAdj.length];  
最终通过某个运算,将oOmMinFreeLow和mOomMinFreeHigh经过运算,最后得出的值存入mOomMinFree中,而如何计算这个值,是根据当前屏幕的分辨率和内存大小来,从英文注释中可以看出。
最后这个值的更新在函数updateOomLevels中,该函数也在ProcessList.java中
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. for (int i=0; i<mOomAdj.length; i++) {  
  2.     long low = mOomMinFreeLow[i];  
  3.     long high = mOomMinFreeHigh[i];  
  4.     mOomMinFree[i] = (long)(low + ((high-low)*scale));  
  5. }  
scale的值根据内存和分辨率的情况来确定,它只有两个值要不是0,要不是1,后面还有一些容错运算,然后最后来确定mOomMinFree的值,是不是感觉很繁琐?是的,我也感觉很繁琐!
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. for (int i=0; i<mOomAdj.length; i++) {  
  2.      if (i > 0) {  
  3.          adjString.append(',');  
  4.          memString.append(',');  
  5.      }  
  6.      adjString.append(mOomAdj[i]);  
  7.      memString.append((mOomMinFree[i]*1024)/PAGE_SIZE);  
  8.  }  
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. 。。。。。 //此处省略一些代码  
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //Slog.i("XXXXXXX", "******************************* MINFREE: " + memString);  
  2.         if (write) {  
  3.             writeFile("/sys/module/lowmemorykiller/parameters/adj", adjString.toString());  
  4.             writeFile("/sys/module/lowmemorykiller/parameters/minfree", memString.toString());  
  5.             SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));  
  6.         }  
  7.         // GB: 2048,3072,4096,6144,7168,8192  
  8.         // HC: 8192,10240,12288,14336,16384,20480  
最终,等待更新快结束的时候,它把值写入到系统中,所以可以在节点/sys/module/lowmemorykiller/parameters/adj 和 minfree中查询到当前的阀门值。
0 0
原创粉丝点击