Android中的进程Process

关于BeesAndroid项目

BeesAndroid项目提供了一系列的工具、理论分析与方法论,旨在降低Android系统源码的阅读门槛,让读者更好的理解Android系统的设计与实现。第一次阅览本系列文章,请参见导读,更多文章请参见文章目录

本篇文章开始,我们来分析Android系统中进程相关知识。

基本概念

在正式介绍进程之前,我们来思考一个问题,何为进程,进程的本质是什么,进程和线程有什么区别?🤔

我们知道,代码是静态的,有代码和资源组成的系统要想运行起来就需要一种动态的存在,进程就是程序的动态执行过程。何为进程?

进程就是处理执行状态的代码以及相关资源的集合,包括代码端段、文件、信号、CPU状态、内存地址空间等。

进程使用task_struct结构体来描述,如下所示:

●代码段:编译后形成的一些指令
●数据段:程序运行时需要的数据
○只读数据段:常量
○已初始化数据段:全局变量,静态变量
○未初始化数据段(bss):未初始化的全局变量和静态变量
●堆栈段:程序运行时动态分配的一些内存
●PCB:进程信息,状态标识等

以上便是从Linux内核角度对进程的理解。回归到Android系统本身,我们再来看看进程。

●进程:Android系统中有诸多进程,守护进程、Zygote进程、App进程等。里应用开发最近的要属App进程了。它是由Zygote fork出来的。具有独立的虚拟地址空间,用于承载四大组件。一般说来一个App只有一个进程,当然也可以配置android:process属性或者通过native代码fork进程。
●线程:对于Linux内核来说,线程也是一个task_struct结构体,和进程并没有本质差别,但是线程没有自己独立的地址空间,而是与其所在的进程共享资源。

进程分类

前台进程

前台进程是用户目前执行操作的进程。满足下列条件之一即可:

●它正在用户的互动屏幕上运行一个 Activity(其 onResume() 方法已被调用)。
●它有一个 BroadcastReceiver 目前正在运行(其 BroadcastReceiver.onReceive() 方法正在执行)。
●它有一个 Service 目前正在执行其某个回调(Service.onCreate()Service.onStart()Service.onDestroy())中的代码。

系统中只有少量此类进程,只有当内存极低(例如内存分页),导致这些进程也无法运行的时候,才会在最后一步终止这些进程。

可见进程

可见进程正在运行用户当前知晓的任务,满足下列条件之一即可:

●它正在运行的 Activity 在屏幕上对用户可见,但不在前台(其 onPause() 方法已被调用)。举例来说,如果前台 Activity 显示为一个对话框,而这个对话框允许在其后面看到上一个 Activity,则可能会出现这种情况。
●它有一个 Service 正在通过 Service.startForeground()(要求系统将该服务视为用户知晓或基本上对用户可见的服务)作为前台服务运行。
●系统正在使用其托管的服务实现用户知晓的特定功能,例如动态壁纸、输入法服务等。

可见进程的数量不受限制,但仍然相对受控,只有当内存偏低,系统为了使所有前台进程保持运行时,才会根据一定策略选择性的终止可见进程。

服务进程

服务进程包含一个已经使用 startService() 方法启动的Service。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

但是随着时间的推移(例如30分钟或者更长时间)它们的优先级可能会被降低,变成下文中的缓存进程。

缓存进程

缓存进程是最不重要的进程,如果其他地方需要内存,缓存进程可以随时被终止,它们存在的目的只是为了更加高效的切换应用(减少创建进程的时间开销)。

这些进程通常会被保存在LRU列中中,列表中的最后一个进程是为了回收内存而终止的第一个进程,列表的排序顺序由操作系统根据策略来决定。

进程启动

我们先来看看我们最熟悉的应用进程是如何被创建的,前面我们已经说来每一个应用都运行在一个单独的进程里,当ActivityManagerService去启动四大组件时,如果发现这个组件所在的进程没有启动,就会去创建一个新的进程,启动进程的时机我们在分析四大组件的启动流程的时候也有讲过,这里再总结一下:

●Activity ActivityStackSupervisor.startSpecificActivityLocked()
●Service ActiveServices.bringUpServiceLocked()
●ContentProvider. ActivityManagerService.getContentProviderImpl()
●Broadcast BroadcastQueue.processNextBroadcast()

这个新进程就是zygote进程通过复制自身来创建的,新进程在启动的过程中还会创建一个Binder线程池(用来做进程通信)和一个消息循环(用来做线程通信)
整个流程如下图所示:

