Android13 救援模式

救援模式:

目前市场上的手机消费者包括资深用户,当他们的手机出现无限循环启动的异常时,用户没有办法修复异常只能通过设备商售后处理。Google在Android 8.0加入该新功能,称之为rescue party救援程序。主要监控系统核心程序出现循环崩溃的时候,会启动该程序,根据不同的救援级别做出一系列操作,看是否可恢复设备,最严重的时候则是通过进入recovery然后提供用户清空用户数据恢复出厂设置解决。

遇到开机进到recovery模式的情况,其实我们全局搜索下关键字“RescueParty”

就可以看到在init进程收到reboot,recovery指令之前,就可以看到因为救援模式,而发生了wipe-data

以下代码基于Android13:

首先救援分不同的级别,不同的级别执行不同的代码逻辑

//QSSI.13\frameworks\base\services\core\java\com\android\server\RescueParty.java
@VisibleForTesting
static final int LEVEL_NONE = 0;
@VisibleForTesting
static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1;
@VisibleForTesting
static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2;
@VisibleForTesting
static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
@VisibleForTesting
static final int LEVEL_WARM_REBOOT = 4;
@VisibleForTesting
static final int LEVEL_FACTORY_RESET = 5;
@VisibleForTesting
static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
@VisibleForTesting
static final String TAG = "RescueParty";
....
private static void executeRescueLevelInternal(Context context, int level, @Nullable
        String failedPackage) throws Exception {
    FrameworkStatsLog.write(FrameworkStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
    // Try our best to reset all settings possible, and once finished
    // rethrow any exception that we encountered
    Exception res = null;
    Runnable runnable;
    Thread thread;
    switch (level) {
        case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
            try {
                resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
                        level);
            } catch (Exception e) {
                res = e;
            }
            try {
                resetDeviceConfig(context, /*isScoped=*/true, failedPackage);
            } catch (Exception e) {
                res = e;
            }
            break;
        case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
            try {
                resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_CHANGES,
                        level);
            } catch (Exception e) {
                res = e;
            }
            try {
                resetDeviceConfig(context, /*isScoped=*/true, failedPackage);
            } catch (Exception e) {
                res = e;
            }
            break;
        case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
            try {
                resetAllSettingsIfNecessary(context, Settings.RESET_MODE_TRUSTED_DEFAULTS,
                        level);
            } catch (Exception e) {
                res = e;
            }
            try {
                resetDeviceConfig(context, /*isScoped=*/false, failedPackage);
            } catch (Exception e) {
                res = e;
            }
            break;
        case LEVEL_WARM_REBOOT:
            // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
            // when device shutting down.
            SystemProperties.set(PROP_ATTEMPTING_REBOOT, "true");
            runnable = () -> {
                try {
                    PowerManager pm = context.getSystemService(PowerManager.class);
                    if (pm != null) {
                        pm.reboot(TAG);
                    }
                } catch (Throwable t) {
                    logRescueException(level, failedPackage, t);
                }
            };
            thread = new Thread(runnable);
            thread.start();
            break;
        case LEVEL_FACTORY_RESET:
            SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true");
            runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        RecoverySystem.rebootPromptAndWipeUserData(context, TAG);
                    } catch (Throwable t) {
                        logRescueException(level, failedPackage, t);
                    }
                }
            };
            thread = new Thread(runnable);
            thread.start();
            break;
    }

    if (res != null) {
        throw res;
    }
}

触发场景:

(1)system_server 在 10 分钟内重启 5 次以上调整一次级别。(Android 12以前 为5分钟内5次)

(2)永久性系统应用在 50 秒内崩溃 5 次以上调整一次级别。(Android 12 以前 默认为30秒内5次)

当检测到上述某种情况时,救援程序会将其上报给下一救援级别、处理与该级别相关联的任务,并让设备继续运行,看看能否恢复。清除或重置内容的程度随级别而增加。最高级别会提示用户将设备恢复出厂设置。

别的逻辑都比较简单,我们看一下恢复出厂的逻辑:

