Skip to content

PMS并发控制

解析 PMS 的锁保护域、mLock 与 mInstallLock 顺序、snapshot 协调、状态写入和 Installer 边界。

基于android-17.0.0_r1
AndroidPackageManagerService并发锁源码阅读

PMS并发控制 ​

PackageManagerService 同时面对 Binder 线程、SystemServer 启动线程、PackageHandler、Installer/installd 访问和各种后台 executor。Android 17 没有用一把“大锁”包住所有操作,而是把不同状态域和外部副作用拆到多把锁,并把调用约束写进类注释、字段注释和方法后缀。

本篇不做“锁名清单”。每一把锁都按四个问题展开:它保护谁、谁负责获取、获取后允许做什么、和其他锁怎样排序。重点追踪以下源码主线:

  • PMS 顶部注释规定的 mLock、mInstallLock、mSnapshotLock 关系;
  • mPackageStateWriteLock 与 PackageStateMutator 如何复用 mLock;
  • mOverlayPathsLock 为什么独立于包状态锁;
  • PackageManagerTracedLock 如何把 ReentrantLock 包装成可注入、可追踪、可自动释放的锁;
  • 系统包扫描、应用数据清理、状态 mutation 和 snapshot rebuild 中锁的真实组合;
  • 哪些代码必须在锁外执行,例如 installd I/O、Binder 回调、耗时日志和后台任务。

PMS010 已经解释 snapshot 的版本和重建,本篇只引用它来说明 mSnapshotLock 的锁序;PMS013 将继续讨论 Handler 消息如何把长操作拆出锁临界区。

1. 锁保护域 ​

1.1 PMS 类级合同 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
/**
 * Keep track of all those APKs everywhere.
 * <p>
 * Internally there are three important locks:
 * <ul>
 * <li>{@link #mLock} is used to guard all in-memory parsed package details
 * and other related state. It is a fine-grained lock that should only be held
 * momentarily, as it's one of the most contended locks in the system.
 * <li>{@link #mInstallLock} is used to guard all {@code installd} access, whose
 * operations typically involve heavy lifting of application data on disk. Since
 * {@code installd} is single-threaded, and it's operations can often be slow,
 * this lock should never be acquired while already holding {@link #mLock}.
 * Conversely, it's safe to acquire {@link #mLock} momentarily while already
 * holding {@link #mInstallLock}.
 * <li>{@link #mSnapshotLock} is used to guard access to two snapshot fields: the snapshot
 * itself and the snapshot invalidation flag.  This lock should never be acquired while
 * already holding {@link #mLock}. Conversely, it's safe to acquire {@link #mLock}
 * momentarily while already holding {@link #mSnapshotLock}.
 * </ul>
 * Many internal methods rely on the caller to hold the appropriate locks, and
 * this contract is expressed through method name suffixes:
 * <ul>
 * <li>fooLI(): the caller must hold {@link #mInstallLock}
 * <li>fooLIF(): the caller must hold {@link #mInstallLock} and the package
 * being modified must be frozen
 * <li>fooLPr(): the caller must hold {@link #mLock} for reading
 * <li>fooLPw(): the caller must hold {@link #mLock} for writing
 * </ul>
 * {@link #mSnapshotLock} is taken in exactly one place - {@code snapshotComputer()}.
 * It should not be taken anywhere else or used for any other purpose.
 */

这段注释是阅读 PMS 并发代码的总入口。它给出三条硬约束:

  1. mLock 保护内存中的 parsed package 和相关状态,但应短暂持有,因为竞争最激烈。
  2. mInstallLock 保护所有 installd 访问;不能在已经持有 mLock 时获取它,反向“先 install、后短暂 package lock”是允许的。
  3. mSnapshotLock 只协调 snapshot 字段和失效标志,而且只允许在 snapshotComputer() 获取。

后缀并不是命名习惯,而是调用者责任:LI、LIF、LPr、LPw 把锁和冻结前置条件编码进方法名。读到一个 fooLIF(),应立即回到所有调用点检查 mInstallLock 和 PackageFreezer 是否已经建立。

