android 内存检测,android native framework

  

  进程管理是Android系统中的一个重要模块,因为它与每个人的日常开发息息相关。相信大家都知道过程的概念。但是对于大多数应用开发者来说,关于进程只有几个熟悉的概念:fork参与进程启动,adj参与进程的优先级管理,FCFS参与进程的调度。但要准确理解它们的含义可能并不容易。   

  

  前几年Android低配版的进程管理存在很多bug,国内应用商店的推送服务也不完善统一,导致很多保活黑科技横行。这些技术大多从框架层入手,深挖早期android的各种漏洞(前台服务、一像素活动等。);还有一个特点就是android应用可以从较低层运行多个进程,在原生层把刚刚被杀死的应用进程拉上来。   

  

  你可能经常听到这些进程和keep-alive黑科技的基本概念,但是Linux进程的本质和Android如何控制和管理进程的创建和销毁的细节可能不是那么清楚。   

  

  接下来我将重点介绍Android进程管理,包括native和framework两个部分,从内核的进程调度、优先级、进程空间和lowmemorykiller,到framework的进程启动、销毁场景和优先级处理,以及四个组件之间的关系。在此期间,我们会抛出几个实用的彩蛋,为文章增加一些素材。与分析执行过程的文章不同,我将主要总结Android进程管理的主要功能、触发时机和设计精髓。   

  

  本文的基本原则是连贯的理论知识和贴近实际的解释。人类天生更喜欢看图像记忆,可以多放图片,多放图片,避免大家输入代码细节。带大家从本质上理解流程管理,而不仅仅是流程相关的执行过程。   

  

  简而言之,普通应用程序流程具有以下功能:   

  

  执行程序   

  

  拥有独占的堆栈空间   

  

  内核中有相应的进程控制块。   

  

  有独立的用户存储空间。   

  

  在Android中,系统进程还有额外的职责:   

  

  提供核心服务,直接管理硬件资源。   

  

  提供对硬件设备的访问。   

  

  管理常见应用程序流程的生命周期。   

  

  Linux内核的以下部分是一个简单的代码,它在创建一个子进程后执行' ls '命令。其基本模式类似于Android通过fork zygote进程执行应用的方式。Android如何启动应用进程,后面会介绍。在这里,我们将重点讨论这个过程在Linux操作系统中是如何运行的。   

  

  int main {   

  

  pid _ t child _ pid   

  

  char * argv={'ls ','-al ','/etc/passwd ',0 };   

  

  child _ pid=fork   

  

  if (child_pid 0) {   

  

  //在父代中,直接返回   

  

  返回child _ pid   

  

  }否则{   

  

  //在子节点中,执行ls命令   

  

  execvp('ls ',argv);   

  

  }   

  

  返回0;   

  

  }   

  

  本质上,上面的代码只是一个由字符串组成的文本序列。它的作用是方便程序员阅读和表达自己的想法。我们都知道计算机只有1和0。所以,如果我们想让操作系统执行程序,生成运行的进程,就需要借用相应的编译器和连接器,把程序翻译成可执行文件。的典型过程。要处理的c源文件如下:   

  

  生成可重定位的目标文件。o来自。h .c文件。   

  

  将目标文件生成静态和动态链接库。一个和。所以通过ar和linker。   

  

  通过链接器生成可执行文件   

  

  通过加载可执行文件并用动态链接器链接动态库,操作系统创建进程执行程序逻辑。   

  

  上图是操作系统中生成可执行文件和执行程序的过程。在Linux执行程序之前,需要将ELF格式的可执行文件加载到内存中。   

  

  进程控制块在Linux中成功分叉进程后,会在系统中创建一个task_struct(也叫PCB,process control block),用来描述当前进程的状态、进程间关系、优先级和资源。   

  

  这里特别提一下进程间的关系,以Android中的各种进程为例:   

  

  Android基于Linux,通过三种进程关系进行管理:parent指父进程,children指子进程,sibling指父进程相同的兄弟进程。如图,我们熟悉的AMS,PMS等衣服。   

务运行在system_server进程之上,但是它是所有应用进程的兄弟进程;zygote是所有系统应用进程、普通应用进程和system_server的父进程

  

有以下几点需要注意:

  

idle进程是系统中的第一个进程,它会创建一个init进程与一个内核线程,这三个线程的名字在系统中是固定的

  

init进程之后会创建zygote进程,用来加载Android应用的通用资源

  

zygote的第一个子进程是system_server,用来给其它应用进程提供通用服务

  

system_server负责管理Android系统中应用进程的创建、销毁与优先级

  

应用进程创建会经由system_server之手,通过socket与zygote进程通信,创建完成后应用进程信息会被system_server记录。应用进程是通过反射调用ActivityThread的main方法启动的

  

应用进程死亡后,会通过binder的死亡回调机制告知system_server

  

应用进程的PID不会固定的,Linux PID的上限是有阈值的,通过循环进行复用

  

调度策略首先,在Linux中CPU的时间片是以线程为调度单位的:

  

