Android进程系列第八篇---LowmemoryKiller机制分析(下)_第1张图片目录概览.png

前面进程系列已经更新了七篇,本文(基于kernel 3.18),基于前两篇博客,继续梳理LMK杀进程机制下篇,主要总结LowmemoryKiller的中kernel的原理部分。
Android进程系列第一篇---进程基础
Android进程系列第二篇---Zygote进程的创建流程
Android进程系列第三篇---SystemServer进程的创建流程
Android进程系列第四篇---SystemServer进程的启动流程
Android进程系列第五篇---应用进程的创建流程
Android进程系列第六篇---LowmemoryKiller机制分析(上)
Android进程系列第七篇---LowmemoryKiller机制分析(中)

上文说到如果lmkd.c中的use_inkernel_interface等于1,那么就执行kernel空间的逻辑,lmkd中数据结构也不用更新,也不用lmkd中杀进程的逻辑,全部都交给LowmemoryKiller完成。在正式进入之前,思考几个问题。

  • LowmemoryKiller杀进程的策略具体是怎么样的?内存低到什么情况下,LowmemoryKiller开始干活呢?
  • 有没有永远也杀不死的进程?
  • minfree水位线和对应的adj,应用开发者能不能擅自修改,让自己不易被杀死?
  • lmkd担当着AMS到LowmemoryKiller的桥梁,那lmkd进程会不会被自己或者LowmemoryKiller杀了呢?
  • 应用开发者如果使得进程活的更好?
    下面先整体过一遍LowmemoryKiller的机制,在回头整理这些问题。

一、lowmemorykiller低内存时触发进程查杀

1.1、基本原理

在linux中,有一个名为kswapd的内核线程,当linux回收存放分页的时候,kswapd线程将会遍历一张shrinker链表,并执行回调,或者某个app启动,发现可用内存不足时,则内核会阻塞请求分配内存的进程分配内存的过程,并在该进程中去执行lowmemorykiller来释放内存。虽然之前没有接触过,大体的理解就是向系统注册了这个shrinker回调函数之后,当系统空闲内存页面不足时会调用这个回调函数。 struct shrinker的定义在linux/kernel/include/linux/shrinker.h中:

http://androidxref.com/kernel_3.18/xref/include/linux/shrinker.h48struct shrinker {49  unsigned long (*count_objects)(struct shrinker *,50                     struct shrink_control *sc);51  unsigned long (*scan_objects)(struct shrinker *,52                    struct shrink_control *sc);5354  int seeks;  /* seeks to recreate an obj */55  long batch; /* reclaim batch size, 0 = default */56  unsigned long flags;5758  /* These are for internal use */59  struct list_head list;60  /* objs pending delete, per node */61  atomic_long_t *nr_deferred;62};63#define DEFAULT_SEEKS 2 /* A good number if you don't know better. */6465/* Flags */66#define SHRINKER_NUMA_AWARE (1 << 0)6768extern int register_shrinker(struct shrinker *);69extern void unregister_shrinker(struct shrinker *);70#endif71

shrinker的注册与反注册

http://androidxref.com/kernel_3.18/xref/drivers/staging/android/lowmemorykiller.c189static struct shrinker lowmem_shrinker = {190 .scan_objects = lowmem_scan,191 .count_objects = lowmem_count,192 .seeks = DEFAULT_SEEKS * 16193};
http://androidxref.com/kernel_3.18/xref/drivers/staging/android/lowmemorykiller.c195static int __init lowmem_init(void)196{197 register_shrinker(&lowmem_shrinker);198 return 0;199}200201static void __exit lowmem_exit(void)202{203 unregister_shrinker(&lowmem_shrinker);204}

注册完成之后,就回调lowmem_scan,这个基本上是lmk核心的代码