1.2 字段声明与 owner ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
// Lock for state used when installing and doing other long running operations.
// Methods that must be called with this lock held have the suffix "LI".
@SystemServerLock(LockGuard.INDEX_PACKAGE_INSTALL)
final PackageManagerTracedLock mInstallLock;

// Lock for global state used when modifying package state or settings.
// Methods that must be called with this lock held have the suffix "Locked".
@SystemServerLock(LockGuard.INDEX_PACKAGES)
final PackageManagerTracedLock mLock;

// Ensures order of overlay updates until data storage can be moved to overlay code
private final PackageManagerTracedLock mOverlayPathsLock = new PackageManagerTracedLock();

// Lock alias for doing package state mutation
private final PackageManagerTracedLock mPackageStateWriteLock;

private final PackageStateMutator mPackageStateMutator = new PackageStateMutator(
        this::getPackageSettingForMutation,
        this::getDisabledPackageSettingForMutation);

mPackageStateWriteLock 不是第三把独立的状态锁。构造函数把它指向 injector 提供的 mLock,因此 mutation API 使用一个语义更窄的别名,未来可以在不改变调用方命名的前提下迁移到独立写锁。mOverlayPathsLock 则是独立对象,用来保证 overlay path 更新顺序,不代表所有 package state 都已被保护。

1.3 锁关系图 ​

图中的虚线是禁止方向,不是推荐方向。锁关系描述“允许的嵌套顺序”,不表示每条业务路径都必须同时拿两把锁。越少进入嵌套,越容易控制持锁时间和跨服务调用风险。

2. TracedLock ​

2.1 封装目的 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerTracedLock.java

java
/**
 * This is a unique class that is used as the PackageManager lock.  It can be targeted for lock
 * injection, similar to {@link ActivityManagerGlobalLock}.
 */
public class PackageManagerTracedLock implements AutoCloseable {
    private static final String TAG = "PackageManagerTracedLock";
    private static final boolean DEBUG = false;
    private @NonNull final RawLock mLock;

    public PackageManagerTracedLock(@Nullable String lockName) {
        mLock = new RawLock(lockName);
    }

    public PackageManagerTracedLock() {
        this(null);
    }

PMS 选择一个独立类有三个实际用途:

  • injector 可以替换和控制 PMS lock,测试不必直接改业务代码;
  • 锁可以有名称,便于 debug 日志识别 mLock、mInstallLock 等具体对象;
  • 实现 AutoCloseable,让长路径使用 try-with-resources 自动释放。

2.2 获取与释放 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerTracedLock.java

java
/**
 * Use this method to acquire the lock. Use it with try-with-resources to make sure the lock is
 * released afterwards.
 */
public PackageManagerTracedLock acquireLock() {
    mLock.lock();
    return this;
}

/** Release the lock if it's held by the current thread. */
@Override
public void close() {
    mLock.unlock();
}

典型用法是:

java
try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
    mInstaller.reconcileSdkData(args);
}

acquireLock() 返回自身,正是为了让资源语法的变量指向已加锁对象。异常、提前 return 和多分支退出都会经过 close();但这只保证“释放动作不遗漏”,不保证业务拿锁顺序正确,也不保证锁内没有执行过慢的 I/O。

2.3 RawLock ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerTracedLock.java

java
public static class RawLock extends ReentrantLock {
    @Nullable private final String mLockName;

    RawLock(@Nullable String lockName) {
        mLockName = lockName;
    }

    @Override
    public void lock() {
        super.lock();
        if (DEBUG && mLockName != null) {
            Slog.i(TAG, "locked " + mLockName);
        }
    }

    @Override
    public void unlock() {
        super.unlock();
        if (DEBUG && mLockName != null) {
            Slog.i(TAG, "unlocked " + mLockName);
        }
    }
}

它仍然是 ReentrantLock,所以同一线程重复获取同一把锁是可行的;但重入不等于跨锁安全。getRawLock() 还允许需要 tryLock()、持有状态或等待队列信息的测试/诊断路径直接操作底层锁。生产代码若绕过 acquireLock(),就失去统一的资源释放模式。

3. mLock ​

3.1 被保护的数据 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
@Watched
@GuardedBy("mLock")
final WatchedArrayMap<String, AndroidPackage> mPackages = new WatchedArrayMap<>();

