Подробное объяснение принципа заморозки приложений в Android (1): версия R.

В зависимости от версии: Android R.

0. Предисловие

В двух предыдущих публикациях блога «Оптимизация повторного использования памяти приложений в Android (1)» и  «Оптимизация повторного использования памяти приложений в Android (2)» подробно анализировался процесс оптимизации памяти приложений в Android. Управление этим механизмом осуществляется через класс CachedAppOptimizer , почему он называется именно так, а не AppCompact? Об этом также упоминалось в двух предыдущих сообщениях блога, поскольку этот класс также управляет важной функцией: заморозкой , оптимизацией процессов приложения, которые находятся в состоянии кэширования в течение длительного времени.

В этой статье мы продолжим анализ еще одной функции класса CachedAppOptimizer — заморозки.

1. Триггер морозильной камеры

Функция OomAdjuster.applyOomAdjLocked () подробно анализируется в « Принципе обновления Android oom_adj (2)» . После изменения oom_adj она будет пересчитана и затем применена. В этой функции вызывается updateAppFreezeStateLocked(app), чтобы подтвердить, процесс заморожен.

1.1 обновлениеAppFreezeStateLocked()

frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java

    void updateAppFreezeStateLocked(ProcessRecord app) {
        // 确定该功能是使能的
        if (!mCachedAppOptimizer.useFreezer()) {
            return;
        }

        // 如果该进程处于 frozen状态,但sholudNoFreeze变为true,需要解冻
        if (app.frozen && app.shouldNotFreeze) {
            mCachedAppOptimizer.unfreezeAppLocked(app);
        }

        // 如果该进程的 adj处于 CACHED,并且可以冻结,则调用 freezeAppAsync() 冻结
        // 如果该进程的 adj离开 CACHED,则解冻
        if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen && !app.shouldNotFreeze) {
            mCachedAppOptimizer.freezeAppAsync(app);
        } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.frozen) {
            mCachedAppOptimizer.unfreezeAppLocked(app);
        }
    }

Прежде чем приступить к анализу морозильной камеры, сначала определите состояние морозильной камеры с помощью сработавшего кода, а также возникновение зависаний и разморозок.

Здесь есть три основные части:

  • Подтвердите, включена ли морозильная камера;
  • Изменился ли статус процесса и был ли он заморожен, но статус процесса изменился на mustNotFreeze, и в данный момент его необходимо разморозить. Но логику здесь, очевидно, немного сложно понять: разморозка выполнена, так зачем продолжать следующий процесс? К счастью, эта логическая проблема решена в Android S;
  • В зависимости от того, находится ли настройка приложения в настоящее время в кэше и заморожена, определите, следует ли вводить процесс заморозки или процесс разморозки;

2. CachedAppOptimizer.init()

Для вызова конструкции CachedAppOptimizer и процесса запуска функции init() вы можете обратиться к разделам 1 и 2  статьи «Оптимизация повторного использования памяти приложений в Android (1)» .

frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java

    public void init() {
        ...
        synchronized (mPhenotypeFlagLock) {
            ...
            updateUseFreezer();
        }
    }

Обновление 2.1UseFreezer()

    private void updateUseFreezer() {
        // 获取settings 中属性 cached_apps_freezer 的值,根据属性值初始化变量mUseFreezer
        final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(),
                Settings.Global.CACHED_APPS_FREEZER_ENABLED);

        if ("disabled".equals(configOverride)) {
            mUseFreezer = false;
        } else if ("enabled".equals(configOverride)
                || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                    KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
            mUseFreezer = isFreezerSupported();
        }

        if (mUseFreezer && mFreezeHandler == null) {
            Slog.d(TAG_AM, "Freezer enabled");
            enableFreezer(true);

            if (!mCachedAppOptimizerThread.isAlive()) {
                mCachedAppOptimizerThread.start();
            }

            mFreezeHandler = new FreezeHandler();

            Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
                    Process.THREAD_GROUP_SYSTEM);
        } else {
            enableFreezer(false);
        }
    }

Сначала убедитесь, что функция заморозки включена. Для иллюстрации будет понятнее использовать блок-схему:

Когда морозильная камера включена:

  • Чтобы включить его, вызовите  метод EnableFreezer() . Подробное описание процесса см. в разделе 4 ;
  • Если ServiceThread в CachedAppOptimizer не запущен, запустите его;
  • Создайте бесплатный обработчик для обработки сообщений, связанных с заморозкой;
  • Установите приоритет ServiceThread на THREAD_GROUP_SYSTEM;