http://androidxref.com/kernel_3.18/xref/drivers/staging/android/lowmemorykiller.c80static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)81{             //tsk进程结构体对象82  struct task_struct *tsk;  //我们需要选择一个进程杀掉,这个selected用来保存不幸中奖的那个进程83  struct task_struct *selected = NULL;84  unsigned long rem = 0;85  int tasksize;86  int i;        // OOM_SCORE_ADJ_MAX = 100087  short min_score_adj = OOM_SCORE_ADJ_MAX + 1;88  int minfree = 0;  //中奖的进程的内存占用大小89  int selected_tasksize = 0;  //中奖的进程的oom_score_adj值90  short selected_oom_score_adj;91  int array_size = ARRAY_SIZE(lowmem_adj);  //global_page_state可以获取当前系统可用的(剩余)内存大小92  int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;93  int other_file = global_page_state(NR_FILE_PAGES) -94                      global_page_state(NR_SHMEM) -95                      total_swapcache_pages();9697  if (lowmem_adj_size < array_size)98      array_size = lowmem_adj_size;99  if (lowmem_minfree_size < array_size)100     array_size = lowmem_minfree_size;        // 遍历lowmem_minfree数组找出相应的最小adj值,目的就是根据剩余内存的大小,确定当前剩余内存的级别的adj101 for (i = 0; i < array_size; i++) {102     minfree = lowmem_minfree[i];103     if (other_free < minfree && other_file < minfree) {104         min_score_adj = lowmem_adj[i];105         break;106     }107 }108109 lowmem_print(3, "lowmem_scan %lu, %x, ofree %d %d, ma %hd\n",110         sc->nr_to_scan, sc->gfp_mask, other_free,111         other_file, min_score_adj);112     //系统的空闲内存数,根据上面的逻辑判断出,low memory killer需要对adj高于多少(min_adj)的进程进行分析是否释放。        //发现min_score_adj值为OOM_SCORE_ADJ_MAX + 1了,说明当前系统很好,不需要杀进程来释放内存了113 if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) {114     lowmem_print(5, "lowmem_scan %lu, %x, return 0\n",115              sc->nr_to_scan, sc->gfp_mask);116     return 0;117 }118119 selected_oom_score_adj = min_score_adj;120     //内核一种同步机制 -- RCU同步机制121 rcu_read_lock();        //遍历所有进程122 for_each_process(tsk) {123     struct task_struct *p;124     short oom_score_adj;125             //内核线程kthread126     if (tsk->flags & PF_KTHREAD)127         continue;128129     p = find_lock_task_mm(tsk);130     if (!p)131         continue;132133     if (test_tsk_thread_flag(p, TIF_MEMDIE) &&134         time_before_eq(jiffies, lowmem_deathpending_timeout)) {135         task_unlock(p);136         rcu_read_unlock();137         return 0;138     }139     oom_score_adj = p->signal->oom_score_adj;                // 如果当前找到的进程的oom_score_adj比当前需要杀的最小优先级还低,不杀140     if (oom_score_adj < min_score_adj) {141         task_unlock(p);142         continue;143     }       /获取进程的占用内存大小(rss值),也就是进程独占内存 + 共享库大小144     tasksize = get_mm_rss(p->mm);145     task_unlock(p);146     if (tasksize <= 0)147         continue;       //第一次循环,selected一定是null的148     if (selected) {       //如果这个进程的oom_score_adj小于我们已经选中的那个进程的oom_score_adj,              //或者这个进程的oom_score_adj等于我们已经选中的那个进程的oom_score_adj,              // 但其所占用的内存大小tasksize小于我们已经选中的那个进程所占用内存大小,则继续寻找下一个进程149         if (oom_score_adj < selected_oom_score_adj)150             continue;151         if (oom_score_adj == selected_oom_score_adj &&152             tasksize <= selected_tasksize)153             continue;154     }          //已经找到了需要寻找的进程,更新它的tasksize与oom_score_adj155     selected = p;156     selected_tasksize = tasksize;157     selected_oom_score_adj = oom_score_adj;158     lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n",159              p->comm, p->pid, oom_score_adj, tasksize);160 }    //selected非null,说明已经找到了161 if (selected) {162     long cache_size = other_file * (long)(PAGE_SIZE / 1024);163     long cache_limit = minfree * (long)(PAGE_SIZE / 1024);164     long free = other_free * (long)(PAGE_SIZE / 1024);165     trace_lowmemory_kill(selected, cache_size, cache_limit, free);    //关键打印166     lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \167             "   to free %ldkB on behalf of '%s' (%d) because\n" \168             "   cache %ldkB is below limit %ldkB for oom_score_adj %hd\n" \169             "   Free memory is %ldkB above reserved\n",170              selected->comm, selected->pid,171              selected_oom_score_adj,172              selected_tasksize * (long)(PAGE_SIZE / 1024),173              current->comm, current->pid,174              cache_size, cache_limit,175              min_score_adj,176              free);    //更新lowmem_deathpending_timeout177     lowmem_deathpending_timeout = jiffies + HZ;     //设置进程的标记是TIF_MEMDIE178     set_tsk_thread_flag(selected, TIF_MEMDIE);     //发送SIGKILL信号,杀死这个进程179     send_sig(SIGKILL, selected, 0);     //更新一下rem值,杀死了一个进程所释放的内存加上去180     rem += selected_tasksize;181 }182183 lowmem_print(4, "lowmem_scan %lu, %x, return %lu\n",184          sc->nr_to_scan, sc->gfp_mask, rem);185 rcu_read_unlock();186 return rem;187}