进程的调度是通过一个队列进行管理的,刚被创建的进程和处于准备运行状态下的进程状态是TASK_RUNNING,意味着进程随时可以在获取到时间片后开始执行;当进程获取到时间片后意味着进程处于执行状态中了;如果进程在执行期间时间片被抢占,状态会再变为TASK_RUNNING,等待内核调度器再次分配时间片。

  

图中主要标记出两种睡眠状态,第一种TASK_INTERRUPIBLE是浅睡眠状态,如果此时信号到来随时可以被唤醒;第二种是TASK_UNINTERUPTIBLE是深度睡眠状态,不能通过信号唤醒,这里注意的是,kill方法的本质也是通过发送信号告知进程进行退出,所以kill对于这种睡眠状态下的进程是不管用的。

  

进程的调度主要分为两种,一种是主动调度,也就是在进程执行时因为等待I/O等操作主动的让出CPU时间片,交由其它进程进行执行,常见的主动调度场景有等待文件写入完成、等待网络读取完成。另外一种调度是抢占式调度,操作系统通过时钟中断来统计进程已经获取到的时间片时间,当一个进程已经执行了足够长的时间,那么操作系统会通过调用 schedule 方法切换到另外一个进程来进行执行。

  

切换执行进程时,最重要的事是执行进程间的上下文切换,Linux 通过调用 context_switch 方法来切换进程空间、寄存器和CPU的上下文。

  

进程空间管理上图描述了一个在32位操作系统中的进程虚拟内存空间布局,主要分成了用户态与内核态,分别占有了1G与3G。每个进程能够访问的虚拟地址空间都是独立的、互不干扰的;每个进程能够访问到的内核态其实都是同一个,其中包含着内存管理模块,通过它内核可以对物理内存进行管理。

  

彩蛋:轻量级进程 & 轻量级线程 (线程 & Kotlin协程)线程比进程更加轻量,它只需要和同进程的其它线程共享内存、文件资源,在创建、销毁与调度时更加轻量。

  

协程比线程更加轻量,它甚至不需要操作系统参与调度,因为寄存器与栈中需要加载的数据更加少,创建、销毁与调度时相比于线程也更加轻量。

  

Android Native部分LowMemoryKillerLowMemoryKiller (lmkd) 是独立运行在Android设备上的守护进程,启动之后便会进入无限循环,通过接受来自framework的socket配置信息来设置杀进程的内存/优先级映射阈值、各个进程的优先级信息。

  

一组内存/优先级映射阈值数据如下:

  

  

angler:/sys/module/lowmemorykiller/parameters # cat adj
0,100,200,300,900,906

angler:/sys/module/lowmemorykiller/parameters # cat minfree
18432,23040,27648,32256,55296,80640
当系统可用内存page小于80640,会杀死adj低于906的进程;当小于55296,会杀死低于900的进程...

  

lwkd借用了Linux shrinker 回收page的机制,当监测到系统内存低于某个内存阈值时,便会选择对应的最低进程优先级,随后会从按进程优先级依次杀死进程,直至系统内存恢复正常。当进程的优先级相等时,lwkd会杀死RSS大的进程。

  

  

static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc)
{
...
// 获取当前系统剩余内存,以page为单位
int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;
int other_file = global_page_state(NR_FILE_PAGES) -
global_page_state(NR_SHMEM);

for (i = 0; i < array_size; i++) {
if (other_free < lowmem_minfree &&
other_file < lowmem_minfree) {
min_score_adj = lowmem_adj;
break;
}
}
...

selected_oom_score_adj = min_score_adj;
rcu_read_lock;
// 遍历所有进程PCB
for_each_process(tsk) {
struct task_struct *p;
int oom_score_adj;
if (tsk->flags & PF_KTHREAD)
continue;
p = find_lock_task_mm(tsk);
...
oom_score_adj = p->signal->oom_score_adj;
if (oom_score_adj < min_score_adj) {
task_unlock(p);
continue;
}

tasksize = get_mm_rss(p->mm);
task_unlock(p);
if (tasksize <= 0)
continue;
if (selected) {
// 检查优先级是否更低
if (oom_score_adj < selected_oom_score_adj)
continue;
// 如果平级,查看是否占用更多内存
if (oom_score_adj == selected_oom_score_adj &&
tasksize <= selected_tasksize)
continue;
}
// 选中该进程
// 如果后续没有优先级更低或者占用内存更多的内存的平级进程的话,会将该进程杀死
selected = p;
...
}

if (selected) {
// 发送信号进行查杀
send_sig(SIGKILL, selected, 0);
}
return rem;
}
framework负责直接写入每个java进程的proc下的oom_score_adj内容,这也意味着上层ProcessRecord.curAdj其实也就是lwkd使用的oom_score_adj的值。oom_adj与oom_score_adj有着直接映射的关系,但是lmkd只使用oom_score_adj来进行查杀进行的判断。

  

  

angler:/proc/14544 # cat oom_score_adj
900

angler:/proc/14544 # cat oom_adj
16
上述就是我们在真实机器上,从/proc节点得到的进程oom_score信息。

  

大家可以参考这张表来看下具体对应的是什么含义:

  

adj

相关文章