LowMemoryKiller机制分析

LowMemoryKiller机制分析,第1张

Linux系统当可用内存较低的时候oom killer机制会根据一定的规则去杀掉一些进程来释放内存,而Android系统的LowMemoryKiller机制就是以此功能为基础做了一些调整。Android系统中的APP在使用完成之后并不会马上被杀掉,而是驻留在内存中,当下一次在此进入此应用的时候可以省去进程创建的过程,加快启动速度。LowMemoryKiller机制会在内存资源紧张的时候,杀掉一些进程来回收内存。

LowMemoryKiller机制分为三个部分

Framework中的ProcessList和Native的lmkd进程通过Socket进行进程间通信,而lmkd和内核中的LowMemoryKiller通过writeFileString向文件节点写内容方法进行通信。

Framework层通过一定的规则调整进程的adj的值和内存空间阀值,然后通过socket发送给lmkd进程,lmkd两种处理方式, 一种将阀值写入文件节点发送给内核的LowMemoryKiller,由内核进行杀进程处理,另一种是lmkd通过cgroup监控内存使用情况,自行计算杀掉进程。

lmkd是一个native进程,由init进程启动,定义在/system/core/lmkd/lmkd.rc中

在lmkd.rc中,启动了lmkd进程,并创建了一个名为lmkd的socket的描述符,用于socket进程间通信。lmkd启动后首先执行main方法。

main方法首先设置了当前进程的调度规则,然后执行了init方法和mainLoop方法。

lmkd的init方法中做的工作

我们先分析内核实现的LowMemoryKiller进程查杀机制, 然后再分析lmkd实现的机制。两者最终的结果都是在内存紧张的时候杀死一些进程来释放内存, 但是实现机制去不太一样。

init执行初始化完成之后, 进入mainloop方法,循环等待epoll事件的上报,init的时候epoll监听的socket连接, 当有socket连接的时候就会调用ctrl_connect_handler方法。

监听到socket连接, 我们知道此时连接lmkd的socket客户端就是framework,当有连接到来的时候accept方法返回连接的socketFD, 然后将连接的socketFD同样加入epoll中, 当socketFD中有可读消息,即framework给lmkd发送消息的时候,epoll唤醒然后会掉ctrl_data_handler方法来处理。

Framework和lmkd进程通过socket来进行进程间通信,在lmkd初始化的时候,通过监听socket描述符lmkd来等待Framework发送的消息。

Framework向lmkd发送命令相关的方法有三个。

上面的三种情况Framework最终是通过socket向lmkd发送了三种消息。

lmkd接收命令处理逻辑

lmkd通过epoll监听socket中是否有数据, 当接受的framework发送的socket命令之后,调用ctrl_cmmand_handler方法处理,显示解析socket中的命令和参数,根据对于的命令来调用不同的方法处理。

对于进程查杀有两种实现方式,一种是内核的LMK,通过shrinker来触发低内存回收, 另一种是lmkd通过cgroup监控内存使用情况,自行计算杀掉进程。两种实现不太一样,需要逐个分析。

设置内存阀值和adj的值就是将从framework收到的数据封装成字符串,通过writefilestring写入到两个文件节点,以供内核LMK使用。

/sys/module/lowmemorykiller/parameters/minfree : 内存级别限额

/sys/module/lowmemorykiller/parameters/adj :内存级别限额对应的要杀掉的进程的adj值.

由于使用内核LMK, 所以调整进程优先级直接将优先级写入对应进程的oom_adj_score文件即可。

移除进程的时候不需要做任何 *** 作

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

内核LowMemoryKiller shrinker的注册过程如下:

注册完成之后, 在内存紧张的时候就会回调shrinker, 其中最主要的是lowmem_scan方法。具体实现如下:

内核LMK的原理很简单:首先注册了shrinker,在内存紧张的时候会触发lowmem_scan方法,这个方法要做的就是找打一个进程,然后杀掉他,释放一些内存。

内核LMK的实现逻辑已经分析完了

lmkd实现内存查实的方式是基于 cgroup memory 来实现的。

什么是cgroup memory?

Cgroup的memory子系统,即memory cgroup(本文以下简称memcg),提供了对系统中一组进程的内存行为的管理,从而对整个系统中对内存有不用需求的进程或应用程序区分管理,实现更有效的资源利用和隔离。

cgroup memory相关的文件

简单的了解了下cgroup的原理,再来看lmkd的init方法

先了解下memory pressure_level的用法

init_mp_common方法严格的按照pressure_level的用法,注册了pressure_level的事件回调, pressure_level分为三个等级

当内存达到相应的等级,就会回调mp_event_common方法, 由mp_event_common方法来处理。