上面代码就是lowmemorykiller核心原理的实现,可以小总结一下。

  • 首先调用global_page_state,可以获取当前系统可用的(剩余)内存大小
  • 遍历lowmem_minfree数组,根据other_free和other_file的剩余内存的大小,确定当前剩余内存的最小级别的min_score_adj
  • 有了上面的adj之后,遍历所有进程,获取每个进程的rss大小,然后不断循环,每次比较进程占用的内存大小tasksize以及小于oom_score_adj,就能确定最终杀死哪个进程了
  • 确定的进程保存在selected变量中,对他发送SIGKILL信号杀死,并且更新rem大小
    所以通过上面的总结已经可以回答我们第一个问题,“LowmemoryKiller杀进程的策略具体是怎么样的?内存低到什么情况下,LowmemoryKiller开始干活呢?”
1.2、拓展思考
1.2.1有没有永远也杀不死的进程呢?

要回答这个问题要看从哪个角度了,如果从native进程的角度回答,确实是存在的,我们通过前面几篇的总结了解到,AMS自己会杀死进程,在内存紧张的时候也会通过lmkd请求lmk来杀进程。如果我们写一个native进程同样做到不死忙,比如我们给测试写一个内存加压的程序。因为要给对手机内存加压,要保证这个加压进程不被杀死,即使低内存,即使上层systemui执行一键清理,都能存活,如何做到呢?

