初识Android的LMK机制

  • 一、文章背景
    • 1.1 LMK中kill进程的关键log(原生系统):![LMK中kill进程的关键log](https://img-blog.csdnimg.cn/788417c46c9641c98c5428d7795fe6cd.png)
    • 1.2 内核log信息(定制系统,原生系统看上面的截图里的log):
  • 二、粗略分析lowmemorykill.c中的实现逻辑
    • 2.1 基本实现思路
    • 2.2 是一定条件满足时触发or系统启动就开始执行了?
    • 2.3 核心逻辑
      • 2.3.1 定义一些“常量“:规则的量化标准
      • 2.3.2 遍历进程->查杀流程:lowmem_scan()函数
  • 三、几个疑问的思考
    • 3.1 为什么在计算剩余可用内存时,有other_free和other_file两个数值?
    • 3.2 lowmem_adj数组中定义的oom_scrore_adj和task_struct->signal->oom_score_adj的值即然能比较,那进程中的oom_score_adj是在哪里赋值的??
    • 3.3 内核中kill掉了应用进程,那怎么通知被杀进程做后续的进程数据清理操作?如何通知过去的?
      • 3.3.1 com.android.server.am.ActivityManagerService#attachApplication()
      • 3.3.2 com.android.server.am.ActivityManagerService#attachApplicationLocked()
      • 3.3.3 com.android.server.am.ActivityManagerService.AppDeathRecipient

一、文章背景

最近在分析一个应用进后台经常被杀死问题,问题发生在一个定制的系统中,存在多个内存动态清理工具(类似手机卫士等工具,是厂商开发的预装应用,具备内存不足时的进程查杀能力)。于是需要逐一排查,最终定位到是被Android系统的LMK机制kill掉的,但是因为对于LMK机制的不熟悉,导致定位了两天时间,这里做一个学习笔记以供总结复盘。如有理解失当支持悉听指教。
注:
(1) 文中的AMS皆指ActivityManager;
(2) LMK指LowMemoryKiller机制,其对应的进程名是lmkd;
(3) 所有Android源码基于Android SDK 28(Android9/Android P),kernel层基于3.18版本

1.1 LMK中kill进程的关键log(原生系统):

1.2 内核log信息(定制系统,原生系统看上面的截图里的log):

lowmemorykiller: Killing 'main' (1222) (tgid 1222), adj 100,\x0a
to free 97596kB on behalf of 'kworker/u8:6' (3975) because\x0a   cache 19876kB is
below limit 20480kB for oom_score_adj 100\x0a   defrag_free 3220kB\x0a  Free memory is -40312kB above reserved

二、粗略分析lowmemorykill.c中的实现逻辑

2.1 基本实现思路

  • Android的LMK机制是基于linux的OOM killer修改而来,具体实现是在驱动层,核心功能实现是在drivers/staging/android/lowmemorykiller.c文件中,linux中的驱动实现都是以文件为载体的。
  • LowMemoryKiller会周期性的检查当前系统的可用内存,当剩余可用内存较低时,便会触发进程查杀策略,根据不同的可用内存的阈值来杀掉相应优先级的进程。
  • 关键字:LowMemoryKiller 可用内存阈值 进程优先级 oom_score_adj

2.2 是一定条件满足时触发or系统启动就开始执行了?

2.3 核心逻辑

查看lowmemorykiller.c完整源码
task_struct 定义在/include/linux/sched.h中:

2.3.1 定义一些“常量“:规则的量化标准
// 低内存时lowmem_adj分级
static short lowmem_adj[6] = {0,1,6,12,
};
// 共分了4个档次
static int lowmem_adj_size = 4;
// 4个档次下的可用内存预值
static int lowmem_minfree[6] = {3 * 512,   /* 6MB */2 * 1024,  /* 8MB */4 * 1024,  /* 16MB */16 * 1024,    /* 64MB */
};
static int lowmem_minfree_size = 4;
  • 疑问:lowmem_minfree数组中定义的内存阈值是这么算的?
2.3.2 遍历进程->查杀流程:lowmem_scan()函数
   // lowmem_scan()函数扫描进程,对比系统剩余内存大小值,计算出当前属于哪个阈值等级
78 // struct shrinker *s:
79 // struct shrink_control *sc:
80 static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
81{82  struct task_struct *tsk;
83  struct task_struct *selected = NULL;
84  unsigned long rem = 0;
85  int tasksize;
86  int i;
87  // score_adj的值越大优先级越低
87  short min_score_adj = OOM_SCORE_ADJ_MAX + 1;
88  int minfree = 0;
89  int selected_tasksize = 0;
90  short selected_oom_score_adj;
91  // lowmem_adj数组的size=4
91  int array_size = ARRAY_SIZE(lowmem_adj);
92  // other_free: 获取剩余内存大小
92  int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
93  // other_file
93  int other_file = global_page_state(NR_FILE_PAGES) -
94                      global_page_state(NR_SHMEM) -
95                      total_swapcache_pages();
96  // step1: 确认内存阈值分级数的size,目前看是4个等级;
97  if (lowmem_adj_size < array_size)
98      array_size = lowmem_adj_size; // 4
99  if (lowmem_minfree_size < array_size)
100     array_size = lowmem_minfree_size; // 4
101 // step2: // 从6、8、16、64M中按从小到大逐个对比,找到当前系统剩余内存大小属于哪个档次
102 // 例如:当剩余内存低于64M时会得到min_score_adj = 12
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 // 这里的log信息在分析进程被kill原因时也很关键!!
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 // min_score_adj == OOM_SCORE_ADJ_MAX + 1代表未从内存阈值数组中找到等级// 说明剩余内存至少大于64M,当前系统剩余内存未达到需要kill某个进程的地步,// 所以直接return结束本次扫描
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
121 rcu_read_lock();// step3: 遍历
122 for_each_process(tsk) {123     struct task_struct *p;
124     short oom_score_adj;
125
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     }// 从进程的结构体中读取oom分数:oom_score_adj,这个值是否和上面定义的lowmem_adj属于相同的取值范围??在哪里赋值的??
139     oom_score_adj = p->signal->oom_score_adj;// 从进程中读到的oom_score_adj < min_score_adj,// 说明p进程的优先级是高于当前低内存阈值对应的优先级的,不进行kill处理,跳过
140     if (oom_score_adj < min_score_adj) {141         task_unlock(p);
142         continue;
143     }// 获取这个p进程所占用的内存大小tasksize ,如果小于比我们当前选出进程的内存,// 则无视。如果大于则选中这个进程。
144     tasksize = get_mm_rss(p->mm);
145     task_unlock(p);
146     if (tasksize <= 0)
147         continue;// 首次走到这里时因为selected初始值为NULL,所以直接走else逻辑
148     if (selected) {// p进程的优先级是oom_score_adj,// 如果比当前内存阈值对应的oom_score_adj还小,// 代表p的优先级是高于当前的内存阈值等级的,也直接跳过
149         if (oom_score_adj < selected_oom_score_adj)
150             continue;// selected_tasksize初始值为0// oom_score_adj == selected_oom_score_adj代表当前进程p的oom_score_adj// 和内存阈值等级相等,也跳过,说明比较优先级时是不包含刚好优先级相等,而是要低于的
151         if (oom_score_adj == selected_oom_score_adj &&
152             tasksize <= selected_tasksize)
153             continue;
154     }// 优先级条件满足,给selected变量赋值,用于后面对进程继续操作
155     selected = p;
156     selected_tasksize = tasksize;
157     selected_oom_score_adj = oom_score_adj;// 这里的log信息也能用于分析当前满足kill条件的进程是哪个!!
158     lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n",
159              p->comm, p->pid, oom_score_adj, tasksize);
160     }
161 if (selected) {// PAGE_SIZE代表一个分页的大小,等于4k// !!到这里也能反过来推出other_file和min_free单位是页,下面为了转成KB单位,// 所以乘以了每个单页的KB大小,比如:一个分页是4KB,PAGE_SIZE / 1024 = 4,// 再用other_file * 4结果就是剩余内存有多少KB
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);// !!流程走到这里,下一步就是发送SIGKILL信号了,// 看到下面这条log就能确定进程即将被kill
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);
177     lowmem_deathpending_timeout = jiffies + HZ;
178     set_tsk_thread_flag(selected, TIF_MEMDIE);
179     send_sig(SIGKILL, selected, 0);
180     rem += selected_tasksize;
181 }
182 // rem
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}