@Watched
@GuardedBy("mLock")
final Settings mSettings;

@GuardedBy("mLock")
final WatchedArrayMap<String, Integer> mFrozenPackages = new WatchedArrayMap<>();

mLock 保护的不只是 mPackages map,而是 parsed package、Settings、冻结计数以及其他相关状态。@Watched 还把状态变化接入 snapshot invalidation;所以在 mLock 下修改 watched object,除了要满足内存可见性,还会影响后续 Computer 版本。

3.2 后缀契约 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
@GuardedBy("mLock")
void forEachPackageSetting(Consumer<PackageSetting> actionLocked) {
    synchronized (mLock) {
        int size = mSettings.getPackagesLocked().size();
        for (int index = 0; index < size; index++) {
            actionLocked.accept(mSettings.getPackagesLocked().valueAt(index));
        }
    }
}

方法名中的 actionLocked 也属于契约:consumer 在 lock 内运行,不能在其中调用可能获取 mInstallLock、等待 Handler 或执行 Binder 回调的代码。PMS 的锁问题经常不是“忘记加锁”,而是把一个未知耗时的 callback 传进了已经持锁的遍历方法。

3.3 锁序限制 ​

PMS 类级注释明确禁止 mLock -> mInstallLock。原因是 installd 单线程且磁盘操作可能很慢:如果持有 mLock 等待 installd,所有包查询和其他状态修改都会被阻塞;另一个路径若先持有 mInstallLock 再短暂等待 mLock,就会形成循环等待。

正确的拆分通常是:

  1. 在 mLock 下读取/更新 package state,准备 Installer 参数;
  2. 释放 mLock;
  3. 获取 mInstallLock 执行 Installer/installd;
  4. 需要发布结果时再次短暂获取 mLock。

这不是绝对模板,具体方法必须查看 LI/LIF 后缀和调用方,但任何在 synchronized (mLock) 中直接调用 mInstaller 的代码都应优先审查。

4. mInstallLock ​

4.1 Installer 边界 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
// Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
// LOCK HELD. Can be called with mInstallLock held.
@GuardedBy("mInstallLock")
final Installer mInstaller;

@SystemServerLock(LockGuard.INDEX_PACKAGE_INSTALL)
final PackageManagerTracedLock mInstallLock;

mInstallLock 的 owner 是 Installer/installd 访问和安装流程中较长的 app-data 操作,不是所有安装相关 Java 状态。包扫描和安装状态仍需要 mLock;因此常见路径是“安装锁保护外部数据,包锁保护内存状态”,两者职责不能互换。

4.2 SDK data ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName,
        @NonNull List<String> subDirNames, int userId, int appId, int previousAppId,
        @NonNull String seInfo, int flags) throws IOException {
    ReconcileSdkDataArgs args = mInstaller.buildReconcileSdkDataArgs(volumeUuid,
            packageName, subDirNames, userId, appId, seInfo, flags);
    args.previousAppId = previousAppId;
    try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
        mInstaller.reconcileSdkData(args);
    } catch (InstallerException e) {
        throw new IOException(e.getMessage());
    }
}

参数构造发生在获取 mInstallLock 之前,真正的 Installer 调用发生在锁内,异常在释放锁后转换为 IOException。这体现了一个重要边界:锁保护的是与 installd 的串行访问,不是参数构造本身;如果参数构造只读外部对象,也不必把它扩大到临界区。

4.3 系统包扫描的双锁 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InitAppsHelper.java

java
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
private List<ApexManager.ScanResult> scanApexPackagesTraced(PackageParser2 packageParser) {
    Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanApexPackages");
    try {
        return mInstallPackageHelper.scanApexPackages(mApexManager.getAllApexInfos(),
                mSystemParseFlags, mSystemScanFlags, packageParser, mExecutorService);
    } finally {
        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    }
}

系统包扫描是少数明确同时要求两把锁的路径。@GuardedBy 没有表达顺序本身,需要结合 PMS 顶部合同:合法顺序是先 mInstallLock,再短暂获取 mLock,而不是反过来。扫描期间还必须满足方法后缀/注释规定的冻结和状态前置条件。