//QSSI.13/frameworks/base/core/java/android/os/RecoverySystem.java
    public static void rebootPromptAndWipeUserData(Context context, String reason)
            throws IOException {
        boolean checkpointing = false;
        boolean needReboot = false;
        IVold vold = null;
        try {
            vold = IVold.Stub.asInterface(ServiceManager.checkService("vold"));
            if (vold != null) {
                checkpointing = vold.needsCheckpoint();
            } else  {
                Log.w(TAG, "Failed to get vold");
            }
        } catch (Exception e) {
            Log.w(TAG, "Failed to check for checkpointing");
        }

        // If we are running in checkpointing mode, we should not prompt a wipe.
        // Checkpointing may save us. If it doesn't, we will wind up here again.
        // 检测是否可以通过回滚消除问题,
        if (checkpointing) {
            try {
                vold.abortChanges("rescueparty", false);
                Log.i(TAG, "Rescue Party requested wipe. Aborting update");
            } catch (Exception e) {
                Log.i(TAG, "Rescue Party requested wipe. Rebooting instead.");
                PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
                pm.reboot("rescueparty");
            }
            return;
        }

        String reasonArg = null;
        if (!TextUtils.isEmpty(reason)) {
            reasonArg = "--reason=" + sanitizeArg(reason);
        }

        final String localeArg = "--locale=" + Locale.getDefault().toString();
          //执行恢复出厂命令--prompt_and_wipe_data
        bootCommand(context, null, "--prompt_and_wipe_data", reasonArg, localeArg);
    }
    
        private static void bootCommand(Context context, String... args) throws IOException {
        LOG_FILE.delete();

        StringBuilder command = new StringBuilder();
        for (String arg : args) {
            if (!TextUtils.isEmpty(arg)) {
                command.append(arg);
                command.append("\n");
            }
        }

        // Write the command into BCB (bootloader control block) and boot from
        // there. Will not return unless failed.
        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
        rs.rebootRecoveryWithCommand(command.toString());

        throw new IOException("Reboot failed (no permissions?)");
    }

这里通过Biner调用远程的RecoverySystemService

//QSSI.13\frameworks\base\services\core\java\com\android\server\recoverysystem\RecoverySystemService.java
@Override // Binder call
public void rebootRecoveryWithCommand(String command) {
    if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
    synchronized (sRequestLock) {
        if (!setupOrClearBcb(true, command)) {
            return;
        }

        // Having set up the BCB, go ahead and reboot.
         //最终还是调用PowerManager,触发重启
        PowerManager pm = mInjector.getPowerManager();
        pm.reboot(PowerManager.REBOOT_RECOVERY);
    }
}

救援程序的禁用场景:

(1)PROP_ENABLE_RESCUE属性值为false,并且PROP_DEVICE_CONFIG_DISABLE_FLAG属性为true

(2)eng版本下

(3)调试版本,并且usb连接电脑

(4)PROP_DISABLE_RESCUE为true

逻辑控制代码:

//QSSI.13\frameworks\base\services\core\java\com\android\server\RescueParty.java
private static boolean isDisabled() {
    // Check if we're explicitly enabled for testing
    if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
        return false;
    }

    // We're disabled if the DeviceConfig disable flag is set to true.
    // This is in case that an emergency rollback of the feature is needed.
    if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
        Slog.v(TAG, "Disabled because of DeviceConfig flag");
        return true;
    }

    // We're disabled on all engineering devices
    if (Build.IS_ENG) {
        Slog.v(TAG, "Disabled because of eng build");
        return true;
    }

    // We're disabled on userdebug devices connected over USB, since that's
    // a decent signal that someone is actively trying to debug the device,
    // or that it's in a lab environment.
    if (Build.IS_USERDEBUG && isUsbActive()) {
        Slog.v(TAG, "Disabled because of active USB connection");
        return true;
    }

    // One last-ditch check
    if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
        Slog.v(TAG, "Disabled because of manual property");
        return true;
    }

    return false;
}

这也就解释了前面的现象,也就是插着usb设备不会进到recovery模式。

系统开机早期,开启RecoverySystemService 服务,然后通过registerHealthObserver注册PackageWatchdog崩溃事件的监听

