AMS系列
[AMS之进程管理LRU算法]-本篇
一概述
AMS 是 Android 中非常重要的一个系统服务,它包含了应用的启动,Activity 任务栈的管理,应用生命周期的管理,进程的管理,内存的管理等等。今天,我们就来聊一聊 AMS 这么多任务中的一个——进程管理。
二浅谈进程
在说 AMS 的进程管理之前,我们先来聊聊进程的概念,还有线程的概念。首先是进程。
2.1 进程
不论是 Android,还是 Linux,都有着进程这个概念。不过相比于 Linux,Android 中的进程,对我们并没有那么直观。在 Linux 中,进程准确的描述,应该是 Task。
struct task_struct {
......
/* 进程状态 */
volatile long state;
/* 指向该进程的内存区描述符 */
struct mm_struct *mm, *active_mm;
/* 进程ID,每个进程(线程)的PID都不同 */
pid_t pid;
...
}
在 Linux 中,进程用一个结构体 task_struct 来描述,这个结构体内有一个 pid,就是这个进程的唯一编号。而这个进程指向的内存地址,就是这个进程在内存中占用的内存。后续,这个进程的资源(包括代码资源,运行中的内存资源,等等)都是依附于这个进程的内存空间的。而操作系统也针对不同的进程的内存空间做分配。
在 Android 中,AMS 管理进程,同样如此,Android 系统的资源是有限的,不论是内存资源,还是 CPU 资源,Android 系统都需要省吃俭用,它需要知道哪些进程比较重要,那么进程不那么重要,然后针对这些进程的内存做不同的优化。这就是 AMS 对进程管理的核心逻辑。
三 ProcessList
在 Android 中,我们应用的进程一般用 ProcessRecord 表示,每一个 ProcessRecord,都表示一个在系统中运行的进程,而进程的各种信息,也记录在这个 ProcessRecord 中。
我们首先看 attachApplicationLocked 这个函数,当一个进程启动时,会调用到 AMS 的 attachApplicationLocked(具体流程可以看进程启动的源码解析)。而关于进程的处理,就是在 attachApplicationLocked 这个函数中进行的。
3.1 attachApplicationLocked
[frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java]
@GuardedBy("this")
private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
// 查找正在附加的应用程序记录...
// 如果我们在多个进程中运行,则通过PID,
// 或者如果我们使用匿名线程模拟进程,则仅拉取下一个应用记录。
ProcessRecord app;
long startTime = SystemClock.uptimeMillis();
long bindApplicationTimeMillis;
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
// 从 mPidsSelfLocked 取出指定进程的进程信息 ProcessRecord
app = mPidsSelfLocked.get(pid);
}
}
// 调用 IApplicationThread 的 bindApplication
if (app.getIsolatedEntryPoint() != null) {
// 这是一个独立的进程,它应该只调用一个入口点,而不是绑定到一个应用程序。
thread.runIsolatedEntryPoint(
app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs());
} else if (instr2 != null) {
thread.bindApplication(processName, appInfo, providerList,
instr2.mClass,
profilerInfo, instr2.mArguments,
instr2.mWatcher,
instr2.mUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
app.getCompat(), getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
app.getDisabledCompatChanges(), serializedSystemFontMap);
} else {
thread.bindApplication(processName, appInfo, providerList, null, profilerInfo,
null, null, null, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
app.getCompat(), getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
app.getDisabledCompatChanges(), serializedSystemFontMap);
}
// 绑定应用程序后激活应用程序,或者客户端可能正在运行请求(例如启动活动),然后才准备就绪。
synchronized (mProcLock) {
// 将 thread(IApplicationThread)保存到进程信息中。
// 此时处在AMS进程中,所以回将 IApplicationThread 这个 binder 对象,保存到 AMS 中。
app.makeActive(thread, mProcessStats);
}
// 更新进程的 LRU
updateLruProcessLocked(app, false, null);
}
在 Android 系统中,所有应用进程的信息都保存在 ProcessRecord 中,同时,AMS 中用两个列表 mPidsSelfLocked 和 mLruProcesses 保存了 ProcessRecord。两种不同的保存方式只是为了方便查找。
mLruProcesses 的类型是 ArrayList,但是看它名字也能猜到,它使用了 LRU Cache。所以它内部成员是按照 ProcessRecord 的 lruWeight 排序的。
在运行过程中,AMS 会根据 lruWeight 的变化来动态调整 mLruProcesses 中成员的位置。
就本例而言,刚连接(attach)上的这个应用进程的 ProcessRecord 需要通过 updateLruProcessLocked 函数加入 mLruProcesses 数组中。来看它的代码,如下所示:
3.2 updateLruProcessLocked
final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
ProcessRecord client) {
mProcessList.updateLruProcessLocked(app, activityChange, client);
}
四 ProcessList
4.1 updateLruProcessLocked
[frameworks/base/services/core/java/com/android/server/am/ProcessList.java]
@GuardedBy("mService")
void updateLruProcessLocked(ProcessRecord app, boolean activityChange, ProcessRecord client) {
final ProcessServiceRecord psr = app.mServices;
final boolean hasActivity = app.hasActivitiesOrRecentTasks() || psr.hasClientActivities()
|| psr.isTreatedLikeActivity();
// 进程是否有服务,这个值为false
final boolean hasService = false; // not impl yet. app.services.size() > 0;
if (!activityChange && hasActivity) {
// 这段代码不会执行,因为没有实现
// 如果进程有 Activity,所以我们只允许基于 Activity 的调整来移动它。
// 它应该和其他有 Activity 的进程一起保持在列表的前面,
// 我们不希望那些进程改变它们的顺序,除非是由于 Activity 操作。
return;
}
if (app.getPid() == 0 && !app.isPendingStart()) {
//被杀的进程,并且清理已完成的,不继续LRU更新。
return;
}
synchronized (mProcLock) {
updateLruProcessLSP(app, client, hasActivity, hasService);
}
}
在 Android 12 的 updateLruProcessLocked 函数中,定义了两种不修改进程 LRU 编号,直接退出的情况。
- 进程有 Service,并且 Activity 没有发生修改。
- 进程已经被杀了,并且清理工作也做完了。
但是实际上第一种情况并不会发生,因为 hasService 这个值一直是 false。也就是说这个功能并没有实现。
4.2 mLruProcesses
我们来看一下进程管理的详细逻辑。看这段代码之前,我们先了解一下 Android 系统中,对进程管理的 LRU 队列 mLruProcesses。
![[mLruProcesses.jpg]]
在 Android 中,会将已经打开的进程保存进一个队列,这个队列就是 mLruProcesses。这个队列分为三个区域。
- 当前进程有 Activity
- 当前进程没有 Activity 但是有 Service
- 当前进程没有 Activity 和 Service。
这三块区域,就组成了 mLruProcesses 这个队列。需要注意的是,在 mLruProcesses 中,越靠前的区域越不重要(容易被回收)。
4.3 updateLruProcessLSP
我们接着说 mLruProcesses 队列调整的函数 updateLruProcessLSP。updateLruProcessLSP 这个函数在之前的版本叫做 updateLruProcessLocked。它之前的意思其实就是加锁更新 mLruProcesses 队列(updateLruProcess+Locked),在 Android 12 的版本中,这个函数变成了 updateLruProcessLSP。指的是 updateLruProcess+Locked+Service+ProcLock。即它使用了 mService 和 mProcLock 进行加锁。
@GuardedBy({"mService", "mProcLock"})
private void updateLruProcessLSP(ProcessRecord app, ProcessRecord client,
boolean hasActivity, boolean hasService) {
//每一次调整LRU列表,系统都会分配一个唯一的编号,这个编号每次加1
mLruSeq++;
final long now = SystemClock.uptimeMillis();
final ProcessServiceRecord psr = app.mServices;
// 更新进程的最后活动时间
app.setLastActivityTime(now);
// 首先是快速返回的情况,如果应用程序已经在mLruProcesses 队列的末尾,那么直接返回
if (hasActivity) {
final int N = mLruProcesses.size();
if (N > 0 && mLruProcesses.get(N - 1) == app) {
return;
}
} else {
// 如果没有hasActivity,则判断当前进程是否是在Service区域的头部。
if (mLruProcessServiceStart > 0
&& mLruProcesses.get(mLruProcessServiceStart-1) == app) {
return;
}
}
int lrui = mLruProcesses.lastIndexOf(app);
// persistent 的 app 不会被杀,所以 lrui 大于0就行。
if (app.isPersistent() && lrui >= 0) {
// 我们不关心持久进程的位置,只要它们在列表中。
return;
}
// 将进程原先的位置删除,删除之后 Activity 的索引和 Service 的索引都需要左移(减一)。
if (lrui >= 0) {
if (lrui < mLruProcessActivityStart) {
mLruProcessActivityStart--;
}
if (lrui < mLruProcessServiceStart) {
mLruProcessServiceStart--;
}
mLruProcesses.remove(lrui);
}
// 然后将进程添加到 mLruProcesses 队列中新的位置。
int nextIndex;
int nextActivityIndex = -1;
// 先知开始计算新的位置,分为有 Activity 和没有两种情况,因为 hasService 一直为false。
if (hasActivity) {
// 当前的总进程数N
final int N = mLruProcesses.size();
// Service 的索引 nextIndex
nextIndex = mLruProcessServiceStart;
// 判断当前进程有没有 Activity,如果有就走 else 直接放到进程列表的顶端。
if (!app.hasActivitiesOrRecentTasks() && !psr.isTreatedLikeActivity()
&& mLruProcessActivityStart < (N - 1)) {
// 进程没有 Activity,就有些麻烦了
// 现在有 Activity,但是我这个进程没有 Activity。那么我就是被其他 Activity 启动的。
// 那么那个 Activity 就会被放到末尾(位置N),我自己被放到位置(N-1)。
// 位置设置为N-1
int pos = N - 1;
// 接下来会对 pos 进行一些调整。
while (pos > mLruProcessActivityStart) {
// 只要我的位置在 Activity 位置之后,就可以进行调整
final ProcessRecord posproc = mLruProcesses.get(pos);
// 如果我这个进程和比
if (posproc.info.uid == app.info.uid) {
// 如果这个应用有多个进程,那么就将它们放到一起(优先级依旧在mLruProcessActivityStart后面)
break;
}
pos--;
}
mLruProcesses.add(pos, app);
// 如果此进程是组的一部分,则需要将该组中的任何其他进程拉出来与它在一起。
int endIndex = pos - 1;
if (endIndex < mLruProcessActivityStart) {
endIndex = mLruProcessActivityStart;
}
nextActivityIndex = endIndex;
updateClientActivitiesOrderingLSP(app, pos, mLruProcessActivityStart, endIndex);
} else {
// 过程有 Activity,把它放在最顶端。
mLruProcesses.add(app);
nextActivityIndex = mLruProcesses.size() - 1;
}
} else if (hasService) {
// 进程有 Service(没有 Activity),将其放在 Service 列表的顶部。
mLruProcesses.add(mLruProcessActivityStart, app);
nextIndex = mLruProcessServiceStart;
mLruProcessActivityStart++;
} else {
// 其他情况的进程,它将转到非服务区的顶部。
int index = mLruProcessServiceStart;
if (client != null) {
// 如果进程有客户端,则不允许进程在列表中上移到高于该客户端的位置。
int clientIndex = mLruProcesses.lastIndexOf(client);
if (clientIndex <= lrui) {
clientIndex = lrui;
}
if (clientIndex >= 0 && index > clientIndex) {
index = clientIndex;
}
}
mLruProcesses.add(index, app);
nextIndex = index - 1;
mLruProcessActivityStart++;
mLruProcessServiceStart++;
if (index > 1) {
updateClientActivitiesOrderingLSP(app, mLruProcessServiceStart - 1, 0, index - 1);
}
}
app.setLruSeq(mLruSeq);
// 如果当前进程还依赖其他的内容提供程序或服务,也需要修改它们的优先级,防止被杀。
for (int j = psr.numberOfConnections() - 1; j >= 0; j--) {
ConnectionRecord cr = psr.getConnectionAt(j);
if (cr.binding != null && !cr.serviceDead && cr.binding.service != null
&& cr.binding.service.app != null
&& cr.binding.service.app.getLruSeq() != mLruSeq
&& (cr.flags & Context.BIND_REDUCTION_FLAGS) == 0
&& !cr.binding.service.app.isPersistent()) {
if (cr.binding.service.app.mServices.hasClientActivities()) {
if (nextActivityIndex >= 0) {
nextActivityIndex = updateLruProcessInternalLSP(cr.binding.service.app,
now,
nextActivityIndex, mLruSeq,
"service connection", cr, app);
}
} else {
nextIndex = updateLruProcessInternalLSP(cr.binding.service.app,
now,
nextIndex, mLruSeq,
"service connection", cr, app);
}
}
}
final ProcessProviderRecord ppr = app.mProviders;
for (int j = ppr.numberOfProviderConnections() - 1; j >= 0; j--) {
ContentProviderRecord cpr = ppr.getProviderConnectionAt(j).provider;
if (cpr.proc != null && cpr.proc.getLruSeq() != mLruSeq && !cpr.proc.isPersistent()) {
nextIndex = updateLruProcessInternalLSP(cpr.proc, now, nextIndex, mLruSeq,
"provider reference", cpr, app);
}
}
}
在 updateLruProcessLSP 中对进程在 mLruProcesses 队列中的位置进行了调整。其实调整的核心就一句话有 Activity 的进程往后移,没有 Activity 的进程往前移,而越往前的进程则越容易被杀。