美文网首页Android开发Android源码解析程序员
Android进程系列第八篇---LowmemoryKiller

Android进程系列第八篇---LowmemoryKiller

作者: LooperJing | 来源:发表于2018-10-16 21:05 被阅读52次
    目录概览.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.h
    48struct 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);
    53
    54  int seeks;  /* seeks to recreate an obj */
    55  long batch; /* reclaim batch size, 0 = default */
    56  unsigned long flags;
    57
    58  /* 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. */
    64
    65/* Flags */
    66#define SHRINKER_NUMA_AWARE (1 << 0)
    67
    68extern int register_shrinker(struct shrinker *);
    69extern void unregister_shrinker(struct shrinker *);
    70#endif
    71
    
    

    shrinker的注册与反注册

    http://androidxref.com/kernel_3.18/xref/drivers/staging/android/lowmemorykiller.c
    189static struct shrinker lowmem_shrinker = {
    190 .scan_objects = lowmem_scan,
    191 .count_objects = lowmem_count,
    192 .seeks = DEFAULT_SEEKS * 16
    193};
    
    
    http://androidxref.com/kernel_3.18/xref/drivers/staging/android/lowmemorykiller.c
    195static int __init lowmem_init(void)
    196{
    197 register_shrinker(&lowmem_shrinker);
    198 return 0;
    199}
    200
    201static 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.c
    80static 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 = 1000
    87  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();
    96
    97  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值,目的就是根据剩余内存的大小,确定当前剩余内存的级别的adj
    101 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 }
    108
    109 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 }
    118
    119 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             //内核线程kthread
    126     if (tsk->flags & PF_KTHREAD)
    127         continue;
    128
    129     p = find_lock_task_mm(tsk);
    130     if (!p)
    131         continue;
    132
    133     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_adj
    155     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_timeout
    177     lowmem_deathpending_timeout = jiffies + HZ;
         //设置进程的标记是TIF_MEMDIE
    178     set_tsk_thread_flag(selected, TIF_MEMDIE);
         //发送SIGKILL信号,杀死这个进程
    179     send_sig(SIGKILL, selected, 0);
         //更新一下rem值,杀死了一个进程所释放的内存加上去
    180     rem += selected_tasksize;
    181 }
    182
    183 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.java
    24049        if (app == mHomeProcess) {
    24050            if (adj > ProcessList.HOME_APP_ADJ) {
    24051                // This process is hosting what we currently consider to be the
    24052                // 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                                                                                                                                                                           
    -17
    sakura:/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来看哪些对象占用内存大,在决定是否释放。

    相关文章

      网友评论

        本文标题:Android进程系列第八篇---LowmemoryKiller

        本文链接:https://www.haomeiwen.com/subject/dkyqzftx.html