//QSSI.13\frameworks\base\services\java\com\android\server\SystemServer.java
private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {
    t.traceBegin("startBootstrapServices");
    ...
    // Bring up recovery system in case a rescue party needs a reboot
    t.traceBegin("StartRecoverySystemService");
    mSystemServiceManager.startService(RecoverySystemService.Lifecycle.class);
    t.traceEnd();
    ...
    // Now that we have the bare essentials of the OS up and running, take
    // note that we just booted, which might send out a rescue party if
    // we're stuck in a runtime restart loop.
    RescueParty.registerHealthObserver(mSystemContext);
    PackageWatchdog.getInstance(mSystemContext).noteBoot();

    t.traceEnd();
    t.traceEnd(); // startBootstrapServices
}
//QSSI.13\frameworks\base\services\core\java\com\android\server\RescueParty.java
/** Register the Rescue Party observer as a Package Watchdog health observer */
public static void registerHealthObserver(Context context) {
    PackageWatchdog.getInstance(context).registerHealthObserver(
            RescuePartyObserver.getInstance(context));
}

这里要注意RescuePartyObserver这个内部类实现了PackageHealthObserver接口,比如说当PackageWatchdog检测到应用崩溃就会回调到execute方法,这里的崩溃包括Crash和Anr,最后executeRescueLevel->executeRescueLevelInternal回到上面不同的level等级做处理

public static class RescuePartyObserver implements PackageHealthObserver {
    ...
    @Override
    public boolean execute(@Nullable VersionedPackage failedPackage,
            @FailureReasons int failureReason, int mitigationCount) {
        if (isDisabled()) {
            return false;
        }
        if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
                || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
             //获取当前救援等级
            final int level = getRescueLevel(mitigationCount,
                    mayPerformFactoryReset(failedPackage));
            executeRescueLevel(mContext,
                    failedPackage == null ? null : failedPackage.getPackageName(), level);
            return true;
        } else {
            return false;
        }
    }
    ...
}

当设备具有有效的 USB 数据连接时,系统会停止所有救援事件,因为这是一个较强的信号,表示有人正在调试设备。如需停止此类抑制行为,请运行以下命令

adb shell setprop persist.sys.enable_rescue 1

在此处,您可以触发系统或界面崩溃循环。

如需触发低级 system_server 崩溃循环,请运行以下命令:

adb shell setprop debug.crash_system 1
//QSSI.13/frameworks/base/services/java/com/android/server/SystemServer.java
// For debugging RescueParty
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_system", false)) {
    throw new RuntimeException();
}

现在我们需要看下PackageWatchdog它是怎么知道system_server或者应用出问题了呢?

  • 如何判断system_server出现问题

首先我们知道PackageWatchdog是运行在system_server里的,system_server异常会直接导致system_server重启,出问题时根本走不到PackageWatchdog里,那它是如何触发的呢?

我们可以称之为Boot记录法,还记得我们上面说过系统开机的时候会触发一次PackageWatchdog的noteBoot方法吗?这个方法其实用处很简单,只是起到通知作用,告诉PackageWatchdog我这边system_server在进行一次Boot引导了,此时PackageWatchdog会通过内部类BootThreshold进行一次记录,将当前时间写入sys.rescue_boot_start,然后自增sys.rescue_boot_count的值记录system_server重启的次数,后续异常重新noteBoot时这个值就会增加一次,当这些时间和次数都超出预设值时就会触发一个level等级,触发对应RescueParty逻辑,并将当前level等级保存到sys.boot_mitigation_count中,有必要也会保存到mate中,然后重置计数器。

//QSSI.13\frameworks\base\services\core\java\com\android\server\PackageWatchdog.java
@VisibleForTesting
static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5;
static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);

