Android–LMK机制分析 一、目标 本篇文章主要分析 Andoird 核心机制与服务中的 ——LMK 机制,低内存管理机制(根据需要杀死进程来释放需要的内存)。源码分析主要通过 http://androidxref.com/,Android 版本为 Marshmallow-Android 6.0.1_r10, 内核版本为 3.18。
源码位置如下:
源代码名称
路径位置
lowmemorykiller.c
/drivers/staging/android/lowmemorykiller.c
lmkd.c
/system/core/lmkd/lmkd.c
ProcessList.java
/frameworks/base/services/core/java/com/android/server/am/ProcessList.java
ActivityManagerService.java
/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
二、LMK概述 1、为什么引入LowmemoryKiller 进程的启动分冷启动和热启动,当用户退出某一个进程的时候,并不会真正的将进程退出,而是将这个进程放到后台,以便下次启动的时候可以马上启动起来,这个过程名为热启动,这也是Android的设计理念之一。这个机制会带来一个问题,每个进程都有自己独立的内存地址空间,随着应用打开数量的增多,系统已使用的内存越来越大,就很有可能导致系统内存不足。为了解决这个问题,系统引入LowmemoryKiller(简称lmk)管理所有进程,根据一定策略 来kill某个进程并释放占用的内存,保证系统的正常运行。
2、 LMK基本原理 所有应用进程都是从zygote孵化出来的,记录在AMS中mLruProcesses列表中,由AMS进行统一管理,AMS中会根据进程的状态更新进程对应的oom_adj值,这个值会通过文件传递到kernel中去,kernel有个低内存回收机制,在内存达到一定阀值时会触发清理oom_adj值高的进程腾出更多的内存空间,这就是Lowmemorykiller工作原理。
三、核心机制源码解读 Framework层通过调整adj的值和阈值数组,输送给kernel中的lmk,为lmk提供杀进程的原材料 ,因为用户空间和内核空间相互隔离,就采用了文件节点进行通讯,用socket将adj的值与阈值数组传给lmkd(5.0之后不在由AMS直接与lmk通信,引入lmkd守护进程),lmkd将这些值写到内核节点中。lmk通过读取这些节点,实现进程的kill,所以整个lmk机制大概可分成三层。
3.1 Framework层 位于ProcessList.java中定义了3种命令类型,这些文件的定义必须跟lmkd.c定义完全一致,格式分别如下:
1 2 3 LMK_TARGET <minfree> <minkillprio> ... (up to 6 pairs) LMK_PROCPRIO <pid> <prio> LMK_PROCREMOVE <pid>
上述3个命令的使用都通过ProcessList.java中的如下方法:
功能
命令
对应方法
LMK_PROCPRIO
设置进程adj
PL.setOomAdj()
LMK_TARGET
更新oom_adj
PL.updateOomLevels()
LMK_PROCREMOVE
移除进程
PL.remove()
AMS中与adj调整的有三个核心的方法,如下
AMS.updateConfiguration:更新窗口配置,这个过程中,分别向/sys/module/lowmemorykiller/parameters目录下的minfree和adj节点写入相应数值;
AMS.applyOomAdjLocked:应用adj,当需要杀掉目标进程则返回false;否则返回true,这个过程中,调用setOomAdj(),向/proc/pid/oom_score_adj写入oom_adj 后直接返回;
AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked:进程死亡后,调用remove(),直接返回;
3.1.1 AMS.updateConfiguration 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public boolean updateConfiguration(Configuration values) { synchronized(this) { if (values == null && mWindowManager != null) { // sentinel: fetch the current configuration from the window manager values = mWindowManager.computeNewConfiguration(DEFAULT_DISPLAY); } if (mWindowManager != null) { // Update OOM levels based on display size. mProcessList.applyDisplaySize(mWindowManager); } ..... } }
mProcessList是ProcessList对象,调用applyDisplaySize方法,基于屏幕尺寸,更新LMK的水位线
1 2 3 4 5 6 7 8 9 10 11 12 13 /frameworks/base/services/core/java/com/android/server/am/ProcessList.java 198 void applyDisplaySize(WindowManagerService wm) { 199 if (!mHaveDisplaySize) { 200 Point p = new Point(); 201 // TODO(multi-display): Compute based on sum of all connected displays' resolutions. 202 wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p); 203 if (p.x != 0 && p.y != 0) { //传入屏幕的尺寸 204 updateOomLevels(p.x, p.y, true); 205 mHaveDisplaySize = true; 206 } 207 } 208 }
传入屏幕的尺寸更新水位线,逻辑很简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 210 private void updateOomLevels(int displayWidth, int displayHeight, boolean write) { 211 // Scale buckets from avail memory: at 300MB we use the lowest values to 212 // 700MB or more for the top values. 213 float scaleMem = ((float)(mTotalMemMb-350))/(700-350); 214 215 //根据屏幕大小计算出scale 216 int minSize = 480*800; // 384000 217 int maxSize = 1280*800; // 1024000 230400 870400 .264 218 float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize); //google代码就是这么写的,表示不好评价了 219 if (false) { 220 Slog.i("XXXXXX", "scaleMem=" + scaleMem); 221 Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth 222 + " dh=" + displayHeight); 223 } 224 225 float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp; 226 if (scale < 0) scale = 0; 227 else if (scale > 1) scale = 1; 228 int minfree_adj = Resources.getSystem().getInteger( 229 com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust); 230 int minfree_abs = Resources.getSystem().getInteger( 231 com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute); 232 if (false) { 233 Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs); 234 } 235 236 final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0; 237 //通过下面的运算,将mOomMinFreeLow和mOomMinFreeHigh经过运算 // 最后得出的 值存入mOomMinFree中,而如何计算这个值,是根据当前屏幕的分辨率和内存大小来 238 for (int i=0; i<mOomAdj.length; i++) { 239 int low = mOomMinFreeLow[i]; 240 int high = mOomMinFreeHigh[i]; 241 if (is64bit) { 242 // 64-bit机器会high增大 243 if (i == 4) high = (high*3)/2; 244 else if (i == 5) high = (high*7)/4; 245 } 246 mOomMinFree[i] = (int)(low + ((high-low)*scale)); 247 } ....... 287 288 if (write) { 289 ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1)); 290 buf.putInt(LMK_TARGET); 291 for (int i=0; i<mOomAdj.length; i++) { 292 buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);//五个水位线 293 buf.putInt(mOomAdj[i]);//与上面水位线对应的五个adj数值 294 } 295 //将AMS已经计算好的值通过socket发送到lmkd 296 writeLmkd(buf); 297 SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve)); 298 } 299 // GB: 2048,3072,4096,6144,7168,8192 300 // HC: 8192,10240,12288,14336,16384,20480 301 }
这里携带的命令协议是LMK_TARGET,它对应到kernel里面执行的函数是cmd_target,要求kernel干的事情就是更新两面两个文件
1 2 /sys/module/lowmemorykiller/parameters/minfree /sys/module/lowmemorykiller/parameters/adj
minfree文件中的值可以理解成五个水位线,而adj这个文件中的值与minfree文件中的数值一一对应,意味着到达什么样的水位线,杀死对应数值的进程。而AMS里面就是通过调用applyDisplaySize方法,基于屏幕尺寸以及机器的CPU位数,更新LMK的水位线的。
3.2.2 AMS.applyOomAdjLocked 再看applyOomAdjLocked方法,这个方法的作用是应用adj,这个过程中,调用setOomAdj(),向/proc/pid/oom_score_adj写入oom_adj 后直接返回;系统中更新adj的操作很频繁,四大组件的生命周期都会影响着adj的值。而更新adj一般由applyOomAdjLocked完成。
下面是AMS中adj的定义:
在Android M之后adj数值变大了,因为这样adj可以更加细化了,即使相同进程,不同任务栈的adj也可以不一样。从Android P开始,进一步细化ADJ级别,增加了VISIBLE_APP_LAYER_MAX(99),是VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200)之间有99个槽,则可见级别ADJ的取值范围为[100,199]。 算法会根据其所在task的mLayerRank来调整其ADJ,100加上mLayerRank就等于目标ADJ,layer越大,则ADJ越小。
AMS调用applyOomAdjLocked更新adj。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 22000 private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now, long nowElapsed) { ......... 22009 22010 if (app.curAdj != app.setAdj) { //之前的adj不等于计算的adj,需要更新 22011 ProcessList.setOomAdj(app.pid, app.uid, app.curAdj); 22012 if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) { 22013 String msg = "Set " + app.pid + " " + app.processName + " adj " 22014 + app.curAdj + ": " + app.adjType; 22015 reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); 22016 } 22017 app.setAdj = app.curAdj; 22018 app.verifiedAdj = ProcessList.INVALID_ADJ; 22019 } ......... 22020 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /frameworks/base/services/core/java/com/android/server/am/ProcessList.java 630 public static final void setOomAdj(int pid, int uid, int amt) { 631 if (amt == UNKNOWN_ADJ) 632 return; 633 634 long start = SystemClock.elapsedRealtime(); 635 ByteBuffer buf = ByteBuffer.allocate(4 * 4); 636 buf.putInt(LMK_PROCPRIO); 637 buf.putInt(pid); 638 buf.putInt(uid); 639 buf.putInt(amt); //将AMS已经计算好的adj值通过socket发送到lmkd 640 writeLmkd(buf); 641 long now = SystemClock.elapsedRealtime(); 642 if ((now-start) > 250) { 643 Slog.w("ActivityManager", "SLOW OOM ADJ: " + (now-start) + "ms for pid " + pid 644 + " = " + amt); 645 } 646 }
这里携带的命令协议是LMK_PROCPRIO,对应kernel里面cmd_procprio函数,要求kernel干的事情是—把AMS发送过来的adj值更新到下面的文件中去。这样内存紧张的时候,LMK就会遍历内核中进程列表,杀死相应adj的进程了。
3.1.3、 AMS.cleanUpApplicationRecordLocked & AMS.handleAppDiedLocked 进程死掉后,会调用该进程的ProcessList.remove方法,也会通过Socket通知lmkd更新adj。
1 2 3 4 5 6 7 /frameworks/base/services/core/java/com/android/server/am/ProcessList.java 651 public static final void remove(int pid) { 652 ByteBuffer buf = ByteBuffer.allocate(4 * 2); 653 buf.putInt(LMK_PROCREMOVE); 654 buf.putInt(pid); 655 writeLmkd(buf); 656 }
这里携带的命令协议是LMK_PROCREMOVE,对应kernel里面的cmd_procremove函数,要求kernel干的事情是,当进程死亡了,删除/proc/下面的文件。
上面三大方法最后都是通过writeLmkd与lmkd通信,现在看看writeLmkd中怎么和lmkd通信的,首先需要打开与lmkd通信的socket,lmkd创建名称为lmkd的socket,节点位于/dev/socket/lmkd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 658 private static boolean openLmkdSocket() { 659 try { 660 sLmkdSocket = new LocalSocket(LocalSocket.SOCKET_SEQPACKET); 661 sLmkdSocket.connect( 662 new LocalSocketAddress("lmkd", 663 LocalSocketAddress.Namespace.RESERVED)); 664 sLmkdOutputStream = sLmkdSocket.getOutputStream(); 665 } catch (IOException ex) { 666 Slog.w(TAG, "lowmemorykiller daemon socket open failed"); 667 sLmkdSocket = null; 668 return false; 669 } 670 671 return true; 672 }
当sLmkdSocket创建之后,就用它来发送数据到对端(lmkd)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 674 private static void writeLmkd(ByteBuffer buf) { 675 //尝试三次 676 for (int i = 0; i < 3; i++) { 677 if (sLmkdSocket == null) { 678 if (openLmkdSocket() == false) { 679 try { 680 Thread.sleep(1000); 681 } catch (InterruptedException ie) { 682 } 683 continue; 684 } 685 } 686 687 try { 688 sLmkdOutputStream.write(buf.array(), 0, buf.position()); 689 return; 690 } catch (IOException ex) { 691 Slog.w(TAG, "Error writing to lowmemorykiller socket"); 692 693 try { 694 sLmkdSocket.close(); 695 } catch (IOException ex2) { 696 } 697 698 sLmkdSocket = null; 699 } 700 } 701 } 702}
3.1.4 小结 LowmemoryKiller核心原理就是Framework层通过调整adj的值和阈值数组,输送给kernel中的lmk,为lmk提供杀进程的原材料。AMS中给lmkd发送数据原材料有三个入口,对应携带的也有三种命令协议,每种协议代表内核中一种数据的控制方式,如下表:
功能
AMS对应方法
命令
内核对应函数
LMK_PROCPRIO
PL.setOomAdj()
设置指定进程的优先级,也就是oom_score_adj
cmd_procprio
LMK_TARGET
PL.updateOomLevels()
更新/sys/module/lowmemorykiller/parameters/中的minfree以及adj
cmd_target
LMK_PROCREMOVE
PL.remove()
移除进程
cmd_procremove
3.2 lmkd机制分析 3.2.1 lmkd的启动 跟大多数守护进程一样,lmkd 也是由 init 进程启动的:
1 2 3 4 5 6 7 8 9 10 11 service lmkd /system/bin/lmkd class core user lmkd group lmkd system readproc capabilities DAC_OVERRIDE KILL IPC_LOCK SYS_NICE SYS_RESOURCE critical socket lmkd seqpacket+passcred 0660 system system writepid /dev/cpuset/system-background/tasks on property:lmkd.reinit=1 exec_background /system/bin/lmkd --reinit
这里创建的 socket lmkd 的 user/group 都是 system,而它的权限是 0660,所以只有 system 应用才能读写(一般是 activity manager)。
接下来的 writepid 跟 Linux 的 cgroups 相关。
3.2.2 lmkd的main方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /system/core/lmkd/lmkd.c 890int main(int argc __unused, char **argv __unused) { 891 struct sched_param param = { 892 .sched_priority = 1, 893 }; 894 ...... 902 903 mlockall(MCL_FUTURE); //指定进程的调度策略, FIFO方式的实时调度策略 904 sched_setscheduler(0, SCHED_FIFO, ¶m); //做一些初始化 905 if (!init()) //进入主循环,等待AMS发送的请求 906 mainloop(); 907 908 ALOGI("exiting"); 909 return 0; 910} 911
3.2.3 lmkd的数据结构 下面所有的代码都在lmkd.c文件中
1 2 48#define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree" 49#define INKERNEL_ADJ_PATH "/sys/module/lowmemorykiller/parameters/adj"
minfree和adj文件分别表示水位线和水位线对应的adj。
1 2 3 4 5 6 90/* OOM score values used by both kernel and framework */ 91#define OOM_SCORE_ADJ_MIN (-1000) 92#define OOM_SCORE_ADJ_MAX 1000 93 94static int lowmem_adj[MAX_TARGETS]; 95static int lowmem_minfree[MAX_TARGETS];
minfree和adj文件中的值实质是来自lowmem_minfree和lowmem_adj两个数组。lowmem_minfree[]和lowmem_adj[]数组大小个数都为6。
1 2 3 4 5 6 53enum lmk_cmd { 54 LMK_TARGET, 55 LMK_PROCPRIO, 56 LMK_PROCREMOVE, 57};
枚举代表三种命令协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 105struct adjslot_list { 106 struct adjslot_list *next; 107 struct adjslot_list *prev; 108}; 109 110struct proc { 111 struct adjslot_list asl; 112 int pid; 113 uid_t uid; 114 int oomadj; 115 struct proc *pidhash_next; 116}; 117
在AMS中进程的数据结构是ProcessRocord,在lmkd中进程的数据结构是proc,adjslot_list是双向链表。
1 2 3 #define ADJTOSLOT(adj) (adj + -OOM_SCORE_ADJ_MIN) static struct adjslot_list procadjslot_list[ADJTOSLOT(OOM_SCORE_ADJ_MAX) + 1];
procadjslot_list是一个双向的链表,数组的下标index就是进程的优先级 ,系统中同一个时刻,有很多进程的优先级都是相同的,那么根据指定的优先级就能从数组中获取一个链表,这个链表上的所有proc的优先级都是相同的,根据这个链表进一步选择杀掉哪些进程。由于进程的优先级可能是一个负数,所以加上了一个-OOM_SCORE_ADJ_MIN(1000)。
3.2.4 lmkd的初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 /system/core/lmkd/lmkd.c 809static int init(void) { 810 struct epoll_event epev; 811 int i; 812 int ret; ..... 824 //1、拿到socket的fd 825 ctrl_lfd = android_get_control_socket("lmkd"); 826 if (ctrl_lfd < 0) { 827 ALOGE("get lmkd control socket failed"); 828 return -1; 829 } 830 //2、监听 831 ret = listen(ctrl_lfd, 1); 832 if (ret < 0) { 833 ALOGE("lmkd control socket listen failed (errno=%d)", errno); 834 return -1; 835 } 836 837 epev.events = EPOLLIN; //3、ctrl_connect_handler中主要完成soclet的accpet以及数据read,当监听到socket连接事件后会调用ctrl_connect_handler方法 838 epev.data.ptr = (void *)ctrl_connect_handler; 839 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_lfd, &epev) == -1) { 840 ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)", errno); 841 return -1; 842 } 843 maxevents++; ..... //通过判断文件是否可读来给use_inkernel_interface赋值,默认为1 use_inkernel_interface = !access(INKERNEL_MINFREE_PATH, W_OK); //4、初始化链表 857 for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) { 858 procadjslot_list[i].next = &procadjslot_list[i]; 859 procadjslot_list[i].prev = &procadjslot_list[i]; 860 } 861 862 return 0; 863}
3.2.5 lmkd的mainloop方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /system/core/lmkd/lmkd.c 865static void mainloop(void) { 866 while (1) { 867 struct epoll_event events[maxevents]; 868 int nevents; 869 int i; 870 871 ctrl_dfd_reopened = 0; //epollfd:由epoll_create 生成的epoll专用的文件描述符; //events:用于回传代处理事件的数组; //maxevents:每次能处理的事件数; //timeout:等待I/O事件发生的超时值(单位我也不太清楚);-1相当于阻塞,0相当于非阻塞。一般用-1即可 872 nevents = epoll_wait(epollfd, events, maxevents, -1); 873 874 if (nevents == -1) { 875 if (errno == EINTR) 876 continue; 877 ALOGE("epoll_wait failed (errno=%d)", errno); 878 continue; 879 } 880 881 for (i = 0; i < nevents; ++i) { 882 if (events[i].events & EPOLLERR) 883 ALOGD("EPOLLERR on event #%d", i); 884 if (events[i].data.ptr) 885 (*(void (*)(uint32_t))events[i].data.ptr)(events[i].events); 886 } 887 } 888}
调用epoll_wait阻塞,等待socket事件的到来
3.2.6 ctrl_command_handler函数对上层command的分发 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 345static void ctrl_command_handler(void) { 346 int ibuf[CTRL_PACKET_MAX / sizeof(int)]; 347 int len; 348 int cmd = -1; 349 int nargs; 350 int targets; 351 // 读取socket管道信息 352 len = ctrl_data_read((char *)ibuf, CTRL_PACKET_MAX); 353 if (len <= 0) 354 return; 355 356 nargs = len / sizeof(int) - 1; 357 if (nargs < 0) 358 goto wronglen; 359 // 获取buffer中的命令协议 360 cmd = ntohl(ibuf[0]); 361 362 switch(cmd) { //处理LMK_TARGET事件,设置水位线,也就是更新/sys/module/lowmemorykiller/parameters/中的minfree以及adj 363 case LMK_TARGET: 364 targets = nargs / 2; 365 if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj)) 366 goto wronglen; 367 cmd_target(targets, &ibuf[1]); 368 break; //处理LMK_PROCPRIO事件,根据pid,设置指定进程的优先级,也就是oom_score_adj 369 case LMK_PROCPRIO: 370 if (nargs != 3) 371 goto wronglen; 372 cmd_procprio(ntohl(ibuf[1]), ntohl(ibuf[2]), ntohl(ibuf[3])); 373 break; //处理LMK_PROCREMOVE事件,根据pid,移除进程, 374 case LMK_PROCREMOVE: 375 if (nargs != 1) 376 goto wronglen; 377 cmd_procremove(ntohl(ibuf[1])); 378 break; 379 default: 380 ALOGE("Received unknown command code %d", cmd); 381 return; 382 } 383 384 return; 385 386wronglen: 387 ALOGE("Wrong control socket read length cmd=%d len=%d", cmd, len); 388}
在init中注册了ctrl_connect_handler的回调函数,然后ctrl_connect_handler->ctrl_data_handler-> ctrl_command_handler的调用,对上层的command命令进行不同的处理。
3.2.7 LMK_TARGET命令— cmd_target 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 http://androidxref.com/8.0.0_r4/xref/system/core/lmkd/lmkd.c 284static void cmd_target(int ntargets, int *params) { 285 int i; 286 287 if (ntargets > (int)ARRAY_SIZE(lowmem_adj)) 288 return; 289 //注释1 290 for (i = 0; i < ntargets; i++) { 291 lowmem_minfree[i] = ntohl(*params++); 292 lowmem_adj[i] = ntohl(*params++); 293 } 294 295 lowmem_targets_size = ntargets; 296 //是否使用kernel空间的处理逻辑 297 if (use_inkernel_interface) { 298 char minfreestr[128]; 299 char killpriostr[128]; 300 301 minfreestr[0] = '\0'; 302 killpriostr[0] = '\0'; 303 304 for (i = 0; i < lowmem_targets_size; i++) { 305 char val[40]; 306 307 if (i) { 308 strlcat(minfreestr, ",", sizeof(minfreestr)); 309 strlcat(killpriostr, ",", sizeof(killpriostr)); 310 } 311 312 snprintf(val, sizeof(val), "%d", lowmem_minfree[i]); 313 strlcat(minfreestr, val, sizeof(minfreestr)); 314 snprintf(val, sizeof(val), "%d", lowmem_adj[i]); 315 strlcat(killpriostr, val, sizeof(killpriostr)); 316 } 317 318 writefilestring(INKERNEL_MINFREE_PATH, minfreestr); 319 writefilestring(INKERNEL_ADJ_PATH, killpriostr); 320 } 321}
注释1中的for是将参数读出来,这些参数来自与哪里呢?在上篇博客写过,其实是和下面的代码的for一一对应的,用lowmem_minfree[i] 数组保存水位线,用 lowmem_adj保存每条水位线对应的adj。其中有一个很关键的变量use_inkernel_interface,这个代表是否要使用kernel中的逻辑,默认是等于1的,意味着需要使用kernel中的逻辑,如果不等于1,那么就采用用户空间的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 287 288 if (write) { 289 ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1)); 290 buf.putInt(LMK_TARGET); 291 for (int i=0; i<mOomAdj.length; i++) { 292 buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);//五个水位线 293 buf.putInt(mOomAdj[i]);//与上面水位线对应的五个adj数值 294 } 295 //将AMS已经计算好的值通过socket发送到lmkd 296 writeLmkd(buf); 297 SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve)); 298 } 299 // GB: 2048,3072,4096,6144,7168,8192 300 // HC: 8192,10240,12288,14336,16384,20480 301 }
将生成好的string写入到文件节点minfree以及adj
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 220static void writefilestring(char *path, char *s) { 221 int fd = open(path, O_WRONLY | O_CLOEXEC); 222 int len = strlen(s); 223 int ret; 224 225 if (fd < 0) { 226 ALOGE("Error opening %s; errno=%d", path, errno); 227 return; 228 } 229 230 ret = write(fd, s, len); 231 if (ret < 0) { 232 ALOGE("Error writing %s; errno=%d", path, errno); 233 } else if (ret < len) { 234 ALOGE("Short write on %s; length=%d", path, ret); 235 } 236 237 close(fd); 238} 239
3.2.8 LMK_PROCPRIO命令— cmd_procprio 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 http://androidxref.com/8.0.0_r4/xref/system/core/lmkd/lmkd.c 240static void cmd_procprio(int pid, int uid, int oomadj) { 241 struct proc *procp; 242 char path[80]; 243 char val[20]; 244 245 if (oomadj < OOM_SCORE_ADJ_MIN || oomadj > OOM_SCORE_ADJ_MAX) { 246 ALOGE("Invalid PROCPRIO oomadj argument %d", oomadj); 247 return; 248 } 249 250 snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid); 251 snprintf(val, sizeof(val), "%d", oomadj); //写到文件中 252 writefilestring(path, val); 253 254 if (use_inkernel_interface) 255 return; 256 //从hashtable找到对应的进程 257 procp = pid_lookup(pid); 258 if (!procp) { //如果没有找到,分配一个结点,调用proc_insert插入hashtable中 259 procp = malloc(sizeof(struct proc)); 260 if (!procp) { 261 // Oh, the irony. May need to rebuild our state. 262 return; 263 } 264 265 procp->pid = pid; 266 procp->uid = uid; 267 procp->oomadj = oomadj; 268 proc_insert(procp); 269 } else { //如果已经存在,将原来优先级的proc移除,然后新的优先级的proc添加到双向链表中 270 proc_unslot(procp); 271 procp->oomadj = oomadj; 272 proc_slot(procp); 273 } 274}
这段逻辑也很清晰,就是更新进程的oom_score_adj
3.2.9 LMK_PROCREMOVE命令—cmd_procremove 进程死掉后,会调用该进程的ProcessList.remove方法,也会通过Socket通知lmkd更新adj。
1 2 3 4 5 6 7 8 /frameworks/base/services/core/java/com/android/server/am/ProcessList.java 651 public static final void remove(int pid) { 652 ByteBuffer buf = ByteBuffer.allocate(4 * 2); 653 buf.putInt(LMK_PROCREMOVE); 654 buf.putInt(pid); 655 writeLmkd(buf); 656 }
紧接着就会执行pid_remove, 更新hashtable和双向链表
1 2 3 4 5 6 7 8 9 10 http://androidxref.com/8.0.0_r4/xref/system/core/lmkd/lmkd.c 276static void cmd_procremove(int pid) { 277 if (use_inkernel_interface) 278 return; 279 280 pid_remove(pid); 281 kill_lasttime = 0; 282} 283
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 198static int pid_remove(int pid) { 199 int hval = pid_hashfn(pid); 200 struct proc *procp; 201 struct proc *prevp; 202 203 for (procp = pidhash[hval], prevp = NULL; procp && procp->pid != pid; 204 procp = procp->pidhash_next) 205 prevp = procp; 206 207 if (!procp) 208 return -1; 209 210 if (!prevp) 211 pidhash[hval] = procp->pidhash_next; 212 else 213 prevp->pidhash_next = procp->pidhash_next; 214 215 proc_unslot(procp); 216 free(procp); 217 return 0; 218}
3.2.10 lmkd如何杀进程 当use_inkernel_interface不等于1,就需要使用lmkd中杀进程的逻辑,无需使用kernel中的LowmemoryKiller机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 http://androidxref.com/8.0.0_r4/xref/system/core/lmkd/lmkd.c 588/* 589 * Find a process to kill based on the current (possibly estimated) free memory 590 * and cached memory sizes. Returns the size of the killed processes. 591 */ 592static int find_and_kill_process(int other_free, int other_file, bool first) 593{ 594 int i; 595 int min_score_adj = OOM_SCORE_ADJ_MAX + 1; 596 int minfree = 0; 597 int killed_size = 0; 598 599 for (i = 0; i < lowmem_targets_size; i++) { 600 minfree = lowmem_minfree[i]; 601 if (other_free < minfree && other_file < minfree) { 602 min_score_adj = lowmem_adj[i]; 603 break; 604 } 605 } 606 607 if (min_score_adj == OOM_SCORE_ADJ_MAX + 1) 608 return 0; 609 610 for (i = OOM_SCORE_ADJ_MAX; i >= min_score_adj; i--) { 611 struct proc *procp; 612 613retry: 614 procp = proc_adj_lru(i); 615 616 if (procp) { 617 killed_size = kill_one_process(procp, other_free, other_file, minfree, min_score_adj, first); 618 if (killed_size < 0) { 619 goto retry; 620 } else { 621 return killed_size; 622 } 623 } 624 } 625 626 return 0; 627}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 548/* Kill one process specified by procp. Returns the size of the process killed */ 549static int kill_one_process(struct proc *procp, int other_free, int other_file, 550 int minfree, int min_score_adj, bool first) 551{ 552 int pid = procp->pid; 553 uid_t uid = procp->uid; 554 char *taskname; 555 int tasksize; 556 int r; 557 558 taskname = proc_get_name(pid); 559 if (!taskname) { 560 pid_remove(pid); 561 return -1; 562 } 563 564 tasksize = proc_get_size(pid); 565 if (tasksize <= 0) { 566 pid_remove(pid); 567 return -1; 568 } 569 570 ALOGI("Killing '%s' (%d), uid %d, adj %d\n" 571 " to free %ldkB because cache %s%ldkB is below limit %ldkB for oom_adj %d\n" 572 " Free memory is %s%ldkB %s reserved", 573 taskname, pid, uid, procp->oomadj, tasksize * page_k, 574 first ? "" : "~", other_file * page_k, minfree * page_k, min_score_adj, 575 first ? "" : "~", other_free * page_k, other_free >= 0 ? "above" : "below"); 576 r = kill(pid, SIGKILL); 577 killProcessGroup(uid, pid, SIGKILL); 578 pid_remove(pid); 579 580 if (r) { 581 ALOGE("kill(%d): errno=%d", procp->pid, errno); 582 return -1; 583 } else { 584 return tasksize; 585 } 586}
3.2.11 小结 梳理了lmkd这一层,了解了AMS三种command在lmkd进程中是如何处理的。并且注意到三种command都对use_inkernel_interface进行了判断,如果use_inkernel_interface等于1,那么就执行kernel空间的逻辑,lmkd中数据结构也不用更新,也不用lmkd中杀进程的逻辑,全部都交给lmk完成。如果不等于1,那么lmkd就需要自己维护进程的这些数据结构了。
3.3 lowmemorykiller分析 3.3.1 基本原理 在linux中,有一个名为kswapd的内核线程,当linux回收存放分页的时候,kswapd线程将会遍历一张shrinker链表,并执行回调,或者某个app启动,发现可用内存不足时,则内核会阻塞请求分配内存的进程分配内存的过程,并在该进程中去执行lowmemorykiller来释放内存。虽然之前没有接触过,大体的理解就是向系统注册了这个shrinker回调函数之后,当系统空闲内存页面不足时会调用这个回调函数。 struct shrinker的定义在linux/kernel/include/linux/shrinker.h中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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
3.3.2 初始化 shrinker的注册与反注册
1 2 3 4 5 6 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};
1 2 3 4 5 6 7 8 9 10 11 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}
3.3.3 lowmem_count 1 2 3 4 5 6 7 8 static unsigned long lowmem_count(struct shrinker *s, struct shrink_control *sc) { return global_page_state(NR_ACTIVE_ANON) + global_page_state(NR_ACTIVE_FILE) + global_page_state(NR_INACTIVE_ANON) + global_page_state(NR_INACTIVE_FILE); }
ANON代表匿名映射,没有后备存储器;FILE代表文件映射; 内存计算公式= 活动匿名内存 + 活动文件内存 + 不活动匿名内存 + 不活动文件内存
3.3.4 lowmem_scan 注册完成之后,就回调lowmem_scan,这个基本上是lmk核心的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 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}
3.3.5 小结 上面代码就是lowmemorykiller核心原理的实现,可以小总结一下。
首先调用global_page_state,可以获取当前系统可用的(剩余)内存大小
遍历lowmem_minfree数组,根据other_free和other_file的剩余内存的大小,确定当前剩余内存的最小级别的min_score_adj
有了上面的adj之后,遍历所有进程,获取每个进程的rss大小,然后不断循环,每次比较进程占用的内存大小tasksize以及小于oom_score_adj,就能确定最终杀死哪个进程了
确定的进程保存在selected变量中,对他发送SIGKILL信号杀死,并且更新rem大小 所以通过上面的总结已经可以回答我们第一个问题,“LowmemoryKiller杀进程的策略具体是怎么样的?内存低到什么情况下,LowmemoryKiller开始干活呢?”
四 编译 Android 4.1 环境配置 1.操作系统:Ubuntu22虚拟机
2.下载和配置所需工具
1 2 3 4 5 6 7 8 9 10 11 12 sudo apt install curl repo git #下载配置repo mkdir ~/bin/ curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo -o ~/bin/repo chmod +x ~/bin/repo #修改 ~/.bashrc文件,再最后一行加入 export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo' #执行 source ~/.bashrc #配置git环境,email和name配置成自己的名称 git config --global user.email "you@example.com" git config --global user.name "Your Name"
4.2 下载Android源码 创建保存代码的目录,我下载分支为android-13.0.0_r35,如果想下载其他分支,请查看source-code-tags-and-builds
1 2 3 4 mkdir -p ~/android-13.0.0_r35 cd ~/android-13.0.0_r35 repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android-13.0.0_r35 repo sync
repo sync后面可以加 -j 参数,-j 后面的数字为使用cpu的线程数,根据个人电脑配置选择,后面编译也是如此。
4.3 代码编译 1.编译前注意事项
(1)内存要够大:我电脑是16GB内存,编译时提示最少需要16G的内存,我就给虚拟机分了16GB,实际最吃内存的是编译开始的阶段,顶过去就好了
(2)增加交换空间:
Ubuntu22默认交换空间只有2G,我们要扩大,我给虚拟机分了16GB,所以我把交换空间也设为16GB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #检查原来的系统中是否有swap分区 free -h #删除原来的swap分区 sudo swapoff /swapfile sudo rm /swapfile #检查硬盘可用空间 df -h #创建分区,设置swap分区与内存一样大,内存是16G,这里也创建一个16G的swap分区 sudo fallocate -l 16G /swapfile #检查是否创建成功 ls -lh /swapfile #使能分区 sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile sudo swapon --show free -h #到这里,分区创建完成了,但是下次启动会丢失,接下来,固化swap分区 sudo cp /etc/fstab /etc/fstab.bak echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
实际上我分配了20G内存后来也编译失败了,建议内存+交换空间要达到30G,而Ubuntu的交换空间只有2G
2.编译工具安装
1 sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z-dev ccache libgl1-mesa-dev libxml2-utils xsltproc unzip libncurses5 openjdk-8-jdk
3.编译代码
1 2 3 4 5 cd ~/android-13.0.0_r35 source build/envsetup.sh #下面选择自己想要的版本,如果想要启动模拟器的话,要选择sdk开头的,比如aosp_car_x86_64-userdebug是无法启动模拟器的,sdk_car_x86_64-userdebug 可以启动模拟器 lunch sdk_car_x86_64-userdebug make -j4
-j4编译了很久,从下午四五点开始到晚上睡觉才编译了一半多,一觉醒来是编译好了的,所以电脑配置好的 -j 后的参数可以调大一些,省时间。
4.4 启动模拟器 4.4.1 启动模拟器的注意事项: 要通过VMware中Ubuntu22虚拟机来启动Android模拟器,需要在虚拟机设置中,勾选虚拟化Intel YT-x/EPT或 AMD-V/RVI
若出现报错:需要关掉基于虚拟化的安全性,具体操作见参考链接:在 Windows 11/10 中启用或禁用核心隔离内存完整性 - 知乎 (zhihu.com) ****
4.4.2 启动虚拟器 编译完成后在同一窗口执行:
如果切换了终端窗口,则执行
1 2 3 source build/envsetup.sh lunch sdk_car_x86_64-userdebug emulator -writable-system
-writable-system表示系统可写,不加的话无法adb push文件到系统
4.5代码调试 4.5.1 调试工具安装 安装adb调试工具
1 2 3 sudo add-apt-repository ppa:nilarimogard/webupd8 sudo apt-get update sudo apt-get install android-tools-adb
4.5.2 调试 启动 adb
查看进程
导出日志
五 总结 通过对 LMK 源码的分析,对 Android 低内存的管理机制有了更深的了解。LMK 的主要流程是 frameworks 的 ProcessList.java 调整 adj,通过 socket 通信将事件发送给 native 的守护进程 lmkd;lmkd 再根据具体的命令来执行相应操作,其主要功能是用来更新 oom_score_adj 值以及 lowmemorykiller 驱动的 parameters(包括 minfree 和 adj)。最后讲到了 lowmemorykiller 驱动,通过注册 shrinker,借助 linux 标准的内存回收机制,根据系统可用内存以及 parameters 配置参数(adj,minfree)来选取合适的 selected_oom_score_adj,再从所有进程中选择 adj 大于该目标值并占有 rss 内存最大的进程,将其杀掉从而释放出内存。
通过查阅资料了解到:Android 使用内核中的低内存终止守护程序(LMK)驱动程序来监控系统内存压力,该驱动程序是一种依赖于硬编码值的严格机制。从内核 4.12 开始,LMK 驱动程序已从上游内核中移除,改由用户空间 lmkd 来执行内存监控和进程终止任务。Android 低内存守护程序 lmkd 进程可监控运行中的 Android 系统的内存状态,并通过终止最不必要的进程来应对内存压力大的问题,使系统以可接受的性能水平运行。
Android 采用层次化系统架构,由底层向上分为 4 个主要功能层,分别是 Linux 内核层、系统运行时库层、应用程序框架层和应用程序层。对于 Linux 内核层来说,Android 以 Linux 操作系统内核为基础,借助 Linux 内核服务实现硬件设备驱动、进程和内存管理、网络协议栈、电源管理、无线通信等核心功能。Android4.0 版本之前基于 Linux2.6 系列内核,4.0 及之后的版本使用更新的 Linux3.X 内核,并且两个开源项目有了互通。Linux3.3 内核中正式包括以下 Android 代码,可以直接引导进 Android。Linux3.4 将会增添电源管理等更多功能,以增加与 Android 的硬件兼容性,使 Android 在更多设备上得到支持。Android 对 Linux 内核进行了增强,增加了一些面向移动计算的特有功能。例如,低内存管理器 LMK、匿名共享内存 Ashmem、轻量级的进程间通信 Binder 机制等。这些内核的增强使 Android 在继承 Linux 内核安全机制的同时,进一步提升了内存管理,进程间通信等方面的安全性。
内核驱动和用户软件之间还存在一层硬件抽象层(HAL),它是对硬件设备的具体实现加以抽象。鉴于许多硬件设备厂商不希望公开其设备驱动的源代码,如果能将 Android 的应用程序框架层与 Linux 系统内核的设备驱动隔离,使应用程序框架的开发尽量独立于具体的驱动程序,则 Android 将减少对 Linux 内核的依赖。所以 HAL 是对 Linux 内核驱动程序进行的封装,将硬件抽象化,屏蔽掉底层的实现细节。HAL 规定了一套应用层对硬件层读写和配置的统一接口,本质上就是将硬件的驱动分为用户空间和内核空间两个层面;Linux 内核驱动程序运行与内核空间,硬件抽象层运行与用户空间。
在系统运行库层,通过一些 C/C++ 库来为 Android 系统提供了主要的特性支持。如 SQLite 提供了数据库的支 持,OpenGL|ES 提供了 3D 绘图的支持,Webkit 库提供了浏览器内核的支持等。同样在这一层还有 Android 运行时库,它主要提供了一些核心库,能够允许开发者使用 Java 语言来编写 Android 应用。另外 Android 运行时库中还包含了 Dalvik 虚拟机,它使得每一个 Android 应用都能运行在独立的进程当中,并且拥有一个自己的 Dalvik 虚拟机实例。相较于 Java 虚拟机,Dalvik 是专门为移动设备定制的,它针对手机内存、CPU 性能有限等情况作了优化处理。
应用程序框架层提供开发 Android 应用程序所需的一系列类库,构建应用程序时可能用到的 API 等,使开发人员可以进行快速的应用程序开发,方便重用组件,也可以通过继承实现个性化的扩展。而应用层就是 Android 平台上包括各类与用户直接交互的应用程序,或由 java 语言编写的运行与后台的服务程序。例如智能手机上实现的基本功能程序,像 SMS 短信,电话拨号,日历,浏览器等。