这张图表达的是锁顺序和 owner 交界,不代表所有 scanApexPackagesTraced() 内部调用都只做这几步;真正的扫描 helper 还会调用 parser、executor 和 APEX manager。教材阅读时要继续追 scanApexPackages() 的内部锁契约。

5. 状态写锁 ​

5.1 初始化为 mLock ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
mLock = injector.getLock();
mPackageStateWriteLock = mLock;
mInstallLock = injector.getInstallLock();

当前 Android 17 环境下,mPackageStateWriteLock == mLock。这个别名把“包状态 mutation”从更广义的“所有 PMS 内存状态”中单独命名出来,让 PackageStateMutator 的 API 不依赖 mLock 这个历史名字。

5.2 mutation提交 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
@NonNull
public PackageStateMutator.Result commitPackageStateMutation(
        @Nullable PackageStateMutator.InitialState initialState,
        @NonNull Consumer<PackageStateMutator> consumer) {
    synchronized (mPackageStateWriteLock) {
        final PackageStateMutator.Result result = mPackageStateMutator.generateResult(
                initialState, mChangedPackagesTracker.getSequenceNumber());
        if (result != PackageStateMutator.Result.SUCCESS) {
            return result;
        }

        consumer.accept(mPackageStateMutator);
        mPackageStateMutator.onFinished();
    }
    return PackageStateMutator.Result.SUCCESS;
}

consumer 在 mPackageStateWriteLock 内执行,所以它只能做精简的 state setter;读取、计算、网络、Installer 和跨服务回调都应在锁外完成。冲突时 consumer 不会运行,调用方必须依据 Result 选择重读或放弃。

5.3 已持锁重试路径 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
PackageStateMutator.Result result = null;
if (Thread.holdsLock(mPackageStateWriteLock)) {
    // If the thread is already holding the lock, this is likely a retry based on a prior
    // failure, and re-calculating whether a state change occurred can be skipped.
    result = PackageStateMutator.Result.SUCCESS;
}
synchronized (mPackageStateWriteLock) {
    if (result == null) {
        result = mPackageStateMutator.generateResult(
                initialState, mChangedPackagesTracker.getSequenceNumber());
    }
    if (result != PackageStateMutator.Result.SUCCESS) {
        return result;
    }
    PackageStateWrite state = mPackageStateMutator.forPackage(packageName);
    if (state == null) {
        return PackageStateMutator.Result.SPECIFIC_PACKAGE_NULL;
    }
    consumer.accept(state);
    state.onChanged();
}

这个重载依赖 ReentrantLock 的可重入性质:如果当前线程已经持有写锁,认为它可能正处于一次失败后的重试,不重复做序列冲突计算。这个优化只有在调用方确实遵守同一把锁的前提下成立;如果未来 mPackageStateWriteLock 改成独立锁,所有已持锁调用点都要重新审查。

6. mSnapshotLock ​

6.1 唯一入口 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
private final Object mSnapshotLock = new Object();