public void noteBoot() {
    synchronized (mLock) {
         //自增Boot阈值计数器,并检查是否超出预设值
        if (mBootThreshold.incrementAndTest()) {
             //重置计数器
            mBootThreshold.reset();
             //自增当前level等级
            int mitigationCount = mBootThreshold.getMitigationCount() + 1;
            PackageHealthObserver currentObserverToNotify = null;
            int currentObserverImpact = Integer.MAX_VALUE;
              //处理其他Observers
            for (int i = 0; i < mAllObservers.size(); i++) {
                final ObserverInternal observer = mAllObservers.valueAt(i);
                PackageHealthObserver registeredObserver = observer.registeredObserver;
                if (registeredObserver != null) {
                    int impact = registeredObserver.onBootLoop(mitigationCount);
                    if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
                            && impact < currentObserverImpact) {
                        currentObserverToNotify = registeredObserver;
                        currentObserverImpact = impact;
                    }
                }
            }
            if (currentObserverToNotify != null) {
                 //保存当前level等级,有必选会保存到metadata中
                mBootThreshold.setMitigationCount(mitigationCount);
                mBootThreshold.saveMitigationCountToMetadata();
                //回调到RescueParty,执行executeRescueLevel
                currentObserverToNotify.executeBootLoopMitigation(mitigationCount);
            }
        }
    }
}

/** Increments the boot counter, and returns whether the device is bootlooping. */
public boolean incrementAndTest() {
    //从Meta中读取level等级
    readMitigationCountFromMetadataIfNecessary();
    final long now = mSystemClock.uptimeMillis();
    if (now - getStart() < 0) {
        Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
        setStart(now);
        setMitigationStart(now);
    }
     //长时间未发生异常,重置level记录
    if (now - getMitigationStart() > DEFAULT_DEESCALATION_WINDOW_MS) {
        setMitigationCount(0);
        setMitigationStart(now);
    }
    final long window = now - getStart();
     //阈值条件内未重新Boot,重置system_server重启计数器
    if (window >= mTriggerWindow) {
        setCount(1);
        setStart(now);
        return false;
    } else {
         //触发阈值条件,计数器+1,当大约mBootTriggerCount时返回true
        int count = getCount() + 1;
        setCount(count);
        EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
        return count >= mBootTriggerCount;
    }
}
  • 如何检查应用出现问题

应用问题一般分两种Crash和Anr,这些对于当前系统来说都是不正常的情况,都需要记录。

先说Crash,首先我们得知道Android系统是如何获取到异常的。

Java中异常发生时,如果没有一个异常处理器来处理这个异常,程序会被中止。在 JVM 当中有一个预先定义好的异常处理层次结构。结构中的第一层是try catch 块,代码类似:

try {
    crashyCode()
 } catch (Exception e) {
    ...
 }

如果第一个 catch 块无法处理这个异常,异常便会向此方法的调用方进行传递。如果所有的 catch 块都无法处理某个异常,该异常便会交由当前线程的 UncaughtExceptionHandler 来处理。

setUncaughtExceptionHandler 可以调协在当前线程里,未被 catch 块捕获的异常处理流程会先来到这里;还可以设置在 ThreadGroup 当中,当前线程的 UncaughtExceptionHandler 无法处理的异常会在这里被处理。如果 ThreadGroup 的 UncaughtExceptionHandler 还是无法处理该异常,那么最终将会被交由默认异常处理程序 ( default uncaught exception handler ) 处理,也就是打印出异常栈,并终止程序。当然你也可以覆盖这种行为:

Thread.setDefaultUncaughtExceptionHandler{/*自定义实现的UncaughtExceptionHandler*/}

Android的异常处理也是基于这个进行设计的,首先在RuntimeInit中

//QSSI.13\frameworks\base\core\java\com\android\internal\os\RuntimeInit.java
    //注册UncaughtExceptionHandler处理函数,出现未捕获的Crash时调用uncaughtException方法
    Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));

    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {

                //触发AMS的Crash机制
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
                ...
            } finally {
                // Try everything to make sure this process goes away.
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }

这边我们将异常捕获到我们的Android代码当中,并由AMS中handleApplicationCrash->handleApplicationCrashInner,此时需要引入AppErrors类,AMS意思调用的其crashApplication进一步处理,

AppErrorscrashApplication->crashApplicationInner

//QSSI.13\frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java
public void handleApplicationCrash(IBinder app,
            ApplicationErrorReport.ParcelableCrashInfo crashInfo) {
        ProcessRecord r = findAppProcess(app, "Crash");
        final String processName = app == null ? "system_server"
                : (r == null ? "unknown" : r.processName);

        handleApplicationCrashInner("crash", r, processName, crashInfo);
    }

    /* Native crash reporting uses this inner version because it needs to be somewhat
     * decoupled from the AM-managed cleanup lifecycle
     */
    void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
            ApplicationErrorReport.CrashInfo crashInfo) {
        ...

        mAppErrors.crashApplication(r, crashInfo);
    }
