AMS之进程管理LRU算法(Android12)

1,493 阅读7分钟

Android的知识体系搭建

AMS系列

AMS之AMS的启动流程

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 编号,直接退出的情况。

  1. 进程有 Service,并且 Activity 没有发生修改。
  2. 进程已经被杀了,并且清理工作也做完了。

但是实际上第一种情况并不会发生,因为 hasService 这个值一直是 false。也就是说这个功能并没有实现。

4.2 mLruProcesses

我们来看一下进程管理的详细逻辑。看这段代码之前,我们先了解一下 Android 系统中,对进程管理的 LRU 队列 mLruProcesses。

![[mLruProcesses.jpg]]

在 Android 中,会将已经打开的进程保存进一个队列,这个队列就是 mLruProcesses。这个队列分为三个区域。

  1. 当前进程有 Activity
  2. 当前进程没有 Activity 但是有 Service
  3. 当前进程没有 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 的进程往前移,而越往前的进程则越容易被杀。