public Computer snapshotComputer(boolean allowLiveComputer) {
    var isHoldingPackageLock = Thread.holdsLock(mLock);
    if (allowLiveComputer && isHoldingPackageLock) {
        return mLiveComputer;
    }

    var oldSnapshot = sSnapshot.get();
    var pendingVersion = sSnapshotPendingVersion.get();
    if (oldSnapshot != null && oldSnapshot.getVersion() == pendingVersion) {
        return oldSnapshot.use();
    }

mSnapshotLock 不是“保护所有 snapshot 读写”的通用锁,而是只在版本不匹配时协调重建。普通查询只读 sSnapshot 和 pending version,不会每次都拿锁;持锁线程若允许 live,则直接返回 mLiveComputer。

6.2 锁内双检 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
synchronized (mSnapshotLock) {
    var rebuildSnapshot = sSnapshot.get();
    var rebuildVersion = sSnapshotPendingVersion.get();
    if (rebuildSnapshot != null && rebuildSnapshot.getVersion() == rebuildVersion) {
        return rebuildSnapshot.use();
    }

    synchronized (mLock) {
        rebuildSnapshot = sSnapshot.get();
        rebuildVersion = sSnapshotPendingVersion.get();
        if (rebuildSnapshot != null && rebuildSnapshot.getVersion() == rebuildVersion) {
            return rebuildSnapshot.use();
        }

        var newSnapshot = rebuildSnapshot(rebuildSnapshot, rebuildVersion);
        sSnapshot.set(newSnapshot);
        return newSnapshot.use();
    }
}

两次检查分别消除两种竞态:多个线程排队等待 mSnapshotLock 时,后来的线程应复用先到线程刚发布的快照;线程从 mSnapshotLock 进入 mLock 的过程中可能发生新 invalidation,因此必须在 mLock 内再次捕获版本。

锁顺序是 mSnapshotLock -> mLock。持有 mLock 的分支不会再获取 mSnapshotLock,这是避免反向等待的关键。任何新代码都不应在别处直接 synchronized (mSnapshotLock)。

6.3 重建边界 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
@GuardedBy("mLock")
private Computer rebuildSnapshot(@Nullable Computer oldSnapshot, int newVersion) {
    var now = SystemClock.currentTimeMicro();
    var hits = oldSnapshot == null ? -1 : oldSnapshot.getUsed();
    var args = new Snapshot(Snapshot.SNAPPED);
    var newSnapshot = new ComputerEngine(args, newVersion);
    var done = SystemClock.currentTimeMicro();

    if (mSnapshotStatistics != null) {
        mSnapshotStatistics.rebuild(now, done, hits,
                newSnapshot.getPackageStates().size());
    }
    return newSnapshot;
}

rebuildSnapshot() 标注 @GuardedBy("mLock"),因此它的 caller 必须已经持有 package lock。构建期间复制 Settings、包 map、resolver 和 AppsFilter,保证快照字段来自一个稳定的 PMS 状态。统计在同一临界区更新,但日志 I/O 在 SnapshotStatistics 内部被放到其统计锁之外。

7. 锁外工作 ​

7.1 清理用户数据 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
final int callingUid = Binder.getCallingUid();
final Computer snapshot = snapshotComputer();
snapshot.enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
        false /* checkShell */, "clear application data");

if (snapshot.getPackageStateForInstalledAndFiltered(
        packageName, callingUid, userId) == null) {
    // Post the observer callback and return.
    return;
}

// Queue up an async operation since the package deletion may take a little while.
mHandler.post(new Runnable() {
    public void run() {
        try (PackageFreezer freezer = freezePackage(packageName, userId,
                "clearApplicationUserData", ApplicationExitInfo.REASON_USER_REQUESTED,
                null /* request */, /* waitAppKilled= */ true)) {
            try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
                succeeded = clearApplicationUserDataLIF(snapshotComputer(), packageName,
                        userId, restorePregrantedPermissions);
            }
            synchronized (mLock) {
                if (succeeded) {
                    resetComponentEnabledSettingsIfNeededLPw(packageName, userId, callingUid);
                }
            }
        }
    }
});

入口先用 snapshot 做身份、user 和 package 存在性检查,然后把可能耗时的清理投递到 Handler。真正执行时重新取得 snapshot、冻结包、拿 mInstallLock 做 app-data 清理,最后才短暂拿 mLock 修改组件状态。

这条链说明三个并发事实:

  • 入口 snapshot 不能跨越异步任务直接当成最新状态;
  • PackageFreezer 和 mInstallLock 保护不同对象,冻结包不等于拿到 install lock;
  • 清理完成后的 package state 更新必须回到 mLock,而 Binder observer 回调应在锁外发送。

7.2 清理profile ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
final Computer snapshot = snapshotComputer();
final AndroidPackage pkg = snapshot.getPackage(packageName);
try (PackageFreezer ignored = freezePackage(packageName, USER_ALL,
        "clearApplicationProfileData", ApplicationExitInfo.REASON_OTHER,
        null /* request */)) {
    try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
        mAppDataHelper.clearAppProfilesLIF(pkg);
    }
}

这里没有在 mLock 内调用 mInstallLock。package 对象先从 snapshot 取得,冻结和 Installer 操作在 install lock 下完成;clearAppProfilesLIF() 的方法后缀要求调用者持有 install lock,并且 package 已被冻结。