//QSSI.13\frameworks\base\services\core\java\com\android\server\am\AppErrors.java
private void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
            int callingPid, int callingUid) {

...
        if (r != null) {
            mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
                    PackageWatchdog.FAILURE_REASON_APP_CRASH);

            synchronized (mService) {
                mService.mProcessList.noteAppKill(r, (crashInfo != null
                          && "Native crash".equals(crashInfo.exceptionClassName))
                          ? ApplicationExitInfo.REASON_CRASH_NATIVE
                          : ApplicationExitInfo.REASON_CRASH,
                          ApplicationExitInfo.SUBREASON_UNKNOWN,
                        "crash");
            }
        }
...
    }

这里就调用了PackageWatchdog的onPackageFailure方法,这里逻辑就不细讲了,主要是通过MonitoredPackage做一些crash信息的记录,不同的packageName保存到不同的MonitoredPackage对象中,并通过记录数量以及时间判断是否触发level级别处理

//QSSI.13\frameworks\base\services\core\java\com\android\server\PackageWatchdog.java
public void onPackageFailure(List<VersionedPackage> packages,
        @FailureReasons int failureReason) {
    if (packages == null) {
        Slog.w(TAG, "Could not resolve a list of failing packages");
        return;
    }
    mLongTaskHandler.post(() -> {
        synchronized (mLock) {
            if (mAllObservers.isEmpty()) {
                return;
            }
            boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH
                    || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
            if (requiresImmediateAction) {
                handleFailureImmediately(packages, failureReason);
            } else {
                for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
                    VersionedPackage versionedPackage = packages.get(pIndex);
                    // Observer that will receive failure for versionedPackage
                    PackageHealthObserver currentObserverToNotify = null;
                    int currentObserverImpact = Integer.MAX_VALUE;
                    MonitoredPackage currentMonitoredPackage = null;

                    // Find observer with least user impact
                    for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
                        ObserverInternal observer = mAllObservers.valueAt(oIndex);
                        PackageHealthObserver registeredObserver = observer.registeredObserver;
                        if (registeredObserver != null
                                && observer.onPackageFailureLocked(
                                versionedPackage.getPackageName())) {
                            MonitoredPackage p = observer.getMonitoredPackage(
                                    versionedPackage.getPackageName());
                            int mitigationCount = 1;
                            if (p != null) {
                                mitigationCount = p.getMitigationCountLocked() + 1;
                            }
                            int impact = registeredObserver.onHealthCheckFailed(
                                    versionedPackage, failureReason, mitigationCount);
                            if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
                                    && impact < currentObserverImpact) {
                                currentObserverToNotify = registeredObserver;
                                currentObserverImpact = impact;
                                currentMonitoredPackage = p;
                            }
                        }
                    }

                    // Execute action with least user impact
                    if (currentObserverToNotify != null) {
                        int mitigationCount = 1;
                        if (currentMonitoredPackage != null) {
                            currentMonitoredPackage.noteMitigationCallLocked();
                            mitigationCount =
                                    currentMonitoredPackage.getMitigationCountLocked();
                        }
                        currentObserverToNotify.execute(versionedPackage,
                                failureReason, mitigationCount);
                    }
                }
            }
        }
    });
}

我们再继续看PowerManager 的reboot操作