3. Знакомство с контрольными группами

cgroups (полное имя: control groups ) — это механизм, предоставляемый ядром Linux, который может ограничивать ресурсы, используемые одним процессом или несколькими процессами, а также обеспечивать более точный контроль над такими ресурсами, как процессор и память. Docker, легкий контейнер, который в настоящее время становится все более популярным, использует возможности ограничения ресурсов, предоставляемые cgroups, для полного контроля ресурсов ЦП, памяти и других отделов.

cgroups определяет подсистему для каждого управляемого ресурса.Типичные подсистемы следующие:

  • процессор: в основном ограничивает использование процессора процессом;
  • cpuaat: может подсчитывать отчеты об использовании процессора процессами в контрольных группах;
  • cpuset: вы можете выделить отдельные узлы процессора или узлы памяти для процессов в контрольных группах;
  • память: вы можете ограничить использование памяти процессом;
  • blkio: блокировать io-устройство, которое может ограничивать процессы;
  • устройства: вы можете контролировать возможность доступа процесса к определенным устройствам;
  • замораживатель: может приостанавливать или возобновлять процессы в контрольных группах;
  • net_cls: сетевые пакеты процессов в cgroups можно помечать, а затем использовать модуль tc (управление трафиком) для управления пакетами;
  • ns: позволяет процессам в разных контрольных группах использовать разные пространства имен;

В Android Q или выше уровень абстракции cgroup используется через профили задач.Профили задач можно использовать для описания ограничений набора или наборов, которые применяются к потоку или процессу. Система выбирает одну или несколько подходящих контрольных групп, указанных в профилях задач. Это ограничение позволяет вносить изменения в базовый набор функций cgroup, не затрагивая более высокие уровни программного обеспечения.

Для получения более подробной информации ознакомьтесь с сообщением в блоге: «Подробное объяснение уровня абстракции cgroup в Android».

Многие важные функции этой статьи реализованы с помощью профилей.

4.  включитьФризер()

Главное — вызвать функцию EnableFreezerInternal():

    private static native void enableFreezerInternal(boolean enable);
frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp

static void com_android_server_am_CachedAppOptimizer_enableFreezerInternal(
        JNIEnv *env, jobject clazz, jboolean enable) {
    bool success = true;

    if (enable) {
        success = SetTaskProfiles(0, {"FreezerEnabled"}, true);
    } else {
        success = SetTaskProfiles(0, {"FreezerDisabled"}, true);
    }

    if (!success) {
        jniThrowException(env, "java/lang/RuntimeException", "Unknown error");
    }
}

Запишите определенное значение в соответствующий узел  через интерфейс SetTaskProfiles() . Для получения подробной информации вы можете просмотреть сообщение в блоге: «Подробное объяснение уровня абстракции cgroup в Android».

5. заморозитьAppAsync()

Параметр имеет тип ProcessRecord, который соответствует соответствующему процессу.

    void freezeAppAsync(ProcessRecord app) {
        mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);

        mFreezeHandler.sendMessageDelayed(
                mFreezeHandler.obtainMessage(
                    SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
                FREEZE_TIMEOUT_MS);
    }

Уведомление,

  • Если процесс уже отправил запрос на заморозку, при повторной отправке запроса сначала будет отменено исходное сообщение;
  • Отправьте сообщение SET_FROZEN_PROCESS_MSG, чтобы запросить заморозку;
  • Длина задержки обработки сообщения FREEZE_TIMEOUT_MS (10 мин.) Если сообщение, которое замораживает процесс, не было отменено через 10 минут, то будет запущен процесс заморозки процесса;

5.1 заморозить обработку сообщений

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SET_FROZEN_PROCESS_MSG:
                    freezeProcess((ProcessRecord) msg.obj);
                    break;
                case REPORT_UNFREEZE_MSG:
                    int pid = msg.arg1;
                    int frozenDuration = msg.arg2;
                    String processName = (String) msg.obj;

                    reportUnfreeze(pid, frozenDuration, processName);
                    break;
                default:
                    return;
            }
        }

Для заморозки есть два сообщения: сообщение REPORT_UNFREEZE_MSG записывается после разморозки.

В этой статье основное внимание уделяется обработке сообщений о замораживании. Здесь мы видим, что наконец вызывается функция FreezeProcess() . Подробности см. в следующем разделе.

