Android中基于CGroup的memory子系统HAL层分析-lmkd
Android在内存管理上于Linux有些小的区别,其中一个就是引入了lowmemorykiller。从lowmemorykiller.c位于drivers/staging/android也可知道,属于Android专有,没有进入Linux kernel的mainline。
lmkd,即Low Memory Killer Daemon,基于memory子系统和Kernel lowmemorykiller功能参数,选择一个合适的进程,然后kill进程,以达到释放内存的目的。所以也绕不开Kernel模块lowmemorykiller(drivers/staging/android/lowmemorykiller.c)。
在考虑一个系统服务的功能,不仅要分析其内部功能,还要对其输入(lmkd socket、memory子系统和lowmemory)和输出(kill)进行详细的分析,才能更好的理解整个lmkd建立的生态。
他们之间的关系可以简要概括如下:
lmkd相关模块关系
启动lmkd系统服务
在/etc/init/lmkd.rc中,启动lmkd系统服务,创建了lmkd socket,并且将lmkd设置为system-background类型的进程。
service lmkd /system/bin/lmkd |
lmkd框架分析
正如上图lmkd相关模块分析中所示,lmkd通过读取CGroup中memory子系统和lowmemory两个模块作为输入参数;输出是kill选定的进程。
正如所有的service一样,lmkd的起点也是main函数,lmkd的main函数很简单:
int main(int argc __unused, char **argv __unused) { mlockall(MCL_FUTURE); 锁住该实时进程在物理内存上全部地址空间。这将阻止Linux将这个内存页调度到交换空间(swap space),及时该进程已有一段时间没有访问这段空间。参见末尾参考资料。 ALOGI("exiting"); |
下面来分析一下主要核心函数init:
static int init(void) { page_k = sysconf(_SC_PAGESIZE); epollfd = epoll_create(MAX_EPOLL_EVENTS); 创建全局epoll文件句柄 ctrl_lfd = android_get_control_socket("lmkd"); 打开lmkd socket文件句柄 ret = listen(ctrl_lfd, 1); epev.events = EPOLLIN; use_inkernel_interface = !access(INKERNEL_MINFREE_PATH, W_OK); if (use_inkernel_interface) { for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) { return 0; |
1.创建epollfd文件,MAX_EPOLL_EVENTS为3,;
2.连接到lmkd socket,并将文件句柄加到epollfd,EPOLLIN的句柄函数问ctrl_connect_handler。
3.init_mp初始化memory pressure相关参数,创建一个用于事件通知的文件句柄,加入到epollfd,EPOLLIN的处理函数为mp_event。
init_mp将memory.presure_level的句柄,和创建用于本进程事件通知的evfd,然后和levelstr一起写入cgroup.event_control。
static int init_mp(char *levelstr, void *event_handler) mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level", O_RDONLY | O_CLOEXEC); evctlfd = open(MEMCG_SYSFS_PATH "cgroup.event_control", O_WRONLY | O_CLOEXEC); evfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); 参见末尾参考资料,eventfd用于创建本进程事件通知的文件句柄。 ret = snprintf(buf, sizeof(buf), "%d %d %s", evfd, mpfd, levelstr); ??? ret = write(evctlfd, buf, strlen(buf) + 1); epev.events = EPOLLIN; err: |
ctrl_connect_handler是lmkd socket相关句柄函数,accept之后又会创建ctrl_dfd句柄。如果是EPOLLHUP,则关闭ctrl_dfd;如果是EPOLLIN,则会根据cmd类型进行不同处理。
static void ctrl_data_handler(uint32_t events) { |
LMK_TARGET类型对应cmd_targt,用于设置"/sys/module/lowmemorykiller/parameters/minfree"和"/sys/module/lowmemorykiller/parameters/adj"。
LMK_PROCPRIO类型对应cmd_procprio,用于写入/proc/xxx/oom_score_adj,并将pid加入pidhash表中。
LMK_PROCREMOVE类型对应cmd_procremove,用于将pid从pidhash中移除。
在vmpressure上报low事件后,lmkd就会触发mp_event处理memory pressure相关事件。mp_event就是low的处理函数,通过kill进程来释放内存空间。
static void mp_event(uint32_t events __unused) { ret = read(mpevfd, &evcount, sizeof(evcount)); if (time(NULL) - kill_lasttime < KILL_TIMEOUT) while (zoneinfo_parse(&mi) < 0) { other_free = mi.nr_free_pages - mi.totalreserve_pages; 基于zoneinfo解析,计算出other_free和other_file两个参数,用于选取待kill的进程。 do { |
find_and_kill_process根据other_free和other_file两个参数,确定在哪个adj组中寻找进程。然后寻找最近使用进程kill。
static int find_and_kill_process(int other_free, int other_file, bool first) for (i = 0; i < lowmem_targets_size; i++) { lowmem_minfree和lowmem_adj是从/sys/module/lowmemorykiller/parameters/minfree和/sys/module/lowmemorykiller/parameters/adj中解析出来的。释放内存以达到最低使用内存,adj从0到906,每一个adj都有对应的最低内存,逐级释放。
if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) { retry: if (procp) { return 0; |
lowmemorykiller分析
lowmemorykiller作为内核一个module,输入参数有如下:
/sys/module/lowmemorykiller/parameters/adj 0,100,200,300,900,906 |
adj文件包含oom_adj的阈值,minfree存放着对应的阈值,以page为单位。当对应的minfree值达到,则进程的oom_adj如果大于这个值将被杀掉。
ProcessList.java中定义的mOomAdj的值通过writeLmkd写入sysfs节点,和上面对应:
private final int[] mOomAdj = new int[] { // These are the low-end OOM level limits. This is appropriate for an |
在frameworks/base/services/core/java/com/android/server/am/ProcessList.java中定义了,不同类型进程对应的adj值:
static final int CACHED_APP_MAX_ADJ = 906; static final int SERVICE_B_ADJ = 800; static final int PREVIOUS_APP_ADJ = 700; static final int HOME_APP_ADJ = 600; static final int SERVICE_ADJ = 500; static final int HEAVY_WEIGHT_APP_ADJ = 400; static final int BACKUP_APP_ADJ = 300; static final int PERCEPTIBLE_APP_ADJ = 200; static final int VISIBLE_APP_ADJ = 100; static final int FOREGROUND_APP_ADJ = 0; static final int PERSISTENT_SERVICE_ADJ = -700; static final int PERSISTENT_PROC_ADJ = -800; static final int SYSTEM_ADJ = -900; static final int NATIVE_ADJ = -1000; |
lowmem_init是整个模块的入口,主要注册一个shrinker,lowmem_shrinker。shrinker是内核内存回收机制。
static struct shrinker lowmem_shrinker = { |
lowmem_scan是shrinker的核心:
static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc) if (lowmem_adj_size < array_size) lowmem_print(3, "lowmem_scan %lu, %x, ofree %d %d, ma %hd\n", if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) { selected_oom_score_adj = min_score_adj; rcu_read_lock(); if (tsk->flags & PF_KTHREAD) p = find_lock_task_mm(tsk); if (test_tsk_thread_flag(p, TIF_MEMDIE) && task_lock(selected); lowmem_print(4, "lowmem_scan %lu, %x, return %lu\n", |
每一个进程都有oom_adj/oom_score/oom_score_adj节点,
oom_adj -13 oom_adj=oom_score_adj*17/1000=800*17/1000=13.6 |
CGroup memory子系统参数详解
要理解memory.pressure_level,就要从何为Memory Pressure开始。
pressure_level通知可以被用来监控内存分配代价;基于不同的pressure_level,采取不同的策略管理内存资源。有以下三种pressure_level:
low:系统会采取回收内存给新的内存分配。
medium:系统会使用swap、换出活动文件缓存等方式来腾空内存
critical:表示系统此时已经OOM或者内核OOM即将触发,应用应该尽可能采取措施腾出内存空间。
pressure_level出发后产生的events会向上传播,直到被处理。比如三个cgroup:A->B->C。A、B、C都有事件监听器,此时C触发了memory pressure。这种情况下,C会受到通知,而A和B则不会。这是为了避免此类消息广播,进而打断系统。
memory.pressure_level只是被用来设置eventfd,节点的读写操作都没有实现,所以在sysfs中无从获得信息。下面是一个使用示例:
- 使用eventfd创建一个evfd句柄
- 打开memory.pressure_level节点mpfd
- 将“<evfd> <mpfd> <level>”组成的字符串写入cgroup.event_control
那么如果memory pressure达到一定level(low/medium/critical),相关应用就会通过eventfd被通知到。下面是lmkd中的一个实现:
static int init_mp(char *levelstr, void *event_handler) mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level", O_RDONLY | O_CLOEXEC); epev.events = EPOLLIN; |
所以重点就转到分析cgroup.event_control
static struct cftype mem_cgroup_legacy_files[] = { } |
memcg_write_event_control解析lmkd写入的字符串,然后注册cgroup的事件处理函数。
static ssize_t memcg_write_event_control(struct kernfs_open_file *of, buf = strstrip(buf); efd = simple_strtoul(buf, &endp, 10); 解析出eventfd文件句柄 cfd = simple_strtoul(buf, &endp, 10); 解析出字符串的第二个参数句柄 event = kzalloc(sizeof(*event), GFP_KERNEL); event->memcg = memcg; efile = fdget(efd); event->eventfd = eventfd_ctx_fileget(efile.file); cfile = fdget(cfd); /* the process need read permission on control file */ /* if (!strcmp(name, "memory.usage_in_bytes")) { 根据第二个参数文件名,选择不同注册/去注册函数。 /* ret = event->register_event(memcg, event->eventfd, buf); 执行注册 efile.file->f_op->poll(efile.file, &event->pt); spin_lock(&memcg->event_list_lock); fdput(cfile); return nbytes; out_put_css: return ret; |
vmpressure_register_event会将vmpressure通知和eventfs绑定,这样lmkd就会收到vmpressure的通知了。
memcg:需要关注vmpressure通知的CGroup子系统memory
eventfd:接收vmpressure通知的eventfd句柄
args:设置pressure_level参数
int vmpressure_register_event(struct mem_cgroup *memcg, for (level = 0; level < VMPRESSURE_NUM_LEVELS; level++) { if (level >= VMPRESSURE_NUM_LEVELS) ev = kzalloc(sizeof(*ev), GFP_KERNEL); ev->efd = eventfd; mutex_lock(&vmpr->events_lock); return 0; |
关于Memory Pressure深度阅读参考:Documents/cgroups/memory.txt 第11小节 Memory Pressure
这里有涉及到一个概念vmpressure。应用不会去关注系统有多少可用空间,但是作为一个整体的系统如果能对内存紧缺进行通知,并让应用采取相关措施以减少内存分配。vmpressure就是这样一种机制,通过vmpressure内核能够通知用户空间,系统当前处于何种memory pressure等级。
应用?
整个框架提供的配置参数就是应用的切入点:
根据内存大小?屏幕分辨率?…情况配置不同的minfree值。
增加adj个数,增加lowmemorykiller的控制粒度;或者修改adj大小,改变不同类型进程的优先级。
memory pressure的levelstr,low?medium?critical?进行不同的处理?
修改vmpressure触发不同level的条件?
参考资料
mlockall/munlockall:http://pubs.opengroup.org/onlinepubs/007908799/xsh/mlockall.html
mlockall函数:http://blog.csdn.net/zhjutao/article/details/8652252
event:http://www.man7.org/linux/man-pages/man2/eventfd.2.html
Memory Pressure:https://linux-mm.org/Memory_pressure
vmpressure_fd:https://lwn.net/Articles/524742/
Android中基于CGroup的memory子系统HAL层分析-lmkd的更多相关文章
- Android零基础入门第35节:Android中基于回调的事件处理
原文:Android零基础入门第35节:Android中基于回调的事件处理 通过前面两期掌握了Android中基于监听的事件处理的五种形式,那么本期一起来学习Android中基于回调的事件处理. 一. ...
- Android零基础入门第34节:Android中基于监听的事件处理
原文:Android零基础入门第34节:Android中基于监听的事件处理 上一期我们学习了Android中的事件处理,也详细学习了Android中基于监听的事件处理,同时学会了匿名内部类形式,那么本 ...
- android中使用jni对字符串加解密实现分析
android中使用jni对字符串加解密实现分析 近期项目有个需求.就是要对用户的敏感信息进行加密处理,比方用户的账户password,手机号等私密信息.在java中,就对字符串的加解密我们能够使用A ...
- Android中基于Socket的网络通信
1. Socket介绍 2. ServerSocket的建立与使用 3. 使用ServerSocket建立聊天服务器-1 4. 使用ServerSocket建立聊天服务器-2 5. 在Android中 ...
- Android 中基于 Binder的进程间通信
摘要:对 Binder 工作机制进行了分析. 首先简述 Android 中 Binder 机制与传统的 Linux 进程间的通信比较,接着对基于 Binder 进程间通信的过程分析 最后结合开发实例 ...
- 【转】Android中实现IPC的几种方式详细分析及比较
1.使用Bundle ----> 用于android四大组件间的进程间通信android的四大组件都可使用Bundle传递数据 所以如果要实现四大组件间的进程间通信 完全可以使用Bundl ...
- Android native进程间通信实例-binder篇之——HAL层访问JAVA层的服务
有一天在群里聊天的时候,有人提出一个问题,怎样才能做到HAL层访问JAVA层的接口?刚好我不会,所以做了一点研究. 之前的文章末尾部分说过了service call 可以用来调试系统的binder服务 ...
- Android中View绘制流程以及invalidate()等相关方法分析
[原文]http://blog.csdn.net/qinjuning 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简 ...
- Android中View绘制流程以及invalidate()等相关方法分析(转载的文章,出处在正文已表明)
转载请注明出处:http://blog.csdn.net/qinjuning 前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时 ...
随机推荐
- Moon.Orm 常见查询实例
一.Moon.Orm框架总述 (您还用hibernate?实体框架?) 1.框架名:Moon 意思是月亮,而非Mono.因为很喜欢明月,所以以此为名.它是一个.NET下的Orm框架. 2.发展历史:历 ...
- CatchPacket网络抓包软件
CatchPacket网络抓包软件 qq 22945088431.技术特点:基于WinPcap库,c# winform2.实现获取机器所有网卡,可任意选择监听3.可以捕获常见网络协议arp dns ...
- arcengine中自定义工具和自带工具条(ICommand)点击后和其他工具使用的冲突
自己系统中本身对于放大缩小等功能直接是单独重写的,但是如果在加一个工具条具有相同功能的话两者之间会有一些冲突,为解决该冲突可以重写工具条的OnItemClick事件 该工具条命名为axTool 我本身 ...
- @Autowired注解的使用
使用Spring时,通过Spring注入的Bean一般都被定义成private,并且要有getter和setter方法,显得比较繁琐,增加了代码量,而且有时会搞忘造成错误. 可以使用@Autowire ...
- Effective java笔记(一),创建与销毁对象
1.考虑用静态工厂方法代替构造器 类的一个实例,通常使用类的公有的构造方法获取.也可以为类提供一个公有的静态工厂方法(不是设计模式中的工厂模式)来返回类的一个实例.例如: //将boolean类型转换 ...
- linux(十二)___Apache服务器用户认证、虚拟主机的配置
创建xiangkejin zhangsan两个用户 可看见文件中创建的两个用户: 建立虚拟目录并配置用户认证 ①建立虚拟目录 /xiangkejin ②在Apache的主配置文件httpd.conf ...
- 如何在web中实现类似excel的表格控件
Execl功能非常强大,内置的很多函数或公式可以大大提高对数据的加工处理能力.那么在web中有没有类似的控件呢?经过一番搜寻,发现handsontable具备了基本的excel功能支持公式,同时能对数 ...
- IIS初始化(预加载),解决第一次访问慢,程序池被回收问题
你以为你可以慢,那是不可能的!你以为你可以不动,那也是不可能的! 河南是守株待兔故事情节的发源地,讲的是懒惰的农夫坐在树桩旁等待可爱的小毛兔撞树的故事,那么这种事情怎么可能天天出现呢!你以为的事并一定 ...
- 基于Nuclear的Web组件-Todo的十一种写法
刀耕火种 刀耕火种是新石器时代残留的农业经营方式.又称迁移农业,为原始生荒耕作制. var TodoApp = Nuclear.create({ add: function (evt) { evt.p ...
- Atitit.uke 团队建设的组织与运营之道attilax总结
Atitit.uke 团队建设的组织与运营之道attilax总结 1. intro引言:2 2. aims组织成立宗旨2 1.1. Mission组织使命2 1.2. val核心价值观2 1.3. c ...