7.3 createNewUser ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
void createNewUser(int userId, @Nullable Set<String> userTypeInstallablePackages,
        String[] disallowedPackages) {
    try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
        mSettings.createNewUserLI(this, mInstaller, userId,
                userTypeInstallablePackages, disallowedPackages);
    }
    synchronized (mLock) {
        scheduleWritePackageRestrictions(userId);
        scheduleWritePackageListLocked(userId);
        mAppsFilter.onUserCreated(snapshotComputer(), userId);
    }
}

新用户创建先在 mInstallLock 下执行 Settings/Installer 相关工作,释放后再在 mLock 下安排持久化和更新 AppsFilter。它体现了“先外部数据,再包状态发布”的顺序,也说明锁外释放并不意味着整个业务结束:后续仍有 schedule 和 visibility cache 工作。

8. Overlay与其他锁 ​

8.1 Overlay锁 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
// Ensures order of overlay updates until data storage can be moved to overlay code
private final PackageManagerTracedLock mOverlayPathsLock = new PackageManagerTracedLock();

该锁只表达 overlay path 更新的顺序约束。它不替代 mLock,也不自动保护 mPackages 或 Settings。读到 overlay 相关方法时,应分别检查:

  • 是否需要 mOverlayPathsLock 保证更新顺序;
  • 是否还需要 mLock 读取/修改 package state;
  • 是否调用了 OverlayManagerService 或文件系统,必须把外部调用移出 PMS 大锁。

8.2 局部锁 ​

PMS 还存在 mDirtyUsers、mProtectedBroadcasts、mKeepUninstalledPackages 等局部对象锁。它们保护单一缓存或待写集合,不能被提升成全局锁。局部锁的安全依赖于“不在持有 PMS 大锁时反向等待局部锁”或相反的既定顺序;具体关系必须按调用点核对。

8.3 优先级提升 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

java
private static final boolean ENABLE_BOOST = false;

private static ThreadPriorityBooster sThreadPriorityBooster = new ThreadPriorityBooster(
        Process.THREAD_PRIORITY_FOREGROUND, LockGuard.INDEX_PACKAGES);

public static void boostPriorityForPackageManagerTracedLockedSection() {
    if (ENABLE_BOOST) {
        sThreadPriorityBooster.boost();
    }
}

public static void resetPriorityAfterPackageManagerTracedLockedSection() {
    if (ENABLE_BOOST) {
        sThreadPriorityBooster.reset();
    }
}

源码保留了锁临界区优先级提升的接口,但 Android 17 当前 ENABLE_BOOST = false。不能把 ThreadPriorityBooster 的存在写成运行时已经启用的性能策略;实际效果取决于开关和调用点。

9. 方法命名与读源码 ​

9.1 后缀责任 ​

后缀源码约定阅读时要确认
LI持有 mInstallLock是否访问 Installer/installd
LIF持有 mInstallLock 且 package frozen是否先创建 PackageFreezer
LPr持有 mLock 读取是否只读 Settings/package state
LPw持有 mLock 写入是否触发 Watchable/snapshot invalidation
Locked传统命名,按具体注释判断不能只凭后缀猜读写方向

后缀不是 Java 编译器能检查的类型系统。@GuardedBy、调用方的 synchronized/acquireLock() 和测试才共同构成证据。若方法同时标注 @GuardedBy({"mPm.mInstallLock", "mPm.mLock"}),还必须检查两把锁的先后顺序。

9.2 典型死锁图 ​

图中上半部分是典型循环等待:A 从 mLock 等 install,B 从 install 等 package。下半部分是 PMS 注释允许的方向。实际审查时还要把 PackageFreezer、PermissionManager、OverlayManager 和回调锁纳入图中,不能只看两把 PMS 锁。

10. 测试与诊断 ​

10.1 现有测试的边界 ​

PackageManagerServiceInternalTest 使用 Mockito 替换 snapshotComputer(),主要证明内部 API 的委托和状态结果,不证明真实锁顺序。AppsFilterImplTest 验证 visibility 查询不修改 watcher,也不证明 mLock/mSnapshotLock 的并发竞争。