lmkd内存查杀原理:

进程查杀的两种实现方式原理类似,都是注册是的回调,当内存紧张的时候根据剩余内存的adj来查杀大于该adj的内存。内核shrinker方式是只有内存紧张的时候才会去释放,而cgroup方式控制更加精细, 根据不同等级来触发内存回收。

Linux下的缓存机制及清理buffer/cache/swap的方法梳理

(1)缓存机制

为了提高文件系统性能,内核利用一部分物理内存分配出缓冲区,用于缓存系统 *** 作和数据文件,当内核收到读写的请求时,内核先去缓存区找是否有请求的数据,有就直接返回,如果没有则通过驱动程序直接 *** 作磁盘。

缓存机制优点:减少系统调用次数,降低CPU上下文切换和磁盘访问频率。

CPU上下文切换:CPU给每个进程一定的服务时间,当时间片用完后,内核从正在运行的进程中收回处理器,同时把进程当前运行状态保存下来,然后加载下一个任务,这个过程叫做上下文切换。实质上就是被终止运行进程与待运行进程的进程切换。

(2)查看缓存区及内存使用情况

[root@localhost ~]# free -m

total used free shared buffers cached

Mem: 7866 7725 141 19 74 6897

-/+ buffers/cache: 752 7113

Swap: 16382 32 16350

可以看到内存总共8G,已使用7725M,剩余141M,不少的人都是这么看的,这样并不能作为实际的使用率。因为有了缓存机制,具体该怎么算呢?

空闲内存=free(141)+buffers(74)+cached(6897)

已用内存=total(7866)-空闲内存

由此算出空闲内存是7112M,已用内存754M,这才是真正的使用率,也可参考-/+ buffers/cache这行信息也是内存正确使用率。

(3)可见缓存区分为buffers和cached,他们有什么区别呢?

内核在保证系统能正常使用物理内存和数据量读写情况下来分配缓冲区大小。buffers用来缓存metadata及pages,可以理解为系统缓存,例如,vi打开一个文件。cached是用来给文件做缓存,可以理解为数据块缓存,例如,dd if=/dev/zero of=/tmp/test count=1 bs=1G 测试写入一个文件,就会被缓存到缓冲区中,当下一次再执行这个测试命令时,写入速度会明显很快。

(4)随便说下Swap做什么用的呢?

Swap意思是交换分区,通常我们说的虚拟内存,是从硬盘中划分出的一个分区。当物理内存不够用的时候,内核就会释放缓存区(buffers/cache)里一些长时间不用的程序,然后将这些程序临时放到Swap中,也就是说如果物理内存和缓存区内存不够用的时候,才会用到Swap。

swap清理:

swapoff -a &&swapon -a

注意:这样清理有个前提条件,空闲的内存必须比已经使用的swap空间大

(5)怎样释放缓存区内存呢?

a)直接改变内核运行参数

#释放pagecache

echo 1 >/proc/sys/vm/drop_caches

#释放dentries和inodes

echo 2 >/proc/sys/vm/drop_caches

#释放pagecache、dentries和inodes

echo 3 >/proc/sys/vm/drop_caches

b)也可以使用sysctl重置内核运行参数

sysctl -w vm.drop_caches=3

注意:这两个方式都是临时生效,永久生效需添加sysctl.conf文件中,一般写成脚本手动清理,建议不要清理。

修改/etc/sysctl.conf 添加如下选项后就不会内存持续增加

vm.dirty_ratio = 1

vm.dirty_background_ratio=1

vm.dirty_writeback_centisecs=2

vm.dirty_expire_centisecs=3

vm.drop_caches=3

vm.swappiness =100

vm.vfs_cache_pressure=163

vm.overcommit_memory=2

vm.lowmem_reserve_ratio=32 32 8

kern.maxvnodes=3

上面的设置比较粗暴,使cache的作用基本无法发挥。需要根据机器的状况进行适当的调节寻找最佳的折衷。

tmpfs 在内存里,影响就是首先这个挂载点目录不能写入。其次就是会占用对应容量的内存。

一般 tmpfs 都是用于大量读写小文件的临时目录,web 服务器在这里似乎用的不多,但也存了一些数据在这里。慢了可能会导致 web 服务器运行某些动态网站代码出问题。

tmpfs 是在使用时才会占用内存,如果 tmpfs 空着,是不占用内存的。

内存不需要进行清理,内核会自动控制。你这个 echo 对应的是清空 cache ,缓存数据是可以提高 IO 性能的。没必要这么 *** 作。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/7107791.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-01
下一篇 2023-04-01

发表评论

登录后才能评论

评论列表(0条)

保存