//TQSSI.13\frameworks\base\core\java\android\os\PowerManager.java
@RequiresPermission(permission.REBOOT)
public void reboot(@Nullable String reason) {
    if (REBOOT_USERSPACE.equals(reason) && !isRebootingUserspaceSupported()) {
        throw new UnsupportedOperationException(
                "Attempted userspace reboot on a device that doesn't support it");
    }
    try {
        mService.reboot(false, reason, true);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
//QSSI.13\frameworks\base\services\core\java\com\android\server\power\PowerManagerService.java
@Override // Binder call
public void reboot(boolean confirm, @Nullable String reason, boolean wait) {
    mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
    if (PowerManager.REBOOT_RECOVERY.equals(reason)
            || PowerManager.REBOOT_RECOVERY_UPDATE.equals(reason)) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
    }

    ShutdownCheckPoints.recordCheckPoint(Binder.getCallingPid(), reason);
    final long ident = Binder.clearCallingIdentity();
    try {
        shutdownOrRebootInternal(HALT_MODE_REBOOT, confirm, reason, wait);
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}

private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
        @Nullable final String reason, boolean wait) {
    ...
    if (mHandler == null || !mSystemReady) {
        //判断是否为救援模式重启
        if (RescueParty.isAttemptingFactoryReset()) {
            // If we're stuck in a really low-level reboot loop, and a
            // rescue party is trying to prompt the user for a factory data
            // reset, we must GET TO DA CHOPPA!
            // No check point from ShutdownCheckPoints will be dumped at this state.
            PowerManagerService.lowLevelReboot(reason);
        } else {
            throw new IllegalStateException("Too early to call shutdown() or reboot()");
        }
    }

    ...
}

public static void lowLevelReboot(String reason) {
    if (reason == null) {
        reason = "";
    }

    ...

    if (reason.equals(PowerManager.REBOOT_RECOVERY)
            || reason.equals(PowerManager.REBOOT_RECOVERY_UPDATE)) {
        reason = "recovery";
    }

    ....
    //这里我们看到设置了属性:sys.powerctl:reboot recovery
    SystemProperties.set("sys.powerctl", "reboot," + reason);
    try {
        Thread.sleep(20 * 1000L);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    Slog.wtf(TAG, "Unexpected return from lowLevelReboot!");
}

此后init进程会收到该属性的变化

//QSSI.13/system/core/init/property_service.cpp
// This returns one of the enum of PROP_SUCCESS or PROP_ERROR*.
uint32_t HandlePropertySet(const std::string& name, const std::string& value,
                           const std::string& source_context, const ucred& cr,
                           SocketConnection* socket, std::string* error) {
    if (auto ret = CheckPermissions(name, value, source_context, cr, error); ret != PROP_SUCCESS) {
        return ret;
    }

    if (StartsWith(name, "ctl.")) {
        return SendControlMessage(name.c_str() + 4, value, cr.pid, socket, error);
    }

    // sys.powerctl is a special property that is used to make the device reboot.  We want to log
    // any process that sets this property to be able to accurately blame the cause of a shutdown.
    if (name == "sys.powerctl") {
        std::string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
        std::string process_cmdline;
        std::string process_log_string;
        if (ReadFileToString(cmdline_path, &process_cmdline)) {
            // Since cmdline is null deliminated, .c_str() conveniently gives us just the process
            // path.
            process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
        }
        LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                  << process_log_string;
        if (!value.empty()) {
            DebugRebootLogging();
        }
        if (value == "reboot,userspace" && !is_userspace_reboot_supported().value_or(false)) {
            *error = "Userspace reboot is not supported by this device";
            return PROP_ERROR_INVALID_VALUE;
        }
    }

...
}
//QSSI.13/system/core/init/init.cpp
void DebugRebootLogging() {
    LOG(INFO) << "sys.powerctl: do_shutdown: " << shutdown_state.do_shutdown()
              << " IsShuttingDown: " << IsShuttingDown();
    if (shutdown_state.do_shutdown()) {
        LOG(ERROR) << "sys.powerctl set while a previous shutdown command has not been handled";
        UnwindMainThreadStack();
    }
    if (IsShuttingDown()) {
        LOG(ERROR) << "sys.powerctl set while init is already shutting down";
        UnwindMainThreadStack();
    }
}

继而都会出现如下日志:

05-06 09:48:26.674   266   266 I init    : sys.powerctl: do_shutdown: 0 IsShuttingDown: 0
05-06 09:48:26.675     1     1 I init    : Got shutdown_command 'reboot,recovery' Calling HandlePowerctlMessage()
05-06 09:48:26.687     1     1 I init    : Clear action queue and start shutdown trigger
05-06 09:48:26.687     1     1 I init    : Entering shutdown mode

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值