经过 for_each 的遍历, selected 就是我们选出要释放掉的bad进程,它具有下面两个条件:
第一、Oom_adj大于当前警戒阈值并且最大;
第二、在同样大小的oom_adj中,占用内存最多。

  • 总结以上的查杀流程如下图:

三、几个疑问的思考

3.1 为什么在计算剩余可用内存时,有other_free和other_file两个数值?

单位都是页数
定义:

92  // other_free: 获取剩余内存大小
92  int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
93  // other_file
93  int other_file = global_page_state(NR_FILE_PAGES) -
94                      global_page_state(NR_SHMEM) -
95                      total_swapcache_pages();

3.2 lowmem_adj数组中定义的oom_scrore_adj和task_struct->signal->oom_score_adj的值即然能比较,那进程中的oom_score_adj是在哪里赋值的??

存储在/proc/pid/oom_score_adj文件中。
比如查看init进程的oom_score_adj文件:

3.3 内核中kill掉了应用进程,那怎么通知被杀进程做后续的进程数据清理操作?如何通知过去的?

AMS中有一个bindApplication方法,内部会在Application执行attachApplication()时,绑定IApplicationThread的实现类的逻辑,会有binder断开的监听逻辑。

3.3.1 com.android.server.am.ActivityManagerService#attachApplication()
@Override
public final void attachApplication(IApplicationThread thread, long startSeq) {synchronized (this) {int callingPid = Binder.getCallingPid();final int callingUid = Binder.getCallingUid();final long origId = Binder.clearCallingIdentity();// 继续看该方法实现-> 3.3.2attachApplicationLocked(thread, callingPid, callingUid, startSeq);Binder.restoreCallingIdentity(origId);}
}
3.3.2 com.android.server.am.ActivityManagerService#attachApplicationLocked()
private final boolean attachApplicationLocked(IApplicationThread thread,int pid, int callingUid, long startSeq) {// ...// If this application record is still attached to a previous// process, clean it up now.if (app.thread != null) {handleAppDiedLocked(app, true, true);}// ...// IBinder.DeathRecipient的实现类是AMS$AppDeathRecipient->3.3.3AppDeathRecipient adr = new AppDeathRecipient(app, pid, thread);// 这里的代码写法跟我们Context.bindService(),传入的ServiceConnect回调中拿到了远程进程的IBinder对象,// 然后为了监听服务端进程死亡后执行相应的处理逻辑(重试或直接停止逻辑),是一样的用法。                      thread.asBinder().linkToDeath(adr, 0);            app.deathRecipient = adr;                    // ...
}
3.3.3 com.android.server.am.ActivityManagerService.AppDeathRecipient
@Override
public void binderDied() {synchronized(ActivityManagerService.this) {appDiedLocked(mApp, mPid, mAppThread, true, null);}
}

注意这时候的CS模型中,应用程序(ActivityThread$IApplicationThread)是服务端(binder接口的实现方),而运行在system_server进程的AMS是客户端。AMS在bindApplication时,传入了IApplicationThread引用,然后每当需要AMS和应用进程通信时,都是通过IApplicationThread对象转调到应用进程。

  • LowMemoryKiller杀死应用后可能在应用的日志文件查询不到“ActivityManager : Killing”相关log,必要时需要查看内核层的log。

Android的LMK机制学习笔记相关推荐

  1. 消息机制学习笔记(四)—— 内核回调机制

    消息机制学习笔记(四)-- 内核回调机制 要点回顾 内核调用 实验1:理解内核调用 第一步:编译并运行以下代码 第二步:修改窗口过程函数,重新运行 KeUserModeCallback 实验2:在OD ...

  2. Windows消息机制学习笔记(三)—— 消息的接收与分发

    Windows消息机制学习笔记(三)-- 消息的接收与分发 要点回顾 消息循环 消息队列 消息的接收 GetMessage 实验1:理解GetMessage 第一步:编译并运行程序A 第二步:编译并运 ...

  3. Windows消息机制学习笔记(二)—— 窗口与线程

    Windows消息机制学习笔记(二)-- 窗口与线程 要点回顾 消息从哪里来? 实验一:Spy++捕获消息 实验二:消息捕获 消息到哪里去? 窗口在哪? 实验:分析CreateWindowExW 窗口 ...

  4. Windows消息机制学习笔记(一)—— 消息队列

    Windows消息机制学习笔记(一)-- 消息队列 基本概念 实验一:使用代码画出最简单窗口 第一步:编译并运行以下代码 第二步:查看运行结果 第三步:使用其它窗口对其进行覆盖,观察效果 总结 消息队 ...

  5. QT-事件机制学习笔记

    QT-事件机制学习笔记 一.事件 二.事件处理函数 三.事件分发机制 四.事件过滤器 五.定时器事件 六.绘图 七.手动触发绘图事件 八.绘图设备 一.事件  事件(event)是由系统或者Qt应用程 ...

  6. Android(java)学习笔记176: 远程服务的应用场景(移动支付案例)

    一. 移动支付:       用户需要在移动终端提交账号.密码以及金额等数据 到 远端服务器.然后远端服务器匹配这些信息,进行逻辑判断,进而完成交易,返回交易成功或失败的信息给移动终端.用户提交账号. ...

  7. Android日常开发 - FlexboxLayout学习笔记

    Android日常开发 - FlexboxLayout学习笔记 Android日常开发使用FlexboxLayout实现流式布局的效果,FlexboxLayout与h5中的flex使用十分相似,都是将 ...

  8. 三七的活血化瘀和止血机制学习笔记

    三七的活血化瘀和止血机制学习笔记 文章目录 三七的活血化瘀和止血机制学习笔记 前言: 文献一:<景天三七对阿司匹林大鼠的止血活血功效及作用机制研究> 三七的止血与活血机制 文献二:< ...

  9. Android Studio下载搭建学习笔记01

    Android Studio下载搭建学习笔记01 下载Android Studio 安装Android Studio 进入安装向导 选择安装组件 选择安装位置 选择文件菜单 等待安装 启动并配置And ...

最新文章

  1. 安装卸载功能 [测试思路]
  2. Android中实现一个简单的逐帧动画(附代码下载)
  3. python绘制指数函数图像及性质_指数函数图像及其性质正式版
  4. Python3 数字转换为字符串str()函数
  5. html5创建对象的方法,JavaScript面向对象-使用工厂方法和构造函数方法创建对象...
  6. 高级数据结构---并查集
  7. 树的遍历-Preorde Traversal,Inorder Traversal,Postoder Traversal
  8. ASP.NET MVC中使用Autofac实现简单依赖注入
  9. python删除连续相同字符_Python 删除连续出现的指定字符的实例
  10. k-d tree算法原理及实现
  11. 人工智能诗歌写作平台_人工智能教作文,只写出二类文,人类语文老师稳赢
  12. C++自学06:sizeof运算符
  13. js map遍历 修改对象里面的值_前端面试之你必须要懂的原生JS
  14. 计算机组成原理学习笔记————存储器(一) 存储器分类
  15. 无法读源文件或磁盘_文件、文件夹、磁盘加密 -我们推荐这个便宜的解决方案!...
  16. 特斯拉又起火燃烧,一人死亡!公司又处于负面新闻中,马斯克到底有多难?
  17. balser相机连接设置设置步骤
  18. 微信分享功能踩坑过程
  19. 【判断一个数是不是素数】
  20. iphone与android共享位置,在iPhone或Android上分享你的位置

热门文章

  1. 列车厢调度(C语言)
  2. R语言使用pbern函数生成伯努利分布(0-1分布)累积分布函数数据、使用plot函数可视化伯努利分布累积分布函数数据( Bernoulli distribution)
  3. 免登录积分商城系统 动力商城 兑换商城源码
  4. 未收到ios更新的推送应该怎么办?
  5. ad hoc无线互联
  6. pyhton小游戏-五子棋
  7. 将阿里巴巴官方图标库批量添加到购物车中
  8. 最大协方差(Maximum covarivance analysis,MCA)
  9. 智慧车联产业生态联盟,汽车和智慧终端跨界合作新动力
  10. 软件有源电力滤波器APF仿真,simulink仿真。三相三线制APF,三相四线制四桥臂APF