5.2 Процесс замораживания()

        private void freezeProcess(ProcessRecord proc) {
            final int pid = proc.pid;
            final String name = proc.processName;
            final long unfrozenDuration;
            final boolean frozen;

            try {
                // 确认进程是否存在任意的文件锁,避免不必要的 free/unfreeze操作
                //   这是为了防止冻结进程持有文件锁,从而引起死锁
                //   冻结成功之后,还会再次确认文件锁,如果有锁,则立即解冻
                if (Process.hasFileLocks(pid)) {
                    if (DEBUG_FREEZER) {
                        Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
                    }
                    return;
                }
            } catch (Exception e) {
                Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
                        + "): " + e);
                return;
            }

            // 使用ASM做锁,在S 版本中换成专一的锁
            synchronized (mAm) {
                // 如果进程没有变成Cached或者不能freeze,则退出此次freeze操作
                if (proc.curAdj < ProcessList.CACHED_APP_MIN_ADJ
                        || proc.shouldNotFreeze) {
                    return;
                }

                // 已经处于frozen,或者不是一个应用进程,则退出此次 freeze操作
                //   pid为0,有可能进程还没有launch完成,或者进程被kill了
                if (pid == 0 || proc.frozen) {
                    return;
                }

                long unfreezeTime = proc.freezeUnfreezeTime;

                // 核心函数setProcessFrozen(),同步冻结进程
                try {
                    Process.setProcessFrozen(pid, proc.uid, true);

                    proc.freezeUnfreezeTime = SystemClock.uptimeMillis();
                    proc.frozen = true;
                } catch (Exception e) {
                    Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
                }

                unfrozenDuration = proc.freezeUnfreezeTime - unfreezeTime;
                frozen = proc.frozen;
            }

            if (!frozen) {
                return;
            }

            //此次freeze记录到event log
            EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);

            // 这里逻辑应该是颠倒了,先冻结进程,再去冻结binder,逻辑上不通
            //   在 S 版本中已经将freeBinder提前到 process冻结之前
            try {
                freezeBinder(pid, true);
            } catch (RuntimeException e) {
                Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                proc.kill("Unable to freeze binder interface",
                        ApplicationExitInfo.REASON_OTHER,
                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
            }

            // See above for why we're not taking mPhenotypeFlagLock here
            if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
                FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
                        FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
                        pid,
                        name,
                        unfrozenDuration);
            }

            try {
                // 再次check文件锁,如果该冻结进程持有文件锁,立即unfreeze
                if (Process.hasFileLocks(pid)) {
                    synchronized (mAm) {
                        unfreezeAppLocked(proc);
                    }
                }
            } catch (Exception e) {
                Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
                synchronized (mAm) {
                    unfreezeAppLocked(proc);
                }
            }
        }

5.2.1 setProcessFrozen()

frameworks/base/core/java/android/os/Process.java

    public static final native void setProcessFrozen(int pid, int uid, boolean frozen);
frameworks/base/core/jni/android_util_Process.cpp

void android_os_Process_setProcessFrozen(
        JNIEnv *env, jobject clazz, jint pid, jint uid, jboolean freeze)
{
    bool success = true;

    if (freeze) {
        success = SetProcessProfiles(uid, pid, {"Frozen"});
    } else {
        success = SetProcessProfiles(uid, pid, {"Unfrozen"});
    }

    if (!success) {
        signalExceptionForGroupError(env, EINVAL, pid);
    }
}

Вызов здесь был проанализирован в  сообщении блога «cgroup Abstraction Layer» . Через интерфейс SetProcessProfiles() он представляет собой профиль типа SetCgroupAction  и, наконец, вызывает ExecuteForProcess():

system/core/libprocessgroup/task_profiles.cpp
 
bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
    std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC)));
    if (tmp_fd < 0) {
        PLOG(WARNING) << "Failed to open " << procs_path;
        return false;
    }
    if (!AddTidToCgroup(pid, tmp_fd)) {
        LOG(ERROR) << "Failed to add task into cgroup";
        return false;
    }
 
    return true;
}

С помощью функции сначала получите путь, который необходимо изменить для профиля, через интерфейс GetProcsFilePath() контроллера. Параметром является путь, настроенный для профиля:

system/core/libprocessgroup/cgroup_map.cpp
std::string CgroupController::GetProcsFilePath(const std::string& rel_path, uid_t uid,
                                               pid_t pid) const {
    std::string proc_path(path());
    proc_path.append("/").append(rel_path);
    proc_path = regex_replace(proc_path, std::regex("<uid>"), std::to_string(uid));
    proc_path = regex_replace(proc_path, std::regex("<pid>"), std::to_string(pid));
 
    return proc_path.append(CGROUP_PROCS_FILE);
}