1当我们点击应用图标启动应用时或者在应用内启动一个带有process标签的Activity时,都会触发创建新进程的请求,这种请求会先通过Binder发送给system_server进程,也即是发送给ActivityManagerService进行处理。
2system_server进程会调用Process.start()方法,会先收集uid、gid等参数,然后通过Socket方式发送给Zygote进程,请求创建新进程。
3Zygote进程接收到创建新进程的请求后,调用ZygoteInit.main()方法进行runSelectLoop()循环体内,当有客户端连接时执行ZygoteConnection.runOnce()方法,最后fork生成新的应用进程。
4新创建的进程会调用handleChildProc()方法,最后调用我们非常熟悉的ActivityThread.main()方法。

注:整个流程会涉及Binder和Socket两种进程通信方式,这个我们后续会有专门的文章单独分析,这个就不再展开。

整个流程大致就是这样,我们接着来看看具体的代码实现,先来看一张进程启动序列图:

从第一步到第三步主要是收集整理uid、gid、groups、target-sdk、nice-name等一系列的参数,为后续启动新进程做准备。然后调用openZygoteSocketIfNeeded()方法
打开Socket通信,向zygote进程发出创建新进程的请求。

注:第二步中的Process.start()方法是个阻塞操作,它会一直等待进程创建完毕,并返回pid才会完成该方法。

我们来重点关注几个关键的函数。

Process.openZygoteSocketIfNeeded(String abi)

关于Process类与Zygote进程的通信是如何进行的呢?🤔

Process的静态内部类ZygoteState有个成员变量LocalSocket对象,它会与ZygoteInit类的成员变量LocalServerSocket对象建立连接,如下所示:

客户端

服务端

我们来具体看看代码里的实现。

建立Socket连接的流程很明朗了,如下所示:

1创建LocalSocket对象。
2将LocalSocket与LocalServerSocket建立连接,建立连接的过程就是LocalSocket对象在/dev/socket目录下查找一个名称为”zygote”的文件,然后将自己与其绑定起来,这样就建立了连接。
3创建LocalSocket的输入流,以便可以接收Zygote进程发送过来的数据。
4创建LocalSocket的输出流,以便可以向Zygote进程发送数据。

ZygoteInit.main(String argv[])

ZygoteInit是Zygote进程的启动类,该类会预加载一些类,然后便开启一个循环,等待通过Socket发过来的创建新进程的命令,fork出新的
子进程。

ZygoteInit的入口函数就是main()方法,如下所示:

可以发现ZygoteInit在其入口函数main()方法里调用runSelectLoop()开启了循环,接收Socket发来的请求。请求分为两种:

1连接请求
2数据请求

没有连接请求时Zygote进程会进入休眠状态,当有连接请求到来时,Zygote进程会被唤醒,调用acceptCommadPeer()方法创建Socket通道ZygoteConnection

然后调用runOnce()方法读取连接请求里的数据,然后创建新进程。

此外,连接的过程中服务端接受的到客户端的connect()操作会执行accpet()操作,建立连接手,客户端通过write()写数据,服务端通过read()读数据。

ZygoteConnection.runOnce()

该方法主要用来读取进程启动参数,然后调用Zygote.forkAndSpecialize()方法fork出新进程,该方法是创建新进程的核心方法,它主要会陆续调用三个
方法来完成工作:

1preFork():先停止Zygote进程的四个Daemon子线程的运行以及初始化GC堆。这四个Daemon子线程分别为:Java堆内存管理现场、堆线下引用队列线程、析构线程与监控线程。
2nativeForkAndSpecialize():调用Linux系统函数fork()创建新进程,创建Java堆处理的线程池,重置GC性能数据,设置进程的信号处理函数,启动JDWP线程。
3postForkCommon():启动之前停止的Zygote进程的四个Daemon子线程。

上面的方法都完成会后,新进程会创建完成,并返回pid,接着就调用handleChildProc()来启动新进程。handleChildProc()方法会接着调用RuntimeInit.zygoteInit()来
完成新进程的启动。

RuntimeInit.zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)

这个就是个关键的方法了,它主要用来创建一些运行时环境,我们来看一看。

该方法主要完成三件事:

1调用commonInit()方法创建应用进程的时区和键盘等通用信息。
2调用nativeZygoteInit()方法在应用进程中创建一个Binder线程池。
3调用applicationInit(targetSdkVersion, argv, classLoader)方法创建应用信息。

Binder线程池我们后续的文章会分析,我们重点来看看applicationInit(targetSdkVersion, argv, classLoader)方法的实现,它主要用来完成应用的创建。