int main(int argc, char *argv[]) {   char text[100];     unsigned int  size;   int percentage = atoi(argv[1]);  //外面传一个参数进来,占用百分之多少的内存,最高门槛是60%   if (percentage >= 0 && percentage <= 60) {       printf("Memory footprint %d%%\n", percentage);   } else {       printf("Memory footprint %d%% error!! must be in range of 0-60%%\n", percentage);       return 0;   }       //修改oom_score_adj为-1000   sprintf(text, "/proc/%d/oom_score_adj", getpid());       int fd = open(text, O_WRONLY);     if (fd >= 0) {         sprintf(text, "%d", -1000);  //让自己不被杀死       write(fd, text, strlen(text));         close(fd);     }      char task_name[50];   char *pid = (char*)calloc(10,sizeof(char));   strcpy(task_name, "logcat");   sprintf(text, "/proc/%s/oom_score_adj", pid);    fd = open(text, O_WRONLY);     if (fd >= 0) {         sprintf(text, "%d", -1000);   //让logcat进程不被杀死       write(fd, text, strlen(text));         close(fd);     }   size = (unsigned int)mygetsize();   mallocflag(percentage, size); //占用内存   while(1) {//等待正常退出       sleep(3);       if((access("/sdcard/finishflag",F_OK)) == 0) {           printf("create memroy process now end.......\n");           free(addr);           addr = NULL;           break;       }      }   free(pid);   return 0;}void mallocflag(int percentage, unsigned int size) {   int i = 0;   if(addr != NULL) {       free(addr);       addr = NULL;   }   printf("size= %d kb percentage=%d\n",size,percentage);   float s=(float) size/1024/1024;   printf("phone  mem size %.2f G \n",s);   float p =(float)percentage/100;   printf("p %.2f rate\n",p);   int sum=s*p*1204*1024*1024;   printf("sum %d bye",sum);   printf(" will malloc %d%% size = %d kb\n",percentage, sum);   addr = (char *)malloc(sum);   printf("malloc %d%% size = %dM\n",percentage, sum/1024/1024);   system("echo 2 start >> /sdcard/memory_log");   if(addr == NULL) {       printf("malloc %d%% fail \n", percentage);       system("echo 2 fail >> /sdcard/memory_log");       exit(0);       //如果申请失败,尝试申请一般的内存   }   else {       myMalloc(addr, sum);       printf("malloc %d%% success \n", percentage);       system("echo 2 success >> /sdcard/memory_log");   }       }

然后写Android.mk在源码下面编译就行了,其实这么长一段代码,核心的地方就几行,即把oom_score_adj修改为-1000

       //修改oom_score_adj为-1000   sprintf(text, "/proc/%d/oom_score_adj", getpid());       int fd = open(text, O_WRONLY);     if (fd >= 0) {         sprintf(text, "%d", -1000);  //让自己不被杀死       write(fd, text, strlen(text));         close(fd);     }

因为-1000是最小的adj值,即使lmk把其他的进程都杀光了,都不会轮到自己,即使自己的占用的内存很多,其次AMS也监控不到,因为native程序是kernel管理的。当我们把编译好的程序放到system/bin下面就能被上层的APP所使用的,当然这需要Root权限。所以从另外一个角度来讲,对于市面上没有Root权限的APP来说存活手段就比较难了,因为你没有修改adj值的机会。唉,要修改oom_score_adj结点同样需要Root权限,且下次开机就没有效果了。在系统中,有对一些APP保驾护航,比如Home。

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java24049        if (app == mHomeProcess) {24050            if (adj > ProcessList.HOME_APP_ADJ) {24051                // This process is hosting what we currently consider to be the24052                // home app, so we don't want to let it go into the background.24053                adj = ProcessList.HOME_APP_ADJ;24054                schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;24055                app.cached = false;24056                app.adjType = "home";24057                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {24058                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);24059                }24060            }24061            if (procState > ActivityManager.PROCESS_STATE_HOME) {24062                procState = ActivityManager.PROCESS_STATE_HOME;24063                app.adjType = "home";24064                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {24065                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app);24066                }24067            }24068        }

HOME_APP_ADJ的值是600,这个相对来说很小了,系统中很多的进程是900+,所以系统中对于重要的进程一般都会加以保护,比如Home进程的adj与调度组都得到了一定的优先。

1.2.2、lmkd会不会被自己杀了呢?

对于这个疑问,我们查看一下lmkd进程的oom_score_adj文件就好了

2|sakura:/proc/589 # cat oom_adj                                                                                                                                                                           -17sakura:/proc/589 # cat oom_score_adj                                                                                                                                                                       -1000

值也是-1000,显然不能被自己杀死

1.2.3、给应用开发者的建议

对于App开发者来说,怎么来存活呢,市面上保活手段很多,我之前也总结过Android进程保活的一般套路,感兴趣可以看一看。对于系统来说,对这么多的APP。一碗水得端平了,不偏袒谁,资源的分配策略希望每一个app都能遵守,不要在做什么其他的保活手段,在必要的情况一下,系统也给了一些措施,ActivityManagerService会根据系统内存以及应用的状态通过app.thread.scheduleTrimMemory发送通知给应用程序,App中的onTrimMemory(int level) 和onLowMemory() 就会被回调,而Activity, Service, ContentProvider和Application都实现了这个接口,在回调中我们可以做一些内存释放的操作,这样在同adj的时候,我们的进程就不会被中奖了。

应用处于Runnig状态可能收到的level级别
TRIM_MEMORY_RUNNING_MODERATE 表示系统内存已经稍低
TRIM_MEMORY_RUNNING_LOW 表示系统内存已经相当低
TRIM_MEMORY_RUNNING_CRITICAL 表示系统内存已经非常低,你的应用程序应当考虑释放部分资源

应用的可见性发生变化时收到的级别
TRIM_MEMORY_UI_HIDDEN 表示应用已经处于不可见状态,可以考虑释放一些与显示相关的资源

应用处于后台时可能收到的级别
TRIM_MEMORY_BACKGROUND 表示系统内存稍低,你的应用被杀的可能性不大。但可以考虑适当释放资源
TRIM_MEMORY_MODERATE 表示系统内存已经较低,当内存持续减少,你的应用可能会被杀死
TRIM_MEMORY_COMPLETE 表示系统内存已经非常低,你的应用即将被杀死,请释放所有可能释放的资源

那么我们一般需要释放哪些资源呢?Android代码内存优化建议-OnTrimMemory优化

  • 缓存 缓存包括一些文件缓存,图片缓存等,在用户正常使用的时候这些缓存很有作用,但当你的应用程序UI不可见的时候,这些缓存就可以被清除以减少内存的使用.比如第三方图片库的缓存.

  • 一些动态生成动态添加的View. 这些动态生成和添加的View且少数情况下才使用到的View,这时候可以被释放,下次使用的时候再进行动态生成即可.比如原生桌面中,会在OnTrimMemory的TRIM_MEMORY_MODERATE等级中,释放所有AppsCustomizePagedView的资源,来保证在低内存的时候,桌面不会轻易被杀掉.

  • 最好的办法是用TraceView或者Memrroy Monitor来看哪些对象占用内存大,在决定是否释放。

更多相关文章

  1. 进程、线程与JVM之间的关系
  2. android 系统服务
  3. Android 应用程序(APK) 如何获得系统签名权限 强制关闭程序(后台
  4. Android 9 (P)系统启动之SystemServer大揭秘下
  5. Android系统架构概况
  6. Android跨进程通信之非AIDL(二)
  7. Android多进程
  8. Android核心分析(14)------ Android GWES之输入系统

随机推荐

  1. android init进程分析 init脚本解析和处
  2. Android为TextView添加字体库和设置描边
  3. Android设置标题栏图标(2)
  4. 谷歌发布Android安全补丁:修复诸多漏洞
  5. 第二章 吸引你的眼球—UI编程(1)
  6. Linux中Jenkins+Git+Gradle自动化打包And
  7. 3月份工作回顾
  8. 建议SQLite操作使用rawQuery方法
  9. [译]Android(安卓)SDK中关于View绘制流程
  10. 分享一个遍历当前文件夹下所以子目录,并在