Последний записанный файл — CGROUP_PROCS_FILE, который представляет собой  файл cgroup.procs  .

6. разморозитьAppLocked()

    void unfreezeAppLocked(ProcessRecord app) {
        // 首先,取消之前该进程的冻结请求
        mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);

        // 如果进程还没有冻结,则无需做解冻处理
        if (!app.frozen) {
            return;
        }

        /********进程处于冻结,进行解冻处理*********/
        boolean processKilled = false;

        // 冻住的进程可以接收异步binder请求,但是不会处理,只是放入binder buffer, 过多的请求会导致buffer耗尽;
        // 这里需要确认下该进程在解冻之前,进程是否在冰冻期间收到同步的binder 请求,有则kill该进程
        try {
            int freezeInfo = getBinderFreezeInfo(app.pid);

            if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
                        + " received sync transactions while frozen, killing");
                app.kill("Sync transaction while in frozen state",
                        ApplicationExitInfo.REASON_OTHER,
                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
                processKilled = true;
            }

            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
                        + " received async transactions while frozen");
            }
        } catch (Exception e) {
            Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + app.pid + " "
                    + app.processName + ". Killing it. Exception: " + e);
            app.kill("Unable to query binder frozen stats",
                    ApplicationExitInfo.REASON_OTHER,
                    ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
            processKilled = true;
        }

        //进程被kill 了,退出unfreeze
        if (processKilled) {
            return;
        }

        // app.freezeUnfreezeTime记录的是上次free、unfreeze的时间
        long freezeTime = app.freezeUnfreezeTime;

        try {
            freezeBinder(app.pid, false);
        } catch (RuntimeException e) {
            Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName
                    + ". Killing it");
            app.kill("Unable to unfreeze",
                    ApplicationExitInfo.REASON_OTHER,
                    ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
            return;
        }

        try {
            Process.setProcessFrozen(app.pid, app.uid, false);

            app.freezeUnfreezeTime = SystemClock.uptimeMillis();
            app.frozen = false;
        } catch (Exception e) {
            Slog.e(TAG_AM, "Unable to unfreeze " + app.pid + " " + app.processName
                    + ". This might cause inconsistency or UI hangs.");
        }

        if (!app.frozen) {
            if (DEBUG_FREEZER) {
                Slog.d(TAG_AM, "sync unfroze " + app.pid + " " + app.processName);
            }

            mFreezeHandler.sendMessage(
                    mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
                        app.pid,
                        (int) Math.min(app.freezeUnfreezeTime - freezeTime, Integer.MAX_VALUE),
                        app.processName));
        }
    }

Основная функция обработки — setProcessFrozen(), подробный процесс был проанализирован в разделе 5.2.1 выше. Главное, на что следует обратить внимание — разморозка в Task_profiles.json:

    {
      "Name": "Frozen",
      "Actions": [
        {
          "Name": "JoinCgroup",
          "Params":
          {
            "Controller": "freezer",
            "Path": ""
          }
        }
      ]
    },
    {
      "Name": "Unfrozen",
      "Actions": [
        {
          "Name": "JoinCgroup",
          "Params":
          {
            "Controller": "freezer",
            "Path": "../"
          }
        }
      ]
    },

В отличие от Frozen, путь Unfrozen — это каталог верхнего уровня, то есть окончательная модификация — sys/fs/cgroup/cgroup.procs , а Frozen изменяет sys/fs/cgroup/freezer/cgroup.procs.

На этом этапе с морозилкой в ​​версии Android R все готово.

Из логического анализа этой статьи мы видим, что в Android R все еще существуют некоторые логические проблемы. Благодаря этой статье мы получили общее представление об общем процессе работы замораживателя и интерфейсе использования уровня абстракции cgroup. поскольку версия нестабильна, мы не будем здесь слишком подробно анализировать реализацию в ядре.

Наконец, вот карта пути замороженного приложения:

 

 

Связанные сообщения в блоге:

https://justinwei.blog.csdn.net/article/details/131974011

https://justinwei.blog.csdn.net/article/details/131854291 

https://justinwei.blog.csdn.net/article/details/131769953

https://justinwei.blog.csdn.net/article/details/131591931

https://justinwei.blog.csdn.net/article/details/131717028

https://justinwei.blog.csdn.net/article/details/131685304

https://justinwei.blog.csdn.net/article/details/131595511

Supongo que te gusta

Origin blog.csdn.net/jingerppp/article/details/131845360
Recomendado
Clasificación