该方法里的argv参数指的就是ActivityThread,该方法会调用invokeStaticMain()通过反射的方式调用ActivityThread类的main()方法。如下所示:

走到ActivityThread类的main()方法,我们就很熟悉了,我们知道在main()方法里,会创建主线程Looper,并开启消息循环,如下所示:

前面我们从Process.start()开始讲起,分析了应用进程的创建及启动流程,既然有启动就会有结束,接下来我们从
Process.killProcess()开始讲起,继续分析进程的结束流程。

进程优先级

进程按照优先级大小不同又可以分为实时进程与普通进程。

prio值越小表示进程优先级越高,

●静态优先级:优先级不会随时间改变,内核也不会修改,只能通过系统调用改变nice值,优先级映射公式为:static_prio = MAX_RT_PRIO + nice + 20,其中MAX_RT_PRIO = 100,那么取值区间为[100, 139];对应普通进程;
●实时优先级:取值区间为[0, MAX_RT_PRIO -1],其中MAX_RT_PRIO = 100,那么取值区间为[0, 99];对应实时进程;
●懂爱优先级:调度程序通过增加或者减少进程优先级,来达到奖励IO消耗型或按照惩罚CPU消耗型的进程的效果。区间范围[0, MX_PRIO-1],其中MX_PRIO = 140,那么取值区间为[0,139];

进程调度

进程的调度在Process类里完成。

优先级调度

优先级调度方法

进程的优先级以及对应的nice值如下所示:

●THREAD_PRIORITY_LOWEST 19 最低优先级
●THREAD_PRIORITY_BACKGROUND 10 后台
●THREAD_PRIORITY_LESS_FAVORABLE 1 比默认略低
●THREAD_PRIORITY_DEFAULT 0 默认
●THREAD_PRIORITY_MORE_FAVORABLE -1 比默认略高
●THREAD_PRIORITY_FOREGROUND -2 前台
●THREAD_PRIORITY_DISPLAY -4 显示相关
●THREAD_PRIORITY_URGENT_DISPLAY -8 显示(更为重要),input事件
●THREAD_PRIORITY_AUDIO -16 音频相关
●THREAD_PRIORITY_URGENT_AUDIO -19 音频(更为重要)

组优先级调度

进程组优先级调度方法

组优先级及对应取值

●THREAD_GROUP_DEFAULT -1 仅用于setProcessGroup,将优先级<=10的进程提升到-2
●THREAD_GROUP_BG_NONINTERACTIVE 0 CPU分时的时长缩短
●THREAD_GROUP_FOREGROUND 1 CPU分时的时长正常
●THREAD_GROUP_SYSTEM 2 系统线程组
●THREAD_GROUP_AUDIO_APP 3 应用程序音频
●THREAD_GROUP_AUDIO_SYS 4 系统程序音频

调度策略

调度策略设置方法

●SCHED_OTHER 默认 标准round-robin分时共享策略
●SCHED_BATCH 批处理调度 针对具有batch风格(批处理)进程的调度策略
●SCHED_IDLE 空闲调度 针对优先级非常低的适合在后台运行的进程
●SCHED_FIFO 先进先出 实时调度策略,android暂未实现
●SCHED_RR 循环调度 实时调度策略,android暂未实现

进程adj调度

另外除了这些基本的调度策略,Android系统还定义了两个和进程相关的状态值,一个就是定义在ProcessList.java里的adj值,另一个
是定义在ActivityManager.java里的procState值。

定义在ProcessList.java文件,oom_adj划分为16级,从-17到16之间取值。

●UNKNOWN_ADJ 16 一般指将要会缓存进程,无法获取确定值
CACHED_APP_MAX_ADJ 15 不可见进程的adj最大值 1
CACHED_APP_MIN_ADJ 9 不可见进程的adj最小值 2
●SERVICE_B_AD 8 B List中的Service(较老的、使用可能性更小)
●PREVIOUS_APP_ADJ 7 上一个App的进程(往往通过按返回键)
●HOME_APP_ADJ 6 Home进程
●SERVICE_ADJ 5 服务进程(Service process)
●HEAVY_WEIGHT_APP_ADJ 4 后台的重量级进程,system/rootdir/init.rc文件中设置
BACKUP_APP_ADJ 3 备份进程 3
PERCEPTIBLE_APP_ADJ 2 可感知进程,比如后台音乐播放 4
VISIBLE_APP_ADJ 1 可见进程(Visible process) 5
FOREGROUND_APP_ADJ 0 前台进程(Foreground process) 6
●PERSISTENT_SERVICE_ADJ -11 关联着系统或persistent进程
●PERSISTENT_PROC_ADJ -12 系统persistent进程,比如telephony
●SYSTEM_ADJ -16 系统进程
●NATIVE_ADJ -17 native进程(不被系统管理)

