Android进程管理机制研究

一、Linux中的进程管理
在Linux中,进程是指处理器上执行的一个实例,可使用任意资源以便完成它的任务,具体的进程管理,是通过“进程描述符”来完成的,对应Linux内核中的task_struct数据结构。进程描述符,包括进程标识、进程的属性、构建进程的资源。
一个进程可以通过fork()或者vfork()调用创建出子进程,这些子进程可以访问父进程的地址空间,包括文本段、数据段、堆栈段。

通常情况下,调用fork()的进程处于task_running状态,则fork出来的子进程默认也处于task_running状态,具体来说,在fork之后、exec之前,子进程处于task_running状态中的就绪状态。

进程的运行状态包括以下几种。
1、task_running:可执行状态。包含正在CPU上执行的、可执行但是尚未被调度执行这两种子状态,后者对应就绪状态。
2、task_interruptible:可中断的睡眠状态。因为等待某事件的发生而被挂起。当等待的事件发生时,处于该状态的进程将被唤醒。
3、task_uninterruptible:不可中断的睡眠状态。处于睡眠状态,但是此刻进程是不可中断的。此时进程不响应异步信号,不能通过发送信号的方式kill之,但可以响应硬件中断,例如磁盘IO,网络IO等。
 4、task_stopped / task_traced:暂停状态或跟踪状态。处于task_traced状态的进程不能响应SIGCONT信号而被唤醒,只能等到调试进程通过ptrace系统调用执行ptrace_cont、ptrace_detach等操作,或调试进程退出,被调试的进程才能恢复task_running状态。
5、task_zombie :僵尸状态。在进程收到SIGSTOP、SIGTTIN、SIGTTOU等信号,即将终止时,会进入该状态,此时进程成为僵尸进程。该状态的进程会处理一些资源释放工作,然后发送SIG_CHLD信号给父进程。

二、Android进程管理机制
在linux系统中,应用程序执行完成后,最终会清理一些进程使用的文件描述符、释放掉进程用户态使用的相关的物理内存,清理页表,然后发送信号给父进程。但在Android系统中,应用程序执行完成后,该应用所在的进程通常还是会在后台继续运行,除非应用程序在执行完成后主动调用System.exit或者Process.killProcess之类的方法。
这样设计的好处,主要是加快应用程序再次启动的速度,改善用户体验。当内存不足时,系统会按照特定规则,包括进程优先级、占用内存等信息,来清理进程并释放对应的资源。

1、进程优先级
在Android中,进程按优先级可以分为:前台进程、可见进程、服务进程、后台进程、空进程。优先级依次降低。


1)Forground,前台进程
这种进程优先级最高,可以细分下面几种情况: 
case1:有个前台Activity,特指已经执行了onResume但还没执行onPause的Activity;
case2:有个Service且和一个前台Activity绑定的进程;
case3:调用了startForground的前台Service所在进程(这种服务会带个通知);
case4:正在执行onReceive函数的BroadcastReceiver所在进程,以及正在执行服务的生命周期方法诸如onCreate、onStartCommand的进程。

2)Visible,可见进程
可见进程没有处于前台的组件,但是用户仍然能看到进程中的组件,例如某进程的Activity调用了申请权限对话框,具体包括:
case1:有个仅onPause被调用的Activity(可见但被遮挡);
case2:进程中有个Service且和一个可见Activity绑定。
注意这里的可见Activity不包括前台Activity(否则就是前台进程了),并且这种进程在内存不足时也是可能被杀掉的。

3)Service,服务进程
服务进程是指有个通过startService方式启动的Service进程,并且不属于前面两类进程,例如MediaScannerService。

4)Background,后台进程
当前不可见的Activity所在进程属于后台进程,即它们的onStop被调用过,例如用户按下home键。
对于后台进程,系统会保存这些进程到一个LRU列表,当系统需要回收内存时,LRU中那些最近最少使用的进程将被杀死。

5)Empty,空进程
空进程不包含任何组件,当系统重新需要它们时(例如用户在别的进程中通过startActivity启动了它们),可以省去fork进程、创建Android运行环境等漫长的工作,节省时间,缓存性质。这种进程优先级最低,在任何时候都可能被杀掉。

如前所述,大部分应用是单进程的,但是进程和组件类型是高度关联的。如果应用中有生命周期差异较大的组件,考虑使用多进程分别处理。一方面是让占用资源较多的进程可以被系统及时回收,另一方面,避免那些需要长时间持续运行的任务由于组件生命周期的影响进入后台进程执行。

2、内存不足时的杀进程策略
在Linux系统中,进程的优先级对应一个参数,也就是oom_score_adj,lowmemorykiller程序会根据内存使用情况和进程优先级,动态杀进程以释放内存资源。 

当内存不足或者发生oom错误时,lowmemorykiller根据特定策略先杀优先级最低的进程,然后逐步杀优先级更低的进程(同样优先级会按照内存占用情况排序),依此类推,以回收预期的可用系统资源,从而保证系统正常运转。

在Android系统中,进程的组件状态变化时,组件所在进程的优先级也会发生变化。一个典型的场景是,切换到后台的进程,其优先级低于前台进程。

App的前台/后台切换操作对oom_score_adj的影响,我们可以使用adb命令cat/proc/[pid]/oom_score_adj来查看。详细的优先级信息在ProcessList.java中有定义,对应的部分进程类型如下。

  • Cached,缓存进程,包括空进程、只有activity的后台进程,其adj在900~906;
  • B Services,无UI组件且在Lru进程表中位于后2/3的服务进程(比较旧的后台服务进程),其adj为800;
  • Previous,上一个应用进程,例如按home键进入后台的进程,其adj为700;
  • Home,也就是launcher进程,其adj为600;
  • A Services,没有UI组件且在Lru进程表中位于后2/3的服务进程(比较旧的后台服务进程),其adj为500;
  • Perceptible,有着用户可感知组件的进程,包括前台服务进程,adj为200;
  • Visible,可见进程,其adj为100;
  • Foreground,前台应用进程,其adj为0;
  • Persistent,系统常驻进程,例如systemui、phone进程,其adj为-700或-800。如果是在AndroidManifest.xml中申明android:persistent="true"的进程,adj为-800;如果是由startIsolatedProcess()方式启动或由SystemServer进程、persistent进程绑定的服务进程,则为-700;
  • System进程,典型的是SystemServer进程。
  • Native进程,例如init、surfaceflinger、mediaserver进程,其adj为-1000;

内存不足时,AMS会根据上述动态优先级信息,通过ProcessRecord,在native层给指定进程发送信号以终止进程,进而释放内存资源。相关函数包括:killProcessesBelowForeground, killProcessesBelowAdj, ProcessRecord.kill, killPids等。

 

(相关完整且成体系的文章,可参见本人原创的开源电子书《Android系统与性能优化》,地址:https://github.com/carylake/androidnotes)

posted @ 2019-12-18 20:27  星禾  阅读(169)  评论(0编辑  收藏