源码文件:frameworks/base/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceInternalTest.java

java
PackageManagerService packageManagerService =
        spy(new PackageManagerService(injector, mTestParams));
doReturn(mSnapshotComputer).when(packageManagerService).snapshotComputer();
mPackageManagerInternal = packageManagerService.new PackageManagerInternalImpl();

要证明并发控制,需要额外的可控屏障和断言:

  • 线程 A 在 mInstallLock 内阻塞时,线程 B 能否继续无锁 snapshot 读取;
  • 两个线程同时发现 snapshot 过期时,是否只有一个进入 rebuildSnapshot();
  • 非法 mLock -> mInstallLock 路径是否被静态检查、LockGuard 或测试捕获;
  • try-with-resources 抛异常后锁是否释放;
  • PackageFreezer 未建立时调用 LIF 方法是否被拒绝或产生错误结果。

10.2 GuardedBy ​

@GuardedBy 主要供静态工具、代码审查和读者理解。它不会在运行时自动获取锁,也不会阻止 Java 调用方直接执行方法。真正运行时的互斥来自 ReentrantLock/synchronized,真正的顺序约束来自所有调用路径的共同遵守。

10.3 诊断锁等待 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerTracedLock.java

java
public RawLock getRawLock() {
    return mLock;
}

调试代码可以通过 raw lock 检查 isHeldByCurrentThread()、getHoldCount() 或等待队列信息。锁名只有在 DEBUG = true 时写日志,因此生产构建中不能假定每次 lock/unlock 都会有 logcat 记录。更可靠的诊断组合是线程 dump、Perfetto 锁 trace、LockGuard 检查和业务方法后缀。

11. 阅读路线 ​

读到 PMS 中一个涉及锁的方法时,按下面顺序建立证据:

  1. 先看方法后缀、@GuardedBy 和调用方是否已持有对应锁。
  2. 标出它访问的状态 owner:mPackages、Settings、frozen map、snapshot 字段、Installer 或 overlay。
  3. 搜索同一方法是否在锁内调用 Binder、Installer、文件系统、Handler、executor 或 observer 回调。
  4. 若出现多把锁,记录获取顺序;特别检查是否存在 mLock -> mInstallLock 或 mLock -> mSnapshotLock。
  5. 若方法跨异步边界,确认入口 snapshot 是否在任务执行时重新获取,结果发布是否重新进入 mLock。
  6. 对 PackageStateMutator,确认 consumer 是否只写 state,是否检查 Result,是否需要在冲突后重试。
  7. 对 Computer,确认是 live computer 还是 snapped computer,不能把 ComputerLocked 当成自动加锁对象。
  8. 最后用线程 dump/测试验证等待关系,而不是仅凭方法名判断“这里应该安全”。

小结 ​

Android 17 PMS 的并发控制是一套调用协议,而不是几把孤立的锁:

  • mLock 保护 parsed package、Settings 和相关内存状态,必须短暂持有;
  • mInstallLock 串行化 Installer/installd 长耗时操作,禁止从 mLock 进入;
  • 合法嵌套方向是先 mInstallLock,再短暂 mLock;mSnapshotLock 只在 snapshotComputer() 中按 mSnapshotLock -> mLock 获取;
  • mPackageStateWriteLock 当前是 mLock 的语义别名,mutation consumer 在写锁内只做精简 state setter;
  • mOverlayPathsLock 和局部缓存锁只保护各自 owner,不应被误读成 PMS 全局锁;
  • PackageManagerTracedLock 提供注入、命名、raw lock 诊断和自动释放,但不能自动修复业务锁顺序;
  • 长耗时 I/O、异步清理、Binder 回调和日志应尽量移出大锁,完成后再回锁发布状态;
  • @GuardedBy、方法后缀、调用路径、测试和线程诊断共同构成并发证明,单独看其中任何一项都不够。

下一篇将进入 PackageHandler 和 PMS 的消息调度:哪些操作从 Binder 入口转成 Handler 消息,消息如何跨越 mLock/mInstallLock,以及 POST_INSTALL、INIT_COPY 等阶段怎样把锁内状态和锁外副作用接起来。