更新进程adj值的方法定义在ActivityManagerService中,分别为:

●updateOomAdjLocked:更新adj,当目标进程为空,或者被杀则返回false;否则返回true;
●computeOomAdjLocked:计算adj,返回计算后RawAdj值;
●applyOomAdjLocked:应用adj,当需要杀掉目标进程则返回false;否则返回true。

那么进程的adj值什么时候会被更新呢?🤔

Activity

●ActivityManagerService.realStartActivityLocked: 启动Activity
●ActivityStack.resumeTopActivityInnerLocked: 恢复栈顶Activity
●ActivityStack.finishCurrentActivityLocked: 结束当前Activity
●ActivityStack.destroyActivityLocked: 摧毁当前Activity

Service

●ActiveServices.realStartServiceLocked: 启动服务
●ActiveServices.bindServiceLocked: 绑定服务(只更新当前app)
●ActiveServices.unbindServiceLocked: 解绑服务 (只更新当前app)
●ActiveServices.bringDownServiceLocked: 结束服务 (只更新当前app)
●ActiveServices.sendServiceArgsLocked: 在bringup或则cleanup服务过程调用 (只更新当前app)

BroadcastReceiver

●BroadcastQueue.processNextBroadcast: 处理下一个广播
●BroadcastQueue.processCurBroadcastLocked: 处理当前广播
●BroadcastQueue.deliverToRegisteredReceiverLocked: 分发已注册的广播 (只更新当前app)

ContentProvider

●ActivityManagerService.removeContentProvider: 移除provider
●ActivityManagerService.publishContentProviders: 发布provider (只更新当前app)
●ActivityManagerService.getContentProviderImpl: 获取provider (只更新当前app)

另外,Lowmemorykiller也会根据当前的内存情况逐级进行进程释放,一共有六个级别(上面加粗的部分):

●CACHED_APP_MAX_ADJ
●CACHED_APP_MIN_ADJ
●BACKUP_APP_ADJ
●PERCEPTIBLE_APP_ADJ
●VISIBLE_APP_ADJ
●FOREGROUND_APP_ADJ

定义在ActivityManager.java文件,process_state划分18类,从-1到16之间取值

●PROCESS_STATE_CACHED_EMPTY 16 进程处于cached状态,且为空进程
●PROCESS_STATE_CACHED_ACTIVITY_CLIENT 15 进程处于cached状态,且为另一个cached进程(内含Activity)的client进程
●PROCESS_STATE_CACHED_ACTIVITY 14 进程处于cached状态,且内含Activity
●PROCESS_STATE_LAST_ACTIVITY 13 后台进程,且拥有上一次显示的Activity
●PROCESS_STATE_HOME 12 后台进程,且拥有home Activity
●PROCESS_STATE_RECEIVER 11 后台进程,且正在运行receiver
●PROCESS_STATE_SERVICE 10 后台进程,且正在运行service
●PROCESS_STATE_HEAVY_WEIGHT 9 后台进程,但无法执行restore,因此尽量避免kill该进程
●PROCESS_STATE_BACKUP 8 后台进程,正在运行backup/restore操作
●PROCESS_STATE_IMPORTANT_BACKGROUND 7 对用户很重要的进程,用户不可感知其存在
●PROCESS_STATE_IMPORTANT_FOREGROUND 6 对用户很重要的进程,用户可感知其存在
●PROCESS_STATE_TOP_SLEEPING 5 与PROCESS_STATE_TOP一样,但此时设备正处于休眠状态
●PROCESS_STATE_FOREGROUND_SERVICE 4 拥有给一个前台Service
●PROCESS_STATE_BOUND_FOREGROUND_SERVICE 3 拥有给一个前台Service,且由系统绑定
●PROCESS_STATE_TOP 2 拥有当前用户可见的top Activity
●PROCESS_STATE_PERSISTENT_UI 1 persistent系统进程,并正在执行UI操作
●PROCESS_STATE_PERSISTENT 0 persistent系统进程
●PROCESS_STATE_NONEXISTENT -1 不存在的进程

根据上面说描述的adj值和state值,我们可以按照重要性程度的不同,将进程划分为不同等级的进程,如下所示:

●前台进程
●可见进程
●服务进程
●缓存进程

具体的划分条件我们在上文的进程分类中已经讲过,这里不再赘述。

文章来源于互联网:https://www.yuque.com/beesx/beesandroid/zp3gzr

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=18272,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?